diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8c0879d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +# Set update schedule for GitHub Actions +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" + cooldown: + default-days: 7 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..fa09f6d --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,20 @@ + + +### Description + + + +### Notes to the reviewers + + + +### Changelog notice + + + + +### Before submitting + +- [ ] I followed the [contribution guidelines](https://github.com/bitcoindevkit/electrum_streaming_client/blob/master/CONTRIBUTING.md) +- [ ] This PR breaks the existing API diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 0000000..2ce977a --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,38 @@ +name: Audit + +# Audits the committed Cargo.lock against RustSec nightly, after dependency +# or audit configuration changes on master, and on manual dispatch. +# On master, creates or updates issues for findings and closes resolved ones. +# Findings are tracked through issues; this workflow is not a PR gate. + +on: + push: + branches: [master] + paths: + - ".github/workflows/audit.yml" + - "**/Cargo.toml" + - "**/Cargo.lock" + - "**/audit.toml" + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +permissions: + contents: read + +jobs: + audit: + name: Audit + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + issues: write # Create and close advisory issues. + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Audit Rust dependencies + uses: actions-rust-lang/audit@72c09e02f132669d52284a3323acdb503cfc1a24 # v1.2.7 diff --git a/.github/workflows/code_coverage.yml b/.github/workflows/code_coverage.yml new file mode 100644 index 0000000..a8957f8 --- /dev/null +++ b/.github/workflows/code_coverage.yml @@ -0,0 +1,80 @@ +name: Code Coverage + +# Combines coverage from both feature configurations, including doctests and +# branches. LCOV and HTML reports are saved as an artifact; Codecov upload +# errors do not fail the job. Public fork PRs can upload without a token. + +on: + pull_request: + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: "1" + +jobs: + Coverage: + name: Coverage + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 + with: + toolchain: nightly-2026-09-01 + components: llvm-tools-preview + cache: true + cache-key: coverage + rustflags: "" + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@d438492cf8a250514fa2d34b30bc3c0dc37c65ff # v2.87.8 + with: + tool: cargo-llvm-cov@0.9.1 + + - name: Generate coverage + run: | + cargo llvm-cov clean --workspace + + cargo llvm-cov --locked --no-default-features \ + --no-report --doctests --branch + cargo llvm-cov --locked --all-features \ + --no-report --doctests --branch + + cargo llvm-cov report --doctests --branch \ + --lcov --output-path lcov.info + cargo llvm-cov report --doctests --branch --html + + - name: Upload coverage artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: coverage-report + path: | + lcov.info + target/llvm-cov/html + if-no-files-found: error + retention-days: 14 + + - name: Codecov upload + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + version: v11.3.1 + files: ./lcov.info + flags: rust + name: codecov-electrum-streaming-client + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml new file mode 100644 index 0000000..313365c --- /dev/null +++ b/.github/workflows/cont_integration.yml @@ -0,0 +1,192 @@ +name: CI + +# Builds and tests both feature configurations on MSRV and pinned Rust. +# Checks formatting, Clippy and documentation with the pinned toolchain. +# Checks that pull request commits are signed. +# Latest-dependency compatibility runs separately in latest_deps.yml. + +on: + pull_request: + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: "1" + +jobs: + build-test: + name: Build and test (${{ matrix.rust }}, ${{ matrix.features }}) + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + rust: [msrv, pinned] + features: ["--no-default-features", "--all-features"] + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Read MSRV + if: matrix.rust == 'msrv' + id: msrv + run: | + msrv=$(sed -n 's/^rust-version = "\(.*\)"/\1/p' Cargo.toml) + if [ -z "$msrv" ]; then + echo "failed to read package rust-version from Cargo.toml" >&2 + exit 1 + fi + echo "msrv=$msrv" >> "$GITHUB_OUTPUT" + + # MSRV overrides rust-toolchain.toml; an empty toolchain uses its pinned version. + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 + with: + toolchain: ${{ matrix.rust == 'msrv' && steps.msrv.outputs.msrv || '' }} + cache: true + cache-key: ${{ matrix.rust }}-${{ matrix.features }} + rustflags: "" + + - name: Pin dependencies for MSRV + if: matrix.rust == 'msrv' + run: ./ci/pin-msrv.sh + + - name: Build and test + env: + FEATURES: ${{ matrix.features }} + run: | + cargo build --locked --lib "$FEATURES" + cargo test --locked --all-targets "$FEATURES" + cargo test --locked --doc "$FEATURES" + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 + with: + cache: false + rustflags: "" + - name: Check formatting + run: cargo fmt --all --check + + clippy: + name: Clippy (${{ matrix.features }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + features: ["--no-default-features", "--all-features"] + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 + with: + cache: true + cache-key: clippy-${{ matrix.features }} + rustflags: "" + - name: Clippy + env: + FEATURES: ${{ matrix.features }} + run: cargo clippy --locked --all-targets "$FEATURES" -- -D warnings + + docs: + name: Docs (${{ matrix.features }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + features: ["--no-default-features", "--all-features"] + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 + with: + cache: true + cache-key: docs-${{ matrix.features }} + rustflags: "" + - name: Check docs + env: + RUSTDOCFLAGS: "-D warnings" + FEATURES: ${{ matrix.features }} + run: cargo doc --locked --no-deps "$FEATURES" + + signed-commits: + name: Signed commits + # PRs only; push/workflow_dispatch skip this job (treated as skipped by CI success). + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - name: Check commits are signed + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: ./ci/check-signed-commits.sh "$BASE_SHA" "$HEAD_SHA" + + CI-success: + name: CI success + # Always evaluate results so failed or cancelled jobs cannot skip the required check. + if: ${{ always() }} + needs: [build-test, fmt, clippy, docs, signed-commits] + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Require all needed jobs + env: + BUILD_TEST: ${{ needs.build-test.result }} + FMT: ${{ needs.fmt.result }} + CLIPPY: ${{ needs.clippy.result }} + DOCS: ${{ needs.docs.result }} + SIGNED_COMMITS: ${{ needs.signed-commits.result }} + run: | + failed=0 + check() { + printf '%s: %s\n' "$1" "$2" + if [ "$2" != success ]; then + failed=1 + fi + } + # PR-only job: skipped on push is OK. + check_pr_only() { + printf '%s: %s\n' "$1" "$2" + if [ "$2" != success ] && [ "$2" != skipped ]; then + failed=1 + fi + } + check build-test "$BUILD_TEST" + check fmt "$FMT" + check clippy "$CLIPPY" + check docs "$DOCS" + check_pr_only signed-commits "$SIGNED_COMMITS" + exit "$failed" diff --git a/.github/workflows/latest_deps.yml b/.github/workflows/latest_deps.yml new file mode 100644 index 0000000..dfebd91 --- /dev/null +++ b/.github/workflows/latest_deps.yml @@ -0,0 +1,52 @@ +name: Latest dependencies + +# Weekly compatibility check: `cargo update` the whole graph and build/test +# on the current stable compiler. A failure here can be caused by a new rustc +# or by new dependency releases; it is informational and not part of the +# required CI success aggregate. + +on: + schedule: + - cron: "0 3 * * 1" + workflow_dispatch: + +permissions: + contents: read + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: "1" + +jobs: + latest: + name: Latest dependencies + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 + with: + toolchain: stable + cache: true + cache-key: latest-stable + rustflags: "" + + - name: Update dependencies + run: cargo update + + - name: Build and test (no default features) + run: | + cargo build --locked --lib --no-default-features + cargo test --locked --all-targets --no-default-features + cargo test --locked --doc --no-default-features + + - name: Build and test (all features) + run: | + cargo build --locked --lib --all-features + cargo test --locked --all-targets --all-features + cargo test --locked --doc --all-features diff --git a/.github/workflows/workflow_checks.yml b/.github/workflows/workflow_checks.yml new file mode 100644 index 0000000..37e13de --- /dev/null +++ b/.github/workflows/workflow_checks.yml @@ -0,0 +1,60 @@ +name: Workflow checks + +# Actionlint plus blocking Zizmor. SARIF is also uploaded for the security +# dashboard; reporting failures are nonblocking and cannot hide findings. + +on: + pull_request: + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + workflow-checks: + name: Workflow checks + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + security-events: write + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Install Actionlint and Zizmor + uses: taiki-e/install-action@d438492cf8a250514fa2d34b30bc3c0dc37c65ff # v2.87.8 + with: + tool: actionlint@1.7.12,zizmor@1.30.0 + + - name: Actionlint + run: actionlint + + - name: Zizmor + if: '!cancelled()' + run: zizmor --format=github . + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Zizmor SARIF + if: '!cancelled()' + continue-on-error: true + run: zizmor --format=sarif . > results.sarif + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload SARIF file + if: '!cancelled()' + continue-on-error: true + uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 + with: + sarif_file: results.sarif + category: zizmor diff --git a/.gitignore b/.gitignore index 0104787..15bd05e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ debug/ target/ +# Coverage reports +lcov.info + # These are backup files generated by rustfmt **/*.rs.bk diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3b4c331 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,154 @@ +Contributing to electrum_streaming_client +============================== + +The BDK project operates an open contributor model where anyone is welcome to +contribute towards development in the form of peer review, documentation, +testing and patches. + +Anyone is invited to contribute without regard to technical experience, +"expertise", OSS experience, age, or other concern. However, the development of +cryptocurrencies demands a high-level of rigor, adversarial thinking, thorough +testing and risk-minimization. +Any bug may cost users real money. That being said, we deeply welcome people +contributing for the first time to an open source project or picking up Rust while +contributing. Don't be shy, you'll learn. + +Communications Channels +----------------------- + +Communication about BDK happens primarily on the [BDK Discord](https://discord.gg/dstn4dQ). + +Discussion about code base improvements happens in GitHub [issues](https://github.com/bitcoindevkit/electrum_streaming_client/issues) and +on [pull requests](https://github.com/bitcoindevkit/electrum_streaming_client/pulls). + +Contribution Workflow +--------------------- + +The codebase is maintained using the "contributor workflow" where everyone +without exception contributes patch proposals using "pull requests". This +facilitates social contribution, easy testing and peer review. + +To contribute a patch, the workflow is as follows: + + 1. Fork Repository + 2. Create topic branch + 3. Commit patches + +In general commits should be atomic and diffs should be easy to read. +For this reason do not mix any formatting fixes or code moves with actual code +changes. Further, each commit, individually, should compile and pass tests, in +order to ensure git bisect and other automated tools function properly. + +When adding a new feature, thought must be given to the long term technical +debt. +Every new feature should be covered by functional tests where possible. + +When refactoring, structure your PR to make it easy to review and don't +hesitate to split it into multiple small, focused PRs. + +The Minimum Supported Rust Version is **1.70.0** (enforced by our CI). + +CI uses [`ci/pin-msrv.sh`](ci/pin-msrv.sh) to pin dependencies for MSRV testing. +Run it only in disposable checkouts; do not commit its `Cargo.lock` changes. + +Commits should cover both the issue fixed and the solution's rationale. +These [guidelines](https://chris.beams.io/posts/git-commit/) should be kept in mind. Commit messages follow the ["Conventional Commits 1.0.0"](https://www.conventionalcommits.org/en/v1.0.0/) to make commit histories easier to read by humans and automated tools. All commits must be [GPG signed](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits). +CI checks signature presence on every pull request commit (`ci/check-signed-commits.sh`). + +To facilitate communication with other contributors, the project is making use +of GitHub's "assignee" field. First check that no one is assigned and then +comment suggesting that you're working on it. If someone is already assigned, +don't hesitate to ask if the assigned party or previous commenter are still +working on it if it has been awhile. + +Pull Request Checklist +---------------------- + +By opening a pull request, you are confirming that: + +- All commits are [GPG signed](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits). +- The PR description links to the issue it solves, if one exists. +- `just pre-push` (alias `just p`) was run before pushing. This checks + formatting, checks that it compiles, runs clippy, runs tests, and checks docs. + Run `just fmt` to format the code. +- For new features, tests covering the new functionality have been added. +- For new features, documentation for the new functionality has been added. +- For bug fixes, tests reproducing the bug (and now passing) have been added. +- Any breaking change to the existing API is clearly called out in the PR description. + +Deprecation policy +------------------ + +Where possible, breaking existing APIs should be avoided. Instead, add new APIs and +use [`#[deprecated]`](https://github.com/rust-lang/rfcs/blob/master/text/1270-deprecation.md) +to discourage use of the old one. + +Deprecated APIs are typically maintained for one release cycle. In other words, an +API that has been deprecated with the 0.10 release can be expected to be removed in the +0.11 release. This allows for smoother upgrades without incurring too much technical +debt inside this library. + +If you deprecated an API as part of a contribution, we encourage you to "own" that API +and send a follow-up to remove it as part of the next release cycle. + +Peer review +----------- + +Anyone may participate in peer review which is expressed by comments in the +pull request. Typically reviewers will review the code for obvious errors, as +well as test out the patch set and opine on the technical merits of the patch. +PR should be reviewed first on the conceptual level before focusing on code +style or grammar fixes. + +To merge a PR we require all CI tests to pass, the PR has at least one approving review by a maintainer with write access, and reasonable criticisms have been addressed. + +Coding Conventions +------------------ + +This codebase uses spaces, not tabs. +Use `just fmt` to format code before committing. +This is also enforced by the CI. +All public items must be documented. We adhere to the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/about.html) with respect to documentation. + +The library is written using safe rust. Special consideration must be given to code which proposes an exception to the rule. + +All new features require testing. Tests should be unique and self-describing. If a test is in development or is broken or no longer useful, then a reason should be given for adding the `#[ignore]` attribute. + +Security +-------- + +Given the critical nature of BDK as a wallet library, we take security very seriously. + +For information on how to report security vulnerabilities, please refer to the [Security Policy](SECURITY.md). + +Testing +------- + +Related to the security aspect, BDK developers take testing very seriously. +Good test coverage of the codebase is an important goal. +Integration tests use `bdk_testenv`, which downloads and starts bitcoind and electrs. + +First Time Contributors +----------------------- + +If it is your first time contributing to the BDK family of libraries, welcome! We're glad to have you with us. If your +first (or few first) PRs are focused on very small fixes to documentation, however, they might not meet our threshold +for acceptance for first time contributors. + +Minor grammar and punctuation fixes aren't a good way to start contributing to a project, and instead we suggest you +start with something a little more substantial. It's better to find an issue where you can demonstrate some knowledge +of bitcoin or the code base, such as improving the substance of documentation, testing, or fixing some small issue +even if it's considered low priority. + +This being said we are always looking forward to working with new folks interested in contributing to our libraries. +If you are looking for issues to work on, check out the good first issue label and join our Discord server! + +Going further +------------- + +You may be interested by Jon Atacks guide on [How to review Bitcoin Core PRs](https://github.com/jonatack/bitcoin-development/blob/master/how-to-review-bitcoin-core-prs.md) +and [How to make Bitcoin Core PRs](https://github.com/jonatack/bitcoin-development/blob/master/how-to-make-bitcoin-core-prs.md). +While there are differences between the projects in terms of context and +maturity, many of the suggestions offered apply to this project. + +Overall, have fun :) diff --git a/Cargo.toml b/Cargo.toml index 5e3c99a..4b1e60d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.4.0" description = "Experimental but sane electrum client by @evanlinjin." license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.70" +rust-version = "1.70.0" repository = "https://github.com/bitcoindevkit/electrum_streaming_client" documentation = "https://docs.rs/electrum_streaming_client" readme = "README.md" diff --git a/README.md b/README.md index 620a76d..94ace0a 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ models. ## Example (async with Tokio) -```rust,no_run +```rust,ignore use electrum_streaming_client::{AsyncClient, Event}; use tokio::net::TcpStream; use futures::StreamExt; @@ -43,7 +43,7 @@ async fn main() -> anyhow::Result<()> { ## Optional Features -- `tokio`: Enables [`AsyncClient::new_tokio`] for use with Tokio-compatible streams. +- `tokio`: Enables `AsyncClient::new_tokio` for use with Tokio-compatible streams. ## License diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..c748282 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,20 @@ +# Security Policy + +To report security issues, either + +- send an email to `security AT bitcoindevkit DOT org` (not for support), or +- open a security advisory on GitHub at +[`https://github.com/bitcoindevkit/electrum_streaming_client/security/advisories`](https://github.com/bitcoindevkit/electrum_streaming_client/security/advisories). + +The following key may be used to communicate sensitive information to BDK via email: + +| Name | Fingerprint | +| ---- | ----------- | +| `security@bitcoindevkit.org` | `7416 BB25 5E60 E40D 482E 591B 7201 8930 A1FB 3444` | + +You can import the key by running the following command: +``` +gpg --keyserver hkps://keys.openpgp.org --recv-keys 7416BB255E60E40D482E591B72018930A1FB3444 +``` + +You can also download it from [our website](https://bitcoindevkit.org/foundation/pgp/#security-disclosures). diff --git a/ci/check-signed-commits.sh b/ci/check-signed-commits.sh new file mode 100755 index 0000000..43c8f25 --- /dev/null +++ b/ci/check-signed-commits.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -euo pipefail + +# Fail if any commit in BASE_SHA..HEAD_SHA is unsigned (`git log %G?` is `N`). +# Checks signature presence only; authenticity is not verified. +# Run from the repository root with a checkout that contains the full range: +# +# ./ci/check-signed-commits.sh + +BASE_SHA="${1:-}" +HEAD_SHA="${2:-}" + +if [ -z "$BASE_SHA" ] || [ -z "$HEAD_SHA" ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +COMMIT_SIGNATURES=$(git log --format="%H %G?" "${BASE_SHA}..${HEAD_SHA}" --) + +UNSIGNED=0 +while IFS=' ' read -r commit status; do + if [ "$status" = "N" ]; then + echo "Commit $commit is not signed." + UNSIGNED=$((UNSIGNED + 1)) + fi +done <<< "$COMMIT_SIGNATURES" + +if [ "$UNSIGNED" -gt 0 ]; then + echo "Error: $UNSIGNED commit(s) are not signed. See CONTRIBUTING.md." >&2 + exit 1 +fi + +echo "All commits are signed." diff --git a/ci/pin-msrv.sh b/ci/pin-msrv.sh new file mode 100755 index 0000000..aac8cad --- /dev/null +++ b/ci/pin-msrv.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -x +set -euo pipefail + +# Pin dependencies for MSRV builds and tests. Run from the repository root +# with the package's rust-version toolchain: +# +# RUSTUP_TOOLCHAIN=$(sed -n 's/^rust-version = "\(.*\)"/\1/p' Cargo.toml) ./ci/pin-msrv.sh +# +# Use a disposable checkout: this rewrites Cargo.lock. Do not commit the result. +# +# Keep CI compatibility pins here rather than restricting published dependencies +# solely to make the MSRV job pass. + +# home 0.5.11 requires Rust 1.81. +cargo update -p home --precise "0.5.5" + +# base64ct 1.7.x requires Rust 1.81 or newer. +cargo update -p base64ct --precise "1.6.0" + +# minreq 2.13.4 uses std::sync::LazyLock for rustls HTTPS, unavailable on MSRV. +cargo update -p minreq --precise "2.13.0" diff --git a/justfile b/justfile new file mode 100644 index 0000000..4b45780 --- /dev/null +++ b/justfile @@ -0,0 +1,43 @@ +alias b := build +alias c := check +alias f := fmt +alias t := test +alias p := pre-push +alias d := doc + +_default: + @just --list + +# Build the crate with the committed lockfile +build: + cargo build --locked + +# Format all code (modifies files) +fmt: + cargo fmt --all + +# Check formatting, library builds, Clippy, and commit signature +check: + cargo fmt --all --check + cargo build --locked --lib --no-default-features + cargo build --locked --lib --all-features + cargo clippy --locked --all-targets --no-default-features -- -D warnings + cargo clippy --locked --all-targets --all-features -- -D warnings + @[ "$(git log --pretty='format:%G?' -1 HEAD)" = "N" ] && \ + echo "\n⚠️ Unsigned commit: BDK requires that commits be signed." || \ + true + +# Run all-target tests and doctests under both feature configurations +test: + cargo test --locked --all-targets --no-default-features + cargo test --locked --doc --no-default-features + cargo test --locked --all-targets --all-features + cargo test --locked --doc --all-features + +# Check documentation under both feature configurations +doc: + RUSTDOCFLAGS="-D warnings" cargo doc --locked --no-deps --no-default-features + RUSTDOCFLAGS="-D warnings" cargo doc --locked --no-deps --all-features + +# Run the full pre-push suite without modifying tracked files +pre-push: check test doc diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..01a3bee --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.96.0" +components = ["clippy", "rustfmt"] diff --git a/src/client.rs b/src/client.rs index 2c55200..3d68f22 100644 --- a/src/client.rs +++ b/src/client.rs @@ -45,7 +45,7 @@ pub type BlockingEventReceiver = std::sync::mpsc::Receiver; /// over any transport that implements [`AsyncBufRead`] and [`AsyncWrite`]. /// /// To drive the client, you must poll the [`Future`] returned by [`AsyncClient::new`] or -/// [`AsyncClient::new_tokio`]. This worker future handles reading and writing to the transport, +/// `AsyncClient::new_tokio`. This worker future handles reading and writing to the transport, /// parsing server responses, and routing them to the internal state and event stream. /// /// Use the associated [`AsyncEventReceiver`] to receive [`Event`]s pushed by the server. @@ -53,7 +53,8 @@ pub type BlockingEventReceiver = std::sync::mpsc::Receiver; /// /// ### Constructors /// - [`AsyncClient::new`] is runtime-agnostic and works with any `futures`-based transport. -/// - [`AsyncClient::new_tokio`] enables integration with `tokio`-based I/O types. +/// - `AsyncClient::new_tokio` enables integration with `tokio`-based I/O types (requires the +/// `tokio` feature). /// /// [`Future`]: futures::Future /// [`Event`]: crate::Event