diff --git a/.commitlintrc.yml b/.commitlintrc.yml new file mode 100644 index 0000000..320a584 --- /dev/null +++ b/.commitlintrc.yml @@ -0,0 +1,21 @@ +extends: + - '@commitlint/config-conventional' + +rules: + # Allow slightly longer subjects; we have descriptive messages. + header-max-length: [2, always, 100] + # Enforce lowercase type (feat, fix, ...) and allow common scopes. + type-enum: + - 2 + - always + - - build + - chore + - ci + - docs + - feat + - fix + - perf + - refactor + - revert + - style + - test \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ef1faa..1dc0722 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,28 +10,60 @@ on: - main - subspaces +env: + CARGO_TERM_COLOR: always + jobs: - run-tests: + test: + name: Test runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: cargo test --verbose - - name: Set up Rust - uses: actions-rs/toolchain@v1 + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - profile: minimal - override: true + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + - run: cargo fmt --all -- --check + - run: cargo clippy --all-targets --all-features -- -D warnings - - name: Cache cargo registry - uses: actions/cache@v3 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo-registry- + docs: + name: Docs + runs-on: ubuntu-latest + env: + RUSTDOCFLAGS: -D warnings + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: cargo doc --no-deps --all-features + + msrv: + name: MSRV (1.88) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@1.88.0 + - uses: Swatinem/rust-cache@v2 + - run: cargo check --all-features - - name: Run tests - run: cargo test + no-std: + name: no_std build (${{ matrix.crate }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + crate: [borsh_utils, spaces_protocol, spaces_nums, sip7] + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: cargo check -p ${{ matrix.crate }} --no-default-features \ No newline at end of file diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml new file mode 100644 index 0000000..57918ab --- /dev/null +++ b/.github/workflows/commitlint.yml @@ -0,0 +1,16 @@ +name: Commitlint + +on: + pull_request: + types: [opened, reopened, edited, synchronize] + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: wagoid/commitlint-github-action@v6 + with: + configFile: .commitlintrc.yml \ No newline at end of file diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml new file mode 100644 index 0000000..1a0548d --- /dev/null +++ b/.github/workflows/release-plz.yml @@ -0,0 +1,49 @@ +name: Release-plz + +permissions: + pull-requests: write + contents: write + +on: + push: + branches: [subspaces] + +jobs: + # Opens / updates the "release PR" that bumps versions and edits the CHANGELOG. + release-plz-pr: + name: Release-plz PR + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'buffrr' }} + concurrency: + group: release-plz-${{ github.ref }} + cancel-in-progress: false + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: dtolnay/rust-toolchain@stable + - name: Run release-plz + uses: release-plz/action@v0.5 + with: + command: release-pr + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + + # Tags + publishes to crates.io when a release commit lands on main. + release-plz-release: + name: Release-plz publish + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'buffrr' }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: dtolnay/rust-toolchain@stable + - name: Run release-plz + uses: release-plz/action@v0.5 + with: + command: release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7be1853..fbef3bb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,9 +1,9 @@ -name: Build binaries and create release +name: Build binaries and upload to release on: push: tags: - - 'v*.*.*' + - 'spaces_client-v*' jobs: release: @@ -24,10 +24,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Get the tag name + - name: Extract version from tag id: get_tag - run: | + run: | + # tag format: spaces_client-v0.1.0 → VERSION=0.1.0, TAG= echo "TAG=${GITHUB_REF##*/}" >> $GITHUB_ENV + echo "VERSION=${GITHUB_REF##*-v}" >> $GITHUB_ENV - name: Set up Rust uses: actions-rust-lang/setup-rust-toolchain@v1 @@ -44,7 +46,7 @@ jobs: env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc run: | - cargo build --release --target ${{ matrix.target }} + cargo build --release --target ${{ matrix.target }} -p spaces_client - name: Get OS and architecture run: | @@ -53,23 +55,16 @@ jobs: - name: Create release archive run: | - mkdir -p spaces-${{ env.TAG }}-${{ env.OS }}-${{ env.ARCH }} - cp target/${{ matrix.target }}/release/spaced spaces-${{ env.TAG }}-${{ env.OS }}-${{ env.ARCH }}/spaced - cp target/${{ matrix.target }}/release/space-cli spaces-${{ env.TAG }}-${{ env.OS }}-${{ env.ARCH }}/space-cli - tar -czf spaces-${{ env.TAG }}-${{ env.OS }}-${{ env.ARCH }}.tar.gz spaces-${{ env.TAG }}-${{ env.OS }}-${{ env.ARCH }} + mkdir -p spaces-v${{ env.VERSION }}-${{ env.OS }}-${{ env.ARCH }} + cp target/${{ matrix.target }}/release/spaced spaces-v${{ env.VERSION }}-${{ env.OS }}-${{ env.ARCH }}/spaced + cp target/${{ matrix.target }}/release/space-cli spaces-v${{ env.VERSION }}-${{ env.OS }}-${{ env.ARCH }}/space-cli + tar -czf spaces-v${{ env.VERSION }}-${{ env.OS }}-${{ env.ARCH }}.tar.gz spaces-v${{ env.VERSION }}-${{ env.OS }}-${{ env.ARCH }} - - name: Create GitHub Release - id: create_release + - name: Upload artifacts to existing release uses: softprops/action-gh-release@v2 with: - tag_name: ${{ env.TAG }} # Dynamically use the pushed tag - name: Release ${{ env.TAG }} # Use the tag for the release name - body: | - Spaces release of version ${{ env.TAG }}. - draft: false - prerelease: false - files: | - spaces-${{ env.TAG }}-${{ env.OS }}-${{ env.ARCH }}.tar.gz - make_latest: true + tag_name: ${{ env.TAG }} + files: | + spaces-v${{ env.VERSION }}-${{ env.OS }}-${{ env.ARCH }}.tar.gz env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a422362..2e2ce84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,9 +175,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] -name = "bdk_chain" +name = "bdk_chain_backport" version = "0.21.0" -source = "git+https://github.com/buffrr/bdk.git?rev=43bca8643dec6fdda99e4a29bf88709729af349e#43bca8643dec6fdda99e4a29bf88709729af349e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d580eb55991b7a2415d71d195935f4f88b138257a5e7756724d77974c2f15153" dependencies = [ "bdk_core", "bitcoin", @@ -188,8 +189,9 @@ dependencies = [ [[package]] name = "bdk_core" -version = "0.4.0" -source = "git+https://github.com/buffrr/bdk.git?rev=43bca8643dec6fdda99e4a29bf88709729af349e#43bca8643dec6fdda99e4a29bf88709729af349e" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b545aea1efc090e4f71f1dd5468090d9f54c3de48002064c04895ef811fbe0b2" dependencies = [ "bitcoin", "hashbrown 0.14.5", @@ -197,11 +199,12 @@ dependencies = [ ] [[package]] -name = "bdk_wallet" +name = "bdk_wallet_backport" version = "1.0.0-beta.6" -source = "git+https://github.com/buffrr/bdk.git?rev=43bca8643dec6fdda99e4a29bf88709729af349e#43bca8643dec6fdda99e4a29bf88709729af349e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf2fadfbc496ddc97a19bc99196abafe6d393eb0f6e51f36aa6cf93b9aeb8db" dependencies = [ - "bdk_chain", + "bdk_chain_backport", "bip39", "bitcoin", "miniscript", @@ -355,9 +358,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block-buffer" @@ -466,9 +469,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.57" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -500,9 +503,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -522,9 +525,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -741,9 +744,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "filetime" @@ -980,9 +983,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "hashlink" @@ -1143,9 +1146,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -1156,7 +1159,6 @@ dependencies = [ "httparse", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -1180,19 +1182,18 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.4.0", - "hyper 1.8.1", + "hyper 1.9.0", "hyper-util", - "rustls 0.23.37", - "rustls-pki-types", + "rustls 0.23.38", "tokio", "tokio-rustls 0.26.4", "tower-service", - "webpki-roots 1.0.6", + "webpki-roots 1.0.7", ] [[package]] @@ -1207,7 +1208,7 @@ dependencies = [ "futures-util", "http 1.4.0", "http-body 1.0.1", - "hyper 1.8.1", + "hyper 1.9.0", "ipnet", "libc", "percent-encoding", @@ -1220,12 +1221,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1233,9 +1235,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1246,9 +1248,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1260,15 +1262,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1280,15 +1282,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1328,12 +1330,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -1355,9 +1357,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e7418f59cc01c88316161279a7f665217ae316b388e58a0d10e29f54f1e5eb" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -1411,10 +1413,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -1546,20 +1550,20 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ "bitflags", "libc", "plain", - "redox_syscall 0.7.3", + "redox_syscall 0.7.4", ] [[package]] @@ -1587,9 +1591,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -1654,9 +1658,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", @@ -1671,9 +1675,9 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-traits" @@ -1813,17 +1817,11 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plain" @@ -1839,18 +1837,18 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -1961,8 +1959,8 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", - "rustls 0.23.37", + "rustc-hash 2.1.2", + "rustls 0.23.38", "socket2 0.6.3", "thiserror 2.0.18", "tokio", @@ -1979,10 +1977,10 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", - "rustc-hash 2.1.1", - "rustls 0.23.37", + "rustc-hash 2.1.2", + "rustls 0.23.38", "rustls-pki-types", "slab", "thiserror 2.0.18", @@ -2039,9 +2037,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -2096,9 +2094,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ "bitflags", ] @@ -2157,15 +2155,15 @@ dependencies = [ "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.8.1", - "hyper-rustls 0.27.7", + "hyper 1.9.0", + "hyper-rustls 0.27.9", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.37", + "rustls 0.23.38", "rustls-pki-types", "serde", "serde_json", @@ -2180,7 +2178,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.6", + "webpki-roots 1.0.7", ] [[package]] @@ -2225,9 +2223,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustix" @@ -2269,15 +2267,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.10", + "rustls-webpki 0.103.12", "subtle", "zeroize", ] @@ -2325,9 +2323,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" dependencies = [ "ring", "rustls-pki-types", @@ -2441,9 +2439,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -2564,9 +2562,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "sip7" @@ -2629,8 +2627,9 @@ dependencies = [ [[package]] name = "spacedb" -version = "0.0.12" -source = "git+https://github.com/spacesprotocol/spacedb#43f23e78e4e5fffb8d89661a4b7f39ab43a5a644" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a48cda82e951391df9d0a54c96f8b04e117ff39abad5359b181cb71c80798d77" dependencies = [ "bincode", "borsh", @@ -2661,7 +2660,7 @@ dependencies = [ [[package]] name = "spaces_client" -version = "0.0.7" +version = "0.1.0" dependencies = [ "anyhow", "assert_cmd", @@ -2712,7 +2711,7 @@ dependencies = [ [[package]] name = "spaces_protocol" -version = "0.0.7" +version = "0.1.0" dependencies = [ "bitcoin", "borsh", @@ -2723,7 +2722,7 @@ dependencies = [ [[package]] name = "spaces_testutil" -version = "0.0.1" +version = "0.1.0" dependencies = [ "anyhow", "assert_cmd", @@ -2740,10 +2739,10 @@ dependencies = [ [[package]] name = "spaces_wallet" -version = "0.0.7" +version = "0.1.0" dependencies = [ "anyhow", - "bdk_wallet", + "bdk_wallet_backport", "bech32", "bitcoin", "borsh", @@ -2938,9 +2937,9 @@ checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -2963,9 +2962,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -2980,9 +2979,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -3005,7 +3004,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.37", + "rustls 0.23.38", "tokio", ] @@ -3037,18 +3036,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.8+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap", "toml_datetime", @@ -3058,9 +3057,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] @@ -3218,11 +3217,11 @@ dependencies = [ "flate2", "log", "percent-encoding", - "rustls 0.23.37", + "rustls 0.23.38", "rustls-pki-types", "ureq-proto", "utf8-zero", - "webpki-roots 1.0.6", + "webpki-roots 1.0.7", ] [[package]] @@ -3329,9 +3328,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -3342,23 +3341,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3366,9 +3361,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -3379,9 +3374,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -3422,9 +3417,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -3448,9 +3443,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -3706,9 +3701,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ "memchr", ] @@ -3803,9 +3798,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "xattr" @@ -3819,9 +3814,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -3830,9 +3825,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -3842,18 +3837,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -3862,18 +3857,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -3889,9 +3884,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -3900,9 +3895,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -3911,9 +3906,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 5b46dc5..a907d09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,12 +2,31 @@ resolver = "2" members = ["borsh_utils", "checkpoint", "client", "protocol", "testutil", "wallet", "nums", "sip7"] +[workspace.package] +version = "0.1.0" +edition = "2024" +rust-version = "1.88" +license = "MIT" +repository = "https://github.com/buffrr/spaces" +homepage = "https://spacesprotocol.org" +authors = ["Buffrr "] + [workspace.dependencies] +# Internal crates +borsh_utils = { path = "borsh_utils", version = "0.1.0", default-features = false } +spaces_protocol = { path = "protocol", version = "0.1.0", default-features = false } +spaces_nums = { path = "nums", version = "0.1.0", default-features = false } +sip7 = { path = "sip7", version = "0.1.0", default-features = false } +spaces_wallet = { path = "wallet", version = "0.1.0", default-features = false } +spaces_client = { path = "client", version = "0.1.0", default-features = false } +spaces_testutil = { path = "testutil", version = "0.1.0", default-features = false } + +# External anyhow = "1.0" hex = "0.4" serde_json = "1.0" tokio = { version = "1", features = ["full"] } -spacedb = { git = "https://github.com/spacesprotocol/spacedb", features = ["hash-idx"] } +spacedb = { version = "0.1.2", features = ["hash-idx"] } base64 = "0.22" rand = "0.8" log = "0.4" @@ -24,7 +43,8 @@ tower = "0.4" hyper = "0.14" secp256k1 = "0.29" bech32 = "0.11" -bdk_wallet = { git = "https://github.com/buffrr/bdk.git", rev = "43bca8643dec6fdda99e4a29bf88709729af349e", features = ["keys-bip39", "rusqlite"] } +# temporary backport of bdk for the double-spend issue until we migrate to bdk v3.0 +bdk_wallet = { package = "bdk_wallet_backport", version = "1.0.0-beta.6", features = ["keys-bip39", "rusqlite"] } # Dev/test dependencies assert_cmd = "2.0" diff --git a/borsh_utils/Cargo.toml b/borsh_utils/Cargo.toml index 1407b9f..f9a7f82 100644 --- a/borsh_utils/Cargo.toml +++ b/borsh_utils/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "borsh_utils" -version = "0.1.0" -edition = "2021" +description = "Borsh serialization helpers for Bitcoin primitives used by the Spaces protocol." +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true [dependencies] bitcoin = { version = "0.32.8", features = ["serde"], default-features = false } diff --git a/borsh_utils/src/lib.rs b/borsh_utils/src/lib.rs index 5696fac..9c787b5 100644 --- a/borsh_utils/src/lib.rs +++ b/borsh_utils/src/lib.rs @@ -9,8 +9,8 @@ extern crate alloc; use alloc::vec; use bitcoin::hashes::Hash; -use bitcoin::{secp256k1::schnorr, Amount, BlockHash, OutPoint, ScriptBuf, Txid}; -use borsh::{io, BorshDeserialize, BorshSerialize}; +use bitcoin::{Amount, BlockHash, OutPoint, ScriptBuf, Txid, secp256k1::schnorr}; +use borsh::{BorshDeserialize, BorshSerialize, io}; /// Serialize a Txid pub fn serialize_txid(txid: &Txid, writer: &mut W) -> io::Result<()> { diff --git a/checkpoint/Cargo.toml b/checkpoint/Cargo.toml index 16bf506..022932a 100644 --- a/checkpoint/Cargo.toml +++ b/checkpoint/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "spaces_checkpoint" -version = "0.1.0" -edition = "2024" +description = "Checkpoint loader and builder for the Spaces protocol." +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true [[bin]] name = "checkpoint-builder" @@ -20,7 +26,7 @@ serde_json = "1" anyhow = { version = "1", optional = true } clap = { version = "4", features = ["derive"], optional = true } spacedb = { workspace = true, optional = true } -spaces_protocol = { path = "../protocol", features = ["std"], optional = true } +spaces_protocol = { workspace = true, features = ["std"], optional = true } [dev-dependencies] tempfile = "3" diff --git a/checkpoint/src/builder.rs b/checkpoint/src/builder.rs index 2affb19..7bb7f27 100644 --- a/checkpoint/src/builder.rs +++ b/checkpoint/src/builder.rs @@ -1,8 +1,13 @@ +use clap::Parser; use std::path::{Path, PathBuf}; use std::process::Command; -use clap::Parser; -fn s3_cp(local: &Path, dest: &str, endpoint_url: Option<&str>, profile: Option<&str>) -> anyhow::Result<()> { +fn s3_cp( + local: &Path, + dest: &str, + endpoint_url: Option<&str>, + profile: Option<&str>, +) -> anyhow::Result<()> { eprintln!("Uploading to {}...", dest); let mut cmd = Command::new("aws"); cmd.args(["s3", "cp"]); @@ -16,7 +21,8 @@ fn s3_cp(local: &Path, dest: &str, endpoint_url: Option<&str>, profile: Option<& } let status = cmd.status().map_err(|e| { anyhow::anyhow!( - "failed to run `aws` CLI: {} — install from https://aws.amazon.com/cli/", e + "failed to run `aws` CLI: {} — install from https://aws.amazon.com/cli/", + e ) })?; if !status.success() { @@ -65,8 +71,11 @@ fn main() -> anyhow::Result<()> { let digest = spaces_checkpoint::build_checkpoint(&args.data_dir, &output)?; - eprintln!("\nArchive: {} ({} bytes)", output.display(), - std::fs::metadata(&output)?.len()); + eprintln!( + "\nArchive: {} ({} bytes)", + output.display(), + std::fs::metadata(&output)?.len() + ); eprintln!("SHA-256: {}", hex::encode(digest)); spaces_checkpoint::write_integrity(height, &block_hash, &digest)?; @@ -76,7 +85,12 @@ fn main() -> anyhow::Result<()> { let bucket = bucket.trim_end_matches('/'); // Upload the archive - s3_cp(&output, &format!("{}/{}", bucket, filename), args.endpoint_url.as_deref(), args.profile.as_deref())?; + s3_cp( + &output, + &format!("{}/{}", bucket, filename), + args.endpoint_url.as_deref(), + args.profile.as_deref(), + )?; // Write and upload latest.json let latest = spaces_checkpoint::Checkpoint { @@ -86,10 +100,15 @@ fn main() -> anyhow::Result<()> { }; let latest_path = args.output_dir.join("latest.json"); std::fs::write(&latest_path, serde_json::to_string_pretty(&latest)?)?; - s3_cp(&latest_path, &format!("{}/latest.json", bucket), args.endpoint_url.as_deref(), args.profile.as_deref())?; + s3_cp( + &latest_path, + &format!("{}/latest.json", bucket), + args.endpoint_url.as_deref(), + args.profile.as_deref(), + )?; eprintln!("Upload complete."); } Ok(()) -} \ No newline at end of file +} diff --git a/checkpoint/src/integrity.rs b/checkpoint/src/integrity.rs index ab60e9f..4492ea4 100644 --- a/checkpoint/src/integrity.rs +++ b/checkpoint/src/integrity.rs @@ -5,4 +5,4 @@ pub fn checkpoint() -> super::Checkpoint { block_hash: "000000000000000000012b40cac64a5fb5417cff15dc01461f99a76fc9643568".to_string(), digest: "e98c1999a3b98b517b174b3add778fb55f0fe63e0795e9f85952edc00a919f58".to_string(), } -} \ No newline at end of file +} diff --git a/checkpoint/src/lib.rs b/checkpoint/src/lib.rs index 8bcbc01..d7d2510 100644 --- a/checkpoint/src/lib.rs +++ b/checkpoint/src/lib.rs @@ -1,10 +1,10 @@ pub mod integrity; +use flate2::read::GzDecoder; +use sha2::{Digest, Sha256}; use std::fmt; use std::io::{Cursor, Read}; use std::path::Path; -use flate2::read::GzDecoder; -use sha2::{Sha256, Digest}; pub const CHECKPOINT_BASE_URL: &str = "https://checkpoints.spacesprotocol.org"; @@ -30,9 +30,10 @@ impl Checkpoint { let bytes = hex::decode(&self.digest) .map_err(|e| CheckpointError::Unavailable(format!("invalid digest hex: {}", e)))?; if bytes.len() != 32 { - return Err(CheckpointError::Unavailable( - format!("invalid digest length: {}", bytes.len()), - )); + return Err(CheckpointError::Unavailable(format!( + "invalid digest length: {}", + bytes.len() + ))); } let mut arr = [0u8; 32]; arr.copy_from_slice(&bytes); @@ -41,7 +42,11 @@ impl Checkpoint { /// Build the download URL for this checkpoint. pub fn url(&self, base_url: &str) -> String { - format!("{}/checkpoint-{}.tar.gz", base_url.trim_end_matches('/'), self.height) + format!( + "{}/checkpoint-{}.tar.gz", + base_url.trim_end_matches('/'), + self.height + ) } } @@ -53,7 +58,9 @@ pub fn fetch_latest(base_url: &str) -> Result, CheckpointErro Ok(r) => r, Err(_) => return Ok(None), }; - let body: String = response.into_body().read_to_string() + let body: String = response + .into_body() + .read_to_string() .map_err(|e| CheckpointError::Unavailable(e.to_string()))?; let latest: Checkpoint = serde_json::from_str(&body) .map_err(|e| CheckpointError::Unavailable(format!("invalid latest.json: {}", e)))?; @@ -80,8 +87,11 @@ impl fmt::Display for CheckpointError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { CheckpointError::Unavailable(msg) => write!(f, "checkpoint unavailable: {}", msg), - CheckpointError::DigestMismatch { expected, got } => - write!(f, "checkpoint hash mismatch: expected {}, got {}", expected, got), + CheckpointError::DigestMismatch { expected, got } => write!( + f, + "checkpoint hash mismatch: expected {}, got {}", + expected, got + ), CheckpointError::Extract(e) => write!(f, "failed to extract checkpoint: {}", e), CheckpointError::Io(e) => write!(f, "io error: {}", e), } @@ -121,7 +131,9 @@ pub fn ensure_checkpoint( let delay = std::time::Duration::from_secs(1 << attempt); tracing::warn!( "checkpoint download attempt {}/{} failed, retrying in {:?}...", - attempt, MAX_RETRIES, delay + attempt, + MAX_RETRIES, + delay ); std::thread::sleep(delay); } @@ -134,7 +146,8 @@ pub fn ensure_checkpoint( if attempt == MAX_RETRIES { tracing::warn!( "checkpoint unavailable after {} attempts: {} — falling back to full sync", - MAX_RETRIES + 1, e + MAX_RETRIES + 1, + e ); return Ok(false); } @@ -153,10 +166,13 @@ fn download_and_verify( ) -> Result, CheckpointError> { tracing::info!("downloading checkpoint from {}", url); - let response = ureq::get(url).call() + let response = ureq::get(url) + .call() .map_err(|e| CheckpointError::Unavailable(e.to_string()))?; - let total = response.headers().get("content-length") + let total = response + .headers() + .get("content-length") .and_then(|v| v.to_str().ok()) .and_then(|v| v.parse::().ok()) .unwrap_or(0); @@ -165,7 +181,8 @@ fn download_and_verify( let mut data = Vec::with_capacity(total as usize); let mut buf = [0u8; 64 * 1024]; loop { - let n = reader.read(&mut buf) + let n = reader + .read(&mut buf) .map_err(|e| CheckpointError::Unavailable(e.to_string()))?; if n == 0 { break; @@ -194,7 +211,9 @@ fn extract(target_dir: &Path, data: &[u8]) -> Result<(), CheckpointError> { tracing::info!("extracting checkpoint..."); let decoder = GzDecoder::new(Cursor::new(data)); let mut archive = tar::Archive::new(decoder); - archive.unpack(target_dir).map_err(CheckpointError::Extract)?; + archive + .unpack(target_dir) + .map_err(CheckpointError::Extract)?; tracing::info!("checkpoint extracted to {:?}", target_dir); Ok(()) } @@ -203,8 +222,8 @@ fn extract(target_dir: &Path, data: &[u8]) -> Result<(), CheckpointError> { /// by inspecting the latest snapshot metadata in root.sdb. #[cfg(feature = "cli")] pub fn read_tip(data_dir: &Path) -> anyhow::Result<(String, u32)> { - use spacedb::db::Database; use spacedb::Sha256Hasher; + use spacedb::db::Database; use spaces_protocol::constants::ChainAnchor; let db_path = data_dir.join("root.sdb"); @@ -214,14 +233,20 @@ pub fn read_tip(data_dir: &Path) -> anyhow::Result<(String, u32)> { let config = spacedb::Configuration::standard(); let db: Database = Database::open_with_config( - db_path.to_str().ok_or_else(|| anyhow::anyhow!("invalid path"))?, + db_path + .to_str() + .ok_or_else(|| anyhow::anyhow!("invalid path"))?, config, )?; - let snapshot = db.iter().next() + let snapshot = db + .iter() + .next() .ok_or_else(|| anyhow::anyhow!("no snapshots in root.sdb"))?; let snapshot = snapshot?; - let anchor: ChainAnchor = snapshot.metadata().try_into() + let anchor: ChainAnchor = snapshot + .metadata() + .try_into() .map_err(|_| anyhow::anyhow!("could not read snapshot metadata"))?; Ok((anchor.hash.to_string(), anchor.height)) @@ -230,12 +255,9 @@ pub fn read_tip(data_dir: &Path) -> anyhow::Result<(String, u32)> { /// Build a checkpoint archive from a spaced data directory. /// Returns the SHA-256 digest of the produced archive. #[cfg(feature = "cli")] -pub fn build_checkpoint( - data_dir: &Path, - output: &Path, -) -> anyhow::Result<[u8; 32]> { - use flate2::write::GzEncoder; +pub fn build_checkpoint(data_dir: &Path, output: &Path) -> anyhow::Result<[u8; 32]> { use flate2::Compression; + use flate2::write::GzEncoder; use std::fs::File; for name in CHECKPOINT_FILES { @@ -274,15 +296,35 @@ pub fn checkpoint() -> super::Checkpoint {{ digest: "{}".to_string(), }} }}"#, - height, block_hash, hex::encode(digest), + height, + block_hash, + hex::encode(digest), ) } +#[cfg(feature = "cli")] +const INTEGRITY_PATH: &str = "checkpoint/src/integrity.rs"; + +/// Write the checkpoint constant to integrity.rs. +/// Must be run from the workspace root. +#[cfg(feature = "cli")] +pub fn write_integrity(height: u32, block_hash: &str, digest: &[u8; 32]) -> anyhow::Result<()> { + let path = Path::new(INTEGRITY_PATH); + if !path.exists() { + anyhow::bail!( + "{} not found — run checkpoint-builder from the workspace root", + INTEGRITY_PATH + ); + } + std::fs::write(path, format_integrity_file(height, block_hash, digest))?; + Ok(()) +} + #[cfg(test)] mod tests { use super::*; - use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; + use std::sync::atomic::{AtomicU64, Ordering}; #[test] fn download_reports_progress() { @@ -307,10 +349,16 @@ mod tests { let applied = ensure_checkpoint(dir.path(), &url, &digest, Some(&*progress)).unwrap(); assert!(applied, "checkpoint should be applied"); - assert!(call_count.load(Ordering::SeqCst) > 1, "progress should be called multiple times"); + assert!( + call_count.load(Ordering::SeqCst) > 1, + "progress should be called multiple times" + ); let total = last_total.load(Ordering::SeqCst); - assert!(total > 0, "total bytes should be reported from content-length"); + assert!( + total > 0, + "total bytes should be reported from content-length" + ); let downloaded = last_downloaded.load(Ordering::SeqCst); assert_eq!(downloaded, total, "final downloaded should equal total"); @@ -321,21 +369,3 @@ mod tests { } } } - -#[cfg(feature = "cli")] -const INTEGRITY_PATH: &str = "checkpoint/src/integrity.rs"; - -/// Write the checkpoint constant to integrity.rs. -/// Must be run from the workspace root. -#[cfg(feature = "cli")] -pub fn write_integrity(height: u32, block_hash: &str, digest: &[u8; 32]) -> anyhow::Result<()> { - let path = Path::new(INTEGRITY_PATH); - if !path.exists() { - anyhow::bail!( - "{} not found — run checkpoint-builder from the workspace root", - INTEGRITY_PATH - ); - } - std::fs::write(path, format_integrity_file(height, block_hash, digest))?; - Ok(()) -} \ No newline at end of file diff --git a/client/Cargo.toml b/client/Cargo.toml index 502db51..45082a9 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "spaces_client" -version = "0.0.7" -edition = "2021" +description = "Spaces client (spaced) and CLI (space-cli) for the Spaces Protocol." +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true [[bin]] name = "space-cli" @@ -20,12 +26,12 @@ required-features = ["schema"] path = "src/lib.rs" [dependencies] -spaces_wallet = { path = "../wallet" } -spaces_protocol = { path = "../protocol", features = ["std"] } -spaces_nums= { path = "../nums", features = ["std"] } -sip7 = { path = "../sip7", features = ["serde"] } +spaces_wallet = { workspace = true } +spaces_protocol = { workspace = true, features = ["std"] } +spaces_nums = { workspace = true, features = ["std"] } +sip7 = { workspace = true, features = ["serde"] } spacedb = { workspace = true } -borsh_utils = { path = "../borsh_utils" } +borsh_utils = { workspace = true } tokio = { workspace = true, features = ["signal"] } anyhow = { workspace = true } @@ -56,4 +62,4 @@ schema = ["schemars"] [dev-dependencies] assert_cmd = { workspace = true } predicates = { workspace = true } -spaces_testutil = { path = "../testutil" } +spaces_testutil = { workspace = true } diff --git a/client/src/app.rs b/client/src/app.rs index dc219a2..38689cb 100644 --- a/client/src/app.rs +++ b/client/src/app.rs @@ -1,14 +1,14 @@ -use std::path::PathBuf; -use std::sync::Arc; -use anyhow::anyhow; -use tokio::sync::{broadcast, mpsc}; -use tokio::task::{JoinHandle, JoinSet}; use crate::config::Args; use crate::rpc::{AsyncChainState, RpcServerImpl, WalletLoadRequest, WalletManager}; use crate::source::{BitcoinBlockSource, BitcoinRpc}; use crate::spaces::Spaced; -use crate::store::chain::{Chain}; +use crate::store::chain::Chain; use crate::wallets::RpcWallet; +use anyhow::anyhow; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::sync::{broadcast, mpsc}; +use tokio::task::{JoinHandle, JoinSet}; pub struct App { shutdown: broadcast::Sender<()>, @@ -23,7 +23,12 @@ impl App { } } - async fn setup_rpc_wallet(&mut self, spaced: &Spaced, rx: mpsc::Receiver, cbf: bool) { + async fn setup_rpc_wallet( + &mut self, + spaced: &Spaced, + rx: mpsc::Receiver, + cbf: bool, + ) { let wallet_service = RpcWallet::service( spaced.network, spaced.rpc.clone(), @@ -31,7 +36,7 @@ impl App { rx, self.shutdown.clone(), spaced.num_workers, - cbf + cbf, ); self.services.spawn(async move { @@ -59,7 +64,7 @@ impl App { chain_state, self.shutdown.subscribe(), ) - .await; + .await; self.services.spawn(async { async_chain_state_handle @@ -79,7 +84,8 @@ impl App { .map_err(|e| anyhow!("RPC Server error: {}", e)) }); - self.setup_rpc_wallet(spaced, wallet_loader_rx, spaced.cbf).await; + self.setup_rpc_wallet(spaced, wallet_loader_rx, spaced.cbf) + .await; } async fn setup_sync_service(&mut self, mut spaced: Spaced) { @@ -123,15 +129,7 @@ async fn create_async_store( let async_store = AsyncChainState::new(tx); let client = reqwest::Client::new(); let handle = tokio::spawn(async move { - AsyncChainState::handler( - &client, - rpc, - anchors, - state, - rx, - shutdown, - ) - .await + AsyncChainState::handler(&client, rpc, anchors, state, rx, shutdown).await }); (async_store, handle) } diff --git a/client/src/auth.rs b/client/src/auth.rs index 0ab9559..45836b5 100644 --- a/client/src/auth.rs +++ b/client/src/auth.rs @@ -1,5 +1,5 @@ use base64::Engine; -use hyper::{http::HeaderValue, Body, HeaderMap, Request, Response, StatusCode}; +use hyper::{Body, HeaderMap, Request, Response, StatusCode, http::HeaderValue}; use jsonrpsee::{ core::ClientError, http_client::{HttpClient, HttpClientBuilder}, @@ -51,7 +51,7 @@ impl BasicAuth { .get("authorization") .and_then(|h| h.to_str().ok()) .and_then(|s| s.strip_prefix("Basic ")) - .map_or(false, |token| token == self.token.as_ref()) + .is_some_and(|token| token == self.token.as_ref()) } fn unauthorized_response() -> Response { diff --git a/client/src/bin/space-cli.rs b/client/src/bin/space-cli.rs index 4faa7e1..4899b67 100644 --- a/client/src/bin/space-cli.rs +++ b/client/src/bin/space-cli.rs @@ -1,41 +1,39 @@ extern crate core; -use std::{ - fs, io, - io::Write, - path::PathBuf, -}; -use std::str::FromStr; use anyhow::anyhow; use clap::{Parser, Subcommand}; use colored::{Color, Colorize}; use jsonrpsee::{ - core::{client::Error, ClientError}, + core::{ClientError, client::Error}, http_client::HttpClient, }; +use spaces_client::rpc::{ + CommitParams, CreateNumParams, DelegateParams, OperateParams, SetFallbackParams, +}; +use spaces_client::store::Sha256; use spaces_client::{ auth::{auth_token_from_cookie, auth_token_from_creds, http_client_with_auth}, - config::{default_cookie_path, default_spaces_rpc_port, ExtendedNetwork}, + config::{ExtendedNetwork, default_cookie_path, default_spaces_rpc_port}, format::{ - print_error_rpc_response, print_list_bidouts, print_list_nums_response, + Format, print_error_rpc_response, print_list_bidouts, print_list_nums_response, print_list_spaces_response, print_list_transactions, print_list_unspent, - print_list_wallets, print_server_info, print_wallet_balance_response, - print_wallet_info, print_wallet_response, Format, + print_list_wallets, print_server_info, print_wallet_balance_response, print_wallet_info, + print_wallet_response, }, rpc::{ - BidParams, OpenParams, RegisterParams, RpcClient, RpcWalletRequest, - RpcWalletTxBuilder, SendCoinsParams, Subject, TransferSpacesParams, + BidParams, OpenParams, RegisterParams, RpcClient, RpcWalletRequest, RpcWalletTxBuilder, + SendCoinsParams, Subject, TransferSpacesParams, }, wallets::{AddressKind, WalletResponse}, }; -use spaces_client::rpc::{CommitParams, CreateNumParams, DelegateParams, OperateParams, SetFallbackParams}; -use spaces_client::store::Sha256; +use spaces_nums::num_id::NumId; use spaces_protocol::bitcoin::{Amount, FeeRate, OutPoint, Txid}; use spaces_protocol::slabel::SLabel; -use spaces_nums::num_id::NumId; -use spaces_wallet::{bitcoin::secp256k1::schnorr::Signature, export::WalletExport, Listing}; -use spaces_wallet::bitcoin::hashes::sha256; use spaces_wallet::bitcoin::ScriptBuf; +use spaces_wallet::bitcoin::hashes::sha256; +use spaces_wallet::{Listing, bitcoin::secp256k1::schnorr::Signature, export::WalletExport}; +use std::str::FromStr; +use std::{fs, io, io::Write, path::PathBuf}; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -454,11 +452,8 @@ impl SpaceCli { args.rpc_url = Some(default_rpc_url(&args.chain)); } - let auth_token = if args.rpc_user.is_some() { - auth_token_from_creds( - args.rpc_user.as_ref().unwrap(), - args.rpc_password.as_ref().unwrap(), - ) + let auth_token = if let Some(user) = args.rpc_user.as_ref() { + auth_token_from_creds(user, args.rpc_password.as_ref().unwrap()) } else { let cookie_path = match &args.rpc_cookie { Some(path) => path, @@ -479,7 +474,7 @@ impl SpaceCli { Self { wallet: args.wallet.clone(), format: args.output_format, - dust: args.dust.map(|d| Amount::from_sat(d)), + dust: args.dust.map(Amount::from_sat), force: args.force, skip_tx_check: args.skip_tx_check, network: args.chain, @@ -538,7 +533,7 @@ async fn main() -> anyhow::Result<()> { match result { Ok(_) => {} - Err(error) => match ClientError::from(error) { + Err(error) => match error { Error::Call(rpc) => { print_error_rpc_response(rpc.code(), rpc.message().to_string(), cli.format); } @@ -633,9 +628,8 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client Commands::ExportWallet { path } => { let result = cli.client.wallet_export(&cli.wallet).await?; let content = serde_json::to_string_pretty(&result).expect("result"); - fs::write(path, content).map_err(|e| { - ClientError::Custom(format!("Could not save to path: {}", e.to_string())) - })?; + fs::write(path, content) + .map_err(|e| ClientError::Custom(format!("Could not save to path: {}", e)))?; } Commands::GetWalletInfo => { let result = cli.client.wallet_get_info(&cli.wallet).await?; @@ -698,12 +692,16 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client .await? } Commands::Renew { spaces, fee_rate } => { - let spaces: Vec<_> = spaces.into_iter().map(|s| { - let normalized = normalize_space(&s); - Subject::Label(SLabel::from_str(&normalized).expect("valid space")) - }).collect(); + let spaces: Vec<_> = spaces + .into_iter() + .map(|s| { + let normalized = normalize_space(&s); + Subject::Label(SLabel::from_str(&normalized).expect("valid space")) + }) + .collect(); cli.send_request( - Some(RpcWalletRequest::Transfer(TransferSpacesParams { secret: None, + Some(RpcWalletRequest::Transfer(TransferSpacesParams { + secret: None, spaces, to: None, data: None, @@ -722,8 +720,9 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client } => { let secret = if secret_stdin { let mut input = String::new(); - io::stdin().read_line(&mut input) - .map_err(|e| ClientError::Custom(format!("failed to read secret from stdin: {}", e)))?; + io::stdin().read_line(&mut input).map_err(|e| { + ClientError::Custom(format!("failed to read secret from stdin: {}", e)) + })?; Some(input.trim().to_string()) } else { None @@ -768,13 +767,17 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client use base64::Engine; let data = if let Some(raw_b64) = raw { // Raw base64-encoded wire-format bytes - base64::engine::general_purpose::STANDARD.decode(&raw_b64) - .map_err(|e| ClientError::Custom(format!("Could not base64 decode data: {}", e)))? + base64::engine::general_purpose::STANDARD + .decode(&raw_b64) + .map_err(|e| { + ClientError::Custom(format!("Could not base64 decode data: {}", e)) + })? } else if stdin { // Read JSON records from stdin let mut input = String::new(); - io::stdin().read_line(&mut input).map_err(|e| - ClientError::Custom(format!("Failed to read stdin: {}", e)))?; + io::stdin() + .read_line(&mut input) + .map_err(|e| ClientError::Custom(format!("Failed to read stdin: {}", e)))?; let record_set: sip7::RecordSet = serde_json::from_str(input.trim()) .map_err(|e| ClientError::Custom(format!("Invalid SIP-7 JSON: {}", e)))?; record_set.to_bytes() @@ -782,15 +785,29 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client // Build from --txt and --blob flags let mut records = Vec::new(); for txt in &txt_records { - let (key, value) = txt.split_once('=').ok_or_else(|| - ClientError::Custom(format!("Invalid --txt format '{}': expected key=value", txt)))?; + let (key, value) = txt.split_once('=').ok_or_else(|| { + ClientError::Custom(format!( + "Invalid --txt format '{}': expected key=value", + txt + )) + })?; records.push(sip7::Record::txt(key, &[value])); } for blob in &blob_records { - let (key, b64_value) = blob.split_once('=').ok_or_else(|| - ClientError::Custom(format!("Invalid --blob format '{}': expected key=base64", blob)))?; - let value = base64::engine::general_purpose::STANDARD.decode(b64_value) - .map_err(|e| ClientError::Custom(format!("Invalid base64 in --blob '{}': {}", key, e)))?; + let (key, b64_value) = blob.split_once('=').ok_or_else(|| { + ClientError::Custom(format!( + "Invalid --blob format '{}': expected key=base64", + blob + )) + })?; + let value = base64::engine::general_purpose::STANDARD + .decode(b64_value) + .map_err(|e| { + ClientError::Custom(format!( + "Invalid base64 in --blob '{}': {}", + key, e + )) + })?; records.push(sip7::Record::blob(key, value)); } sip7::RecordSet::pack(records) @@ -798,21 +815,22 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client .to_bytes() } else { return Err(ClientError::Custom( - "No data specified. Use --txt, --blob, --raw, or --stdin".to_string() + "No data specified. Use --txt, --blob, --raw, or --stdin".to_string(), )); }; cli.send_request( - Some(RpcWalletRequest::SetFallback(SetFallbackParams { subject, data })), + Some(RpcWalletRequest::SetFallback(SetFallbackParams { + subject, + data, + })), None, fee_rate, false, ) .await?; } - Commands::GetFallback { - subject, - } => { + Commands::GetFallback { subject } => { let response = cli.client.get_fallback(subject).await?; println!("{}", serde_json::to_string_pretty(&response)?); } @@ -944,10 +962,10 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client println!("{} Listing verified", "✓".color(Color::Green)); } Commands::GenerateKey => { - use spaces_wallet::bitcoin::secp256k1::{Secp256k1, Keypair}; use spaces_wallet::bitcoin::key::TapTweak; - use spaces_wallet::bitcoin::script::Builder; use spaces_wallet::bitcoin::opcodes::all::OP_PUSHNUM_1; + use spaces_wallet::bitcoin::script::Builder; + use spaces_wallet::bitcoin::secp256k1::{Keypair, Secp256k1}; let secp = Secp256k1::new(); let (secret_key, _) = secp.generate_keypair(&mut rand::thread_rng()); @@ -970,8 +988,10 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client Commands::CreateNum { bind_spk, fee_rate } => { let spk = match bind_spk { Some(hex) => { - let spk = ScriptBuf::from(hex::decode(hex) - .map_err(|_| ClientError::Custom("Invalid spk hex".to_string()))?); + let spk = ScriptBuf::from( + hex::decode(hex) + .map_err(|_| ClientError::Custom("Invalid spk hex".to_string()))?, + ); let num_id = NumId::from_spk::(spk.clone()); println!("Creating num id: {}", num_id); Some(spk) @@ -989,7 +1009,7 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client fee_rate, false, ) - .await? + .await? } Commands::GetNum { subject } => { let num = cli @@ -1010,17 +1030,19 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client } Commands::Operate { subject, fee_rate } => { cli.send_request( - Some(RpcWalletRequest::Operate(OperateParams { - subject, - })), + Some(RpcWalletRequest::Operate(OperateParams { subject })), None, fee_rate, false, ) - .await?; + .await?; println!("Operate setup should be complete once tx is confirmed"); } - Commands::Commit { subject, root, fee_rate } => { + Commands::Commit { + subject, + root, + fee_rate, + } => { cli.send_request( Some(RpcWalletRequest::Commit(CommitParams { subject, @@ -1030,7 +1052,7 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client fee_rate, false, ) - .await?; + .await?; } Commands::Rollback { subject, fee_rate } => { cli.send_request( @@ -1042,20 +1064,21 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client fee_rate, false, ) - .await?; + .await?; println!("Rollback transaction sent"); } - Commands::Delegate { subject, to, fee_rate } => { + Commands::Delegate { + subject, + to, + fee_rate, + } => { cli.send_request( - Some(RpcWalletRequest::Delegate(DelegateParams { - subject, - to, - })), + Some(RpcWalletRequest::Delegate(DelegateParams { subject, to })), None, fee_rate, false, ) - .await?; + .await?; } Commands::GetDelegator { subject } => { let delegator = cli diff --git a/client/src/bin/spaced.rs b/client/src/bin/spaced.rs index 319e1ed..87850b4 100644 --- a/client/src/bin/spaced.rs +++ b/client/src/bin/spaced.rs @@ -1,14 +1,10 @@ -use std::{env}; +use std::env; use env_logger::Env; use log::error; -use spaces_client::{ - config::{safe_exit}, -}; -use tokio::{ - sync::{broadcast}, -}; use spaces_client::app::App; +use spaces_client::config::safe_exit; +use tokio::sync::broadcast; #[tokio::main] async fn main() { @@ -26,7 +22,7 @@ async fn main() { match app.run(env::args().collect()).await { Ok(_) => {} Err(e) => { - error!("{}", e.to_string()); + error!("{}", e); safe_exit(1); } } diff --git a/client/src/cbf.rs b/client/src/cbf.rs index 2e428f0..6c3ae37 100644 --- a/client/src/cbf.rs +++ b/client/src/cbf.rs @@ -1,19 +1,21 @@ -use std::collections::{BTreeMap, HashSet, VecDeque}; -use std::time::Duration; +use crate::client::{BlockSource, BlockchainInfo}; +use crate::source::BitcoinBlockSource; +use crate::wallets::{WalletProgressUpdate, WalletStatus}; use anyhow::anyhow; use log::info; -use tokio::time::Instant; use spaces_protocol::bitcoin::BlockHash; use spaces_protocol::constants::ChainAnchor; -use spaces_wallet::bdk_wallet::chain::{local_chain, BlockId, ConfirmationBlockTime, IndexedTxGraph, TxUpdate}; +use spaces_wallet::SpacesWallet; use spaces_wallet::bdk_wallet::chain::keychain_txout::KeychainTxOutIndex; +use spaces_wallet::bdk_wallet::chain::{ + BlockId, ConfirmationBlockTime, IndexedTxGraph, TxUpdate, local_chain, +}; use spaces_wallet::bdk_wallet::{KeychainKind, Update}; -use spaces_wallet::bitcoin::bip158::BlockFilter; use spaces_wallet::bitcoin::ScriptBuf; -use spaces_wallet::SpacesWallet; -use crate::client::{BlockSource, BlockchainInfo}; -use crate::source::BitcoinBlockSource; -use crate::wallets::{WalletStatus, WalletProgressUpdate}; +use spaces_wallet::bitcoin::bip158::BlockFilter; +use std::collections::{BTreeMap, HashSet, VecDeque}; +use std::time::Duration; +use tokio::time::Instant; pub struct CompactFilterSync { graph: IndexedTxGraph>, @@ -47,7 +49,10 @@ impl CompactFilterSync { pub fn new(wallet: &SpacesWallet) -> Self { let initial_tip = { let tip = wallet.local_chain().tip(); - ChainAnchor { height: tip.height(), hash: tip.hash() } + ChainAnchor { + height: tip.height(), + hash: tip.hash(), + } }; let mut cbf = Self { @@ -110,7 +115,10 @@ impl CompactFilterSync { source: &BitcoinBlockSource, progress: &mut WalletProgressUpdate, ) -> anyhow::Result<()> { - if self.wait.is_some_and(|w| w.elapsed() < Duration::from_secs(10)) { + if self + .wait + .is_some_and(|w| w.elapsed() < Duration::from_secs(10)) + { return Ok(()); } self.wait = None; @@ -119,12 +127,17 @@ impl CompactFilterSync { SyncState::SyncChecks => { let info = source.get_blockchain_info()?; // if wallet already past prune height, we don't need filters - if let Some(prune_height) = info.prune_height { - if self.initial_tip.height >= prune_height { - info!("wallet({}): tip {} >= prune height {}, cbf done", wallet.name(), self.initial_tip.height, prune_height); - self.state = SyncState::Synced; - return Ok(()); - } + if let Some(prune_height) = info.prune_height + && self.initial_tip.height >= prune_height + { + info!( + "wallet({}): tip {} >= prune height {}, cbf done", + wallet.name(), + self.initial_tip.height, + prune_height + ); + self.state = SyncState::Synced; + return Ok(()); } if info.headers != info.blocks { info!("Source still syncing, retrying..."); @@ -140,8 +153,9 @@ impl CompactFilterSync { } info!("Filters syncing, retrying..."); - *progress = WalletProgressUpdate::new(WalletStatus::CbfFilterSync, - Some(info.filters_progress.unwrap_or(0.0)) + *progress = WalletProgressUpdate::new( + WalletStatus::CbfFilterSync, + Some(info.filters_progress.unwrap_or(0.0)), ); self.wait = Some(Instant::now()); return Ok(()); @@ -154,17 +168,21 @@ impl CompactFilterSync { .ok_or_else(|| anyhow!("filter sync: checkpoint missing"))?; if self.initial_tip.height < checkpoint.height { return Err(anyhow!( - "Wallet birthday {} < checkpoint {}", self.initial_tip.height, checkpoint.height + "Wallet birthday {} < checkpoint {}", + self.initial_tip.height, + checkpoint.height )); } let start = self.initial_tip.height; - let end = info - .prune_height - .ok_or(anyhow!("Prune height missing"))?; + let end = info.prune_height.ok_or(anyhow!("Prune height missing"))?; let available_filters = info.filters.ok_or(anyhow!("Filters missing"))?; if end > available_filters { - return Err(anyhow!("Prune height {} > {} available filters", end, available_filters)); + return Err(anyhow!( + "Prune height {} > {} available filters", + end, + available_filters + )); } if start >= end { @@ -193,21 +211,33 @@ impl CompactFilterSync { self.queued_blocks.insert(height, idx_filter.hash); self.load_more_scripts(wallet); self.block_matches += 1; - info!("wallet({}) processed block filter {} - match found", wallet.name(), height); + info!( + "wallet({}) processed block filter {} - match found", + wallet.name(), + height + ); } else { - info!("wallet({}) processed block filter {} - no match", wallet.name(), height); + info!( + "wallet({}) processed block filter {} - no match", + wallet.name(), + height + ); } let completed = self.total_filters as f32 - self.queued_filters.len() as f32; *progress = WalletProgressUpdate::new( WalletStatus::CbfProcessFilters, - Some(completed / self.total_filters as f32) + Some(completed / self.total_filters as f32), ); } SyncState::QueueBlocks => { if !self.queued_blocks.is_empty() { let heights: Vec = self.queued_blocks.keys().copied().collect(); - info!("wallet({}): queueing {} blocks", wallet.name(), heights.len()); + info!( + "wallet({}): queueing {} blocks", + wallet.name(), + heights.len() + ); source.queue_blocks(heights)?; } self.state = SyncState::WaitForBlocks; @@ -220,7 +250,11 @@ impl CompactFilterSync { .ok_or_else(|| anyhow!("filter sync: block queue missing"))?; if status.pending > 0 { - info!("wallet({}): waiting for {} pending blocks", wallet.name(), status.pending); + info!( + "wallet({}): waiting for {} pending blocks", + wallet.name(), + status.pending + ); // The client has a global state for pending blocks in the queue // so we cap it just in case other things are queuing blocks @@ -229,7 +263,7 @@ impl CompactFilterSync { let completed = self.block_matches as f32 - pending; *progress = WalletProgressUpdate::new( WalletStatus::CbfDownloadMatchingBlocks, - Some(completed / self.block_matches as f32) + Some(completed / self.block_matches as f32), ); self.wait = Some(Instant::now()); return Ok(()); @@ -237,7 +271,9 @@ impl CompactFilterSync { if status.completed < self.queued_blocks.len() as u32 { return Err(anyhow!( - "incomplete downloads: {} of {}", status.completed, self.queued_blocks.len() + "incomplete downloads: {} of {}", + status.completed, + self.queued_blocks.len() )); } self.state = SyncState::ProcessBlocks; @@ -251,19 +287,31 @@ impl CompactFilterSync { } Some(f) => f, }; - info!("wallet({}): processing block {} {}", wallet.name(), height, hash); - let block = source.get_block(&hash)? - .ok_or(anyhow!("block {} {} not found", height, hash))?; + info!( + "wallet({}): processing block {} {}", + wallet.name(), + height, + hash + ); + let block = source.get_block(&hash)?.ok_or(anyhow!( + "block {} {} not found", + height, + hash + ))?; self.chain_changeset.insert(height, Some(hash)); let _ = self.graph.apply_block_relevant(&block, height); let completed = self.block_matches - self.queued_blocks.len() as u32; *progress = WalletProgressUpdate::new( WalletStatus::CbfProcessMatchingBlocks, - Some(completed as f32 / self.block_matches as f32) + Some(completed as f32 / self.block_matches as f32), ); } SyncState::ApplyUpdate => { - info!("wallet({}): updating wallet tip to {}", wallet.name(), self.filters_tip); + info!( + "wallet({}): updating wallet tip to {}", + wallet.name(), + self.filters_tip + ); let filters_anchor = BlockId { height: self.filters_tip, hash: source.get_block_hash(self.filters_tip)?, @@ -272,7 +320,11 @@ impl CompactFilterSync { let update = self.get_scan_response(); wallet.apply_update(update)?; wallet.insert_checkpoint(filters_anchor)?; - info!("wallet({}): compact filter sync portion complete at {}", wallet.name(), self.filters_tip); + info!( + "wallet({}): compact filter sync portion complete at {}", + wallet.name(), + self.filters_tip + ); self.state = SyncState::Synced; // Only CBF portion is done *progress = WalletProgressUpdate::new(WalletStatus::Syncing, None); diff --git a/client/src/checker.rs b/client/src/checker.rs index 9009b40..f4519b1 100644 --- a/client/src/checker.rs +++ b/client/src/checker.rs @@ -2,15 +2,15 @@ use std::collections::BTreeMap; use anyhow::anyhow; use spaces_protocol::{ + Covenant, RevokeReason, SpaceOut, bitcoin::{OutPoint, Transaction}, hasher::{KeyHasher, SpaceKey}, prepare::{SpacesSource, TxContext}, validate::{TxChangeSet, UpdateKind, Validator}, - Covenant, RevokeReason, SpaceOut, }; -use crate::store::chain::Chain; use crate::store::Sha256; +use crate::store::chain::Chain; pub struct TxChecker<'a> { pub original: &'a mut Chain, @@ -46,7 +46,7 @@ impl<'a> TxChecker<'a> { ) -> anyhow::Result> { let changeset = self.apply_tx(height, tx)?; if let Some(changeset) = changeset.as_ref() { - Self::check(&changeset)?; + Self::check(changeset)?; } Ok(changeset) } @@ -74,11 +74,9 @@ impl<'a> TxChecker<'a> { txid, vout: create.n as _, }; - if create.space.is_some() { - let space = SpaceKey::from(Sha256::hash( - create.space.as_ref().expect("space").name.as_ref(), - )); - self.spaces.insert(space, Some(outpoint)); + if let Some(space) = create.space.as_ref() { + let key = SpaceKey::from(Sha256::hash(space.name.as_ref())); + self.spaces.insert(key, Some(outpoint)); } self.spaceouts.insert(outpoint, Some(create)); } @@ -115,29 +113,27 @@ impl<'a> TxChecker<'a> { .iter() .any(|spend| spend.script_error.is_some()) { - return Err(anyhow!("tx-check: transaction not broadcasted as it may have an open that will be rejected")); + return Err(anyhow!( + "tx-check: transaction not broadcasted as it may have an open that will be rejected" + )); } for create in changset.creates.iter() { - if let Some(space) = create.space.as_ref() { - match space.covenant { - Covenant::Reserved => { - return Err(anyhow!("tx-check: transaction not broadcasted as it may cause spaces to use a reserved covenant")) - } - _ => {} - } + if let Some(space) = create.space.as_ref() + && matches!(space.covenant, Covenant::Reserved) + { + return Err(anyhow!( + "tx-check: transaction not broadcasted as it may cause spaces to use a reserved covenant" + )); } } for update in changset.updates.iter() { - match update.kind { - UpdateKind::Revoke(kind) => { - match kind { - RevokeReason::Expired => {} - _ => { - return Err(anyhow!("tx-check: transaction not broadcasted as it may cause a space to be revoked (code: {:?})", kind)) - } - } - } - _ => {} + if let UpdateKind::Revoke(kind) = update.kind + && !matches!(kind, RevokeReason::Expired) + { + return Err(anyhow!( + "tx-check: transaction not broadcasted as it may cause a space to be revoked (code: {:?})", + kind + )); } } Ok(()) @@ -150,8 +146,8 @@ impl SpacesSource for TxChecker<'_> { space_hash: &SpaceKey, ) -> spaces_protocol::errors::Result> { match self.spaces.get(space_hash) { - None => self.original.get_space_outpoint(space_hash.into()), - Some(res) => Ok(res.clone()), + None => self.original.get_space_outpoint(space_hash), + Some(res) => Ok(*res), } } diff --git a/client/src/client.rs b/client/src/client.rs index e97d011..6b49fdd 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -3,27 +3,25 @@ pub extern crate spaces_protocol; use std::{error::Error, fmt}; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use borsh::{BorshDeserialize, BorshSerialize}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::de::Error as SerdeError; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use spaces_nums::{CommitmentKey, CommitmentTipKey, DelegatorKey, NumOutpointKey}; use spaces_protocol::{ + Bytes, Covenant, FullSpaceOut, RevokeReason, SpaceOut, bitcoin::{Amount, Block, BlockHash, OutPoint, Txid}, constants::{ChainAnchor, ROLLOUT_BATCH_SIZE, ROLLOUT_BLOCK_INTERVAL}, hasher::{BidKey, KeyHasher, OutpointKey, SpaceKey}, prepare::TxContext, validate::{TxChangeSet, UpdateKind, Validator}, - Bytes, Covenant, FullSpaceOut, RevokeReason, SpaceOut, }; -use spaces_nums::{CommitmentKey, CommitmentTipKey, DelegatorKey, NumOutpointKey}; use spaces_wallet::bitcoin::{Network, Transaction}; -use crate::{ - source::{BitcoinRpcError, BestChain}, -}; use crate::source::BlockQueueResult; -use crate::store::chain::{Chain}; +use crate::source::{BestChain, BitcoinRpcError}; use crate::store::Sha256; +use crate::store::chain::Chain; pub trait BlockSource { fn get_block_hash(&self, height: u32) -> Result; @@ -31,9 +29,16 @@ pub trait BlockSource { fn get_median_time(&self) -> Result; fn in_mempool(&self, txid: &Txid, height: u32) -> Result; fn get_block_count(&self) -> Result; - fn get_best_chain(&self, tip: Option, expected_chain: Network) -> Result; + fn get_best_chain( + &self, + tip: Option, + expected_chain: Network, + ) -> Result; fn get_blockchain_info(&self) -> Result; - fn get_block_filter_by_height(&self, height: u32) -> Result, BitcoinRpcError>; + fn get_block_filter_by_height( + &self, + height: u32, + ) -> Result, BitcoinRpcError>; fn queue_blocks(&self, heights: Vec) -> Result<(), BitcoinRpcError>; fn queue_filters(&self) -> Result<(), BitcoinRpcError>; } @@ -42,10 +47,7 @@ pub trait BlockSource { pub struct BlockFilterRpc { pub hash: BlockHash, pub height: u32, - #[serde( - serialize_with = "serialize_hex", - deserialize_with = "deserialize_hex" - )] + #[serde(serialize_with = "serialize_hex", deserialize_with = "deserialize_hex")] pub content: Vec, } @@ -74,7 +76,6 @@ pub struct BlockchainInfo { pub headers_synced: Option, } - #[derive(Debug, Clone)] pub struct Client { validator: Validator, @@ -105,7 +106,6 @@ pub struct PtrTxEntry { pub tx: Option, } - #[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct TxEntry { #[serde(flatten)] @@ -147,16 +147,21 @@ impl Client { } } - fn verify_block_connected(chain: &mut Chain, height: u32, block_hash: BlockHash, block: &Block) -> anyhow::Result<()> { + fn verify_block_connected( + chain: &mut Chain, + height: u32, + block_hash: BlockHash, + block: &Block, + ) -> anyhow::Result<()> { // Spaces tip must connect to block { let tip = chain.tip(); if tip.hash != block.header.prev_blockhash || tip.height + 1 != height { return Err(SyncError { - checkpoint: tip.clone(), + checkpoint: tip, connect_to: (height, block_hash), } - .into()); + .into()); } } // Nums tip must connect to block @@ -164,10 +169,10 @@ impl Client { let tip = chain.nums_tip(); if tip.hash != block.header.prev_blockhash || tip.height + 1 != height { return Err(SyncError { - checkpoint: tip.clone(), + checkpoint: tip, connect_to: (height, block_hash), } - .into()); + .into()); } } @@ -202,7 +207,7 @@ impl Client { } // Rollouts: - if (height - 1) % ROLLOUT_BLOCK_INTERVAL == 0 { + if (height - 1).is_multiple_of(ROLLOUT_BLOCK_INTERVAL) { let batch = Self::get_rollout_batch(ROLLOUT_BATCH_SIZE, chain)?; let coinbase = block .coinbase() @@ -232,7 +237,7 @@ impl Client { let mut spaceouts_input_ctx = None; if let Some(prepared) = TxContext::from_tx::(chain, tx)? { spaceouts_input_ctx = Some(prepared.inputs.clone()); - let validated_tx = self.validator.process(height, &tx, prepared); + let validated_tx = self.validator.process(height, tx, prepared); spaceouts = Some(validated_tx.creates.clone()); if let Some(idx) = spaces_meta.as_mut() { @@ -250,7 +255,7 @@ impl Client { }, }); } - self.apply_space_tx(chain, &tx, validated_tx); + self.apply_space_tx(chain, tx, validated_tx); } let ptrs_ctx = if chain.can_scan_nums(height) { @@ -258,17 +263,28 @@ impl Client { chain, tx, spaceouts_input_ctx.is_some(), - spaceouts.clone().unwrap_or(vec![]) , height)? + spaceouts.clone().unwrap_or(vec![]), + height, + )? } else { None }; if let Some(ptrs_ctx) = ptrs_ctx { - let spent_spaceouts = spaceouts_input_ctx.unwrap_or_default().into_iter() - .map(|input| input.sstxo.previous_output).collect::>(); + let spent_spaceouts = spaceouts_input_ctx + .unwrap_or_default() + .into_iter() + .map(|input| input.sstxo.previous_output) + .collect::>(); let created_spaceouts = spaceouts.unwrap_or_default(); - let ptrs_validated = self.ptr_validator - .process::(height, &tx, position as _, ptrs_ctx, spent_spaceouts, created_spaceouts); + let ptrs_validated = self.ptr_validator.process::( + height, + tx, + position as _, + ptrs_ctx, + spent_spaceouts, + created_spaceouts, + ); if let Some(idx) = num_meta.as_mut() { { @@ -299,7 +315,12 @@ impl Client { Ok((spaces_meta, num_meta)) } - fn apply_ptrs_tx(&self, state: &mut Chain, tx: &Transaction, changeset: spaces_nums::TxChangeSet) { + fn apply_ptrs_tx( + &self, + state: &mut Chain, + tx: &Transaction, + changeset: spaces_nums::TxChangeSet, + ) { // Remove spends for n in changeset.spends.into_iter() { let previous = tx.input[n].previous_output; @@ -313,7 +334,8 @@ impl Client { } // Remove revoked commitments for revoked in changeset.revoked_commitments { - let commitment_key = CommitmentKey::new::(&revoked.space, revoked.commitment.state_root); + let commitment_key = + CommitmentKey::new::(&revoked.space, revoked.commitment.state_root); state.remove_commitment(commitment_key); let registry_key = CommitmentTipKey::from_slabel::(&revoked.space); @@ -333,14 +355,16 @@ impl Client { // Insert new commitments for commitment_info in changeset.commitments { - let commitment_key = CommitmentKey::new::(&commitment_info.space, commitment_info.commitment.state_root); + let commitment_key = CommitmentKey::new::( + &commitment_info.space, + commitment_info.commitment.state_root, + ); let registry_key = CommitmentTipKey::from_slabel::(&commitment_info.space); // Points space -> commitments tip state.insert_commitment_tip(registry_key, commitment_info.commitment.state_root); // commitment key = HASH(HASH(space) || state root) -> commitment state.insert_commitment(commitment_key, commitment_info.commitment); - } // Create ptrs @@ -385,18 +409,15 @@ impl Client { state.remove_space(space_key); // Remove any bids from pre-auction pool - match space.covenant { - Covenant::Bid { - total_burned, - claim_height, - .. - } => { - if claim_height.is_none() { - let bid_key = BidKey::from_bid(total_burned, base_hash); - state.remove_bid(bid_key) - } - } - _ => {} + if let Covenant::Bid { + total_burned, + claim_height, + .. + } = space.covenant + && claim_height.is_none() + { + let bid_key = BidKey::from_bid(total_burned, base_hash); + state.remove_bid(bid_key) } } RevokeReason::Expired => { diff --git a/client/src/config.rs b/client/src/config.rs index f336ee5..ed18e8d 100644 --- a/client/src/config.rs +++ b/client/src/config.rs @@ -12,17 +12,17 @@ use jsonrpsee::core::Serialize; use log::error; use rand::{ distributions::Alphanumeric, - {thread_rng, Rng}, + {Rng, thread_rng}, }; use serde::Deserialize; use spaces_protocol::bitcoin::Network; +use crate::store::chain::{Chain, ROOT_ANCHORS_COUNT}; use crate::{ auth::{auth_token_from_cookie, auth_token_from_creds}, source::{BitcoinRpc, BitcoinRpcAuth}, spaces::Spaced, }; -use crate::store::chain::{Chain, ROOT_ANCHORS_COUNT}; const RPC_OPTIONS: &str = "RPC Server Options"; @@ -72,7 +72,7 @@ pub struct Args { /// This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost) #[arg(long, help_heading = Some(RPC_OPTIONS), default_values = ["127.0.0.1", "::1"], env = "SPACED_RPC_BIND")] rpc_bind: Vec, - /// Listen for JSON-RPC connections on + /// Listen for JSON-RPC connections on `` #[arg(long, help_heading = Some(RPC_OPTIONS), env = "SPACED_RPC_PORT")] rpc_port: Option, /// Index blocks including the full transaction data @@ -126,6 +126,7 @@ impl ExtendedNetwork { } } + #[allow(clippy::result_unit_err)] pub fn from_core_arg(arg: &str) -> Result { match arg.to_lowercase().as_str() { "main" => Ok(ExtendedNetwork::Mainnet), @@ -173,11 +174,8 @@ impl Args { }) .collect(); - let auth_token = if args.rpc_user.is_some() { - auth_token_from_creds( - args.rpc_user.as_ref().unwrap(), - args.rpc_password.as_ref().unwrap(), - ) + let auth_token = if let Some(user) = args.rpc_user.as_ref() { + auth_token_from_creds(user, args.rpc_password.as_ref().unwrap()) } else { let cookie = format!( "__cookie__:{}", @@ -200,13 +198,17 @@ impl Args { let bitcoin_rpc_auth = if let Some(cookie) = args.bitcoin_rpc_cookie { let cookie = std::fs::read_to_string(&cookie).map_err(|e| { - anyhow!("Failed to read Bitcoin RPC cookie '{}': {}", cookie.display(), e) + anyhow!( + "Failed to read Bitcoin RPC cookie '{}': {}", + cookie.display(), + e + ) })?; BitcoinRpcAuth::Cookie(cookie) } else if let Some(user) = args.bitcoin_rpc_user { BitcoinRpcAuth::UserPass(user, args.bitcoin_rpc_password.expect("password")) - } else if let Some(cookie) = default_bitcoin_cookie_path(&args.chain) - .and_then(|p| std::fs::read_to_string(&p).ok()) + } else if let Some(cookie) = + default_bitcoin_cookie_path(&args.chain).and_then(|p| std::fs::read_to_string(&p).ok()) { log::info!("Using Bitcoin Core cookie authentication"); BitcoinRpcAuth::Cookie(cookie) @@ -288,7 +290,9 @@ pub fn default_bitcoin_cookie_path(network: &ExtendedNetwork) -> Option } else if cfg!(target_os = "macos") { home?.join("Library/Application Support/Bitcoin") } else if cfg!(target_os = "windows") { - std::env::var_os("APPDATA").map(PathBuf::from)?.join("Bitcoin") + std::env::var_os("APPDATA") + .map(PathBuf::from)? + .join("Bitcoin") } else { return None; }; diff --git a/client/src/format.rs b/client/src/format.rs index 8297e5d..a6a0805 100644 --- a/client/src/format.rs +++ b/client/src/format.rs @@ -3,9 +3,11 @@ use colored::{Color, Colorize}; use jsonrpsee::core::Serialize; use serde::Deserialize; use spaces_protocol::{ - bitcoin::{Amount, Network, OutPoint}, Covenant + Covenant, + bitcoin::{Amount, Network, OutPoint}, }; use spaces_wallet::{ + Balance, DoubleUtxo, WalletOutput, address::SpaceAddress, bdk_wallet::KeychainKind, bitcoin::{Address, Txid}, @@ -13,15 +15,14 @@ use spaces_wallet::{ BidEventDetails, BidoutEventDetails, OpenEventDetails, SendEventDetails, TransferEventDetails, TxEventKind, }, - Balance, DoubleUtxo, WalletOutput, }; use tabled::{Table, Tabled}; +use crate::wallets::{WalletInfoWithProgress, WalletStatus}; use crate::{ rpc::ServerInfo, wallets::{ListNumsResponse, ListSpacesResponse, TxInfo, TxResponse, WalletResponse}, }; -use crate::wallets::{WalletInfoWithProgress, WalletStatus}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, ValueEnum, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] @@ -184,7 +185,10 @@ pub fn print_wallet_info(prog: WalletInfoWithProgress, format: Format) { match format { Format::Text => { println!("WALLET: {}", prog.info.label); - println!(" Tip {}\n Birthday {}", prog.info.tip, prog.info.start_block); + println!( + " Tip {}\n Birthday {}", + prog.info.tip, prog.info.start_block + ); println!(" Public descriptors"); for desc in prog.info.descriptors { @@ -195,7 +199,7 @@ pub fn print_wallet_info(prog: WalletInfoWithProgress, format: Format) { println!(" Sync Status:"); let p = prog.sync.progress.unwrap_or(0.0); match prog.sync.status { - WalletStatus::HeadersSync => { + WalletStatus::HeadersSync => { println!(" Syncing block headers"); } WalletStatus::ChainSync => { @@ -204,20 +208,23 @@ pub fn print_wallet_info(prog: WalletInfoWithProgress, format: Format) { WalletStatus::SpacesSync => { println!(" Spaces Syncing: {:.1}%", p * 100.0); } - WalletStatus::CbfFilterSync => { + WalletStatus::CbfFilterSync => { println!(" Filters Syncing: {:.1}%", p * 100.0); } WalletStatus::CbfProcessFilters => { - println!(" Processing Filters: {:.1}%", p* 100.0); + println!(" Processing Filters: {:.1}%", p * 100.0); } WalletStatus::CbfDownloadMatchingBlocks => { println!(" Downloading Matching Blocks: {:.1}%", p * 100.0); } WalletStatus::CbfProcessMatchingBlocks => { - println!(" Processing Matching Blocks: {:.1}%",p * 100.0); + println!(" Processing Matching Blocks: {:.1}%", p * 100.0); } WalletStatus::Syncing => { - println!(" Syncing: In progress ({:.1}%):", prog.info.progress * 100.0); + println!( + " Syncing: In progress ({:.1}%):", + prog.info.progress * 100.0 + ); } WalletStatus::CbfApplyUpdate => { println!(" Applying compact filters update"); @@ -225,7 +232,6 @@ pub fn print_wallet_info(prog: WalletInfoWithProgress, format: Format) { WalletStatus::Complete => { println!(" Complete"); } - } println!(); @@ -238,7 +244,7 @@ pub fn print_wallet_info(prog: WalletInfoWithProgress, format: Format) { fn ascii_table(iter: I) -> String where - I: IntoIterator, + I: IntoIterator, T: Tabled, { Table::new(iter) @@ -282,7 +288,9 @@ pub fn print_list_spaces_response( let mut winnings = Vec::new(); let mut owned = Vec::new(); for slabel in response.pending { - pendings.push(PendingSpaces { space: slabel.to_string() }); + pendings.push(PendingSpaces { + space: slabel.to_string(), + }); } for res in response.outbid { let space = res.spaceout.space.as_ref().expect("space"); @@ -291,16 +299,14 @@ pub fn print_list_spaces_response( last_confirmed_bid: 0, days_left: "".to_string(), }; - match space.covenant { - Covenant::Bid { - total_burned, - claim_height, - .. - } => { - outbid.last_confirmed_bid = total_burned.to_sat(); - outbid.days_left = format_days_left(current_block, claim_height); - } - _ => {} + if let Covenant::Bid { + total_burned, + claim_height, + .. + } = space.covenant + { + outbid.last_confirmed_bid = total_burned.to_sat(); + outbid.days_left = format_days_left(current_block, claim_height); } outbids.push(outbid); } @@ -313,22 +319,20 @@ pub fn print_list_spaces_response( days_left: "--".to_string(), claim_at: "--".to_string(), }; - match space.covenant { - Covenant::Bid { - total_burned, - claim_height, - .. - } => { - winning.bid = total_burned.to_sat(); - winning.claim_at = claim_height - .map(|h| h.to_string()) - .unwrap_or("--".to_string()); - winning.days_left = format_days_left(current_block, claim_height); - if winning.days_left == "0.00" { - winning.days_left = "Ready to claim".to_string(); - } + if let Covenant::Bid { + total_burned, + claim_height, + .. + } = space.covenant + { + winning.bid = total_burned.to_sat(); + winning.claim_at = claim_height + .map(|h| h.to_string()) + .unwrap_or("--".to_string()); + winning.days_left = format_days_left(current_block, claim_height); + if winning.days_left == "0.00" { + winning.days_left = "Ready to claim".to_string(); } - _ => {} } winnings.push(winning); } @@ -340,19 +344,18 @@ pub fn print_list_spaces_response( days_left: "--".to_string(), utxo: res.outpoint(), }; - match &space.covenant { - Covenant::Transfer { expire_height, .. } => { - registered.expire_at = *expire_height as _; - registered.days_left = - format_days_left(current_block, Some(*expire_height)); - } - _ => {} + if let Covenant::Transfer { expire_height, .. } = &space.covenant { + registered.expire_at = *expire_height as _; + registered.days_left = format_days_left(current_block, Some(*expire_height)); } owned.push(registered); } if !pendings.is_empty() { - println!("⏳ PENDING ({} spaces): ", pendings.len().to_string().bold()); + println!( + "⏳ PENDING ({} spaces): ", + pendings.len().to_string().bold() + ); let table = ascii_table(pendings); println!("{}", table); } @@ -374,11 +377,7 @@ pub fn print_list_spaces_response( } if !owned.is_empty() { - println!( - "{} ({} spaces): ", - "🔑 OWNED", - owned.len().to_string().bold() - ); + println!("🔑 OWNED ({} spaces): ", owned.len().to_string().bold()); let table = ascii_table(owned); println!("{}", table); } @@ -411,15 +410,17 @@ pub fn print_wallet_response_text(network: Network, response: WalletResponse) { let mut secondary_txs = Vec::new(); for tx in response.result { - if tx.events.iter().any(|event| match event.kind { - TxEventKind::Open - | TxEventKind::Bid - | TxEventKind::Register - | TxEventKind::Transfer - | TxEventKind::Send - | TxEventKind::Renew - | TxEventKind::Buy => true, - _ => false, + if tx.events.iter().any(|event| { + matches!( + event.kind, + TxEventKind::Open + | TxEventKind::Bid + | TxEventKind::Register + | TxEventKind::Transfer + | TxEventKind::Send + | TxEventKind::Renew + | TxEventKind::Buy + ) }) { main_txs.push(tx); } else { diff --git a/client/src/lib.rs b/client/src/lib.rs index 934d626..64fb07a 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -9,7 +9,9 @@ use std::time::{Duration, Instant}; use base64::Engine; use serde::{Deserialize, Deserializer, Serializer}; +pub mod app; pub mod auth; +mod cbf; mod checker; pub mod client; pub mod config; @@ -18,11 +20,9 @@ pub mod rpc; #[cfg(feature = "schema")] pub mod rpc_schema; pub mod source; +mod spaces; pub mod store; pub mod wallets; -mod cbf; -pub mod app; -mod spaces; fn std_wait(mut predicate: F, wait: Duration) where diff --git a/client/src/rpc.rs b/client/src/rpc.rs index 3cb64fa..33447a5 100644 --- a/client/src/rpc.rs +++ b/client/src/rpc.rs @@ -1,77 +1,78 @@ -use std::{ - collections::BTreeMap, fs, fs::File, io::Write, net::SocketAddr, path::PathBuf, str::FromStr, - sync::Arc, +use crate::auth::BasicAuthLayer; +use crate::store::Sha256; +use crate::store::chain::{CACHED_SNAPSHOT_LOOKBACK, COMMIT_BLOCK_INTERVAL, Chain}; +use crate::store::spaces::RolloutEntry; +use crate::wallets::WalletInfoWithProgress; +use crate::{ + calc_progress, + checker::TxChecker, + client::{BlockMeta, BlockchainInfo, NumBlockMeta, TxEntry}, + config::ExtendedNetwork, + deserialize_base64, serialize_base64, + source::BitcoinRpc, + wallets::{ + AddressKind, ListNumsResponse, ListSpacesResponse, RpcWallet, TxInfo, TxResponse, + WalletCommand, WalletResponse, + }, }; -use std::collections::HashSet; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use bdk::{ + KeychainKind, bitcoin::{Amount, BlockHash, FeeRate, Network, Txid}, chain::BlockId, keys::{ - bip39::{Language, Mnemonic, WordCount}, DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey, + bip39::{Language, Mnemonic, WordCount}, }, miniscript::Tap, - KeychainKind, }; use jsonrpsee::{ core::async_trait, proc_macros::rpc, - server::{middleware::http::ProxyGetRequestLayer, Server}, + server::{Server, middleware::http::ProxyGetRequestLayer}, types::ErrorObjectOwned, }; use log::info; use serde::{Deserialize, Serialize}; use spacedb::tx::ProofType; +use spaces_nums::num_id::NumId; +use spaces_nums::snumeric::SNumeric; +use spaces_nums::{ + ChainProofRequest, Commitment, CommitmentKey, CommitmentTipKey, DelegatorKey, FullNumOut, + NumKeyKind, NumOut, NumOutpointKey, NumSource, RootAnchor, +}; +use spaces_protocol::bitcoin::ScriptBuf; +use spaces_protocol::hasher::Hash; use spaces_protocol::{ - bitcoin, + Bytes, Covenant, FullSpaceOut, SpaceOut, bitcoin, bitcoin::{ - bip32::Xpriv, Network::{Regtest, Testnet}, OutPoint, + bip32::Xpriv, }, constants::ChainAnchor, hasher::{KeyHasher, OutpointKey, SpaceKey}, prepare::SpacesSource, slabel::SLabel, validate::TxChangeSet, - Bytes, Covenant, FullSpaceOut, SpaceOut, }; +pub use spaces_wallet::Subject; +use spaces_wallet::bitcoin::hashes::sha256; use spaces_wallet::{ + Balance, DoubleUtxo, Listing, SpacesWallet, WalletConfig, WalletDescriptors, WalletOutput, bdk_wallet as bdk, bdk_wallet::template::Bip86, bitcoin::hashes::Hash as BitcoinHash, - bitcoin::secp256k1::schnorr, - export::WalletExport, Balance, DoubleUtxo, Listing, SpacesWallet, - WalletConfig, WalletDescriptors, WalletOutput, + bitcoin::secp256k1::schnorr, export::WalletExport, +}; +use std::collections::HashSet; +use std::{ + collections::BTreeMap, fs, fs::File, io::Write, net::SocketAddr, path::PathBuf, str::FromStr, + sync::Arc, }; -pub use spaces_wallet::Subject; use tokio::{ select, - sync::{broadcast, mpsc, oneshot, RwLock}, + sync::{RwLock, broadcast, mpsc, oneshot}, task::JoinSet, }; -use spaces_protocol::bitcoin::ScriptBuf; -use spaces_protocol::hasher::Hash; -use spaces_nums::{NumSource, FullNumOut, NumOut, Commitment, CommitmentTipKey, CommitmentKey, DelegatorKey, NumOutpointKey, RootAnchor, ChainProofRequest, NumKeyKind}; -use spaces_nums::snumeric::SNumeric; -use spaces_nums::num_id::NumId; -use spaces_wallet::bitcoin::hashes::sha256; -use crate::auth::BasicAuthLayer; -use crate::wallets::WalletInfoWithProgress; -use crate::{ - calc_progress, - checker::TxChecker, - client::{BlockMeta, NumBlockMeta, TxEntry, BlockchainInfo}, - config::ExtendedNetwork, - deserialize_base64, serialize_base64, - source::BitcoinRpc, - wallets::{ - AddressKind, ListNumsResponse, ListSpacesResponse, RpcWallet, TxInfo, TxResponse, - WalletCommand, WalletResponse, - }, -}; -use crate::store::chain::{Chain, COMMIT_BLOCK_INTERVAL, CACHED_SNAPSHOT_LOOKBACK}; -use crate::store::Sha256; -use crate::store::spaces::RolloutEntry; pub(crate) type Responder = oneshot::Sender; @@ -87,7 +88,6 @@ pub struct ServerInfo { pub progress: f32, } - #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] pub struct ChainInfo { @@ -219,7 +219,6 @@ pub struct AsyncChainState { sender: mpsc::Sender, } - #[rpc(server, client)] pub trait Rpc { #[method(name = "getserverinfo")] @@ -241,27 +240,24 @@ pub trait Rpc { async fn get_spaceout(&self, outpoint: OutPoint) -> Result, ErrorObjectOwned>; #[method(name = "getnum")] - async fn get_num( - &self, - subject: Subject, - ) -> Result, ErrorObjectOwned>; + async fn get_num(&self, subject: Subject) -> Result, ErrorObjectOwned>; #[method(name = "getnumowner")] - async fn get_num_owner( - &self, - subject: Subject, - ) -> Result, ErrorObjectOwned>; + async fn get_num_owner(&self, subject: Subject) -> Result, ErrorObjectOwned>; #[method(name = "getnumout")] async fn get_numout(&self, outpoint: OutPoint) -> Result, ErrorObjectOwned>; #[method(name = "getcommitment")] - async fn get_commitment(&self, subject: Subject, root: Option) -> Result, ErrorObjectOwned>; + async fn get_commitment( + &self, + subject: Subject, + root: Option, + ) -> Result, ErrorObjectOwned>; #[method(name = "getdelegation")] async fn get_delegation(&self, subject: Subject) -> Result, ErrorObjectOwned>; - #[method(name = "getdelegator")] async fn get_delegator(&self, subject: Subject) -> Result, ErrorObjectOwned>; @@ -301,7 +297,6 @@ pub trait Rpc { #[method(name = "walletimport")] async fn wallet_import(&self, wallet: WalletExport) -> Result<(), ErrorObjectOwned>; - #[method(name = "walletcanoperate")] async fn wallet_can_operate( &self, @@ -327,7 +322,7 @@ pub trait Rpc { #[method(name = "walletgetinfo")] async fn wallet_get_info(&self, name: &str) - -> Result; + -> Result; #[method(name = "walletexport")] async fn wallet_export(&self, name: &str) -> Result; @@ -447,7 +442,11 @@ pub trait Rpc { /// Debug method to set a space's expire height (regtest only) #[method(name = "debugsetexpireheight")] - async fn debug_set_expire_height(&self, space: &str, expire_height: u32) -> Result<(), ErrorObjectOwned>; + async fn debug_set_expire_height( + &self, + space: &str, + expire_height: u32, + ) -> Result<(), ErrorObjectOwned>; } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -604,7 +603,6 @@ pub struct RpcServerImpl { client: reqwest::Client, } - /// Combined proof result for a chain proof request containing subtrees from both /// spaces and ptrs trees at the same snapshot height. #[derive(Clone, Serialize, Deserialize)] @@ -679,7 +677,11 @@ impl WalletManager { Ok(export) } - pub async fn create_wallet(&self, client: &reqwest::Client, name: &str) -> anyhow::Result { + pub async fn create_wallet( + &self, + client: &reqwest::Client, + name: &str, + ) -> anyhow::Result { let mnemonic: GeneratedKey<_, Tap> = Mnemonic::generate((WordCount::Words12, Language::English)) .map_err(|_| anyhow!("Mnemonic generation error"))?; @@ -690,7 +692,12 @@ impl WalletManager { Ok(mnemonic.to_string()) } - pub async fn recover_wallet(&self, client: &reqwest::Client, name: &str, mnemonic: &str) -> anyhow::Result<()> { + pub async fn recover_wallet( + &self, + client: &reqwest::Client, + name: &str, + mnemonic: &str, + ) -> anyhow::Result<()> { let start_block = self.get_wallet_start_block(client).await?; self.setup_new_wallet(name.to_string(), mnemonic.to_string(), start_block)?; self.load_wallet(name).await?; @@ -836,13 +843,13 @@ impl WalletManager { async fn get_wallet_start_block(&self, client: &reqwest::Client) -> anyhow::Result { let count: i32 = self .rpc - .send_json(&client, &self.rpc.get_block_count()) + .send_json(client, &self.rpc.get_block_count()) .await?; let height = std::cmp::max(count - 1, 0) as u32; let hash = self .rpc - .send_json(&client, &self.rpc.get_block_hash(height)) + .send_json(client, &self.rpc.get_block_hash(height)) .await?; Ok(BlockId { height, hash }) @@ -913,16 +920,19 @@ impl RpcServerImpl { let mut module = self.clone().into_rpc(); let methods: Vec = module.method_names().map(|s| s.to_string()).collect(); - module.register_method("rpc.discover", move |_, _| { - serde_json::json!({ "methods": methods }) - }).expect("register rpc.discover"); + module + .register_method( + "rpc.discover", + move |_, _| serde_json::json!({ "methods": methods }), + ) + .expect("register rpc.discover"); #[cfg(feature = "schema")] { let spec = crate::rpc_schema::full_spec(); - module.register_method("rpc.schema", move |_, _| { - spec.clone() - }).expect("register rpc.schema"); + module + .register_method("rpc.schema", move |_, _| spec.clone()) + .expect("register rpc.schema"); } let handle = listener.start(module); @@ -1028,7 +1038,11 @@ impl RpcServer for RpcServerImpl { Ok(spaceout) } - async fn get_commitment(&self, subject: Subject, root: Option) -> Result, ErrorObjectOwned> { + async fn get_commitment( + &self, + subject: Subject, + root: Option, + ) -> Result, ErrorObjectOwned> { let c = self .store .get_commitment(subject, root.map(|r| *r.as_ref())) @@ -1055,7 +1069,6 @@ impl RpcServer for RpcServerImpl { Ok(delegator) } - async fn check_package( &self, txs: Vec, @@ -1152,7 +1165,7 @@ impl RpcServer for RpcServerImpl { subject: Subject, message: Bytes, ) -> Result { - self.wallet(&wallet) + self.wallet(wallet) .await? .send_sign_schnorr(subject, message.to_vec()) .await @@ -1165,7 +1178,7 @@ impl RpcServer for RpcServerImpl { wallet: &str, subject: Subject, ) -> Result { - self.wallet(&wallet) + self.wallet(wallet) .await? .send_can_operate(subject) .await @@ -1189,7 +1202,7 @@ impl RpcServer for RpcServerImpl { &self, wallet: &str, ) -> Result { - self.wallet(&wallet) + self.wallet(wallet) .await? .send_get_info() .await @@ -1228,7 +1241,7 @@ impl RpcServer for RpcServerImpl { request: RpcWalletTxBuilder, ) -> Result { let result = self - .wallet(&wallet) + .wallet(wallet) .await? .send_batch_tx(request) .await @@ -1241,7 +1254,7 @@ impl RpcServer for RpcServerImpl { wallet: &str, kind: AddressKind, ) -> Result { - self.wallet(&wallet) + self.wallet(wallet) .await? .send_get_new_address(kind) .await @@ -1253,7 +1266,7 @@ impl RpcServer for RpcServerImpl { wallet: &str, kind: AddressKind, ) -> Result { - self.wallet(&wallet) + self.wallet(wallet) .await? .send_increment_address(kind) .await @@ -1267,7 +1280,7 @@ impl RpcServer for RpcServerImpl { fee_rate: FeeRate, skip_tx_check: bool, ) -> Result, ErrorObjectOwned> { - self.wallet(&wallet) + self.wallet(wallet) .await? .send_fee_bump(txid, fee_rate, skip_tx_check) .await @@ -1281,7 +1294,7 @@ impl RpcServer for RpcServerImpl { fee_rate: Option, skip_tx_check: bool, ) -> Result { - self.wallet(&wallet) + self.wallet(wallet) .await? .send_buy(listing, fee_rate, skip_tx_check) .await @@ -1294,7 +1307,7 @@ impl RpcServer for RpcServerImpl { space: String, amount: u64, ) -> Result { - self.wallet(&wallet) + self.wallet(wallet) .await? .send_sell(space, amount) .await @@ -1332,7 +1345,7 @@ impl RpcServer for RpcServerImpl { count: usize, skip: usize, ) -> Result, ErrorObjectOwned> { - self.wallet(&wallet) + self.wallet(wallet) .await? .send_list_transactions(count, skip) .await @@ -1345,7 +1358,7 @@ impl RpcServer for RpcServerImpl { outpoint: OutPoint, fee_rate: FeeRate, ) -> Result { - self.wallet(&wallet) + self.wallet(wallet) .await? .send_force_spend(outpoint, fee_rate) .await @@ -1356,7 +1369,7 @@ impl RpcServer for RpcServerImpl { &self, wallet: &str, ) -> Result { - self.wallet(&wallet) + self.wallet(wallet) .await? .send_list_spaces() .await @@ -1369,7 +1382,7 @@ impl RpcServer for RpcServerImpl { kind: Option, ) -> Result { let external = kind.as_deref() == Some("external"); - self.wallet(&wallet) + self.wallet(wallet) .await? .send_list_nums(external) .await @@ -1380,7 +1393,7 @@ impl RpcServer for RpcServerImpl { &self, wallet: &str, ) -> Result, ErrorObjectOwned> { - self.wallet(&wallet) + self.wallet(wallet) .await? .send_list_unspent() .await @@ -1388,7 +1401,7 @@ impl RpcServer for RpcServerImpl { } async fn wallet_list_bidouts(&self, wallet: &str) -> Result, ErrorObjectOwned> { - self.wallet(&wallet) + self.wallet(wallet) .await? .send_list_bidouts() .await @@ -1396,34 +1409,42 @@ impl RpcServer for RpcServerImpl { } async fn wallet_get_balance(&self, wallet: &str) -> Result { - self.wallet(&wallet) + self.wallet(wallet) .await? .send_get_balance() .await .map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::)) } - async fn get_fallback(&self, subject: Subject) -> Result, ErrorObjectOwned> { - let data = match &subject { - Subject::Label(label) if !label.is_numeric() => { - let space_hash = SpaceKey::from(Sha256::hash(label.as_ref())); - let fso = self.store.get_space(space_hash).await - .map_err(|e| ErrorObjectOwned::owned(-1, e.to_string(), None::))?; - fso.and_then(|fso| { - if let Some(space) = &fso.spaceout.space { - if let Covenant::Transfer { data, .. } = &space.covenant { + async fn get_fallback( + &self, + subject: Subject, + ) -> Result, ErrorObjectOwned> { + let data = + match &subject { + Subject::Label(label) if !label.is_numeric() => { + let space_hash = SpaceKey::from(Sha256::hash(label.as_ref())); + let fso = + self.store.get_space(space_hash).await.map_err(|e| { + ErrorObjectOwned::owned(-1, e.to_string(), None::) + })?; + fso.and_then(|fso| { + if let Some(space) = &fso.spaceout.space + && let Covenant::Transfer { data, .. } = &space.covenant + { return data.as_ref().map(|b| b.clone().to_vec()); } - } - None - }) - } - _ => { - let fpt = self.store.get_ptr(subject).await - .map_err(|e| ErrorObjectOwned::owned(-1, e.to_string(), None::))?; - fpt.and_then(|fpt| fpt.numout.num.data.map(|b| b.to_vec())) - } - }; + None + }) + } + _ => { + let fpt = + self.store.get_ptr(subject).await.map_err(|e| { + ErrorObjectOwned::owned(-1, e.to_string(), None::) + })?; + fpt.and_then(|fpt| fpt.numout.num.data.map(|b| b.to_vec())) + } + }; match data { None => Ok(None), @@ -1440,16 +1461,28 @@ impl RpcServer for RpcServerImpl { } } - async fn debug_set_expire_height(&self, space: &str, expire_height: u32) -> Result<(), ErrorObjectOwned> { + async fn debug_set_expire_height( + &self, + space: &str, + expire_height: u32, + ) -> Result<(), ErrorObjectOwned> { // Only allow on regtest - let info = self.store.get_server_info().await + let info = self + .store + .get_server_info() + .await .map_err(|e| ErrorObjectOwned::owned(-1, e.to_string(), None::))?; if info.network != ExtendedNetwork::Regtest { - return Err(ErrorObjectOwned::owned(-1, "debug_set_expire_height is only available on regtest", None::)); + return Err(ErrorObjectOwned::owned( + -1, + "debug_set_expire_height is only available on regtest", + None::, + )); } - let space_label = SLabel::from_str(space) - .map_err(|e| ErrorObjectOwned::owned(-1, format!("Invalid space name: {}", e), None::))?; + let space_label = SLabel::from_str(space).map_err(|e| { + ErrorObjectOwned::owned(-1, format!("Invalid space name: {}", e), None::) + })?; self.store .debug_set_expire_height(space_label, expire_height) @@ -1470,7 +1503,7 @@ impl AsyncChainState { rpc: &BitcoinRpc, ) -> Result, anyhow::Error> { let info: serde_json::Value = rpc - .send_json(client, &rpc.get_raw_transaction(&txid, true)) + .send_json(client, &rpc.get_raw_transaction(txid, true)) .await .map_err(|e| anyhow!("Could not retrieve tx ({})", e))?; @@ -1478,13 +1511,8 @@ impl AsyncChainState { BlockHash::from_str(info.get("blockhash").and_then(|t| t.as_str()).ok_or_else( || anyhow!("Could not retrieve block hash for tx (is it in the mempool?)"), )?)?; - let block = Self::get_indexed_block( - state, - HeightOrHash::Hash(block_hash), - client, - rpc, - ) - .await?; + let block = + Self::get_indexed_block(state, HeightOrHash::Hash(block_hash), client, rpc).await?; Ok(block .block_meta @@ -1633,16 +1661,22 @@ impl AsyncChainState { let _ = resp.send(result); } ChainStateCommand::GetNum { subject, resp } => { - let result = resolve_num_id(state, &subject) - .and_then(|id| state.get_num_info(&id)); + let result = resolve_num_id(state, &subject).and_then(|id| state.get_num_info(&id)); let _ = resp.send(result); } ChainStateCommand::GetNumOutpoint { subject, resp } => { - let result = resolve_num_id(state, &subject) - .and_then(|id| state.get_num_outpoint_by_id(&id).context("could not fetch numout")); + let result = resolve_num_id(state, &subject).and_then(|id| { + state + .get_num_outpoint_by_id(&id) + .context("could not fetch numout") + }); let _ = resp.send(result); } - ChainStateCommand::GetCommitment { subject, root, resp } => { + ChainStateCommand::GetCommitment { + subject, + root, + resp, + } => { let result = get_commitment(state, &subject, root); let _ = resp.send(result); } @@ -1651,9 +1685,11 @@ impl AsyncChainState { let _ = resp.send(result); } ChainStateCommand::GetDelegator { subject, resp } => { - let result = resolve_num_id(state, &subject) - .and_then(|id| state.get_delegator(&DelegatorKey::from_id::(id)) - .map_err(|e| anyhow!("could not get delegator: {}", e))); + let result = resolve_num_id(state, &subject).and_then(|id| { + state + .get_delegator(&DelegatorKey::from_id::(id)) + .map_err(|e| anyhow!("could not get delegator: {}", e)) + }); let _ = resp.send(result); } ChainStateCommand::GetNumOut { outpoint, resp } => { @@ -1666,18 +1702,14 @@ impl AsyncChainState { height_or_hash, resp, } => { - let res = - Self::get_indexed_block(state, height_or_hash, client, rpc) - .await; + let res = Self::get_indexed_block(state, height_or_hash, client, rpc).await; let _ = resp.send(res); } ChainStateCommand::GetNumBlockMeta { height_or_hash, resp, } => { - let res = - Self::get_indexed_ptr_block(state, height_or_hash, client, rpc) - .await; + let res = Self::get_indexed_ptr_block(state, height_or_hash, client, rpc).await; let _ = resp.send(res); } ChainStateCommand::GetTxMeta { txid, resp } => { @@ -1693,11 +1725,14 @@ impl AsyncChainState { _ = resp.send(rollouts); } ChainStateCommand::VerifyListing { listing, resp } => { - _ = resp.send( - SpacesWallet::verify_listing::(state, &listing).map(|_| ()), - ); + _ = resp.send(SpacesWallet::verify_listing::(state, &listing).map(|_| ())); } - ChainStateCommand::VerifySchnorr { subject, message, signature, resp } => { + ChainStateCommand::VerifySchnorr { + subject, + message, + signature, + resp, + } => { let result = (|| { let sig = schnorr::Signature::from_slice(&signature) .map_err(|_| anyhow!("Invalid signature format"))?; @@ -1719,8 +1754,16 @@ impl AsyncChainState { ChainStateCommand::GetRootAnchors { resp } => { _ = resp.send(Self::handle_get_anchor(anchors_path, state)); } - ChainStateCommand::DebugSetExpireHeight { space, expire_height, resp } => { - _ = resp.send(Self::handle_debug_set_expire_height(state, space, expire_height)); + ChainStateCommand::DebugSetExpireHeight { + space, + expire_height, + resp, + } => { + _ = resp.send(Self::handle_debug_set_expire_height( + state, + space, + expire_height, + )); } } } @@ -1731,18 +1774,26 @@ impl AsyncChainState { expire_height: u32, ) -> anyhow::Result<()> { let space_key = SpaceKey::from(Sha256::hash(space.as_ref())); - let outpoint = state.get_space_outpoint(&space_key)? + let outpoint = state + .get_space_outpoint(&space_key)? .ok_or_else(|| anyhow::anyhow!("Space not found: {}", space))?; - let mut spaceout = state.get_spaceout(&outpoint)? + let mut spaceout = state + .get_spaceout(&outpoint)? .ok_or_else(|| anyhow::anyhow!("Spaceout not found for outpoint"))?; // Update expire_height in the covenant if let Some(ref mut space_data) = spaceout.space { match &mut space_data.covenant { - Covenant::Transfer { expire_height: ref mut eh, .. } => { + Covenant::Transfer { + expire_height: eh, .. + } => { *eh = expire_height; } - _ => return Err(anyhow::anyhow!("Space is not in Transfer covenant (not owned)")), + _ => { + return Err(anyhow::anyhow!( + "Space is not in Transfer covenant (not owned)" + )); + } } } else { return Err(anyhow::anyhow!("SpaceOut has no space data")); @@ -1761,9 +1812,9 @@ impl AsyncChainState { if let Some(anchors_path) = anchors_path { let anchors: Vec = serde_json::from_reader( File::open(anchors_path) - .or_else(|e| Err(anyhow!("Could not open anchors file: {}", e)))?, + .map_err(|e| anyhow!("Could not open anchors file: {}", e))?, ) - .or_else(|e| Err(anyhow!("Could not read anchors file: {}", e)))?; + .map_err(|e| anyhow!("Could not read anchors file: {}", e))?; return Ok(anchors); } @@ -1773,7 +1824,10 @@ impl AsyncChainState { // Try to compute PTR root if we're past PTR genesis let ptrs_root = if state.can_scan_nums(meta.height) { - state.nums_mut().state.inner() + state + .nums_mut() + .state + .inner() .ok() .and_then(|s| s.compute_root().ok()) } else { @@ -1821,12 +1875,12 @@ impl AsyncChainState { let outpoint_key = OutpointKey::from_outpoint::(fso.outpoint()); space_tree_keys.insert(outpoint_key.into()); - if let Some(space) = &fso.spaceout.space { - if let Covenant::Transfer { expire_height, .. } = &space.covenant { - let last_update = expire_height - .saturating_sub(spaces_protocol::constants::RENEWAL_INTERVAL); - most_recent_update = std::cmp::max(most_recent_update, last_update); - } + if let Some(space) = &fso.spaceout.space + && let Covenant::Transfer { expire_height, .. } = &space.covenant + { + let last_update = + expire_height.saturating_sub(spaces_protocol::constants::RENEWAL_INTERVAL); + most_recent_update = std::cmp::max(most_recent_update, last_update); } let id = NumId::from_spk::(fso.spaceout.script_pubkey); @@ -1838,44 +1892,45 @@ impl AsyncChainState { NumKeyKind::Num(numeric) => { let id = state.get_num_id(&numeric)?; if let Some(id) = id { - let fpt = state.get_num_info(&id)? + let fpt = state + .get_num_info(&id)? .expect("num id must exist if numeric exists"); - num_tree_keys.insert( - NumOutpointKey::from_outpoint::(fpt.outpoint()).into() - ); - most_recent_update = std::cmp::max(most_recent_update, fpt.numout.num.last_update); + num_tree_keys + .insert(NumOutpointKey::from_outpoint::(fpt.outpoint()).into()); + most_recent_update = + std::cmp::max(most_recent_update, fpt.numout.num.last_update); // insert delegate information let operator_id = NumId::from_spk::(fpt.numout.script_pubkey); let operator = state.get_num_info(&operator_id)?; if let Some(operator) = operator { num_tree_keys.insert( - NumOutpointKey::from_outpoint::(operator.outpoint()).into() + NumOutpointKey::from_outpoint::(operator.outpoint()).into(), ); - most_recent_update = std::cmp::max(most_recent_update, operator.numout.num.last_update); - + most_recent_update = + std::cmp::max(most_recent_update, operator.numout.num.last_update); } else { num_tree_keys.insert(operator_id.into()); } - } } NumKeyKind::Id(id) => { if let Some(fpt) = state.get_num_info(&id)? { - num_tree_keys.insert( - NumOutpointKey::from_outpoint::(fpt.outpoint()).into() - ); - most_recent_update = std::cmp::max(most_recent_update, fpt.numout.num.last_update); + num_tree_keys + .insert(NumOutpointKey::from_outpoint::(fpt.outpoint()).into()); + most_recent_update = + std::cmp::max(most_recent_update, fpt.numout.num.last_update); // insert delegate information let operator_id = NumId::from_spk::(fpt.numout.script_pubkey); let operator = state.get_num_info(&operator_id)?; if let Some(operator) = operator { num_tree_keys.insert( - NumOutpointKey::from_outpoint::(operator.outpoint()).into() + NumOutpointKey::from_outpoint::(operator.outpoint()).into(), ); - most_recent_update = std::cmp::max(most_recent_update, operator.numout.num.last_update); + most_recent_update = + std::cmp::max(most_recent_update, operator.numout.num.last_update); } else { num_tree_keys.insert(operator_id.into()); } @@ -1886,10 +1941,10 @@ impl AsyncChainState { } NumKeyKind::Commitment(k) => { num_tree_keys.insert(k.into()); - }, + } NumKeyKind::CommitmentTip(k) => { num_tree_keys.insert(k.into()); - }, + } } } @@ -1900,23 +1955,25 @@ impl AsyncChainState { let blocks_remaining = next_commit - tip.height; return Err(anyhow!( "Cannot prove: data updated at block {} is not yet committed. Try again in {} block(s)", - most_recent_update, blocks_remaining + most_recent_update, + blocks_remaining )); } - let num_tree_keys : Vec<_> = num_tree_keys.into_iter().collect(); - let space_tree_keys : Vec<_> = space_tree_keys.into_iter().collect(); + let num_tree_keys: Vec<_> = num_tree_keys.into_iter().collect(); + let space_tree_keys: Vec<_> = space_tree_keys.into_iter().collect(); let cached_height = Self::cached_snapshot_height(tip.height); - let use_cached = !prefer_recent - && cached_height.is_some_and(|h| most_recent_update <= h); + let use_cached = !prefer_recent && cached_height.is_some_and(|h| most_recent_update <= h); let (spaces_proof, spaces_root, block_anchor, ptrs_proof, ptrs_root) = if use_cached { let height = cached_height.unwrap(); let snapshot = state.snapshot_at(height)?; let spaces_anchor: ChainAnchor = snapshot.spaces.metadata().try_into()?; - let spaces_proof = snapshot.spaces.prove(&space_tree_keys, ProofType::Standard)?; + let spaces_proof = snapshot + .spaces + .prove(&space_tree_keys, ProofType::Standard)?; let spaces_root = spaces_proof.compute_root()?; let ptrs_anchor: ChainAnchor = snapshot.nums.metadata().try_into()?; @@ -1929,7 +1986,13 @@ impl AsyncChainState { let ptrs_proof = snapshot.nums.prove(&num_tree_keys, ProofType::Standard)?; let ptrs_root = ptrs_proof.compute_root()?; - (spaces_proof, spaces_root, spaces_anchor, ptrs_proof, ptrs_root) + ( + spaces_proof, + spaces_root, + spaces_anchor, + ptrs_proof, + ptrs_root, + ) } else { let spaces_snapshot = state.spaces_inner()?; let spaces_root = spaces_snapshot.compute_root()?; @@ -1948,7 +2011,13 @@ impl AsyncChainState { let ptrs_proof = ptrs_snapshot.prove(&num_tree_keys, ProofType::Standard)?; let ptrs_root = ptrs_proof.compute_root()?; - (spaces_proof, spaces_root, spaces_anchor, ptrs_proof, ptrs_root) + ( + spaces_proof, + spaces_root, + spaces_anchor, + ptrs_proof, + ptrs_root, + ) }; let spaces_buf = spaces_proof.to_vec()?; @@ -2044,10 +2113,18 @@ impl AsyncChainState { resp_rx.await? } - pub async fn debug_set_expire_height(&self, space: SLabel, expire_height: u32) -> anyhow::Result<()> { + pub async fn debug_set_expire_height( + &self, + space: SLabel, + expire_height: u32, + ) -> anyhow::Result<()> { let (resp, resp_rx) = oneshot::channel(); self.sender - .send(ChainStateCommand::DebugSetExpireHeight { space, expire_height, resp }) + .send(ChainStateCommand::DebugSetExpireHeight { + space, + expire_height, + resp, + }) .await?; resp_rx.await? } @@ -2127,10 +2204,18 @@ impl AsyncChainState { resp_rx.await? } - pub async fn get_commitment(&self, subject: Subject, root: Option) -> anyhow::Result> { + pub async fn get_commitment( + &self, + subject: Subject, + root: Option, + ) -> anyhow::Result> { let (resp, resp_rx) = oneshot::channel(); self.sender - .send(ChainStateCommand::GetCommitment { subject, root, resp }) + .send(ChainStateCommand::GetCommitment { + subject, + root, + resp, + }) .await?; resp_rx.await? } @@ -2193,7 +2278,8 @@ fn resolve_num_id(state: &mut Chain, subject: &Subject) -> anyhow::Result Subject::NumId(id) => Ok(*id), Subject::Label(label) if label.is_numeric() => { let numeric: SNumeric = label.clone().try_into().unwrap(); - state.get_num_id(&numeric)? + state + .get_num_id(&numeric)? .ok_or_else(|| anyhow!("numeric '{}' not found", numeric)) } Subject::Label(_) => Err(anyhow!("expected a num id or numeric, not a space")), @@ -2259,12 +2345,12 @@ async fn get_server_info( }) } - fn resolve_label(state: &mut Chain, subject: &Subject) -> anyhow::Result { match subject { Subject::Label(label) => Ok(label.clone()), Subject::NumId(id) => { - let info = state.get_num_info(id)? + let info = state + .get_num_info(id)? .ok_or_else(|| anyhow!("num id '{}' not found", id))?; Ok(info.numout.num.name.to_slabel()) } @@ -2281,18 +2367,22 @@ fn get_delegation(state: &mut Chain, subject: &Subject) -> anyhow::Result