diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1a531e3646c..40131a47a17 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -9,7 +9,7 @@ body: - type: textarea attributes: label: Summary - description: Please provide a short summary of the bug, along with any information you feel relevant to replicate the bug. + description: Please provide a short summary of the bug, along with any information you feel is relevant to replicate the bug. validations: required: true - type: textarea @@ -34,7 +34,7 @@ body: - type: textarea attributes: label: Possible Solution - description: Suggest a fix/reason for the bug, or ideas how to implement the addition or change. + description: Suggest a fix/reason for the bug, or ideas on how to implement the addition or change. validations: required: false - type: textarea @@ -45,11 +45,11 @@ body: required: false - type: dropdown attributes: - label: Would you like to work on fixing this bug ? + label: Would you like to work on fixing this bug? description: Any contribution towards fixing the bug is greatly appreciated. We are more than happy to provide help on the process. options: - "Yes" - "No" - Maybe validations: - required: true \ No newline at end of file + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 29c54c335a4..c51267e140f 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ blank_issues_enabled: true contact_links: - - name: Question + - name: Technical Questions url: https://github.com/libp2p/rust-libp2p/discussions/new?category=q-a - about: Please ask questions in the rust-libp2p GitHub Discussions forum. - - name: Libp2p Discourse Forum + about: Please ask technical questions in the rust-libp2p GitHub Discussions forum. + - name: Community-wide libp2p Discussion url: https://discuss.libp2p.io - about: Discussions and questions related to multiple libp2p implementations. + about: Discussions and questions about the libp2p community. diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml index ed7aeb644b3..05330cf071c 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yml +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -21,11 +21,11 @@ body: required: true - type: dropdown attributes: - label: Are you planning to do it yourself in a pull request ? + label: Are you planning to do it yourself in a pull request? description: Any contribution is greatly appreciated. We are more than happy to provide help on the process. options: - "Yes" - "No" - Maybe validations: - required: true \ No newline at end of file + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 6fa3e638be8..45e1da2cad0 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -32,11 +32,11 @@ body: required: false - type: dropdown attributes: - label: Are you planning to do it yourself in a pull request ? + label: Are you planning to do it yourself in a pull request? description: Any contribution is greatly appreciated. We are more than happy to provide help on the process. options: - "Yes" - "No" - Maybe validations: - required: true \ No newline at end of file + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a0656e6162c..6c43a31d2c1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -22,6 +22,10 @@ updates: - "axum" - "tower" - "tower-http" + webrtc: + patterns: + - "rcgen" + - "webrtc" - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.github/mergify.yml b/.github/mergify.yml index c630d23cea0..38f025c7814 100644 --- a/.github/mergify.yml +++ b/.github/mergify.yml @@ -32,6 +32,17 @@ pull_request_rules: - base=master actions: queue: + name: default + + # Adds the Pr to the batch queue, so that we can run the interop tests. See the `external_prs` queue for more info. + - name: Add to batch merge queue + conditions: + # All branch protection rules are implicit: https://docs.mergify.com/conditions/#about-branch-protection + - label=send-it-batch + - base=master + actions: + queue: + name: external_prs - name: Add approved dependabot PRs to merge queue conditions: @@ -40,6 +51,7 @@ pull_request_rules: - base=master actions: queue: + name: default - name: Remove reviews on updates after PR is queued for merging conditions: @@ -59,6 +71,7 @@ pull_request_rules: - author=@libp2p/rust-libp2p-maintainers actions: review: + type: APPROVE - name: Approve dependabot PRs of semver-compatible updates conditions: @@ -68,7 +81,13 @@ pull_request_rules: - title~=bump [^\s]+ from 0\.([\d]+)\..+ to 0\.\1\. # For major == 0 versions, only approve updates with the same minor version. actions: review: + type: APPROVE queue_rules: - name: default conditions: [] + # External PR's don't have access to secrets and variables, therefore they don't run the interop tests. + # using a batch queue allows to circumvent that as mergify creates it from an internal branch. + - name: external_prs + conditions: [] + batch_size: 1 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f724d2fd7de..24eb1b75b2c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,9 +4,9 @@ Please write a summary of your changes and why you made them. This section will appear as the commit message after merging. Please craft it accordingly. -For a quick primer on good commit mesages, check out this blog post: https://cbea.ms/git-commit/ +For a quick primer on good commit messages, check out this blog post: https://cbea.ms/git-commit/ -Please include any relevant issues in here, for example: +Please include any relevant issues here, for example: Related https://github.com/libp2p/rust-libp2p/issues/ABCD. Fixes https://github.com/libp2p/rust-libp2p/issues/XYZ. @@ -15,7 +15,7 @@ Fixes https://github.com/libp2p/rust-libp2p/issues/XYZ. ## Notes & open questions ## Change checklist diff --git a/.github/workflows/cache-factory.yml b/.github/workflows/cache-factory.yml index 07e2bb1f7cf..7623b56f450 100644 --- a/.github/workflows/cache-factory.yml +++ b/.github/workflows/cache-factory.yml @@ -22,7 +22,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 with: shared-key: stable-cache diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8770a29bc9..f8cff086990 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,11 +36,11 @@ jobs: with: fetch-depth: 0 - - uses: r7kamura/rust-problem-matchers@2c2f1016021a7455a6b5b4bbae31145f3b3cd83a #v1.4.0 + - uses: r7kamura/rust-problem-matchers@9fe7ca9f6550e5d6358e179d451cc25ea6b54f98 #v1.5.0 - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 with: shared-key: stable-cache save-if: false @@ -57,27 +57,27 @@ jobs: cargo metadata --format-version=1 --no-deps | \ jq -e -r '.packages[] | select(.name == "'"$CRATE"'") | .dependencies | all(.name != "libp2p")' - - uses: taiki-e/cache-cargo-install-action@924d49e0af41f449f0ad549559bc608ee4653562 # v1 + - uses: taiki-e/cache-cargo-install-action@v2 with: tool: tomlq - name: Extract version from manifest run: | CRATE_VERSION=$(cargo metadata --format-version=1 --no-deps | jq -e -r '.packages[] | select(.name == "'"$CRATE"'") | .version') - + echo "CRATE_VERSION=$CRATE_VERSION" >> $GITHUB_ENV - - name: Enforce version in `workspace.dependencies` matches latest version + - name: Enforce version in `workspace.dependencies` matches the latest version if: env.CRATE != 'libp2p' run: | - SPECIFIED_VERSION=$(tomlq "workspace.dependencies.$CRATE.version" --file ./Cargo.toml) + SPECIFIED_VERSION=$(tq "workspace.dependencies.$CRATE.version" --file ./Cargo.toml) echo "Package version: $CRATE_VERSION"; echo "Specified version: $SPECIFIED_VERSION"; test "$CRATE_VERSION" = "$SPECIFIED_VERSION" || test "=$CRATE_VERSION" = "$SPECIFIED_VERSION" - - name: Enforce version in CHANGELOG.md matches version in manifest + - name: Enforce version in CHANGELOG.md matches the version in manifest run: | MANIFEST_PATH=$(cargo metadata --format-version=1 --no-deps | jq -e -r '.packages[] | select(.name == "'"$CRATE"'") | .manifest_path') DIR_TO_CRATE=$(dirname "$MANIFEST_PATH") @@ -107,8 +107,6 @@ jobs: wasm_tests: name: Run all WASM tests runs-on: ubuntu-latest - env: - CHROMEDRIVER_VERSION: '114.0.5735.90' steps: - uses: actions/checkout@v4 @@ -116,19 +114,12 @@ jobs: with: target: wasm32-unknown-unknown - - uses: taiki-e/cache-cargo-install-action@v1 + - uses: taiki-e/cache-cargo-install-action@v2 with: tool: wasm-pack@0.12.0 - - name: Install Google Chrome - run: | - curl -o /tmp/google-chrome-stable_amd64.deb https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROMEDRIVER_VERSION}-1_amd64.deb - sudo dpkg -i /tmp/google-chrome-stable_amd64.deb - - name: Install chromedriver uses: nanasess/setup-chromedriver@v2 - with: - chromedriver-version: ${{ env.CHROMEDRIVER_VERSION }} - name: Run all tests run: ./wasm-tests/run-all.sh @@ -156,9 +147,9 @@ jobs: with: target: ${{ matrix.target }} - - uses: r7kamura/rust-problem-matchers@2c2f1016021a7455a6b5b4bbae31145f3b3cd83a #v1.4.0 + - uses: r7kamura/rust-problem-matchers@9fe7ca9f6550e5d6358e179d451cc25ea6b54f98 #v1.5.0 - - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 with: key: ${{ matrix.target }} save-if: ${{ github.ref == 'refs/heads/master' }} @@ -181,9 +172,9 @@ jobs: with: toolchain: ${{ env.MSRV }} - - uses: r7kamura/rust-problem-matchers@2c2f1016021a7455a6b5b4bbae31145f3b3cd83a #v1.4.0 + - uses: r7kamura/rust-problem-matchers@9fe7ca9f6550e5d6358e179d451cc25ea6b54f98 #v1.5.0 - - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 with: save-if: ${{ github.ref == 'refs/heads/master' }} @@ -202,9 +193,9 @@ jobs: - uses: dtolnay/rust-toolchain@stable - - uses: r7kamura/rust-problem-matchers@2c2f1016021a7455a6b5b4bbae31145f3b3cd83a #v1.4.0 + - uses: r7kamura/rust-problem-matchers@9fe7ca9f6550e5d6358e179d451cc25ea6b54f98 #v1.5.0 - - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 with: key: ${{ matrix.features }} save-if: ${{ github.ref == 'refs/heads/master' }} @@ -219,9 +210,9 @@ jobs: - uses: dtolnay/rust-toolchain@stable - - uses: r7kamura/rust-problem-matchers@2c2f1016021a7455a6b5b4bbae31145f3b3cd83a #v1.4.0 + - uses: r7kamura/rust-problem-matchers@9fe7ca9f6550e5d6358e179d451cc25ea6b54f98 #v1.5.0 - - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 with: save-if: ${{ github.ref == 'refs/heads/master' }} @@ -234,9 +225,8 @@ jobs: fail-fast: false matrix: rust-version: [ - # 1.72.0, # current stable - # beta, - nightly-2023-09-10 + 1.83.0, # current stable + beta, ] steps: - uses: actions/checkout@v4 @@ -246,9 +236,9 @@ jobs: toolchain: ${{ matrix.rust-version }} components: clippy - - uses: r7kamura/rust-problem-matchers@2c2f1016021a7455a6b5b4bbae31145f3b3cd83a #v1.4.0 + - uses: r7kamura/rust-problem-matchers@9fe7ca9f6550e5d6358e179d451cc25ea6b54f98 #v1.5.0 - - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 with: save-if: ${{ github.ref == 'refs/heads/master' }} @@ -262,9 +252,9 @@ jobs: - uses: dtolnay/rust-toolchain@stable - - uses: r7kamura/rust-problem-matchers@2c2f1016021a7455a6b5b4bbae31145f3b3cd83a #v1.4.0 + - uses: r7kamura/rust-problem-matchers@9fe7ca9f6550e5d6358e179d451cc25ea6b54f98 #v1.5.0 - - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 with: save-if: ${{ github.ref == 'refs/heads/master' }} @@ -281,9 +271,9 @@ jobs: - uses: dtolnay/rust-toolchain@stable - - uses: r7kamura/rust-problem-matchers@2c2f1016021a7455a6b5b4bbae31145f3b3cd83a #v1.4.0 + - uses: r7kamura/rust-problem-matchers@9fe7ca9f6550e5d6358e179d451cc25ea6b54f98 #v1.5.0 - - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 with: shared-key: stable-cache save-if: false @@ -296,7 +286,7 @@ jobs: cargo check --manifest-path "$toml"; done - - uses: taiki-e/cache-cargo-install-action@v1 + - uses: taiki-e/cache-cargo-install-action@v2 with: tool: wasm-pack@0.12.0 @@ -318,20 +308,20 @@ jobs: RUSTFLAGS: '' steps: - uses: actions/checkout@v4 - - run: wget -q -O- https://github.com/obi1kenobi/cargo-semver-checks/releases/download/v0.25.0/cargo-semver-checks-x86_64-unknown-linux-gnu.tar.gz | tar -xz -C ~/.cargo/bin + - run: wget -q -O- https://github.com/obi1kenobi/cargo-semver-checks/releases/download/v0.36.0/cargo-semver-checks-x86_64-unknown-linux-gnu.tar.gz | tar -xz -C ~/.cargo/bin shell: bash - - uses: obi1kenobi/cargo-semver-checks-action@e275dda72e250d4df5b564e969e1348d67fefa52 # v2 + - uses: obi1kenobi/cargo-semver-checks-action@7272cc2caa468d3e009a2b0a9cc366839348237b # v2.6 rustfmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@nightly with: components: rustfmt - - uses: r7kamura/rust-problem-matchers@2c2f1016021a7455a6b5b4bbae31145f3b3cd83a #v1.4.0 + - uses: r7kamura/rust-problem-matchers@9fe7ca9f6550e5d6358e179d451cc25ea6b54f98 #v1.5.0 - name: Check formatting run: cargo fmt -- --check @@ -343,7 +333,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable - - uses: r7kamura/rust-problem-matchers@2c2f1016021a7455a6b5b4bbae31145f3b3cd83a #v1.4.0 + - uses: r7kamura/rust-problem-matchers@9fe7ca9f6550e5d6358e179d451cc25ea6b54f98 #v1.5.0 - name: Ensure `full` feature contains all features run: | @@ -375,12 +365,12 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 - run: cargo install --version 0.10.0 pb-rs --locked - name: Glob match - uses: tj-actions/glob@v17 + uses: tj-actions/glob@v22 id: glob with: files: | @@ -401,13 +391,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 - run: cargo metadata --locked --format-version=1 > /dev/null cargo-deny: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: EmbarkStudios/cargo-deny-action@v2 with: command: check advisories bans licenses sources diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index b9cd82897c2..5cbfc20d69d 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -6,7 +6,6 @@ on: - 'master' tags: - 'libp2p-server-**' - pull_request: jobs: server: @@ -30,15 +29,10 @@ jobs: images: ghcr.io/${{ github.repository }}-server - name: Build and push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . file: ./misc/server/Dockerfile - push: ${{ ! github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} # Only push image if we have the required permissions, i.e. not running from a fork - cache-from: ${{ ! github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' && type=s3,mode=max,bucket=libp2p-by-tf-aws-bootstrap,region=us-east-1,prefix=buildCache,name=rust-libp2p-server }} - cache-to: ${{ ! github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' && type=s3,mode=max,bucket=libp2p-by-tf-aws-bootstrap,region=us-east-1,prefix=buildCache,name=rust-libp2p-server }} + push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - env: - AWS_ACCESS_KEY_ID: ${{ vars.TEST_PLANS_BUILD_CACHE_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.TEST_PLANS_BUILD_CACHE_KEY }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bffcc60d2ea..e2bac78c006 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,14 +17,13 @@ jobs: run: cargo +nightly doc --no-deps --workspace -F full env: RUSTDOCFLAGS: "--cfg docsrs" - RUSTFLAGS: "--cfg docsrs" - name: Add index file run: | mkdir host-docs echo "" > target/doc/index.html cp -r target/doc/* ./host-docs - name: Upload documentation - uses: actions/upload-pages-artifact@v2.0.0 + uses: actions/upload-pages-artifact@v3.0.1 with: path: "host-docs/" @@ -42,5 +41,5 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/interop-test.yml b/.github/workflows/interop-test.yml index f3950897089..c88579be68a 100644 --- a/.github/workflows/interop-test.yml +++ b/.github/workflows/interop-test.yml @@ -12,6 +12,7 @@ concurrency: jobs: run-transport-interop: name: Run transport interoperability tests + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository runs-on: ${{ fromJSON(github.repository == 'libp2p/rust-libp2p' && '["self-hosted", "linux", "x64", "4xlarge"]' || '"ubuntu-latest"') }} strategy: matrix: @@ -24,8 +25,10 @@ jobs: - name: Build ${{ matrix.flavour }} image run: ./scripts/build-interop-image.sh env: - AWS_ACCESS_KEY_ID: ${{ vars.TEST_PLANS_BUILD_CACHE_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.TEST_PLANS_BUILD_CACHE_KEY }} + AWS_BUCKET_NAME: ${{ vars.S3_LIBP2P_BUILD_CACHE_BUCKET_NAME }} + AWS_ACCESS_KEY_ID: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_LIBP2P_BUILD_CACHE_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_REGION }} FLAVOUR: ${{ matrix.flavour }} - name: Run ${{ matrix.flavour }} tests @@ -33,12 +36,14 @@ jobs: with: test-filter: ${{ matrix.flavour }}-rust-libp2p-head extra-versions: ${{ github.workspace }}/interop-tests/${{ matrix.flavour }}-ping-version.json - s3-cache-bucket: libp2p-by-tf-aws-bootstrap - s3-access-key-id: ${{ vars.TEST_PLANS_BUILD_CACHE_KEY_ID }} - s3-secret-access-key: ${{ secrets.TEST_PLANS_BUILD_CACHE_KEY }} + s3-cache-bucket: ${{ vars.S3_LIBP2P_BUILD_CACHE_BUCKET_NAME }} + s3-access-key-id: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_ACCESS_KEY_ID }} + s3-secret-access-key: ${{ secrets.S3_LIBP2P_BUILD_CACHE_AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_REGION }} worker-count: 16 run-holepunching-interop: name: Run hole-punch interoperability tests + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository runs-on: ${{ fromJSON(github.repository == 'libp2p/rust-libp2p' && '["self-hosted", "linux", "x64", "4xlarge"]' || '"ubuntu-latest"') }} steps: - uses: actions/checkout@v4 @@ -50,7 +55,8 @@ jobs: with: test-filter: rust-libp2p-head extra-versions: ${{ github.workspace }}/hole-punching-tests/version.json - s3-cache-bucket: libp2p-by-tf-aws-bootstrap - s3-access-key-id: ${{ vars.TEST_PLANS_BUILD_CACHE_KEY_ID }} - s3-secret-access-key: ${{ secrets.TEST_PLANS_BUILD_CACHE_KEY }} + s3-cache-bucket: ${{ vars.S3_LIBP2P_BUILD_CACHE_BUCKET_NAME }} + s3-access-key-id: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_ACCESS_KEY_ID }} + s3-secret-access-key: ${{ secrets.S3_LIBP2P_BUILD_CACHE_AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ vars.S3_LIBP2P_BUILD_CACHE_AWS_REGION }} worker-count: 16 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c3466708c9..4fccb9a489e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## Main APIs - [`libp2p-core` CHANGELOG](core/CHANGELOG.md) +- [`libp2p-identity` CHANGELOG](identity/CHANGELOG.md) - [`libp2p-swarm` CHANGELOG](swarm/CHANGELOG.md) - [`libp2p-swarm-derive` CHANGELOG](swarm-derive/CHANGELOG.md) @@ -13,7 +14,6 @@ - [`libp2p-floodsub` CHANGELOG](protocols/floodsub/CHANGELOG.md) - [`libp2p-gossipsub` CHANGELOG](protocols/gossipsub/CHANGELOG.md) - [`libp2p-identify` CHANGELOG](protocols/identify/CHANGELOG.md) -- [`libp2p-identity` CHANGELOG](protocols/identity/CHANGELOG.md) - [`libp2p-kad` CHANGELOG](protocols/kad/CHANGELOG.md) - [`libp2p-mdns` CHANGELOG](protocols/mdns/CHANGELOG.md) - [`libp2p-ping` CHANGELOG](protocols/ping/CHANGELOG.md) diff --git a/Cargo.lock b/Cargo.lock index b69a700f7ca..091b1b8dede 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "addr2line" -version = "0.20.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aead" @@ -29,9 +29,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -54,38 +54,30 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" -dependencies = [ - "memchr", -] - -[[package]] -name = "aho-corasick" -version = "1.0.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anes" @@ -95,95 +87,106 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "arbitrary" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[package]] name = "arc-swap" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] -name = "arrayvec" -version = "0.7.4" +name = "asn1-rs" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive 0.4.0", + "asn1-rs-impl 0.1.0", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] [[package]] name = "asn1-rs" -version = "0.5.2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", + "asn1-rs-derive 0.5.1", + "asn1-rs-impl 0.2.0", "displaydoc", "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -196,7 +199,19 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.92", + "synstructure 0.13.1", ] [[package]] @@ -210,6 +225,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.92", +] + [[package]] name = "asn1_der" version = "0.7.6" @@ -237,154 +263,164 @@ dependencies = [ "futures-core", ] +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-executor" -version = "1.5.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ - "async-lock 2.7.0", "async-task", "concurrent-queue", - "fastrand 1.9.0", - "futures-lite 1.13.0", + "fastrand", + "futures-lite", "slab", ] [[package]] name = "async-fs" -version = "1.6.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ - "async-lock 2.7.0", - "autocfg", + "async-lock", "blocking", - "futures-lite 1.13.0", + "futures-lite", ] [[package]] name = "async-global-executor" -version = "2.3.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ - "async-channel", + "async-channel 2.3.1", "async-executor", - "async-io 1.13.0", - "async-lock 2.7.0", + "async-io", + "async-lock", "blocking", - "futures-lite 1.13.0", + "futures-lite", "once_cell", ] [[package]] name = "async-io" -version = "1.13.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ - "async-lock 2.7.0", - "autocfg", + "async-lock", "cfg-if", "concurrent-queue", - "futures-lite 1.13.0", - "log", + "futures-io", + "futures-lite", "parking", - "polling 2.8.0", - "rustix 0.37.25", + "polling", + "rustix", "slab", - "socket2 0.4.9", - "waker-fn", + "tracing", + "windows-sys 0.59.0", ] [[package]] -name = "async-io" -version = "2.2.1" +name = "async-lock" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6d3b15875ba253d1110c740755e246537483f152fa334f91abd7fe84c88b3ff" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "async-lock 3.1.0", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite 2.0.1", - "parking", - "polling 3.3.0", - "rustix 0.38.21", - "slab", - "tracing", - "windows-sys 0.52.0", + "event-listener 5.3.1", + "event-listener-strategy", + "pin-project-lite", ] [[package]] -name = "async-lock" -version = "2.7.0" +name = "async-net" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ - "event-listener 2.5.3", + "async-io", + "blocking", + "futures-lite", ] [[package]] -name = "async-lock" -version = "3.1.0" +name = "async-process" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb2ab2aa8a746e221ab826c73f48bc6ba41be6763f0855cb249eb6d154cf1d7" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ - "event-listener 3.1.0", - "event-listener-strategy", - "pin-project-lite", + "async-channel 2.3.1", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.3.1", + "futures-lite", + "rustix", + "tracing", ] [[package]] -name = "async-net" -version = "1.7.0" +name = "async-recursion" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ - "async-io 1.13.0", - "autocfg", - "blocking", - "futures-lite 1.13.0", + "proc-macro2", + "quote", + "syn 2.0.92", ] [[package]] -name = "async-process" -version = "1.7.0" +name = "async-signal" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 1.13.0", - "async-lock 2.7.0", - "autocfg", - "blocking", + "async-io", + "async-lock", + "atomic-waker", "cfg-if", - "event-listener 2.5.3", - "futures-lite 1.13.0", - "rustix 0.37.25", - "signal-hook", - "windows-sys 0.48.0", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", ] [[package]] name = "async-std" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" dependencies = [ "async-attributes", - "async-channel", + "async-channel 1.9.0", "async-global-executor", - "async-io 1.13.0", - "async-lock 2.7.0", + "async-io", + "async-lock", "async-process", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 1.13.0", - "gloo-timers", + "futures-lite", + "gloo-timers 0.3.0", "kv-log-macro", "log", "memchr", @@ -397,9 +433,9 @@ dependencies = [ [[package]] name = "async-std-resolver" -version = "0.24.0" +version = "0.25.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0ed2b6671c13d2c28756c5a64e04759c1e0b5d3d7ac031f521c3561e21fbcb" +checksum = "f42964492d88a2a555cc65d8ab30e5e1178c1776f40b2717643c1aebb4297a1a" dependencies = [ "async-std", "async-trait", @@ -407,24 +443,46 @@ dependencies = [ "futures-util", "hickory-resolver", "pin-utils", - "socket2 0.5.5", + "socket2", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.92", ] [[package]] name = "async-task" -version = "4.4.0" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", ] [[package]] @@ -442,9 +500,9 @@ dependencies = [ [[package]] name = "atomic-waker" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "attohttpc" @@ -452,16 +510,16 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" dependencies = [ - "http", + "http 0.2.12", "log", "url", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "autonat-example" @@ -471,24 +529,40 @@ dependencies = [ "futures", "libp2p", "tokio", + "tracing-subscriber", +] + +[[package]] +name = "autonatv2" +version = "0.1.0" +dependencies = [ + "cfg-if", + "clap", + "libp2p", + "opentelemetry-jaeger", + "opentelemetry_sdk 0.21.2", + "rand 0.8.5", + "tokio", "tracing", + "tracing-opentelemetry 0.22.0", "tracing-subscriber", ] [[package]] name = "axum" -version = "0.6.20" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", "axum-core", - "bitflags 1.3.2", "bytes", "futures-util", - "http", + "http 1.2.0", "http-body", + "http-body-util", "hyper", + "hyper-util", "itoa", "matchit", "memchr", @@ -502,41 +576,46 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tower", + "tower 0.5.2", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "axum-core" -version = "0.3.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", "futures-util", - "http", + "http 1.2.0", "http-body", + "http-body-util", "mime", + "pin-project-lite", "rustversion", + "sync_wrapper", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -559,24 +638,21 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "base64ct" -version = "1.6.0" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "basic-toml" -version = "0.1.4" +name = "base64ct" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6" -dependencies = [ - "serde", -] +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bimap" @@ -601,9 +677,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "blake2" @@ -643,17 +719,15 @@ dependencies = [ [[package]] name = "blocking" -version = "1.3.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ - "async-channel", - "async-lock 2.7.0", + "async-channel 2.3.1", "async-task", - "atomic-waker", - "fastrand 1.9.0", - "futures-lite 1.13.0", - "log", + "futures-io", + "futures-lite", + "piper", ] [[package]] @@ -672,7 +746,7 @@ dependencies = [ "rust-embed", "tokio", "tokio-util", - "tower", + "tower 0.4.13", "tower-http", "tracing", "tracing-subscriber", @@ -684,18 +758,18 @@ dependencies = [ [[package]] name = "bs58" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ "tinyvec", ] [[package]] name = "bstr" -version = "1.6.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" dependencies = [ "memchr", "serde", @@ -703,9 +777,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" @@ -715,9 +789,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" dependencies = [ "serde", ] @@ -739,20 +813,20 @@ dependencies = [ [[package]] name = "cbor4ii" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b4c883b9cc4757b061600d39001d4d0232bece4a3174696cf8f58a14db107d" +checksum = "472931dd4dfcc785075b09be910147f9c6258883fc4591d0dac6116392b2daa6" dependencies = [ "serde", ] [[package]] name = "cc" -version = "1.0.83" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" dependencies = [ - "libc", + "shlex", ] [[package]] @@ -773,6 +847,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chacha20" version = "0.9.1" @@ -801,19 +881,17 @@ dependencies = [ name = "chat-example" version = "0.1.0" dependencies = [ - "async-trait", "futures", "libp2p", "tokio", - "tracing", "tracing-subscriber", ] [[package]] name = "ciborium" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -822,15 +900,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", @@ -849,9 +927,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.10" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -859,9 +937,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.9" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -871,33 +949,33 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "combine" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "futures-core", @@ -909,9 +987,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -928,26 +1006,15 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" - -[[package]] -name = "cookie" -version = "0.16.2" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -955,9 +1022,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core2" @@ -970,27 +1037,27 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] [[package]] name = "crc" -version = "3.0.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "criterion" @@ -1003,8 +1070,9 @@ dependencies = [ "ciborium", "clap", "criterion-plot", + "futures", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -1015,6 +1083,7 @@ dependencies = [ "serde_derive", "serde_json", "tinytemplate", + "tokio", "walkdir", ] @@ -1025,51 +1094,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset 0.9.0", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" @@ -1079,9 +1139,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -1132,16 +1192,15 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.1" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "platforms", "rustc_version", "subtle", "zeroize", @@ -1149,26 +1208,26 @@ dependencies = [ [[package]] name = "curve25519-dalek-derive" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", ] [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "data-encoding-macro" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99" +checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1176,9 +1235,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" +checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" dependencies = [ "data-encoding", "syn 1.0.109", @@ -1192,7 +1251,6 @@ dependencies = [ "futures", "futures-timer", "libp2p", - "log", "tokio", "tracing", "tracing-subscriber", @@ -1200,9 +1258,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.7" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", @@ -1215,7 +1273,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" dependencies = [ - "asn1-rs", + "asn1-rs 0.5.2", "displaydoc", "nom", "num-bigint", @@ -1223,6 +1281,29 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs 0.6.2", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" version = "0.9.0" @@ -1246,44 +1327,43 @@ dependencies = [ [[package]] name = "dirs" -version = "4.0.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.3.7" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", + "option-ext", "redox_users", - "winapi", + "windows-sys 0.48.0", ] [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", ] [[package]] name = "distributed-key-value-store-example" version = "0.1.0" dependencies = [ - "async-std", - "async-trait", "futures", "libp2p", - "tracing", + "tokio", "tracing-subscriber", ] @@ -1295,9 +1375,9 @@ checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" [[package]] name = "ecdsa" -version = "0.16.8" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", "digest 0.10.7", @@ -1309,9 +1389,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "2.2.2" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", "signature", @@ -1319,9 +1399,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", @@ -1334,15 +1414,15 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "elliptic-curve" -version = "0.13.5" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", @@ -1361,23 +1441,33 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "enum-as-inner" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", ] [[package]] @@ -1392,15 +1482,15 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.1" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ + "anstream", + "anstyle", + "env_filter", "humantime", - "is-terminal", "log", - "regex", - "termcolor", ] [[package]] @@ -1411,23 +1501,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ - "cc", "libc", + "windows-sys 0.59.0", ] [[package]] @@ -1438,9 +1517,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "3.1.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -1449,50 +1528,19 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.3.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96b852f1345da36d551b9473fa1e2b1eb5c5195585c6c018118bc92a8d91160" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ - "event-listener 3.1.0", + "event-listener 5.3.1", "pin-project-lite", ] -[[package]] -name = "fantoccini" -version = "0.20.0-rc.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5eb32b0001134a1d3b9e16010eb4b119451edf68446963a30a8130a0d056e98" -dependencies = [ - "base64 0.13.1", - "cookie", - "futures-core", - "futures-util", - "http", - "hyper", - "hyper-rustls", - "mime", - "serde", - "serde_json", - "time", - "tokio", - "url", - "webdriver", -] - [[package]] name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "fastrand" -version = "2.0.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "ff" @@ -1506,23 +1554,20 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.1" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "file-sharing-example" version = "0.1.0" dependencies = [ - "async-std", "clap", - "either", "futures", "libp2p", "serde", - "tracing", + "tokio", "tracing-subscriber", - "void", ] [[package]] @@ -1531,6 +1576,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1557,9 +1608,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1572,19 +1623,19 @@ dependencies = [ [[package]] name = "futures-bounded" -version = "0.2.3" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e" dependencies = [ - "futures", "futures-timer", "futures-util", - "tokio", ] [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1592,15 +1643,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1610,94 +1661,72 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "1.13.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ - "fastrand 1.9.0", + "fastrand", "futures-core", "futures-io", - "memchr", "parking", "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-lite" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3831c2651acb5177cbd83943f3d9c8912c5ad03c76afcc0e9511ba568ec5ebb" -dependencies = [ - "futures-core", - "pin-project-lite", ] [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", ] [[package]] name = "futures-rustls" -version = "0.24.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd3cf68c183738046838e300353e4716c674dc5e56890de4826801a6622a28" +checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.21.9", + "rustls 0.23.20", + "rustls-pki-types", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" - -[[package]] -name = "futures-ticker" -version = "0.0.3" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9763058047f713632a52e916cc7f6a4b3fc6e9fc1ff8c5b1dc49e5a89041682e" -dependencies = [ - "futures", - "futures-timer", - "instant", -] +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ - "gloo-timers", + "gloo-timers 0.2.6", "send_wrapper 0.4.0", ] [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1747,9 +1776,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -1760,9 +1789,9 @@ dependencies = [ [[package]] name = "ghash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ "opaque-debug", "polyval", @@ -1770,9 +1799,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.3" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -1782,15 +1811,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.10" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ - "aho-corasick 0.7.20", + "aho-corasick", "bstr", - "fnv", "log", - "regex", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -1805,6 +1834,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "group" version = "0.13.0" @@ -1818,17 +1859,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.20" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", - "http", - "indexmap 1.9.3", + "http 1.2.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -1837,9 +1878,13 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] [[package]] name = "hashbrown" @@ -1849,25 +1894,50 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", ] [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex" @@ -1889,10 +1959,11 @@ checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" [[package]] name = "hickory-proto" -version = "0.24.0" +version = "0.25.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091a6fbccf4860009355e3efc52ff4acf37a63489aad7435372d44ceeb6fbbcf" +checksum = "d063c0692ee669aa6d261988aa19ca5510f1cc40e4f211024f50c888499a35d7" dependencies = [ + "async-recursion", "async-trait", "cfg-if", "data-encoding", @@ -1900,12 +1971,12 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna 0.4.0", + "idna", "ipnet", "once_cell", "rand 0.8.5", - "socket2 0.5.5", - "thiserror", + "socket2", + "thiserror 2.0.9", "tinyvec", "tokio", "tracing", @@ -1914,30 +1985,30 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.24.0" +version = "0.25.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35b8f021164e6a984c9030023544c57789c51760065cd510572fedcfb04164e8" +checksum = "42bc352e4412fb657e795f79b4efcf2bd60b59ee5ca0187f3554194cd1107a27" dependencies = [ "cfg-if", "futures-util", "hickory-proto", "ipconfig", - "lru-cache", + "moka", "once_cell", "parking_lot", "rand 0.8.5", "resolv-conf", "smallvec", - "thiserror", + "thiserror 2.0.9", "tokio", "tracing", ] [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac 0.12.1", ] @@ -1978,7 +2049,7 @@ version = "0.1.0" dependencies = [ "anyhow", "either", - "env_logger 0.10.1", + "env_logger 0.11.6", "futures", "libp2p", "redis", @@ -2001,9 +2072,20 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -2012,32 +2094,44 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.2.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "http", + "futures-util", + "http 1.2.0", + "http-body", "pin-project-lite", ] [[package]] name = "http-range-header" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -2047,167 +2141,313 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.27" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", "h2", - "http", + "http 1.2.0", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] name = "hyper-rustls" -version = "0.23.2" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ - "http", + "futures-util", + "http 1.2.0", "hyper", - "log", - "rustls 0.20.8", - "rustls-native-certs", + "hyper-util", + "rustls 0.23.20", + "rustls-pki-types", "tokio", "tokio-rustls", + "tower-service", + "webpki-roots 0.26.7", ] [[package]] name = "hyper-timeout" -version = "0.4.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ "hyper", + "hyper-util", "pin-project-lite", "tokio", - "tokio-io-timeout", + "tower-service", ] [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", + "http-body-util", "hyper", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", ] [[package]] -name = "identify-example" -version = "0.1.0" +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ - "async-std", - "async-trait", - "futures", - "libp2p", + "bytes", + "futures-channel", + "futures-util", + "http 1.2.0", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", "tracing", - "tracing-subscriber", ] [[package]] -name = "idna" -version = "0.4.0" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "idna" -version = "0.5.0" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "if-addrs" -version = "0.10.2" +name = "icu_locid_transform" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ - "libc", - "windows-sys 0.48.0", + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "if-watch" -version = "3.2.0" +name = "icu_locid_transform_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e" -dependencies = [ - "async-io 2.2.1", - "core-foundation", - "fnv", - "futures", - "if-addrs", - "ipnet", - "log", - "rtnetlink", - "smol", - "system-configuration", - "tokio", - "windows", -] +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] -name = "igd-next" -version = "0.14.2" +name = "icu_normalizer" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e065e90a518ab5fedf79aa1e4b784e10f8e484a834f6bda85c42633a2cb7af" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ - "async-trait", - "attohttpc", - "bytes", - "futures", - "http", - "hyper", - "log", - "rand 0.8.5", - "tokio", - "url", - "xmltree", + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", ] [[package]] -name = "indexmap" -version = "1.9.3" +name = "icu_normalizer_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.92", +] + +[[package]] +name = "identify-example" +version = "0.1.0" +dependencies = [ + "futures", + "libp2p", + "tokio", + "tracing-subscriber", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "if-addrs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "if-watch" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf9d64cfcf380606e64f9a0bcf493616b65331199f984151a6fa11a7b3cde38" +dependencies = [ + "async-io", + "core-foundation", + "fnv", + "futures", + "if-addrs", + "ipnet", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "rtnetlink", + "smol", + "system-configuration", + "tokio", + "windows 0.53.0", +] + +[[package]] +name = "igd-next" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b0d7d4541def58a37bf8efc559683f21edce7c82f0d866c93ac21f7e098f93" +dependencies = [ + "async-trait", + "attohttpc", + "bytes", + "futures", + "http 1.2.0", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.8.5", + "tokio", + "url", + "xmltree", +] [[package]] name = "indexmap" -version = "2.0.0" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.15.2", ] [[package]] @@ -2221,16 +2461,10 @@ dependencies = [ ] [[package]] -name = "instant" -version = "0.1.12" +name = "integer-encoding" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" [[package]] name = "interceptor" @@ -2243,12 +2477,12 @@ dependencies = [ "log", "rand 0.8.5", "rtcp", - "rtp", - "thiserror", + "rtp 0.9.0", + "thiserror 1.0.69", "tokio", "waitgroup", "webrtc-srtp", - "webrtc-util", + "webrtc-util 0.8.1", ] [[package]] @@ -2258,10 +2492,8 @@ dependencies = [ "anyhow", "axum", "console_error_panic_hook", - "either", "futures", "futures-timer", - "instant", "libp2p", "libp2p-mplex", "libp2p-noise", @@ -2283,17 +2515,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "wasm-logger", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", + "web-time 1.1.0", ] [[package]] @@ -2302,7 +2524,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.5", + "socket2", "widestring", "windows-sys 0.48.0", "winreg", @@ -2313,13 +2535,10 @@ name = "ipfs-kad-example" version = "0.1.0" dependencies = [ "anyhow", - "async-trait", "clap", - "env_logger 0.10.1", "futures", "libp2p", "tokio", - "tracing", "tracing-subscriber", ] @@ -2327,32 +2546,36 @@ dependencies = [ name = "ipfs-private-example" version = "0.1.0" dependencies = [ - "async-trait", "either", "futures", "libp2p", "tokio", - "tracing", "tracing-subscriber", ] [[package]] name = "ipnet" -version = "2.8.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi", - "rustix 0.38.21", - "windows-sys 0.48.0", + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -2362,26 +2585,36 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] @@ -2390,9 +2623,8 @@ dependencies = [ name = "keygen" version = "0.1.0" dependencies = [ - "base64 0.21.5", + "base64 0.22.1", "clap", - "libp2p-core", "libp2p-identity", "serde", "serde_json", @@ -2410,29 +2642,26 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libp2p" -version = "0.53.2" +version = "0.54.2" dependencies = [ "async-std", - "async-trait", "bytes", - "clap", "either", "futures", "futures-timer", - "getrandom 0.2.11", - "instant", + "getrandom 0.2.15", "libp2p-allow-block-list", "libp2p-autonat", "libp2p-connection-limits", @@ -2468,14 +2697,14 @@ dependencies = [ "multiaddr", "pin-project", "rw-stream-sink", - "thiserror", + "thiserror 2.0.9", "tokio", "tracing-subscriber", ] [[package]] name = "libp2p-allow-block-list" -version = "0.3.0" +version = "0.4.1" dependencies = [ "async-std", "libp2p-core", @@ -2483,34 +2712,38 @@ dependencies = [ "libp2p-swarm", "libp2p-swarm-derive", "libp2p-swarm-test", - "void", ] [[package]] name = "libp2p-autonat" -version = "0.12.0" +version = "0.13.1" dependencies = [ - "async-std", "async-trait", "asynchronous-codec", + "either", "futures", + "futures-bounded", "futures-timer", - "instant", "libp2p-core", + "libp2p-identify", "libp2p-identity", "libp2p-request-response", "libp2p-swarm", "libp2p-swarm-test", + "libp2p-test-utils", "quick-protobuf", "quick-protobuf-codec", "rand 0.8.5", + "rand_core 0.6.4", + "thiserror 2.0.9", + "tokio", "tracing", - "tracing-subscriber", + "web-time 1.1.0", ] [[package]] name = "libp2p-connection-limits" -version = "0.3.0" +version = "0.4.1" dependencies = [ "async-std", "libp2p-core", @@ -2522,19 +2755,17 @@ dependencies = [ "libp2p-swarm-test", "quickcheck-ext", "rand 0.8.5", - "void", ] [[package]] name = "libp2p-core" -version = "0.41.2" +version = "0.42.1" dependencies = [ "async-std", "either", "fnv", "futures", "futures-timer", - "instant", "libp2p-identity", "libp2p-mplex", "libp2p-noise", @@ -2545,54 +2776,45 @@ dependencies = [ "parking_lot", "pin-project", "quick-protobuf", - "quickcheck-ext", "rand 0.8.5", "rw-stream-sink", - "serde", - "smallvec", - "thiserror", + "thiserror 2.0.9", "tracing", - "unsigned-varint 0.8.0", - "void", + "unsigned-varint", + "web-time 1.1.0", ] [[package]] name = "libp2p-dcutr" -version = "0.11.0" +version = "0.12.1" dependencies = [ - "async-std", "asynchronous-codec", - "clap", "either", "futures", "futures-bounded", "futures-timer", - "instant", "libp2p-core", - "libp2p-dns", "libp2p-identify", "libp2p-identity", - "libp2p-noise", - "libp2p-ping", "libp2p-plaintext", "libp2p-relay", "libp2p-swarm", "libp2p-swarm-test", "libp2p-tcp", + "libp2p-test-utils", "libp2p-yamux", "lru", "quick-protobuf", "quick-protobuf-codec", - "rand 0.8.5", - "thiserror", + "thiserror 2.0.9", + "tokio", "tracing", - "tracing-subscriber", - "void", + "web-time 1.1.0", ] [[package]] name = "libp2p-dns" -version = "0.41.1" +version = "0.42.1" dependencies = [ "async-std", "async-std-resolver", @@ -2601,16 +2823,16 @@ dependencies = [ "hickory-resolver", "libp2p-core", "libp2p-identity", + "libp2p-test-utils", "parking_lot", "smallvec", "tokio", "tracing", - "tracing-subscriber", ] [[package]] name = "libp2p-floodsub" -version = "0.44.0" +version = "0.45.0" dependencies = [ "asynchronous-codec", "bytes", @@ -2624,33 +2846,31 @@ dependencies = [ "quick-protobuf-codec", "rand 0.8.5", "smallvec", - "thiserror", + "thiserror 2.0.9", "tracing", ] [[package]] name = "libp2p-gossipsub" -version = "0.46.1" +version = "0.48.0" dependencies = [ - "async-std", + "async-channel 2.3.1", "asynchronous-codec", - "base64 0.21.5", + "base64 0.22.1", "byteorder", "bytes", "either", "fnv", "futures", - "futures-ticker", - "getrandom 0.2.11", - "hex", + "futures-timer", + "getrandom 0.2.15", + "hashlink", "hex_fmt", - "instant", "libp2p-core", "libp2p-identity", - "libp2p-noise", "libp2p-swarm", "libp2p-swarm-test", - "libp2p-yamux", + "libp2p-test-utils", "prometheus-client", "quick-protobuf", "quick-protobuf-codec", @@ -2659,15 +2879,14 @@ dependencies = [ "regex", "serde", "sha2 0.10.8", - "smallvec", + "tokio", "tracing", - "tracing-subscriber", - "void", + "web-time 1.1.0", ] [[package]] name = "libp2p-identify" -version = "0.44.1" +version = "0.46.0" dependencies = [ "async-std", "asynchronous-codec", @@ -2679,22 +2898,19 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "libp2p-swarm-test", - "lru", + "libp2p-test-utils", "quick-protobuf", "quick-protobuf-codec", "smallvec", - "thiserror", + "thiserror 2.0.9", "tracing", - "tracing-subscriber", - "void", ] [[package]] name = "libp2p-identity" -version = "0.2.8" +version = "0.2.10" dependencies = [ "asn1_der", - "base64 0.21.5", "bs58", "criterion", "ed25519-dalek", @@ -2706,23 +2922,21 @@ dependencies = [ "quick-protobuf", "quickcheck-ext", "rand 0.8.5", - "ring 0.17.5", + "ring 0.17.8", "rmp-serde", "sec1", "serde", "serde_json", "sha2 0.10.8", - "thiserror", + "thiserror 2.0.9", "tracing", - "void", "zeroize", ] [[package]] name = "libp2p-kad" -version = "0.45.3" +version = "0.47.0" dependencies = [ - "arrayvec", "async-std", "asynchronous-codec", "bytes", @@ -2731,13 +2945,13 @@ dependencies = [ "futures", "futures-bounded", "futures-timer", - "instant", "libp2p-core", "libp2p-identify", "libp2p-identity", "libp2p-noise", "libp2p-swarm", "libp2p-swarm-test", + "libp2p-test-utils", "libp2p-yamux", "quick-protobuf", "quick-protobuf-codec", @@ -2746,44 +2960,37 @@ dependencies = [ "serde", "sha2 0.10.8", "smallvec", - "thiserror", + "thiserror 2.0.9", "tracing", - "tracing-subscriber", "uint", - "void", + "web-time 1.1.0", ] [[package]] name = "libp2p-mdns" -version = "0.45.1" +version = "0.46.1" dependencies = [ - "async-io 2.2.1", + "async-io", "async-std", - "data-encoding", "futures", "hickory-proto", "if-watch", "libp2p-core", "libp2p-identity", - "libp2p-noise", "libp2p-swarm", "libp2p-swarm-test", - "libp2p-tcp", - "libp2p-yamux", + "libp2p-test-utils", "rand 0.8.5", "smallvec", - "socket2 0.5.5", + "socket2", "tokio", "tracing", - "tracing-subscriber", - "void", ] [[package]] name = "libp2p-memory-connection-limits" -version = "0.2.0" +version = "0.3.1" dependencies = [ - "async-std", "libp2p-core", "libp2p-identify", "libp2p-identity", @@ -2791,18 +2998,15 @@ dependencies = [ "libp2p-swarm-derive", "libp2p-swarm-test", "memory-stats", - "rand 0.8.5", "sysinfo", "tracing", - "void", ] [[package]] name = "libp2p-metrics" -version = "0.14.1" +version = "0.15.0" dependencies = [ "futures", - "instant", "libp2p-core", "libp2p-dcutr", "libp2p-gossipsub", @@ -2814,11 +3018,12 @@ dependencies = [ "libp2p-swarm", "pin-project", "prometheus-client", + "web-time 1.1.0", ] [[package]] name = "libp2p-mplex" -version = "0.41.0" +version = "0.42.0" dependencies = [ "async-std", "asynchronous-codec", @@ -2830,14 +3035,14 @@ dependencies = [ "libp2p-muxer-test-harness", "libp2p-plaintext", "libp2p-tcp", + "libp2p-test-utils", "nohash-hasher", "parking_lot", "quickcheck-ext", "rand 0.8.5", "smallvec", "tracing", - "tracing-subscriber", - "unsigned-varint 0.8.0", + "unsigned-varint", ] [[package]] @@ -2853,84 +3058,76 @@ dependencies = [ [[package]] name = "libp2p-noise" -version = "0.44.0" +version = "0.45.1" dependencies = [ "asynchronous-codec", "bytes", - "curve25519-dalek", "futures", "futures_ringbuf", "libp2p-core", "libp2p-identity", + "libp2p-test-utils", "multiaddr", "multihash", "once_cell", "quick-protobuf", "quickcheck-ext", "rand 0.8.5", - "sha2 0.10.8", "snow", "static_assertions", - "thiserror", + "thiserror 2.0.9", "tracing", - "tracing-subscriber", "x25519-dalek", "zeroize", ] [[package]] name = "libp2p-perf" -version = "0.3.0" +version = "0.4.0" dependencies = [ "anyhow", "clap", "futures", "futures-bounded", "futures-timer", - "instant", "libp2p", "libp2p-core", - "libp2p-dns", "libp2p-identity", - "libp2p-quic", "libp2p-swarm", "libp2p-swarm-test", "libp2p-tcp", + "libp2p-test-utils", "libp2p-tls", "libp2p-yamux", - "rand 0.8.5", "serde", "serde_json", - "thiserror", + "thiserror 2.0.9", "tokio", "tracing", "tracing-subscriber", - "void", + "web-time 1.1.0", ] [[package]] name = "libp2p-ping" -version = "0.44.0" +version = "0.45.1" dependencies = [ - "async-std", - "either", "futures", "futures-timer", - "instant", "libp2p-core", "libp2p-identity", "libp2p-swarm", "libp2p-swarm-test", "quickcheck-ext", "rand 0.8.5", + "tokio", "tracing", - "tracing-subscriber", - "void", + "web-time 1.1.0", ] [[package]] name = "libp2p-plaintext" -version = "0.41.0" +version = "0.42.0" dependencies = [ "asynchronous-codec", "bytes", @@ -2938,17 +3135,16 @@ dependencies = [ "futures_ringbuf", "libp2p-core", "libp2p-identity", + "libp2p-test-utils", "quick-protobuf", "quick-protobuf-codec", "quickcheck-ext", - "rand 0.8.5", "tracing", - "tracing-subscriber", ] [[package]] name = "libp2p-pnet" -version = "0.24.0" +version = "0.25.0" dependencies = [ "futures", "libp2p-core", @@ -2969,10 +3165,9 @@ dependencies = [ [[package]] name = "libp2p-quic" -version = "0.10.2" +version = "0.11.2" dependencies = [ "async-std", - "bytes", "futures", "futures-timer", "if-watch", @@ -2981,24 +3176,23 @@ dependencies = [ "libp2p-muxer-test-harness", "libp2p-noise", "libp2p-tcp", + "libp2p-test-utils", "libp2p-tls", "libp2p-yamux", - "parking_lot", "quickcheck", "quinn", "rand 0.8.5", - "ring 0.16.20", - "rustls 0.21.9", - "socket2 0.5.5", - "thiserror", + "ring 0.17.8", + "rustls 0.23.20", + "socket2", + "thiserror 2.0.9", "tokio", "tracing", - "tracing-subscriber", ] [[package]] name = "libp2p-relay" -version = "0.17.1" +version = "0.18.1" dependencies = [ "asynchronous-codec", "bytes", @@ -3006,58 +3200,51 @@ dependencies = [ "futures", "futures-bounded", "futures-timer", - "instant", "libp2p-core", "libp2p-identity", "libp2p-ping", "libp2p-plaintext", "libp2p-swarm", "libp2p-swarm-test", + "libp2p-test-utils", "libp2p-yamux", "quick-protobuf", "quick-protobuf-codec", "quickcheck-ext", "rand 0.8.5", "static_assertions", - "thiserror", + "thiserror 2.0.9", "tracing", - "tracing-subscriber", - "void", + "web-time 1.1.0", ] [[package]] name = "libp2p-rendezvous" -version = "0.14.0" +version = "0.15.1" dependencies = [ "async-trait", "asynchronous-codec", "bimap", "futures", "futures-timer", - "instant", "libp2p-core", - "libp2p-identify", "libp2p-identity", - "libp2p-noise", - "libp2p-ping", "libp2p-request-response", "libp2p-swarm", "libp2p-swarm-test", - "libp2p-tcp", - "libp2p-yamux", + "libp2p-test-utils", "quick-protobuf", "quick-protobuf-codec", "rand 0.8.5", - "thiserror", + "thiserror 2.0.9", "tokio", "tracing", - "tracing-subscriber", - "void", + "web-time 1.1.0", ] [[package]] name = "libp2p-request-response" -version = "0.26.1" +version = "0.28.0" dependencies = [ "anyhow", "async-std", @@ -3065,38 +3252,30 @@ dependencies = [ "cbor4ii", "futures", "futures-bounded", - "futures-timer", "futures_ringbuf", - "instant", "libp2p-core", "libp2p-identity", - "libp2p-noise", "libp2p-swarm", "libp2p-swarm-test", - "libp2p-tcp", - "libp2p-yamux", + "libp2p-test-utils", "rand 0.8.5", "serde", "serde_json", "smallvec", "tracing", - "tracing-subscriber", - "void", ] [[package]] name = "libp2p-server" -version = "0.12.5" +version = "0.12.6" dependencies = [ - "base64 0.21.5", + "axum", + "base64 0.22.1", "clap", "futures", - "futures-timer", - "hyper", "libp2p", "prometheus-client", "serde", - "serde_derive", "serde_json", "tokio", "tracing", @@ -3105,16 +3284,31 @@ dependencies = [ ] [[package]] -name = "libp2p-swarm" -version = "0.44.0" +name = "libp2p-stream" +version = "0.2.0-alpha.1" dependencies = [ - "async-std", - "either", + "futures", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "libp2p-swarm-test", + "libp2p-test-utils", + "rand 0.8.5", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-swarm" +version = "0.45.2" +dependencies = [ + "async-std", + "criterion", + "either", "fnv", "futures", "futures-timer", - "getrandom 0.2.11", - "instant", + "getrandom 0.2.15", "libp2p-core", "libp2p-identify", "libp2p-identity", @@ -3123,7 +3317,9 @@ dependencies = [ "libp2p-plaintext", "libp2p-swarm-derive", "libp2p-swarm-test", + "libp2p-test-utils", "libp2p-yamux", + "lru", "multistream-select", "once_cell", "quickcheck-ext", @@ -3131,25 +3327,23 @@ dependencies = [ "smallvec", "tokio", "tracing", - "tracing-subscriber", "trybuild", - "void", "wasm-bindgen-futures", + "web-time 1.1.0", ] [[package]] name = "libp2p-swarm-derive" -version = "0.34.0" +version = "0.35.0" dependencies = [ "heck", - "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", ] [[package]] name = "libp2p-swarm-test" -version = "0.3.0" +version = "0.5.0" dependencies = [ "async-trait", "futures", @@ -3160,53 +3354,57 @@ dependencies = [ "libp2p-swarm", "libp2p-tcp", "libp2p-yamux", - "rand 0.8.5", "tracing", ] [[package]] name = "libp2p-tcp" -version = "0.41.0" +version = "0.42.0" dependencies = [ - "async-io 2.2.1", + "async-io", "async-std", "futures", "futures-timer", "if-watch", "libc", "libp2p-core", - "libp2p-identity", - "socket2 0.5.5", + "libp2p-test-utils", + "socket2", "tokio", "tracing", +] + +[[package]] +name = "libp2p-test-utils" +version = "0.1.0" +dependencies = [ "tracing-subscriber", ] [[package]] name = "libp2p-tls" -version = "0.3.0" +version = "0.5.0" dependencies = [ "futures", "futures-rustls", - "hex", "hex-literal", "libp2p-core", "libp2p-identity", "libp2p-swarm", "libp2p-yamux", "rcgen", - "ring 0.16.20", - "rustls 0.21.9", - "rustls-webpki", - "thiserror", + "ring 0.17.8", + "rustls 0.23.20", + "rustls-webpki 0.101.7", + "thiserror 2.0.9", "tokio", - "x509-parser", + "x509-parser 0.16.0", "yasna", ] [[package]] name = "libp2p-uds" -version = "0.40.0" +version = "0.41.0" dependencies = [ "async-std", "futures", @@ -3218,7 +3416,7 @@ dependencies = [ [[package]] name = "libp2p-upnp" -version = "0.2.0" +version = "0.3.1" dependencies = [ "futures", "futures-timer", @@ -3227,15 +3425,13 @@ dependencies = [ "libp2p-swarm", "tokio", "tracing", - "void", ] [[package]] name = "libp2p-webrtc" -version = "0.7.0-alpha" +version = "0.8.0-alpha" dependencies = [ "async-trait", - "bytes", "futures", "futures-timer", "hex", @@ -3243,25 +3439,24 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-noise", + "libp2p-test-utils", "libp2p-webrtc-utils", "multihash", "quickcheck", "rand 0.8.5", "rcgen", - "serde", - "stun", - "thiserror", - "tinytemplate", + "stun 0.7.0", + "thiserror 2.0.9", "tokio", "tokio-util", "tracing", - "tracing-subscriber", "webrtc", + "webrtc-ice", ] [[package]] name = "libp2p-webrtc-utils" -version = "0.2.0" +version = "0.3.0" dependencies = [ "asynchronous-codec", "bytes", @@ -3276,30 +3471,24 @@ dependencies = [ "rand 0.8.5", "serde", "sha2 0.10.8", - "thiserror", "tinytemplate", "tracing", ] [[package]] name = "libp2p-webrtc-websys" -version = "0.3.0-alpha" +version = "0.4.0-alpha.2" dependencies = [ "bytes", "futures", - "futures-timer", - "getrandom 0.2.11", + "getrandom 0.2.15", "hex", - "hex-literal", "js-sys", "libp2p-core", "libp2p-identity", - "libp2p-ping", - "libp2p-swarm", "libp2p-webrtc-utils", "send_wrapper 0.6.0", - "serde", - "thiserror", + "thiserror 2.0.9", "tracing", "wasm-bindgen", "wasm-bindgen-futures", @@ -3308,7 +3497,7 @@ dependencies = [ [[package]] name = "libp2p-websocket" -version = "0.43.0" +version = "0.44.1" dependencies = [ "async-std", "either", @@ -3323,14 +3512,15 @@ dependencies = [ "rcgen", "rw-stream-sink", "soketto", + "thiserror 2.0.9", "tracing", "url", - "webpki-roots", + "webpki-roots 0.25.4", ] [[package]] name = "libp2p-websocket-websys" -version = "0.3.1" +version = "0.4.1" dependencies = [ "bytes", "futures", @@ -3339,9 +3529,8 @@ dependencies = [ "libp2p-identity", "libp2p-noise", "libp2p-yamux", - "parking_lot", "send_wrapper 0.6.0", - "thiserror", + "thiserror 2.0.9", "tracing", "wasm-bindgen", "web-sys", @@ -3349,7 +3538,7 @@ dependencies = [ [[package]] name = "libp2p-webtransport-websys" -version = "0.2.0" +version = "0.4.1" dependencies = [ "futures", "js-sys", @@ -3359,8 +3548,9 @@ dependencies = [ "multiaddr", "multibase", "multihash", + "once_cell", "send_wrapper 0.6.0", - "thiserror", + "thiserror 2.0.9", "tracing", "wasm-bindgen", "wasm-bindgen-futures", @@ -3369,15 +3559,27 @@ dependencies = [ [[package]] name = "libp2p-yamux" -version = "0.45.1" +version = "0.46.0" dependencies = [ "async-std", + "either", "futures", "libp2p-core", "libp2p-muxer-test-harness", - "thiserror", + "thiserror 2.0.9", "tracing", - "yamux", + "yamux 0.12.1", + "yamux 0.13.4", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", ] [[package]] @@ -3428,29 +3630,23 @@ dependencies = [ "libsecp256k1-core", ] -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] -name = "linux-raw-sys" -version = "0.4.10" +name = "litemap" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -3458,29 +3654,20 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" dependencies = [ "value-bag", ] [[package]] name = "lru" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2994eeba8ed550fd9b47a0b38f0242bc3344e496483c6180b69139cc2fa5d1d7" -dependencies = [ - "hashbrown 0.14.0", -] - -[[package]] -name = "lru-cache" -version = "0.1.2" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "linked-hash-map", + "hashbrown 0.15.2", ] [[package]] @@ -3500,24 +3687,25 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67827e6ea8ee8a7c4a72227ef4fc08957040acffdb5f122733b24fa12daff41b" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "md-5" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ + "cfg-if", "digest 0.10.7", ] [[package]] name = "memchr" -version = "2.6.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" @@ -3528,39 +3716,30 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "memory-stats" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34f79cf9964c5c9545493acda1263f1912f8d2c56c8a2ffee2606cb960acaacc" +checksum = "c73f5c649995a115e1a0220b35e4df0a1294500477f97a91d0660fb5abeb574a" dependencies = [ "libc", - "winapi", + "windows-sys 0.52.0", ] [[package]] name = "metrics-example" version = "0.1.0" dependencies = [ + "axum", "futures", - "hyper", "libp2p", - "opentelemetry", + "opentelemetry 0.27.1", "opentelemetry-otlp", - "opentelemetry_api", + "opentelemetry_sdk 0.27.1", "prometheus-client", "tokio", "tracing", - "tracing-opentelemetry", + "tracing-opentelemetry 0.28.0", "tracing-subscriber", ] @@ -3572,14 +3751,24 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", ] +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3588,29 +3777,49 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.9" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "moka" +version = "0.12.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cf62eb4dd975d2dde76432fb1075c49e3ee2331cf36f1f8fd4b66550d32b6f" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "once_cell", + "parking_lot", + "quanta", + "rustc_version", + "smallvec", + "tagptr", + "thiserror 1.0.69", + "triomphe", + "uuid", ] [[package]] name = "multiaddr" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b852bc02a2da5feed68cd14fa50d0774b92790a5bdbfa932a813926c8472070" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" dependencies = [ "arrayref", "byteorder", @@ -3621,7 +3830,7 @@ dependencies = [ "percent-encoding", "serde", "static_assertions", - "unsigned-varint 0.7.2", + "unsigned-varint", "url", ] @@ -3638,16 +3847,16 @@ dependencies = [ [[package]] name = "multihash" -version = "0.19.1" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" dependencies = [ "arbitrary", "core2", "quickcheck", "rand 0.8.5", "serde", - "unsigned-varint 0.7.2", + "unsigned-varint", ] [[package]] @@ -3658,23 +3867,21 @@ dependencies = [ "bytes", "futures", "futures_ringbuf", + "libp2p-test-utils", "pin-project", "quickcheck-ext", - "rand 0.8.5", "rw-stream-sink", "smallvec", "tracing", - "tracing-subscriber", - "unsigned-varint 0.8.0", + "unsigned-varint", ] [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -3688,21 +3895,20 @@ dependencies = [ [[package]] name = "netlink-packet-core" -version = "0.4.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" dependencies = [ "anyhow", "byteorder", - "libc", "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" -version = "0.12.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" dependencies = [ "anyhow", "bitflags 1.3.2", @@ -3721,31 +3927,31 @@ dependencies = [ "anyhow", "byteorder", "paste", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "netlink-proto" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" +checksum = "86b33524dc0968bfad349684447bfce6db937a9ac3332a1fe60c0c5a5ce63f21" dependencies = [ "bytes", "futures", "log", "netlink-packet-core", "netlink-sys", - "thiserror", + "thiserror 1.0.69", "tokio", ] [[package]] name = "netlink-sys" -version = "0.8.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" +checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23" dependencies = [ - "async-io 1.13.0", + "async-io", "bytes", "futures", "libc", @@ -3753,17 +3959,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "nix" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", -] - [[package]] name = "nix" version = "0.26.4" @@ -3773,7 +3968,7 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.7.1", + "memoffset", "pin-utils", ] @@ -3814,30 +4009,34 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -3848,15 +4047,15 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] [[package]] name = "object" -version = "0.31.1" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -3867,34 +4066,43 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" dependencies = [ - "asn1-rs", + "asn1-rs 0.5.2", +] + +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs 0.6.2", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.60" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -3911,7 +4119,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", ] [[package]] @@ -3922,9 +4130,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.96" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -3934,98 +4142,153 @@ dependencies = [ [[package]] name = "opentelemetry" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" +dependencies = [ + "futures-core", + "futures-sink", + "indexmap 2.7.0", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror 1.0.69", + "urlencoding", +] + +[[package]] +name = "opentelemetry" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab70038c28ed37b97d8ed414b6429d343a8bbf44c9f79ec854f3a643029ba6d7" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "opentelemetry-jaeger" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9591d937bc0e6d2feb6f71a559540ab300ea49955229c347a517a28d27784c54" +checksum = "e617c66fd588e40e0dbbd66932fdc87393095b125d4459b1a3a10feb1712f8a1" dependencies = [ - "opentelemetry_api", - "opentelemetry_sdk", + "async-trait", + "futures-core", + "futures-util", + "opentelemetry 0.21.0", + "opentelemetry-semantic-conventions", + "opentelemetry_sdk 0.21.2", + "thrift", + "tokio", ] [[package]] name = "opentelemetry-otlp" -version = "0.13.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e5e5a5c4135864099f3faafbe939eb4d7f9b80ebf68a8448da961b32a7c1275" +checksum = "91cf61a1868dacc576bf2b2a1c3e9ab150af7272909e80085c3173384fe11f76" dependencies = [ "async-trait", "futures-core", - "http", + "http 1.2.0", + "opentelemetry 0.27.1", "opentelemetry-proto", - "opentelemetry-semantic-conventions", - "opentelemetry_api", - "opentelemetry_sdk", + "opentelemetry_sdk 0.27.1", "prost", - "thiserror", + "thiserror 1.0.69", "tokio", "tonic", + "tracing", ] [[package]] name = "opentelemetry-proto" -version = "0.3.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e3f814aa9f8c905d0ee4bde026afd3b2577a97c10e1699912e3e44f0c4cbeb" +checksum = "a6e05acbfada5ec79023c85368af14abd0b307c015e9064d249b2a950ef459a6" dependencies = [ - "opentelemetry_api", - "opentelemetry_sdk", + "opentelemetry 0.27.1", + "opentelemetry_sdk 0.27.1", "prost", "tonic", ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73c9f9340ad135068800e7f1b24e9e09ed9e7143f5bf8518ded3d3ec69789269" +checksum = "f5774f1ef1f982ef2a447f6ee04ec383981a3ab99c8e77a1a7b30182e65bbc84" dependencies = [ - "opentelemetry", + "opentelemetry 0.21.0", ] [[package]] -name = "opentelemetry_api" -version = "0.20.0" +name = "opentelemetry_sdk" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a81f725323db1b1206ca3da8bb19874bbd3f57c3bcd59471bfb04525b265b9b" +checksum = "2f16aec8a98a457a52664d69e0091bac3a0abd18ead9b641cb00202ba4e0efe4" dependencies = [ + "async-trait", + "crossbeam-channel", "futures-channel", + "futures-executor", "futures-util", - "indexmap 1.9.3", - "js-sys", + "glob", "once_cell", - "pin-project-lite", - "thiserror", - "urlencoding", + "opentelemetry 0.21.0", + "ordered-float 4.6.0", + "percent-encoding", + "rand 0.8.5", + "thiserror 1.0.69", + "tokio", + "tokio-stream", ] [[package]] name = "opentelemetry_sdk" -version = "0.20.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8e705a0612d48139799fcbaba0d4a90f06277153e43dd2bdc16c6f0edd8026" +checksum = "231e9d6ceef9b0b2546ddf52335785ce41252bc7474ee8ba05bfad277be13ab8" dependencies = [ "async-trait", - "crossbeam-channel", "futures-channel", "futures-executor", "futures-util", - "once_cell", - "opentelemetry_api", - "ordered-float", + "glob", + "opentelemetry 0.27.1", "percent-encoding", "rand 0.8.5", - "regex", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", + "tracing", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", ] [[package]] name = "ordered-float" -version = "3.9.2" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" dependencies = [ "num-traits", ] @@ -4062,15 +4325,15 @@ dependencies = [ [[package]] name = "parking" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -4078,30 +4341,30 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pem" -version = "3.0.2" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.21.5", + "base64 0.22.1", "serde", ] @@ -4122,29 +4385,29 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -4159,10 +4422,20 @@ dependencies = [ "futures", "libp2p", "tokio", - "tracing", "tracing-subscriber", ] +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -4175,21 +4448,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - -[[package]] -name = "platforms" -version = "3.0.2" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -4200,47 +4467,32 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "polling" -version = "2.8.0" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - -[[package]] -name = "polling" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53b6af1f60f36f8c2ac2aad5459d75a5a9b4be1e8cdd40264f315d78193e531" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", + "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.21", + "rustix", "tracing", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -4256,9 +4508,9 @@ dependencies = [ [[package]] name = "polyval" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", "cpufeatures", @@ -4267,58 +4519,49 @@ dependencies = [ ] [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "portable-atomic" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] -name = "primeorder" -version = "0.13.2" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" -dependencies = [ - "elliptic-curve", -] +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "ppv-lite86" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", + "zerocopy", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "primeorder" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "elliptic-curve", ] [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "prometheus-client" -version = "0.22.0" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510c4f1c9d81d556458f94c98f857748130ea9737bbd6053da497503b26ea63c" +checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" dependencies = [ "dtoa", "itoa", @@ -4334,14 +4577,14 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", ] [[package]] name = "prost" -version = "0.11.9" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" dependencies = [ "bytes", "prost-derive", @@ -4349,15 +4592,30 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.9" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", - "itertools", + "itertools 0.13.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.92", +] + +[[package]] +name = "quanta" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773ce68d0bb9bc7ef20be3536ffe94e223e1f365bd374108b2659fac0c65cfe6" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi 0.11.0+wasi-snapshot-preview1", + "web-sys", + "winapi", ] [[package]] @@ -4385,8 +4643,8 @@ dependencies = [ "futures", "quick-protobuf", "quickcheck-ext", - "thiserror", - "unsigned-varint 0.8.0", + "thiserror 2.0.9", + "unsigned-varint", ] [[package]] @@ -4410,11 +4668,11 @@ dependencies = [ [[package]] name = "quinn" -version = "0.10.2" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ - "async-io 1.13.0", + "async-io", "async-std", "bytes", "futures-io", @@ -4422,47 +4680,52 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.21.9", - "thiserror", + "rustls 0.23.20", + "socket2", + "thiserror 2.0.9", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.10.5" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c78e758510582acc40acb90458401172d41f1016f8c9dde89e49677afb7eec1" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", + "getrandom 0.2.15", "rand 0.8.5", - "ring 0.16.20", + "ring 0.17.8", "rustc-hash", - "rustls 0.21.9", + "rustls 0.23.20", + "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.9", "tinyvec", "tracing", + "web-time 1.1.0", ] [[package]] name = "quinn-udp" -version = "0.4.0" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df19e284d93757a9fb91d63672f7741b129246a669db09d1c0063071debc0c0" +checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" dependencies = [ - "bytes", + "cfg_aliases", "libc", - "socket2 0.5.5", + "once_cell", + "socket2", "tracing", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -4526,7 +4789,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.15", ] [[package]] @@ -4538,11 +4801,20 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "raw-cpuid" +version = "11.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "rayon" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -4550,14 +4822,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] @@ -4569,15 +4839,15 @@ dependencies = [ "pem", "ring 0.16.20", "time", - "x509-parser", + "x509-parser 0.15.1", "yasna", ] [[package]] name = "redis" -version = "0.23.3" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba" +checksum = "c580d9cbbe1d1b479e8d67cf9daf6a62c957e6846048408b80b43ac3f6af84cd" dependencies = [ "async-trait", "bytes", @@ -4594,52 +4864,34 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.11", - "redox_syscall 0.2.16", - "thiserror", + "getrandom 0.2.15", + "libredox", + "thiserror 1.0.69", ] [[package]] name = "regex" -version = "1.10.2" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ - "aho-corasick 1.0.2", + "aho-corasick", "memchr", - "regex-automata 0.4.3", - "regex-syntax 0.8.2", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -4653,13 +4905,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ - "aho-corasick 1.0.2", + "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.5", ] [[package]] @@ -4670,20 +4922,18 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relay-server-example" version = "0.1.0" dependencies = [ - "async-std", - "async-trait", "clap", "futures", "libp2p", - "tracing", + "tokio", "tracing-subscriber", ] @@ -4691,8 +4941,6 @@ dependencies = [ name = "rendezvous-example" version = "0.1.0" dependencies = [ - "async-std", - "async-trait", "futures", "libp2p", "tokio", @@ -4702,20 +4950,23 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.22" +version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "7fe060fe50f524be480214aba758c71f99f90ee8c83c5a36b5e9e1d568eb4eb3" dependencies = [ - "base64 0.21.5", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", - "http", + "http 1.2.0", "http-body", + "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -4724,18 +4975,26 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "quinn", + "rustls 0.23.20", + "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls", + "tower 0.5.2", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "webpki-roots 0.26.7", + "windows-registry", ] [[package]] @@ -4775,16 +5034,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.5" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", - "getrandom 0.2.11", + "cfg-if", + "getrandom 0.2.15", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -4798,9 +5058,9 @@ dependencies = [ [[package]] name = "rmp" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" dependencies = [ "byteorder", "num-traits", @@ -4809,9 +5069,9 @@ dependencies = [ [[package]] name = "rmp-serde" -version = "1.1.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" dependencies = [ "byteorder", "rmp", @@ -4820,28 +5080,31 @@ dependencies = [ [[package]] name = "rtcp" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3677908cadfbecb4cc1da9a56a32524fae4ebdfa7c2ea93886e1b1e846488cb9" +checksum = "33648a781874466a62d89e265fee9f17e32bc7d05a256e6cca41bf97eadcd8aa" dependencies = [ "bytes", - "thiserror", - "webrtc-util", + "thiserror 1.0.69", + "webrtc-util 0.8.1", ] [[package]] name = "rtnetlink" -version = "0.10.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" +checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" dependencies = [ "async-global-executor", "futures", "log", + "netlink-packet-core", "netlink-packet-route", + "netlink-packet-utils", "netlink-proto", - "nix 0.24.3", - "thiserror", + "netlink-sys", + "nix", + "thiserror 1.0.69", "tokio", ] @@ -4854,15 +5117,28 @@ dependencies = [ "bytes", "rand 0.8.5", "serde", - "thiserror", - "webrtc-util", + "thiserror 1.0.69", + "webrtc-util 0.8.1", +] + +[[package]] +name = "rtp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47fca9bd66ae0b1f3f649b8f5003d6176433d7293b78b0fce7e1031816bdd99d" +dependencies = [ + "bytes", + "rand 0.8.5", + "serde", + "thiserror 1.0.69", + "webrtc-util 0.8.1", ] [[package]] name = "rust-embed" -version = "8.0.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -4871,23 +5147,23 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.0.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3d8c6fd84090ae348e63a84336b112b5c3918b3bf0493a581f7bd8ee623c29" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", "shellexpand", - "syn 2.0.39", + "syn 2.0.92", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.0.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "873feff8cb7bf86fdf0a71bb21c95159f4e4a37dd7a4bd1855a940909b583ada" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" dependencies = [ "globset", "sha2 0.10.8", @@ -4896,21 +5172,21 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] @@ -4926,74 +5202,59 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4eb579851244c2c03e7c24f501c3432bed80b8f720af1d6e5b0e0f01555a035" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustix" -version = "0.38.21" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.10", - "windows-sys 0.48.0", + "linux-raw-sys", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.20.8" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.16.20", + "ring 0.17.8", + "rustls-webpki 0.101.7", "sct", - "webpki", ] [[package]] name = "rustls" -version = "0.21.9" +version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ - "log", - "ring 0.17.5", - "rustls-webpki", - "sct", + "once_cell", + "ring 0.17.8", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", ] [[package]] -name = "rustls-native-certs" -version = "0.6.3" +name = "rustls-pemfile" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "openssl-probe", - "rustls-pemfile", - "schannel", - "security-framework", + "rustls-pki-types", ] [[package]] -name = "rustls-pemfile" -version = "1.0.3" +name = "rustls-pki-types" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" dependencies = [ - "base64 0.21.5", + "web-time 1.1.0", ] [[package]] @@ -5002,15 +5263,26 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.5", + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "rw-stream-sink" @@ -5024,9 +5296,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "salsa20" @@ -5048,11 +5320,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -5069,23 +5341,23 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] name = "sdp" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4653054c30ebce63658762eb0d64e27673868a95564474811ae6c220cf767640" +checksum = "13254db766b17451aced321e7397ebf0a446ef0c8d2942b6e67a95815421093f" dependencies = [ "rand 0.8.5", "substring", - "thiserror", + "thiserror 1.0.69", "url", ] @@ -5105,11 +5377,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -5118,9 +5390,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" dependencies = [ "core-foundation-sys", "libc", @@ -5128,9 +5400,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" [[package]] name = "send_wrapper" @@ -5149,41 +5421,42 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.193" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.7.0", "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "serde_path_to_error" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ "itoa", "serde", @@ -5191,13 +5464,22 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.16" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", ] [[package]] @@ -5212,24 +5494,11 @@ dependencies = [ "serde", ] -[[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -5272,46 +5541,42 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "shellexpand" -version = "2.1.2" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" +checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" dependencies = [ "dirs", ] [[package]] -name = "signal-hook" -version = "0.3.17" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", "rand_core 0.6.4", @@ -5319,57 +5584,57 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smol" -version = "1.3.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1" +checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" dependencies = [ - "async-channel", + "async-channel 2.3.1", "async-executor", "async-fs", - "async-io 1.13.0", - "async-lock 2.7.0", + "async-io", + "async-lock", "async-net", "async-process", "blocking", - "futures-lite 1.13.0", + "futures-lite", ] [[package]] name = "smol_str" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" dependencies = [ "serde", ] [[package]] name = "snow" -version = "0.9.4" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58021967fd0a5eeeb23b08df6cc244a4d4a5b4aec1d27c9e02fad1a58b4cd74e" +checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" dependencies = [ "aes-gcm", "blake2", "chacha20poly1305", "curve25519-dalek", "rand_core 0.6.4", - "ring 0.17.5", + "ring 0.17.8", "rustc_version", "sha2 0.10.8", "subtle", @@ -5377,37 +5642,27 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "soketto" -version = "0.7.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" +checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" dependencies = [ - "base64 0.13.1", + "base64 0.22.1", "bytes", "futures", "httparse", "log", "rand 0.8.5", - "sha-1", + "sha1", ] [[package]] @@ -5424,20 +5679,40 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stream-example" +version = "0.1.0" +dependencies = [ + "anyhow", + "futures", + "libp2p", + "libp2p-stream", + "rand 0.8.5", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "stringmatch" version = "0.4.0" @@ -5449,27 +5724,68 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "stun" -version = "0.5.0" +name = "strum" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7beb1624a3ea34778d58d30e2b8606b4d29fe65e87c4d50b87ed30afd5c3830c" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "base64 0.21.5", - "crc", - "lazy_static", - "md-5", - "rand 0.8.5", - "ring 0.16.20", + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.92", +] + +[[package]] +name = "stun" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3f371788132e9d623e6eab4ba28aac083763a4133f045e6ebaee5ceb869803d" +dependencies = [ + "base64 0.21.7", + "crc", + "lazy_static", + "md-5", + "rand 0.8.5", + "ring 0.17.8", + "subtle", + "thiserror 1.0.69", + "tokio", + "url", + "webrtc-util 0.8.1", +] + +[[package]] +name = "stun" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea256fb46a13f9204e9dee9982997b2c3097db175a9fddaa8350310d03c4d5a3" +dependencies = [ + "base64 0.22.1", + "crc", + "lazy_static", + "md-5", + "rand 0.8.5", + "ring 0.17.8", "subtle", - "thiserror", + "thiserror 1.0.69", "tokio", "url", - "webrtc-util", + "webrtc-util 0.10.0", ] [[package]] @@ -5483,9 +5799,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -5500,9 +5816,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "70ae51629bf965c5c098cc9e87908a3df5301051a9e087d6f9bef5c9771ed126" dependencies = [ "proc-macro2", "quote", @@ -5511,9 +5827,12 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -5527,139 +5846,205 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.92", +] + [[package]] name = "sysinfo" -version = "0.29.11" +version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" dependencies = [ - "cfg-if", "core-foundation-sys", "libc", + "memchr", "ntapi", - "once_cell", "rayon", - "winapi", + "windows 0.57.0", ] [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "target-triple" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" + [[package]] name = "tempfile" -version = "3.8.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand 2.0.0", - "redox_syscall 0.4.1", - "rustix 0.38.21", - "windows-sys 0.48.0", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", ] [[package]] name = "termcolor" -version = "1.2.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "thirtyfour" -version = "0.32.0-rc.8" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf0fe180d5f1f7dd32bb5f1a8d19231bb63dc9bbb1985e1dbb6f07163b6a8578" +checksum = "0641fa1353dd7b7864a7a7782e495433de18a9096bc91b5a2d5838ac209c2fa8" dependencies = [ "async-trait", - "base64 0.21.5", - "cookie", - "fantoccini", + "base64 0.22.1", "futures", - "http", - "indexmap 1.9.3", - "log", + "http 1.2.0", + "indexmap 2.7.0", "parking_lot", "paste", + "reqwest", "serde", "serde_json", "serde_repr", "stringmatch", + "strum", "thirtyfour-macros", - "thiserror", + "thiserror 1.0.69", "tokio", + "tracing", "url", ] [[package]] name = "thirtyfour-macros" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cae91d1c7c61ec65817f1064954640ee350a50ae6548ff9a1bdd2489d6ffbb0" +checksum = "b72d056365e368fc57a56d0cec9e41b02fb4a3474a61c8735262b1cfebe67425" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.92", ] [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +dependencies = [ + "thiserror-impl 2.0.9", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "thiserror-impl", + "proc-macro2", + "quote", + "syn 2.0.92", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "thrift" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09" +dependencies = [ + "byteorder", + "integer-encoding", + "log", + "ordered-float 2.10.1", + "threadpool", +] + [[package]] name = "time" -version = "0.3.23" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ + "deranged", "itoa", + "num-conv", + "powerfmt", "serde", "time-core", "time-macros", @@ -5667,19 +6052,30 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.10" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ + "num-conv", "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -5692,9 +6088,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -5707,42 +6103,31 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2", "tokio-macros", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", ] [[package]] @@ -5757,20 +6142,19 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls 0.20.8", + "rustls 0.23.20", "tokio", - "webpki", ] [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -5779,9 +6163,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -5789,32 +6173,67 @@ dependencies = [ "futures-sink", "pin-project-lite", "tokio", - "tracing", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap 2.7.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", ] [[package]] name = "tonic" -version = "0.9.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ + "async-stream", "async-trait", "axum", - "base64 0.21.5", + "base64 0.22.1", "bytes", - "futures-core", - "futures-util", "h2", - "http", + "http 1.2.0", "http-body", + "http-body-util", "hyper", "hyper-timeout", + "hyper-util", "percent-encoding", "pin-project", "prost", + "socket2", "tokio", "tokio-stream", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -5840,18 +6259,34 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-http" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.6.0", "bytes", - "futures-core", "futures-util", - "http", + "http 1.2.0", "http-body", + "http-body-util", "http-range-header", "httpdate", "mime", @@ -5867,21 +6302,21 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -5891,20 +6326,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -5912,47 +6347,56 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] [[package]] -name = "tracing-log" -version = "0.2.0" +name = "tracing-opentelemetry" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +checksum = "c67ac25c5407e7b961fafc6f7e9aa5958fd297aada2d20fa2ae1737357e55596" dependencies = [ - "log", + "js-sys", "once_cell", + "opentelemetry 0.21.0", + "opentelemetry_sdk 0.21.2", + "smallvec", + "tracing", "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time 0.2.4", ] [[package]] name = "tracing-opentelemetry" -version = "0.21.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75327c6b667828ddc28f5e3f169036cb793c3f588d83bf0f262a7f062ffed3c8" +checksum = "97a971f6058498b5c0f1affa23e7ea202057a7301dbff68e968b2d578bcbd053" dependencies = [ + "js-sys", "once_cell", - "opentelemetry", - "opentelemetry_sdk", + "opentelemetry 0.27.1", + "opentelemetry_sdk 0.27.1", "smallvec", "tracing", "tracing-core", - "tracing-log 0.1.3", + "tracing-log", "tracing-subscriber", + "web-time 1.1.0", ] [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -5963,7 +6407,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", ] [[package]] @@ -5977,57 +6421,64 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "triomphe" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" + [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trybuild" -version = "1.0.85" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196a58260a906cedb9bf6d8034b6379d0c11f552416960452f267402ceeddff1" +checksum = "8dcd332a5496c026f1e14b7f3d2b7bd98e509660c04239c58b0ba38a12daded4" dependencies = [ - "basic-toml", "glob", - "once_cell", "serde", "serde_derive", "serde_json", + "target-triple", "termcolor", + "toml", ] [[package]] name = "turn" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f4fcb97da0426e8146fe0e9b78cc13120161087256198701d12d9df77f7701" +checksum = "ffb2ac4f331064513ad510b7a36edc0df555bd61672986607f7c9ff46f98f415" dependencies = [ "async-trait", - "base64 0.21.5", + "base64 0.21.7", "futures", "log", "md-5", "rand 0.8.5", - "ring 0.16.20", - "stun", - "thiserror", + "ring 0.17.8", + "stun 0.5.1", + "thiserror 1.0.69", "tokio", - "webrtc-util", + "tokio-util", + "webrtc-util 0.8.1", ] [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uint" -version = "0.9.5" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" dependencies = [ "byteorder", "crunchy", @@ -6037,45 +6488,21 @@ dependencies = [ [[package]] name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.13" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.10.1" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -6087,12 +6514,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "unsigned-varint" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" - [[package]] name = "unsigned-varint" version = "0.8.0" @@ -6127,12 +6548,12 @@ dependencies = [ [[package]] name = "url" -version = "2.5.0" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna", "percent-encoding", ] @@ -6142,19 +6563,31 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.4.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.15", ] [[package]] @@ -6165,9 +6598,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.4.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" [[package]] name = "vcpkg" @@ -6177,15 +6610,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "void" -version = "1.0.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "waitgroup" @@ -6196,17 +6623,11 @@ dependencies = [ "atomic-waker", ] -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "walkdir" -version = "2.3.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -6235,46 +6656,47 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6282,31 +6704,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-bindgen-test" -version = "0.3.38" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6433b7c56db97397842c46b67e11873eda263170afeb3a2dc74a7cb370fee0d" +checksum = "c61d44563646eb934577f2772656c7ad5e9c90fac78aa8013d776fcdaf24625d" dependencies = [ - "console_error_panic_hook", "js-sys", + "minicov", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", @@ -6315,13 +6737,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.38" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "493fcbab756bb764fa37e6bee8cec2dd709eb4273d06d0c282a5e74275ded735" +checksum = "54171416ce73aa0b9c377b51cc3cb542becee1cd678204812e8392e5b0e4a031" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", ] [[package]] @@ -6337,48 +6759,48 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "webdriver" -version = "0.46.0" +name = "web-time" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9973cb72c8587d5ad5efdb91e663d36177dc37725e6c90ca86c626b0cc45c93f" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" dependencies = [ - "base64 0.13.1", - "bytes", - "cookie", - "http", - "log", - "serde", - "serde_derive", - "serde_json", - "time", - "unicode-segmentation", - "url", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "webpki" -version = "0.22.2" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "js-sys", + "wasm-bindgen", ] [[package]] name = "webpki-roots" -version = "0.25.2" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "webrtc" @@ -6400,15 +6822,15 @@ dependencies = [ "regex", "ring 0.16.20", "rtcp", - "rtp", - "rustls 0.21.9", + "rtp 0.9.0", + "rustls 0.21.12", "sdp", "serde", "serde_json", "sha2 0.10.8", "smol_str", - "stun", - "thiserror", + "stun 0.5.1", + "thiserror 1.0.69", "time", "tokio", "turn", @@ -6421,21 +6843,21 @@ dependencies = [ "webrtc-media", "webrtc-sctp", "webrtc-srtp", - "webrtc-util", + "webrtc-util 0.8.1", ] [[package]] name = "webrtc-data" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45d2461d0e0bf93f181e30eb0b40df32b8bf3efb89c53cebb1990e603e2067d" +checksum = "e8c08e648e10572b9edbe741074e0f4d3cb221aa7cdf9a814ee71606de312f33" dependencies = [ "bytes", "log", - "thiserror", + "thiserror 1.0.69", "tokio", "webrtc-sctp", - "webrtc-util", + "webrtc-util 0.8.1", ] [[package]] @@ -6451,7 +6873,7 @@ dependencies = [ "byteorder", "cbc", "ccm", - "der-parser", + "der-parser 8.2.0", "hkdf", "hmac 0.12.1", "log", @@ -6462,17 +6884,17 @@ dependencies = [ "rand_core 0.6.4", "rcgen", "ring 0.16.20", - "rustls 0.21.9", + "rustls 0.21.12", "sec1", "serde", "sha1", "sha2 0.10.8", "subtle", - "thiserror", + "thiserror 1.0.69", "tokio", - "webrtc-util", + "webrtc-util 0.8.1", "x25519-dalek", - "x509-parser", + "x509-parser 0.15.1", ] [[package]] @@ -6488,48 +6910,48 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "stun", - "thiserror", + "stun 0.5.1", + "thiserror 1.0.69", "tokio", "turn", "url", "uuid", "waitgroup", "webrtc-mdns", - "webrtc-util", + "webrtc-util 0.8.1", ] [[package]] name = "webrtc-mdns" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bebbd40e7f8b630a0f1a74783dbfff1edfc0ccaae891c4689891156a8c4d8c" +checksum = "ce981f93104a8debb3563bb0cedfe4aa2f351fdf6b53f346ab50009424125c08" dependencies = [ "log", - "socket2 0.5.5", - "thiserror", + "socket2", + "thiserror 1.0.69", "tokio", - "webrtc-util", + "webrtc-util 0.8.1", ] [[package]] name = "webrtc-media" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfde3c7b9450b67d466bb2f02c6d9ff9514d33535eb9994942afd1f828839d1" +checksum = "280017b6b9625ef7329146332518b339c3cceff231cc6f6a9e0e6acab25ca4af" dependencies = [ "byteorder", "bytes", "rand 0.8.5", - "rtp", - "thiserror", + "rtp 0.10.0", + "thiserror 1.0.69", ] [[package]] name = "webrtc-sctp" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1af6116b7f9703560c3ad0b32f67220b171bb1b59633b03563db8404d0e482ea" +checksum = "df75ec042002fe995194712cbeb2029107a60a7eab646f1b789eb1be94d0e367" dependencies = [ "arc-swap", "async-trait", @@ -6537,9 +6959,9 @@ dependencies = [ "crc", "log", "rand 0.8.5", - "thiserror", + "thiserror 1.0.69", "tokio", - "webrtc-util", + "webrtc-util 0.8.1", ] [[package]] @@ -6557,31 +6979,51 @@ dependencies = [ "hmac 0.12.1", "log", "rtcp", - "rtp", + "rtp 0.9.0", "sha1", "subtle", - "thiserror", + "thiserror 1.0.69", "tokio", - "webrtc-util", + "webrtc-util 0.8.1", ] [[package]] name = "webrtc-util" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adc96bee68417e1f4d19dd7698124a7f859db55ae2fd3eedbbb7e732f614735" +checksum = "5e85154ef743d9a2a116d104faaaa82740a281b8b4bed5ee691a2df6c133d873" +dependencies = [ + "async-trait", + "bitflags 1.3.2", + "bytes", + "ipnet", + "lazy_static", + "libc", + "log", + "nix", + "rand 0.8.5", + "thiserror 1.0.69", + "tokio", + "winapi", +] + +[[package]] +name = "webrtc-util" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1438a8fd0d69c5775afb4a71470af92242dbd04059c61895163aa3c1ef933375" dependencies = [ "async-trait", "bitflags 1.3.2", "bytes", - "cc", "ipnet", "lazy_static", "libc", "log", - "nix 0.26.4", + "nix", + "portable-atomic", "rand 0.8.5", - "thiserror", + "thiserror 1.0.69", "tokio", "winapi", ] @@ -6591,7 +7033,7 @@ name = "webtransport-tests" version = "0.1.0" dependencies = [ "futures", - "getrandom 0.2.11", + "getrandom 0.2.15", "libp2p-core", "libp2p-identity", "libp2p-noise", @@ -6606,9 +7048,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] name = "winapi" @@ -6628,11 +7070,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -6643,21 +7085,105 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.51.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538" dependencies = [ - "windows-core", - "windows-targets 0.48.5", + "windows-core 0.53.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.51.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "9dcc5b895a6377f1ab9fa55acedab1fd5ac0db66ad1e6c7f47e28a22e446a5dd" dependencies = [ - "windows-targets 0.48.5", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.92", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.92", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", ] [[package]] @@ -6675,7 +7201,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -6695,17 +7230,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -6716,9 +7252,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -6728,9 +7264,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -6740,9 +7276,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -6752,9 +7294,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -6764,9 +7306,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -6776,9 +7318,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -6788,9 +7330,18 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] [[package]] name = "winreg" @@ -6802,11 +7353,23 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "x25519-dalek" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", "rand_core 0.6.4", @@ -6820,23 +7383,40 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" dependencies = [ - "asn1-rs", + "asn1-rs 0.5.2", "data-encoding", - "der-parser", + "der-parser 8.2.0", "lazy_static", "nom", - "oid-registry", + "oid-registry 0.6.1", "ring 0.16.20", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs 0.6.2", + "data-encoding", + "der-parser 9.0.0", + "lazy_static", + "nom", + "oid-registry 0.7.1", + "rusticata-macros", + "thiserror 1.0.69", "time", ] [[package]] name = "xml-rs" -version = "0.8.17" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eee6bf5926be7cf998d7381a9a23d833fd493f6a8034658a9505a4dc4b20444" +checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432" [[package]] name = "xmltree" @@ -6862,6 +7442,22 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "yamux" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17610762a1207ee816c6fadc29220904753648aba0a9ed61c7b8336e80a559c4" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot", + "pin-project", + "rand 0.8.5", + "static_assertions", + "web-time 1.1.0", +] + [[package]] name = "yasna" version = "0.5.2" @@ -6871,11 +7467,77 @@ dependencies = [ "time", ] +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.92", + "synstructure 0.13.1", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.92", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.92", + "synstructure 0.13.1", +] + [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -6888,5 +7550,27 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.92", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.92", ] diff --git a/Cargo.toml b/Cargo.toml index 8fca61c365e..b8186584487 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "core", "examples/autonat", + "examples/autonatv2", "examples/browser-webrtc", "examples/chat", "examples/dcutr", @@ -14,13 +15,13 @@ members = [ "examples/ping", "examples/relay-server", "examples/rendezvous", + "examples/stream", "examples/upnp", "hole-punching-tests", "identity", "interop-tests", "misc/allow-block-list", "misc/connection-limits", - "misc/futures-bounded", "misc/keygen", "misc/memory-connection-limits", "misc/metrics", @@ -29,6 +30,7 @@ members = [ "misc/quickcheck-ext", "misc/rw-stream-sink", "misc/server", + "misc/test-utils", "misc/webrtc-utils", "muxers/mplex", "muxers/test-harness", @@ -45,10 +47,11 @@ members = [ "protocols/relay", "protocols/rendezvous", "protocols/request-response", + "protocols/stream", "protocols/upnp", - "swarm", "swarm-derive", "swarm-test", + "swarm", "transports/dns", "transports/noise", "transports/plaintext", @@ -57,70 +60,88 @@ members = [ "transports/tcp", "transports/tls", "transports/uds", - "transports/webrtc", "transports/webrtc-websys", + "transports/webrtc", + "transports/websocket-websys", "transports/websocket", "transports/webtransport-websys", - "transports/websocket-websys", "wasm-tests/webtransport-tests", ] resolver = "2" [workspace.package] -rust-version = "1.73.0" +rust-version = "1.75.0" [workspace.dependencies] +libp2p = { version = "0.54.2", path = "libp2p" } +libp2p-allow-block-list = { version = "0.4.1", path = "misc/allow-block-list" } +libp2p-autonat = { version = "0.13.1", path = "protocols/autonat" } +libp2p-connection-limits = { version = "0.4.1", path = "misc/connection-limits" } +libp2p-core = { version = "0.42.1", path = "core" } +libp2p-dcutr = { version = "0.12.1", path = "protocols/dcutr" } +libp2p-dns = { version = "0.42.1", path = "transports/dns" } +libp2p-floodsub = { version = "0.45.0", path = "protocols/floodsub" } +libp2p-gossipsub = { version = "0.48.0", path = "protocols/gossipsub" } +libp2p-identify = { version = "0.46.0", path = "protocols/identify" } +libp2p-identity = { version = "0.2.10" } +libp2p-kad = { version = "0.47.0", path = "protocols/kad" } +libp2p-mdns = { version = "0.46.1", path = "protocols/mdns" } +libp2p-memory-connection-limits = { version = "0.3.1", path = "misc/memory-connection-limits" } +libp2p-metrics = { version = "0.15.0", path = "misc/metrics" } +libp2p-mplex = { version = "0.42.0", path = "muxers/mplex" } +libp2p-noise = { version = "0.45.1", path = "transports/noise" } +libp2p-perf = { version = "0.4.0", path = "protocols/perf" } +libp2p-ping = { version = "0.45.1", path = "protocols/ping" } +libp2p-plaintext = { version = "0.42.0", path = "transports/plaintext" } +libp2p-pnet = { version = "0.25.0", path = "transports/pnet" } +libp2p-quic = { version = "0.11.2", path = "transports/quic" } +libp2p-relay = { version = "0.18.1", path = "protocols/relay" } +libp2p-rendezvous = { version = "0.15.1", path = "protocols/rendezvous" } +libp2p-request-response = { version = "0.28.0", path = "protocols/request-response" } +libp2p-server = { version = "0.12.6", path = "misc/server" } +libp2p-stream = { version = "0.2.0-alpha.1", path = "protocols/stream" } +libp2p-swarm = { version = "0.45.2", path = "swarm" } +libp2p-swarm-derive = { version = "=0.35.0", path = "swarm-derive" } # `libp2p-swarm-derive` may not be compatible with different `libp2p-swarm` non-breaking releases. E.g. `libp2p-swarm` might introduce a new enum variant `FromSwarm` (which is `#[non-exhaustive]`) in a non-breaking release. Older versions of `libp2p-swarm-derive` would not forward this enum variant within the `NetworkBehaviour` hierarchy. Thus the version pinning is required. +libp2p-swarm-test = { version = "0.5.0", path = "swarm-test" } +libp2p-tcp = { version = "0.42.0", path = "transports/tcp" } +libp2p-tls = { version = "0.5.0", path = "transports/tls" } +libp2p-uds = { version = "0.41.0", path = "transports/uds" } +libp2p-upnp = { version = "0.3.1", path = "protocols/upnp" } +libp2p-webrtc = { version = "0.8.0-alpha", path = "transports/webrtc" } +libp2p-webrtc-utils = { version = "0.3.0", path = "misc/webrtc-utils" } +libp2p-webrtc-websys = { version = "0.4.0-alpha.2", path = "transports/webrtc-websys" } +libp2p-websocket = { version = "0.44.1", path = "transports/websocket" } +libp2p-websocket-websys = { version = "0.4.1", path = "transports/websocket-websys" } +libp2p-webtransport-websys = { version = "0.4.1", path = "transports/webtransport-websys" } +libp2p-yamux = { version = "0.46.0", path = "muxers/yamux" } +libp2p-test-utils = { version = "0.1.0", path = "misc/test-utils" } + +# External dependencies +async-std-resolver = { version = "0.25.0-alpha.4", default-features = false } asynchronous-codec = { version = "0.7.0" } -futures-bounded = { version = "0.2.3", path = "misc/futures-bounded" } -libp2p = { version = "0.53.2", path = "libp2p" } -libp2p-allow-block-list = { version = "0.3.0", path = "misc/allow-block-list" } -libp2p-autonat = { version = "0.12.0", path = "protocols/autonat" } -libp2p-connection-limits = { version = "0.3.0", path = "misc/connection-limits" } -libp2p-core = { version = "0.41.2", path = "core" } -libp2p-dcutr = { version = "0.11.0", path = "protocols/dcutr" } -libp2p-dns = { version = "0.41.1", path = "transports/dns" } -libp2p-floodsub = { version = "0.44.0", path = "protocols/floodsub" } -libp2p-gossipsub = { version = "0.46.1", path = "protocols/gossipsub" } -libp2p-identify = { version = "0.44.1", path = "protocols/identify" } -libp2p-identity = { version = "0.2.8" } -libp2p-kad = { version = "0.45.3", path = "protocols/kad" } -libp2p-mdns = { version = "0.45.1", path = "protocols/mdns" } -libp2p-memory-connection-limits = { version = "0.2.0", path = "misc/memory-connection-limits" } -libp2p-metrics = { version = "0.14.1", path = "misc/metrics" } -libp2p-mplex = { version = "0.41.0", path = "muxers/mplex" } -libp2p-muxer-test-harness = { path = "muxers/test-harness" } -libp2p-noise = { version = "0.44.0", path = "transports/noise" } -libp2p-perf = { version = "0.3.0", path = "protocols/perf" } -libp2p-ping = { version = "0.44.0", path = "protocols/ping" } -libp2p-plaintext = { version = "0.41.0", path = "transports/plaintext" } -libp2p-pnet = { version = "0.24.0", path = "transports/pnet" } -libp2p-quic = { version = "0.10.2", path = "transports/quic" } -libp2p-relay = { version = "0.17.1", path = "protocols/relay" } -libp2p-rendezvous = { version = "0.14.0", path = "protocols/rendezvous" } -libp2p-request-response = { version = "0.26.1", path = "protocols/request-response" } -libp2p-server = { version = "0.12.5", path = "misc/server" } -libp2p-swarm = { version = "0.44.0", path = "swarm" } -libp2p-swarm-derive = { version = "=0.34.0", path = "swarm-derive" } # `libp2p-swarm-derive` may not be compatible with different `libp2p-swarm` non-breaking releases. E.g. `libp2p-swarm` might introduce a new enum variant `FromSwarm` (which is `#[non-exhaustive]`) in a non-breaking release. Older versions of `libp2p-swarm-derive` would not forward this enum variant within the `NetworkBehaviour` hierarchy. Thus the version pinning is required. -libp2p-swarm-test = { version = "0.3.0", path = "swarm-test" } -libp2p-tcp = { version = "0.41.0", path = "transports/tcp" } -libp2p-tls = { version = "0.3.0", path = "transports/tls" } -libp2p-uds = { version = "0.40.0", path = "transports/uds" } -libp2p-upnp = { version = "0.2.0", path = "protocols/upnp" } -libp2p-webrtc = { version = "0.7.0-alpha", path = "transports/webrtc" } -libp2p-webrtc-utils = { version = "0.2.0", path = "misc/webrtc-utils" } -libp2p-webrtc-websys = { version = "0.3.0-alpha", path = "transports/webrtc-websys" } -libp2p-websocket = { version = "0.43.0", path = "transports/websocket" } -libp2p-websocket-websys = { version = "0.3.1", path = "transports/websocket-websys" } -libp2p-webtransport-websys = { version = "0.2.0", path = "transports/webtransport-websys" } -libp2p-yamux = { version = "0.45.1", path = "muxers/yamux" } +env_logger = "0.11" +futures = "0.3.30" +futures-bounded = { version = "0.2.4" } +futures-rustls = { version = "0.26.0", default-features = false } +getrandom = "0.2" +hickory-proto = { version = "0.25.0-alpha.4", default-features = false } +hickory-resolver = { version = "0.25.0-alpha.4", default-features = false } multiaddr = "0.18.1" multihash = "0.19.1" multistream-select = { version = "0.13.0", path = "misc/multistream-select" } -prometheus-client = "0.22.0" +prometheus-client = "0.22.2" quick-protobuf-codec = { version = "0.3.1", path = "misc/quick-protobuf-codec" } quickcheck = { package = "quickcheck-ext", path = "misc/quickcheck-ext" } +rcgen = "0.11.3" +ring = "0.17.8" rw-stream-sink = { version = "0.4.0", path = "misc/rw-stream-sink" } +thiserror = "2" +tokio = { version = "1.38", default-features = false } +tracing = "0.1.41" +tracing-subscriber = "0.3.19" unsigned-varint = { version = "0.8.0" } +web-time = "1.1.0" +hashlink = "0.9.0" [patch.crates-io] @@ -134,11 +155,15 @@ libp2p-identity = { path = "identity" } [workspace.lints] rust.unreachable_pub = "warn" clippy.used_underscore_binding = "warn" -clippy.pedantic = "allow" +clippy.pedantic = { level = "allow", priority = -1 } clippy.type_complexity = "allow" clippy.unnecessary_wraps = "warn" clippy.manual_let_else = "warn" clippy.dbg_macro = "warn" [workspace.metadata.release] -pre-release-hook = ["/bin/sh", '-c', '/bin/sh $WORKSPACE_ROOT/scripts/add-changelog-header.sh'] # Nested use of shell to expand variables. +pre-release-hook = [ + "/bin/sh", + '-c', + '/bin/sh $WORKSPACE_ROOT/scripts/add-changelog-header.sh', +] # Nested use of shell to expand variables. diff --git a/FUNDING.json b/FUNDING.json new file mode 100644 index 00000000000..d707bcb2f8c --- /dev/null +++ b/FUNDING.json @@ -0,0 +1,13 @@ +{ + "drips": { + "ethereum": { + "ownedBy": "0x79c49637182Ea32734f7e8445a3649c22ff348f2" + }, + "filecoin": { + "ownedBy": "0x79c49637182Ea32734f7e8445a3649c22ff348f2" + } + }, + "opRetro": { + "projectId": "0xdf1bb03d08808e2d789f5eac8462bdc560f1bb5b0877f0cf8c66ab53a0bc2f5c" + } +} diff --git a/README.md b/README.md index 62375b3dab0..24e88f62751 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,8 @@ This repository is the central place for Rust development of the [libp2p](https: many protocols in this repository. - For **security related issues** please [file a private security vulnerability - report](https://github.com/libp2p/rust-libp2p/security/advisories/new) - or reach out to [security@libp2p.io](mailto:security@libp2p.io). Please do not - file a public issue on GitHub. + report](https://github.com/libp2p/rust-libp2p/security/advisories/new) . Please do not file a + public issue on GitHub. - To **report bugs, suggest improvements or request new features** please open a GitHub issue on this repository. @@ -79,9 +78,8 @@ Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). (In alphabetical order.) +- Guillaume Michel ([@guillaumemichel](https://github.com/guillaumemichel)) - João Oliveira ([@jxs](https://github.com/jxs)) -- Max Inden ([@mxinden](https://github.com/mxinden/)) -- Thomas Eizinger ([@thomaseizinger](https://github.com/thomaseizinger)) ## Notable users @@ -99,8 +97,10 @@ Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). - [OpenMina](https://github.com/openmina/openmina) - In-browser Mina Rust implementation. - [rust-ipfs](https://github.com/rs-ipfs/rust-ipfs) - IPFS implementation in Rust. - [Safe Network](https://github.com/maidsafe/safe_network) - Safe Network implementation in Rust. +- [SQD Network](https://github.com/subsquid/sqd-network) - A decentralized storage for Web3 data. - [Starcoin](https://github.com/starcoinorg/starcoin) - A smart contract blockchain network that scales by layering. - [Subspace](https://github.com/subspace/subspace) - Subspace Network reference implementation - [Substrate](https://github.com/paritytech/substrate) - Framework for blockchain innovation, used by [Polkadot](https://www.parity.io/technologies/polkadot/). - [Taple](https://github.com/opencanarias/taple-core) - Sustainable DLT for asset and process traceability by [OpenCanarias](https://www.opencanarias.com/en/). +- [Ceylon](https://github.com/ceylonai/ceylon) - A Multi-Agent System (MAS) Development Framework. diff --git a/ROADMAP.md b/ROADMAP.md index 0d422a6d385..a8df8242730 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -61,7 +61,7 @@ The project supports Wasm already today, though the developer experience is cumb Properly supporting Wasm opens rust-libp2p to a whole new set of use-cases. I would love for this to happen earlier. Though (a) I think we should prioritize improving existing functionality over new functionality and (b) we don't have high demand for this feature from the community. -(One could argue that that demand follows this roadmap item and not the other way round.) +(One could argue that the demand follows this roadmap item and not the other way round.) ### WebRTC in the browser via WASM diff --git a/SECURITY.md b/SECURITY.md index 0e5a3f2e55f..f3ae83405e8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,5 +7,3 @@ By default we provide security patches for the latest released version only. On ## Reporting a Vulnerability Please do not file a public issue on GitHub. Instead, please [file a private security vulnerability report](https://github.com/libp2p/rust-libp2p/security/advisories/new). - -If you need further assistance, please reach out to [security@libp2p.io](mailto:security@libp2p.io). diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index a7cd7fd46b4..68b1f99cc2a 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -1,3 +1,23 @@ +## 0.42.1 + +- Added `libp2p::core::util::unreachable` that is a drop-in replacement of `void::unreachable`. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). + +## 0.42.0 + +- Update `Transport::dial` function signature with a `DialOpts` param and remove `Transport::dial_as_listener`: + - `DialOpts` struct contains `PortUse` and `Endpoint`, + - `PortUse` allows controlling port allocation of new connections (defaults to `PortUse::Reuse`) - + - Add `port_use` field to `ConnectedPoint` + - Set `endpoint` field in `DialOpts` to `Endpoint::Listener` to dial as a listener +- Remove `Transport::address_translation` and relocate functionality to `libp2p_swarm` + +See [PR 4568]. + +## 0.41.3 +- Use `web-time` instead of `instant`. + See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). + ## 0.41.2 - Implement `std::fmt::Display` on `ListenerId`. @@ -304,7 +324,7 @@ - Add `From<&PublicKey> for PeerId` (see [PR 2145]). -- Remove `TInEvent` and `TOutEvent` trait paramters on most public types. +- Remove `TInEvent` and `TOutEvent` trait parameters on most public types. `TInEvent` and `TOutEvent` are implied through `THandler` and thus superflucious. Both are removed in favor of a derivation through `THandler` (see [PR 2183]). diff --git a/core/Cargo.toml b/core/Cargo.toml index 7ead34b69d9..162800b96c2 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-core" edition = "2021" rust-version = { workspace = true } description = "Core traits and structs of libp2p" -version = "0.41.2" +version = "0.42.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,45 +11,39 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -either = "1.9" +either = "1.11" fnv = "1.0" -futures = { version = "0.3.29", features = ["executor", "thread-pool"] } +futures = { workspace = true, features = ["executor", "thread-pool"] } futures-timer = "3" -instant = "0.1.12" +web-time = { workspace = true } libp2p-identity = { workspace = true, features = ["peerid", "ed25519"] } multiaddr = { workspace = true } multihash = { workspace = true } multistream-select = { workspace = true } -once_cell = "1.18.0" -parking_lot = "0.12.0" -pin-project = "1.1.3" +once_cell = "1.19.0" +parking_lot = "0.12.3" +pin-project = "1.1.5" quick-protobuf = "0.8" rand = "0.8" rw-stream-sink = { workspace = true } -serde = { version = "1", optional = true, features = ["derive"] } -smallvec = "1.11.2" -thiserror = "1.0" -tracing = "0.1.37" +thiserror = { workspace = true } +tracing = { workspace = true } unsigned-varint = { workspace = true } -void = "1" [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } -libp2p-mplex = { path = "../muxers/mplex" } # Using `path` here because this is a cyclic dev-dependency which otherwise breaks releasing. -libp2p-noise = { path = "../transports/noise" } # Using `path` here because this is a cyclic dev-dependency which otherwise breaks releasing. +libp2p-mplex = { path = "../muxers/mplex" } # Using `path` here because this is a cyclic dev-dependency which otherwise breaks releasing. +libp2p-noise = { path = "../transports/noise" } # Using `path` here because this is a cyclic dev-dependency which otherwise breaks releasing. multihash = { workspace = true, features = ["arb"] } -quickcheck = { workspace = true } libp2p-identity = { workspace = true, features = ["ed25519", "rand"] } [features] -serde = ["multihash/serde-codec", "dep:serde", "libp2p-identity/serde"] +serde = ["multihash/serde-codec", "libp2p-identity/serde"] # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/core/src/connection.rs b/core/src/connection.rs index 7a339b0661b..8779f76c03c 100644 --- a/core/src/connection.rs +++ b/core/src/connection.rs @@ -18,7 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::multiaddr::{Multiaddr, Protocol}; +use crate::{ + multiaddr::{Multiaddr, Protocol}, + transport::PortUse, +}; /// The endpoint roles associated with a peer-to-peer communication channel. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -60,26 +63,31 @@ pub enum ConnectedPoint { /// Multiaddress that was successfully dialed. address: Multiaddr, /// Whether the role of the local node on the connection should be - /// overriden. I.e. whether the local node should act as a listener on + /// overridden. I.e. whether the local node should act as a listener on /// the outgoing connection. /// /// This option is needed for NAT and firewall hole punching. /// /// - [`Endpoint::Dialer`] represents the default non-overriding option. /// - /// - [`Endpoint::Listener`] represents the overriding option. - /// Realization depends on the transport protocol. E.g. in the case of - /// TCP, both endpoints dial each other, resulting in a _simultaneous - /// open_ TCP connection. On this new connection both endpoints assume - /// to be the dialer of the connection. This is problematic during the - /// connection upgrade process where an upgrade assumes one side to be - /// the listener. With the help of this option, both peers can - /// negotiate the roles (dialer and listener) for the new connection - /// ahead of time, through some external channel, e.g. the DCUtR - /// protocol, and thus have one peer dial the other and upgrade the - /// connection as a dialer and one peer dial the other and upgrade the - /// connection _as a listener_ overriding its role. + /// - [`Endpoint::Listener`] represents the overriding option. Realization depends on the + /// transport protocol. E.g. in the case of TCP, both endpoints dial each other, + /// resulting in a _simultaneous open_ TCP connection. On this new connection both + /// endpoints assume to be the dialer of the connection. This is problematic during the + /// connection upgrade process where an upgrade assumes one side to be the listener. With + /// the help of this option, both peers can negotiate the roles (dialer and listener) for + /// the new connection ahead of time, through some external channel, e.g. the DCUtR + /// protocol, and thus have one peer dial the other and upgrade the connection as a + /// dialer and one peer dial the other and upgrade the connection _as a listener_ + /// overriding its role. role_override: Endpoint, + /// Whether the port for the outgoing connection was reused from a listener + /// or a new port was allocated. This is useful for address translation. + /// + /// The port use is implemented on a best-effort basis. It is not guaranteed + /// that [`PortUse::Reuse`] actually reused a port. A good example is the case + /// where there is no listener available to reuse a port from. + port_use: PortUse, }, /// We received the node. Listener { @@ -113,27 +121,18 @@ impl ConnectedPoint { /// Returns true if we are `Dialer`. pub fn is_dialer(&self) -> bool { - match self { - ConnectedPoint::Dialer { .. } => true, - ConnectedPoint::Listener { .. } => false, - } + matches!(self, ConnectedPoint::Dialer { .. }) } /// Returns true if we are `Listener`. pub fn is_listener(&self) -> bool { - match self { - ConnectedPoint::Dialer { .. } => false, - ConnectedPoint::Listener { .. } => true, - } + matches!(self, ConnectedPoint::Listener { .. }) } /// Returns true if the connection is relayed. pub fn is_relayed(&self) -> bool { match self { - ConnectedPoint::Dialer { - address, - role_override: _, - } => address, + ConnectedPoint::Dialer { address, .. } => address, ConnectedPoint::Listener { local_addr, .. } => local_addr, } .iter() diff --git a/core/src/either.rs b/core/src/either.rs index 3f79b2b37a9..aa0340a46bf 100644 --- a/core/src/either.rs +++ b/core/src/either.rs @@ -18,16 +18,20 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::muxing::StreamMuxerEvent; -use crate::{ - muxing::StreamMuxer, - transport::{ListenerId, Transport, TransportError, TransportEvent}, - Multiaddr, +use std::{ + pin::Pin, + task::{Context, Poll}, }; + use either::Either; use futures::prelude::*; use pin_project::pin_project; -use std::{pin::Pin, task::Context, task::Poll}; + +use crate::{ + muxing::{StreamMuxer, StreamMuxerEvent}, + transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}, + Multiaddr, +}; impl StreamMuxer for future::Either where @@ -172,48 +176,23 @@ where } } - fn dial(&mut self, addr: Multiaddr) -> Result> { - use TransportError::*; - match self { - Either::Left(a) => match a.dial(addr) { - Ok(connec) => Ok(EitherFuture::First(connec)), - Err(MultiaddrNotSupported(addr)) => Err(MultiaddrNotSupported(addr)), - Err(Other(err)) => Err(Other(Either::Left(err))), - }, - Either::Right(b) => match b.dial(addr) { - Ok(connec) => Ok(EitherFuture::Second(connec)), - Err(MultiaddrNotSupported(addr)) => Err(MultiaddrNotSupported(addr)), - Err(Other(err)) => Err(Other(Either::Right(err))), - }, - } - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, - ) -> Result> - where - Self: Sized, - { + opts: DialOpts, + ) -> Result> { use TransportError::*; match self { - Either::Left(a) => match a.dial_as_listener(addr) { + Either::Left(a) => match a.dial(addr, opts) { Ok(connec) => Ok(EitherFuture::First(connec)), Err(MultiaddrNotSupported(addr)) => Err(MultiaddrNotSupported(addr)), Err(Other(err)) => Err(Other(Either::Left(err))), }, - Either::Right(b) => match b.dial_as_listener(addr) { + Either::Right(b) => match b.dial(addr, opts) { Ok(connec) => Ok(EitherFuture::Second(connec)), Err(MultiaddrNotSupported(addr)) => Err(MultiaddrNotSupported(addr)), Err(Other(err)) => Err(Other(Either::Right(err))), }, } } - - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - match self { - Either::Left(a) => a.address_translation(server, observed), - Either::Right(b) => b.address_translation(server, observed), - } - } } diff --git a/core/src/lib.rs b/core/src/lib.rs index abb83481d6c..bbe42adc26a 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -22,14 +22,12 @@ //! //! The main concepts of libp2p-core are: //! -//! - The [`Transport`] trait defines how to reach a remote node or listen for -//! incoming remote connections. See the [`transport`] module. -//! - The [`StreamMuxer`] trait is implemented on structs that hold a connection -//! to a remote and can subdivide this connection into multiple substreams. -//! See the [`muxing`] module. -//! - The [`UpgradeInfo`], [`InboundUpgrade`] and [`OutboundUpgrade`] traits -//! define how to upgrade each individual substream to use a protocol. -//! See the `upgrade` module. +//! - The [`Transport`] trait defines how to reach a remote node or listen for incoming remote +//! connections. See the [`transport`] module. +//! - The [`StreamMuxer`] trait is implemented on structs that hold a connection to a remote and can +//! subdivide this connection into multiple substreams. See the [`muxing`] module. +//! - The [`UpgradeInfo`], [`InboundUpgrade`] and [`OutboundUpgrade`] traits define how to upgrade +//! each individual substream to use a protocol. See the `upgrade` module. #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] @@ -37,7 +35,8 @@ mod proto { #![allow(unreachable_pub)] include!("generated/mod.rs"); pub use self::{ - envelope_proto::*, peer_record_proto::mod_PeerRecord::*, peer_record_proto::PeerRecord, + envelope_proto::*, + peer_record_proto::{mod_PeerRecord::*, PeerRecord}, }; } @@ -45,8 +44,6 @@ mod proto { pub use multiaddr; pub type Negotiated = multistream_select::Negotiated; -mod translation; - pub mod connection; pub mod either; pub mod muxing; @@ -56,15 +53,25 @@ pub mod transport; pub mod upgrade; pub use connection::{ConnectedPoint, Endpoint}; +pub use libp2p_identity::PeerId; pub use multiaddr::Multiaddr; pub use multihash; pub use muxing::StreamMuxer; pub use peer_record::PeerRecord; pub use signed_envelope::SignedEnvelope; -pub use translation::address_translation; pub use transport::Transport; pub use upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; #[derive(Debug, thiserror::Error)] #[error(transparent)] pub struct DecodeError(quick_protobuf::Error); + +pub mod util { + use std::convert::Infallible; + + /// A safe version of [`std::intrinsics::unreachable`]. + #[inline(always)] + pub fn unreachable(x: Infallible) -> ! { + match x {} + } +} diff --git a/core/src/muxing.rs b/core/src/muxing.rs index 477e1608073..60062f899f9 100644 --- a/core/src/muxing.rs +++ b/core/src/muxing.rs @@ -24,7 +24,7 @@ //! has ownership of a connection, lets you open and close substreams. //! //! > **Note**: You normally don't need to use the methods of the `StreamMuxer` directly, as this -//! > is managed by the library's internals. +//! > is managed by the library's internals. //! //! Each substream of a connection is an isolated stream of data. All the substreams are muxed //! together so that the data read from or written to each substream doesn't influence the other @@ -36,9 +36,9 @@ //! require maintaining long-lived channels of communication. //! //! > **Example**: The Kademlia protocol opens a new substream for each request it wants to -//! > perform. Multiple requests can be performed simultaneously by opening multiple -//! > substreams, without having to worry about associating responses with the -//! > right request. +//! > perform. Multiple requests can be performed simultaneously by opening multiple +//! > substreams, without having to worry about associating responses with the +//! > right request. //! //! # Implementing a muxing protocol //! @@ -50,21 +50,23 @@ //! The upgrade process will take ownership of the connection, which makes it possible for the //! implementation of `StreamMuxer` to control everything that happens on the wire. -use futures::{task::Context, task::Poll, AsyncRead, AsyncWrite}; +use std::{future::Future, pin::Pin}; + +use futures::{ + task::{Context, Poll}, + AsyncRead, AsyncWrite, +}; use multiaddr::Multiaddr; -use std::future::Future; -use std::pin::Pin; -pub use self::boxed::StreamMuxerBox; -pub use self::boxed::SubstreamBox; +pub use self::boxed::{StreamMuxerBox, SubstreamBox}; mod boxed; /// Provides multiplexing for a connection by allowing users to open substreams. /// -/// A substream created by a [`StreamMuxer`] is a type that implements [`AsyncRead`] and [`AsyncWrite`]. -/// The [`StreamMuxer`] itself is modelled closely after [`AsyncWrite`]. It features `poll`-style -/// functions that allow the implementation to make progress on various tasks. +/// A substream created by a [`StreamMuxer`] is a type that implements [`AsyncRead`] and +/// [`AsyncWrite`]. The [`StreamMuxer`] itself is modelled closely after [`AsyncWrite`]. It features +/// `poll`-style functions that allow the implementation to make progress on various tasks. pub trait StreamMuxer { /// Type of the object that represents the raw substream where data can be read and written. type Substream: AsyncRead + AsyncWrite; @@ -90,13 +92,13 @@ pub trait StreamMuxer { /// Poll to close this [`StreamMuxer`]. /// - /// After this has returned `Poll::Ready(Ok(()))`, the muxer has become useless and may be safely - /// dropped. + /// After this has returned `Poll::Ready(Ok(()))`, the muxer has become useless and may be + /// safely dropped. /// /// > **Note**: You are encouraged to call this method and wait for it to return `Ready`, so - /// > that the remote is properly informed of the shutdown. However, apart from - /// > properly informing the remote, there is no difference between this and - /// > immediately dropping the muxer. + /// > that the remote is properly informed of the shutdown. However, apart from + /// > properly informing the remote, there is no difference between this and + /// > immediately dropping the muxer. fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; /// Poll to allow the underlying connection to make progress. @@ -120,7 +122,8 @@ pub enum StreamMuxerEvent { /// Extension trait for [`StreamMuxer`]. pub trait StreamMuxerExt: StreamMuxer + Sized { - /// Convenience function for calling [`StreamMuxer::poll_inbound`] for [`StreamMuxer`]s that are `Unpin`. + /// Convenience function for calling [`StreamMuxer::poll_inbound`] + /// for [`StreamMuxer`]s that are `Unpin`. fn poll_inbound_unpin( &mut self, cx: &mut Context<'_>, @@ -131,7 +134,8 @@ pub trait StreamMuxerExt: StreamMuxer + Sized { Pin::new(self).poll_inbound(cx) } - /// Convenience function for calling [`StreamMuxer::poll_outbound`] for [`StreamMuxer`]s that are `Unpin`. + /// Convenience function for calling [`StreamMuxer::poll_outbound`] + /// for [`StreamMuxer`]s that are `Unpin`. fn poll_outbound_unpin( &mut self, cx: &mut Context<'_>, @@ -142,7 +146,8 @@ pub trait StreamMuxerExt: StreamMuxer + Sized { Pin::new(self).poll_outbound(cx) } - /// Convenience function for calling [`StreamMuxer::poll`] for [`StreamMuxer`]s that are `Unpin`. + /// Convenience function for calling [`StreamMuxer::poll`] + /// for [`StreamMuxer`]s that are `Unpin`. fn poll_unpin(&mut self, cx: &mut Context<'_>) -> Poll> where Self: Unpin, @@ -150,7 +155,8 @@ pub trait StreamMuxerExt: StreamMuxer + Sized { Pin::new(self).poll(cx) } - /// Convenience function for calling [`StreamMuxer::poll_close`] for [`StreamMuxer`]s that are `Unpin`. + /// Convenience function for calling [`StreamMuxer::poll_close`] + /// for [`StreamMuxer`]s that are `Unpin`. fn poll_close_unpin(&mut self, cx: &mut Context<'_>) -> Poll> where Self: Unpin, diff --git a/core/src/muxing/boxed.rs b/core/src/muxing/boxed.rs index e909fb9fbf1..8e76c32b73e 100644 --- a/core/src/muxing/boxed.rs +++ b/core/src/muxing/boxed.rs @@ -1,12 +1,15 @@ -use crate::muxing::{StreamMuxer, StreamMuxerEvent}; +use std::{ + error::Error, + fmt, io, + io::{IoSlice, IoSliceMut}, + pin::Pin, + task::{Context, Poll}, +}; + use futures::{AsyncRead, AsyncWrite}; use pin_project::pin_project; -use std::error::Error; -use std::fmt; -use std::io; -use std::io::{IoSlice, IoSliceMut}; -use std::pin::Pin; -use std::task::{Context, Poll}; + +use crate::muxing::{StreamMuxer, StreamMuxerEvent}; /// Abstract `StreamMuxer`. pub struct StreamMuxerBox { @@ -139,7 +142,8 @@ impl StreamMuxer for StreamMuxerBox { } impl SubstreamBox { - /// Construct a new [`SubstreamBox`] from something that implements [`AsyncRead`] and [`AsyncWrite`]. + /// Construct a new [`SubstreamBox`] from something + /// that implements [`AsyncRead`] and [`AsyncWrite`]. pub fn new(stream: S) -> Self { Self(Box::pin(stream)) } diff --git a/core/src/peer_record.rs b/core/src/peer_record.rs index 2fd6a39ef2b..9c6b7f73f05 100644 --- a/core/src/peer_record.rs +++ b/core/src/peer_record.rs @@ -1,19 +1,16 @@ -use crate::signed_envelope::SignedEnvelope; -use crate::{proto, signed_envelope, DecodeError, Multiaddr}; -use instant::SystemTime; -use libp2p_identity::Keypair; -use libp2p_identity::PeerId; -use libp2p_identity::SigningError; +use libp2p_identity::{Keypair, PeerId, SigningError}; use quick_protobuf::{BytesReader, Writer}; -use std::convert::TryInto; +use web_time::SystemTime; + +use crate::{proto, signed_envelope, signed_envelope::SignedEnvelope, DecodeError, Multiaddr}; const PAYLOAD_TYPE: &str = "/libp2p/routing-state-record"; const DOMAIN_SEP: &str = "libp2p-routing-state"; /// Represents a peer routing record. /// -/// Peer records are designed to be distributable and carry a signature by being wrapped in a signed envelope. -/// For more information see RFC0003 of the libp2p specifications: +/// Peer records are designed to be distributable and carry a signature by being wrapped in a signed +/// envelope. For more information see RFC0003 of the libp2p specifications: #[derive(Debug, PartialEq, Eq, Clone)] pub struct PeerRecord { peer_id: PeerId, @@ -22,14 +19,16 @@ pub struct PeerRecord { /// A signed envelope representing this [`PeerRecord`]. /// - /// If this [`PeerRecord`] was constructed from a [`SignedEnvelope`], this is the original instance. + /// If this [`PeerRecord`] was constructed from a [`SignedEnvelope`], this is the original + /// instance. envelope: SignedEnvelope, } impl PeerRecord { /// Attempt to re-construct a [`PeerRecord`] from a [`SignedEnvelope`]. /// - /// If this function succeeds, the [`SignedEnvelope`] contained a peer record with a valid signature and can hence be considered authenticated. + /// If this function succeeds, the [`SignedEnvelope`] contained a peer record with a valid + /// signature and can hence be considered authenticated. pub fn from_signed_envelope(envelope: SignedEnvelope) -> Result { use quick_protobuf::MessageRead; @@ -61,7 +60,8 @@ impl PeerRecord { /// Construct a new [`PeerRecord`] by authenticating the provided addresses with the given key. /// - /// This is the same key that is used for authenticating every libp2p connection of your application, i.e. what you use when setting up your [`crate::transport::Transport`]. + /// This is the same key that is used for authenticating every libp2p connection of your + /// application, i.e. what you use when setting up your [`crate::transport::Transport`]. pub fn new(key: &Keypair, addresses: Vec) -> Result { use quick_protobuf::MessageWrite; diff --git a/core/src/signed_envelope.rs b/core/src/signed_envelope.rs index 19a0cac4f82..754d6ec204d 100644 --- a/core/src/signed_envelope.rs +++ b/core/src/signed_envelope.rs @@ -1,11 +1,13 @@ -use crate::{proto, DecodeError}; -use libp2p_identity::SigningError; -use libp2p_identity::{Keypair, PublicKey}; -use quick_protobuf::{BytesReader, Writer}; use std::fmt; + +use libp2p_identity::{Keypair, PublicKey, SigningError}; +use quick_protobuf::{BytesReader, Writer}; use unsigned_varint::encode::usize_buffer; -/// A signed envelope contains an arbitrary byte string payload, a signature of the payload, and the public key that can be used to verify the signature. +use crate::{proto, DecodeError}; + +/// A signed envelope contains an arbitrary byte string payload, a signature of the payload, and the +/// public key that can be used to verify the signature. /// /// For more details see libp2p RFC0002: #[derive(Debug, Clone, PartialEq, Eq)] @@ -46,8 +48,9 @@ impl SignedEnvelope { /// Extract the payload and signing key of this [`SignedEnvelope`]. /// - /// You must provide the correct domain-separation string and expected payload type in order to get the payload. - /// This guards against accidental mis-use of the payload where the signature was created for a different purpose or payload type. + /// You must provide the correct domain-separation string and expected payload type in order to + /// get the payload. This guards against accidental mis-use of the payload where the + /// signature was created for a different purpose or payload type. /// /// It is the caller's responsibility to check that the signing key is what /// is expected. For example, checking that the signing key is from a @@ -156,7 +159,8 @@ pub enum DecodingError { /// Errors that occur whilst extracting the payload of a [`SignedEnvelope`]. #[derive(Debug)] pub enum ReadPayloadError { - /// The signature on the signed envelope does not verify with the provided domain separation string. + /// The signature on the signed envelope does not verify + /// with the provided domain separation string. InvalidSignature, /// The payload contained in the envelope is not of the expected type. UnexpectedPayloadType { expected: Vec, got: Vec }, diff --git a/core/src/transport.rs b/core/src/transport.rs index 22e7a0532fa..ecd332f28cc 100644 --- a/core/src/transport.rs +++ b/core/src/transport.rs @@ -25,8 +25,6 @@ //! any desired protocols. The rest of the module defines combinators for //! modifying a transport through composition with other transports or protocol upgrades. -use futures::prelude::*; -use multiaddr::Multiaddr; use std::{ error::Error, fmt, @@ -35,6 +33,9 @@ use std::{ task::{Context, Poll}, }; +use futures::prelude::*; +use multiaddr::Multiaddr; + pub mod and_then; pub mod choice; pub mod dummy; @@ -48,16 +49,39 @@ pub mod upgrade; mod boxed; mod optional; -use crate::ConnectedPoint; - -pub use self::boxed::Boxed; -pub use self::choice::OrTransport; -pub use self::memory::MemoryTransport; -pub use self::optional::OptionalTransport; -pub use self::upgrade::Upgrade; +pub use self::{ + boxed::Boxed, choice::OrTransport, memory::MemoryTransport, optional::OptionalTransport, + upgrade::Upgrade, +}; +use crate::{ConnectedPoint, Endpoint}; static NEXT_LISTENER_ID: AtomicUsize = AtomicUsize::new(1); +/// The port use policy for a new connection. +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)] +pub enum PortUse { + /// Always allocate a new port for the dial. + New, + /// Best effor reusing of an existing port. + /// + /// If there is no listener present that can be used to dial, a new port is allocated. + #[default] + Reuse, +} + +/// Options to customize the behaviour during dialing. +#[derive(Debug, Copy, Clone)] +pub struct DialOpts { + /// The endpoint establishing a new connection. + /// + /// When attempting a hole-punch, both parties simultaneously "dial" each other but one party + /// has to be the "listener" on the final connection. This option specifies the role of + /// this node in the final connection. + pub role: Endpoint, + /// The port use policy for a new connection. + pub port_use: PortUse, +} + /// A transport provides connection-oriented communication between two peers /// through ordered streams of data (i.e. connections). /// @@ -129,24 +153,18 @@ pub trait Transport { /// /// If [`TransportError::MultiaddrNotSupported`] is returned, it may be desirable to /// try an alternative [`Transport`], if available. - fn dial(&mut self, addr: Multiaddr) -> Result>; - - /// As [`Transport::dial`] but has the local node act as a listener on the outgoing connection. - /// - /// This option is needed for NAT and firewall hole punching. - /// - /// See [`ConnectedPoint::Dialer`] for related option. - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result>; /// Poll for [`TransportEvent`]s. /// - /// A [`TransportEvent::Incoming`] should be produced whenever a connection is received at the lowest - /// level of the transport stack. The item must be a [`ListenerUpgrade`](Transport::ListenerUpgrade) - /// future that resolves to an [`Output`](Transport::Output) value once all protocol upgrades have - /// been applied. + /// A [`TransportEvent::Incoming`] should be produced whenever a connection is received at the + /// lowest level of the transport stack. The item must be a + /// [`ListenerUpgrade`](Transport::ListenerUpgrade) future that resolves to an + /// [`Output`](Transport::Output) value once all protocol upgrades have been applied. /// /// Transports are expected to produce [`TransportEvent::Incoming`] events only for /// listen addresses which have previously been announced via @@ -157,24 +175,6 @@ pub trait Transport { cx: &mut Context<'_>, ) -> Poll>; - /// Performs a transport-specific mapping of an address `observed` by a remote onto a - /// local `listen` address to yield an address for the local node that may be reachable - /// for other peers. - /// - /// This is relevant for transports where Network Address Translation (NAT) can occur - /// so that e.g. the peer is observed at a different IP than the IP of the local - /// listening address. See also [`address_translation`][crate::address_translation]. - /// - /// Within [`libp2p::Swarm`]() this is - /// used when extending the listening addresses of the local peer with external addresses - /// observed by remote peers. - /// On transports where this is not relevant (i.e. no NATs are present) `None` should be - /// returned for the sake of de-duplication. - /// - /// Note: if the listen or observed address is not a valid address of this transport, - /// `None` should be returned as well. - fn address_translation(&self, listen: &Multiaddr, observed: &Multiaddr) -> Option; - /// Boxes the transport, including custom transport errors. fn boxed(self) -> boxed::Boxed where @@ -256,7 +256,7 @@ impl ListenerId { } } -impl std::fmt::Display for ListenerId { +impl fmt::Display for ListenerId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } @@ -311,7 +311,7 @@ pub enum TransportEvent { impl TransportEvent { /// In case this [`TransportEvent`] is an upgrade, apply the given function - /// to the upgrade and produce another transport event based the the function's result. + /// to the upgrade and produce another transport event based the function's result. pub fn map_upgrade(self, map: impl FnOnce(TUpgr) -> U) -> TransportEvent { match self { TransportEvent::Incoming { @@ -530,7 +530,7 @@ pub enum TransportError { } impl TransportError { - /// Applies a function to the the error in [`TransportError::Other`]. + /// Applies a function to the error in [`TransportError::Other`]. pub fn map(self, map: impl FnOnce(TErr) -> TNewErr) -> TransportError { match self { TransportError::MultiaddrNotSupported(addr) => { diff --git a/core/src/transport/and_then.rs b/core/src/transport/and_then.rs index 6e0c7e32067..5d2b7d91553 100644 --- a/core/src/transport/and_then.rs +++ b/core/src/transport/and_then.rs @@ -18,14 +18,21 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{ - connection::{ConnectedPoint, Endpoint}, - transport::{ListenerId, Transport, TransportError, TransportEvent}, +use std::{ + error, + marker::PhantomPinned, + pin::Pin, + task::{Context, Poll}, }; + use either::Either; use futures::prelude::*; use multiaddr::Multiaddr; -use std::{error, marker::PhantomPinned, pin::Pin, task::Context, task::Poll}; + +use crate::{ + connection::ConnectedPoint, + transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}, +}; /// See the [`Transport::and_then`] method. #[pin_project::pin_project] @@ -68,32 +75,14 @@ where self.transport.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - let dialed_fut = self - .transport - .dial(addr.clone()) - .map_err(|err| err.map(Either::Left))?; - let future = AndThenFuture { - inner: Either::Left(Box::pin(dialed_fut)), - args: Some(( - self.fun.clone(), - ConnectedPoint::Dialer { - address: addr, - role_override: Endpoint::Dialer, - }, - )), - _marker: PhantomPinned, - }; - Ok(future) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { let dialed_fut = self .transport - .dial_as_listener(addr.clone()) + .dial(addr.clone(), opts) .map_err(|err| err.map(Either::Left))?; let future = AndThenFuture { inner: Either::Left(Box::pin(dialed_fut)), @@ -101,7 +90,8 @@ where self.fun.clone(), ConnectedPoint::Dialer { address: addr, - role_override: Endpoint::Listener, + role_override: opts.role, + port_use: opts.port_use, }, )), _marker: PhantomPinned, @@ -109,10 +99,6 @@ where Ok(future) } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.transport.address_translation(server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/transport/boxed.rs b/core/src/transport/boxed.rs index 1cede676c8e..6894d9876aa 100644 --- a/core/src/transport/boxed.rs +++ b/core/src/transport/boxed.rs @@ -18,9 +18,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::transport::{ListenerId, Transport, TransportError, TransportEvent}; -use futures::{prelude::*, stream::FusedStream}; -use multiaddr::Multiaddr; use std::{ error::Error, fmt, io, @@ -28,6 +25,11 @@ use std::{ task::{Context, Poll}, }; +use futures::{prelude::*, stream::FusedStream}; +use multiaddr::Multiaddr; + +use crate::transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}; + /// Creates a new [`Boxed`] transport from the given transport. pub(crate) fn boxed(transport: T) -> Boxed where @@ -58,9 +60,11 @@ trait Abstract { addr: Multiaddr, ) -> Result<(), TransportError>; fn remove_listener(&mut self, id: ListenerId) -> bool; - fn dial(&mut self, addr: Multiaddr) -> Result, TransportError>; - fn dial_as_listener(&mut self, addr: Multiaddr) -> Result, TransportError>; - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option; + fn dial( + &mut self, + addr: Multiaddr, + opts: DialOpts, + ) -> Result, TransportError>; fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -86,24 +90,17 @@ where Transport::remove_listener(self, id) } - fn dial(&mut self, addr: Multiaddr) -> Result, TransportError> { - let fut = Transport::dial(self, addr) - .map(|r| r.map_err(box_err)) - .map_err(|e| e.map(box_err))?; - Ok(Box::pin(fut) as Dial<_>) - } - - fn dial_as_listener(&mut self, addr: Multiaddr) -> Result, TransportError> { - let fut = Transport::dial_as_listener(self, addr) + fn dial( + &mut self, + addr: Multiaddr, + opts: DialOpts, + ) -> Result, TransportError> { + let fut = Transport::dial(self, addr, opts) .map(|r| r.map_err(box_err)) .map_err(|e| e.map(box_err))?; Ok(Box::pin(fut) as Dial<_>) } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - Transport::address_translation(self, server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -143,19 +140,12 @@ impl Transport for Boxed { self.inner.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - self.inner.dial(addr) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { - self.inner.dial_as_listener(addr) - } - - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.inner.address_translation(server, observed) + self.inner.dial(addr, opts) } fn poll( diff --git a/core/src/transport/choice.rs b/core/src/transport/choice.rs index aa3acfc3231..251091f2008 100644 --- a/core/src/transport/choice.rs +++ b/core/src/transport/choice.rs @@ -18,12 +18,19 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::either::EitherFuture; -use crate::transport::{ListenerId, Transport, TransportError, TransportEvent}; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + use either::Either; use futures::future; use multiaddr::Multiaddr; -use std::{pin::Pin, task::Context, task::Poll}; + +use crate::{ + either::EitherFuture, + transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}, +}; /// Struct returned by `or_transport()`. #[derive(Debug, Copy, Clone)] @@ -92,19 +99,23 @@ where self.0.remove_listener(id) || self.1.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + opts: DialOpts, + ) -> Result> { tracing::trace!( address=%addr, - "Attempting to dial address using {}", + "Attempting to dial using {}", std::any::type_name::() ); - let addr = match self.0.dial(addr) { + let addr = match self.0.dial(addr, opts) { Ok(connec) => return Ok(EitherFuture::First(connec)), Err(TransportError::MultiaddrNotSupported(addr)) => { tracing::debug!( address=%addr, - "Failed to dial address using {}", - std::any::type_name::() + "Failed to dial using {}", + std::any::type_name::(), ); addr } @@ -115,16 +126,16 @@ where tracing::trace!( address=%addr, - "Attempting to dial address using {}", + "Attempting to dial {}", std::any::type_name::() ); - let addr = match self.1.dial(addr) { + let addr = match self.1.dial(addr, opts) { Ok(connec) => return Ok(EitherFuture::Second(connec)), Err(TransportError::MultiaddrNotSupported(addr)) => { tracing::debug!( address=%addr, - "Failed to dial address using {}", - std::any::type_name::() + "Failed to dial using {}", + std::any::type_name::(), ); addr } @@ -136,37 +147,6 @@ where Err(TransportError::MultiaddrNotSupported(addr)) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - let addr = match self.0.dial_as_listener(addr) { - Ok(connec) => return Ok(EitherFuture::First(connec)), - Err(TransportError::MultiaddrNotSupported(addr)) => addr, - Err(TransportError::Other(err)) => { - return Err(TransportError::Other(Either::Left(err))) - } - }; - - let addr = match self.1.dial_as_listener(addr) { - Ok(connec) => return Ok(EitherFuture::Second(connec)), - Err(TransportError::MultiaddrNotSupported(addr)) => addr, - Err(TransportError::Other(err)) => { - return Err(TransportError::Other(Either::Right(err))) - } - }; - - Err(TransportError::MultiaddrNotSupported(addr)) - } - - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - if let Some(addr) = self.0.address_translation(server, observed) { - Some(addr) - } else { - self.1.address_translation(server, observed) - } - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/transport/dummy.rs b/core/src/transport/dummy.rs index 951d1039328..85c5815fd37 100644 --- a/core/src/transport/dummy.rs +++ b/core/src/transport/dummy.rs @@ -18,11 +18,18 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::transport::{ListenerId, Transport, TransportError, TransportEvent}; -use crate::Multiaddr; -use futures::{prelude::*, task::Context, task::Poll}; use std::{fmt, io, marker::PhantomData, pin::Pin}; +use futures::{ + prelude::*, + task::{Context, Poll}, +}; + +use crate::{ + transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}, + Multiaddr, +}; + /// Implementation of `Transport` that doesn't support any multiaddr. /// /// Useful for testing purposes, or as a fallback implementation when no protocol is available. @@ -71,21 +78,14 @@ impl Transport for DummyTransport { false } - fn dial(&mut self, addr: Multiaddr) -> Result> { - Err(TransportError::MultiaddrNotSupported(addr)) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + _opts: DialOpts, ) -> Result> { Err(TransportError::MultiaddrNotSupported(addr)) } - fn address_translation(&self, _server: &Multiaddr, _observed: &Multiaddr) -> Option { - None - } - fn poll( self: Pin<&mut Self>, _: &mut Context<'_>, @@ -94,7 +94,7 @@ impl Transport for DummyTransport { } } -/// Implementation of `AsyncRead` and `AsyncWrite`. Not meant to be instanciated. +/// Implementation of `AsyncRead` and `AsyncWrite`. Not meant to be instantiated. pub struct DummyStream(()); impl fmt::Debug for DummyStream { diff --git a/core/src/transport/global_only.rs b/core/src/transport/global_only.rs index 0671b0e9984..00df6457412 100644 --- a/core/src/transport/global_only.rs +++ b/core/src/transport/global_only.rs @@ -18,15 +18,16 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{ - multiaddr::{Multiaddr, Protocol}, - transport::{ListenerId, TransportError, TransportEvent}, -}; use std::{ pin::Pin, task::{Context, Poll}, }; +use crate::{ + multiaddr::{Multiaddr, Protocol}, + transport::{DialOpts, ListenerId, TransportError, TransportEvent}, +}; + /// Dropping all dial requests to non-global IP addresses. #[derive(Debug, Clone, Default)] pub struct Transport { @@ -104,7 +105,8 @@ mod ipv4_global { /// Returns [`true`] if the address appears to be globally reachable /// as specified by the [IANA IPv4 Special-Purpose Address Registry]. - /// Whether or not an address is practically reachable will depend on your network configuration. + /// Whether or not an address is practically reachable will depend on your network + /// configuration. /// /// Most IPv4 addresses are globally reachable; /// unless they are specifically defined as *not* globally reachable. @@ -121,7 +123,8 @@ mod ipv4_global { /// - Reserved addresses ([`is_reserved`](Ipv4Addr::is_reserved)) /// - The [broadcast address] ([`is_broadcast`](Ipv4Addr::is_broadcast)) /// - /// For the complete overview of which addresses are globally reachable, see the table at the [IANA IPv4 Special-Purpose Address Registry]. + /// For the complete overview of which addresses are globally reachable, see the table at the + /// [IANA IPv4 Special-Purpose Address Registry]. /// /// [IANA IPv4 Special-Purpose Address Registry]: https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml /// [unspecified address]: Ipv4Addr::UNSPECIFIED @@ -154,9 +157,10 @@ mod ipv6_global { /// Returns `true` if the address is a unicast address with link-local scope, /// as defined in [RFC 4291]. /// - /// A unicast address has link-local scope if it has the prefix `fe80::/10`, as per [RFC 4291 section 2.4]. - /// Note that this encompasses more addresses than those defined in [RFC 4291 section 2.5.6], - /// which describes "Link-Local IPv6 Unicast Addresses" as having the following stricter format: + /// A unicast address has link-local scope if it has the prefix `fe80::/10`, as per [RFC 4291 + /// section 2.4]. Note that this encompasses more addresses than those defined in [RFC 4291 + /// section 2.5.6], which describes "Link-Local IPv6 Unicast Addresses" as having the + /// following stricter format: /// /// ```text /// | 10 bits | 54 bits | 64 bits | @@ -164,12 +168,14 @@ mod ipv6_global { /// |1111111010| 0 | interface ID | /// +----------+-------------------------+----------------------------+ /// ``` - /// So while currently the only addresses with link-local scope an application will encounter are all in `fe80::/64`, - /// this might change in the future with the publication of new standards. More addresses in `fe80::/10` could be allocated, - /// and those addresses will have link-local scope. + /// So while currently the only addresses with link-local scope an application will encounter + /// are all in `fe80::/64`, this might change in the future with the publication of new + /// standards. More addresses in `fe80::/10` could be allocated, and those addresses will + /// have link-local scope. /// - /// Also note that while [RFC 4291 section 2.5.3] mentions about the [loopback address] (`::1`) that "it is treated as having Link-Local scope", - /// this does not mean that the loopback address actually has link-local scope and this method will return `false` on it. + /// Also note that while [RFC 4291 section 2.5.3] mentions about the [loopback address] (`::1`) + /// that "it is treated as having Link-Local scope", this does not mean that the loopback + /// address actually has link-local scope and this method will return `false` on it. /// /// [RFC 4291]: https://tools.ietf.org/html/rfc4291 /// [RFC 4291 section 2.4]: https://tools.ietf.org/html/rfc4291#section-2.4 @@ -207,7 +213,8 @@ mod ipv6_global { /// Returns [`true`] if the address appears to be globally reachable /// as specified by the [IANA IPv6 Special-Purpose Address Registry]. - /// Whether or not an address is practically reachable will depend on your network configuration. + /// Whether or not an address is practically reachable will depend on your network + /// configuration. /// /// Most IPv6 addresses are globally reachable; /// unless they are specifically defined as *not* globally reachable. @@ -219,13 +226,15 @@ mod ipv6_global { /// - Addresses reserved for benchmarking /// - Addresses reserved for documentation ([`is_documentation`](Ipv6Addr::is_documentation)) /// - Unique local addresses ([`is_unique_local`](Ipv6Addr::is_unique_local)) - /// - Unicast addresses with link-local scope ([`is_unicast_link_local`](Ipv6Addr::is_unicast_link_local)) + /// - Unicast addresses with link-local scope + /// ([`is_unicast_link_local`](Ipv6Addr::is_unicast_link_local)) /// - /// For the complete overview of which addresses are globally reachable, see the table at the [IANA IPv6 Special-Purpose Address Registry]. + /// For the complete overview of which addresses are globally reachable, see the table at the + /// [IANA IPv6 Special-Purpose Address Registry]. /// /// Note that an address having global scope is not the same as being globally reachable, - /// and there is no direct relation between the two concepts: There exist addresses with global scope - /// that are not globally reachable (for example unique local addresses), + /// and there is no direct relation between the two concepts: There exist addresses with global + /// scope that are not globally reachable (for example unique local addresses), /// and addresses that are globally reachable without having global scope /// (multicast addresses with non-global scope). /// @@ -287,47 +296,25 @@ impl crate::Transport for Transport { self.inner.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - match addr.iter().next() { - Some(Protocol::Ip4(a)) => { - if !ipv4_global::is_global(a) { - tracing::debug!(ip=%a, "Not dialing non global IP address"); - return Err(TransportError::MultiaddrNotSupported(addr)); - } - self.inner.dial(addr) - } - Some(Protocol::Ip6(a)) => { - if !ipv6_global::is_global(a) { - tracing::debug!(ip=%a, "Not dialing non global IP address"); - return Err(TransportError::MultiaddrNotSupported(addr)); - } - self.inner.dial(addr) - } - _ => { - tracing::debug!(address=%addr, "Not dialing unsupported Multiaddress"); - Err(TransportError::MultiaddrNotSupported(addr)) - } - } - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { match addr.iter().next() { Some(Protocol::Ip4(a)) => { if !ipv4_global::is_global(a) { - tracing::debug!(ip=?a, "Not dialing non global IP address"); + tracing::debug!(ip=%a, "Not dialing non global IP address"); return Err(TransportError::MultiaddrNotSupported(addr)); } - self.inner.dial_as_listener(addr) + self.inner.dial(addr, opts) } Some(Protocol::Ip6(a)) => { if !ipv6_global::is_global(a) { - tracing::debug!(ip=?a, "Not dialing non global IP address"); + tracing::debug!(ip=%a, "Not dialing non global IP address"); return Err(TransportError::MultiaddrNotSupported(addr)); } - self.inner.dial_as_listener(addr) + self.inner.dial(addr, opts) } _ => { tracing::debug!(address=%addr, "Not dialing unsupported Multiaddress"); @@ -336,10 +323,6 @@ impl crate::Transport for Transport { } } - fn address_translation(&self, listen: &Multiaddr, observed: &Multiaddr) -> Option { - self.inner.address_translation(listen, observed) - } - fn poll( mut self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/transport/map.rs b/core/src/transport/map.rs index 553f3e6338d..4f6910b141f 100644 --- a/core/src/transport/map.rs +++ b/core/src/transport/map.rs @@ -18,15 +18,19 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{ - connection::{ConnectedPoint, Endpoint}, - transport::{Transport, TransportError, TransportEvent}, +use std::{ + pin::Pin, + task::{Context, Poll}, }; + use futures::prelude::*; use multiaddr::Multiaddr; -use std::{pin::Pin, task::Context, task::Poll}; use super::ListenerId; +use crate::{ + connection::ConnectedPoint, + transport::{DialOpts, Transport, TransportError, TransportEvent}, +}; /// See `Transport::map`. #[derive(Debug, Copy, Clone)] @@ -73,26 +77,16 @@ where self.transport.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - let future = self.transport.dial(addr.clone())?; - let p = ConnectedPoint::Dialer { - address: addr, - role_override: Endpoint::Dialer, - }; - Ok(MapFuture { - inner: future, - args: Some((self.fun.clone(), p)), - }) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { - let future = self.transport.dial_as_listener(addr.clone())?; + let future = self.transport.dial(addr.clone(), opts)?; let p = ConnectedPoint::Dialer { address: addr, - role_override: Endpoint::Listener, + role_override: opts.role, + port_use: opts.port_use, }; Ok(MapFuture { inner: future, @@ -100,10 +94,6 @@ where }) } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.transport.address_translation(server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/transport/map_err.rs b/core/src/transport/map_err.rs index 56e1ebf2929..f47f5713225 100644 --- a/core/src/transport/map_err.rs +++ b/core/src/transport/map_err.rs @@ -18,10 +18,16 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::transport::{ListenerId, Transport, TransportError, TransportEvent}; +use std::{ + error, + pin::Pin, + task::{Context, Poll}, +}; + use futures::prelude::*; use multiaddr::Multiaddr; -use std::{error, pin::Pin, task::Context, task::Poll}; + +use crate::transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}; /// See `Transport::map_err`. #[derive(Debug, Copy, Clone)] @@ -65,23 +71,13 @@ where self.transport.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - let map = self.map.clone(); - match self.transport.dial(addr) { - Ok(future) => Ok(MapErrDial { - inner: future, - map: Some(map), - }), - Err(err) => Err(err.map(map)), - } - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { let map = self.map.clone(); - match self.transport.dial_as_listener(addr) { + match self.transport.dial(addr, opts) { Ok(future) => Ok(MapErrDial { inner: future, map: Some(map), @@ -90,10 +86,6 @@ where } } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.transport.address_translation(server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/transport/memory.rs b/core/src/transport/memory.rs index bf88215dd43..19197ddf714 100644 --- a/core/src/transport/memory.rs +++ b/core/src/transport/memory.rs @@ -18,25 +18,26 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::transport::{ListenerId, Transport, TransportError, TransportEvent}; +use std::{ + collections::{hash_map::Entry, VecDeque}, + error, fmt, io, + num::NonZeroU64, + pin::Pin, +}; + use fnv::FnvHashMap; use futures::{ channel::mpsc, - future::{self, Ready}, + future::Ready, prelude::*, - task::Context, - task::Poll, + task::{Context, Poll}, }; use multiaddr::{Multiaddr, Protocol}; use once_cell::sync::Lazy; use parking_lot::Mutex; use rw_stream_sink::RwStreamSink; -use std::{ - collections::{hash_map::Entry, VecDeque}, - error, fmt, io, - num::NonZeroU64, - pin::Pin, -}; + +use crate::transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}; static HUB: Lazy = Lazy::new(|| Hub(Mutex::new(FnvHashMap::default()))); @@ -214,7 +215,11 @@ impl Transport for MemoryTransport { } } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + _opts: DialOpts, + ) -> Result> { let port = if let Ok(port) = parse_memory_addr(&addr) { if let Some(port) = NonZeroU64::new(port) { port @@ -228,17 +233,6 @@ impl Transport for MemoryTransport { DialFuture::new(port).ok_or(TransportError::Other(MemoryTransportError::Unreachable)) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - self.dial(addr) - } - - fn address_translation(&self, _server: &Multiaddr, _observed: &Multiaddr) -> Option { - None - } - fn poll( mut self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -412,6 +406,7 @@ impl Drop for Chan { #[cfg(test)] mod tests { use super::*; + use crate::{transport::PortUse, Endpoint}; #[test] fn parse_memory_addr_works() { @@ -495,7 +490,13 @@ mod tests { fn port_not_in_use() { let mut transport = MemoryTransport::default(); assert!(transport - .dial("/memory/810172461024613".parse().unwrap()) + .dial( + "/memory/810172461024613".parse().unwrap(), + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New + } + ) .is_err()); transport .listen_on( @@ -504,7 +505,13 @@ mod tests { ) .unwrap(); assert!(transport - .dial("/memory/810172461024613".parse().unwrap()) + .dial( + "/memory/810172461024613".parse().unwrap(), + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New + } + ) .is_ok()); } @@ -571,7 +578,17 @@ mod tests { let mut t2 = MemoryTransport::default(); let dialer = async move { - let mut socket = t2.dial(cloned_t1_addr).unwrap().await.unwrap(); + let mut socket = t2 + .dial( + cloned_t1_addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New, + }, + ) + .unwrap() + .await + .unwrap(); socket.write_all(&msg).await.unwrap(); }; @@ -607,7 +624,13 @@ mod tests { let dialer = async move { MemoryTransport::default() - .dial(listener_addr_cloned) + .dial( + listener_addr_cloned, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New, + }, + ) .unwrap() .await .unwrap(); @@ -658,7 +681,13 @@ mod tests { let dialer = async move { let chan = MemoryTransport::default() - .dial(listener_addr_cloned) + .dial( + listener_addr_cloned, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New, + }, + ) .unwrap() .await .unwrap(); diff --git a/core/src/transport/optional.rs b/core/src/transport/optional.rs index 839f55a4000..262f84f3095 100644 --- a/core/src/transport/optional.rs +++ b/core/src/transport/optional.rs @@ -18,9 +18,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::transport::{ListenerId, Transport, TransportError, TransportEvent}; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + use multiaddr::Multiaddr; -use std::{pin::Pin, task::Context, task::Poll}; + +use crate::transport::{DialOpts, ListenerId, Transport, TransportError, TransportEvent}; /// Transport that is possibly disabled. /// @@ -80,33 +85,18 @@ where } } - fn dial(&mut self, addr: Multiaddr) -> Result> { - if let Some(inner) = self.0.as_mut() { - inner.dial(addr) - } else { - Err(TransportError::MultiaddrNotSupported(addr)) - } - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { if let Some(inner) = self.0.as_mut() { - inner.dial_as_listener(addr) + inner.dial(addr, opts) } else { Err(TransportError::MultiaddrNotSupported(addr)) } } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - if let Some(inner) = &self.0 { - inner.address_translation(server, observed) - } else { - None - } - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/transport/timeout.rs b/core/src/transport/timeout.rs index 0e8ab3f5201..ce494216279 100644 --- a/core/src/transport/timeout.rs +++ b/core/src/transport/timeout.rs @@ -24,13 +24,20 @@ //! underlying `Transport`. // TODO: add example -use crate::{ - transport::{ListenerId, TransportError, TransportEvent}, - Multiaddr, Transport, +use std::{ + error, fmt, io, + pin::Pin, + task::{Context, Poll}, + time::Duration, }; + use futures::prelude::*; use futures_timer::Delay; -use std::{error, fmt, io, pin::Pin, task::Context, task::Poll, time::Duration}; + +use crate::{ + transport::{DialOpts, ListenerId, TransportError, TransportEvent}, + Multiaddr, Transport, +}; /// A `TransportTimeout` is a `Transport` that wraps another `Transport` and adds /// timeouts to all inbound and outbound connection attempts. @@ -99,24 +106,14 @@ where self.inner.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - let dial = self - .inner - .dial(addr) - .map_err(|err| err.map(TransportTimeoutError::Other))?; - Ok(Timeout { - inner: dial, - timer: Delay::new(self.outgoing_timeout), - }) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { let dial = self .inner - .dial_as_listener(addr) + .dial(addr, opts) .map_err(|err| err.map(TransportTimeoutError::Other))?; Ok(Timeout { inner: dial, @@ -124,10 +121,6 @@ where }) } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.inner.address_translation(server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/core/src/transport/upgrade.rs b/core/src/transport/upgrade.rs index 8525ab741ff..480c2710020 100644 --- a/core/src/transport/upgrade.rs +++ b/core/src/transport/upgrade.rs @@ -20,14 +20,25 @@ //! Configuration of transport protocol upgrades. -pub use crate::upgrade::Version; +use std::{ + error::Error, + fmt, + pin::Pin, + task::{Context, Poll}, + time::Duration, +}; + +use futures::{prelude::*, ready}; +use libp2p_identity::PeerId; +use multiaddr::Multiaddr; +pub use crate::upgrade::Version; use crate::{ connection::ConnectedPoint, muxing::{StreamMuxer, StreamMuxerBox}, transport::{ - and_then::AndThen, boxed::boxed, timeout::TransportTimeout, ListenerId, Transport, - TransportError, TransportEvent, + and_then::AndThen, boxed::boxed, timeout::TransportTimeout, DialOpts, ListenerId, + Transport, TransportError, TransportEvent, }, upgrade::{ self, apply_inbound, apply_outbound, InboundConnectionUpgrade, InboundUpgradeApply, @@ -35,16 +46,6 @@ use crate::{ }, Negotiated, }; -use futures::{prelude::*, ready}; -use libp2p_identity::PeerId; -use multiaddr::Multiaddr; -use std::{ - error::Error, - fmt, - pin::Pin, - task::{Context, Poll}, - time::Duration, -}; /// A `Builder` facilitates upgrading of a [`Transport`] for use with /// a `Swarm`. @@ -58,13 +59,13 @@ use std::{ /// It thus enforces the following invariants on every transport /// obtained from [`multiplex`](Authenticated::multiplex): /// -/// 1. The transport must be [authenticated](Builder::authenticate) -/// and [multiplexed](Authenticated::multiplex). +/// 1. The transport must be [authenticated](Builder::authenticate) and +/// [multiplexed](Authenticated::multiplex). /// 2. Authentication must precede the negotiation of a multiplexer. /// 3. Applying a multiplexer is the last step in the upgrade process. -/// 4. The [`Transport::Output`] conforms to the requirements of a `Swarm`, -/// namely a tuple of a [`PeerId`] (from the authentication upgrade) and a -/// [`StreamMuxer`] (from the multiplexing upgrade). +/// 4. The [`Transport::Output`] conforms to the requirements of a `Swarm`, namely a tuple of a +/// [`PeerId`] (from the authentication upgrade) and a [`StreamMuxer`] (from the multiplexing +/// upgrade). #[derive(Clone)] pub struct Builder { inner: T, @@ -335,21 +336,18 @@ where type ListenerUpgrade = T::ListenerUpgrade; type Dial = T::Dial; - fn dial(&mut self, addr: Multiaddr) -> Result> { - self.0.dial(addr) + fn dial( + &mut self, + addr: Multiaddr, + opts: DialOpts, + ) -> Result> { + self.0.dial(addr, opts) } fn remove_listener(&mut self, id: ListenerId) -> bool { self.0.remove_listener(id) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - self.0.dial_as_listener(addr) - } - fn listen_on( &mut self, id: ListenerId, @@ -358,10 +356,6 @@ where self.0.listen_on(id, addr) } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.0.address_translation(server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -404,10 +398,14 @@ where type ListenerUpgrade = ListenerUpgradeFuture; type Dial = DialUpgradeFuture; - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + opts: DialOpts, + ) -> Result> { let future = self .inner - .dial(addr) + .dial(addr, opts) .map_err(|err| err.map(TransportUpgradeError::Transport))?; Ok(DialUpgradeFuture { future: Box::pin(future), @@ -419,20 +417,6 @@ where self.inner.remove_listener(id) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - let future = self - .inner - .dial_as_listener(addr) - .map_err(|err| err.map(TransportUpgradeError::Transport))?; - Ok(DialUpgradeFuture { - future: Box::pin(future), - upgrade: future::Either::Left(Some(self.upgrade.clone())), - }) - } - fn listen_on( &mut self, id: ListenerId, @@ -443,10 +427,6 @@ where .map_err(|err| err.map(TransportUpgradeError::Transport)) } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.inner.address_translation(server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -520,7 +500,7 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { // We use a `this` variable because the compiler can't mutably borrow multiple times - // accross a `Deref`. + // across a `Deref`. let this = &mut *self; loop { @@ -579,7 +559,7 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { // We use a `this` variable because the compiler can't mutably borrow multiple times - // accross a `Deref`. + // across a `Deref`. let this = &mut *self; loop { diff --git a/core/src/upgrade.rs b/core/src/upgrade.rs index 69561fbebd8..93039705938 100644 --- a/core/src/upgrade.rs +++ b/core/src/upgrade.rs @@ -29,8 +29,8 @@ //! connection or substream. //! //! > **Note**: Multiple versions of the same protocol are treated as different protocols. -//! > For example, `/foo/1.0.0` and `/foo/1.1.0` are totally unrelated as far as -//! > upgrading is concerned. +//! > For example, `/foo/1.0.0` and `/foo/1.1.0` are totally unrelated as far as +//! > upgrading is concerned. //! //! # Upgrade process //! @@ -52,10 +52,9 @@ //! can be used by the user to control the behaviour of the protocol. //! //! > **Note**: You can use the `apply_inbound` or `apply_outbound` methods to try upgrade a -//! connection or substream. However if you use the recommended `Swarm` or -//! `ConnectionHandler` APIs, the upgrade is automatically handled for you and you don't -//! need to use these methods. -//! +//! > connection or substream. However if you use the recommended `Swarm` or +//! > `ConnectionHandler` APIs, the upgrade is automatically handled for you and you don't +//! > need to use these methods. mod apply; mod denied; @@ -70,12 +69,12 @@ pub(crate) use apply::{ }; pub(crate) use error::UpgradeError; use futures::future::Future; +pub use multistream_select::{NegotiatedComplete, NegotiationError, ProtocolError, Version}; pub use self::{ denied::DeniedUpgrade, pending::PendingUpgrade, ready::ReadyUpgrade, select::SelectUpgrade, }; pub use crate::Negotiated; -pub use multistream_select::{NegotiatedComplete, NegotiationError, ProtocolError, Version}; /// Common trait for upgrades that can be applied on inbound substreams, outbound substreams, /// or both. diff --git a/core/src/upgrade/apply.rs b/core/src/upgrade/apply.rs index 15cb0348cf3..9e090267b0c 100644 --- a/core/src/upgrade/apply.rs +++ b/core/src/upgrade/apply.rs @@ -18,13 +18,21 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade, UpgradeError}; -use crate::{connection::ConnectedPoint, Negotiated}; -use futures::{future::Either, prelude::*}; -use multistream_select::{self, DialerSelectFuture, ListenerSelectFuture}; -use std::{mem, pin::Pin, task::Context, task::Poll}; +use std::{ + mem, + pin::Pin, + task::{Context, Poll}, +}; +use futures::{future::Either, prelude::*}; pub(crate) use multistream_select::Version; +use multistream_select::{DialerSelectFuture, ListenerSelectFuture}; + +use crate::{ + connection::ConnectedPoint, + upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade, UpgradeError}, + Negotiated, +}; // TODO: Still needed? /// Applies an upgrade to the inbound and outbound direction of a connection or substream. diff --git a/core/src/upgrade/denied.rs b/core/src/upgrade/denied.rs index 353a184822d..9bea6fb023b 100644 --- a/core/src/upgrade/denied.rs +++ b/core/src/upgrade/denied.rs @@ -18,10 +18,11 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; +use std::{convert::Infallible, iter}; + use futures::future; -use std::iter; -use void::Void; + +use crate::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; /// Dummy implementation of `UpgradeInfo`/`InboundUpgrade`/`OutboundUpgrade` that doesn't support /// any protocol. @@ -38,8 +39,8 @@ impl UpgradeInfo for DeniedUpgrade { } impl InboundUpgrade for DeniedUpgrade { - type Output = Void; - type Error = Void; + type Output = Infallible; + type Error = Infallible; type Future = future::Pending>; fn upgrade_inbound(self, _: C, _: Self::Info) -> Self::Future { @@ -48,8 +49,8 @@ impl InboundUpgrade for DeniedUpgrade { } impl OutboundUpgrade for DeniedUpgrade { - type Output = Void; - type Error = Void; + type Output = Infallible; + type Error = Infallible; type Future = future::Pending>; fn upgrade_outbound(self, _: C, _: Self::Info) -> Self::Future { diff --git a/core/src/upgrade/either.rs b/core/src/upgrade/either.rs index db62f8d6558..9970dcb0b1d 100644 --- a/core/src/upgrade/either.rs +++ b/core/src/upgrade/either.rs @@ -18,13 +18,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::iter::Map; + +use either::Either; +use futures::future; + use crate::{ either::EitherFuture, upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}, }; -use either::Either; -use futures::future; -use std::iter::Map; impl UpgradeInfo for Either where diff --git a/core/src/upgrade/error.rs b/core/src/upgrade/error.rs index 3d349587c2c..c81ed7cf75b 100644 --- a/core/src/upgrade/error.rs +++ b/core/src/upgrade/error.rs @@ -18,9 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use multistream_select::NegotiationError; use std::fmt; +use multistream_select::NegotiationError; + /// Error that can happen when upgrading a connection or substream to use a protocol. #[derive(Debug)] pub enum UpgradeError { diff --git a/core/src/upgrade/pending.rs b/core/src/upgrade/pending.rs index 6931e20bfdc..60a9fb9aba1 100644 --- a/core/src/upgrade/pending.rs +++ b/core/src/upgrade/pending.rs @@ -19,10 +19,11 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; +use std::{convert::Infallible, iter}; + use futures::future; -use std::iter; -use void::Void; + +use crate::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; /// Implementation of [`UpgradeInfo`], [`InboundUpgrade`] and [`OutboundUpgrade`] that always /// returns a pending upgrade. @@ -53,8 +54,8 @@ impl InboundUpgrade for PendingUpgrade

where P: AsRef + Clone, { - type Output = Void; - type Error = Void; + type Output = Infallible; + type Error = Infallible; type Future = future::Pending>; fn upgrade_inbound(self, _: C, _: Self::Info) -> Self::Future { @@ -66,8 +67,8 @@ impl OutboundUpgrade for PendingUpgrade

where P: AsRef + Clone, { - type Output = Void; - type Error = Void; + type Output = Infallible; + type Error = Infallible; type Future = future::Pending>; fn upgrade_outbound(self, _: C, _: Self::Info) -> Self::Future { diff --git a/core/src/upgrade/ready.rs b/core/src/upgrade/ready.rs index 323f1f73f32..22708d726e7 100644 --- a/core/src/upgrade/ready.rs +++ b/core/src/upgrade/ready.rs @@ -19,19 +19,21 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; +use std::{convert::Infallible, iter}; + use futures::future; -use std::iter; -use void::Void; -/// Implementation of [`UpgradeInfo`], [`InboundUpgrade`] and [`OutboundUpgrade`] that directly yields the substream. +use crate::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; + +/// Implementation of [`UpgradeInfo`], [`InboundUpgrade`] and [`OutboundUpgrade`] +/// that directly yields the substream. #[derive(Debug, Copy, Clone)] pub struct ReadyUpgrade

{ protocol_name: P, } impl

ReadyUpgrade

{ - pub fn new(protocol_name: P) -> Self { + pub const fn new(protocol_name: P) -> Self { Self { protocol_name } } } @@ -53,7 +55,7 @@ where P: AsRef + Clone, { type Output = C; - type Error = Void; + type Error = Infallible; type Future = future::Ready>; fn upgrade_inbound(self, stream: C, _: Self::Info) -> Self::Future { @@ -66,7 +68,7 @@ where P: AsRef + Clone, { type Output = C; - type Error = Void; + type Error = Infallible; type Future = future::Ready>; fn upgrade_outbound(self, stream: C, _: Self::Info) -> Self::Future { diff --git a/core/src/upgrade/select.rs b/core/src/upgrade/select.rs index 037045a2f29..b7fe4a53a7f 100644 --- a/core/src/upgrade/select.rs +++ b/core/src/upgrade/select.rs @@ -18,14 +18,18 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::either::EitherFuture; -use crate::upgrade::{ - InboundConnectionUpgrade, InboundUpgrade, OutboundConnectionUpgrade, OutboundUpgrade, - UpgradeInfo, -}; +use std::iter::{Chain, Map}; + use either::Either; use futures::future; -use std::iter::{Chain, Map}; + +use crate::{ + either::EitherFuture, + upgrade::{ + InboundConnectionUpgrade, InboundUpgrade, OutboundConnectionUpgrade, OutboundUpgrade, + UpgradeInfo, + }, +}; /// Upgrade that combines two upgrades into one. Supports all the protocols supported by either /// sub-upgrade. diff --git a/core/tests/transport_upgrade.rs b/core/tests/transport_upgrade.rs index a8872051618..b9733e38322 100644 --- a/core/tests/transport_upgrade.rs +++ b/core/tests/transport_upgrade.rs @@ -18,17 +18,19 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::{io, pin::Pin}; + use futures::prelude::*; -use libp2p_core::transport::{ListenerId, MemoryTransport, Transport}; -use libp2p_core::upgrade::{ - self, InboundConnectionUpgrade, OutboundConnectionUpgrade, UpgradeInfo, +use libp2p_core::{ + transport::{DialOpts, ListenerId, MemoryTransport, PortUse, Transport}, + upgrade::{self, InboundConnectionUpgrade, OutboundConnectionUpgrade, UpgradeInfo}, + Endpoint, }; use libp2p_identity as identity; use libp2p_mplex::MplexConfig; use libp2p_noise as noise; use multiaddr::{Multiaddr, Protocol}; use rand::random; -use std::{io, pin::Pin}; #[derive(Clone)] struct HelloUpgrade {} @@ -121,7 +123,17 @@ fn upgrade_pipeline() { }; let client = async move { - let (peer, _mplex) = dialer_transport.dial(listen_addr2).unwrap().await.unwrap(); + let (peer, _mplex) = dialer_transport + .dial( + listen_addr2, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New, + }, + ) + .unwrap() + .await + .unwrap(); assert_eq!(peer, listener_id); }; diff --git a/deny.toml b/deny.toml index 6634887128e..47487553028 100644 --- a/deny.toml +++ b/deny.toml @@ -2,20 +2,14 @@ # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html [advisories] +# Version of the advisory config. See https://github.com/EmbarkStudios/cargo-deny/pull/611 +version = 2 # The path where the advisory database is cloned/fetched into db-path = "~/cargo/advisory-db" # The url of the advisory database to use -db-urls = [ "https://github.com/rustsec/advisory-db" ] -# The lint level for security vulnerabilities -vulnerability = "deny" -# The lint level for unmaintained crates -unmaintained = "warn" +db-urls = ["https://github.com/rustsec/advisory-db"] # The lint level for crates that have been yanked from their source registry yanked = "warn" -# The lint level for crates with security notices. Note that as of -# 2019-12-17 there are no security notice advisories in -# https://github.com/rustsec/advisory-db -notice = "warn" # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ @@ -35,35 +29,23 @@ ignore = [ # More documentation for the licenses section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] -# The lint level for crates which do not have a detectable license -unlicensed = "deny" -# List of explictly allowed licenses +# Version of the license config. See https://github.com/EmbarkStudios/cargo-deny/pull/611 +version = 2 +# List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses -# [possible values: any SPDX 3.7 short identifier (+ optional exception)]. +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. allow = [ + "Apache-2.0 WITH LLVM-exception", "Apache-2.0", "BSD-2-Clause", + "BSD-3-Clause", + "ISC", "MIT", + "MPL-2.0", "Unlicense", + "Unicode-3.0", + "Zlib", ] -# List of explictly disallowed licenses -# See https://spdx.org/licenses/ for list of possible licenses -# [possible values: any SPDX 3.7 short identifier (+ optional exception)]. -deny = [] -# Lint level for licenses considered copyleft -copyleft = "allow" -# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses -# * both - The license will be approved if it is both OSI-approved *AND* FSF -# * either - The license will be approved if it is either OSI-approved *OR* FSF -# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF -# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved -# * neither - This predicate is ignored and the default lint level is used -allow-osi-fsf-free = "both" -# Lint level used when no other predicates are matched -# 1. License isn't in the allow or deny lists -# 2. License isn't copyleft -# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" -default = "deny" # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the # canonical license text of a valid SPDX license file. @@ -76,7 +58,7 @@ exceptions = [ # https://www.openssl.org/blog/blog/2017/03/22/license/ # ring crate is ISC & MIT { allow = ["ISC", "MIT", "OpenSSL"], name = "ring" }, - # libp2p is not re-distributing unicode tables data by itself + # libp2p is not re-distributing unicode tables data by itself { allow = ["MIT", "Apache-2.0", "Unicode-DFS-2016"], name = "unicode-ident" }, ] diff --git a/docs/coding-guidelines.md b/docs/coding-guidelines.md index bacbfe9509e..473d7020fcf 100644 --- a/docs/coding-guidelines.md +++ b/docs/coding-guidelines.md @@ -236,7 +236,7 @@ Concurrency adds complexity. Concurrency adds overhead due to synchronization. Thus unless proven to be a bottleneck, don't make things concurrent. As an example the hierarchical `NetworkBehaviour` state machine runs sequentially. It is easy to debug as it runs sequentially. Thus far there has been no proof that -shows a speed up when running it concurrently. +shows a speed-up when running it concurrently. ## Use `async/await` for sequential execution only diff --git a/docs/maintainer-handbook.md b/docs/maintainer-handbook.md index 6d36f6fe77c..0b090901216 100644 --- a/docs/maintainer-handbook.md +++ b/docs/maintainer-handbook.md @@ -31,7 +31,7 @@ This will have mergify approve your PR, thus fulfilling all requirements to auto Our CI checks that each crate which is modified gets a changelog entry. Whilst this is a good default safety-wise, it creates a lot of false-positives for changes that are internal and don't need a changelog entry. -For PRs that in the categories `chore`, `deps`, `refactor` and `docs`, this check is disabled automatically. +For PRs in the categories `chore`, `deps`, `refactor` and `docs`, this check is disabled automatically. Any other PR needs to explicitly disable this check if desired by applying the `internal-change` label. ## Dependencies diff --git a/examples/README.md b/examples/README.md index 28e085587b7..0a3e55aed39 100644 --- a/examples/README.md +++ b/examples/README.md @@ -20,6 +20,6 @@ A set of examples showcasing how to use rust-libp2p. - [IPFS Private](./ipfs-private) Implementation using the gossipsub, ping and identify protocols to implement the ipfs private swarms feature. -- [Ping](./ping) Small `ping` clone, sending a ping to a peer, expecting a pong as a response. See [tutorial](../src/tutorials/ping.rs) for a step-by-step guide building the example. +- [Ping](./ping) Small `ping` clone, sending a ping to a peer, expecting a pong as a response. See [tutorial](../libp2p/src/tutorials/ping.rs) for a step-by-step guide building the example. - [Rendezvous](./rendezvous) Rendezvous Protocol. See [specs](https://github.com/libp2p/specs/blob/master/rendezvous/README.md). diff --git a/examples/autonat/Cargo.toml b/examples/autonat/Cargo.toml index cca3b5e326e..7c06b48a105 100644 --- a/examples/autonat/Cargo.toml +++ b/examples/autonat/Cargo.toml @@ -3,18 +3,17 @@ name = "autonat-example" version = "0.1.0" edition = "2021" publish = false -license = "MIT" +license = "MIT or Apache-2.0" [package.metadata.release] release = false [dependencies] -tokio = { version = "1.34", features = ["full"] } -clap = { version = "4.4.10", features = ["derive"] } -futures = "0.3.29" +tokio = { workspace = true, features = ["full"] } +clap = { version = "4.5.6", features = ["derive"] } +futures = { workspace = true } libp2p = { path = "../../libp2p", features = ["tokio", "tcp", "noise", "yamux", "autonat", "identify", "macros"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true diff --git a/examples/autonat/src/bin/autonat_client.rs b/examples/autonat/src/bin/autonat_client.rs index 3fb25aa6222..768a2052c80 100644 --- a/examples/autonat/src/bin/autonat_client.rs +++ b/examples/autonat/src/bin/autonat_client.rs @@ -20,15 +20,17 @@ #![doc = include_str!("../../README.md")] +use std::{error::Error, net::Ipv4Addr, time::Duration}; + use clap::Parser; use futures::StreamExt; -use libp2p::core::multiaddr::Protocol; -use libp2p::core::Multiaddr; -use libp2p::swarm::{NetworkBehaviour, SwarmEvent}; -use libp2p::{autonat, identify, identity, noise, tcp, yamux, PeerId}; -use std::error::Error; -use std::net::Ipv4Addr; -use std::time::Duration; +use libp2p::{ + autonat, + core::{multiaddr::Protocol, Multiaddr}, + identify, identity, noise, + swarm::{NetworkBehaviour, SwarmEvent}, + tcp, yamux, PeerId, +}; use tracing_subscriber::EnvFilter; #[derive(Debug, Parser)] @@ -60,7 +62,6 @@ async fn main() -> Result<(), Box> { yamux::Config::default, )? .with_behaviour(|key| Behaviour::new(key.public()))? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) .build(); swarm.listen_on( @@ -110,22 +111,3 @@ impl Behaviour { } } } - -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -enum Event { - AutoNat(autonat::Event), - Identify(identify::Event), -} - -impl From for Event { - fn from(v: identify::Event) -> Self { - Self::Identify(v) - } -} - -impl From for Event { - fn from(v: autonat::Event) -> Self { - Self::AutoNat(v) - } -} diff --git a/examples/autonat/src/bin/autonat_server.rs b/examples/autonat/src/bin/autonat_server.rs index 44a53f0d17f..f3bb6b6a439 100644 --- a/examples/autonat/src/bin/autonat_server.rs +++ b/examples/autonat/src/bin/autonat_server.rs @@ -20,14 +20,17 @@ #![doc = include_str!("../../README.md")] +use std::{error::Error, net::Ipv4Addr}; + use clap::Parser; use futures::StreamExt; -use libp2p::core::{multiaddr::Protocol, Multiaddr}; -use libp2p::swarm::{NetworkBehaviour, SwarmEvent}; -use libp2p::{autonat, identify, identity, noise, tcp, yamux}; -use std::error::Error; -use std::net::Ipv4Addr; -use std::time::Duration; +use libp2p::{ + autonat, + core::{multiaddr::Protocol, Multiaddr}, + identify, identity, noise, + swarm::{NetworkBehaviour, SwarmEvent}, + tcp, yamux, +}; use tracing_subscriber::EnvFilter; #[derive(Debug, Parser)] @@ -53,7 +56,6 @@ async fn main() -> Result<(), Box> { yamux::Config::default, )? .with_behaviour(|key| Behaviour::new(key.public()))? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) .build(); swarm.listen_on( @@ -94,22 +96,3 @@ impl Behaviour { } } } - -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -enum Event { - AutoNat(autonat::Event), - Identify(identify::Event), -} - -impl From for Event { - fn from(v: identify::Event) -> Self { - Self::Identify(v) - } -} - -impl From for Event { - fn from(v: autonat::Event) -> Self { - Self::AutoNat(v) - } -} diff --git a/examples/autonatv2/Cargo.toml b/examples/autonatv2/Cargo.toml new file mode 100644 index 00000000000..d400c53e7fd --- /dev/null +++ b/examples/autonatv2/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "autonatv2" +version = "0.1.0" +edition = "2021" +publish = false +license = "MIT or Apache-2.0" + +[package.metadata.release] +release = false + +[[bin]] +name = "autonatv2_client" + +[[bin]] +name = "autonatv2_server" + +[dependencies] +libp2p = { workspace = true, features = ["macros", "tokio", "tcp", "noise", "yamux", "autonat", "identify", "dns", "quic"] } +clap = { version = "4.4.18", features = ["derive"] } +tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] } +tracing = "0.1.40" +tracing-subscriber = { workspace = true, features = ["env-filter"] } +rand = "0.8.5" +opentelemetry_sdk = { version = "0.21.1", optional = true, features = ["rt-tokio"] } +tracing-opentelemetry = { version = "0.22.0", optional = true } +opentelemetry-jaeger = { version = "0.20.0", optional = true, features = ["rt-tokio"] } +cfg-if = "1.0.0" + +[features] +jaeger = ["opentelemetry_sdk", "tracing-opentelemetry", "opentelemetry-jaeger"] +opentelemetry_sdk = ["dep:opentelemetry_sdk"] +tracing-opentelemetry = ["dep:tracing-opentelemetry"] +opentelemetry-jaeger = ["dep:opentelemetry-jaeger"] + +[lints] +workspace = true diff --git a/examples/autonatv2/Dockerfile b/examples/autonatv2/Dockerfile new file mode 100644 index 00000000000..6bc92e4d11b --- /dev/null +++ b/examples/autonatv2/Dockerfile @@ -0,0 +1,20 @@ +FROM rust:1.81-alpine as builder + +RUN apk add musl-dev + +WORKDIR /workspace +COPY . . +RUN --mount=type=cache,target=./target \ + --mount=type=cache,target=/usr/local/cargo/registry \ + cargo build --release --package autonatv2 --bin autonatv2_server -F jaeger + +RUN --mount=type=cache,target=./target \ + mv ./target/release/autonatv2_server /usr/local/bin/autonatv2_server + +FROM alpine:latest + +COPY --from=builder /usr/local/bin/autonatv2_server /app/autonatv2_server + +EXPOSE 4884 + +ENTRYPOINT [ "/app/autonatv2_server", "-l", "4884" ] diff --git a/examples/autonatv2/docker-compose.yml b/examples/autonatv2/docker-compose.yml new file mode 100644 index 00000000000..75f44e7e6f9 --- /dev/null +++ b/examples/autonatv2/docker-compose.yml @@ -0,0 +1,16 @@ +version: '3' + +services: + autonatv2: + build: + context: ../.. + dockerfile: examples/autonatv2/Dockerfile + ports: + - 4884:4884 + jaeger: + image: jaegertracing/all-in-one + ports: + - 6831:6831/udp + - 6832:6832/udp + - 16686:16686 + - 14268:14268 diff --git a/examples/autonatv2/src/bin/autonatv2_client.rs b/examples/autonatv2/src/bin/autonatv2_client.rs new file mode 100644 index 00000000000..de902514dd8 --- /dev/null +++ b/examples/autonatv2/src/bin/autonatv2_client.rs @@ -0,0 +1,111 @@ +use std::{error::Error, net::Ipv4Addr, time::Duration}; + +use clap::Parser; +use libp2p::{ + autonat, + futures::StreamExt, + identify, identity, + multiaddr::Protocol, + noise, + swarm::{dial_opts::DialOpts, NetworkBehaviour, SwarmEvent}, + tcp, yamux, Multiaddr, SwarmBuilder, +}; +use rand::rngs::OsRng; +use tracing_subscriber::EnvFilter; + +#[derive(Debug, Parser)] +#[clap(name = "libp2p autonatv2 client")] +struct Opt { + /// Port where the client will listen for incoming connections. + #[clap(short = 'p', long, default_value_t = 0)] + listen_port: u16, + + /// Address of the server where want to connect to. + #[clap(short = 'a', long)] + server_address: Multiaddr, + + /// Probe interval in seconds. + #[clap(short = 't', long, default_value = "2")] + probe_interval: u64, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let _ = tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .try_init(); + + let opt = Opt::parse(); + + let mut swarm = SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_quic() + .with_dns()? + .with_behaviour(|key| Behaviour::new(key.public(), opt.probe_interval))? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(10))) + .build(); + + swarm.listen_on( + Multiaddr::empty() + .with(Protocol::Ip4(Ipv4Addr::UNSPECIFIED)) + .with(Protocol::Tcp(opt.listen_port)), + )?; + + swarm.dial( + DialOpts::unknown_peer_id() + .address(opt.server_address) + .build(), + )?; + + loop { + match swarm.select_next_some().await { + SwarmEvent::NewListenAddr { address, .. } => { + println!("Listening on {address:?}"); + } + SwarmEvent::Behaviour(BehaviourEvent::Autonat(autonat::v2::client::Event { + server, + tested_addr, + bytes_sent, + result: Ok(()), + })) => { + println!("Tested {tested_addr} with {server}. Sent {bytes_sent} bytes for verification. Everything Ok and verified."); + } + SwarmEvent::Behaviour(BehaviourEvent::Autonat(autonat::v2::client::Event { + server, + tested_addr, + bytes_sent, + result: Err(e), + })) => { + println!("Tested {tested_addr} with {server}. Sent {bytes_sent} bytes for verification. Failed with {e:?}."); + } + SwarmEvent::ExternalAddrConfirmed { address } => { + println!("External address confirmed: {address}"); + } + _ => {} + } + } +} + +#[derive(NetworkBehaviour)] +pub struct Behaviour { + autonat: autonat::v2::client::Behaviour, + identify: identify::Behaviour, +} + +impl Behaviour { + pub fn new(key: identity::PublicKey, probe_interval: u64) -> Self { + Self { + autonat: autonat::v2::client::Behaviour::new( + OsRng, + autonat::v2::client::Config::default() + .with_probe_interval(Duration::from_secs(probe_interval)), + ), + identify: identify::Behaviour::new(identify::Config::new("/ipfs/0.1.0".into(), key)), + } + } +} diff --git a/examples/autonatv2/src/bin/autonatv2_server.rs b/examples/autonatv2/src/bin/autonatv2_server.rs new file mode 100644 index 00000000000..849ed3b3b0a --- /dev/null +++ b/examples/autonatv2/src/bin/autonatv2_server.rs @@ -0,0 +1,87 @@ +use std::{error::Error, net::Ipv4Addr, time::Duration}; + +use cfg_if::cfg_if; +use clap::Parser; +use libp2p::{ + autonat, + futures::StreamExt, + identify, identity, + multiaddr::Protocol, + noise, + swarm::{NetworkBehaviour, SwarmEvent}, + tcp, yamux, Multiaddr, SwarmBuilder, +}; +use rand::rngs::OsRng; + +#[derive(Debug, Parser)] +#[clap(name = "libp2p autonatv2 server")] +struct Opt { + #[clap(short, long, default_value_t = 0)] + listen_port: u16, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + cfg_if! { + if #[cfg(feature = "jaeger")] { + use tracing_subscriber::layer::SubscriberExt; + use opentelemetry_sdk::runtime::Tokio; + let tracer = opentelemetry_jaeger::new_agent_pipeline() + .with_endpoint("jaeger:6831") + .with_service_name("autonatv2") + .install_batch(Tokio)?; + let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); + let subscriber = tracing_subscriber::Registry::default() + .with(telemetry); + } else { + let subscriber = tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .finish(); + } + } + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); + + let opt = Opt::parse(); + + let mut swarm = SwarmBuilder::with_new_identity() + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_quic() + .with_dns()? + .with_behaviour(|key| Behaviour::new(key.public()))? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) + .build(); + + swarm.listen_on( + Multiaddr::empty() + .with(Protocol::Ip4(Ipv4Addr::UNSPECIFIED)) + .with(Protocol::Tcp(opt.listen_port)), + )?; + + loop { + match swarm.select_next_some().await { + SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {address:?}"), + SwarmEvent::Behaviour(event) => println!("{event:?}"), + e => println!("{e:?}"), + } + } +} + +#[derive(NetworkBehaviour)] +pub struct Behaviour { + autonat: autonat::v2::server::Behaviour, + identify: identify::Behaviour, +} + +impl Behaviour { + pub fn new(key: identity::PublicKey) -> Self { + Self { + autonat: autonat::v2::server::Behaviour::new(OsRng), + identify: identify::Behaviour::new(identify::Config::new("/ipfs/0.1.0".into(), key)), + } + } +} diff --git a/examples/browser-webrtc/Cargo.toml b/examples/browser-webrtc/Cargo.toml index 57232abeb5e..c3630d805fb 100644 --- a/examples/browser-webrtc/Cargo.toml +++ b/examples/browser-webrtc/Cargo.toml @@ -16,30 +16,30 @@ release = false crate-type = ["cdylib"] [dependencies] -anyhow = "1.0.72" -futures = "0.3.29" +anyhow = "1.0.86" +futures = { workspace = true } rand = "0.8" -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -axum = "0.6.19" +axum = "0.7.5" libp2p = { path = "../../libp2p", features = [ "ed25519", "macros", "ping", "tokio"] } libp2p-webrtc = { workspace = true, features = ["tokio"] } -rust-embed = { version = "8.0.0", features = ["include-exclude", "interpolate-folder-path"] } -tokio = { version = "1.34", features = ["macros", "net", "rt", "signal"] } +rust-embed = { version = "8.4.0", features = ["include-exclude", "interpolate-folder-path"] } +tokio = { workspace = true, features = ["macros", "net", "rt", "signal"] } tokio-util = { version = "0.7", features = ["compat"] } tower = "0.4" -tower-http = { version = "0.4.0", features = ["cors"] } +tower-http = { version = "0.5.2", features = ["cors"] } mime_guess = "2.0.4" [target.'cfg(target_arch = "wasm32")'.dependencies] -js-sys = "0.3.66" +js-sys = "0.3.69" libp2p = { path = "../../libp2p", features = [ "ed25519", "macros", "ping", "wasm-bindgen"] } libp2p-webrtc-websys = { workspace = true } tracing-wasm = "0.2.1" -wasm-bindgen = "0.2.89" -wasm-bindgen-futures = "0.4.38" +wasm-bindgen = "0.2.90" +wasm-bindgen-futures = "0.4.42" web-sys = { version = "0.3", features = ['Document', 'Element', 'HtmlElement', 'Node', 'Response', 'Window'] } [lints] diff --git a/examples/browser-webrtc/README.md b/examples/browser-webrtc/README.md index d44cf879905..eec2c9c0494 100644 --- a/examples/browser-webrtc/README.md +++ b/examples/browser-webrtc/README.md @@ -5,6 +5,8 @@ It uses [wasm-pack](https://rustwasm.github.io/docs/wasm-pack/) to build the pro ## Running the example +Ensure you have `wasm-pack` [installed](https://rustwasm.github.io/wasm-pack/). + 1. Build the client library: ```shell wasm-pack build --target web --out-dir static diff --git a/examples/browser-webrtc/src/lib.rs b/examples/browser-webrtc/src/lib.rs index 2112919c6de..e2d884cb445 100644 --- a/examples/browser-webrtc/src/lib.rs +++ b/examples/browser-webrtc/src/lib.rs @@ -1,13 +1,11 @@ #![cfg(target_arch = "wasm32")] +use std::{io, time::Duration}; + use futures::StreamExt; use js_sys::Date; -use libp2p::core::Multiaddr; -use libp2p::ping; -use libp2p::swarm::SwarmEvent; +use libp2p::{core::Multiaddr, ping, swarm::SwarmEvent}; use libp2p_webrtc_websys as webrtc_websys; -use std::io; -use std::time::Duration; use wasm_bindgen::prelude::*; use web_sys::{Document, HtmlElement}; @@ -15,8 +13,13 @@ use web_sys::{Document, HtmlElement}; pub async fn run(libp2p_endpoint: String) -> Result<(), JsError> { tracing_wasm::set_as_global_default(); + let ping_duration = Duration::from_secs(30); + let body = Body::from_current_window()?; - body.append_p("Let's ping the WebRTC Server!")?; + body.append_p(&format!( + "Let's ping the rust-libp2p server over WebRTC for {:?}:", + ping_duration + ))?; let mut swarm = libp2p::SwarmBuilder::with_new_identity() .with_wasm_bindgen() @@ -24,7 +27,7 @@ pub async fn run(libp2p_endpoint: String) -> Result<(), JsError> { webrtc_websys::Transport::new(webrtc_websys::Config::new(&key)) })? .with_behaviour(|_| ping::Behaviour::new(ping::Config::new()))? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) + .with_swarm_config(|c| c.with_idle_connection_timeout(ping_duration)) .build(); let addr = libp2p_endpoint.parse::()?; @@ -46,6 +49,18 @@ pub async fn run(libp2p_endpoint: String) -> Result<(), JsError> { tracing::info!("Ping successful: RTT: {rtt:?}, from {peer}"); body.append_p(&format!("RTT: {rtt:?} at {}", Date::new_0().to_string()))?; } + SwarmEvent::ConnectionClosed { + cause: Some(cause), .. + } => { + tracing::info!("Swarm event: {:?}", cause); + + if let libp2p::swarm::ConnectionError::KeepAliveTimeout = cause { + body.append_p("All done with pinging! ")?; + + break; + } + body.append_p(&format!("Connection closed due to: {:?}", cause))?; + } evt => tracing::info!("Swarm event: {:?}", evt), } } diff --git a/examples/browser-webrtc/src/main.rs b/examples/browser-webrtc/src/main.rs index 098b70f3054..52222dc882b 100644 --- a/examples/browser-webrtc/src/main.rs +++ b/examples/browser-webrtc/src/main.rs @@ -1,23 +1,25 @@ #![allow(non_upper_case_globals)] +use std::net::{Ipv4Addr, SocketAddr}; + use anyhow::Result; -use axum::extract::{Path, State}; -use axum::http::header::CONTENT_TYPE; -use axum::http::StatusCode; -use axum::response::{Html, IntoResponse}; -use axum::{http::Method, routing::get, Router}; +use axum::{ + extract::{Path, State}, + http::{header::CONTENT_TYPE, Method, StatusCode}, + response::{Html, IntoResponse}, + routing::get, + Router, +}; use futures::StreamExt; use libp2p::{ - core::muxing::StreamMuxerBox, - core::Transport, + core::{muxing::StreamMuxerBox, Transport}, multiaddr::{Multiaddr, Protocol}, ping, swarm::SwarmEvent, }; use libp2p_webrtc as webrtc; use rand::thread_rng; -use std::net::{Ipv4Addr, SocketAddr}; -use std::time::Duration; +use tokio::net::TcpListener; use tower_http::cors::{Any, CorsLayer}; #[tokio::main] @@ -36,11 +38,6 @@ async fn main() -> anyhow::Result<()> { .map(|(peer_id, conn), _| (peer_id, StreamMuxerBox::new(conn)))) })? .with_behaviour(|_| ping::Behaviour::default())? - .with_swarm_config(|cfg| { - cfg.with_idle_connection_timeout( - Duration::from_secs(30), // Allows us to observe the pings. - ) - }) .build(); let address_webrtc = Multiaddr::from(Ipv4Addr::UNSPECIFIED) @@ -112,10 +109,12 @@ pub(crate) async fn serve(libp2p_transport: Multiaddr) { tracing::info!(url=%format!("http://{addr}"), "Serving client files at url"); - axum::Server::bind(&addr) - .serve(server.into_make_service()) - .await - .unwrap(); + axum::serve( + TcpListener::bind((listen_addr, 8080)).await.unwrap(), + server.into_make_service(), + ) + .await + .unwrap(); } #[derive(Clone)] @@ -124,7 +123,8 @@ struct Libp2pEndpoint(Multiaddr); /// Serves the index.html file for our client. /// /// Our server listens on a random UDP port for the WebRTC transport. -/// To allow the client to connect, we replace the `__LIBP2P_ENDPOINT__` placeholder with the actual address. +/// To allow the client to connect, we replace the `__LIBP2P_ENDPOINT__` +/// placeholder with the actual address. async fn get_index( State(Libp2pEndpoint(libp2p_endpoint)): State, ) -> Result, StatusCode> { diff --git a/examples/chat/Cargo.toml b/examples/chat/Cargo.toml index 9b3561dab48..031f84b6f95 100644 --- a/examples/chat/Cargo.toml +++ b/examples/chat/Cargo.toml @@ -9,12 +9,10 @@ license = "MIT" release = false [dependencies] -tokio = { version = "1.34", features = ["full"] } -async-trait = "0.1" -futures = "0.3.29" +tokio = { workspace = true, features = ["full"] } +futures = { workspace = true } libp2p = { path = "../../libp2p", features = [ "tokio", "gossipsub", "mdns", "noise", "macros", "tcp", "yamux", "quic"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true diff --git a/examples/chat/README.md b/examples/chat/README.md index 96bad137b07..2dbf585100b 100644 --- a/examples/chat/README.md +++ b/examples/chat/README.md @@ -11,7 +11,7 @@ It showcases how peers can connect, discover each other using mDNS, and engage i ``` 2. Mutual mDNS discovery may take a few seconds. When each peer does discover the other -it will print a message like: + it will print a message like: ```sh mDNS discovered a new peer: {peerId} ``` diff --git a/examples/chat/src/main.rs b/examples/chat/src/main.rs index c785d301c2f..b0dcc767b6f 100644 --- a/examples/chat/src/main.rs +++ b/examples/chat/src/main.rs @@ -20,12 +20,19 @@ #![doc = include_str!("../README.md")] +use std::{ + collections::hash_map::DefaultHasher, + error::Error, + hash::{Hash, Hasher}, + time::Duration, +}; + use futures::stream::StreamExt; -use libp2p::{gossipsub, mdns, noise, swarm::NetworkBehaviour, swarm::SwarmEvent, tcp, yamux}; -use std::collections::hash_map::DefaultHasher; -use std::error::Error; -use std::hash::{Hash, Hasher}; -use std::time::Duration; +use libp2p::{ + gossipsub, mdns, noise, + swarm::{NetworkBehaviour, SwarmEvent}, + tcp, yamux, +}; use tokio::{io, io::AsyncBufReadExt, select}; use tracing_subscriber::EnvFilter; @@ -61,7 +68,8 @@ async fn main() -> Result<(), Box> { // Set a custom gossipsub configuration let gossipsub_config = gossipsub::ConfigBuilder::default() .heartbeat_interval(Duration::from_secs(10)) // This is set to aid debugging by not cluttering the log space - .validation_mode(gossipsub::ValidationMode::Strict) // This sets the kind of message validation. The default is Strict (enforce message signing) + .validation_mode(gossipsub::ValidationMode::Strict) // This sets the kind of message validation. The default is Strict (enforce message + // signing) .message_id_fn(message_id_fn) // content-address messages. No two messages of the same content will be propagated. .build() .map_err(|msg| io::Error::new(io::ErrorKind::Other, msg))?; // Temporary hack because `build` does not return a proper `std::error::Error`. @@ -76,7 +84,6 @@ async fn main() -> Result<(), Box> { mdns::tokio::Behaviour::new(mdns::Config::default(), key.public().to_peer_id())?; Ok(MyBehaviour { gossipsub, mdns }) })? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) .build(); // Create a Gossipsub topic diff --git a/examples/dcutr/Cargo.toml b/examples/dcutr/Cargo.toml index 8dcb403218e..67edf04e2b0 100644 --- a/examples/dcutr/Cargo.toml +++ b/examples/dcutr/Cargo.toml @@ -9,14 +9,13 @@ license = "MIT" release = false [dependencies] -clap = { version = "4.4.10", features = ["derive"] } -futures = "0.3.29" +clap = { version = "4.5.6", features = ["derive"] } +futures = { workspace = true } futures-timer = "3.0" libp2p = { path = "../../libp2p", features = [ "dns", "dcutr", "identify", "macros", "noise", "ping", "quic", "relay", "rendezvous", "tcp", "tokio", "yamux"] } -log = "0.4" -tokio = { version = "1.34", features = ["macros", "net", "rt", "signal"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tokio = { workspace = true, features = ["macros", "net", "rt", "signal"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true diff --git a/examples/dcutr/src/main.rs b/examples/dcutr/src/main.rs index 51df670f8a7..3f403d534e7 100644 --- a/examples/dcutr/src/main.rs +++ b/examples/dcutr/src/main.rs @@ -20,6 +20,8 @@ #![doc = include_str!("../README.md")] +use std::{error::Error, str::FromStr}; + use clap::Parser; use futures::{executor::block_on, future::FutureExt, stream::StreamExt}; use libp2p::{ @@ -28,8 +30,6 @@ use libp2p::{ swarm::{NetworkBehaviour, SwarmEvent}, tcp, yamux, PeerId, }; -use std::str::FromStr; -use std::{error::Error, time::Duration}; use tracing_subscriber::EnvFilter; #[derive(Debug, Parser)] @@ -89,7 +89,7 @@ async fn main() -> Result<(), Box> { libp2p::SwarmBuilder::with_existing_identity(generate_ed25519(opts.secret_key_seed)) .with_tokio() .with_tcp( - tcp::Config::default().port_reuse(true).nodelay(true), + tcp::Config::default().nodelay(true), noise::Config::new, yamux::Config::default, )? @@ -105,7 +105,6 @@ async fn main() -> Result<(), Box> { )), dcutr: dcutr::Behaviour::new(keypair.public().to_peer_id()), })? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) .build(); swarm diff --git a/examples/distributed-key-value-store/Cargo.toml b/examples/distributed-key-value-store/Cargo.toml index dc084b30091..8e30dd2c75d 100644 --- a/examples/distributed-key-value-store/Cargo.toml +++ b/examples/distributed-key-value-store/Cargo.toml @@ -9,12 +9,10 @@ license = "MIT" release = false [dependencies] -async-std = { version = "1.12", features = ["attributes"] } -async-trait = "0.1" -futures = "0.3.29" -libp2p = { path = "../../libp2p", features = [ "async-std", "dns", "kad", "mdns", "noise", "macros", "tcp", "yamux"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tokio = { workspace = true, features = ["full"] } +futures = { workspace = true } +libp2p = { path = "../../libp2p", features = [ "tokio", "dns", "kad", "mdns", "noise", "macros", "tcp", "yamux"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true diff --git a/examples/distributed-key-value-store/README.md b/examples/distributed-key-value-store/README.md index 6065609cfdf..4a44e0c5427 100644 --- a/examples/distributed-key-value-store/README.md +++ b/examples/distributed-key-value-store/README.md @@ -9,10 +9,10 @@ This example showcases a basic distributed key-value store implemented using **l 1. Open two terminal windows, type `cargo run` and press Enter. 2. In terminal one, type `PUT my-key my-value` and press Enter. -This command will store the value `my-value` with the key `my-key` in the distributed key-value store. + This command will store the value `my-value` with the key `my-key` in the distributed key-value store. 3. In terminal two, type `GET my-key` and press Enter. -This command will retrieve the value associated with the key `my-key` from the key-value store. + This command will retrieve the value associated with the key `my-key` from the key-value store. 4. To exit, press `Ctrl-c` in each terminal window to gracefully close the instances. @@ -22,13 +22,13 @@ This command will retrieve the value associated with the key `my-key` from the k You can also use provider records instead of key-value records in the distributed store. 1. Open two terminal windows and start two instances of the key-value store. -If your local network supports mDNS, the instances will automatically connect. + If your local network supports mDNS, the instances will automatically connect. 2. In terminal one, type `PUT_PROVIDER my-key` and press Enter. -This command will register the peer as a provider for the key `my-key` in the distributed key-value store. + This command will register the peer as a provider for the key `my-key` in the distributed key-value store. 3. In terminal two, type `GET_PROVIDERS my-key` and press Enter. -This command will retrieve the list of providers for the key `my-key` from the key-value store. + This command will retrieve the list of providers for the key `my-key` from the key-value store. 4. To exit, press `Ctrl-c` in each terminal window to gracefully close the instances. diff --git a/examples/distributed-key-value-store/src/main.rs b/examples/distributed-key-value-store/src/main.rs index 404333f3d20..3522c84c720 100644 --- a/examples/distributed-key-value-store/src/main.rs +++ b/examples/distributed-key-value-store/src/main.rs @@ -20,21 +20,23 @@ #![doc = include_str!("../README.md")] -use async_std::io; -use futures::{prelude::*, select}; -use libp2p::kad; -use libp2p::kad::store::MemoryStore; -use libp2p::kad::Mode; +use std::error::Error; + +use futures::stream::StreamExt; use libp2p::{ + kad, + kad::{store::MemoryStore, Mode}, mdns, noise, swarm::{NetworkBehaviour, SwarmEvent}, tcp, yamux, }; -use std::error::Error; -use std::time::Duration; +use tokio::{ + io::{self, AsyncBufReadExt}, + select, +}; use tracing_subscriber::EnvFilter; -#[async_std::main] +#[tokio::main] async fn main() -> Result<(), Box> { let _ = tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) @@ -44,11 +46,11 @@ async fn main() -> Result<(), Box> { #[derive(NetworkBehaviour)] struct Behaviour { kademlia: kad::Behaviour, - mdns: mdns::async_io::Behaviour, + mdns: mdns::tokio::Behaviour, } let mut swarm = libp2p::SwarmBuilder::with_new_identity() - .with_async_std() + .with_tokio() .with_tcp( tcp::Config::default(), noise::Config::new, @@ -60,19 +62,18 @@ async fn main() -> Result<(), Box> { key.public().to_peer_id(), MemoryStore::new(key.public().to_peer_id()), ), - mdns: mdns::async_io::Behaviour::new( + mdns: mdns::tokio::Behaviour::new( mdns::Config::default(), key.public().to_peer_id(), )?, }) })? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) .build(); swarm.behaviour_mut().kademlia.set_mode(Some(Mode::Server)); // Read full lines from stdin - let mut stdin = io::BufReader::new(io::stdin()).lines().fuse(); + let mut stdin = io::BufReader::new(io::stdin()).lines(); // Listen on all interfaces and whatever port the OS assigns. swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?; @@ -80,7 +81,9 @@ async fn main() -> Result<(), Box> { // Kick it off. loop { select! { - line = stdin.select_next_some() => handle_input_line(&mut swarm.behaviour_mut().kademlia, line.expect("Stdin not to close")), + Ok(Some(line)) = stdin.next_line() => { + handle_input_line(&mut swarm.behaviour_mut().kademlia, line); + } event = swarm.select_next_some() => match event { SwarmEvent::NewListenAddr { address, .. } => { println!("Listening in {address:?}"); diff --git a/examples/file-sharing/Cargo.toml b/examples/file-sharing/Cargo.toml index 52d1b3b9b20..021215c003b 100644 --- a/examples/file-sharing/Cargo.toml +++ b/examples/file-sharing/Cargo.toml @@ -10,14 +10,11 @@ release = false [dependencies] serde = { version = "1.0", features = ["derive"] } -async-std = { version = "1.12", features = ["attributes"] } -clap = { version = "4.4.10", features = ["derive"] } -either = "1.9" -futures = "0.3.29" -libp2p = { path = "../../libp2p", features = [ "async-std", "cbor", "dns", "kad", "noise", "macros", "request-response", "tcp", "websocket", "yamux"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -void = "1.0.2" +tokio = { workspace = true, features = ["full"] } +clap = { version = "4.5.6", features = ["derive"] } +futures = { workspace = true } +libp2p = { path = "../../libp2p", features = [ "tokio", "cbor", "dns", "kad", "noise", "macros", "request-response", "tcp", "websocket", "yamux"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true diff --git a/examples/file-sharing/README.md b/examples/file-sharing/README.md index 2a2b3ec5317..5424026bb12 100644 --- a/examples/file-sharing/README.md +++ b/examples/file-sharing/README.md @@ -12,28 +12,28 @@ Retrievers can locate and retrieve files by their names from any node in the net Let's understand the flow of the file sharing process: - **File Providers**: Nodes A and B serve as file providers. -Each node offers a specific file: file FA for node A and file FB for node B. -To make their files available, they advertise themselves as providers on the DHT using `libp2p-kad`. -This enables other nodes in the network to discover and retrieve their files. + Each node offers a specific file: file FA for node A and file FB for node B. + To make their files available, they advertise themselves as providers on the DHT using `libp2p-kad`. + This enables other nodes in the network to discover and retrieve their files. - **File Retrievers**: Node C acts as a file retriever. -It wants to retrieve either file FA or FB. -Using `libp2p-kad`, it can locate the providers for these files on the DHT without being directly connected to them. -Node C connects to the corresponding provider node and requests the file content using `libp2p-request-response`. + It wants to retrieve either file FA or FB. + Using `libp2p-kad`, it can locate the providers for these files on the DHT without being directly connected to them. + Node C connects to the corresponding provider node and requests the file content using `libp2p-request-response`. - **DHT and Network Connectivity**: The DHT (Distributed Hash Table) plays a crucial role in the file sharing process. -It allows nodes to store and discover information about file providers. -Nodes in the network are interconnected via the DHT, enabling efficient file discovery and retrieval. + It allows nodes to store and discover information about file providers. + Nodes in the network are interconnected via the DHT, enabling efficient file discovery and retrieval. ## Architectural Properties The File Sharing application has the following architectural properties: - **Clean and Clonable Interface**: The application provides a clean and clonable async/await interface, allowing users to interact with the network layer seamlessly. -The `Client` module encapsulates the necessary functionality for network communication. + The `Client` module encapsulates the necessary functionality for network communication. - **Efficient Network Handling**: The application operates with a single task that drives the network layer. -This design choice ensures efficient network communication without the need for locks or complex synchronization mechanisms. + This design choice ensures efficient network communication without the need for locks or complex synchronization mechanisms. ## Usage diff --git a/examples/file-sharing/src/main.rs b/examples/file-sharing/src/main.rs index a834ee0600e..1e3b80a330c 100644 --- a/examples/file-sharing/src/main.rs +++ b/examples/file-sharing/src/main.rs @@ -22,18 +22,15 @@ mod network; -use async_std::task::spawn; -use clap::Parser; +use std::{error::Error, io::Write, path::PathBuf}; -use futures::prelude::*; -use futures::StreamExt; +use clap::Parser; +use futures::{prelude::*, StreamExt}; use libp2p::{core::Multiaddr, multiaddr::Protocol}; -use std::error::Error; -use std::io::Write; -use std::path::PathBuf; +use tokio::task::spawn; use tracing_subscriber::EnvFilter; -#[async_std::main] +#[tokio::main] async fn main() -> Result<(), Box> { let _ = tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) diff --git a/examples/file-sharing/src/network.rs b/examples/file-sharing/src/network.rs index 59625fc39ea..409255ee9ec 100644 --- a/examples/file-sharing/src/network.rs +++ b/examples/file-sharing/src/network.rs @@ -1,6 +1,14 @@ -use futures::channel::{mpsc, oneshot}; -use futures::prelude::*; +use std::{ + collections::{hash_map, HashMap, HashSet}, + error::Error, + time::Duration, +}; +use futures::{ + channel::{mpsc, oneshot}, + prelude::*, + StreamExt, +}; use libp2p::{ core::Multiaddr, identity, kad, @@ -8,19 +16,13 @@ use libp2p::{ noise, request_response::{self, OutboundRequestId, ProtocolSupport, ResponseChannel}, swarm::{NetworkBehaviour, Swarm, SwarmEvent}, - tcp, yamux, PeerId, + tcp, yamux, PeerId, StreamProtocol, }; - -use libp2p::StreamProtocol; use serde::{Deserialize, Serialize}; -use std::collections::{hash_map, HashMap, HashSet}; -use std::error::Error; -use std::time::Duration; /// Creates the network components, namely: /// -/// - The network client to interact with the network layer from anywhere -/// within your application. +/// - The network client to interact with the network layer from anywhere within your application. /// /// - The network event stream, e.g. for incoming requests. /// @@ -40,7 +42,7 @@ pub(crate) async fn new( let peer_id = id_keys.public().to_peer_id(); let mut swarm = libp2p::SwarmBuilder::with_existing_identity(id_keys) - .with_async_std() + .with_tokio() .with_tcp( tcp::Config::default(), noise::Config::new, @@ -197,8 +199,8 @@ impl EventLoop { pub(crate) async fn run(mut self) { loop { - futures::select! { - event = self.swarm.next() => self.handle_event(event.expect("Swarm stream to be infinite.")).await , + tokio::select! { + event = self.swarm.select_next_some() => self.handle_event(event).await, command = self.command_receiver.next() => match command { Some(c) => self.handle_command(c).await, // Command channel closed, thus shutting down the network event loop. diff --git a/examples/identify/Cargo.toml b/examples/identify/Cargo.toml index 48449636c61..c18f71a0386 100644 --- a/examples/identify/Cargo.toml +++ b/examples/identify/Cargo.toml @@ -9,12 +9,10 @@ license = "MIT" release = false [dependencies] -async-std = { version = "1.12", features = ["attributes"] } -async-trait = "0.1" -futures = "0.3.29" -libp2p = { path = "../../libp2p", features = ["async-std", "dns", "dcutr", "identify", "macros", "noise", "ping", "relay", "rendezvous", "tcp", "tokio","yamux"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tokio = { version = "1.37.0", features = ["full"] } +futures = { workspace = true } +libp2p = { path = "../../libp2p", features = ["identify", "noise", "tcp", "tokio", "yamux"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true diff --git a/examples/identify/src/main.rs b/examples/identify/src/main.rs index 916317a5a43..3f08ac01e23 100644 --- a/examples/identify/src/main.rs +++ b/examples/identify/src/main.rs @@ -20,19 +20,20 @@ #![doc = include_str!("../README.md")] +use std::error::Error; + use futures::StreamExt; use libp2p::{core::multiaddr::Multiaddr, identify, noise, swarm::SwarmEvent, tcp, yamux}; -use std::{error::Error, time::Duration}; use tracing_subscriber::EnvFilter; -#[async_std::main] +#[tokio::main] async fn main() -> Result<(), Box> { let _ = tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .try_init(); let mut swarm = libp2p::SwarmBuilder::with_new_identity() - .with_async_std() + .with_tokio() .with_tcp( tcp::Config::default(), noise::Config::new, @@ -44,7 +45,6 @@ async fn main() -> Result<(), Box> { key.public(), )) })? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) .build(); // Tell the swarm to listen on all interfaces and a random, OS-assigned diff --git a/examples/ipfs-kad/Cargo.toml b/examples/ipfs-kad/Cargo.toml index 7870d3adb79..fa04da4edcf 100644 --- a/examples/ipfs-kad/Cargo.toml +++ b/examples/ipfs-kad/Cargo.toml @@ -9,15 +9,12 @@ license = "MIT" release = false [dependencies] -tokio = { version = "1.34", features = ["rt-multi-thread", "macros"] } -async-trait = "0.1" -clap = { version = "4.4.10", features = ["derive"] } -env_logger = "0.10" -futures = "0.3.29" -anyhow = "1.0.75" +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +clap = { version = "4.5.6", features = ["derive"] } +futures = { workspace = true } +anyhow = "1.0.86" libp2p = { path = "../../libp2p", features = [ "tokio", "dns", "kad", "noise", "tcp", "yamux", "rsa"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true diff --git a/examples/ipfs-kad/README.md b/examples/ipfs-kad/README.md index ccefb6b1d9f..05556c89382 100644 --- a/examples/ipfs-kad/README.md +++ b/examples/ipfs-kad/README.md @@ -11,7 +11,7 @@ By running this example, users can gain a better understanding of how the Kademl The example code demonstrates how to perform Kademlia queries on the IPFS network using the Rust P2P Library. -### Getting closes peers +### Getting closest peers By specifying a peer ID as a parameter, the code will search for the closest peers to the given peer ID. @@ -87,5 +87,5 @@ Failed to insert the PK record ## Conclusion In conclusion, this example provides a practical demonstration of using the Rust P2P Library to interact with the Kademlia protocol on the IPFS network. -By examining the code and running the example, users can gain insights into the inner workings of Kademlia and how it performs various basic actions like getting the closes peers or inserting records into the DHT. +By examining the code and running the example, users can gain insights into the inner workings of Kademlia and how it performs various basic actions like getting the closest peers or inserting records into the DHT. This knowledge can be valuable when developing peer-to-peer applications or understanding decentralized networks. diff --git a/examples/ipfs-kad/src/main.rs b/examples/ipfs-kad/src/main.rs index 0d11bdd851a..8d9a289bdd1 100644 --- a/examples/ipfs-kad/src/main.rs +++ b/examples/ipfs-kad/src/main.rs @@ -20,14 +20,21 @@ #![doc = include_str!("../README.md")] -use std::num::NonZeroUsize; -use std::ops::Add; -use std::time::{Duration, Instant}; +use std::{ + num::NonZeroUsize, + ops::Add, + time::{Duration, Instant}, +}; use anyhow::{bail, Result}; use clap::Parser; use futures::StreamExt; -use libp2p::{bytes::BufMut, identity, kad, noise, swarm::SwarmEvent, tcp, yamux, PeerId}; +use libp2p::{ + bytes::BufMut, + identity, kad, noise, + swarm::{StreamProtocol, SwarmEvent}, + tcp, yamux, PeerId, +}; use tracing_subscriber::EnvFilter; const BOOTNODES: [&str; 4] = [ @@ -37,6 +44,8 @@ const BOOTNODES: [&str; 4] = [ "QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt", ]; +const IPFS_PROTO_NAME: StreamProtocol = StreamProtocol::new("/ipfs/kad/1.0.0"); + #[tokio::main] async fn main() -> Result<()> { let _ = tracing_subscriber::fmt() @@ -56,12 +65,11 @@ async fn main() -> Result<()> { .with_dns()? .with_behaviour(|key| { // Create a Kademlia behaviour. - let mut cfg = kad::Config::default(); + let mut cfg = kad::Config::new(IPFS_PROTO_NAME); cfg.set_query_timeout(Duration::from_secs(5 * 60)); let store = kad::store::MemoryStore::new(key.public().to_peer_id()); kad::Behaviour::with_config(key.public().to_peer_id(), store, cfg) })? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(); // Add the bootnodes to the local routing table. `libp2p-dns` built diff --git a/examples/ipfs-private/Cargo.toml b/examples/ipfs-private/Cargo.toml index 97c1ca59368..4dfe596d609 100644 --- a/examples/ipfs-private/Cargo.toml +++ b/examples/ipfs-private/Cargo.toml @@ -9,13 +9,11 @@ license = "MIT" release = false [dependencies] -tokio = { version = "1.34", features = ["rt-multi-thread", "macros", "io-std"] } -async-trait = "0.1" -either = "1.9" -futures = "0.3.29" +tokio = { workspace = true, features = ["rt-multi-thread", "macros", "io-std"] } +either = "1.12" +futures = { workspace = true } libp2p = { path = "../../libp2p", features = [ "tokio", "gossipsub", "dns", "identify", "kad", "macros", "noise", "ping", "pnet", "tcp", "websocket", "yamux"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true diff --git a/examples/ipfs-private/README.md b/examples/ipfs-private/README.md index bc3b12a7ac8..ae7fc42c9c4 100644 --- a/examples/ipfs-private/README.md +++ b/examples/ipfs-private/README.md @@ -24,11 +24,11 @@ To run the example, follow these steps: 2. Once the example is running, you can interact with the IPFS node using the following commands: - **Pubsub (Gossipsub):** You can use the gossipsub protocol to send and receive messages on the "chat" topic. - To send a message, type it in the console and press Enter. - The message will be broadcasted to other connected nodes using gossipsub. + To send a message, type it in the console and press Enter. + The message will be broadcasted to other connected nodes using gossipsub. - **Ping:** You can ping other connected nodes to test network connectivity. - The example will display the round-trip time (RTT) for successful pings or indicate if a timeout occurs. + The example will display the round-trip time (RTT) for successful pings or indicate if a timeout occurs. ## Conclusion diff --git a/examples/ipfs-private/src/main.rs b/examples/ipfs-private/src/main.rs index a57bfd465e0..6d8f9beb75d 100644 --- a/examples/ipfs-private/src/main.rs +++ b/examples/ipfs-private/src/main.rs @@ -20,6 +20,8 @@ #![doc = include_str!("../README.md")] +use std::{env, error::Error, fs, path::Path, str::FromStr}; + use either::Either; use futures::prelude::*; use libp2p::{ @@ -31,7 +33,6 @@ use libp2p::{ swarm::{NetworkBehaviour, SwarmEvent}, tcp, yamux, Multiaddr, Transport, }; -use std::{env, error::Error, fs, path::Path, str::FromStr, time::Duration}; use tokio::{io, io::AsyncBufReadExt, select}; use tracing_subscriber::EnvFilter; @@ -151,7 +152,6 @@ async fn main() -> Result<(), Box> { ping: ping::Behaviour::new(ping::Config::new()), }) })? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) .build(); println!("Subscribing to {gossipsub_topic:?}"); diff --git a/examples/metrics/Cargo.toml b/examples/metrics/Cargo.toml index c8f74a17ebd..ad2941e3761 100644 --- a/examples/metrics/Cargo.toml +++ b/examples/metrics/Cargo.toml @@ -9,17 +9,17 @@ license = "MIT" release = false [dependencies] -futures = "0.3.29" -hyper = { version = "0.14", features = ["server", "tcp", "http1"] } +futures = { workspace = true } +axum = "0.7" libp2p = { path = "../../libp2p", features = ["tokio", "metrics", "ping", "noise", "identify", "tcp", "yamux", "macros"] } -opentelemetry = { version = "0.20.0", features = ["rt-tokio", "metrics"] } -opentelemetry-otlp = { version = "0.13.0", features = ["metrics"]} -opentelemetry_api = "0.20.0" +opentelemetry = { version = "0.27.0", features = ["metrics"] } +opentelemetry-otlp = { version = "0.27.0", features = ["metrics"] } +opentelemetry_sdk = { version = "0.27.0", features = ["rt-tokio", "metrics"] } prometheus-client = { workspace = true } -tokio = { version = "1", features = ["full"] } -tracing = "0.1.37" -tracing-opentelemetry = "0.21.0" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true } +tracing-opentelemetry = "0.28.0" +tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true diff --git a/examples/metrics/src/http_service.rs b/examples/metrics/src/http_service.rs index 8c77d724ea3..f1485832d86 100644 --- a/examples/metrics/src/http_service.rs +++ b/examples/metrics/src/http_service.rs @@ -18,109 +18,58 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use hyper::http::StatusCode; -use hyper::service::Service; -use hyper::{Body, Method, Request, Response, Server}; -use prometheus_client::encoding::text::encode; -use prometheus_client::registry::Registry; -use std::future::Future; -use std::pin::Pin; -use std::sync::{Arc, Mutex}; -use std::task::{Context, Poll}; +use std::{ + net::SocketAddr, + sync::{Arc, Mutex}, +}; + +use axum::{extract::State, http::StatusCode, response::IntoResponse, routing::get, Router}; +use prometheus_client::{encoding::text::encode, registry::Registry}; +use tokio::net::TcpListener; const METRICS_CONTENT_TYPE: &str = "application/openmetrics-text;charset=utf-8;version=1.0.0"; pub(crate) async fn metrics_server(registry: Registry) -> Result<(), std::io::Error> { // Serve on localhost. - let addr = ([127, 0, 0, 1], 0).into(); - - let server = Server::bind(&addr).serve(MakeMetricService::new(registry)); - tracing::info!(metrics_server=%format!("http://{}/metrics", server.local_addr())); - if let Err(e) = server.await { - tracing::error!("server error: {}", e); - } + let addr: SocketAddr = ([127, 0, 0, 1], 0).into(); + let service = MetricService::new(registry); + let server = Router::new() + .route("/metrics", get(respond_with_metrics)) + .with_state(service); + let tcp_listener = TcpListener::bind(addr).await?; + let local_addr = tcp_listener.local_addr()?; + tracing::info!(metrics_server=%format!("http://{}/metrics", local_addr)); + axum::serve(tcp_listener, server.into_make_service()).await?; Ok(()) } +#[derive(Clone)] pub(crate) struct MetricService { reg: Arc>, } -type SharedRegistry = Arc>; - -impl MetricService { - fn get_reg(&mut self) -> SharedRegistry { - Arc::clone(&self.reg) - } - fn respond_with_metrics(&mut self) -> Response { - let mut response: Response = Response::default(); - - response.headers_mut().insert( - hyper::header::CONTENT_TYPE, - METRICS_CONTENT_TYPE.try_into().unwrap(), - ); - - let reg = self.get_reg(); - encode(&mut response.body_mut(), ®.lock().unwrap()).unwrap(); - - *response.status_mut() = StatusCode::OK; +async fn respond_with_metrics(state: State) -> impl IntoResponse { + let mut sink = String::new(); + let reg = state.get_reg(); + encode(&mut sink, ®.lock().unwrap()).unwrap(); - response - } - fn respond_with_404_not_found(&mut self) -> Response { - Response::builder() - .status(StatusCode::NOT_FOUND) - .body("Not found try localhost:[port]/metrics".to_string()) - .unwrap() - } + ( + StatusCode::OK, + [(axum::http::header::CONTENT_TYPE, METRICS_CONTENT_TYPE)], + sink, + ) } -impl Service> for MetricService { - type Response = Response; - type Error = hyper::Error; - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Request) -> Self::Future { - let req_path = req.uri().path(); - let req_method = req.method(); - let resp = if (req_method == Method::GET) && (req_path == "/metrics") { - // Encode and serve metrics from registry. - self.respond_with_metrics() - } else { - self.respond_with_404_not_found() - }; - Box::pin(async { Ok(resp) }) - } -} - -pub(crate) struct MakeMetricService { - reg: SharedRegistry, -} +type SharedRegistry = Arc>; -impl MakeMetricService { - pub(crate) fn new(registry: Registry) -> MakeMetricService { - MakeMetricService { +impl MetricService { + fn new(registry: Registry) -> Self { + Self { reg: Arc::new(Mutex::new(registry)), } } -} - -impl Service for MakeMetricService { - type Response = MetricService; - type Error = hyper::Error; - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, _: &mut Context) -> Poll> { - Poll::Ready(Ok(())) - } - fn call(&mut self, _: T) -> Self::Future { - let reg = self.reg.clone(); - let fut = async move { Ok(MetricService { reg }) }; - Box::pin(fut) + fn get_reg(&self) -> SharedRegistry { + Arc::clone(&self.reg) } } diff --git a/examples/metrics/src/main.rs b/examples/metrics/src/main.rs index 3ab6815cb32..6f6e9d08e31 100644 --- a/examples/metrics/src/main.rs +++ b/examples/metrics/src/main.rs @@ -20,19 +20,22 @@ #![doc = include_str!("../README.md")] +use std::error::Error; + use futures::StreamExt; -use libp2p::core::Multiaddr; -use libp2p::metrics::{Metrics, Recorder}; -use libp2p::swarm::{NetworkBehaviour, SwarmEvent}; -use libp2p::{identify, identity, noise, ping, tcp, yamux}; -use opentelemetry::sdk; -use opentelemetry_api::KeyValue; +use libp2p::{ + core::Multiaddr, + identify, identity, + metrics::{Metrics, Recorder}, + noise, ping, + swarm::{NetworkBehaviour, SwarmEvent}, + tcp, yamux, +}; +use opentelemetry::{trace::TracerProvider as _, KeyValue}; +use opentelemetry_otlp::SpanExporter; +use opentelemetry_sdk::{runtime, trace::TracerProvider}; use prometheus_client::registry::Registry; -use std::error::Error; -use std::time::Duration; -use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::util::SubscriberInitExt; -use tracing_subscriber::{EnvFilter, Layer}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer}; mod http_service; @@ -51,7 +54,6 @@ async fn main() -> Result<(), Box> { )? .with_bandwidth_metrics(&mut metric_registry) .with_behaviour(|key| Behaviour::new(key.public()))? - .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX))) .build(); swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?; @@ -67,6 +69,13 @@ async fn main() -> Result<(), Box> { loop { match swarm.select_next_some().await { + SwarmEvent::NewListenAddr { address, .. } => { + tracing::info!( + "Local node is listening on\n {}/p2p/{}", + address, + swarm.local_peer_id() + ); + } SwarmEvent::Behaviour(BehaviourEvent::Ping(ping_event)) => { tracing::info!(?ping_event); metrics.record(&ping_event); @@ -84,25 +93,24 @@ async fn main() -> Result<(), Box> { } fn setup_tracing() -> Result<(), Box> { - let tracer = opentelemetry_otlp::new_pipeline() - .tracing() - .with_exporter(opentelemetry_otlp::new_exporter().tonic()) - .with_trace_config( - sdk::trace::Config::default().with_resource(sdk::Resource::new(vec![KeyValue::new( - "service.name", - "libp2p", - )])), + let provider = TracerProvider::builder() + .with_batch_exporter( + SpanExporter::builder().with_tonic().build()?, + runtime::Tokio, ) - .install_batch(opentelemetry::runtime::Tokio)?; - + .with_resource(opentelemetry_sdk::Resource::new(vec![KeyValue::new( + "service.name", + "libp2p", + )])) + .build(); tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().with_filter(EnvFilter::from_default_env())) .with( tracing_opentelemetry::layer() - .with_tracer(tracer) + .with_tracer(provider.tracer("libp2p-subscriber")) .with_filter(EnvFilter::from_default_env()), ) - .try_init()?; + .init(); Ok(()) } diff --git a/examples/ping/Cargo.toml b/examples/ping/Cargo.toml index 66d2847cb8f..acc3b2affed 100644 --- a/examples/ping/Cargo.toml +++ b/examples/ping/Cargo.toml @@ -9,11 +9,10 @@ license = "MIT" release = false [dependencies] -futures = "0.3.29" +futures = { workspace = true } libp2p = { path = "../../libp2p", features = ["noise", "ping", "tcp", "tokio", "yamux"] } -tokio = { version = "1.34.0", features = ["full"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tokio = { workspace = true, features = ["full"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true diff --git a/examples/ping/src/main.rs b/examples/ping/src/main.rs index 911b0384f89..565ef057c0d 100644 --- a/examples/ping/src/main.rs +++ b/examples/ping/src/main.rs @@ -20,9 +20,10 @@ #![doc = include_str!("../README.md")] +use std::{error::Error, time::Duration}; + use futures::prelude::*; use libp2p::{noise, ping, swarm::SwarmEvent, tcp, yamux, Multiaddr}; -use std::{error::Error, time::Duration}; use tracing_subscriber::EnvFilter; #[tokio::main] diff --git a/examples/relay-server/Cargo.toml b/examples/relay-server/Cargo.toml index fe18106e60c..3bdaf89b04f 100644 --- a/examples/relay-server/Cargo.toml +++ b/examples/relay-server/Cargo.toml @@ -9,13 +9,11 @@ license = "MIT" release = false [dependencies] -clap = { version = "4.4.10", features = ["derive"] } -async-std = { version = "1.12", features = ["attributes"] } -async-trait = "0.1" -futures = "0.3.2" -libp2p = { path = "../../libp2p", features = [ "async-std", "noise", "macros", "ping", "tcp", "identify", "yamux", "relay", "quic"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +clap = { version = "4.5.6", features = ["derive"] } +tokio = { version = "1.37.0", features = ["full"] } +futures = { workspace = true } +libp2p = { path = "../../libp2p", features = ["tokio", "noise", "macros", "ping", "tcp", "identify", "yamux", "relay", "quic"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true diff --git a/examples/relay-server/README.md b/examples/relay-server/README.md index ae2ab3fdfd8..cd8f0340a5a 100644 --- a/examples/relay-server/README.md +++ b/examples/relay-server/README.md @@ -16,7 +16,7 @@ To run the example, follow these steps: Replace `` with a seed value used to generate a deterministic peer ID for the relay node. 2. The relay node will start listening for incoming connections. -It will print the listening address once it is ready. + It will print the listening address once it is ready. 3. Connect other **libp2p** nodes to the relay node by specifying the relay's listening address as one of the bootstrap nodes in their configuration. diff --git a/examples/relay-server/src/main.rs b/examples/relay-server/src/main.rs index bf5817454f8..b7868418fb0 100644 --- a/examples/relay-server/src/main.rs +++ b/examples/relay-server/src/main.rs @@ -21,21 +21,23 @@ #![doc = include_str!("../README.md")] +use std::{ + error::Error, + net::{Ipv4Addr, Ipv6Addr}, +}; + use clap::Parser; -use futures::executor::block_on; -use futures::stream::StreamExt; +use futures::StreamExt; use libp2p::{ - core::multiaddr::Protocol, - core::Multiaddr, + core::{multiaddr::Protocol, Multiaddr}, identify, identity, noise, ping, relay, swarm::{NetworkBehaviour, SwarmEvent}, tcp, yamux, }; -use std::error::Error; -use std::net::{Ipv4Addr, Ipv6Addr}; use tracing_subscriber::EnvFilter; -fn main() -> Result<(), Box> { +#[tokio::main] +async fn main() -> Result<(), Box> { let _ = tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .try_init(); @@ -46,7 +48,7 @@ fn main() -> Result<(), Box> { let local_key: identity::Keypair = generate_ed25519(opt.secret_key_seed); let mut swarm = libp2p::SwarmBuilder::with_existing_identity(local_key) - .with_async_std() + .with_tokio() .with_tcp( tcp::Config::default(), noise::Config::new, @@ -81,27 +83,25 @@ fn main() -> Result<(), Box> { .with(Protocol::QuicV1); swarm.listen_on(listen_addr_quic)?; - block_on(async { - loop { - match swarm.next().await.expect("Infinite Stream.") { - SwarmEvent::Behaviour(event) => { - if let BehaviourEvent::Identify(identify::Event::Received { - info: identify::Info { observed_addr, .. }, - .. - }) = &event - { - swarm.add_external_address(observed_addr.clone()); - } - - println!("{event:?}") - } - SwarmEvent::NewListenAddr { address, .. } => { - println!("Listening on {address:?}"); + loop { + match swarm.next().await.expect("Infinite Stream.") { + SwarmEvent::Behaviour(event) => { + if let BehaviourEvent::Identify(identify::Event::Received { + info: identify::Info { observed_addr, .. }, + .. + }) = &event + { + swarm.add_external_address(observed_addr.clone()); } - _ => {} + + println!("{event:?}") + } + SwarmEvent::NewListenAddr { address, .. } => { + println!("Listening on {address:?}"); } + _ => {} } - }) + } } #[derive(NetworkBehaviour)] diff --git a/examples/rendezvous/Cargo.toml b/examples/rendezvous/Cargo.toml index fbb2ed44a03..4eea38616f4 100644 --- a/examples/rendezvous/Cargo.toml +++ b/examples/rendezvous/Cargo.toml @@ -9,13 +9,11 @@ license = "MIT" release = false [dependencies] -async-std = { version = "1.12", features = ["attributes"] } -async-trait = "0.1" -futures = "0.3.29" -libp2p = { path = "../../libp2p", features = [ "async-std", "identify", "macros", "noise", "ping", "rendezvous", "tcp", "tokio", "yamux"] } -tokio = { version = "1.34", features = ["rt-multi-thread", "macros", "time"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +futures = { workspace = true } +libp2p = { path = "../../libp2p", features = ["identify", "macros", "noise", "ping", "rendezvous", "tcp", "tokio", "yamux"] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros", "time"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true diff --git a/examples/rendezvous/src/bin/rzv-discover.rs b/examples/rendezvous/src/bin/rzv-discover.rs index edd3d10a0ce..bdf9aeafdab 100644 --- a/examples/rendezvous/src/bin/rzv-discover.rs +++ b/examples/rendezvous/src/bin/rzv-discover.rs @@ -18,6 +18,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::{error::Error, time::Duration}; + use futures::StreamExt; use libp2p::{ multiaddr::Protocol, @@ -25,8 +27,6 @@ use libp2p::{ swarm::{NetworkBehaviour, SwarmEvent}, tcp, yamux, Multiaddr, }; -use std::error::Error; -use std::time::Duration; use tracing_subscriber::EnvFilter; const NAMESPACE: &str = "rendezvous"; @@ -53,7 +53,6 @@ async fn main() -> Result<(), Box> { rendezvous: rendezvous::client::Behaviour::new(key.clone()), ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(1))), })? - .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(5))) .build(); swarm.dial(rendezvous_point_address.clone()).unwrap(); diff --git a/examples/rendezvous/src/bin/rzv-identify.rs b/examples/rendezvous/src/bin/rzv-identify.rs index 1d545592829..00e94627292 100644 --- a/examples/rendezvous/src/bin/rzv-identify.rs +++ b/examples/rendezvous/src/bin/rzv-identify.rs @@ -18,13 +18,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::time::Duration; + use futures::StreamExt; use libp2p::{ identify, noise, ping, rendezvous, swarm::{NetworkBehaviour, SwarmEvent}, tcp, yamux, Multiaddr, }; -use std::time::Duration; use tracing_subscriber::EnvFilter; #[tokio::main] @@ -55,7 +56,6 @@ async fn main() { ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(1))), }) .unwrap() - .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(5))) .build(); let _ = swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()); @@ -76,8 +76,12 @@ async fn main() { } // once `/identify` did its job, we know our external address and can register SwarmEvent::Behaviour(MyBehaviourEvent::Identify(identify::Event::Received { + info, .. })) => { + // Register our external address. Needs to be done explicitly + // for this case, as it's a local address. + swarm.add_external_address(info.observed_addr); if let Err(error) = swarm.behaviour_mut().rendezvous.register( rendezvous::Namespace::from_static("rendezvous"), rendezvous_point, diff --git a/examples/rendezvous/src/bin/rzv-register.rs b/examples/rendezvous/src/bin/rzv-register.rs index bd848238d4a..f70eda5d55e 100644 --- a/examples/rendezvous/src/bin/rzv-register.rs +++ b/examples/rendezvous/src/bin/rzv-register.rs @@ -18,13 +18,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::time::Duration; + use futures::StreamExt; use libp2p::{ noise, ping, rendezvous, swarm::{NetworkBehaviour, SwarmEvent}, tcp, yamux, Multiaddr, }; -use std::time::Duration; use tracing_subscriber::EnvFilter; #[tokio::main] @@ -51,11 +52,10 @@ async fn main() { ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(1))), }) .unwrap() - .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(5))) .build(); - // In production the external address should be the publicly facing IP address of the rendezvous point. - // This address is recorded in the registration entry by the rendezvous point. + // In production the external address should be the publicly facing IP address of the rendezvous + // point. This address is recorded in the registration entry by the rendezvous point. let external_address = "/ip4/127.0.0.1/tcp/0".parse::().unwrap(); swarm.add_external_address(external_address); diff --git a/examples/rendezvous/src/main.rs b/examples/rendezvous/src/main.rs index a15bc1ca2d3..a345d0faed9 100644 --- a/examples/rendezvous/src/main.rs +++ b/examples/rendezvous/src/main.rs @@ -20,14 +20,14 @@ #![doc = include_str!("../README.md")] +use std::{error::Error, time::Duration}; + use futures::StreamExt; use libp2p::{ identify, noise, ping, rendezvous, swarm::{NetworkBehaviour, SwarmEvent}, tcp, yamux, }; -use std::error::Error; -use std::time::Duration; use tracing_subscriber::EnvFilter; #[tokio::main] @@ -55,7 +55,6 @@ async fn main() -> Result<(), Box> { rendezvous: rendezvous::server::Behaviour::new(rendezvous::server::Config::default()), ping: ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(1))), })? - .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(5))) .build(); let _ = swarm.listen_on("/ip4/0.0.0.0/tcp/62649".parse().unwrap()); diff --git a/examples/stream/Cargo.toml b/examples/stream/Cargo.toml new file mode 100644 index 00000000000..ba31d4d9e13 --- /dev/null +++ b/examples/stream/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "stream-example" +version = "0.1.0" +edition = "2021" +publish = false +license = "MIT" + +[package.metadata.release] +release = false + +[dependencies] +anyhow = "1" +futures = { workspace = true } +libp2p = { path = "../../libp2p", features = [ "tokio", "quic"] } +libp2p-stream = { path = "../../protocols/stream", version = "0.2.0-alpha" } +rand = "0.8" +tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } + +[lints] +workspace = true diff --git a/examples/stream/README.md b/examples/stream/README.md new file mode 100644 index 00000000000..3b0325519f7 --- /dev/null +++ b/examples/stream/README.md @@ -0,0 +1,35 @@ +## Description + +This example shows the usage of the `stream::Behaviour`. +As a counter-part to the `request_response::Behaviour`, the `stream::Behaviour` allows users to write stream-oriented protocols whilst having minimal interaction with the `Swarm`. + +In this showcase, we implement an echo protocol: All incoming data is echoed back to the dialer, until the stream is closed. + +## Usage + +To run the example, follow these steps: + +1. Start an instance of the example in one terminal: + + ```sh + cargo run --bin stream-example + ``` + + Observe printed listen address. + +2. Start another instance in a new terminal, providing the listen address of the first one. + + ```sh + cargo run --bin stream-example --

+ ``` + +3. Both terminals should now continuously print messages. + +## Conclusion + +The `stream::Behaviour` is an "escape-hatch" from the way typical rust-libp2p protocols are written. +It is suitable for several scenarios including: + +- prototyping of new protocols +- experimentation with rust-libp2p +- integration in `async/await`-heavy applications \ No newline at end of file diff --git a/examples/stream/src/main.rs b/examples/stream/src/main.rs new file mode 100644 index 00000000000..71d2d2fcc76 --- /dev/null +++ b/examples/stream/src/main.rs @@ -0,0 +1,157 @@ +use std::{io, time::Duration}; + +use anyhow::{Context, Result}; +use futures::{AsyncReadExt, AsyncWriteExt, StreamExt}; +use libp2p::{multiaddr::Protocol, Multiaddr, PeerId, Stream, StreamProtocol}; +use libp2p_stream as stream; +use rand::RngCore; +use tracing::level_filters::LevelFilter; +use tracing_subscriber::EnvFilter; + +const ECHO_PROTOCOL: StreamProtocol = StreamProtocol::new("/echo"); + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt() + .with_env_filter( + EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env()?, + ) + .init(); + + let maybe_address = std::env::args() + .nth(1) + .map(|arg| arg.parse::()) + .transpose() + .context("Failed to parse argument as `Multiaddr`")?; + + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_quic() + .with_behaviour(|_| stream::Behaviour::new())? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(10))) + .build(); + + swarm.listen_on("/ip4/127.0.0.1/udp/0/quic-v1".parse()?)?; + + let mut incoming_streams = swarm + .behaviour() + .new_control() + .accept(ECHO_PROTOCOL) + .unwrap(); + + // Deal with incoming streams. + // Spawning a dedicated task is just one way of doing this. + // libp2p doesn't care how you handle incoming streams but you _must_ handle them somehow. + // To mitigate DoS attacks, libp2p will internally drop incoming streams if your application + // cannot keep up processing them. + tokio::spawn(async move { + // This loop handles incoming streams _sequentially_ but that doesn't have to be the case. + // You can also spawn a dedicated task per stream if you want to. + // Be aware that this breaks backpressure though as spawning new tasks is equivalent to an + // unbounded buffer. Each task needs memory meaning an aggressive remote peer may + // force you OOM this way. + + while let Some((peer, stream)) = incoming_streams.next().await { + match echo(stream).await { + Ok(n) => { + tracing::info!(%peer, "Echoed {n} bytes!"); + } + Err(e) => { + tracing::warn!(%peer, "Echo failed: {e}"); + continue; + } + }; + } + }); + + // In this demo application, the dialing peer initiates the protocol. + if let Some(address) = maybe_address { + let Some(Protocol::P2p(peer_id)) = address.iter().last() else { + anyhow::bail!("Provided address does not end in `/p2p`"); + }; + + swarm.dial(address)?; + + tokio::spawn(connection_handler(peer_id, swarm.behaviour().new_control())); + } + + // Poll the swarm to make progress. + loop { + let event = swarm.next().await.expect("never terminates"); + + match event { + libp2p::swarm::SwarmEvent::NewListenAddr { address, .. } => { + let listen_address = address.with_p2p(*swarm.local_peer_id()).unwrap(); + tracing::info!(%listen_address); + } + event => tracing::trace!(?event), + } + } +} + +/// A very simple, `async fn`-based connection handler for our custom echo protocol. +async fn connection_handler(peer: PeerId, mut control: stream::Control) { + loop { + tokio::time::sleep(Duration::from_secs(1)).await; // Wait a second between echos. + + let stream = match control.open_stream(peer, ECHO_PROTOCOL).await { + Ok(stream) => stream, + Err(error @ stream::OpenStreamError::UnsupportedProtocol(_)) => { + tracing::info!(%peer, %error); + return; + } + Err(error) => { + // Other errors may be temporary. + // In production, something like an exponential backoff / circuit-breaker may be + // more appropriate. + tracing::debug!(%peer, %error); + continue; + } + }; + + if let Err(e) = send(stream).await { + tracing::warn!(%peer, "Echo protocol failed: {e}"); + continue; + } + + tracing::info!(%peer, "Echo complete!") + } +} + +async fn echo(mut stream: Stream) -> io::Result { + let mut total = 0; + + let mut buf = [0u8; 100]; + + loop { + let read = stream.read(&mut buf).await?; + if read == 0 { + return Ok(total); + } + + total += read; + stream.write_all(&buf[..read]).await?; + } +} + +async fn send(mut stream: Stream) -> io::Result<()> { + let num_bytes = rand::random::() % 1000; + + let mut bytes = vec![0; num_bytes]; + rand::thread_rng().fill_bytes(&mut bytes); + + stream.write_all(&bytes).await?; + + let mut buf = vec![0; num_bytes]; + stream.read_exact(&mut buf).await?; + + if bytes != buf { + return Err(io::Error::new(io::ErrorKind::Other, "incorrect echo")); + } + + stream.close().await?; + + Ok(()) +} diff --git a/examples/upnp/Cargo.toml b/examples/upnp/Cargo.toml index 940f3dff65f..ae292e0a48d 100644 --- a/examples/upnp/Cargo.toml +++ b/examples/upnp/Cargo.toml @@ -9,10 +9,10 @@ license = "MIT" release = false [dependencies] -tokio = { version = "1", features = ["rt-multi-thread", "macros"] } -futures = "0.3.29" +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +futures = { workspace = true } libp2p = { path = "../../libp2p", features = ["tokio", "dns", "macros", "noise", "ping", "tcp", "yamux", "upnp"] } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true diff --git a/examples/upnp/src/main.rs b/examples/upnp/src/main.rs index fd0764990d1..19de8d773ae 100644 --- a/examples/upnp/src/main.rs +++ b/examples/upnp/src/main.rs @@ -20,9 +20,10 @@ #![doc = include_str!("../README.md")] +use std::error::Error; + use futures::prelude::*; use libp2p::{noise, swarm::SwarmEvent, upnp, yamux, Multiaddr}; -use std::error::Error; use tracing_subscriber::EnvFilter; #[tokio::main] diff --git a/hole-punching-tests/Cargo.toml b/hole-punching-tests/Cargo.toml index a6f1bd6571c..c4f36d2a990 100644 --- a/hole-punching-tests/Cargo.toml +++ b/hole-punching-tests/Cargo.toml @@ -7,12 +7,12 @@ license = "MIT" [dependencies] anyhow = "1" -env_logger = "0.10.1" -futures = "0.3.29" +env_logger = { workspace = true } +futures = { workspace = true } libp2p = { path = "../libp2p", features = ["tokio", "dcutr", "identify", "macros", "noise", "ping", "relay", "tcp", "yamux", "quic"] } -tracing = "0.1.37" -redis = { version = "0.23.0", default-features = false, features = ["tokio-comp"] } -tokio = { version = "1.34.0", features = ["full"] } -serde = { version = "1.0.193", features = ["derive"] } -serde_json = "1.0.108" -either = "1.9.0" +tracing = { workspace = true } +redis = { version = "0.24.0", default-features = false, features = ["tokio-comp"] } +tokio = { workspace = true, features = ["full"] } +serde = { version = "1.0.203", features = ["derive"] } +serde_json = "1.0.117" +either = "1.12.0" diff --git a/hole-punching-tests/Dockerfile b/hole-punching-tests/Dockerfile index 864f058799e..403cc301fc6 100644 --- a/hole-punching-tests/Dockerfile +++ b/hole-punching-tests/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1.5-labs -FROM rust:1.73.0 as builder +FROM rust:1.81.0 as builder # Run with access to the target cache to speed up builds WORKDIR /workspace diff --git a/hole-punching-tests/src/main.rs b/hole-punching-tests/src/main.rs index 4f81cd65480..bc5a1bae4f5 100644 --- a/hole-punching-tests/src/main.rs +++ b/hole-punching-tests/src/main.rs @@ -18,24 +18,27 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::{ + collections::HashMap, + fmt, io, + net::{IpAddr, Ipv4Addr}, + str::FromStr, + time::Duration, +}; + use anyhow::{Context, Result}; use either::Either; use futures::stream::StreamExt; -use libp2p::core::transport::ListenerId; -use libp2p::swarm::dial_opts::DialOpts; -use libp2p::swarm::ConnectionId; use libp2p::{ - core::multiaddr::{Multiaddr, Protocol}, + core::{ + multiaddr::{Multiaddr, Protocol}, + transport::ListenerId, + }, dcutr, identify, noise, ping, relay, - swarm::{NetworkBehaviour, SwarmEvent}, + swarm::{dial_opts::DialOpts, ConnectionId, NetworkBehaviour, SwarmEvent}, tcp, yamux, Swarm, }; use redis::AsyncCommands; -use std::collections::HashMap; -use std::net::{IpAddr, Ipv4Addr}; -use std::str::FromStr; -use std::time::Duration; -use std::{fmt, io}; /// The redis key we push the relay's TCP listen address to. const RELAY_TCP_ADDRESS: &str = "RELAY_TCP_ADDRESS"; @@ -64,7 +67,7 @@ async fn main() -> Result<()> { let mut swarm = libp2p::SwarmBuilder::with_new_identity() .with_tokio() .with_tcp( - tcp::Config::new().port_reuse(true).nodelay(true), + tcp::Config::new().nodelay(true), noise::Config::new, yamux::Config::default, )? @@ -294,9 +297,7 @@ impl RedisClient { tracing::debug!("Pushing {key}={value} to redis"); - self.inner.rpush(key, value).await?; - - Ok(()) + self.inner.rpush(key, value).await.map_err(Into::into) } async fn pop(&mut self, key: &str) -> Result @@ -308,7 +309,7 @@ impl RedisClient { let value = self .inner - .blpop::<_, HashMap>(key, 0) + .blpop::<_, HashMap>(key, 0.0) .await? .remove(key) .with_context(|| format!("Failed to get value for {key} from redis"))? diff --git a/identity/CHANGELOG.md b/identity/CHANGELOG.md index 004943ce195..98f3e5c5636 100644 --- a/identity/CHANGELOG.md +++ b/identity/CHANGELOG.md @@ -1,6 +1,16 @@ +## 0.2.10 + +- Deprecate `void` crate. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). + +## 0.2.9 + +- Add `rand` feature gate to ecdsa methods requiring a random number generator. + See [PR 5212](https://github.com/libp2p/rust-libp2p/pull/5212). + ## 0.2.8 -- Bump `ring` to `0.17.5. +- Bump `ring` to `0.17.5`. See [PR 4779](https://github.com/libp2p/rust-libp2p/pull/4779). ## 0.2.7 diff --git a/identity/Cargo.toml b/identity/Cargo.toml index e7aa15fef07..b13229c5826 100644 --- a/identity/Cargo.toml +++ b/identity/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-identity" -version = "0.2.8" +version = "0.2.10" edition = "2021" description = "Data structures and algorithms for identifying peers in libp2p." rust-version = "1.73.0" # MUST NOT inherit from workspace because we don't want to publish breaking changes to `libp2p-identity`. @@ -13,28 +13,27 @@ categories = ["cryptography"] [dependencies] asn1_der = { version = "0.7.6", optional = true } -bs58 = { version = "0.5.0", optional = true } +bs58 = { version = "0.5.1", optional = true } ed25519-dalek = { version = "2.1", optional = true } -hkdf = { version = "0.12.3", optional = true } +hkdf = { version = "0.12.4", optional = true } libsecp256k1 = { version = "0.7.0", optional = true } -tracing = "0.1.37" +tracing = { workspace = true } multihash = { version = "0.19.1", optional = true } -p256 = { version = "0.13", default-features = false, features = [ "ecdsa", "std", "pem"], optional = true } +p256 = { version = "0.13", default-features = false, features = ["ecdsa", "std", "pem"], optional = true } quick-protobuf = "0.8.1" rand = { version = "0.8", optional = true } sec1 = { version = "0.7", default-features = false, optional = true } serde = { version = "1", optional = true, features = ["derive"] } sha2 = { version = "0.10.8", optional = true } -thiserror = { version = "1.0", optional = true } -void = { version = "1.0", optional = true } -zeroize = { version = "1.7", optional = true } +thiserror = { workspace = true, optional = true } +zeroize = { version = "1.8", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -ring = { version = "0.17.5", features = [ "alloc", "std"], default-features = false, optional = true } +ring = { workspace = true, features = ["alloc", "std"], optional = true } [features] secp256k1 = ["dep:libsecp256k1", "dep:asn1_der", "dep:sha2", "dep:hkdf", "dep:zeroize"] -ecdsa = ["dep:p256", "dep:void", "dep:zeroize", "dep:sec1", "dep:sha2", "dep:hkdf"] +ecdsa = ["dep:p256", "dep:zeroize", "dep:sec1", "dep:sha2", "dep:hkdf"] rsa = ["dep:ring", "dep:asn1_der", "dep:rand", "dep:zeroize"] ed25519 = ["dep:ed25519-dalek", "dep:zeroize", "dep:sha2", "dep:hkdf"] peerid = ["dep:multihash", "dep:bs58", "dep:thiserror", "dep:sha2", "dep:hkdf"] @@ -42,9 +41,8 @@ rand = ["dep:rand", "ed25519-dalek?/rand_core"] [dev-dependencies] quickcheck = { workspace = true } -base64 = "0.21.5" serde_json = "1.0" -rmp-serde = "1.1" +rmp-serde = "1.3" criterion = "0.5" hex-literal = "0.4.1" @@ -56,8 +54,6 @@ harness = false # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/identity/src/ecdsa.rs b/identity/src/ecdsa.rs index 2f1a286d46d..11cdaced795 100644 --- a/identity/src/ecdsa.rs +++ b/identity/src/ecdsa.rs @@ -20,10 +20,9 @@ //! ECDSA keys with secp256r1 curve support. -use super::error::DecodingError; -use core::cmp; -use core::fmt; -use core::hash; +use core::{cmp, fmt, hash}; +use std::convert::Infallible; + use p256::{ ecdsa::{ signature::{Signer, Verifier}, @@ -32,9 +31,10 @@ use p256::{ EncodedPoint, }; use sec1::{DecodeEcPrivateKey, EncodeEcPrivateKey}; -use void::Void; use zeroize::Zeroize; +use super::error::DecodingError; + /// An ECDSA keypair generated using `secp256r1` curve. #[derive(Clone)] pub struct Keypair { @@ -94,6 +94,7 @@ pub struct SecretKey(SigningKey); impl SecretKey { /// Generate a new random ECDSA secret key. + #[cfg(feature = "rand")] pub fn generate() -> SecretKey { SecretKey(SigningKey::random(&mut rand::thread_rng())) } @@ -157,7 +158,8 @@ impl PublicKey { self.0.verify(msg, &sig).is_ok() } - /// Try to parse a public key from a byte buffer containing raw components of a key with or without compression. + /// Try to parse a public key from a byte buffer containing raw + /// components of a key with or without compression. pub fn try_from_bytes(k: &[u8]) -> Result { let enc_pt = EncodedPoint::from_bytes(k) .map_err(|e| DecodingError::failed_to_parse("ecdsa p256 encoded point", e))?; @@ -167,7 +169,8 @@ impl PublicKey { .map(PublicKey) } - /// Convert a public key into a byte buffer containing raw components of the key without compression. + /// Convert a public key into a byte buffer containing + /// raw components of the key without compression. pub fn to_bytes(&self) -> Vec { self.0.to_encoded_point(false).as_bytes().to_owned() } @@ -181,7 +184,10 @@ impl PublicKey { /// Try to decode a public key from a DER encoded byte buffer as defined by SEC1 standard. pub fn try_decode_der(k: &[u8]) -> Result { let buf = Self::del_asn1_header(k).ok_or_else(|| { - DecodingError::failed_to_parse::("ASN.1-encoded ecdsa p256 public key", None) + DecodingError::failed_to_parse::( + "ASN.1-encoded ecdsa p256 public key", + None, + ) })?; Self::try_from_bytes(buf) } diff --git a/identity/src/ed25519.rs b/identity/src/ed25519.rs index 529a4dddea1..5a1a53dd4af 100644 --- a/identity/src/ed25519.rs +++ b/identity/src/ed25519.rs @@ -20,14 +20,13 @@ //! Ed25519 keys. -use super::error::DecodingError; -use core::cmp; -use core::fmt; -use core::hash; +use core::{cmp, fmt, hash}; + use ed25519_dalek::{self as ed25519, Signer as _, Verifier as _}; -use std::convert::TryFrom; use zeroize::Zeroize; +use super::error::DecodingError; + /// An Ed25519 keypair. #[derive(Clone)] pub struct Keypair(ed25519::SigningKey); @@ -153,7 +152,8 @@ impl PublicKey { self.0.to_bytes() } - /// Try to parse a public key from a byte array containing the actual key as produced by `to_bytes`. + /// Try to parse a public key from a byte array containing + /// the actual key as produced by `to_bytes`. pub fn try_from_bytes(k: &[u8]) -> Result { let k = <[u8; 32]>::try_from(k) .map_err(|e| DecodingError::failed_to_parse("Ed25519 public key", e))?; @@ -207,9 +207,10 @@ impl SecretKey { #[cfg(test)] mod tests { - use super::*; use quickcheck::*; + use super::*; + fn eq_keypairs(kp1: &Keypair, kp2: &Keypair) -> bool { kp1.public() == kp2.public() && kp1.0.to_bytes() == kp2.0.to_bytes() } diff --git a/identity/src/error.rs b/identity/src/error.rs index 71cd78fe1ea..6e8c4d02caa 100644 --- a/identity/src/error.rs +++ b/identity/src/error.rs @@ -20,8 +20,7 @@ //! Errors during identity key operations. -use std::error::Error; -use std::fmt; +use std::{error::Error, fmt}; use crate::KeyType; diff --git a/identity/src/keypair.rs b/identity/src/keypair.rs index bdfb68c0091..a1bbba00fa9 100644 --- a/identity/src/keypair.rs +++ b/identity/src/keypair.rs @@ -24,42 +24,40 @@ feature = "ed25519", feature = "rsa" ))] -use crate::error::OtherVariantError; -use crate::error::{DecodingError, SigningError}; +use quick_protobuf::{BytesReader, Writer}; + +#[cfg(feature = "ecdsa")] +use crate::ecdsa; #[cfg(any( feature = "ecdsa", feature = "secp256k1", feature = "ed25519", feature = "rsa" ))] -use crate::proto; +#[cfg(feature = "ed25519")] +use crate::ed25519; #[cfg(any( feature = "ecdsa", feature = "secp256k1", feature = "ed25519", feature = "rsa" ))] -use quick_protobuf::{BytesReader, Writer}; +use crate::error::OtherVariantError; #[cfg(any( feature = "ecdsa", feature = "secp256k1", feature = "ed25519", feature = "rsa" ))] -use std::convert::TryFrom; - -#[cfg(feature = "ed25519")] -use crate::ed25519; - +use crate::proto; #[cfg(all(feature = "rsa", not(target_arch = "wasm32")))] use crate::rsa; - #[cfg(feature = "secp256k1")] use crate::secp256k1; - -#[cfg(feature = "ecdsa")] -use crate::ecdsa; -use crate::KeyType; +use crate::{ + error::{DecodingError, SigningError}, + KeyType, +}; /// Identity keypair of a node. /// @@ -77,7 +75,6 @@ use crate::KeyType; /// let mut bytes = std::fs::read("private.pk8").unwrap(); /// let keypair = Keypair::rsa_from_pkcs8(&mut bytes); /// ``` -/// #[derive(Debug, Clone)] pub struct Keypair { keypair: KeyPairInner, @@ -343,7 +340,8 @@ impl Keypair { } } - /// Deterministically derive a new secret from this [`Keypair`], taking into account the provided domain. + /// Deterministically derive a new secret from this [`Keypair`], + /// taking into account the provided domain. /// /// This works for all key types except RSA where it returns `None`. /// @@ -354,10 +352,11 @@ impl Keypair { /// # use libp2p_identity as identity; /// let key = identity::Keypair::generate_ed25519(); /// - /// let new_key = key.derive_secret(b"my encryption key").expect("can derive secret for ed25519"); + /// let new_key = key + /// .derive_secret(b"my encryption key") + /// .expect("can derive secret for ed25519"); /// # } /// ``` - /// #[cfg(any( feature = "ecdsa", feature = "secp256k1", @@ -906,9 +905,10 @@ mod tests { #[test] fn public_key_implements_hash() { - use crate::PublicKey; use std::hash::Hash; + use crate::PublicKey; + fn assert_implements_hash() {} assert_implements_hash::(); @@ -916,9 +916,10 @@ mod tests { #[test] fn public_key_implements_ord() { - use crate::PublicKey; use std::cmp::Ord; + use crate::PublicKey; + fn assert_implements_ord() {} assert_implements_ord::(); diff --git a/identity/src/lib.rs b/identity/src/lib.rs index c78e68d1652..4f4313e8f17 100644 --- a/identity/src/lib.rs +++ b/identity/src/lib.rs @@ -34,7 +34,7 @@ //! All key types have functions to enable conversion to/from their binary representations. #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] - +#![allow(unreachable_pub)] #[cfg(any( feature = "ecdsa", feature = "secp256k1", @@ -42,7 +42,6 @@ feature = "rsa" ))] mod proto { - #![allow(unreachable_pub)] include!("generated/mod.rs"); pub(crate) use self::keys_proto::*; } diff --git a/identity/src/peer_id.rs b/identity/src/peer_id.rs index 1d85fe66ffa..7f6d1f44eab 100644 --- a/identity/src/peer_id.rs +++ b/identity/src/peer_id.rs @@ -18,17 +18,19 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::{fmt, str::FromStr}; + #[cfg(feature = "rand")] use rand::Rng; use sha2::Digest as _; -use std::{convert::TryFrom, fmt, str::FromStr}; use thiserror::Error; /// Local type-alias for multihash. /// /// Must be big enough to accommodate for `MAX_INLINE_KEY_LENGTH`. /// 64 satisfies that and can hold 512 bit hashes which is what the ecosystem typically uses. -/// Given that this appears in our type-signature, using a "common" number here makes us more compatible. +/// Given that this appears in our type-signature, +/// using a "common" number here makes us more compatible. type Multihash = multihash::Multihash<64>; #[cfg(feature = "serde")] @@ -191,7 +193,7 @@ impl<'de> Deserialize<'de> for PeerId { struct PeerIdVisitor; - impl<'de> Visitor<'de> for PeerIdVisitor { + impl Visitor<'_> for PeerIdVisitor { type Value = PeerId; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/identity/src/rsa.rs b/identity/src/rsa.rs index cbfe3c1b919..b14d8c66d86 100644 --- a/identity/src/rsa.rs +++ b/identity/src/rsa.rs @@ -20,15 +20,20 @@ //! RSA keys. -use super::error::*; -use asn1_der::typed::{DerDecodable, DerEncodable, DerTypeView, Sequence}; -use asn1_der::{Asn1DerError, Asn1DerErrorVariant, DerObject, Sink, VecBacking}; -use ring::rand::SystemRandom; -use ring::signature::KeyPair; -use ring::signature::{self, RsaKeyPair, RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_SHA256}; use std::{fmt, sync::Arc}; + +use asn1_der::{ + typed::{DerDecodable, DerEncodable, DerTypeView, Sequence}, + Asn1DerError, Asn1DerErrorVariant, DerObject, Sink, VecBacking, +}; +use ring::{ + rand::SystemRandom, + signature::{self, KeyPair, RsaKeyPair, RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_SHA256}, +}; use zeroize::Zeroize; +use super::error::*; + /// An RSA keypair. #[derive(Clone)] pub struct Keypair(Arc); @@ -149,7 +154,7 @@ struct Asn1RawOid<'a> { object: DerObject<'a>, } -impl<'a> Asn1RawOid<'a> { +impl Asn1RawOid<'_> { /// The underlying OID as byte literal. pub(crate) fn oid(&self) -> &[u8] { self.object.value() @@ -169,7 +174,7 @@ impl<'a> DerTypeView<'a> for Asn1RawOid<'a> { } } -impl<'a> DerEncodable for Asn1RawOid<'a> { +impl DerEncodable for Asn1RawOid<'_> { fn encode(&self, sink: &mut S) -> Result<(), Asn1DerError> { self.object.encode(sink) } @@ -315,9 +320,10 @@ impl DerDecodable<'_> for Asn1SubjectPublicKeyInfo { #[cfg(test)] mod tests { - use super::*; use quickcheck::*; + use super::*; + const KEY1: &[u8] = include_bytes!("test/rsa-2048.pk8"); const KEY2: &[u8] = include_bytes!("test/rsa-3072.pk8"); const KEY3: &[u8] = include_bytes!("test/rsa-4096.pk8"); diff --git a/identity/src/secp256k1.rs b/identity/src/secp256k1.rs index 5e1fda2933b..e884cf1385d 100644 --- a/identity/src/secp256k1.rs +++ b/identity/src/secp256k1.rs @@ -20,15 +20,15 @@ //! Secp256k1 keys. -use super::error::DecodingError; +use core::{cmp, fmt, hash}; + use asn1_der::typed::{DerDecodable, Sequence}; -use core::cmp; -use core::fmt; -use core::hash; use libsecp256k1::{Message, Signature}; use sha2::{Digest as ShaDigestTrait, Sha256}; use zeroize::Zeroize; +use super::error::DecodingError; + /// A Secp256k1 keypair. #[derive(Clone)] pub struct Keypair { @@ -214,7 +214,7 @@ impl PublicKey { self.0.serialize() } - /// Decode a public key from a byte slice in the the format produced + /// Decode a public key from a byte slice in the format produced /// by `encode`. pub fn try_from_bytes(k: &[u8]) -> Result { libsecp256k1::PublicKey::parse_slice(k, Some(libsecp256k1::PublicKeyFormat::Compressed)) diff --git a/interop-tests/Cargo.toml b/interop-tests/Cargo.toml index eba65b782e7..8f12275668d 100644 --- a/interop-tests/Cargo.toml +++ b/interop-tests/Cargo.toml @@ -13,31 +13,30 @@ crate-type = ["cdylib", "rlib"] [dependencies] anyhow = "1" -either = "1.9.0" -futures = "0.3.29" +futures = { workspace = true } rand = "0.8.5" serde = { version = "1", features = ["derive"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -axum = "0.6" +axum = "0.7" libp2p = { path = "../libp2p", features = [ "ping", "noise", "tls", "rsa", "macros", "websocket", "tokio", "yamux", "tcp", "dns", "identify", "quic"] } libp2p-mplex = { path = "../muxers/mplex" } libp2p-noise = { workspace = true } libp2p-tls = { workspace = true } libp2p-webrtc = { workspace = true, features = ["tokio"] } mime_guess = "2.0" -redis = { version = "0.23.3", default-features = false, features = [ +redis = { version = "0.24.0", default-features = false, features = [ "tokio-comp", ] } -rust-embed = "8.0" +rust-embed = "8.4" serde_json = "1" -thirtyfour = "=0.32.0-rc.8" # https://github.com/stevepryde/thirtyfour/issues/169 -tokio = { version = "1.34.0", features = ["full"] } -tower-http = { version = "0.4", features = ["cors", "fs", "trace"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +thirtyfour = "=0.32.0" # https://github.com/stevepryde/thirtyfour/issues/169 +tokio = { workspace = true, features = ["full"] } +tower-http = { version = "0.5", features = ["cors", "fs", "trace"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [target.'cfg(target_arch = "wasm32")'.dependencies] libp2p = { path = "../libp2p", features = [ "ping", "macros", "webtransport-websys", "wasm-bindgen", "identify", "websocket-websys", "yamux", "noise"] } @@ -46,10 +45,10 @@ libp2p-webrtc-websys = { workspace = true } wasm-bindgen = { version = "0.2" } wasm-bindgen-futures = { version = "0.4" } wasm-logger = { version = "0.2.0" } -instant = "0.1.12" -reqwest = { version = "0.11", features = ["json"] } +web-time = { workspace = true } +reqwest = { version = "0.12", features = ["json"] } console_error_panic_hook = { version = "0.1.7" } -futures-timer = "3.0.2" +futures-timer = "3.0.3" [lints] workspace = true diff --git a/interop-tests/Dockerfile.chromium b/interop-tests/Dockerfile.chromium index 5ec46e313aa..86edbc5b9d2 100644 --- a/interop-tests/Dockerfile.chromium +++ b/interop-tests/Dockerfile.chromium @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1.5-labs -FROM rust:1.73.0 as chef +FROM rust:1.81 as chef RUN rustup target add wasm32-unknown-unknown RUN wget -q -O- https://github.com/rustwasm/wasm-pack/releases/download/v0.12.1/wasm-pack-v0.12.1-x86_64-unknown-linux-musl.tar.gz | tar -zx -C /usr/local/bin --strip-components 1 --wildcards "wasm-pack-*/wasm-pack" RUN wget -q -O- https://github.com/WebAssembly/binaryen/releases/download/version_115/binaryen-version_115-x86_64-linux.tar.gz | tar -zx -C /usr/local/bin --strip-components 2 --wildcards "binaryen-version_*/bin/wasm-opt" @@ -20,7 +20,7 @@ COPY . . RUN wasm-pack build --target web interop-tests RUN RUSTFLAGS='-C target-feature=+crt-static' cargo build --release --package interop-tests --target x86_64-unknown-linux-gnu --bin wasm_ping -FROM selenium/standalone-chrome:115.0 +FROM selenium/standalone-chrome:125.0 COPY --from=builder /app/target/x86_64-unknown-linux-gnu/release/wasm_ping /usr/local/bin/testplan ENV RUST_BACKTRACE=1 ENTRYPOINT ["testplan"] diff --git a/interop-tests/Dockerfile.native b/interop-tests/Dockerfile.native index 91e6cf8893e..499c73437fc 100644 --- a/interop-tests/Dockerfile.native +++ b/interop-tests/Dockerfile.native @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1.5-labs -FROM lukemathwalker/cargo-chef:0.1.62-rust-1.73.0 as chef +FROM lukemathwalker/cargo-chef:0.1.67-rust-bullseye as chef WORKDIR /app FROM chef AS planner diff --git a/interop-tests/src/arch.rs b/interop-tests/src/arch.rs index 52000f90a86..91fc69dc215 100644 --- a/interop-tests/src/arch.rs +++ b/interop-tests/src/arch.rs @@ -1,7 +1,6 @@ // Native re-exports #[cfg(not(target_arch = "wasm32"))] pub(crate) use native::{build_swarm, init_logger, sleep, Instant, RedisClient}; - // Wasm re-exports #[cfg(target_arch = "wasm32")] pub(crate) use wasm::{build_swarm, init_logger, sleep, Instant, RedisClient}; @@ -11,11 +10,13 @@ pub(crate) mod native { use std::time::Duration; use anyhow::{bail, Context, Result}; - use futures::future::BoxFuture; - use futures::FutureExt; - use libp2p::identity::Keypair; - use libp2p::swarm::{NetworkBehaviour, Swarm}; - use libp2p::{noise, tcp, tls, yamux}; + use futures::{future::BoxFuture, FutureExt}; + use libp2p::{ + identity::Keypair, + noise, + swarm::{NetworkBehaviour, Swarm}, + tcp, tls, yamux, + }; use libp2p_mplex as mplex; use libp2p_webrtc as webrtc; use redis::AsyncCommands; @@ -48,7 +49,6 @@ pub(crate) mod native { .with_tokio() .with_quic() .with_behaviour(behaviour_constructor)? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(), format!("/ip4/{ip}/udp/0/quic-v1"), ), @@ -61,7 +61,6 @@ pub(crate) mod native { mplex::MplexConfig::default, )? .with_behaviour(behaviour_constructor)? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(), format!("/ip4/{ip}/tcp/0"), ), @@ -74,7 +73,6 @@ pub(crate) mod native { yamux::Config::default, )? .with_behaviour(behaviour_constructor)? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(), format!("/ip4/{ip}/tcp/0"), ), @@ -87,7 +85,6 @@ pub(crate) mod native { mplex::MplexConfig::default, )? .with_behaviour(behaviour_constructor)? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(), format!("/ip4/{ip}/tcp/0"), ), @@ -100,7 +97,6 @@ pub(crate) mod native { yamux::Config::default, )? .with_behaviour(behaviour_constructor)? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(), format!("/ip4/{ip}/tcp/0"), ), @@ -110,7 +106,6 @@ pub(crate) mod native { .with_websocket(tls::Config::new, mplex::MplexConfig::default) .await? .with_behaviour(behaviour_constructor)? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(), format!("/ip4/{ip}/tcp/0/ws"), ), @@ -120,7 +115,6 @@ pub(crate) mod native { .with_websocket(tls::Config::new, yamux::Config::default) .await? .with_behaviour(behaviour_constructor)? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(), format!("/ip4/{ip}/tcp/0/ws"), ), @@ -130,7 +124,6 @@ pub(crate) mod native { .with_websocket(noise::Config::new, mplex::MplexConfig::default) .await? .with_behaviour(behaviour_constructor)? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(), format!("/ip4/{ip}/tcp/0/ws"), ), @@ -140,7 +133,6 @@ pub(crate) mod native { .with_websocket(noise::Config::new, yamux::Config::default) .await? .with_behaviour(behaviour_constructor)? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(), format!("/ip4/{ip}/tcp/0/ws"), ), @@ -154,7 +146,6 @@ pub(crate) mod native { )) })? .with_behaviour(behaviour_constructor)? - .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(), format!("/ip4/{ip}/udp/0/webrtc-direct"), ), @@ -174,32 +165,35 @@ pub(crate) mod native { pub(crate) async fn blpop(&self, key: &str, timeout: u64) -> Result> { let mut conn = self.0.get_async_connection().await?; - Ok(conn.blpop(key, timeout as usize).await?) + Ok(conn.blpop(key, timeout as f64).await?) } pub(crate) async fn rpush(&self, key: &str, value: String) -> Result<()> { let mut conn = self.0.get_async_connection().await?; - conn.rpush(key, value).await?; - Ok(()) + conn.rpush(key, value).await.map_err(Into::into) } } } #[cfg(target_arch = "wasm32")] pub(crate) mod wasm { + use std::time::Duration; + use anyhow::{bail, Context, Result}; use futures::future::{BoxFuture, FutureExt}; - use libp2p::core::upgrade::Version; - use libp2p::identity::Keypair; - use libp2p::swarm::{NetworkBehaviour, Swarm}; - use libp2p::{noise, websocket_websys, webtransport_websys, yamux, Transport as _}; + use libp2p::{ + core::upgrade::Version, + identity::Keypair, + noise, + swarm::{NetworkBehaviour, Swarm}, + websocket_websys, webtransport_websys, yamux, Transport as _, + }; use libp2p_mplex as mplex; use libp2p_webrtc_websys as webrtc_websys; - use std::time::Duration; use crate::{BlpopRequest, Muxer, SecProtocol, Transport}; - pub(crate) type Instant = instant::Instant; + pub(crate) type Instant = web_time::Instant; pub(crate) fn init_logger() { console_error_panic_hook::set_once(); @@ -246,7 +240,7 @@ pub(crate) mod wasm { .with_behaviour(behaviour_constructor)? .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(), - format!("/ip4/{ip}/tcp/0/wss"), + format!("/ip4/{ip}/tcp/0/tls/ws"), ), (Transport::Ws, Some(SecProtocol::Noise), Some(Muxer::Yamux)) => ( libp2p::SwarmBuilder::with_new_identity() @@ -263,7 +257,7 @@ pub(crate) mod wasm { .with_behaviour(behaviour_constructor)? .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5))) .build(), - format!("/ip4/{ip}/tcp/0/wss"), + format!("/ip4/{ip}/tcp/0/tls/ws"), ), (Transport::WebRtcDirect, None, None) => ( libp2p::SwarmBuilder::with_new_identity() diff --git a/interop-tests/src/bin/wasm_ping.rs b/interop-tests/src/bin/wasm_ping.rs index 8269ff064ad..7730b869456 100644 --- a/interop-tests/src/bin/wasm_ping.rs +++ b/interop-tests/src/bin/wasm_ping.rs @@ -1,24 +1,27 @@ #![allow(non_upper_case_globals)] -use std::process::Stdio; -use std::time::Duration; + +use std::{future::IntoFuture, process::Stdio, time::Duration}; use anyhow::{bail, Context, Result}; -use axum::body; -use axum::http::{header, Uri}; -use axum::response::{Html, IntoResponse, Response}; -use axum::routing::get; -use axum::{extract::State, http::StatusCode, routing::post, Json, Router}; +use axum::{ + extract::State, + http::{header, StatusCode, Uri}, + response::{Html, IntoResponse, Response}, + routing::{get, post}, + Json, Router, +}; +use interop_tests::{BlpopRequest, Report}; use redis::{AsyncCommands, Client}; use thirtyfour::prelude::*; -use tokio::io::{AsyncBufReadExt, BufReader}; -use tokio::process::Child; -use tokio::sync::mpsc; -use tower_http::cors::CorsLayer; -use tower_http::trace::TraceLayer; +use tokio::{ + io::{AsyncBufReadExt, BufReader}, + net::TcpListener, + process::Child, + sync::mpsc, +}; +use tower_http::{cors::CorsLayer, trace::TraceLayer}; use tracing_subscriber::{fmt, prelude::*, EnvFilter}; -use interop_tests::{BlpopRequest, Report}; - mod config; const BIND_ADDR: &str = "127.0.0.1:8080"; @@ -76,7 +79,7 @@ async fn main() -> Result<()> { .with_state(state); // Run the service in background - tokio::spawn(axum::Server::bind(&BIND_ADDR.parse()?).serve(app.into_make_service())); + tokio::spawn(axum::serve(TcpListener::bind(BIND_ADDR).await?, app).into_future()); // Start executing the test in a browser let (mut chrome, driver) = open_in_browser().await?; @@ -128,6 +131,8 @@ async fn open_in_browser() -> Result<(Child, WebDriver)> { // run a webdriver client let mut caps = DesiredCapabilities::chrome(); caps.set_headless()?; + caps.set_disable_dev_shm_usage()?; + caps.set_no_sandbox()?; let driver = WebDriver::new("http://localhost:45782", caps).await?; // go to the wasm test service driver.goto(format!("http://{BIND_ADDR}")).await?; @@ -147,7 +152,7 @@ async fn redis_blpop( StatusCode::INTERNAL_SERVER_ERROR })?; let res = conn - .blpop(&request.key, request.timeout as usize) + .blpop(&request.key, request.timeout as f64) .await .map_err(|e| { tracing::warn!( @@ -229,7 +234,7 @@ async fn serve_wasm_pkg(uri: Uri) -> Result { let mime = mime_guess::from_path(&path).first_or_octet_stream(); Ok(Response::builder() .header(header::CONTENT_TYPE, mime.as_ref()) - .body(body::boxed(body::Full::from(content.data))) + .body(content.data.into()) .unwrap()) } else { Err(StatusCode::NOT_FOUND) diff --git a/interop-tests/src/lib.rs b/interop-tests/src/lib.rs index 0154bec51a4..a16dc4b8228 100644 --- a/interop-tests/src/lib.rs +++ b/interop-tests/src/lib.rs @@ -1,11 +1,14 @@ -use std::str::FromStr; -use std::time::Duration; +use std::{str::FromStr, time::Duration}; use anyhow::{bail, Context, Result}; use futures::{FutureExt, StreamExt}; -use libp2p::identity::Keypair; -use libp2p::swarm::SwarmEvent; -use libp2p::{identify, ping, swarm::NetworkBehaviour, Multiaddr}; +use libp2p::{ + identify, + identity::Keypair, + ping, + swarm::{NetworkBehaviour, SwarmEvent}, + Multiaddr, +}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; diff --git a/libp2p/CHANGELOG.md b/libp2p/CHANGELOG.md index 80b32c35643..8b7bf0ff55f 100644 --- a/libp2p/CHANGELOG.md +++ b/libp2p/CHANGELOG.md @@ -1,3 +1,48 @@ +## 0.54.2 + +- Add `with_connection_timeout` on `SwarmBuilder` to allow configuration of the connection_timeout parameter. + See [PR 5575](https://github.com/libp2p/rust-libp2p/pull/5575). + +- Deprecate `void` crate. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). + +- Update default for idle-connection-timeout to 10s. + See [PR 4967](https://github.com/libp2p/rust-libp2p/pull/4967). + +- Expose swarm builder phase errors. + See [PR 5726](https://github.com/libp2p/rust-libp2p/pull/5726). + +## 0.54.1 + +- Update individual crates. + - Update to [`libp2p-metrics` `0.15.0`](misc/metrics/CHANGELOG.md#0150). + +## 0.54.0 + +- Update individual crates. + - Update to [`libp2p-kad` `v0.46.0`](protocols/kad/CHANGELOG.md#0460). + - Update to [`libp2p-identify` `v0.45.0`](protocols/identify/CHANGELOG.md#0450). + +- Raise MSRV to 1.73. + See [PR 5266](https://github.com/libp2p/rust-libp2p/pull/5266). + +- Implement refactored `Transport`. + See [PR 4568]. + +- Move `address_translation` from `libp2p-core` to `libp2p-swarm` and `libp2p-identify`. To now get + address translation behaviour, i.e. discovery of extern address (candidates) based on connecting + to other peers, one needs to use `libp2p-identify` now. This pertains to you if your nodes can be + behind NATs and they aren't aware of their true external address. + See [PR 4568]. + +- Use `web-time` instead of `instant`. + See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). + +- Remove redundant async signature from builder methods. + See [PR 5468](https://github.com/libp2p/rust-libp2p/pull/5468). + +[PR 4568]: https://github.com/libp2p/rust-libp2p/pull/4568 + ## 0.53.2 - Allow `SwarmBuilder::with_bandwidth_metrics` after `SwarmBuilder::with_websocket`. @@ -1185,7 +1230,7 @@ must not be skipped! - Merged `PeriodicPing` and `PingListen` into one `Ping` behaviour. - `Floodsub` now generates `FloodsubEvent`s instead of direct floodsub messages. - Added `ProtocolsHandler::connection_keep_alive`. If all the handlers return `false`, then the connection to the remote node will automatically be gracefully closed after a few seconds. -- The crate now successfuly compiles for the `wasm32-unknown-unknown` target. +- The crate now successfully compiles for the `wasm32-unknown-unknown` target. - Updated `ring` to version 0.13. - Updated `secp256k1` to version 0.12. - The enum returned by `RawSwarm::peer()` can now return `LocalNode`. This makes it impossible to accidentally attempt to dial the local node. diff --git a/libp2p/Cargo.toml b/libp2p/Cargo.toml index 9dc9667be10..ae23dcf0db5 100644 --- a/libp2p/Cargo.toml +++ b/libp2p/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p" edition = "2021" rust-version = { workspace = true } description = "Peer-to-peer networking library" -version = "0.53.2" +version = "0.54.2" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -82,7 +82,7 @@ tcp = ["dep:libp2p-tcp"] tls = ["dep:libp2p-tls"] tokio = [ "libp2p-swarm/tokio", "libp2p-mdns?/tokio", "libp2p-tcp?/tokio", "libp2p-dns?/tokio", "libp2p-quic?/tokio", "libp2p-upnp?/tokio"] uds = ["dep:libp2p-uds"] -wasm-bindgen = [ "futures-timer/wasm-bindgen", "instant/wasm-bindgen", "getrandom/js", "libp2p-swarm/wasm-bindgen", "libp2p-gossipsub?/wasm-bindgen",] +wasm-bindgen = [ "futures-timer/wasm-bindgen", "getrandom/js", "libp2p-swarm/wasm-bindgen", "libp2p-gossipsub?/wasm-bindgen"] websocket-websys = ["dep:libp2p-websocket-websys"] websocket = ["dep:libp2p-websocket"] webtransport-websys = ["dep:libp2p-webtransport-websys"] @@ -92,10 +92,9 @@ upnp = ["dep:libp2p-upnp"] [dependencies] bytes = "1" either = "1.9.0" -futures = "0.3.26" +futures = { workspace = true } futures-timer = "3.0.2" # Explicit dependency to be used in `wasm-bindgen` feature -getrandom = "0.2.3" # Explicit dependency to be used in `wasm-bindgen` feature -instant = "0.1.12" # Explicit dependency to be used in `wasm-bindgen` feature +getrandom = { workspace = true } # Explicit dependency to be used in `wasm-bindgen` feature # TODO feature flag? rw-stream-sink = { workspace = true } @@ -123,7 +122,7 @@ libp2p-webtransport-websys = { workspace = true, optional = true } libp2p-yamux = { workspace = true, optional = true } multiaddr = { workspace = true } pin-project = "1.0.0" -thiserror = "1.0" +thiserror = { workspace = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] libp2p-dns = { workspace = true, optional = true } @@ -138,21 +137,16 @@ libp2p-websocket = { workspace = true, optional = true } [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } -async-trait = "0.1" -clap = { version = "4.1.6", features = ["derive"] } -tokio = { version = "1.15", features = [ "io-util", "io-std", "macros", "rt", "rt-multi-thread"] } +tokio = { workspace = true, features = [ "io-util", "io-std", "macros", "rt", "rt-multi-thread"] } libp2p-mplex = { workspace = true } -libp2p-noise = { workspace = true } libp2p-tcp = { workspace = true, features = ["tokio"] } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-subscriber = { workspace = true, features = ["env-filter"] } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/libp2p/src/bandwidth.rs b/libp2p/src/bandwidth.rs index b84cbb7e27b..ac668e26b3f 100644 --- a/libp2p/src/bandwidth.rs +++ b/libp2p/src/bandwidth.rs @@ -20,13 +20,6 @@ #![allow(deprecated)] -use crate::core::muxing::{StreamMuxer, StreamMuxerEvent}; - -use futures::{ - io::{IoSlice, IoSliceMut}, - prelude::*, - ready, -}; use std::{ convert::TryFrom as _, io, @@ -38,6 +31,14 @@ use std::{ task::{Context, Poll}, }; +use futures::{ + io::{IoSlice, IoSliceMut}, + prelude::*, + ready, +}; + +use crate::core::muxing::{StreamMuxer, StreamMuxerEvent}; + /// Wraps around a [`StreamMuxer`] and counts the number of bytes that go through all the opened /// streams. #[derive(Clone)] @@ -123,7 +124,7 @@ impl BandwidthSinks { /// Returns the total number of bytes that have been downloaded on all the streams. /// /// > **Note**: This method is by design subject to race conditions. The returned value should - /// > only ever be used for statistics purposes. + /// > only ever be used for statistics purposes. pub fn total_inbound(&self) -> u64 { self.inbound.load(Ordering::Relaxed) } @@ -131,7 +132,7 @@ impl BandwidthSinks { /// Returns the total number of bytes that have been uploaded on all the streams. /// /// > **Note**: This method is by design subject to race conditions. The returned value should - /// > only ever be used for statistics purposes. + /// > only ever be used for statistics purposes. pub fn total_outbound(&self) -> u64 { self.outbound.load(Ordering::Relaxed) } @@ -154,7 +155,7 @@ impl AsyncRead for InstrumentedStream { let this = self.project(); let num_bytes = ready!(this.inner.poll_read(cx, buf))?; this.sinks.inbound.fetch_add( - u64::try_from(num_bytes).unwrap_or(u64::max_value()), + u64::try_from(num_bytes).unwrap_or(u64::MAX), Ordering::Relaxed, ); Poll::Ready(Ok(num_bytes)) @@ -168,7 +169,7 @@ impl AsyncRead for InstrumentedStream { let this = self.project(); let num_bytes = ready!(this.inner.poll_read_vectored(cx, bufs))?; this.sinks.inbound.fetch_add( - u64::try_from(num_bytes).unwrap_or(u64::max_value()), + u64::try_from(num_bytes).unwrap_or(u64::MAX), Ordering::Relaxed, ); Poll::Ready(Ok(num_bytes)) @@ -184,7 +185,7 @@ impl AsyncWrite for InstrumentedStream { let this = self.project(); let num_bytes = ready!(this.inner.poll_write(cx, buf))?; this.sinks.outbound.fetch_add( - u64::try_from(num_bytes).unwrap_or(u64::max_value()), + u64::try_from(num_bytes).unwrap_or(u64::MAX), Ordering::Relaxed, ); Poll::Ready(Ok(num_bytes)) @@ -198,7 +199,7 @@ impl AsyncWrite for InstrumentedStream { let this = self.project(); let num_bytes = ready!(this.inner.poll_write_vectored(cx, bufs))?; this.sinks.outbound.fetch_add( - u64::try_from(num_bytes).unwrap_or(u64::max_value()), + u64::try_from(num_bytes).unwrap_or(u64::MAX), Ordering::Relaxed, ); Poll::Ready(Ok(num_bytes)) diff --git a/libp2p/src/builder.rs b/libp2p/src/builder.rs index c96c20d470a..ae4d0b0d4e4 100644 --- a/libp2p/src/builder.rs +++ b/libp2p/src/builder.rs @@ -4,6 +4,10 @@ mod phase; mod select_muxer; mod select_security; +#[cfg(all(not(target_arch = "wasm32"), feature = "websocket"))] +pub use phase::WebsocketError; +pub use phase::{BehaviourError, TransportError}; + /// Build a [`Swarm`](libp2p_swarm::Swarm) by combining an identity, a set of /// [`Transport`](libp2p_core::Transport)s and a /// [`NetworkBehaviour`](libp2p_swarm::NetworkBehaviour). @@ -33,31 +37,31 @@ mod select_security; /// # relay: libp2p_relay::client::Behaviour, /// # } /// -/// let swarm = SwarmBuilder::with_new_identity() -/// .with_tokio() -/// .with_tcp( -/// Default::default(), -/// (libp2p_tls::Config::new, libp2p_noise::Config::new), -/// libp2p_yamux::Config::default, -/// )? -/// .with_quic() -/// .with_other_transport(|_key| DummyTransport::<(PeerId, StreamMuxerBox)>::new())? -/// .with_dns()? -/// .with_websocket( -/// (libp2p_tls::Config::new, libp2p_noise::Config::new), -/// libp2p_yamux::Config::default, -/// ) -/// .await? -/// .with_relay_client( -/// (libp2p_tls::Config::new, libp2p_noise::Config::new), -/// libp2p_yamux::Config::default, -/// )? -/// .with_behaviour(|_key, relay| MyBehaviour { relay })? -/// .with_swarm_config(|cfg| { -/// // Edit cfg here. -/// cfg -/// }) -/// .build(); +/// let swarm = SwarmBuilder::with_new_identity() +/// .with_tokio() +/// .with_tcp( +/// Default::default(), +/// (libp2p_tls::Config::new, libp2p_noise::Config::new), +/// libp2p_yamux::Config::default, +/// )? +/// .with_quic() +/// .with_other_transport(|_key| DummyTransport::<(PeerId, StreamMuxerBox)>::new())? +/// .with_dns()? +/// .with_websocket( +/// (libp2p_tls::Config::new, libp2p_noise::Config::new), +/// libp2p_yamux::Config::default, +/// ) +/// .await? +/// .with_relay_client( +/// (libp2p_tls::Config::new, libp2p_noise::Config::new), +/// libp2p_yamux::Config::default, +/// )? +/// .with_behaviour(|_key, relay| MyBehaviour { relay })? +/// .with_swarm_config(|cfg| { +/// // Edit cfg here. +/// cfg +/// }) +/// .build(); /// # /// # Ok(()) /// # } @@ -70,11 +74,12 @@ pub struct SwarmBuilder { #[cfg(test)] mod tests { - use crate::SwarmBuilder; use libp2p_core::{muxing::StreamMuxerBox, transport::dummy::DummyTransport}; use libp2p_identity::PeerId; use libp2p_swarm::NetworkBehaviour; + use crate::SwarmBuilder; + #[test] #[cfg(all( feature = "tokio", @@ -575,7 +580,7 @@ mod tests { #[test] #[cfg(all(feature = "tokio", feature = "quic"))] - fn quic_bandwidth_metrics() -> Result<(), Box> { + fn quic_bandwidth_metrics() { let _ = SwarmBuilder::with_new_identity() .with_tokio() .with_quic() @@ -583,8 +588,6 @@ mod tests { .with_behaviour(|_| libp2p_swarm::dummy::Behaviour) .unwrap() .build(); - - Ok(()) } #[test] diff --git a/libp2p/src/builder/phase.rs b/libp2p/src/builder/phase.rs index c9679a46767..f8f1672f952 100644 --- a/libp2p/src/builder/phase.rs +++ b/libp2p/src/builder/phase.rs @@ -16,23 +16,26 @@ mod websocket; use bandwidth_logging::*; use bandwidth_metrics::*; +pub use behaviour::BehaviourError; use behaviour::*; use build::*; use dns::*; +use libp2p_core::{muxing::StreamMuxerBox, Transport}; +use libp2p_identity::Keypair; +pub use other_transport::TransportError; use other_transport::*; use provider::*; use quic::*; use relay::*; use swarm::*; use tcp::*; +#[cfg(all(not(target_arch = "wasm32"), feature = "websocket"))] +pub use websocket::WebsocketError; use websocket::*; -use super::select_muxer::SelectMuxerUpgrade; -use super::select_security::SelectSecurityUpgrade; -use super::SwarmBuilder; - -use libp2p_core::{muxing::StreamMuxerBox, Transport}; -use libp2p_identity::Keypair; +use super::{ + select_muxer::SelectMuxerUpgrade, select_security::SelectSecurityUpgrade, SwarmBuilder, +}; #[allow(unreachable_pub)] pub trait IntoSecurityUpgrade { diff --git a/libp2p/src/builder/phase/bandwidth_logging.rs b/libp2p/src/builder/phase/bandwidth_logging.rs index cee9498fcaa..f24df5f3df5 100644 --- a/libp2p/src/builder/phase/bandwidth_logging.rs +++ b/libp2p/src/builder/phase/bandwidth_logging.rs @@ -1,10 +1,9 @@ +use std::{marker::PhantomData, sync::Arc}; + use super::*; #[allow(deprecated)] use crate::bandwidth::BandwidthSinks; -use crate::transport_ext::TransportExt; -use crate::SwarmBuilder; -use std::marker::PhantomData; -use std::sync::Arc; +use crate::{transport_ext::TransportExt, SwarmBuilder}; pub struct BandwidthLoggingPhase { pub(crate) relay_behaviour: R, diff --git a/libp2p/src/builder/phase/bandwidth_metrics.rs b/libp2p/src/builder/phase/bandwidth_metrics.rs index 52daa731ddd..ddd292c140e 100644 --- a/libp2p/src/builder/phase/bandwidth_metrics.rs +++ b/libp2p/src/builder/phase/bandwidth_metrics.rs @@ -1,10 +1,9 @@ +use std::{marker::PhantomData, sync::Arc}; + use super::*; #[allow(deprecated)] use crate::bandwidth::BandwidthSinks; -use crate::transport_ext::TransportExt; -use crate::SwarmBuilder; -use std::marker::PhantomData; -use std::sync::Arc; +use crate::{transport_ext::TransportExt, SwarmBuilder}; pub struct BandwidthMetricsPhase { pub(crate) relay_behaviour: R, diff --git a/libp2p/src/builder/phase/behaviour.rs b/libp2p/src/builder/phase/behaviour.rs index 939db935c80..22f8c617051 100644 --- a/libp2p/src/builder/phase/behaviour.rs +++ b/libp2p/src/builder/phase/behaviour.rs @@ -1,8 +1,9 @@ +use std::{convert::Infallible, marker::PhantomData}; + +use libp2p_swarm::NetworkBehaviour; + use super::*; use crate::SwarmBuilder; -use libp2p_swarm::NetworkBehaviour; -use std::convert::Infallible; -use std::marker::PhantomData; pub struct BehaviourPhase { pub(crate) relay_behaviour: R, diff --git a/libp2p/src/builder/phase/build.rs b/libp2p/src/builder/phase/build.rs index 80a83994eeb..d3138cb8b8d 100644 --- a/libp2p/src/builder/phase/build.rs +++ b/libp2p/src/builder/phase/build.rs @@ -1,28 +1,31 @@ +use std::time::Duration; + +use libp2p_core::{transport::timeout::TransportTimeout, Transport}; +use libp2p_swarm::Swarm; + #[allow(unused_imports)] use super::*; - use crate::SwarmBuilder; -use libp2p_core::Transport; -use libp2p_swarm::Swarm; pub struct BuildPhase { pub(crate) behaviour: B, pub(crate) transport: T, pub(crate) swarm_config: libp2p_swarm::Config, + pub(crate) connection_timeout: Duration, } -const CONNECTION_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); - impl SwarmBuilder> { + /// Timeout of the [`TransportTimeout`] wrapping the transport. + pub fn with_connection_timeout(mut self, connection_timeout: Duration) -> Self { + self.phase.connection_timeout = connection_timeout; + self + } + pub fn build(self) -> Swarm { Swarm::new( - libp2p_core::transport::timeout::TransportTimeout::new( - self.phase.transport, - CONNECTION_TIMEOUT, - ) - .boxed(), + TransportTimeout::new(self.phase.transport, self.phase.connection_timeout).boxed(), self.phase.behaviour, self.keypair.public().to_peer_id(), self.phase.swarm_config, diff --git a/libp2p/src/builder/phase/dns.rs b/libp2p/src/builder/phase/dns.rs index 135f6c57b19..83653836a34 100644 --- a/libp2p/src/builder/phase/dns.rs +++ b/libp2p/src/builder/phase/dns.rs @@ -1,6 +1,7 @@ +use std::marker::PhantomData; + use super::*; use crate::SwarmBuilder; -use std::marker::PhantomData; pub struct DnsPhase { pub(crate) transport: T, @@ -8,8 +9,7 @@ pub struct DnsPhase { #[cfg(all(not(target_arch = "wasm32"), feature = "async-std", feature = "dns"))] impl SwarmBuilder> { - // TODO: Remove `async` - pub async fn with_dns( + pub fn with_dns( self, ) -> Result< SwarmBuilder< diff --git a/libp2p/src/builder/phase/identity.rs b/libp2p/src/builder/phase/identity.rs index ceb86819dc7..e2511267cd3 100644 --- a/libp2p/src/builder/phase/identity.rs +++ b/libp2p/src/builder/phase/identity.rs @@ -1,6 +1,7 @@ +use std::marker::PhantomData; + use super::*; use crate::SwarmBuilder; -use std::marker::PhantomData; pub struct IdentityPhase {} diff --git a/libp2p/src/builder/phase/other_transport.rs b/libp2p/src/builder/phase/other_transport.rs index b0d56cd92d2..c3b951c8c75 100644 --- a/libp2p/src/builder/phase/other_transport.rs +++ b/libp2p/src/builder/phase/other_transport.rs @@ -1,20 +1,19 @@ -use std::convert::Infallible; -use std::marker::PhantomData; -use std::sync::Arc; +use std::{convert::Infallible, marker::PhantomData, sync::Arc}; -use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}; -use libp2p_core::Transport; +use libp2p_core::{ + upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}, + Transport, +}; #[cfg(feature = "relay")] use libp2p_core::{Negotiated, UpgradeInfo}; #[cfg(feature = "relay")] use libp2p_identity::PeerId; +use super::*; #[allow(deprecated)] use crate::bandwidth::BandwidthSinks; use crate::SwarmBuilder; -use super::*; - pub struct OtherTransportPhase { pub(crate) transport: T, } @@ -73,7 +72,7 @@ impl impl SwarmBuilder> { - pub async fn with_dns( + pub fn with_dns( self, ) -> Result< SwarmBuilder< @@ -82,7 +81,7 @@ impl >, std::io::Error, > { - self.without_any_other_transports().with_dns().await + self.without_any_other_transports().with_dns() } } #[cfg(all(not(target_arch = "wasm32"), feature = "tokio", feature = "dns"))] diff --git a/libp2p/src/builder/phase/provider.rs b/libp2p/src/builder/phase/provider.rs index 32321442689..00a79e14a30 100644 --- a/libp2p/src/builder/phase/provider.rs +++ b/libp2p/src/builder/phase/provider.rs @@ -1,46 +1,64 @@ +use std::marker::PhantomData; + #[allow(unused_imports)] use super::*; - use crate::SwarmBuilder; +/// Represents the phase where a provider is not yet specified. +/// This is a marker type used in the type-state pattern to ensure compile-time checks of the +/// builder's state. +pub enum NoProviderSpecified {} + +// Define enums for each of the possible runtime environments. These are used as markers in the +// type-state pattern, allowing compile-time checks for the appropriate environment configuration. + +#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))] +/// Represents the AsyncStd runtime environment. +pub enum AsyncStd {} + +#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))] +/// Represents the Tokio runtime environment. +pub enum Tokio {} + +#[cfg(feature = "wasm-bindgen")] +/// Represents the WasmBindgen environment for WebAssembly. +pub enum WasmBindgen {} +/// Represents a phase in the SwarmBuilder where a provider has been chosen but not yet specified. pub struct ProviderPhase {} impl SwarmBuilder { + /// Configures the SwarmBuilder to use the AsyncStd runtime. + /// This method is only available when compiling for non-Wasm + /// targets with the `async-std` feature enabled. #[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))] pub fn with_async_std(self) -> SwarmBuilder { SwarmBuilder { keypair: self.keypair, - phantom: std::marker::PhantomData, + phantom: PhantomData, phase: TcpPhase {}, } } + /// Configures the SwarmBuilder to use the Tokio runtime. + /// This method is only available when compiling for non-Wasm + /// targets with the `tokio` feature enabled #[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))] pub fn with_tokio(self) -> SwarmBuilder { SwarmBuilder { keypair: self.keypair, - phantom: std::marker::PhantomData, + phantom: PhantomData, phase: TcpPhase {}, } } + /// Configures the SwarmBuilder for WebAssembly using WasmBindgen. + /// This method is available when the `wasm-bindgen` feature is enabled. #[cfg(feature = "wasm-bindgen")] pub fn with_wasm_bindgen(self) -> SwarmBuilder { SwarmBuilder { keypair: self.keypair, - phantom: std::marker::PhantomData, + phantom: PhantomData, phase: TcpPhase {}, } } } - -pub enum NoProviderSpecified {} - -#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))] -pub enum AsyncStd {} - -#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))] -pub enum Tokio {} - -#[cfg(feature = "wasm-bindgen")] -pub enum WasmBindgen {} diff --git a/libp2p/src/builder/phase/quic.rs b/libp2p/src/builder/phase/quic.rs index 885b16e2e03..1b6329c1095 100644 --- a/libp2p/src/builder/phase/quic.rs +++ b/libp2p/src/builder/phase/quic.rs @@ -1,5 +1,5 @@ -use super::*; -use crate::SwarmBuilder; +use std::{marker::PhantomData, sync::Arc}; + #[cfg(all(not(target_arch = "wasm32"), feature = "websocket"))] use libp2p_core::muxing::StreamMuxer; use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}; @@ -8,7 +8,9 @@ use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}; all(not(target_arch = "wasm32"), feature = "websocket") ))] use libp2p_core::{InboundUpgrade, Negotiated, OutboundUpgrade, UpgradeInfo}; -use std::{marker::PhantomData, sync::Arc}; + +use super::*; +use crate::SwarmBuilder; pub struct QuicPhase { pub(crate) transport: T, @@ -150,7 +152,7 @@ impl SwarmBuilder SwarmBuilder> { - pub async fn with_dns( + pub fn with_dns( self, ) -> Result< SwarmBuilder< @@ -162,7 +164,6 @@ impl SwarmBuilder { pub(crate) transport: T, diff --git a/libp2p/src/builder/phase/swarm.rs b/libp2p/src/builder/phase/swarm.rs index ee456ced927..e751ad672e4 100644 --- a/libp2p/src/builder/phase/swarm.rs +++ b/libp2p/src/builder/phase/swarm.rs @@ -1,6 +1,9 @@ #[allow(unused_imports)] use super::*; +#[allow(unused)] // used below but due to feature flag combinations, clippy gives an unnecessary warning. +const DEFAULT_CONNECTION_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); + #[allow(dead_code)] pub struct SwarmPhase { pub(crate) behaviour: B, @@ -20,6 +23,7 @@ macro_rules! impl_with_swarm_config { behaviour: self.phase.behaviour, transport: self.phase.transport, swarm_config: constructor($config), + connection_timeout: DEFAULT_CONNECTION_TIMEOUT, }, keypair: self.keypair, phantom: std::marker::PhantomData, diff --git a/libp2p/src/builder/phase/tcp.rs b/libp2p/src/builder/phase/tcp.rs index 4b7cf29b3d2..f38f52441e5 100644 --- a/libp2p/src/builder/phase/tcp.rs +++ b/libp2p/src/builder/phase/tcp.rs @@ -1,5 +1,5 @@ -use super::*; -use crate::SwarmBuilder; +use std::marker::PhantomData; + #[cfg(all( not(target_arch = "wasm32"), any(feature = "tcp", feature = "websocket") @@ -14,7 +14,9 @@ use libp2p_core::Transport; use libp2p_core::{ upgrade::InboundConnectionUpgrade, upgrade::OutboundConnectionUpgrade, Negotiated, UpgradeInfo, }; -use std::marker::PhantomData; + +use super::*; +use crate::SwarmBuilder; pub struct TcpPhase {} diff --git a/libp2p/src/builder/phase/websocket.rs b/libp2p/src/builder/phase/websocket.rs index 68a85bb77b7..a23c6eca854 100644 --- a/libp2p/src/builder/phase/websocket.rs +++ b/libp2p/src/builder/phase/websocket.rs @@ -1,5 +1,5 @@ -use super::*; -use crate::SwarmBuilder; +use std::marker::PhantomData; + #[cfg(all(not(target_arch = "wasm32"), feature = "websocket"))] use libp2p_core::muxing::{StreamMuxer, StreamMuxerBox}; use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}; @@ -15,7 +15,9 @@ use libp2p_core::{InboundUpgrade, Negotiated, OutboundUpgrade, UpgradeInfo}; feature = "relay" ))] use libp2p_identity::PeerId; -use std::marker::PhantomData; + +use super::*; +use crate::SwarmBuilder; pub struct WebsocketPhase { pub(crate) transport: T, @@ -126,8 +128,8 @@ impl_websocket_builder!( impl_websocket_builder!( "tokio", super::provider::Tokio, - // Note this is an unnecessary await for Tokio Websocket (i.e. tokio dns) in order to be consistent - // with above AsyncStd construction. + // Note this is an unnecessary await for Tokio Websocket (i.e. tokio dns) in order to be + // consistent with above AsyncStd construction. futures::future::ready(libp2p_dns::tokio::Transport::system( libp2p_tcp::tokio::Transport::new(libp2p_tcp::Config::default()) )), diff --git a/libp2p/src/builder/select_muxer.rs b/libp2p/src/builder/select_muxer.rs index c93ba9d9991..93ae0547269 100644 --- a/libp2p/src/builder/select_muxer.rs +++ b/libp2p/src/builder/select_muxer.rs @@ -20,12 +20,15 @@ #![allow(unreachable_pub)] +use std::iter::{Chain, Map}; + use either::Either; use futures::future; -use libp2p_core::either::EitherFuture; -use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}; -use libp2p_core::UpgradeInfo; -use std::iter::{Chain, Map}; +use libp2p_core::{ + either::EitherFuture, + upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}, + UpgradeInfo, +}; #[derive(Debug, Clone)] pub struct SelectMuxerUpgrade(A, B); diff --git a/libp2p/src/builder/select_security.rs b/libp2p/src/builder/select_security.rs index d6c7f8c172f..1ed760feb1b 100644 --- a/libp2p/src/builder/select_security.rs +++ b/libp2p/src/builder/select_security.rs @@ -21,13 +21,15 @@ #![allow(unreachable_pub)] +use std::iter::{Chain, Map}; + use either::Either; -use futures::future::MapOk; -use futures::{future, TryFutureExt}; -use libp2p_core::either::EitherFuture; -use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade, UpgradeInfo}; +use futures::{future, future::MapOk, TryFutureExt}; +use libp2p_core::{ + either::EitherFuture, + upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade, UpgradeInfo}, +}; use libp2p_identity::PeerId; -use std::iter::{Chain, Map}; /// Upgrade that combines two upgrades into one. Supports all the protocols supported by either /// sub-upgrade. diff --git a/libp2p/src/lib.rs b/libp2p/src/lib.rs index 58f911e9445..47e1142d0e9 100644 --- a/libp2p/src/lib.rs +++ b/libp2p/src/lib.rs @@ -34,11 +34,6 @@ pub use bytes; pub use futures; -#[doc(inline)] -pub use libp2p_core::multihash; -#[doc(inline)] -pub use multiaddr; - #[doc(inline)] pub use libp2p_allow_block_list as allow_block_list; #[cfg(feature = "autonat")] @@ -48,6 +43,8 @@ pub use libp2p_autonat as autonat; pub use libp2p_connection_limits as connection_limits; #[doc(inline)] pub use libp2p_core as core; +#[doc(inline)] +pub use libp2p_core::multihash; #[cfg(feature = "dcutr")] #[doc(inline)] pub use libp2p_dcutr as dcutr; @@ -140,6 +137,8 @@ pub use libp2p_webtransport_websys as webtransport_websys; #[cfg(feature = "yamux")] #[doc(inline)] pub use libp2p_yamux as yamux; +#[doc(inline)] +pub use multiaddr; mod builder; mod transport_ext; @@ -149,15 +148,23 @@ pub mod bandwidth; #[cfg(doc)] pub mod tutorials; -pub use self::builder::SwarmBuilder; -pub use self::core::{ - transport::TransportError, - upgrade::{InboundUpgrade, OutboundUpgrade}, - Transport, -}; -pub use self::multiaddr::{multiaddr as build_multiaddr, Multiaddr}; -pub use self::swarm::Swarm; -pub use self::transport_ext::TransportExt; +#[cfg(all(not(target_arch = "wasm32"), feature = "websocket"))] +pub use builder::WebsocketError as WebsocketBuilderError; pub use libp2p_identity as identity; pub use libp2p_identity::PeerId; pub use libp2p_swarm::{Stream, StreamProtocol}; + +pub use self::{ + builder::{ + BehaviourError as BehaviourBuilderError, SwarmBuilder, + TransportError as TransportBuilderError, + }, + core::{ + transport::TransportError, + upgrade::{InboundUpgrade, OutboundUpgrade}, + Transport, + }, + multiaddr::{multiaddr as build_multiaddr, Multiaddr}, + swarm::Swarm, + transport_ext::TransportExt, +}; diff --git a/libp2p/src/transport_ext.rs b/libp2p/src/transport_ext.rs index bca0b8f4576..0ed5b816903 100644 --- a/libp2p/src/transport_ext.rs +++ b/libp2p/src/transport_ext.rs @@ -20,20 +20,24 @@ //! Provides the `TransportExt` trait. +use std::sync::Arc; + +use libp2p_identity::PeerId; + #[allow(deprecated)] use crate::bandwidth::{BandwidthLogging, BandwidthSinks}; -use crate::core::{ - muxing::{StreamMuxer, StreamMuxerBox}, - transport::Boxed, +use crate::{ + core::{ + muxing::{StreamMuxer, StreamMuxerBox}, + transport::Boxed, + }, + Transport, }; -use crate::Transport; -use libp2p_identity::PeerId; -use std::sync::Arc; /// Trait automatically implemented on all objects that implement `Transport`. Provides some /// additional utilities. pub trait TransportExt: Transport { - /// Adds a layer on the `Transport` that logs all trafic that passes through the streams + /// Adds a layer on the `Transport` that logs all traffic that passes through the streams /// created by it. /// /// This method returns an `Arc` that can be used to retrieve the total number @@ -42,23 +46,17 @@ pub trait TransportExt: Transport { /// # Example /// /// ``` - /// use libp2p_yamux as yamux; + /// use libp2p::{core::upgrade, identity, Transport, TransportExt}; /// use libp2p_noise as noise; /// use libp2p_tcp as tcp; - /// use libp2p::{ - /// core::upgrade, - /// identity, - /// TransportExt, - /// Transport, - /// }; + /// use libp2p_yamux as yamux; /// /// let id_keys = identity::Keypair::generate_ed25519(); /// /// let transport = tcp::tokio::Transport::new(tcp::Config::default().nodelay(true)) /// .upgrade(upgrade::Version::V1) /// .authenticate( - /// noise::Config::new(&id_keys) - /// .expect("Signing libp2p-noise static DH keypair failed."), + /// noise::Config::new(&id_keys).expect("Signing libp2p-noise static DH keypair failed."), /// ) /// .multiplex(yamux::Config::default()) /// .boxed(); diff --git a/libp2p/src/tutorials/hole_punching.rs b/libp2p/src/tutorials/hole_punching.rs index f9f42432ba4..06a4dad4037 100644 --- a/libp2p/src/tutorials/hole_punching.rs +++ b/libp2p/src/tutorials/hole_punching.rs @@ -57,8 +57,8 @@ //! cargo build --bin relay-server-example //! ``` //! -//! You can find the binary at `target/debug/relay-server-example`. In case you built it locally, copy -//! it to your server. +//! You can find the binary at `target/debug/relay-server-example`. In case you built it locally, +//! copy it to your server. //! //! On your server, start the relay server binary: //! @@ -69,7 +69,7 @@ //! Now let's make sure that the server is public, in other words let's make sure one can reach it //! through the Internet. First, either manually replace `$RELAY_SERVER_IP` in the following //! commands or `export RELAY_SERVER_IP=ipaddr` with the appropriate relay server `ipaddr` in -//! the dailing client and listening client. +//! the dialing client and listening client. //! //! Now, from the dialing client: //! @@ -98,7 +98,8 @@ //! //! ``` bash //! $ libp2p-lookup direct --address /ip4/111.11.111.111/tcp/4001 -//! Lookup for peer with id PeerId("12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN") succeeded. +//! Lookup for peer with id PeerId("12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN") +//! succeeded. //! //! Protocol version: "/TODO/0.0.1" //! Agent version: "rust-libp2p/0.36.0" @@ -163,12 +164,18 @@ //! [`Multiaddr`](crate::Multiaddr). //! //! ``` ignore -//! [2022-01-30T12:54:10Z INFO client] Established connection to PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X") via Dialer { address: "/ip4/$RELAY_PEER_ID/tcp/4001/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/p2p-circuit/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X", role_override: Dialer } +//! [2022-01-30T12:54:10Z INFO client] Established connection to +//! PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X") via Dialer { address: +//! "/ip4/$RELAY_PEER_ID/tcp/4001/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/ +//! p2p-circuit/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X", +//! role_override: Dialer } //! ``` //! -//! 2. The direct connection upgrade, also known as hole punch, succeeding. -//! Reported by [`dcutr`](crate::dcutr) through [`Event`](crate::dcutr::Event) containing [`Result::Ok`] with the [`ConnectionId`](libp2p_swarm::ConnectionId) of the new direct connection. +//! 2. The direct connection upgrade, also known as hole punch, succeeding. Reported by +//! [`dcutr`](crate::dcutr) through [`Event`](crate::dcutr::Event) containing [`Result::Ok`] with +//! the [`ConnectionId`](libp2p_swarm::ConnectionId) of the new direct connection. //! //! ``` ignore -//! [2022-01-30T12:54:11Z INFO client] Event { remote_peer_id: PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X"), result: Ok(2) } +//! [2022-01-30T12:54:11Z INFO client] Event { remote_peer_id: +//! PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X"), result: Ok(2) } //! ``` diff --git a/libp2p/src/tutorials/ping.rs b/libp2p/src/tutorials/ping.rs index 1413531cd72..ebaea29f33a 100644 --- a/libp2p/src/tutorials/ping.rs +++ b/libp2p/src/tutorials/ping.rs @@ -44,7 +44,7 @@ //! 3. Adding `libp2p` as well as `futures` as dependencies in the //! `Cargo.toml` file. Current crate versions may be found at //! [crates.io](https://crates.io/). -//! We will also include `async-std` with the +//! We will also include `tokio` with the //! "attributes" feature to allow for an `async main`. //! At the time of writing we have: //! @@ -55,9 +55,9 @@ //! edition = "2021" //! //! [dependencies] -//! libp2p = { version = "0.52", features = ["tcp", "tls", "dns", "async-std", "noise", "yamux", "websocket", "ping", "macros"] } -//! futures = "0.3.21" -//! async-std = { version = "1.12.0", features = ["attributes"] } +//! libp2p = { version = "0.54", features = ["noise", "ping", "tcp", "tokio", "yamux"] } +//! futures = "0.3.30" +//! tokio = { version = "1.37.0", features = ["full"] } //! tracing-subscriber = { version = "0.3", features = ["env-filter"] } //! ``` //! @@ -72,11 +72,14 @@ //! //! ```rust //! use std::error::Error; +//! //! use tracing_subscriber::EnvFilter; //! -//! #[async_std::main] +//! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); +//! let _ = tracing_subscriber::fmt() +//! .with_env_filter(EnvFilter::from_default_env()) +//! .try_init(); //! //! let mut swarm = libp2p::SwarmBuilder::with_new_identity(); //! @@ -89,25 +92,29 @@ //! ## Transport //! //! Next up we need to construct a transport. Each transport in libp2p provides encrypted streams. -//! E.g. combining TCP to establish connections, TLS to encrypt these connections and Yamux to run +//! E.g. combining TCP to establish connections, NOISE to encrypt these connections and Yamux to run //! one or more streams on a connection. Another libp2p transport is QUIC, providing encrypted //! streams out-of-the-box. We will stick to TCP for now. Each of these implement the [`Transport`] //! trait. //! //! ```rust //! use std::error::Error; +//! +//! use libp2p::{noise, tcp, yamux}; //! use tracing_subscriber::EnvFilter; //! -//! #[async_std::main] +//! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); +//! let _ = tracing_subscriber::fmt() +//! .with_env_filter(EnvFilter::from_default_env()) +//! .try_init(); //! //! let mut swarm = libp2p::SwarmBuilder::with_new_identity() -//! .with_async_std() +//! .with_tokio() //! .with_tcp( -//! libp2p::tcp::Config::default(), -//! libp2p::tls::Config::new, -//! libp2p::yamux::Config::default, +//! tcp::Config::default(), +//! noise::Config::new, +//! yamux::Config::default, //! )?; //! //! Ok(()) @@ -134,23 +141,27 @@ //! The two traits [`Transport`] and [`NetworkBehaviour`] allow us to cleanly //! separate _how_ to send bytes from _what_ bytes and to _whom_ to send. //! -//! With the above in mind, let's extend our example, creating a [`ping::Behaviour`](crate::ping::Behaviour) at the end: +//! With the above in mind, let's extend our example, creating a +//! [`ping::Behaviour`](crate::ping::Behaviour) at the end: //! //! ```rust -//! use libp2p::ping; -//! use tracing_subscriber::EnvFilter; //! use std::error::Error; //! -//! #[async_std::main] +//! use libp2p::{noise, ping, tcp, yamux}; +//! use tracing_subscriber::EnvFilter; +//! +//! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); +//! let _ = tracing_subscriber::fmt() +//! .with_env_filter(EnvFilter::from_default_env()) +//! .try_init(); //! //! let mut swarm = libp2p::SwarmBuilder::with_new_identity() -//! .with_async_std() +//! .with_tokio() //! .with_tcp( -//! libp2p::tcp::Config::default(), -//! libp2p::tls::Config::new, -//! libp2p::yamux::Config::default, +//! tcp::Config::default(), +//! noise::Config::new, +//! yamux::Config::default, //! )? //! .with_behaviour(|_| ping::Behaviour::default())?; //! @@ -166,20 +177,23 @@ //! to the [`Transport`] as well as events from the [`Transport`] to the [`NetworkBehaviour`]. //! //! ```rust -//! use libp2p::ping; //! use std::error::Error; +//! +//! use libp2p::{noise, ping, tcp, yamux}; //! use tracing_subscriber::EnvFilter; //! -//! #[async_std::main] +//! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); +//! let _ = tracing_subscriber::fmt() +//! .with_env_filter(EnvFilter::from_default_env()) +//! .try_init(); //! //! let mut swarm = libp2p::SwarmBuilder::with_new_identity() -//! .with_async_std() +//! .with_tokio() //! .with_tcp( -//! libp2p::tcp::Config::default(), -//! libp2p::tls::Config::new, -//! libp2p::yamux::Config::default, +//! tcp::Config::default(), +//! noise::Config::new, +//! yamux::Config::default, //! )? //! .with_behaviour(|_| ping::Behaviour::default())? //! .build(); @@ -199,24 +213,25 @@ //! Thus, without any other behaviour in place, we would not be able to observe the pings. //! //! ```rust -//! use libp2p::ping; -//! use std::error::Error; -//! use std::time::Duration; +//! use std::{error::Error, time::Duration}; +//! +//! use libp2p::{noise, ping, tcp, yamux}; //! use tracing_subscriber::EnvFilter; //! -//! #[async_std::main] +//! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); +//! let _ = tracing_subscriber::fmt() +//! .with_env_filter(EnvFilter::from_default_env()) +//! .try_init(); //! //! let mut swarm = libp2p::SwarmBuilder::with_new_identity() -//! .with_async_std() +//! .with_tokio() //! .with_tcp( -//! libp2p::tcp::Config::default(), -//! libp2p::tls::Config::new, -//! libp2p::yamux::Config::default, +//! tcp::Config::default(), +//! noise::Config::new, +//! yamux::Config::default, //! )? //! .with_behaviour(|_| ping::Behaviour::default())? -//! .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(30))) // Allows us to observe pings for 30 seconds. //! .build(); //! //! Ok(()) @@ -250,24 +265,25 @@ //! remote peer. //! //! ```rust -//! use libp2p::{ping, Multiaddr}; -//! use std::error::Error; -//! use std::time::Duration; +//! use std::{error::Error, time::Duration}; +//! +//! use libp2p::{noise, ping, tcp, yamux, Multiaddr}; //! use tracing_subscriber::EnvFilter; //! -//! #[async_std::main] +//! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); +//! let _ = tracing_subscriber::fmt() +//! .with_env_filter(EnvFilter::from_default_env()) +//! .try_init(); //! //! let mut swarm = libp2p::SwarmBuilder::with_new_identity() -//! .with_async_std() +//! .with_tokio() //! .with_tcp( -//! libp2p::tcp::Config::default(), -//! libp2p::tls::Config::new, -//! libp2p::yamux::Config::default, +//! tcp::Config::default(), +//! noise::Config::new, +//! yamux::Config::default, //! )? //! .with_behaviour(|_| ping::Behaviour::default())? -//! .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(30))) // Allows us to observe pings for 30 seconds. //! .build(); //! //! // Tell the swarm to listen on all interfaces and a random, OS-assigned @@ -293,26 +309,26 @@ //! outgoing connection in case we specify an address on the CLI. //! //! ```no_run +//! use std::{error::Error, time::Duration}; +//! //! use futures::prelude::*; -//! use libp2p::swarm::SwarmEvent; -//! use libp2p::{ping, Multiaddr}; -//! use std::error::Error; -//! use std::time::Duration; +//! use libp2p::{noise, ping, swarm::SwarmEvent, tcp, yamux, Multiaddr}; //! use tracing_subscriber::EnvFilter; //! -//! #[async_std::main] +//! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); +//! let _ = tracing_subscriber::fmt() +//! .with_env_filter(EnvFilter::from_default_env()) +//! .try_init(); //! //! let mut swarm = libp2p::SwarmBuilder::with_new_identity() -//! .with_async_std() +//! .with_tokio() //! .with_tcp( -//! libp2p::tcp::Config::default(), -//! libp2p::tls::Config::new, -//! libp2p::yamux::Config::default, +//! tcp::Config::default(), +//! noise::Config::new, +//! yamux::Config::default, //! )? //! .with_behaviour(|_| ping::Behaviour::default())? -//! .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::from_secs(30))) // Allows us to observe pings for 30 seconds. //! .build(); //! //! // Tell the swarm to listen on all interfaces and a random, OS-assigned diff --git a/misc/allow-block-list/CHANGELOG.md b/misc/allow-block-list/CHANGELOG.md index 7778e924886..e7f68f6f8fe 100644 --- a/misc/allow-block-list/CHANGELOG.md +++ b/misc/allow-block-list/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.4.1 + +- Add getters & setters for the allowed/blocked peers. + Return a `bool` for every "insert/remove" function, informing if a change was performed. + See [PR 5572](https://github.com/libp2p/rust-libp2p/pull/5572). +- Deprecate `void` crate. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). + +## 0.4.0 + + + ## 0.3.0 diff --git a/misc/allow-block-list/Cargo.toml b/misc/allow-block-list/Cargo.toml index c620e7f4a2b..c169be87056 100644 --- a/misc/allow-block-list/Cargo.toml +++ b/misc/allow-block-list/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-allow-block-list" edition = "2021" rust-version = { workspace = true } description = "Allow/block list connection management for libp2p." -version = "0.3.0" +version = "0.4.1" license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] @@ -13,7 +13,6 @@ categories = ["network-programming", "asynchronous"] libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true, features = ["peerid"] } -void = "1" [dev-dependencies] async-std = { version = "1.12.0", features = ["attributes"] } diff --git a/misc/allow-block-list/src/lib.rs b/misc/allow-block-list/src/lib.rs index c1d31433db1..ea0d56b5a67 100644 --- a/misc/allow-block-list/src/lib.rs +++ b/misc/allow-block-list/src/lib.rs @@ -31,12 +31,12 @@ //! #[derive(NetworkBehaviour)] //! # #[behaviour(prelude = "libp2p_swarm::derive_prelude")] //! struct MyBehaviour { -//! allowed_peers: allow_block_list::Behaviour, +//! allowed_peers: allow_block_list::Behaviour, //! } //! //! # fn main() { //! let behaviour = MyBehaviour { -//! allowed_peers: allow_block_list::Behaviour::default() +//! allowed_peers: allow_block_list::Behaviour::default(), //! }; //! # } //! ``` @@ -51,26 +51,29 @@ //! #[derive(NetworkBehaviour)] //! # #[behaviour(prelude = "libp2p_swarm::derive_prelude")] //! struct MyBehaviour { -//! blocked_peers: allow_block_list::Behaviour, +//! blocked_peers: allow_block_list::Behaviour, //! } //! //! # fn main() { //! let behaviour = MyBehaviour { -//! blocked_peers: allow_block_list::Behaviour::default() +//! blocked_peers: allow_block_list::Behaviour::default(), //! }; //! # } //! ``` -use libp2p_core::{Endpoint, Multiaddr}; +use std::{ + collections::{HashSet, VecDeque}, + convert::Infallible, + fmt, + task::{Context, Poll, Waker}, +}; + +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ dummy, CloseConnection, ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; -use std::collections::{HashSet, VecDeque}; -use std::fmt; -use std::task::{Context, Poll, Waker}; -use void::Void; /// A [`NetworkBehaviour`] that can act as an allow or block list. #[derive(Default, Debug)] @@ -93,44 +96,78 @@ pub struct BlockedPeers { } impl Behaviour { + /// Peers that are currently allowed. + pub fn allowed_peers(&self) -> &HashSet { + &self.state.peers + } + /// Allow connections to the given peer. - pub fn allow_peer(&mut self, peer: PeerId) { - self.state.peers.insert(peer); - if let Some(waker) = self.waker.take() { - waker.wake() + /// + /// Returns whether the peer was newly inserted. Does nothing if the peer + /// was already present in the set. + pub fn allow_peer(&mut self, peer: PeerId) -> bool { + let inserted = self.state.peers.insert(peer); + if inserted { + if let Some(waker) = self.waker.take() { + waker.wake() + } } + inserted } /// Disallow connections to the given peer. /// /// All active connections to this peer will be closed immediately. - pub fn disallow_peer(&mut self, peer: PeerId) { - self.state.peers.remove(&peer); - self.close_connections.push_back(peer); - if let Some(waker) = self.waker.take() { - waker.wake() + /// + /// Returns whether the peer was present in the set. Does nothing if the peer + /// was not present in the set. + pub fn disallow_peer(&mut self, peer: PeerId) -> bool { + let removed = self.state.peers.remove(&peer); + if removed { + self.close_connections.push_back(peer); + if let Some(waker) = self.waker.take() { + waker.wake() + } } + removed } } impl Behaviour { + /// Peers that are currently blocked. + pub fn blocked_peers(&self) -> &HashSet { + &self.state.peers + } + /// Block connections to a given peer. /// /// All active connections to this peer will be closed immediately. - pub fn block_peer(&mut self, peer: PeerId) { - self.state.peers.insert(peer); - self.close_connections.push_back(peer); - if let Some(waker) = self.waker.take() { - waker.wake() + /// + /// Returns whether the peer was newly inserted. Does nothing if the peer was already present in + /// the set. + pub fn block_peer(&mut self, peer: PeerId) -> bool { + let inserted = self.state.peers.insert(peer); + if inserted { + self.close_connections.push_back(peer); + if let Some(waker) = self.waker.take() { + waker.wake() + } } + inserted } /// Unblock connections to a given peer. - pub fn unblock_peer(&mut self, peer: PeerId) { - self.state.peers.remove(&peer); - if let Some(waker) = self.waker.take() { - waker.wake() + /// + /// Returns whether the peer was present in the set. Does nothing if the peer + /// was not present in the set. + pub fn unblock_peer(&mut self, peer: PeerId) -> bool { + let removed = self.state.peers.remove(&peer); + if removed { + if let Some(waker) = self.waker.take() { + waker.wake() + } } + removed } } @@ -191,7 +228,7 @@ where S: Enforce, { type ConnectionHandler = dummy::ConnectionHandler; - type ToSwarm = Void; + type ToSwarm = Infallible; fn handle_established_inbound_connection( &mut self, @@ -225,6 +262,7 @@ where peer: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { self.state.enforce(&peer)?; @@ -239,7 +277,9 @@ where _: ConnectionId, event: THandlerOutEvent, ) { - void::unreachable(event) + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(event) } fn poll( @@ -260,10 +300,11 @@ where #[cfg(test)] mod tests { - use super::*; use libp2p_swarm::{dial_opts::DialOpts, DialError, ListenError, Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt; + use super::*; + #[async_std::test] async fn cannot_dial_blocked_peer() { let mut dialer = Swarm::new_ephemeral(|_| Behaviour::::default()); diff --git a/misc/connection-limits/CHANGELOG.md b/misc/connection-limits/CHANGELOG.md index a5b68a6f51b..f2722b3745a 100644 --- a/misc/connection-limits/CHANGELOG.md +++ b/misc/connection-limits/CHANGELOG.md @@ -1,3 +1,17 @@ +## 0.4.1 + +- Deprecate `void` crate. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). + +## 0.4.0 + + + +## 0.3.1 + +- Add function to mutate `ConnectionLimits`. + See [PR 4964](https://github.com/libp2p/rust-libp2p/pull/4964). + ## 0.3.0 diff --git a/misc/connection-limits/Cargo.toml b/misc/connection-limits/Cargo.toml index 2aa26ad44f1..a0ecfd9da39 100644 --- a/misc/connection-limits/Cargo.toml +++ b/misc/connection-limits/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-connection-limits" edition = "2021" rust-version = { workspace = true } description = "Connection limits for libp2p." -version = "0.3.0" +version = "0.4.1" license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] @@ -13,7 +13,6 @@ categories = ["network-programming", "asynchronous"] libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true, features = ["peerid"] } -void = "1" [dev-dependencies] async-std = { version = "1.12.0", features = ["attributes"] } diff --git a/misc/connection-limits/src/lib.rs b/misc/connection-limits/src/lib.rs index cb12599ad79..c8df5be5653 100644 --- a/misc/connection-limits/src/lib.rs +++ b/misc/connection-limits/src/lib.rs @@ -18,29 +18,36 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use libp2p_core::{ConnectedPoint, Endpoint, Multiaddr}; +use std::{ + collections::{HashMap, HashSet}, + convert::Infallible, + fmt, + task::{Context, Poll}, +}; + +use libp2p_core::{transport::PortUse, ConnectedPoint, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ behaviour::{ConnectionEstablished, DialFailure, ListenFailure}, dummy, ConnectionClosed, ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; -use std::collections::{HashMap, HashSet}; -use std::fmt; -use std::task::{Context, Poll}; -use void::Void; /// A [`NetworkBehaviour`] that enforces a set of [`ConnectionLimits`]. /// -/// For these limits to take effect, this needs to be composed into the behaviour tree of your application. +/// For these limits to take effect, this needs to be composed +/// into the behaviour tree of your application. /// -/// If a connection is denied due to a limit, either a [`SwarmEvent::IncomingConnectionError`](libp2p_swarm::SwarmEvent::IncomingConnectionError) -/// or [`SwarmEvent::OutgoingConnectionError`](libp2p_swarm::SwarmEvent::OutgoingConnectionError) will be emitted. -/// The [`ListenError::Denied`](libp2p_swarm::ListenError::Denied) and respectively the [`DialError::Denied`](libp2p_swarm::DialError::Denied) variant -/// contain a [`ConnectionDenied`] type that can be downcast to [`Exceeded`] error if (and only if) **this** -/// behaviour denied the connection. +/// If a connection is denied due to a limit, either a +/// [`SwarmEvent::IncomingConnectionError`](libp2p_swarm::SwarmEvent::IncomingConnectionError) +/// or [`SwarmEvent::OutgoingConnectionError`](libp2p_swarm::SwarmEvent::OutgoingConnectionError) +/// will be emitted. The [`ListenError::Denied`](libp2p_swarm::ListenError::Denied) and respectively +/// the [`DialError::Denied`](libp2p_swarm::DialError::Denied) variant +/// contain a [`ConnectionDenied`] type that can be downcast to [`Exceeded`] error if (and only if) +/// **this** behaviour denied the connection. /// -/// If you employ multiple [`NetworkBehaviour`]s that manage connections, it may also be a different error. +/// If you employ multiple [`NetworkBehaviour`]s that manage connections, +/// it may also be a different error. /// /// # Example /// @@ -53,9 +60,9 @@ use void::Void; /// #[derive(NetworkBehaviour)] /// # #[behaviour(prelude = "libp2p_swarm::derive_prelude")] /// struct MyBehaviour { -/// identify: identify::Behaviour, -/// ping: ping::Behaviour, -/// limits: connection_limits::Behaviour +/// identify: identify::Behaviour, +/// ping: ping::Behaviour, +/// limits: connection_limits::Behaviour, /// } /// ``` pub struct Behaviour { @@ -79,6 +86,12 @@ impl Behaviour { established_per_peer: Default::default(), } } + + /// Returns a mutable reference to [`ConnectionLimits`]. + /// > **Note**: A new limit will not be enforced against existing connections. + pub fn limits_mut(&mut self) -> &mut ConnectionLimits { + &mut self.limits + } } fn check_limit(limit: Option, current: usize, kind: Kind) -> Result<(), ConnectionDenied> { @@ -197,7 +210,7 @@ impl ConnectionLimits { impl NetworkBehaviour for Behaviour { type ConnectionHandler = dummy::ConnectionHandler; - type ToSwarm = Void; + type ToSwarm = Infallible; fn handle_pending_inbound_connection( &mut self, @@ -272,6 +285,7 @@ impl NetworkBehaviour for Behaviour { peer: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { self.pending_outbound_connections.remove(&connection_id); @@ -348,7 +362,9 @@ impl NetworkBehaviour for Behaviour { _: ConnectionId, event: THandlerOutEvent, ) { - void::unreachable(event) + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(event) } fn poll(&mut self, _: &mut Context<'_>) -> Poll>> { @@ -358,14 +374,16 @@ impl NetworkBehaviour for Behaviour { #[cfg(test)] mod tests { - use super::*; use libp2p_swarm::{ - behaviour::toggle::Toggle, dial_opts::DialOpts, dial_opts::PeerCondition, DialError, - ListenError, Swarm, SwarmEvent, + behaviour::toggle::Toggle, + dial_opts::{DialOpts, PeerCondition}, + DialError, ListenError, Swarm, SwarmEvent, }; use libp2p_swarm_test::SwarmExt; use quickcheck::*; + use super::*; + #[test] fn max_outgoing() { use rand::Rng; @@ -542,7 +560,7 @@ mod tests { impl NetworkBehaviour for ConnectionDenier { type ConnectionHandler = dummy::ConnectionHandler; - type ToSwarm = Void; + type ToSwarm = Infallible; fn handle_established_inbound_connection( &mut self, @@ -563,6 +581,7 @@ mod tests { _peer: PeerId, _addr: &Multiaddr, _role_override: Endpoint, + _port_use: PortUse, ) -> Result, ConnectionDenied> { Err(ConnectionDenied::new(std::io::Error::new( std::io::ErrorKind::Other, @@ -578,7 +597,9 @@ mod tests { _connection_id: ConnectionId, event: THandlerOutEvent, ) { - void::unreachable(event) + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(event) } fn poll( diff --git a/misc/futures-bounded/CHANGELOG.md b/misc/futures-bounded/CHANGELOG.md deleted file mode 100644 index 72b0b4f457d..00000000000 --- a/misc/futures-bounded/CHANGELOG.md +++ /dev/null @@ -1,23 +0,0 @@ -## 0.2.3 - -- Introduce `FuturesTupleSet`, holding tuples of a `Future` together with an arbitrary piece of data. - See [PR 4841](https://github.com/libp2p/rust-libp2p/pull/4841). - -## 0.2.2 - -- Fix an issue where `{Futures,Stream}Map` returns `Poll::Pending` despite being ready after an item has been replaced as part of `try_push`. - See [PR 4865](https://github.com/libp2p/rust-libp2p/pull/4865). - -## 0.2.1 - -- Add `.len()` getter to `FuturesMap`, `FuturesSet`, `StreamMap` and `StreamSet`. - See [PR 4745](https://github.com/libp2p/rust-libp2p/pull/4745). - -## 0.2.0 - -- Add `StreamMap` type and remove `Future`-suffix from `PushError::ReplacedFuture` to reuse it for `StreamMap`. - See [PR 4616](https://github.com/libp2p/rust-libp2p/pull/4616). - -## 0.1.0 - -Initial release. diff --git a/misc/futures-bounded/Cargo.toml b/misc/futures-bounded/Cargo.toml deleted file mode 100644 index b375242bb3c..00000000000 --- a/misc/futures-bounded/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "futures-bounded" -version = "0.2.3" -edition = "2021" -rust-version.workspace = true -license = "MIT" -repository = "https://github.com/libp2p/rust-libp2p" -keywords = ["futures", "async", "backpressure"] -categories = ["data-structures", "asynchronous"] -description = "Utilities for bounding futures in size and time." -publish = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -futures-util = { version = "0.3.29" } -futures-timer = "3.0.2" - -[dev-dependencies] -tokio = { version = "1.34.0", features = ["macros", "rt", "sync"] } -futures = "0.3.28" - -[lints] -workspace = true diff --git a/misc/futures-bounded/src/futures_map.rs b/misc/futures-bounded/src/futures_map.rs deleted file mode 100644 index fba3543f67b..00000000000 --- a/misc/futures-bounded/src/futures_map.rs +++ /dev/null @@ -1,319 +0,0 @@ -use std::future::Future; -use std::hash::Hash; -use std::pin::Pin; -use std::task::{Context, Poll, Waker}; -use std::time::Duration; -use std::{future, mem}; - -use futures_timer::Delay; -use futures_util::future::BoxFuture; -use futures_util::stream::FuturesUnordered; -use futures_util::{FutureExt, StreamExt}; - -use crate::{PushError, Timeout}; - -/// Represents a map of [`Future`]s. -/// -/// Each future must finish within the specified time and the map never outgrows its capacity. -pub struct FuturesMap { - timeout: Duration, - capacity: usize, - inner: FuturesUnordered>>>, - empty_waker: Option, - full_waker: Option, -} - -impl FuturesMap { - pub fn new(timeout: Duration, capacity: usize) -> Self { - Self { - timeout, - capacity, - inner: Default::default(), - empty_waker: None, - full_waker: None, - } - } -} - -impl FuturesMap -where - ID: Clone + Hash + Eq + Send + Unpin + 'static, - O: 'static, -{ - /// Push a future into the map. - /// - /// This method inserts the given future with defined `future_id` to the set. - /// If the length of the map is equal to the capacity, this method returns [PushError::BeyondCapacity], - /// that contains the passed future. In that case, the future is not inserted to the map. - /// If a future with the given `future_id` already exists, then the old future will be replaced by a new one. - /// In that case, the returned error [PushError::Replaced] contains the old future. - pub fn try_push(&mut self, future_id: ID, future: F) -> Result<(), PushError>> - where - F: Future + Send + 'static, - { - if self.inner.len() >= self.capacity { - return Err(PushError::BeyondCapacity(future.boxed())); - } - - if let Some(waker) = self.empty_waker.take() { - waker.wake(); - } - - let old = self.remove(future_id.clone()); - self.inner.push(TaggedFuture { - tag: future_id, - inner: TimeoutFuture { - inner: future.boxed(), - timeout: Delay::new(self.timeout), - cancelled: false, - }, - }); - match old { - None => Ok(()), - Some(old) => Err(PushError::Replaced(old)), - } - } - - pub fn remove(&mut self, id: ID) -> Option> { - let tagged = self.inner.iter_mut().find(|s| s.tag == id)?; - - let inner = mem::replace(&mut tagged.inner.inner, future::pending().boxed()); - tagged.inner.cancelled = true; - - Some(inner) - } - - pub fn len(&self) -> usize { - self.inner.len() - } - - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } - - #[allow(unknown_lints, clippy::needless_pass_by_ref_mut)] // &mut Context is idiomatic. - pub fn poll_ready_unpin(&mut self, cx: &mut Context<'_>) -> Poll<()> { - if self.inner.len() < self.capacity { - return Poll::Ready(()); - } - - self.full_waker = Some(cx.waker().clone()); - - Poll::Pending - } - - pub fn poll_unpin(&mut self, cx: &mut Context<'_>) -> Poll<(ID, Result)> { - loop { - let maybe_result = futures_util::ready!(self.inner.poll_next_unpin(cx)); - - match maybe_result { - None => { - self.empty_waker = Some(cx.waker().clone()); - return Poll::Pending; - } - Some((id, Ok(output))) => return Poll::Ready((id, Ok(output))), - Some((id, Err(TimeoutError::Timeout))) => { - return Poll::Ready((id, Err(Timeout::new(self.timeout)))) - } - Some((_, Err(TimeoutError::Cancelled))) => continue, - } - } - } -} - -struct TimeoutFuture { - inner: F, - timeout: Delay, - - cancelled: bool, -} - -impl Future for TimeoutFuture -where - F: Future + Unpin, -{ - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if self.cancelled { - return Poll::Ready(Err(TimeoutError::Cancelled)); - } - - if self.timeout.poll_unpin(cx).is_ready() { - return Poll::Ready(Err(TimeoutError::Timeout)); - } - - self.inner.poll_unpin(cx).map(Ok) - } -} - -enum TimeoutError { - Timeout, - Cancelled, -} - -struct TaggedFuture { - tag: T, - inner: F, -} - -impl Future for TaggedFuture -where - T: Clone + Unpin, - F: Future + Unpin, -{ - type Output = (T, F::Output); - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let output = futures_util::ready!(self.inner.poll_unpin(cx)); - - Poll::Ready((self.tag.clone(), output)) - } -} - -#[cfg(test)] -mod tests { - use futures::channel::oneshot; - use futures_util::task::noop_waker_ref; - use std::future::{pending, poll_fn, ready}; - use std::pin::Pin; - use std::time::Instant; - - use super::*; - - #[test] - fn cannot_push_more_than_capacity_tasks() { - let mut futures = FuturesMap::new(Duration::from_secs(10), 1); - - assert!(futures.try_push("ID_1", ready(())).is_ok()); - matches!( - futures.try_push("ID_2", ready(())), - Err(PushError::BeyondCapacity(_)) - ); - } - - #[test] - fn cannot_push_the_same_id_few_times() { - let mut futures = FuturesMap::new(Duration::from_secs(10), 5); - - assert!(futures.try_push("ID", ready(())).is_ok()); - matches!( - futures.try_push("ID", ready(())), - Err(PushError::Replaced(_)) - ); - } - - #[tokio::test] - async fn futures_timeout() { - let mut futures = FuturesMap::new(Duration::from_millis(100), 1); - - let _ = futures.try_push("ID", pending::<()>()); - Delay::new(Duration::from_millis(150)).await; - let (_, result) = poll_fn(|cx| futures.poll_unpin(cx)).await; - - assert!(result.is_err()) - } - - #[test] - fn resources_of_removed_future_are_cleaned_up() { - let mut futures = FuturesMap::new(Duration::from_millis(100), 1); - - let _ = futures.try_push("ID", pending::<()>()); - futures.remove("ID"); - - let poll = futures.poll_unpin(&mut Context::from_waker(noop_waker_ref())); - assert!(poll.is_pending()); - - assert_eq!(futures.len(), 0); - } - - #[tokio::test] - async fn replaced_pending_future_is_polled() { - let mut streams = FuturesMap::new(Duration::from_millis(100), 3); - - let (_tx1, rx1) = oneshot::channel(); - let (tx2, rx2) = oneshot::channel(); - - let _ = streams.try_push("ID1", rx1); - let _ = streams.try_push("ID2", rx2); - - let _ = tx2.send(2); - let (id, res) = poll_fn(|cx| streams.poll_unpin(cx)).await; - assert_eq!(id, "ID2"); - assert_eq!(res.unwrap().unwrap(), 2); - - let (new_tx1, new_rx1) = oneshot::channel(); - let replaced = streams.try_push("ID1", new_rx1); - assert!(matches!(replaced.unwrap_err(), PushError::Replaced(_))); - - let _ = new_tx1.send(4); - let (id, res) = poll_fn(|cx| streams.poll_unpin(cx)).await; - - assert_eq!(id, "ID1"); - assert_eq!(res.unwrap().unwrap(), 4); - } - - // Each future causes a delay, `Task` only has a capacity of 1, meaning they must be processed in sequence. - // We stop after NUM_FUTURES tasks, meaning the overall execution must at least take DELAY * NUM_FUTURES. - #[tokio::test] - async fn backpressure() { - const DELAY: Duration = Duration::from_millis(100); - const NUM_FUTURES: u32 = 10; - - let start = Instant::now(); - Task::new(DELAY, NUM_FUTURES, 1).await; - let duration = start.elapsed(); - - assert!(duration >= DELAY * NUM_FUTURES); - } - - struct Task { - future: Duration, - num_futures: usize, - num_processed: usize, - inner: FuturesMap, - } - - impl Task { - fn new(future: Duration, num_futures: u32, capacity: usize) -> Self { - Self { - future, - num_futures: num_futures as usize, - num_processed: 0, - inner: FuturesMap::new(Duration::from_secs(60), capacity), - } - } - } - - impl Future for Task { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.get_mut(); - - while this.num_processed < this.num_futures { - if let Poll::Ready((_, result)) = this.inner.poll_unpin(cx) { - if result.is_err() { - panic!("Timeout is great than future delay") - } - - this.num_processed += 1; - continue; - } - - if let Poll::Ready(()) = this.inner.poll_ready_unpin(cx) { - // We push the constant future's ID to prove that user can use the same ID - // if the future was finished - let maybe_future = this.inner.try_push(1u8, Delay::new(this.future)); - assert!(maybe_future.is_ok(), "we polled for readiness"); - - continue; - } - - return Poll::Pending; - } - - Poll::Ready(()) - } - } -} diff --git a/misc/futures-bounded/src/futures_set.rs b/misc/futures-bounded/src/futures_set.rs deleted file mode 100644 index af7cedfcc85..00000000000 --- a/misc/futures-bounded/src/futures_set.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::future::Future; -use std::task::{ready, Context, Poll}; -use std::time::Duration; - -use futures_util::future::BoxFuture; - -use crate::{FuturesMap, PushError, Timeout}; - -/// Represents a list of [Future]s. -/// -/// Each future must finish within the specified time and the list never outgrows its capacity. -pub struct FuturesSet { - id: u32, - inner: FuturesMap, -} - -impl FuturesSet { - pub fn new(timeout: Duration, capacity: usize) -> Self { - Self { - id: 0, - inner: FuturesMap::new(timeout, capacity), - } - } -} - -impl FuturesSet -where - O: 'static, -{ - /// Push a future into the list. - /// - /// This method adds the given future to the list. - /// If the length of the list is equal to the capacity, this method returns a error that contains the passed future. - /// In that case, the future is not added to the set. - pub fn try_push(&mut self, future: F) -> Result<(), BoxFuture> - where - F: Future + Send + 'static, - { - self.id = self.id.wrapping_add(1); - - match self.inner.try_push(self.id, future) { - Ok(()) => Ok(()), - Err(PushError::BeyondCapacity(w)) => Err(w), - Err(PushError::Replaced(_)) => unreachable!("we never reuse IDs"), - } - } - - pub fn len(&self) -> usize { - self.inner.len() - } - - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } - - pub fn poll_ready_unpin(&mut self, cx: &mut Context<'_>) -> Poll<()> { - self.inner.poll_ready_unpin(cx) - } - - pub fn poll_unpin(&mut self, cx: &mut Context<'_>) -> Poll> { - let (_, res) = ready!(self.inner.poll_unpin(cx)); - - Poll::Ready(res) - } -} diff --git a/misc/futures-bounded/src/futures_tuple_set.rs b/misc/futures-bounded/src/futures_tuple_set.rs deleted file mode 100644 index e19b236aaf8..00000000000 --- a/misc/futures-bounded/src/futures_tuple_set.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::collections::HashMap; -use std::future::Future; -use std::task::{ready, Context, Poll}; -use std::time::Duration; - -use futures_util::future::BoxFuture; - -use crate::{FuturesMap, PushError, Timeout}; - -/// Represents a list of tuples of a [Future] and an associated piece of data. -/// -/// Each future must finish within the specified time and the list never outgrows its capacity. -pub struct FuturesTupleSet { - id: u32, - inner: FuturesMap, - data: HashMap, -} - -impl FuturesTupleSet { - pub fn new(timeout: Duration, capacity: usize) -> Self { - Self { - id: 0, - inner: FuturesMap::new(timeout, capacity), - data: HashMap::new(), - } - } -} - -impl FuturesTupleSet -where - O: 'static, -{ - /// Push a future into the list. - /// - /// This method adds the given future to the list. - /// If the length of the list is equal to the capacity, this method returns a error that contains the passed future. - /// In that case, the future is not added to the set. - pub fn try_push(&mut self, future: F, data: D) -> Result<(), (BoxFuture, D)> - where - F: Future + Send + 'static, - { - self.id = self.id.wrapping_add(1); - - match self.inner.try_push(self.id, future) { - Ok(()) => {} - Err(PushError::BeyondCapacity(w)) => return Err((w, data)), - Err(PushError::Replaced(_)) => unreachable!("we never reuse IDs"), - } - self.data.insert(self.id, data); - - Ok(()) - } - - pub fn len(&self) -> usize { - self.inner.len() - } - - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } - - pub fn poll_ready_unpin(&mut self, cx: &mut Context<'_>) -> Poll<()> { - self.inner.poll_ready_unpin(cx) - } - - pub fn poll_unpin(&mut self, cx: &mut Context<'_>) -> Poll<(Result, D)> { - let (id, res) = ready!(self.inner.poll_unpin(cx)); - let data = self.data.remove(&id).expect("must have data for future"); - - Poll::Ready((res, data)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use futures_util::future::poll_fn; - use futures_util::FutureExt; - use std::future::ready; - - #[test] - fn tracks_associated_data_of_future() { - let mut set = FuturesTupleSet::new(Duration::from_secs(10), 10); - - let _ = set.try_push(ready(1), 1); - let _ = set.try_push(ready(2), 2); - - let (res1, data1) = poll_fn(|cx| set.poll_unpin(cx)).now_or_never().unwrap(); - let (res2, data2) = poll_fn(|cx| set.poll_unpin(cx)).now_or_never().unwrap(); - - assert_eq!(res1.unwrap(), data1); - assert_eq!(res2.unwrap(), data2); - } -} diff --git a/misc/futures-bounded/src/lib.rs b/misc/futures-bounded/src/lib.rs deleted file mode 100644 index da8483a595f..00000000000 --- a/misc/futures-bounded/src/lib.rs +++ /dev/null @@ -1,46 +0,0 @@ -mod futures_map; -mod futures_set; -mod futures_tuple_set; -mod stream_map; -mod stream_set; - -pub use futures_map::FuturesMap; -pub use futures_set::FuturesSet; -pub use futures_tuple_set::FuturesTupleSet; -pub use stream_map::StreamMap; -pub use stream_set::StreamSet; - -use std::fmt; -use std::fmt::Formatter; -use std::time::Duration; - -/// A future failed to complete within the given timeout. -#[derive(Debug)] -pub struct Timeout { - limit: Duration, -} - -impl Timeout { - fn new(duration: Duration) -> Self { - Self { limit: duration } - } -} - -impl fmt::Display for Timeout { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "future failed to complete within {:?}", self.limit) - } -} - -/// Error of a future pushing -#[derive(PartialEq, Debug)] -pub enum PushError { - /// The length of the set is equal to the capacity - BeyondCapacity(T), - /// The map already contained an item with this key. - /// - /// The old item is returned. - Replaced(T), -} - -impl std::error::Error for Timeout {} diff --git a/misc/futures-bounded/src/stream_map.rs b/misc/futures-bounded/src/stream_map.rs deleted file mode 100644 index 8464f432d02..00000000000 --- a/misc/futures-bounded/src/stream_map.rs +++ /dev/null @@ -1,362 +0,0 @@ -use std::mem; -use std::pin::Pin; -use std::task::{Context, Poll, Waker}; -use std::time::Duration; - -use futures_timer::Delay; -use futures_util::stream::{BoxStream, SelectAll}; -use futures_util::{stream, FutureExt, Stream, StreamExt}; - -use crate::{PushError, Timeout}; - -/// Represents a map of [`Stream`]s. -/// -/// Each stream must finish within the specified time and the map never outgrows its capacity. -pub struct StreamMap { - timeout: Duration, - capacity: usize, - inner: SelectAll>>>, - empty_waker: Option, - full_waker: Option, -} - -impl StreamMap -where - ID: Clone + Unpin, -{ - pub fn new(timeout: Duration, capacity: usize) -> Self { - Self { - timeout, - capacity, - inner: Default::default(), - empty_waker: None, - full_waker: None, - } - } -} - -impl StreamMap -where - ID: Clone + PartialEq + Send + Unpin + 'static, - O: Send + 'static, -{ - /// Push a stream into the map. - pub fn try_push(&mut self, id: ID, stream: F) -> Result<(), PushError>> - where - F: Stream + Send + 'static, - { - if self.inner.len() >= self.capacity { - return Err(PushError::BeyondCapacity(stream.boxed())); - } - - if let Some(waker) = self.empty_waker.take() { - waker.wake(); - } - - let old = self.remove(id.clone()); - self.inner.push(TaggedStream::new( - id, - TimeoutStream { - inner: stream.boxed(), - timeout: Delay::new(self.timeout), - }, - )); - - match old { - None => Ok(()), - Some(old) => Err(PushError::Replaced(old)), - } - } - - pub fn remove(&mut self, id: ID) -> Option> { - let tagged = self.inner.iter_mut().find(|s| s.key == id)?; - - let inner = mem::replace(&mut tagged.inner.inner, stream::pending().boxed()); - tagged.exhausted = true; // Setting this will emit `None` on the next poll and ensure `SelectAll` cleans up the resources. - - Some(inner) - } - - pub fn len(&self) -> usize { - self.inner.len() - } - - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } - - #[allow(unknown_lints, clippy::needless_pass_by_ref_mut)] // &mut Context is idiomatic. - pub fn poll_ready_unpin(&mut self, cx: &mut Context<'_>) -> Poll<()> { - if self.inner.len() < self.capacity { - return Poll::Ready(()); - } - - self.full_waker = Some(cx.waker().clone()); - - Poll::Pending - } - - pub fn poll_next_unpin( - &mut self, - cx: &mut Context<'_>, - ) -> Poll<(ID, Option>)> { - match futures_util::ready!(self.inner.poll_next_unpin(cx)) { - None => { - self.empty_waker = Some(cx.waker().clone()); - Poll::Pending - } - Some((id, Some(Ok(output)))) => Poll::Ready((id, Some(Ok(output)))), - Some((id, Some(Err(())))) => { - self.remove(id.clone()); // Remove stream, otherwise we keep reporting the timeout. - - Poll::Ready((id, Some(Err(Timeout::new(self.timeout))))) - } - Some((id, None)) => Poll::Ready((id, None)), - } - } -} - -struct TimeoutStream { - inner: S, - timeout: Delay, -} - -impl Stream for TimeoutStream -where - F: Stream + Unpin, -{ - type Item = Result; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if self.timeout.poll_unpin(cx).is_ready() { - return Poll::Ready(Some(Err(()))); - } - - self.inner.poll_next_unpin(cx).map(|a| a.map(Ok)) - } -} - -struct TaggedStream { - key: K, - inner: S, - - exhausted: bool, -} - -impl TaggedStream { - fn new(key: K, inner: S) -> Self { - Self { - key, - inner, - exhausted: false, - } - } -} - -impl Stream for TaggedStream -where - K: Clone + Unpin, - S: Stream + Unpin, -{ - type Item = (K, Option); - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if self.exhausted { - return Poll::Ready(None); - } - - match futures_util::ready!(self.inner.poll_next_unpin(cx)) { - Some(item) => Poll::Ready(Some((self.key.clone(), Some(item)))), - None => { - self.exhausted = true; - - Poll::Ready(Some((self.key.clone(), None))) - } - } - } -} - -#[cfg(test)] -mod tests { - use futures::channel::mpsc; - use futures_util::stream::{once, pending}; - use futures_util::SinkExt; - use std::future::{poll_fn, ready, Future}; - use std::pin::Pin; - use std::time::Instant; - - use super::*; - - #[test] - fn cannot_push_more_than_capacity_tasks() { - let mut streams = StreamMap::new(Duration::from_secs(10), 1); - - assert!(streams.try_push("ID_1", once(ready(()))).is_ok()); - matches!( - streams.try_push("ID_2", once(ready(()))), - Err(PushError::BeyondCapacity(_)) - ); - } - - #[test] - fn cannot_push_the_same_id_few_times() { - let mut streams = StreamMap::new(Duration::from_secs(10), 5); - - assert!(streams.try_push("ID", once(ready(()))).is_ok()); - matches!( - streams.try_push("ID", once(ready(()))), - Err(PushError::Replaced(_)) - ); - } - - #[tokio::test] - async fn streams_timeout() { - let mut streams = StreamMap::new(Duration::from_millis(100), 1); - - let _ = streams.try_push("ID", pending::<()>()); - Delay::new(Duration::from_millis(150)).await; - let (_, result) = poll_fn(|cx| streams.poll_next_unpin(cx)).await; - - assert!(result.unwrap().is_err()) - } - - #[tokio::test] - async fn timed_out_stream_gets_removed() { - let mut streams = StreamMap::new(Duration::from_millis(100), 1); - - let _ = streams.try_push("ID", pending::<()>()); - Delay::new(Duration::from_millis(150)).await; - poll_fn(|cx| streams.poll_next_unpin(cx)).await; - - let poll = streams.poll_next_unpin(&mut Context::from_waker( - futures_util::task::noop_waker_ref(), - )); - assert!(poll.is_pending()) - } - - #[test] - fn removing_stream() { - let mut streams = StreamMap::new(Duration::from_millis(100), 1); - - let _ = streams.try_push("ID", stream::once(ready(()))); - - { - let cancelled_stream = streams.remove("ID"); - assert!(cancelled_stream.is_some()); - } - - let poll = streams.poll_next_unpin(&mut Context::from_waker( - futures_util::task::noop_waker_ref(), - )); - - assert!(poll.is_pending()); - assert_eq!( - streams.len(), - 0, - "resources of cancelled streams are cleaned up properly" - ); - } - - #[tokio::test] - async fn replaced_stream_is_still_registered() { - let mut streams = StreamMap::new(Duration::from_millis(100), 3); - - let (mut tx1, rx1) = mpsc::channel(5); - let (mut tx2, rx2) = mpsc::channel(5); - - let _ = streams.try_push("ID1", rx1); - let _ = streams.try_push("ID2", rx2); - - let _ = tx2.send(2).await; - let _ = tx1.send(1).await; - let _ = tx2.send(3).await; - let (id, res) = poll_fn(|cx| streams.poll_next_unpin(cx)).await; - assert_eq!(id, "ID1"); - assert_eq!(res.unwrap().unwrap(), 1); - let (id, res) = poll_fn(|cx| streams.poll_next_unpin(cx)).await; - assert_eq!(id, "ID2"); - assert_eq!(res.unwrap().unwrap(), 2); - let (id, res) = poll_fn(|cx| streams.poll_next_unpin(cx)).await; - assert_eq!(id, "ID2"); - assert_eq!(res.unwrap().unwrap(), 3); - - let (mut new_tx1, new_rx1) = mpsc::channel(5); - let replaced = streams.try_push("ID1", new_rx1); - assert!(matches!(replaced.unwrap_err(), PushError::Replaced(_))); - - let _ = new_tx1.send(4).await; - let (id, res) = poll_fn(|cx| streams.poll_next_unpin(cx)).await; - - assert_eq!(id, "ID1"); - assert_eq!(res.unwrap().unwrap(), 4); - } - - // Each stream emits 1 item with delay, `Task` only has a capacity of 1, meaning they must be processed in sequence. - // We stop after NUM_STREAMS tasks, meaning the overall execution must at least take DELAY * NUM_STREAMS. - #[tokio::test] - async fn backpressure() { - const DELAY: Duration = Duration::from_millis(100); - const NUM_STREAMS: u32 = 10; - - let start = Instant::now(); - Task::new(DELAY, NUM_STREAMS, 1).await; - let duration = start.elapsed(); - - assert!(duration >= DELAY * NUM_STREAMS); - } - - struct Task { - item_delay: Duration, - num_streams: usize, - num_processed: usize, - inner: StreamMap, - } - - impl Task { - fn new(item_delay: Duration, num_streams: u32, capacity: usize) -> Self { - Self { - item_delay, - num_streams: num_streams as usize, - num_processed: 0, - inner: StreamMap::new(Duration::from_secs(60), capacity), - } - } - } - - impl Future for Task { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.get_mut(); - - while this.num_processed < this.num_streams { - match this.inner.poll_next_unpin(cx) { - Poll::Ready((_, Some(result))) => { - if result.is_err() { - panic!("Timeout is great than item delay") - } - - this.num_processed += 1; - continue; - } - Poll::Ready((_, None)) => { - continue; - } - _ => {} - } - - if let Poll::Ready(()) = this.inner.poll_ready_unpin(cx) { - // We push the constant ID to prove that user can use the same ID if the stream was finished - let maybe_future = this.inner.try_push(1u8, once(Delay::new(this.item_delay))); - assert!(maybe_future.is_ok(), "we polled for readiness"); - - continue; - } - - return Poll::Pending; - } - - Poll::Ready(()) - } - } -} diff --git a/misc/futures-bounded/src/stream_set.rs b/misc/futures-bounded/src/stream_set.rs deleted file mode 100644 index bb32835065f..00000000000 --- a/misc/futures-bounded/src/stream_set.rs +++ /dev/null @@ -1,64 +0,0 @@ -use futures_util::stream::BoxStream; -use futures_util::Stream; -use std::task::{ready, Context, Poll}; -use std::time::Duration; - -use crate::{PushError, StreamMap, Timeout}; - -/// Represents a set of [Stream]s. -/// -/// Each stream must finish within the specified time and the list never outgrows its capacity. -pub struct StreamSet { - id: u32, - inner: StreamMap, -} - -impl StreamSet { - pub fn new(timeout: Duration, capacity: usize) -> Self { - Self { - id: 0, - inner: StreamMap::new(timeout, capacity), - } - } -} - -impl StreamSet -where - O: Send + 'static, -{ - /// Push a stream into the list. - /// - /// This method adds the given stream to the list. - /// If the length of the list is equal to the capacity, this method returns a error that contains the passed stream. - /// In that case, the stream is not added to the set. - pub fn try_push(&mut self, stream: F) -> Result<(), BoxStream> - where - F: Stream + Send + 'static, - { - self.id = self.id.wrapping_add(1); - - match self.inner.try_push(self.id, stream) { - Ok(()) => Ok(()), - Err(PushError::BeyondCapacity(w)) => Err(w), - Err(PushError::Replaced(_)) => unreachable!("we never reuse IDs"), - } - } - - pub fn len(&self) -> usize { - self.inner.len() - } - - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } - - pub fn poll_ready_unpin(&mut self, cx: &mut Context<'_>) -> Poll<()> { - self.inner.poll_ready_unpin(cx) - } - - pub fn poll_next_unpin(&mut self, cx: &mut Context<'_>) -> Poll>> { - let (_, res) = ready!(self.inner.poll_next_unpin(cx)); - - Poll::Ready(res) - } -} diff --git a/misc/keygen/Cargo.toml b/misc/keygen/Cargo.toml index 9c1c37bd428..c5e96553a5c 100644 --- a/misc/keygen/Cargo.toml +++ b/misc/keygen/Cargo.toml @@ -13,12 +13,11 @@ publish = false release = false [dependencies] -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.5.6", features = ["derive"] } zeroize = "1" -serde = { version = "1.0.193", features = ["derive"] } -serde_json = "1.0.108" -libp2p-core = { workspace = true } -base64 = "0.21.5" +serde = { version = "1.0.203", features = ["derive"] } +serde_json = "1.0.117" +base64 = "0.22.1" libp2p-identity = { workspace = true } [lints] diff --git a/misc/keygen/src/config.rs b/misc/keygen/src/config.rs index e6c563b3c32..7d46b1849bd 100644 --- a/misc/keygen/src/config.rs +++ b/misc/keygen/src/config.rs @@ -1,10 +1,8 @@ +use std::{error::Error, path::Path}; + use base64::prelude::*; +use libp2p_identity::{Keypair, PeerId}; use serde::{Deserialize, Serialize}; -use std::error::Error; -use std::path::Path; - -use libp2p_identity::Keypair; -use libp2p_identity::PeerId; #[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] diff --git a/misc/keygen/src/main.rs b/misc/keygen/src/main.rs index 64d98005369..4c4d3bfbf66 100644 --- a/misc/keygen/src/main.rs +++ b/misc/keygen/src/main.rs @@ -1,9 +1,12 @@ +use std::{ + error::Error, + path::PathBuf, + str::{self, FromStr}, + sync::mpsc, + thread, +}; + use base64::prelude::*; -use std::error::Error; -use std::path::PathBuf; -use std::str::{self, FromStr}; -use std::sync::mpsc; -use std::thread; mod config; diff --git a/misc/memory-connection-limits/CHANGELOG.md b/misc/memory-connection-limits/CHANGELOG.md index fc598872d50..bf198e27c65 100644 --- a/misc/memory-connection-limits/CHANGELOG.md +++ b/misc/memory-connection-limits/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.3.1 + +- Deprecate `void` crate. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). + +## 0.3.0 + + + ## 0.2.0 diff --git a/misc/memory-connection-limits/Cargo.toml b/misc/memory-connection-limits/Cargo.toml index ae6bb386373..2d04b6cf2ac 100644 --- a/misc/memory-connection-limits/Cargo.toml +++ b/misc/memory-connection-limits/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-memory-connection-limits" edition = "2021" rust-version = { workspace = true } description = "Memory usage based connection limits for libp2p." -version = "0.2.0" +version = "0.3.1" license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] @@ -14,16 +14,13 @@ memory-stats = { version = "1", features = ["always_use_statm"] } libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true, features = ["peerid"] } -sysinfo = "0.29" -tracing = "0.1.37" -void = "1" +sysinfo = "0.33" +tracing = { workspace = true } [dev-dependencies] -async-std = { version = "1.12.0", features = ["attributes"] } libp2p-identify = { workspace = true } libp2p-swarm-derive = { path = "../../swarm-derive" } libp2p-swarm-test = { path = "../../swarm-test" } -rand = "0.8.5" [lints] workspace = true diff --git a/misc/memory-connection-limits/src/lib.rs b/misc/memory-connection-limits/src/lib.rs index ac911654979..28fa5598481 100644 --- a/misc/memory-connection-limits/src/lib.rs +++ b/misc/memory-connection-limits/src/lib.rs @@ -18,34 +18,40 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use libp2p_core::{Endpoint, Multiaddr}; -use libp2p_identity::PeerId; -use libp2p_swarm::{ - dummy, ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, THandler, THandlerInEvent, - THandlerOutEvent, ToSwarm, -}; -use void::Void; - use std::{ + convert::Infallible, fmt, task::{Context, Poll}, time::{Duration, Instant}, }; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; +use libp2p_identity::PeerId; +use libp2p_swarm::{ + dummy, ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, THandler, THandlerInEvent, + THandlerOutEvent, ToSwarm, +}; +use sysinfo::MemoryRefreshKind; + /// A [`NetworkBehaviour`] that enforces a set of memory usage based limits. /// -/// For these limits to take effect, this needs to be composed into the behaviour tree of your application. +/// For these limits to take effect, this needs to be composed +/// into the behaviour tree of your application. /// -/// If a connection is denied due to a limit, either a [`SwarmEvent::IncomingConnectionError`](libp2p_swarm::SwarmEvent::IncomingConnectionError) -/// or [`SwarmEvent::OutgoingConnectionError`](libp2p_swarm::SwarmEvent::OutgoingConnectionError) will be emitted. -/// The [`ListenError::Denied`](libp2p_swarm::ListenError::Denied) and respectively the [`DialError::Denied`](libp2p_swarm::DialError::Denied) variant -/// contain a [`ConnectionDenied`] type that can be downcast to [`MemoryUsageLimitExceeded`] error if (and only if) **this** -/// behaviour denied the connection. +/// If a connection is denied due to a limit, either a +/// [`SwarmEvent::IncomingConnectionError`](libp2p_swarm::SwarmEvent::IncomingConnectionError) +/// or [`SwarmEvent::OutgoingConnectionError`](libp2p_swarm::SwarmEvent::OutgoingConnectionError) +/// will be emitted. The [`ListenError::Denied`](libp2p_swarm::ListenError::Denied) and respectively +/// the [`DialError::Denied`](libp2p_swarm::DialError::Denied) variant +/// contain a [`ConnectionDenied`] type that can be downcast to [`MemoryUsageLimitExceeded`] error +/// if (and only if) **this** behaviour denied the connection. /// -/// If you employ multiple [`NetworkBehaviour`]s that manage connections, it may also be a different error. +/// If you employ multiple [`NetworkBehaviour`]s that manage connections, +/// it may also be a different error. /// /// [Behaviour::with_max_bytes] and [Behaviour::with_max_percentage] are mutually exclusive. -/// If you need to employ both of them, compose two instances of [Behaviour] into your custom behaviour. +/// If you need to employ both of them, +/// compose two instances of [Behaviour] into your custom behaviour. /// /// # Example /// @@ -57,8 +63,8 @@ use std::{ /// #[derive(NetworkBehaviour)] /// # #[behaviour(prelude = "libp2p_swarm::derive_prelude")] /// struct MyBehaviour { -/// identify: identify::Behaviour, -/// limits: memory_connection_limits::Behaviour +/// identify: identify::Behaviour, +/// limits: memory_connection_limits::Behaviour, /// } /// ``` pub struct Behaviour { @@ -67,7 +73,8 @@ pub struct Behaviour { last_refreshed: Instant, } -/// The maximum duration for which the retrieved memory-stats of the process are allowed to be stale. +/// The maximum duration for which the retrieved memory-stats +/// of the process are allowed to be stale. /// /// Once exceeded, we will retrieve new stats. const MAX_STALE_DURATION: Duration = Duration::from_millis(100); @@ -90,10 +97,12 @@ impl Behaviour { /// /// New inbound and outbound connections will be denied when the threshold is reached. pub fn with_max_percentage(percentage: f64) -> Self { - use sysinfo::{RefreshKind, SystemExt}; + use sysinfo::{RefreshKind, System}; - let system_memory_bytes = - sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()).total_memory(); + let system_memory_bytes = System::new_with_specifics( + RefreshKind::default().with_memory(MemoryRefreshKind::default().with_ram()), + ) + .total_memory(); Self::with_max_bytes((system_memory_bytes as f64 * percentage).round() as usize) } @@ -136,7 +145,7 @@ impl Behaviour { impl NetworkBehaviour for Behaviour { type ConnectionHandler = dummy::ConnectionHandler; - type ToSwarm = Void; + type ToSwarm = Infallible; fn handle_pending_inbound_connection( &mut self, @@ -174,6 +183,7 @@ impl NetworkBehaviour for Behaviour { _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(dummy::ConnectionHandler) } @@ -186,7 +196,9 @@ impl NetworkBehaviour for Behaviour { _: ConnectionId, event: THandlerOutEvent, ) { - void::unreachable(event) + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(event) } fn poll(&mut self, _: &mut Context<'_>) -> Poll>> { diff --git a/misc/memory-connection-limits/tests/max_bytes.rs b/misc/memory-connection-limits/tests/max_bytes.rs index 7f89e2c7a9a..e82ad67d076 100644 --- a/misc/memory-connection-limits/tests/max_bytes.rs +++ b/misc/memory-connection-limits/tests/max_bytes.rs @@ -20,14 +20,14 @@ mod util; +use std::time::Duration; + use libp2p_core::Multiaddr; use libp2p_identity::PeerId; use libp2p_memory_connection_limits::*; -use std::time::Duration; -use util::*; - use libp2p_swarm::{dial_opts::DialOpts, DialError, Swarm}; use libp2p_swarm_test::SwarmExt; +use util::*; #[test] fn max_bytes() { @@ -69,7 +69,8 @@ fn max_bytes() { .expect("Unexpected connection limit."); } - std::thread::sleep(Duration::from_millis(100)); // Memory stats are only updated every 100ms internally, ensure they are up-to-date when we try to exceed it. + std::thread::sleep(Duration::from_millis(100)); // Memory stats are only updated every 100ms internally, ensure they are up-to-date when we try + // to exceed it. match network .dial( diff --git a/misc/memory-connection-limits/tests/max_percentage.rs b/misc/memory-connection-limits/tests/max_percentage.rs index daee20703ee..bdadad437b8 100644 --- a/misc/memory-connection-limits/tests/max_percentage.rs +++ b/misc/memory-connection-limits/tests/max_percentage.rs @@ -20,23 +20,25 @@ mod util; +use std::time::Duration; + use libp2p_core::Multiaddr; use libp2p_identity::PeerId; use libp2p_memory_connection_limits::*; -use std::time::Duration; -use sysinfo::{RefreshKind, SystemExt}; -use util::*; - use libp2p_swarm::{ dial_opts::{DialOpts, PeerCondition}, DialError, Swarm, }; use libp2p_swarm_test::SwarmExt; +use sysinfo::{MemoryRefreshKind, RefreshKind}; +use util::*; #[test] fn max_percentage() { const CONNECTION_LIMIT: usize = 20; - let system_info = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()); + let system_info = sysinfo::System::new_with_specifics( + RefreshKind::default().with_memory(MemoryRefreshKind::default().with_ram()), + ); let mut network = Swarm::new_ephemeral(|_| TestBehaviour { connection_limits: Behaviour::with_max_percentage(0.1), @@ -74,7 +76,9 @@ fn max_percentage() { .expect("Unexpected connection limit."); } - std::thread::sleep(Duration::from_millis(100)); // Memory stats are only updated every 100ms internally, ensure they are up-to-date when we try to exceed it. + // Memory stats are only updated every 100ms internally, + // ensure they are up-to-date when we try to exceed it. + std::thread::sleep(Duration::from_millis(100)); match network .dial( diff --git a/misc/memory-connection-limits/tests/util.rs b/misc/memory-connection-limits/tests/util/mod.rs similarity index 92% rename from misc/memory-connection-limits/tests/util.rs rename to misc/memory-connection-limits/tests/util/mod.rs index f40ce319929..205f4d13bc4 100644 --- a/misc/memory-connection-limits/tests/util.rs +++ b/misc/memory-connection-limits/tests/util/mod.rs @@ -18,15 +18,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::task::{Context, Poll}; +use std::{ + convert::Infallible, + task::{Context, Poll}, +}; -use libp2p_core::{Endpoint, Multiaddr}; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ dummy, ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; -use void::Void; #[derive(libp2p_swarm_derive::NetworkBehaviour)] #[behaviour(prelude = "libp2p_swarm::derive_prelude")] @@ -62,7 +64,7 @@ impl NetworkBehaviour for ConsumeMemoryBehaviour { type ConnectionHandler = dummy::ConnectionHandler; - type ToSwarm = Void; + type ToSwarm = Infallible; fn handle_pending_inbound_connection( &mut self, @@ -102,6 +104,7 @@ impl NetworkBehaviour _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { self.handle_established(); Ok(dummy::ConnectionHandler) @@ -115,7 +118,9 @@ impl NetworkBehaviour _: ConnectionId, event: THandlerOutEvent, ) { - void::unreachable(event) + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(event) } fn poll(&mut self, _: &mut Context<'_>) -> Poll>> { diff --git a/misc/metrics/CHANGELOG.md b/misc/metrics/CHANGELOG.md index 67c304680db..bd109c42811 100644 --- a/misc/metrics/CHANGELOG.md +++ b/misc/metrics/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.15.0 +- Use `web-time` instead of `instant`. + See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). + ## 0.14.1 - Add `BandwidthTransport`, wrapping an existing `Transport`, exposing Prometheus bandwidth metrics. diff --git a/misc/metrics/Cargo.toml b/misc/metrics/Cargo.toml index 0607808214c..0b7a3c93b2f 100644 --- a/misc/metrics/Cargo.toml +++ b/misc/metrics/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-metrics" edition = "2021" rust-version = { workspace = true } description = "Metrics for libp2p" -version = "0.14.1" +version = "0.15.0" authors = ["Max Inden "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -19,8 +19,8 @@ ping = ["libp2p-ping"] relay = ["libp2p-relay"] [dependencies] -futures = "0.3.26" -instant = "0.1.12" +futures = { workspace = true } +web-time = { workspace = true } libp2p-core = { workspace = true } libp2p-dcutr = { workspace = true, optional = true } libp2p-gossipsub = { workspace = true, optional = true } @@ -30,7 +30,7 @@ libp2p-kad = { workspace = true, optional = true } libp2p-ping = { workspace = true, optional = true } libp2p-relay = { workspace = true, optional = true } libp2p-swarm = { workspace = true } -pin-project = "1.0.0" +pin-project = "1.1.5" prometheus-client = { workspace = true } [dev-dependencies] @@ -40,8 +40,6 @@ libp2p-identity = { workspace = true, features = ["rand"] } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustc-args = ["--cfg", "docsrs"] -rustdoc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/misc/metrics/src/bandwidth.rs b/misc/metrics/src/bandwidth.rs index 2792e00612c..b6308ed1b51 100644 --- a/misc/metrics/src/bandwidth.rs +++ b/misc/metrics/src/bandwidth.rs @@ -1,4 +1,10 @@ -use crate::protocol_stack; +use std::{ + convert::TryFrom as _, + io, + pin::Pin, + task::{Context, Poll}, +}; + use futures::{ future::{MapOk, TryFutureExt}, io::{IoSlice, IoSliceMut}, @@ -7,7 +13,7 @@ use futures::{ }; use libp2p_core::{ muxing::{StreamMuxer, StreamMuxerEvent}, - transport::{ListenerId, TransportError, TransportEvent}, + transport::{DialOpts, ListenerId, TransportError, TransportEvent}, Multiaddr, }; use libp2p_identity::PeerId; @@ -16,12 +22,8 @@ use prometheus_client::{ metrics::{counter::Counter, family::Family}, registry::{Registry, Unit}, }; -use std::{ - convert::TryFrom as _, - io, - pin::Pin, - task::{Context, Poll}, -}; + +use crate::protocol_stack; #[derive(Debug, Clone)] #[pin_project::pin_project] @@ -84,33 +86,20 @@ where self.transport.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - let metrics = ConnectionMetrics::from_family_and_addr(&self.metrics, &addr); - Ok(self - .transport - .dial(addr.clone())? - .map_ok(Box::new(|(peer_id, stream_muxer)| { - (peer_id, Muxer::new(stream_muxer, metrics)) - }))) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + dial_opts: DialOpts, ) -> Result> { let metrics = ConnectionMetrics::from_family_and_addr(&self.metrics, &addr); Ok(self .transport - .dial_as_listener(addr.clone())? + .dial(addr.clone(), dial_opts)? .map_ok(Box::new(|(peer_id, stream_muxer)| { (peer_id, Muxer::new(stream_muxer, metrics)) }))) } - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.transport.address_translation(server, observed) - } - fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -255,7 +244,7 @@ impl AsyncRead for InstrumentedStream { let num_bytes = ready!(this.inner.poll_read(cx, buf))?; this.metrics .inbound - .inc_by(u64::try_from(num_bytes).unwrap_or(u64::max_value())); + .inc_by(u64::try_from(num_bytes).unwrap_or(u64::MAX)); Poll::Ready(Ok(num_bytes)) } @@ -268,7 +257,7 @@ impl AsyncRead for InstrumentedStream { let num_bytes = ready!(this.inner.poll_read_vectored(cx, bufs))?; this.metrics .inbound - .inc_by(u64::try_from(num_bytes).unwrap_or(u64::max_value())); + .inc_by(u64::try_from(num_bytes).unwrap_or(u64::MAX)); Poll::Ready(Ok(num_bytes)) } } @@ -283,7 +272,7 @@ impl AsyncWrite for InstrumentedStream { let num_bytes = ready!(this.inner.poll_write(cx, buf))?; this.metrics .outbound - .inc_by(u64::try_from(num_bytes).unwrap_or(u64::max_value())); + .inc_by(u64::try_from(num_bytes).unwrap_or(u64::MAX)); Poll::Ready(Ok(num_bytes)) } @@ -296,7 +285,7 @@ impl AsyncWrite for InstrumentedStream { let num_bytes = ready!(this.inner.poll_write_vectored(cx, bufs))?; this.metrics .outbound - .inc_by(u64::try_from(num_bytes).unwrap_or(u64::max_value())); + .inc_by(u64::try_from(num_bytes).unwrap_or(u64::MAX)); Poll::Ready(Ok(num_bytes)) } diff --git a/misc/metrics/src/dcutr.rs b/misc/metrics/src/dcutr.rs index 3e60dca2cab..6a0f27394e9 100644 --- a/misc/metrics/src/dcutr.rs +++ b/misc/metrics/src/dcutr.rs @@ -18,10 +18,11 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; -use prometheus_client::metrics::counter::Counter; -use prometheus_client::metrics::family::Family; -use prometheus_client::registry::Registry; +use prometheus_client::{ + encoding::{EncodeLabelSet, EncodeLabelValue}, + metrics::{counter::Counter, family::Family}, + registry::Registry, +}; pub(crate) struct Metrics { events: Family, diff --git a/misc/metrics/src/gossipsub.rs b/misc/metrics/src/gossipsub.rs index 2d90b92fbc6..b3e2e11f0b0 100644 --- a/misc/metrics/src/gossipsub.rs +++ b/misc/metrics/src/gossipsub.rs @@ -18,8 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use prometheus_client::metrics::counter::Counter; -use prometheus_client::registry::Registry; +use prometheus_client::{metrics::counter::Counter, registry::Registry}; pub(crate) struct Metrics { messages: Counter, diff --git a/misc/metrics/src/identify.rs b/misc/metrics/src/identify.rs index b1d4e9f0c89..b16c6a56ccf 100644 --- a/misc/metrics/src/identify.rs +++ b/misc/metrics/src/identify.rs @@ -18,17 +18,21 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::protocol_stack; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + use libp2p_identity::PeerId; use libp2p_swarm::StreamProtocol; -use prometheus_client::collector::Collector; -use prometheus_client::encoding::{DescriptorEncoder, EncodeLabelSet, EncodeMetric}; -use prometheus_client::metrics::counter::Counter; -use prometheus_client::metrics::gauge::ConstGauge; -use prometheus_client::metrics::MetricType; -use prometheus_client::registry::Registry; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; +use prometheus_client::{ + collector::Collector, + encoding::{DescriptorEncoder, EncodeMetric}, + metrics::{counter::Counter, gauge::ConstGauge, MetricType}, + registry::Registry, +}; + +use crate::protocol_stack; const ALLOWED_PROTOCOLS: &[StreamProtocol] = &[ #[cfg(feature = "dcutr")] @@ -138,11 +142,6 @@ impl super::Recorder> for Metrics { } } -#[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)] -struct AddressLabels { - protocols: String, -} - #[derive(Default, Debug, Clone)] struct Peers(Arc>>); diff --git a/misc/metrics/src/kad.rs b/misc/metrics/src/kad.rs index bd5a6526737..0a2a8038511 100644 --- a/misc/metrics/src/kad.rs +++ b/misc/metrics/src/kad.rs @@ -18,11 +18,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; -use prometheus_client::metrics::counter::Counter; -use prometheus_client::metrics::family::Family; -use prometheus_client::metrics::histogram::{exponential_buckets, Histogram}; -use prometheus_client::registry::{Registry, Unit}; +use prometheus_client::{ + encoding::{EncodeLabelSet, EncodeLabelValue}, + metrics::{ + counter::Counter, + family::Family, + histogram::{exponential_buckets, Histogram}, + }, + registry::{Registry, Unit}, +}; pub(crate) struct Metrics { query_result_get_record_ok: Counter, diff --git a/misc/metrics/src/lib.rs b/misc/metrics/src/lib.rs index 74fd15e2181..1fd79e7846f 100644 --- a/misc/metrics/src/lib.rs +++ b/misc/metrics/src/lib.rs @@ -67,8 +67,8 @@ impl Metrics { /// Create a new set of Swarm and protocol [`Metrics`]. /// /// ``` - /// use prometheus_client::registry::Registry; /// use libp2p_metrics::Metrics; + /// use prometheus_client::registry::Registry; /// let mut registry = Registry::default(); /// let metrics = Metrics::new(&mut registry); /// ``` diff --git a/misc/metrics/src/ping.rs b/misc/metrics/src/ping.rs index afdd05134a6..ce653c72ea1 100644 --- a/misc/metrics/src/ping.rs +++ b/misc/metrics/src/ping.rs @@ -18,11 +18,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; -use prometheus_client::metrics::counter::Counter; -use prometheus_client::metrics::family::Family; -use prometheus_client::metrics::histogram::{exponential_buckets, Histogram}; -use prometheus_client::registry::{Registry, Unit}; +use prometheus_client::{ + encoding::{EncodeLabelSet, EncodeLabelValue}, + metrics::{ + counter::Counter, + family::Family, + histogram::{exponential_buckets, Histogram}, + }, + registry::{Registry, Unit}, +}; #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug)] struct FailureLabels { diff --git a/misc/metrics/src/protocol_stack.rs b/misc/metrics/src/protocol_stack.rs index 59e8c0bfa6a..57760df79a1 100644 --- a/misc/metrics/src/protocol_stack.rs +++ b/misc/metrics/src/protocol_stack.rs @@ -23,5 +23,11 @@ mod tests { let protocol_stack = as_string(&ma); assert_eq!(protocol_stack, "/ip6/tcp/wss/p2p"); + + let ma = Multiaddr::try_from("/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/8000/tls/ws/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC").expect("testbad"); + + let protocol_stack = as_string(&ma); + + assert_eq!(protocol_stack, "/ip6/tcp/tls/ws/p2p"); } } diff --git a/misc/metrics/src/relay.rs b/misc/metrics/src/relay.rs index 607daf3f1e1..d4c25b6eb3e 100644 --- a/misc/metrics/src/relay.rs +++ b/misc/metrics/src/relay.rs @@ -18,10 +18,11 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; -use prometheus_client::metrics::counter::Counter; -use prometheus_client::metrics::family::Family; -use prometheus_client::registry::Registry; +use prometheus_client::{ + encoding::{EncodeLabelSet, EncodeLabelValue}, + metrics::{counter::Counter, family::Family}, + registry::Registry, +}; pub(crate) struct Metrics { events: Family, diff --git a/misc/metrics/src/swarm.rs b/misc/metrics/src/swarm.rs index ad83401f316..6e95d082de6 100644 --- a/misc/metrics/src/swarm.rs +++ b/misc/metrics/src/swarm.rs @@ -18,17 +18,24 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; -use crate::protocol_stack; -use instant::Instant; use libp2p_swarm::{ConnectionId, DialError, SwarmEvent}; -use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; -use prometheus_client::metrics::counter::Counter; -use prometheus_client::metrics::family::Family; -use prometheus_client::metrics::histogram::{exponential_buckets, Histogram}; -use prometheus_client::registry::{Registry, Unit}; +use prometheus_client::{ + encoding::{EncodeLabelSet, EncodeLabelValue}, + metrics::{ + counter::Counter, + family::Family, + histogram::{exponential_buckets, Histogram}, + }, + registry::{Registry, Unit}, +}; +use web_time::Instant; + +use crate::protocol_stack; pub(crate) struct Metrics { connections_incoming: Family, diff --git a/misc/multistream-select/Cargo.toml b/misc/multistream-select/Cargo.toml index 34b5b6a636c..66ab434b613 100644 --- a/misc/multistream-select/Cargo.toml +++ b/misc/multistream-select/Cargo.toml @@ -12,26 +12,23 @@ categories = ["network-programming", "asynchronous"] [dependencies] bytes = "1" -futures = "0.3" -tracing = "0.1.37" -pin-project = "1.1.3" -smallvec = "1.11.2" +futures = { workspace = true } +tracing = { workspace = true } +pin-project = "1.1.5" +smallvec = "1.13.2" unsigned-varint = { workspace = true } [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } futures_ringbuf = "0.4.0" quickcheck = { workspace = true } -rand = "0.8" rw-stream-sink = { workspace = true } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +libp2p-test-utils = { workspace = true } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/misc/multistream-select/src/dialer_select.rs b/misc/multistream-select/src/dialer_select.rs index 83bb4909041..bd537e7fc7b 100644 --- a/misc/multistream-select/src/dialer_select.rs +++ b/misc/multistream-select/src/dialer_select.rs @@ -20,10 +20,6 @@ //! Protocol negotiation strategies for the peer acting as the dialer. -use crate::protocol::{HeaderLine, Message, MessageIO, Protocol, ProtocolError}; -use crate::{Negotiated, NegotiationError, Version}; - -use futures::prelude::*; use std::{ convert::TryFrom as _, iter, mem, @@ -31,6 +27,13 @@ use std::{ task::{Context, Poll}, }; +use futures::prelude::*; + +use crate::{ + protocol::{HeaderLine, Message, MessageIO, Protocol, ProtocolError}, + Negotiated, NegotiationError, Version, +}; + /// Returns a `Future` that negotiates a protocol on the given I/O stream /// for a peer acting as the _dialer_ (or _initiator_). /// @@ -84,8 +87,9 @@ enum State { impl Future for DialerSelectFuture where - // The Unpin bound here is required because we produce a `Negotiated` as the output. - // It also makes the implementation considerably easier to write. + // The Unpin bound here is required because we produce + // a `Negotiated` as the output. It also makes + // the implementation considerably easier to write. R: AsyncRead + AsyncWrite + Unpin, I: Iterator, I::Item: AsRef, @@ -204,14 +208,18 @@ where #[cfg(test)] mod tests { - use super::*; - use crate::listener_select_proto; - use async_std::future::timeout; - use async_std::net::{TcpListener, TcpStream}; - use quickcheck::{Arbitrary, Gen, GenRange}; use std::time::Duration; + + use async_std::{ + future::timeout, + net::{TcpListener, TcpStream}, + }; + use libp2p_test_utils::EnvFilter; + use quickcheck::{Arbitrary, Gen, GenRange}; use tracing::metadata::LevelFilter; - use tracing_subscriber::EnvFilter; + + use super::*; + use crate::listener_select_proto; #[test] fn select_proto_basic() { @@ -267,13 +275,11 @@ mod tests { ListenerProtos(listen_protos): ListenerProtos, DialPayload(dial_payload): DialPayload, ) { - let _ = tracing_subscriber::fmt() - .with_env_filter( - EnvFilter::builder() - .with_default_directive(LevelFilter::DEBUG.into()) - .from_env_lossy(), - ) - .try_init(); + libp2p_test_utils::with_env_filter( + EnvFilter::builder() + .with_default_directive(LevelFilter::DEBUG.into()) + .from_env_lossy(), + ); async_std::task::block_on(async move { let listener = TcpListener::bind("0.0.0.0:0").await.unwrap(); @@ -353,8 +359,8 @@ mod tests { .unwrap(); assert_eq!(proto, "/proto1"); - // client can close the connection even though protocol negotiation is not yet done, i.e. - // `_server_connection` had been untouched. + // client can close the connection even though protocol negotiation is not yet done, + // i.e. `_server_connection` had been untouched. io.close().await.unwrap(); }); diff --git a/misc/multistream-select/src/length_delimited.rs b/misc/multistream-select/src/length_delimited.rs index 6515d00c717..8062455de46 100644 --- a/misc/multistream-select/src/length_delimited.rs +++ b/misc/multistream-select/src/length_delimited.rs @@ -18,16 +18,16 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use bytes::{Buf as _, BufMut as _, Bytes, BytesMut}; -use futures::{io::IoSlice, prelude::*}; use std::{ convert::TryFrom as _, io, pin::Pin, task::{Context, Poll}, - u16, }; +use bytes::{Buf as _, BufMut as _, Bytes, BytesMut}; +use futures::{io::IoSlice, prelude::*}; + const MAX_LEN_BYTES: u16 = 2; const MAX_FRAME_SIZE: u16 = (1 << (MAX_LEN_BYTES * 8 - MAX_LEN_BYTES)) - 1; const DEFAULT_BUFFER_SIZE: usize = 64; @@ -384,10 +384,12 @@ where #[cfg(test)] mod tests { - use crate::length_delimited::LengthDelimited; + use std::io::ErrorKind; + use futures::{io::Cursor, prelude::*}; use quickcheck::*; - use std::io::ErrorKind; + + use crate::length_delimited::LengthDelimited; #[test] fn basic_read() { diff --git a/misc/multistream-select/src/lib.rs b/misc/multistream-select/src/lib.rs index 5565623f25e..96432de6cb0 100644 --- a/misc/multistream-select/src/lib.rs +++ b/misc/multistream-select/src/lib.rs @@ -70,20 +70,21 @@ //! //! ```no_run //! use async_std::net::TcpStream; -//! use multistream_select::{dialer_select_proto, Version}; //! use futures::prelude::*; +//! use multistream_select::{dialer_select_proto, Version}; //! //! async_std::task::block_on(async move { //! let socket = TcpStream::connect("127.0.0.1:10333").await.unwrap(); //! //! let protos = vec!["/echo/1.0.0", "/echo/2.5.0"]; -//! let (protocol, _io) = dialer_select_proto(socket, protos, Version::V1).await.unwrap(); +//! let (protocol, _io) = dialer_select_proto(socket, protos, Version::V1) +//! .await +//! .unwrap(); //! //! println!("Negotiated protocol: {:?}", protocol); //! // You can now use `_io` to communicate with the remote. //! }); //! ``` -//! #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] @@ -93,10 +94,12 @@ mod listener_select; mod negotiated; mod protocol; -pub use self::dialer_select::{dialer_select_proto, DialerSelectFuture}; -pub use self::listener_select::{listener_select_proto, ListenerSelectFuture}; -pub use self::negotiated::{Negotiated, NegotiatedComplete, NegotiationError}; -pub use self::protocol::ProtocolError; +pub use self::{ + dialer_select::{dialer_select_proto, DialerSelectFuture}, + listener_select::{listener_select_proto, ListenerSelectFuture}, + negotiated::{Negotiated, NegotiatedComplete, NegotiationError}, + protocol::ProtocolError, +}; /// Supported multistream-select versions. #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] diff --git a/misc/multistream-select/src/listener_select.rs b/misc/multistream-select/src/listener_select.rs index 21c507096e2..cd5af72a9d0 100644 --- a/misc/multistream-select/src/listener_select.rs +++ b/misc/multistream-select/src/listener_select.rs @@ -21,19 +21,21 @@ //! Protocol negotiation strategies for the peer acting as the listener //! in a multistream-select protocol negotiation. -use crate::protocol::{HeaderLine, Message, MessageIO, Protocol, ProtocolError}; -use crate::{Negotiated, NegotiationError}; - -use futures::prelude::*; -use smallvec::SmallVec; use std::{ convert::TryFrom as _, - iter::FromIterator, mem, pin::Pin, task::{Context, Poll}, }; +use futures::prelude::*; +use smallvec::SmallVec; + +use crate::{ + protocol::{HeaderLine, Message, MessageIO, Protocol, ProtocolError}, + Negotiated, NegotiationError, +}; + /// Returns a `Future` that negotiates a protocol on the given I/O stream /// for a peer acting as the _listener_ (or _responder_). /// @@ -110,8 +112,10 @@ enum State { impl Future for ListenerSelectFuture where - // The Unpin bound here is required because we produce a `Negotiated` as the output. - // It also makes the implementation considerably easier to write. + // The Unpin bound here is required because + // we produce a `Negotiated` as the output. + // It also makes the implementation considerably + // easier to write. R: AsyncRead + AsyncWrite + Unpin, N: AsRef + Clone, { diff --git a/misc/multistream-select/src/negotiated.rs b/misc/multistream-select/src/negotiated.rs index a24014a4f5f..6693b3b5636 100644 --- a/misc/multistream-select/src/negotiated.rs +++ b/misc/multistream-select/src/negotiated.rs @@ -18,7 +18,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::protocol::{HeaderLine, Message, MessageReader, Protocol, ProtocolError}; +use std::{ + error::Error, + fmt, io, mem, + pin::Pin, + task::{Context, Poll}, +}; use futures::{ io::{IoSlice, IoSliceMut}, @@ -26,12 +31,8 @@ use futures::{ ready, }; use pin_project::pin_project; -use std::{ - error::Error, - fmt, io, mem, - pin::Pin, - task::{Context, Poll}, -}; + +use crate::protocol::{HeaderLine, Message, MessageReader, Protocol, ProtocolError}; /// An I/O stream that has settled on an (application-layer) protocol to use. /// @@ -59,8 +60,10 @@ pub struct NegotiatedComplete { impl Future for NegotiatedComplete where - // `Unpin` is required not because of implementation details but because we produce the - // `Negotiated` as the output of the future. + // `Unpin` is required not because of + // implementation details but because we produce + // the `Negotiated` as the output of the + // future. TInner: AsyncRead + AsyncWrite + Unpin, { type Output = Result, NegotiationError>; @@ -250,13 +253,13 @@ where } // TODO: implement once method is stabilized in the futures crate - /*unsafe fn initializer(&self) -> Initializer { - match &self.state { - State::Completed { io, .. } => io.initializer(), - State::Expecting { io, .. } => io.inner_ref().initializer(), - State::Invalid => panic!("Negotiated: Invalid state"), - } - }*/ + // unsafe fn initializer(&self) -> Initializer { + // match &self.state { + // State::Completed { io, .. } => io.initializer(), + // State::Expecting { io, .. } => io.inner_ref().initializer(), + // State::Invalid => panic!("Negotiated: Invalid state"), + // } + // } fn poll_read_vectored( mut self: Pin<&mut Self>, diff --git a/misc/multistream-select/src/protocol.rs b/misc/multistream-select/src/protocol.rs index d5c2bfa773a..93cd4ac02b5 100644 --- a/misc/multistream-select/src/protocol.rs +++ b/misc/multistream-select/src/protocol.rs @@ -25,20 +25,22 @@ //! `Stream` and `Sink` implementations of `MessageIO` and //! `MessageReader`. -use crate::length_delimited::{LengthDelimited, LengthDelimitedReader}; -use crate::Version; - -use bytes::{BufMut, Bytes, BytesMut}; -use futures::{io::IoSlice, prelude::*, ready}; use std::{ - convert::TryFrom, error::Error, fmt, io, pin::Pin, task::{Context, Poll}, }; + +use bytes::{BufMut, Bytes, BytesMut}; +use futures::{io::IoSlice, prelude::*, ready}; use unsigned_varint as uvi; +use crate::{ + length_delimited::{LengthDelimited, LengthDelimitedReader}, + Version, +}; + /// The maximum number of supported protocols that can be processed. const MAX_PROTOCOLS: usize = 1000; @@ -49,7 +51,7 @@ const MSG_PROTOCOL_NA: &[u8] = b"na\n"; /// The encoded form of a multistream-select 'ls' message. const MSG_LS: &[u8] = b"ls\n"; -/// The multistream-select header lines preceeding negotiation. +/// The multistream-select header lines preceding negotiation. /// /// Every [`Version`] has a corresponding header line. #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -137,24 +139,21 @@ pub(crate) enum Message { impl Message { /// Encodes a `Message` into its byte representation. - fn encode(&self, dest: &mut BytesMut) -> Result<(), ProtocolError> { + fn encode(&self, dest: &mut BytesMut) { match self { Message::Header(HeaderLine::V1) => { dest.reserve(MSG_MULTISTREAM_1_0.len()); dest.put(MSG_MULTISTREAM_1_0); - Ok(()) } Message::Protocol(p) => { let len = p.as_ref().len() + 1; // + 1 for \n dest.reserve(len); dest.put(p.0.as_ref()); dest.put_u8(b'\n'); - Ok(()) } Message::ListProtocols => { dest.reserve(MSG_LS.len()); dest.put(MSG_LS); - Ok(()) } Message::Protocols(ps) => { let mut buf = uvi::encode::usize_buffer(); @@ -167,12 +166,10 @@ impl Message { encoded.push(b'\n'); dest.reserve(encoded.len()); dest.put(encoded.as_ref()); - Ok(()) } Message::NotAvailable => { dest.reserve(MSG_PROTOCOL_NA.len()); dest.put(MSG_PROTOCOL_NA); - Ok(()) } } } @@ -289,7 +286,7 @@ where fn start_send(self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> { let mut buf = BytesMut::new(); - item.encode(&mut buf)?; + item.encode(&mut buf); self.project() .inner .start_send(buf.freeze()) @@ -467,10 +464,12 @@ impl fmt::Display for ProtocolError { #[cfg(test)] mod tests { - use super::*; - use quickcheck::*; use std::iter; + use quickcheck::*; + + use super::*; + impl Arbitrary for Protocol { fn arbitrary(g: &mut Gen) -> Protocol { let n = g.gen_range(1..g.size()); @@ -500,8 +499,7 @@ mod tests { fn encode_decode_message() { fn prop(msg: Message) { let mut buf = BytesMut::new(); - msg.encode(&mut buf) - .unwrap_or_else(|_| panic!("Encoding message failed: {msg:?}")); + msg.encode(&mut buf); match Message::decode(buf.freeze()) { Ok(m) => assert_eq!(m, msg), Err(e) => panic!("Decoding failed: {e:?}"), diff --git a/misc/quick-protobuf-codec/Cargo.toml b/misc/quick-protobuf-codec/Cargo.toml index 484e2c9bc8b..2501e94ca19 100644 --- a/misc/quick-protobuf-codec/Cargo.toml +++ b/misc/quick-protobuf-codec/Cargo.toml @@ -13,13 +13,13 @@ categories = ["asynchronous"] [dependencies] asynchronous-codec = { workspace = true } bytes = { version = "1" } -thiserror = "1.0" +thiserror = { workspace = true } unsigned-varint = { workspace = true, features = ["std"] } quick-protobuf = "0.8" [dev-dependencies] criterion = "0.5.1" -futures = "0.3.28" +futures = { workspace = true } quickcheck = { workspace = true } [[bench]] @@ -30,8 +30,6 @@ harness = false # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/misc/quick-protobuf-codec/src/lib.rs b/misc/quick-protobuf-codec/src/lib.rs index c50b1264af6..d49315a54c3 100644 --- a/misc/quick-protobuf-codec/src/lib.rs +++ b/misc/quick-protobuf-codec/src/lib.rs @@ -1,10 +1,10 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +use std::{io, marker::PhantomData}; + use asynchronous_codec::{Decoder, Encoder}; use bytes::{Buf, BufMut, BytesMut}; use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer, WriterBackend}; -use std::io; -use std::marker::PhantomData; mod generated; @@ -12,6 +12,7 @@ mod generated; pub use generated::test as proto; /// [`Codec`] implements [`Encoder`] and [`Decoder`], uses [`unsigned_varint`] +/// /// to prefix messages with their length and uses [`quick_protobuf`] and a provided /// `struct` implementing [`MessageRead`] and [`MessageWrite`] to do the encoding. pub struct Codec { @@ -119,7 +120,7 @@ impl<'a> BytesMutWriterBackend<'a> { } } -impl<'a> WriterBackend for BytesMutWriterBackend<'a> { +impl WriterBackend for BytesMutWriterBackend<'_> { fn pb_write_u8(&mut self, x: u8) -> quick_protobuf::Result<()> { self.dst.put_u8(x); @@ -181,13 +182,13 @@ impl From for io::Error { #[cfg(test)] mod tests { - use super::*; - use crate::proto; + use std::error::Error; + use asynchronous_codec::FramedRead; - use futures::io::Cursor; - use futures::{FutureExt, StreamExt}; + use futures::{io::Cursor, FutureExt, StreamExt}; use quickcheck::{Arbitrary, Gen, QuickCheck}; - use std::error::Error; + + use super::*; #[test] fn honors_max_message_length() { diff --git a/misc/quick-protobuf-codec/tests/large_message.rs b/misc/quick-protobuf-codec/tests/large_message.rs index 65dafe065d1..a434d3ce17f 100644 --- a/misc/quick-protobuf-codec/tests/large_message.rs +++ b/misc/quick-protobuf-codec/tests/large_message.rs @@ -1,7 +1,6 @@ use asynchronous_codec::Encoder; use bytes::BytesMut; -use quick_protobuf_codec::proto; -use quick_protobuf_codec::Codec; +use quick_protobuf_codec::{proto, Codec}; #[test] fn encode_large_message() { diff --git a/misc/quickcheck-ext/src/lib.rs b/misc/quickcheck-ext/src/lib.rs index a3b9ce26e6e..9c2deec8743 100644 --- a/misc/quickcheck-ext/src/lib.rs +++ b/misc/quickcheck-ext/src/lib.rs @@ -1,15 +1,15 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -pub use quickcheck::*; - use core::ops::Range; + use num_traits::sign::Unsigned; +pub use quickcheck::*; pub trait GenRange { fn gen_range(&mut self, _range: Range) -> T; fn gen_index(&mut self, ubound: usize) -> usize { - if ubound <= (core::u32::MAX as usize) { + if ubound <= (u32::MAX as usize) { self.gen_range(0..ubound as u32) as usize } else { self.gen_range(0..ubound) diff --git a/misc/rw-stream-sink/Cargo.toml b/misc/rw-stream-sink/Cargo.toml index ae10cbc35b2..20fa2fa23fa 100644 --- a/misc/rw-stream-sink/Cargo.toml +++ b/misc/rw-stream-sink/Cargo.toml @@ -11,8 +11,8 @@ keywords = ["networking"] categories = ["network-programming", "asynchronous"] [dependencies] -futures = "0.3.29" -pin-project = "1.1.3" +futures = { workspace = true } +pin-project = "1.1.5" static_assertions = "1" [dev-dependencies] @@ -22,8 +22,6 @@ async-std = "1.0" # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/misc/rw-stream-sink/src/lib.rs b/misc/rw-stream-sink/src/lib.rs index ee05196bfb4..5fdf1987252 100644 --- a/misc/rw-stream-sink/src/lib.rs +++ b/misc/rw-stream-sink/src/lib.rs @@ -27,7 +27,6 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use futures::{prelude::*, ready}; use std::{ io::{self, Read}, mem, @@ -35,6 +34,8 @@ use std::{ task::{Context, Poll}, }; +use futures::{prelude::*, ready}; + static_assertions::const_assert!(mem::size_of::() <= mem::size_of::()); /// Wraps a [`Stream`] and [`Sink`] whose items are buffers. @@ -115,14 +116,16 @@ where #[cfg(test)] mod tests { - use super::RwStreamSink; - use async_std::task; - use futures::{channel::mpsc, prelude::*, stream}; use std::{ pin::Pin, task::{Context, Poll}, }; + use async_std::task; + use futures::{channel::mpsc, prelude::*}; + + use super::RwStreamSink; + // This struct merges a stream and a sink and is quite useful for tests. struct Wrapper(St, Si); diff --git a/misc/server/CHANGELOG.md b/misc/server/CHANGELOG.md index e4c5dd4a103..53341baa9ab 100644 --- a/misc/server/CHANGELOG.md +++ b/misc/server/CHANGELOG.md @@ -1,3 +1,16 @@ +## 0.12.6 + +### Changed + +- Stop using kad default protocol. + See [PR 5122](https://github.com/libp2p/rust-libp2p/pull/5122) +- Use periodic and automatic bootstrap of Kademlia. + See [PR 4838](https://github.com/libp2p/rust-libp2p/pull/4838). +- Update to [`libp2p-identify` `v0.45.0`](protocols/identify/CHANGELOG.md#0450). + See [PR 4981](https://github.com/libp2p/rust-libp2p/pull/4981). +- Remove deprecated [`libp2p-lookup`](https://github.com/mxinden/libp2p-lookup) from Dockerfile. + See [PR 5610](https://github.com/libp2p/rust-libp2p/pull/5610). + ## 0.12.5 ### Added @@ -15,6 +28,7 @@ ## 0.12.3 ### Changed + - Add libp2p-lookup to Dockerfile to enable healthchecks. ### Fixed @@ -26,14 +40,18 @@ [PR 4467]: https://github.com/libp2p/rust-libp2p/pull/4467 ## 0.12.2 + ### Fixed + - Adhere to `--metrics-path` flag and listen on `0.0.0.0:8888` (default IPFS metrics port). [PR 4392] [PR 4392]: https://github.com/libp2p/rust-libp2p/pull/4392 ## 0.12.1 + ### Changed + - Move to tokio and hyper. See [PR 4311]. - Move to distroless Docker base image. @@ -42,39 +60,57 @@ [PR 4311]: https://github.com/libp2p/rust-libp2p/pull/4311 ## 0.8.0 + ### Changed + - Remove mplex support. ## 0.7.0 + ### Changed + - Update to libp2p v0.47.0. ## 0.6.0 - 2022-05-05 + ### Changed + - Update to libp2p v0.44.0. ## 0.5.4 - 2022-01-11 + ### Changed + - Pull latest autonat changes. ## 0.5.3 - 2021-12-25 + ### Changed + - Update dependencies. - Pull in autonat fixes. ## 0.5.2 - 2021-12-20 + ### Added + - Add support for libp2p autonat protocol via `--enable-autonat`. ## 0.5.1 - 2021-12-20 + ### Fixed + - Update dependencies. - Fix typo in command line flag `--enable-kademlia`. ## 0.5.0 - 2021-11-18 + ### Changed + - Disable Kademlia protocol by default. ## 0.4.0 - 2021-11-18 + ### Fixed + - Update dependencies. diff --git a/misc/server/Cargo.toml b/misc/server/Cargo.toml index 87bdc20b169..b2b3d33ca1e 100644 --- a/misc/server/Cargo.toml +++ b/misc/server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-server" -version = "0.12.5" +version = "0.12.6" authors = ["Max Inden "] edition = "2021" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,19 +11,33 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -base64 = "0.21" -clap = { version = "4.4.10", features = ["derive"] } -futures = "0.3" -futures-timer = "3" -hyper = { version = "0.14", features = ["server", "tcp", "http1"] } -libp2p = { workspace = true, features = ["autonat", "dns", "tokio", "noise", "tcp", "yamux", "identify", "kad", "ping", "relay", "metrics", "rsa", "macros", "quic", "websocket"] } +base64 = "0.22" +clap = { version = "4.5.6", features = ["derive"] } +futures = { workspace = true } +axum = "0.7" +libp2p = { workspace = true, features = [ + "autonat", + "dns", + "tokio", + "noise", + "tcp", + "yamux", + "identify", + "kad", + "ping", + "relay", + "metrics", + "rsa", + "macros", + "quic", + "websocket", +] } prometheus-client = { workspace = true } -serde = "1.0.193" -serde_derive = "1.0.125" +serde = { version = "1", features = ["derive"] } serde_json = "1.0" -tokio = { version = "1", features = ["rt-multi-thread", "macros"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } zeroize = "1" [lints] diff --git a/misc/server/Dockerfile b/misc/server/Dockerfile index 9d2742f97e8..12a8982eb3f 100644 --- a/misc/server/Dockerfile +++ b/misc/server/Dockerfile @@ -1,7 +1,6 @@ # syntax=docker/dockerfile:1.5-labs -FROM rust:1.73.0 as chef +FROM rust:1.81.0 as chef RUN wget -q -O- https://github.com/LukeMathWalker/cargo-chef/releases/download/v0.1.62/cargo-chef-x86_64-unknown-linux-gnu.tar.gz | tar -zx -C /usr/local/bin -RUN cargo install --locked --root /usr/local libp2p-lookup --version 0.6.4 WORKDIR /app FROM chef AS planner @@ -17,5 +16,4 @@ COPY . . RUN cargo build --release --package libp2p-server FROM gcr.io/distroless/cc -COPY --from=builder /usr/local/bin/libp2p-server /usr/local/bin/libp2p-lookup /usr/local/bin/ CMD ["libp2p-server"] diff --git a/misc/server/README.md b/misc/server/README.md index 0da1bd8abd9..f9a5d65124a 100644 --- a/misc/server/README.md +++ b/misc/server/README.md @@ -25,7 +25,6 @@ Options: -h, --help Print help ``` - ``` cargo run -- --config ~/.ipfs/config @@ -33,9 +32,3 @@ Local peer id: PeerId("12D3KooWSa1YEeQVSwvoqAMhwjKQ6kqZQckhWPb3RWEGV3sZGU6Z") Listening on "/ip4/127.0.0.1/udp/4001/quic" [...] ``` - -The Docker container includes [libp2-lookup](https://github.com/mxinden/libp2p-lookup/) to enable adding a proper healthcheck for container startup, e.g. - -``` shell -docker run --health-cmd 'libp2p-lookup direct --address /ip4/127.0.0.1/tcp/4001/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa' /home/ipfs/.ipfs:/ipfs ghcr.io/libp2p/rust-libp2p-server --config /ipfs/config -``` diff --git a/misc/server/src/behaviour.rs b/misc/server/src/behaviour.rs index ec025e02129..230d62a2ef3 100644 --- a/misc/server/src/behaviour.rs +++ b/misc/server/src/behaviour.rs @@ -1,12 +1,10 @@ -use libp2p::autonat; -use libp2p::identify; -use libp2p::kad; -use libp2p::ping; -use libp2p::relay; -use libp2p::swarm::behaviour::toggle::Toggle; -use libp2p::{identity, swarm::NetworkBehaviour, Multiaddr, PeerId}; -use std::str::FromStr; -use std::time::Duration; +use std::{str::FromStr, time::Duration}; + +use libp2p::{ + autonat, identify, identity, kad, ping, relay, + swarm::{behaviour::toggle::Toggle, NetworkBehaviour, StreamProtocol}, + Multiaddr, PeerId, +}; const BOOTNODES: [&str; 4] = [ "QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", @@ -15,6 +13,8 @@ const BOOTNODES: [&str; 4] = [ "QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt", ]; +const IPFS_PROTO_NAME: StreamProtocol = StreamProtocol::new("/ipfs/kad/1.0.0"); + #[derive(NetworkBehaviour)] pub(crate) struct Behaviour { relay: relay::Behaviour, @@ -31,7 +31,7 @@ impl Behaviour { enable_autonat: bool, ) -> Self { let kademlia = if enable_kademlia { - let mut kademlia_config = kad::Config::default(); + let mut kademlia_config = kad::Config::new(IPFS_PROTO_NAME); // Instantly remove records and provider records. // // TODO: Replace hack with option to disable both. diff --git a/misc/server/src/config.rs b/misc/server/src/config.rs index c3e3ec529c1..8f8c71369b2 100644 --- a/misc/server/src/config.rs +++ b/misc/server/src/config.rs @@ -1,7 +1,7 @@ +use std::{error::Error, path::Path}; + use libp2p::Multiaddr; -use serde_derive::Deserialize; -use std::error::Error; -use std::path::Path; +use serde::Deserialize; #[derive(Clone, Deserialize)] #[serde(rename_all = "PascalCase")] diff --git a/misc/server/src/http_service.rs b/misc/server/src/http_service.rs index 7905933fbf5..87a8adb94e0 100644 --- a/misc/server/src/http_service.rs +++ b/misc/server/src/http_service.rs @@ -18,115 +18,60 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use hyper::http::StatusCode; -use hyper::service::Service; -use hyper::{Body, Method, Request, Response, Server}; -use prometheus_client::encoding::text::encode; -use prometheus_client::registry::Registry; -use std::future::Future; -use std::pin::Pin; -use std::sync::{Arc, Mutex}; -use std::task::{Context, Poll}; +use std::{ + net::SocketAddr, + sync::{Arc, Mutex}, +}; + +use axum::{extract::State, http::StatusCode, response::IntoResponse, routing::get, Router}; +use prometheus_client::{encoding::text::encode, registry::Registry}; +use tokio::net::TcpListener; const METRICS_CONTENT_TYPE: &str = "application/openmetrics-text;charset=utf-8;version=1.0.0"; pub(crate) async fn metrics_server( registry: Registry, metrics_path: String, -) -> Result<(), hyper::Error> { +) -> Result<(), std::io::Error> { // Serve on localhost. - let addr = ([0, 0, 0, 0], 8888).into(); - - let server = Server::bind(&addr).serve(MakeMetricService::new(registry, metrics_path.clone())); - tracing::info!(metrics_server=%format!("http://{}{}", server.local_addr(), metrics_path)); - server.await?; + let addr: SocketAddr = ([0, 0, 0, 0], 8888).into(); + let service = MetricService::new(registry); + let server = Router::new() + .route(&metrics_path, get(respond_with_metrics)) + .with_state(service); + let tcp_listener = TcpListener::bind(addr).await?; + let local_addr = tcp_listener.local_addr()?; + tracing::info!(metrics_server=%format!("http://{}{}", local_addr, metrics_path)); + axum::serve(tcp_listener, server.into_make_service()).await?; Ok(()) } -pub(crate) struct MetricService { - reg: Arc>, - metrics_path: String, -} - -type SharedRegistry = Arc>; -impl MetricService { - fn get_reg(&mut self) -> SharedRegistry { - Arc::clone(&self.reg) - } - fn respond_with_metrics(&mut self) -> Response { - let mut response: Response = Response::default(); - - response.headers_mut().insert( - hyper::header::CONTENT_TYPE, - METRICS_CONTENT_TYPE.try_into().unwrap(), - ); +async fn respond_with_metrics(state: State) -> impl IntoResponse { + let mut sink = String::new(); + let reg = state.get_reg(); + encode(&mut sink, ®.lock().unwrap()).unwrap(); - let reg = self.get_reg(); - encode(&mut response.body_mut(), ®.lock().unwrap()).unwrap(); - - *response.status_mut() = StatusCode::OK; - - response - } - fn respond_with_404_not_found(&mut self) -> Response { - Response::builder() - .status(StatusCode::NOT_FOUND) - .body(format!( - "Not found try localhost:[port]/{}", - self.metrics_path - )) - .unwrap() - } + ( + StatusCode::OK, + [(axum::http::header::CONTENT_TYPE, METRICS_CONTENT_TYPE)], + sink, + ) } -impl Service> for MetricService { - type Response = Response; - type Error = hyper::Error; - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Request) -> Self::Future { - let req_path = req.uri().path(); - let req_method = req.method(); - let resp = if (req_method == Method::GET) && (req_path == self.metrics_path) { - // Encode and serve metrics from registry. - self.respond_with_metrics() - } else { - self.respond_with_404_not_found() - }; - Box::pin(async { Ok(resp) }) - } +#[derive(Clone)] +pub(crate) struct MetricService { + reg: Arc>, } -pub(crate) struct MakeMetricService { - reg: SharedRegistry, - metrics_path: String, -} +type SharedRegistry = Arc>; -impl MakeMetricService { - pub(crate) fn new(registry: Registry, metrics_path: String) -> MakeMetricService { - MakeMetricService { - reg: Arc::new(Mutex::new(registry)), - metrics_path, +impl MetricService { + fn new(reg: Registry) -> Self { + Self { + reg: Arc::new(Mutex::new(reg)), } } -} - -impl Service for MakeMetricService { - type Response = MetricService; - type Error = hyper::Error; - type Future = Pin> + Send>>; - fn poll_ready(&mut self, _: &mut Context) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _: T) -> Self::Future { - let reg = self.reg.clone(); - let metrics_path = self.metrics_path.clone(); - let fut = async move { Ok(MetricService { reg, metrics_path }) }; - Box::pin(fut) + fn get_reg(&self) -> SharedRegistry { + Arc::clone(&self.reg) } } diff --git a/misc/server/src/main.rs b/misc/server/src/main.rs index 16e6530e946..a633a80207e 100644 --- a/misc/server/src/main.rs +++ b/misc/server/src/main.rs @@ -1,21 +1,18 @@ +use std::{error::Error, path::PathBuf, str::FromStr}; + use base64::Engine; use clap::Parser; use futures::stream::StreamExt; -use futures_timer::Delay; -use libp2p::identity; -use libp2p::identity::PeerId; -use libp2p::kad; -use libp2p::metrics::{Metrics, Recorder}; -use libp2p::swarm::SwarmEvent; -use libp2p::tcp; -use libp2p::{identify, noise, yamux}; -use prometheus_client::metrics::info::Info; -use prometheus_client::registry::Registry; -use std::error::Error; -use std::path::PathBuf; -use std::str::FromStr; -use std::task::Poll; -use std::time::Duration; +use libp2p::{ + identify, identity, + identity::PeerId, + kad, + metrics::{Metrics, Recorder}, + noise, + swarm::SwarmEvent, + tcp, yamux, +}; +use prometheus_client::{metrics::info::Info, registry::Registry}; use tracing_subscriber::EnvFilter; use zeroize::Zeroizing; @@ -23,8 +20,6 @@ mod behaviour; mod config; mod http_service; -const BOOTSTRAP_INTERVAL: Duration = Duration::from_secs(5 * 60); - #[derive(Debug, Parser)] #[clap(name = "libp2p server", about = "A rust-libp2p server binary.")] struct Opts { @@ -76,7 +71,7 @@ async fn main() -> Result<(), Box> { let mut swarm = libp2p::SwarmBuilder::with_existing_identity(local_keypair) .with_tokio() .with_tcp( - tcp::Config::default().port_reuse(true).nodelay(true), + tcp::Config::default().nodelay(true), noise::Config::new, yamux::Config::default, )? @@ -127,18 +122,7 @@ async fn main() -> Result<(), Box> { } }); - let mut bootstrap_timer = Delay::new(BOOTSTRAP_INTERVAL); - loop { - if let Poll::Ready(()) = futures::poll!(&mut bootstrap_timer) { - bootstrap_timer.reset(BOOTSTRAP_INTERVAL); - let _ = swarm - .behaviour_mut() - .kademlia - .as_mut() - .map(|k| k.bootstrap()); - } - let event = swarm.next().await.expect("Swarm not to terminate."); metrics.record(&event); match event { @@ -154,6 +138,7 @@ async fn main() -> Result<(), Box> { protocols, .. }, + .. } = e { if protocols.iter().any(|p| *p == kad::PROTOCOL_NAME) { diff --git a/misc/test-utils/CHANGELOG.md b/misc/test-utils/CHANGELOG.md new file mode 100644 index 00000000000..0b8ed3ab931 --- /dev/null +++ b/misc/test-utils/CHANGELOG.md @@ -0,0 +1,4 @@ +## 0.1.0 + +- Introduce 'test-utils` crate. + See [PR 5725](https://github.com/libp2p/rust-libp2p/pull/5725). \ No newline at end of file diff --git a/misc/test-utils/Cargo.toml b/misc/test-utils/Cargo.toml new file mode 100644 index 00000000000..438bcabcf2a --- /dev/null +++ b/misc/test-utils/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "libp2p-test-utils" +version = "0.1.0" +edition = "2021" +authors = ["Krishang Shah "] +license = "MIT" +repository = "https://github.com/libp2p/rust-libp2p" +publish = false + +[package.metadata.release] +release = false + +[dependencies] +tracing-subscriber = { workspace = true, features = ["env-filter"] } + +[lints] +workspace = true diff --git a/misc/test-utils/src/lib.rs b/misc/test-utils/src/lib.rs new file mode 100644 index 00000000000..1155c79b614 --- /dev/null +++ b/misc/test-utils/src/lib.rs @@ -0,0 +1,15 @@ +pub use tracing_subscriber::EnvFilter; + +/// Initializes logging with the default environment filter (`RUST_LOG`). +pub fn with_default_env_filter() { + with_env_filter(EnvFilter::from_default_env()); +} + +/// Initializes logging with a custom environment filter. +/// Logs are written to standard error (`stderr`). +pub fn with_env_filter(filter: impl Into) { + let _ = tracing_subscriber::fmt() + .with_env_filter(filter) + .with_writer(std::io::stderr) + .try_init(); +} diff --git a/misc/webrtc-utils/CHANGELOG.md b/misc/webrtc-utils/CHANGELOG.md index 6949113a377..3bb31610fa1 100644 --- a/misc/webrtc-utils/CHANGELOG.md +++ b/misc/webrtc-utils/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.3.0 + + + +## 0.2.1 + +- Fix end of stream handling when buffer is empty or not present. + See [PR 5439](https://github.com/libp2p/rust-libp2p/pull/5439). + ## 0.2.0 - Update to latest version of `libp2p-noise`. diff --git a/misc/webrtc-utils/Cargo.toml b/misc/webrtc-utils/Cargo.toml index 7173dedae7b..2c50a2f8ab7 100644 --- a/misc/webrtc-utils/Cargo.toml +++ b/misc/webrtc-utils/Cargo.toml @@ -7,13 +7,13 @@ license = "MIT" name = "libp2p-webrtc-utils" repository = "https://github.com/libp2p/rust-libp2p" rust-version = { workspace = true } -version = "0.2.0" +version = "0.3.0" publish = true [dependencies] asynchronous-codec = { workspace = true } bytes = "1" -futures = "0.3" +futures = { workspace = true } hex = "0.4" libp2p-core = { workspace = true } libp2p-identity = { workspace = true } @@ -23,9 +23,8 @@ quick-protobuf-codec = { workspace = true } rand = "0.8" serde = { version = "1.0", features = ["derive"] } sha2 = "0.10.8" -thiserror = "1" tinytemplate = "1.2" -tracing = "0.1.37" +tracing = { workspace = true } [dev-dependencies] hex-literal = "0.4" diff --git a/misc/webrtc-utils/src/fingerprint.rs b/misc/webrtc-utils/src/fingerprint.rs index a02c4d1116d..c32d33d5bab 100644 --- a/misc/webrtc-utils/src/fingerprint.rs +++ b/misc/webrtc-utils/src/fingerprint.rs @@ -19,9 +19,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::fmt; + use libp2p_core::multihash; use sha2::Digest as _; -use std::fmt; pub const SHA256: &str = "sha-256"; const MULTIHASH_SHA256_CODE: u64 = 0x12; diff --git a/misc/webrtc-utils/src/noise.rs b/misc/webrtc-utils/src/noise.rs index 9180acfc1ca..705db7f4697 100644 --- a/misc/webrtc-utils/src/noise.rs +++ b/misc/webrtc-utils/src/noise.rs @@ -19,16 +19,17 @@ // DEALINGS IN THE SOFTWARE. use futures::{AsyncRead, AsyncWrite, AsyncWriteExt}; -use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}; -use libp2p_core::UpgradeInfo; +use libp2p_core::{ + upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}, + UpgradeInfo, +}; use libp2p_identity as identity; use libp2p_identity::PeerId; use libp2p_noise as noise; +pub use noise::Error; use crate::fingerprint::Fingerprint; -pub use noise::Error; - pub async fn inbound( id_keys: identity::Keypair, stream: T, @@ -89,9 +90,10 @@ pub(crate) fn noise_prologue( #[cfg(test)] mod tests { - use super::*; use hex_literal::hex; + use super::*; + #[test] fn noise_prologue_tests() { let a = Fingerprint::raw(hex!( diff --git a/misc/webrtc-utils/src/sdp.rs b/misc/webrtc-utils/src/sdp.rs index 0796548f449..96a07f5db95 100644 --- a/misc/webrtc-utils/src/sdp.rs +++ b/misc/webrtc-utils/src/sdp.rs @@ -18,13 +18,13 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::fingerprint::Fingerprint; -use serde::Serialize; use std::net::{IpAddr, SocketAddr}; + +use rand::{distributions::Alphanumeric, thread_rng, Rng}; +use serde::Serialize; use tinytemplate::TinyTemplate; -use rand::distributions::Alphanumeric; -use rand::{thread_rng, Rng}; +use crate::fingerprint::Fingerprint; pub fn answer(addr: SocketAddr, server_fingerprint: Fingerprint, client_ufrag: &str) -> String { let answer = render_description( @@ -71,7 +71,8 @@ pub fn answer(addr: SocketAddr, server_fingerprint: Fingerprint, client_ufrag: & // the answerer is received, which adds additional latency. setup:active allows the answer and // the DTLS handshake to occur in parallel. Thus, setup:active is RECOMMENDED. // -// a=candidate: +// a=candidate: +// // // A transport address for a candidate that can be used for connectivity checks (RFC8839). // diff --git a/misc/webrtc-utils/src/stream.rs b/misc/webrtc-utils/src/stream.rs index 0e1496eb640..0ec420a103a 100644 --- a/misc/webrtc-utils/src/stream.rs +++ b/misc/webrtc-utils/src/stream.rs @@ -19,20 +19,22 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use bytes::Bytes; -use futures::{channel::oneshot, prelude::*, ready}; - use std::{ io, pin::Pin, task::{Context, Poll}, }; -use crate::proto::{Flag, Message}; +use bytes::Bytes; +use futures::{channel::oneshot, prelude::*, ready}; + use crate::{ - stream::drop_listener::GracefullyClosed, - stream::framed_dc::FramedDc, - stream::state::{Closing, State}, + proto::{Flag, Message}, + stream::{ + drop_listener::GracefullyClosed, + framed_dc::FramedDc, + state::{Closing, State}, + }, }; mod drop_listener; @@ -69,7 +71,8 @@ impl Stream where T: AsyncRead + AsyncWrite + Unpin + Clone, { - /// Returns a new [`Stream`] and a [`DropListener`], which will notify the receiver when/if the stream is dropped. + /// Returns a new [`Stream`] and a [`DropListener`], + /// which will notify the receiver when/if the stream is dropped. pub fn new(data_channel: T) -> (Self, DropListener) { let (sender, receiver) = oneshot::channel(); @@ -146,8 +149,14 @@ where } debug_assert!(read_buffer.is_empty()); - if let Some(message) = message { - *read_buffer = message.into(); + match message { + Some(msg) if !msg.is_empty() => { + *read_buffer = msg.into(); + } + _ => { + tracing::debug!("poll_read buffer is empty, received None"); + return Poll::Ready(Ok(0)); + } } } None => { @@ -169,8 +178,9 @@ where buf: &[u8], ) -> Poll> { while self.state.read_flags_in_async_write() { - // TODO: In case AsyncRead::poll_read encountered an error or returned None earlier, we will poll the - // underlying I/O resource once more. Is that allowed? How about introducing a state IoReadClosed? + // TODO: In case AsyncRead::poll_read encountered an error or returned None earlier, we + // will poll the underlying I/O resource once more. Is that allowed? How + // about introducing a state IoReadClosed? let Self { read_buffer, @@ -259,11 +269,12 @@ where #[cfg(test)] mod tests { - use super::*; - use crate::stream::framed_dc::codec; use asynchronous_codec::Encoder; use bytes::BytesMut; + use super::*; + use crate::stream::framed_dc::codec; + #[test] fn max_data_len() { // Largest possible message. diff --git a/misc/webrtc-utils/src/stream/drop_listener.rs b/misc/webrtc-utils/src/stream/drop_listener.rs index 9745e3d4364..ea3f19d2f57 100644 --- a/misc/webrtc-utils/src/stream/drop_listener.rs +++ b/misc/webrtc-utils/src/stream/drop_listener.rs @@ -18,17 +18,22 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::channel::oneshot; -use futures::channel::oneshot::Canceled; -use futures::{AsyncRead, AsyncWrite, FutureExt, SinkExt}; +use std::{ + future::Future, + io, + pin::Pin, + task::{Context, Poll}, +}; -use std::future::Future; -use std::io; -use std::pin::Pin; -use std::task::{Context, Poll}; +use futures::{ + channel::{oneshot, oneshot::Canceled}, + AsyncRead, AsyncWrite, FutureExt, SinkExt, +}; -use crate::proto::{Flag, Message}; -use crate::stream::framed_dc::FramedDc; +use crate::{ + proto::{Flag, Message}, + stream::framed_dc::FramedDc, +}; #[must_use] pub struct DropListener { diff --git a/misc/webrtc-utils/src/stream/framed_dc.rs b/misc/webrtc-utils/src/stream/framed_dc.rs index 721178fdcd3..a7b9b6214e0 100644 --- a/misc/webrtc-utils/src/stream/framed_dc.rs +++ b/misc/webrtc-utils/src/stream/framed_dc.rs @@ -21,8 +21,10 @@ use asynchronous_codec::Framed; use futures::{AsyncRead, AsyncWrite}; -use crate::proto::Message; -use crate::stream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}; +use crate::{ + proto::Message, + stream::{MAX_DATA_LEN, MAX_MSG_LEN, VARINT_LEN}, +}; pub(crate) type FramedDc = Framed>; pub(crate) fn new(inner: T) -> FramedDc diff --git a/misc/webrtc-utils/src/stream/state.rs b/misc/webrtc-utils/src/stream/state.rs index 082325e4d47..006c1610d00 100644 --- a/misc/webrtc-utils/src/stream/state.rs +++ b/misc/webrtc-utils/src/stream/state.rs @@ -18,10 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use bytes::Bytes; - use std::io; +use bytes::Bytes; + use crate::proto::Flag; #[derive(Debug, Copy, Clone)] @@ -46,8 +46,8 @@ pub(crate) enum State { /// Represents the state of closing one half (either read or write) of the connection. /// -/// Gracefully closing the read or write requires sending the `STOP_SENDING` or `FIN` flag respectively -/// and flushing the underlying connection. +/// Gracefully closing the read or write requires sending the `STOP_SENDING` or `FIN` flag +/// respectively and flushing the underlying connection. #[derive(Debug, Copy, Clone)] pub(crate) enum Closing { Requested, @@ -181,8 +181,8 @@ impl State { /// Whether we should read from the stream in the [`futures::AsyncWrite`] implementation. /// - /// This is necessary for read-closed streams because we would otherwise not read any more flags from - /// the socket. + /// This is necessary for read-closed streams because we would otherwise + /// not read any more flags from the socket. pub(crate) fn read_flags_in_async_write(&self) -> bool { matches!(self, Self::ReadClosed) } @@ -324,9 +324,10 @@ impl State { #[cfg(test)] mod tests { - use super::*; use std::io::ErrorKind; + use super::*; + #[test] fn cannot_read_after_receiving_fin() { let mut open = State::Open; diff --git a/misc/webrtc-utils/src/transport.rs b/misc/webrtc-utils/src/transport.rs index 440ad73ed02..60b1934082f 100644 --- a/misc/webrtc-utils/src/transport.rs +++ b/misc/webrtc-utils/src/transport.rs @@ -1,7 +1,9 @@ -use crate::fingerprint::Fingerprint; -use libp2p_core::{multiaddr::Protocol, Multiaddr}; use std::net::{IpAddr, SocketAddr}; +use libp2p_core::{multiaddr::Protocol, Multiaddr}; + +use crate::fingerprint::Fingerprint; + /// Parse the given [`Multiaddr`] into a [`SocketAddr`] and a [`Fingerprint`] for dialing. pub fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerprint)> { let mut iter = addr.iter(); @@ -38,9 +40,10 @@ pub fn parse_webrtc_dial_addr(addr: &Multiaddr) -> Option<(SocketAddr, Fingerpri #[cfg(test)] mod tests { - use super::*; use std::net::{Ipv4Addr, Ipv6Addr}; + use super::*; + #[test] fn parse_valid_address_with_certhash_and_p2p() { let addr = "/ip4/127.0.0.1/udp/39901/webrtc-direct/certhash/uEiDikp5KVUgkLta1EjUN-IKbHk-dUBg8VzKgf5nXxLK46w/p2p/12D3KooWNpDk9w6WrEEcdsEH1y47W71S36yFjw4sd3j7omzgCSMS" diff --git a/muxers/mplex/CHANGELOG.md b/muxers/mplex/CHANGELOG.md index 48ab616e131..f0c2c0353da 100644 --- a/muxers/mplex/CHANGELOG.md +++ b/muxers/mplex/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.42.0 + + + ## 0.41.0 - Migrate to `{In,Out}boundConnectionUpgrade` traits. diff --git a/muxers/mplex/Cargo.toml b/muxers/mplex/Cargo.toml index 1980fd5be0a..78650218f4b 100644 --- a/muxers/mplex/Cargo.toml +++ b/muxers/mplex/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-mplex" edition = "2021" rust-version = { workspace = true } description = "Mplex multiplexing protocol for libp2p" -version = "0.41.0" +version = "0.42.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -12,38 +12,36 @@ categories = ["network-programming", "asynchronous"] [dependencies] bytes = "1" -futures = "0.3.29" +futures = { workspace = true } asynchronous-codec = { workspace = true } libp2p-core = { workspace = true } libp2p-identity = { workspace = true } nohash-hasher = "0.2" parking_lot = "0.12" rand = "0.8" -smallvec = "1.11.2" -tracing = "0.1.37" +smallvec = "1.13.2" +tracing = { workspace = true } unsigned-varint = { workspace = true, features = ["asynchronous_codec"] } [dev-dependencies] async-std = { version = "1.7.0", features = ["attributes"] } criterion = "0.5" -futures = "0.3" +futures = { workspace = true } libp2p-identity = { workspace = true, features = ["rand"] } libp2p-muxer-test-harness = { path = "../test-harness" } libp2p-plaintext = { workspace = true } libp2p-tcp = { workspace = true, features = ["async-io"] } quickcheck = { workspace = true } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +libp2p-test-utils = { workspace = true } [[bench]] name = "split_send_size" harness = false -# Passing arguments to the docsrs builder in order to properly document cfg's. +# Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/muxers/mplex/benches/split_send_size.rs b/muxers/mplex/benches/split_send_size.rs index 9a9814d2f2a..7a0e9780ca7 100644 --- a/muxers/mplex/benches/split_send_size.rs +++ b/muxers/mplex/benches/split_send_size.rs @@ -21,21 +21,23 @@ //! A benchmark for the `split_send_size` configuration option //! using different transports. +use std::{pin::Pin, time::Duration}; + use async_std::task; use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; -use futures::future::poll_fn; -use futures::prelude::*; -use futures::{channel::oneshot, future::join}; -use libp2p_core::muxing::StreamMuxerExt; -use libp2p_core::transport::ListenerId; -use libp2p_core::{multiaddr::multiaddr, muxing, transport, upgrade, Multiaddr, Transport}; +use futures::{ + channel::oneshot, + future::{join, poll_fn}, + prelude::*, +}; +use libp2p_core::{ + multiaddr::multiaddr, muxing, muxing::StreamMuxerExt, transport, transport::ListenerId, + upgrade, Endpoint, Multiaddr, Transport, +}; use libp2p_identity as identity; use libp2p_identity::PeerId; use libp2p_mplex as mplex; use libp2p_plaintext as plaintext; -use std::pin::Pin; -use std::time::Duration; -use tracing_subscriber::EnvFilter; type BenchTransport = transport::Boxed<(PeerId, muxing::StreamMuxerBox)>; @@ -52,9 +54,7 @@ const BENCH_SIZES: [usize; 8] = [ ]; fn prepare(c: &mut Criterion) { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let payload: Vec = vec![1; 1024 * 1024]; @@ -101,7 +101,7 @@ fn prepare(c: &mut Criterion) { fn run( receiver_trans: &mut BenchTransport, sender_trans: &mut BenchTransport, - payload: &Vec, + payload: &[u8], listen_addr: &Multiaddr, ) { receiver_trans @@ -119,7 +119,8 @@ fn run( } transport::TransportEvent::Incoming { upgrade, .. } => { let (_peer, mut conn) = upgrade.await.unwrap(); - // Just calling `poll_inbound` without `poll` is fine here because mplex makes progress through all `poll_` functions. It is hacky though. + // Just calling `poll_inbound` without `poll` is fine here because mplex makes + // progress through all `poll_` functions. It is hacky though. let mut s = poll_fn(|cx| conn.poll_inbound_unpin(cx)) .await .expect("unexpected error"); @@ -146,8 +147,19 @@ fn run( // Spawn and block on the sender, i.e. until all data is sent. let sender = async move { let addr = addr_receiver.await.unwrap(); - let (_peer, mut conn) = sender_trans.dial(addr).unwrap().await.unwrap(); - // Just calling `poll_outbound` without `poll` is fine here because mplex makes progress through all `poll_` functions. It is hacky though. + let (_peer, mut conn) = sender_trans + .dial( + addr, + transport::DialOpts { + role: Endpoint::Dialer, + port_use: transport::PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap(); + // Just calling `poll_outbound` without `poll` is fine here because mplex makes progress + // through all `poll_` functions. It is hacky though. let mut stream = poll_fn(|cx| conn.poll_outbound_unpin(cx)).await.unwrap(); let mut off = 0; loop { diff --git a/muxers/mplex/src/codec.rs b/muxers/mplex/src/codec.rs index 014ee899280..a4a04d1964d 100644 --- a/muxers/mplex/src/codec.rs +++ b/muxers/mplex/src/codec.rs @@ -18,14 +18,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use asynchronous_codec::{Decoder, Encoder}; -use bytes::{BufMut, Bytes, BytesMut}; -use libp2p_core::Endpoint; use std::{ fmt, hash::{Hash, Hasher}, io, mem, }; + +use asynchronous_codec::{Decoder, Encoder}; +use bytes::{BufMut, Bytes, BytesMut}; +use libp2p_core::Endpoint; use unsigned_varint::{codec, encode}; // Maximum size for a packet: 1MB as per the spec. diff --git a/muxers/mplex/src/config.rs b/muxers/mplex/src/config.rs index 3bf5e703a18..45bb05b2240 100644 --- a/muxers/mplex/src/config.rs +++ b/muxers/mplex/src/config.rs @@ -18,9 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::codec::MAX_FRAME_SIZE; use std::cmp; +use crate::codec::MAX_FRAME_SIZE; + pub(crate) const DEFAULT_MPLEX_PROTOCOL_NAME: &str = "/mplex/6.7.0"; /// Configuration for the multiplexer. diff --git a/muxers/mplex/src/io.rs b/muxers/mplex/src/io.rs index 753294a7845..eeea4ce734f 100644 --- a/muxers/mplex/src/io.rs +++ b/muxers/mplex/src/io.rs @@ -18,23 +18,31 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::codec::{Codec, Frame, LocalStreamId, RemoteStreamId}; -use crate::{MaxBufferBehaviour, MplexConfig}; +pub(crate) use std::io::{Error, Result}; +use std::{ + cmp, + collections::VecDeque, + fmt, io, mem, + sync::Arc, + task::{Context, Poll, Waker}, +}; + use asynchronous_codec::Framed; use bytes::Bytes; -use futures::task::{waker_ref, ArcWake, AtomicWaker, WakerRef}; -use futures::{prelude::*, ready, stream::Fuse}; +use futures::{ + prelude::*, + ready, + stream::Fuse, + task::{waker_ref, ArcWake, AtomicWaker, WakerRef}, +}; use nohash_hasher::{IntMap, IntSet}; use parking_lot::Mutex; use smallvec::SmallVec; -use std::collections::VecDeque; -use std::{ - cmp, fmt, io, mem, - sync::Arc, - task::{Context, Poll, Waker}, -}; -pub(crate) use std::io::{Error, Result}; +use crate::{ + codec::{Codec, Frame, LocalStreamId, RemoteStreamId}, + MaxBufferBehaviour, MplexConfig, +}; /// A connection identifier. /// /// Randomly generated and mainly intended to improve log output @@ -224,7 +232,7 @@ where // yield to give the current task a chance to read // from the respective substreams. if num_buffered == self.config.max_buffer_len { - cx.waker().clone().wake(); + cx.waker().wake_by_ref(); return Poll::Pending; } @@ -302,13 +310,11 @@ where /// reading and writing immediately. The remote is informed /// based on the current state of the substream: /// - /// * If the substream was open, a `Reset` frame is sent at - /// the next opportunity. - /// * If the substream was half-closed, i.e. a `Close` frame - /// has already been sent, nothing further happens. - /// * If the substream was half-closed by the remote, i.e. - /// a `Close` frame has already been received, a `Close` - /// frame is sent at the next opportunity. + /// * If the substream was open, a `Reset` frame is sent at the next opportunity. + /// * If the substream was half-closed, i.e. a `Close` frame has already been sent, nothing + /// further happens. + /// * If the substream was half-closed by the remote, i.e. a `Close` frame has already been + /// received, a `Close` frame is sent at the next opportunity. /// /// If the multiplexed stream is closed or encountered /// an error earlier, or there is no known substream with @@ -456,7 +462,7 @@ where // next frame for `id`, yield to give the current task // a chance to read from the other substream(s). if num_buffered == self.config.max_buffer_len { - cx.waker().clone().wake(); + cx.waker().wake_by_ref(); return Poll::Pending; } @@ -663,7 +669,7 @@ where connection=%self.id, "No task to read from blocked stream. Waking current task." ); - cx.waker().clone().wake(); + cx.waker().wake_by_ref(); } else if let Some(id) = stream_id { // We woke some other task, but are still interested in // reading `Data` frames from the current stream when unblocked. @@ -1146,15 +1152,14 @@ const EXTRA_PENDING_FRAMES: usize = 1000; #[cfg(test)] mod tests { - use super::*; + use std::{collections::HashSet, num::NonZeroU8, ops::DerefMut, pin::Pin}; + use async_std::task; use asynchronous_codec::{Decoder, Encoder}; use bytes::BytesMut; use quickcheck::*; - use std::collections::HashSet; - use std::num::NonZeroU8; - use std::ops::DerefMut; - use std::pin::Pin; + + use super::*; impl Arbitrary for MaxBufferBehaviour { fn arbitrary(g: &mut Gen) -> MaxBufferBehaviour { @@ -1226,10 +1231,7 @@ mod tests { #[test] fn max_buffer_behaviour() { - use tracing_subscriber::EnvFilter; - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); fn prop(cfg: MplexConfig, overflow: NonZeroU8) { let mut r_buf = BytesMut::new(); @@ -1364,10 +1366,7 @@ mod tests { #[test] fn close_on_error() { - use tracing_subscriber::EnvFilter; - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); fn prop(cfg: MplexConfig, num_streams: NonZeroU8) { let num_streams = cmp::min(cfg.max_substreams, num_streams.get() as usize); diff --git a/muxers/mplex/src/lib.rs b/muxers/mplex/src/lib.rs index c67e0e3baec..1ef89dc283a 100644 --- a/muxers/mplex/src/lib.rs +++ b/muxers/mplex/src/lib.rs @@ -26,15 +26,22 @@ mod codec; mod config; mod io; -pub use config::{MaxBufferBehaviour, MplexConfig}; +use std::{ + cmp, iter, + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; use bytes::Bytes; use codec::LocalStreamId; -use futures::{future, prelude::*, ready}; -use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; -use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade, UpgradeInfo}; +pub use config::{MaxBufferBehaviour, MplexConfig}; +use futures::{prelude::*, ready}; +use libp2p_core::{ + muxing::{StreamMuxer, StreamMuxerEvent}, + upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade, UpgradeInfo}, +}; use parking_lot::Mutex; -use std::{cmp, iter, pin::Pin, sync::Arc, task::Context, task::Poll}; impl UpgradeInfo for MplexConfig { type Info = &'static str; diff --git a/muxers/test-harness/Cargo.toml b/muxers/test-harness/Cargo.toml index 05cef9ad49f..f4632437ce6 100644 --- a/muxers/test-harness/Cargo.toml +++ b/muxers/test-harness/Cargo.toml @@ -12,10 +12,10 @@ release = false [dependencies] libp2p-core = { workspace = true } -futures = "0.3.29" -futures-timer = "3.0.2" +futures = { workspace = true } +futures-timer = "3.0.3" futures_ringbuf = "0.4.0" -tracing = "0.1.37" +tracing = { workspace = true } [lints] workspace = true diff --git a/muxers/test-harness/src/lib.rs b/muxers/test-harness/src/lib.rs index 16c71f414f0..489d476f158 100644 --- a/muxers/test-harness/src/lib.rs +++ b/muxers/test-harness/src/lib.rs @@ -1,15 +1,20 @@ +use std::{ + fmt, + future::Future, + mem, + pin::Pin, + task::{Context, Poll}, + time::Duration, +}; + +use futures::{future, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, Stream, StreamExt}; +use libp2p_core::{ + muxing::StreamMuxerExt, + upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}, + StreamMuxer, UpgradeInfo, +}; + use crate::future::{BoxFuture, Either, FutureExt}; -use futures::{future, AsyncRead, AsyncWrite}; -use futures::{AsyncReadExt, Stream}; -use futures::{AsyncWriteExt, StreamExt}; -use libp2p_core::muxing::StreamMuxerExt; -use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}; -use libp2p_core::{StreamMuxer, UpgradeInfo}; -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::time::Duration; -use std::{fmt, mem}; pub async fn connected_muxers_on_memory_ring_buffer() -> (M, M) where @@ -41,7 +46,8 @@ where .unwrap() } -/// Verifies that Alice can send a message and immediately close the stream afterwards and Bob can use `read_to_end` to read the entire message. +/// Verifies that Alice can send a message and immediately close the stream afterwards and Bob can +/// use `read_to_end` to read the entire message. pub async fn close_implies_flush(alice: A, bob: B) where A: StreamMuxer + Unpin, @@ -99,7 +105,8 @@ where .await; } -/// Runs the given protocol between the two parties, ensuring commutativity, i.e. either party can be the dialer and listener. +/// Runs the given protocol between the two parties, ensuring commutativity, i.e. either party can +/// be the dialer and listener. async fn run_commutative( mut alice: A, mut bob: B, @@ -120,7 +127,8 @@ async fn run_commutative( /// Runs a given protocol between the two parties. /// /// The first party will open a new substream and the second party will wait for this. -/// The [`StreamMuxer`] is polled until both parties have completed the protocol to ensure that the underlying connection can make progress at all times. +/// The [`StreamMuxer`] is polled until both parties have completed the protocol to ensure that the +/// underlying connection can make progress at all times. async fn run( dialer: &mut A, listener: &mut B, @@ -206,7 +214,7 @@ enum Event { ProtocolComplete, } -impl<'m, M> Stream for Harness<'m, M> +impl Stream for Harness<'_, M> where M: StreamMuxer + Unpin, { diff --git a/muxers/yamux/CHANGELOG.md b/muxers/yamux/CHANGELOG.md index c8534166ea6..855b3a33773 100644 --- a/muxers/yamux/CHANGELOG.md +++ b/muxers/yamux/CHANGELOG.md @@ -1,9 +1,23 @@ +## 0.46.0 + + + +## 0.45.2 + +- Update `yamux` to version `v0.13.3`.` + ## 0.45.1 - Deprecate `WindowUpdateMode::on_receive`. It does not enforce flow-control, i.e. breaks backpressure. Use `WindowUpdateMode::on_read` instead. See `yamux` crate version `v0.12.1` and [Yamux PR #177](https://github.com/libp2p/rust-yamux/pull/177). +- `yamux` `v0.13` enables auto-tuning for the Yamux stream receive window. + While preserving small buffers on low-latency and/or low-bandwidth connections, this change allows for high-latency and/or high-bandwidth connections to exhaust the available bandwidth on a single stream. + Have `libp2p-yamux` use `yamux` `v0.13` (new version) by default and fall back to `yamux` `v0.12` (old version) when setting any configuration options. + Thus default users benefit from the increased performance, while power users with custom configurations maintain the old behavior. + `libp2p-yamux` will switch over to `yamux` `v0.13` entirely with the next breaking release. + See [PR 4970](https://github.com/libp2p/rust-libp2p/pull/4970). ## 0.45.0 diff --git a/muxers/yamux/Cargo.toml b/muxers/yamux/Cargo.toml index 1456238121b..cd3f8347bd0 100644 --- a/muxers/yamux/Cargo.toml +++ b/muxers/yamux/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-yamux" edition = "2021" rust-version = { workspace = true } description = "Yamux multiplexing protocol for libp2p" -version = "0.45.1" +version = "0.46.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,11 +11,13 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -futures = "0.3.29" +either = "1" +futures = { workspace = true } libp2p-core = { workspace = true } -thiserror = "1.0" -yamux = "0.12" -tracing = "0.1.37" +thiserror = { workspace = true } +yamux012 = { version = "0.12.1", package = "yamux" } +yamux013 = { version = "0.13.3", package = "yamux" } +tracing = { workspace = true } [dev-dependencies] async-std = { version = "1.7.0", features = ["attributes"] } @@ -25,8 +27,6 @@ libp2p-muxer-test-harness = { path = "../test-harness" } # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/muxers/yamux/src/lib.rs b/muxers/yamux/src/lib.rs index fc7ff430396..001eb6b0348 100644 --- a/muxers/yamux/src/lib.rs +++ b/muxers/yamux/src/lib.rs @@ -22,28 +22,33 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use futures::{future, prelude::*, ready}; -use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; -use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade, UpgradeInfo}; -use std::collections::VecDeque; -use std::io::{IoSlice, IoSliceMut}; -use std::task::Waker; use std::{ - io, iter, + collections::VecDeque, + io, + io::{IoSlice, IoSliceMut}, + iter, pin::Pin, - task::{Context, Poll}, + task::{Context, Poll, Waker}, +}; + +use either::Either; +use futures::{prelude::*, ready}; +use libp2p_core::{ + muxing::{StreamMuxer, StreamMuxerEvent}, + upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade, UpgradeInfo}, }; use thiserror::Error; -use yamux::ConnectionError; /// A Yamux connection. #[derive(Debug)] pub struct Muxer { - connection: yamux::Connection, - /// Temporarily buffers inbound streams in case our node is performing backpressure on the remote. + connection: Either, yamux013::Connection>, + /// Temporarily buffers inbound streams in case our node is + /// performing backpressure on the remote. /// - /// The only way how yamux can make progress is by calling [`yamux::Connection::poll_next_inbound`]. However, the - /// [`StreamMuxer`] interface is designed to allow a caller to selectively make progress via + /// The only way how yamux can make progress is by calling + /// [`yamux013::Connection::poll_next_inbound`]. However, the [`StreamMuxer`] interface is + /// designed to allow a caller to selectively make progress via /// [`StreamMuxer::poll_inbound`] and [`StreamMuxer::poll_outbound`] whilst the more general /// [`StreamMuxer::poll`] is designed to make progress on existing streams etc. /// @@ -57,7 +62,8 @@ pub struct Muxer { /// How many streams to buffer before we start resetting them. /// /// This is equal to the ACK BACKLOG in `rust-yamux`. -/// Thus, for peers running on a recent version of `rust-libp2p`, we should never need to reset streams because they'll voluntarily stop opening them once they hit the ACK backlog. +/// Thus, for peers running on a recent version of `rust-libp2p`, we should never need to reset +/// streams because they'll voluntarily stop opening them once they hit the ACK backlog. const MAX_BUFFERED_INBOUND_STREAMS: usize = 256; impl Muxer @@ -65,9 +71,9 @@ where C: AsyncRead + AsyncWrite + Send + Unpin + 'static, { /// Create a new Yamux connection. - fn new(io: C, cfg: yamux::Config, mode: yamux::Mode) -> Self { + fn new(connection: Either, yamux013::Connection>) -> Self { Muxer { - connection: yamux::Connection::new(io, cfg, mode), + connection, inbound_stream_buffer: VecDeque::default(), inbound_stream_waker: None, } @@ -103,16 +109,23 @@ where mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - let stream = ready!(self.connection.poll_new_outbound(cx).map_err(Error)?); - - Poll::Ready(Ok(Stream(stream))) + let stream = match self.connection.as_mut() { + Either::Left(c) => ready!(c.poll_new_outbound(cx)) + .map_err(|e| Error(Either::Left(e))) + .map(|s| Stream(Either::Left(s))), + Either::Right(c) => ready!(c.poll_new_outbound(cx)) + .map_err(|e| Error(Either::Right(e))) + .map(|s| Stream(Either::Right(s))), + }?; + Poll::Ready(Ok(stream)) } #[tracing::instrument(level = "trace", name = "StreamMuxer::poll_close", skip(self, cx))] fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - ready!(self.connection.poll_close(cx).map_err(Error)?); - - Poll::Ready(Ok(())) + match self.connection.as_mut() { + Either::Left(c) => c.poll_close(cx).map_err(|e| Error(Either::Left(e))), + Either::Right(c) => c.poll_close(cx).map_err(|e| Error(Either::Right(e))), + } } #[tracing::instrument(level = "trace", name = "StreamMuxer::poll", skip(self, cx))] @@ -146,7 +159,7 @@ where /// A stream produced by the yamux multiplexer. #[derive(Debug)] -pub struct Stream(yamux::Stream); +pub struct Stream(Either); impl AsyncRead for Stream { fn poll_read( @@ -154,7 +167,7 @@ impl AsyncRead for Stream { cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - Pin::new(&mut self.0).poll_read(cx, buf) + either::for_both!(self.0.as_mut(), s => Pin::new(s).poll_read(cx, buf)) } fn poll_read_vectored( @@ -162,7 +175,7 @@ impl AsyncRead for Stream { cx: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>], ) -> Poll> { - Pin::new(&mut self.0).poll_read_vectored(cx, bufs) + either::for_both!(self.0.as_mut(), s => Pin::new(s).poll_read_vectored(cx, bufs)) } } @@ -172,7 +185,7 @@ impl AsyncWrite for Stream { cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - Pin::new(&mut self.0).poll_write(cx, buf) + either::for_both!(self.0.as_mut(), s => Pin::new(s).poll_write(cx, buf)) } fn poll_write_vectored( @@ -180,15 +193,15 @@ impl AsyncWrite for Stream { cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll> { - Pin::new(&mut self.0).poll_write_vectored(cx, bufs) + either::for_both!(self.0.as_mut(), s => Pin::new(s).poll_write_vectored(cx, bufs)) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.0).poll_flush(cx) + either::for_both!(self.0.as_mut(), s => Pin::new(s).poll_flush(cx)) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.0).poll_close(cx) + either::for_both!(self.0.as_mut(), s => Pin::new(s).poll_close(cx)) } } @@ -197,11 +210,16 @@ where C: AsyncRead + AsyncWrite + Unpin + 'static, { fn poll_inner(&mut self, cx: &mut Context<'_>) -> Poll> { - let stream = ready!(self.connection.poll_next_inbound(cx)) - .transpose() - .map_err(Error)? - .map(Stream) - .ok_or(Error(ConnectionError::Closed))?; + let stream = match self.connection.as_mut() { + Either::Left(c) => ready!(c.poll_next_inbound(cx)) + .ok_or(Error(Either::Left(yamux012::ConnectionError::Closed)))? + .map_err(|e| Error(Either::Left(e))) + .map(|s| Stream(Either::Left(s)))?, + Either::Right(c) => ready!(c.poll_next_inbound(cx)) + .ok_or(Error(Either::Right(yamux013::ConnectionError::Closed)))? + .map_err(|e| Error(Either::Right(e))) + .map(|s| Stream(Either::Right(s)))?, + }; Poll::Ready(Ok(stream)) } @@ -209,14 +227,33 @@ where /// The yamux configuration. #[derive(Debug, Clone)] -pub struct Config { - inner: yamux::Config, - mode: Option, +pub struct Config(Either); + +impl Default for Config { + fn default() -> Self { + Self(Either::Right(Config013::default())) + } +} + +#[derive(Debug, Clone)] +struct Config012 { + inner: yamux012::Config, + mode: Option, +} + +impl Default for Config012 { + fn default() -> Self { + let mut inner = yamux012::Config::default(); + // For conformity with mplex, read-after-close on a multiplexed + // connection is never permitted and not configurable. + inner.set_read_after_close(false); + Self { inner, mode: None } + } } /// The window update mode determines when window updates are /// sent to the remote, giving it new credit to send more data. -pub struct WindowUpdateMode(yamux::WindowUpdateMode); +pub struct WindowUpdateMode(yamux012::WindowUpdateMode); impl WindowUpdateMode { /// The window update mode whereby the remote is given @@ -234,7 +271,7 @@ impl WindowUpdateMode { #[deprecated(note = "Use `WindowUpdateMode::on_read` instead.")] pub fn on_receive() -> Self { #[allow(deprecated)] - WindowUpdateMode(yamux::WindowUpdateMode::OnReceive) + WindowUpdateMode(yamux012::WindowUpdateMode::OnReceive) } /// The window update mode whereby the remote is given new @@ -252,62 +289,71 @@ impl WindowUpdateMode { /// > **Note**: With this strategy, there is usually no point in the /// > receive buffer being larger than the window size. pub fn on_read() -> Self { - WindowUpdateMode(yamux::WindowUpdateMode::OnRead) + WindowUpdateMode(yamux012::WindowUpdateMode::OnRead) } } impl Config { /// Creates a new `YamuxConfig` in client mode, regardless of whether /// it will be used for an inbound or outbound upgrade. + #[deprecated(note = "Will be removed with the next breaking release.")] pub fn client() -> Self { - Self { - mode: Some(yamux::Mode::Client), + Self(Either::Left(Config012 { + mode: Some(yamux012::Mode::Client), ..Default::default() - } + })) } /// Creates a new `YamuxConfig` in server mode, regardless of whether /// it will be used for an inbound or outbound upgrade. + #[deprecated(note = "Will be removed with the next breaking release.")] pub fn server() -> Self { - Self { - mode: Some(yamux::Mode::Server), + Self(Either::Left(Config012 { + mode: Some(yamux012::Mode::Server), ..Default::default() - } + })) } /// Sets the size (in bytes) of the receive window per substream. + #[deprecated( + note = "Will be replaced in the next breaking release with a connection receive window size limit." + )] pub fn set_receive_window_size(&mut self, num_bytes: u32) -> &mut Self { - self.inner.set_receive_window(num_bytes); - self + self.set(|cfg| cfg.set_receive_window(num_bytes)) } /// Sets the maximum size (in bytes) of the receive buffer per substream. + #[deprecated(note = "Will be removed with the next breaking release.")] pub fn set_max_buffer_size(&mut self, num_bytes: usize) -> &mut Self { - self.inner.set_max_buffer_size(num_bytes); - self + self.set(|cfg| cfg.set_max_buffer_size(num_bytes)) } /// Sets the maximum number of concurrent substreams. pub fn set_max_num_streams(&mut self, num_streams: usize) -> &mut Self { - self.inner.set_max_num_streams(num_streams); - self + self.set(|cfg| cfg.set_max_num_streams(num_streams)) } /// Sets the window update mode that determines when the remote /// is given new credit for sending more data. + #[deprecated( + note = "`WindowUpdate::OnRead` is the default. `WindowUpdate::OnReceive` breaks backpressure, is thus not recommended, and will be removed in the next breaking release. Thus this method becomes obsolete and will be removed with the next breaking release." + )] pub fn set_window_update_mode(&mut self, mode: WindowUpdateMode) -> &mut Self { - self.inner.set_window_update_mode(mode.0); - self + self.set(|cfg| cfg.set_window_update_mode(mode.0)) } -} -impl Default for Config { - fn default() -> Self { - let mut inner = yamux::Config::default(); - // For conformity with mplex, read-after-close on a multiplexed - // connection is never permitted and not configurable. - inner.set_read_after_close(false); - Config { inner, mode: None } + fn set(&mut self, f: impl FnOnce(&mut yamux012::Config) -> &mut yamux012::Config) -> &mut Self { + let cfg012 = match self.0.as_mut() { + Either::Left(c) => &mut c.inner, + Either::Right(_) => { + self.0 = Either::Left(Config012::default()); + &mut self.0.as_mut().unwrap_left().inner + } + }; + + f(cfg012); + + self } } @@ -329,8 +375,18 @@ where type Future = future::Ready>; fn upgrade_inbound(self, io: C, _: Self::Info) -> Self::Future { - let mode = self.mode.unwrap_or(yamux::Mode::Server); - future::ready(Ok(Muxer::new(io, self.inner, mode))) + let connection = match self.0 { + Either::Left(Config012 { inner, mode }) => Either::Left(yamux012::Connection::new( + io, + inner, + mode.unwrap_or(yamux012::Mode::Server), + )), + Either::Right(Config013(cfg)) => { + Either::Right(yamux013::Connection::new(io, cfg, yamux013::Mode::Server)) + } + }; + + future::ready(Ok(Muxer::new(connection))) } } @@ -343,21 +399,69 @@ where type Future = future::Ready>; fn upgrade_outbound(self, io: C, _: Self::Info) -> Self::Future { - let mode = self.mode.unwrap_or(yamux::Mode::Client); - future::ready(Ok(Muxer::new(io, self.inner, mode))) + let connection = match self.0 { + Either::Left(Config012 { inner, mode }) => Either::Left(yamux012::Connection::new( + io, + inner, + mode.unwrap_or(yamux012::Mode::Client), + )), + Either::Right(Config013(cfg)) => { + Either::Right(yamux013::Connection::new(io, cfg, yamux013::Mode::Client)) + } + }; + + future::ready(Ok(Muxer::new(connection))) + } +} + +#[derive(Debug, Clone)] +struct Config013(yamux013::Config); + +impl Default for Config013 { + fn default() -> Self { + let mut cfg = yamux013::Config::default(); + // For conformity with mplex, read-after-close on a multiplexed + // connection is never permitted and not configurable. + cfg.set_read_after_close(false); + Self(cfg) } } /// The Yamux [`StreamMuxer`] error type. #[derive(Debug, Error)] #[error(transparent)] -pub struct Error(yamux::ConnectionError); +pub struct Error(Either); impl From for io::Error { fn from(err: Error) -> Self { match err.0 { - yamux::ConnectionError::Io(e) => e, - e => io::Error::new(io::ErrorKind::Other, e), + Either::Left(err) => match err { + yamux012::ConnectionError::Io(e) => e, + e => io::Error::new(io::ErrorKind::Other, e), + }, + Either::Right(err) => match err { + yamux013::ConnectionError::Io(e) => e, + e => io::Error::new(io::ErrorKind::Other, e), + }, } } } + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn config_set_switches_to_v012() { + // By default we use yamux v0.13. Thus we provide the benefits of yamux v0.13 to all users + // that do not depend on any of the behaviors (i.e. configuration options) of v0.12. + let mut cfg = Config::default(); + assert!(matches!( + cfg, + Config(Either::Right(Config013(yamux013::Config { .. }))) + )); + + // In case a user makes any configurations, use yamux v0.12 instead. + cfg.set_max_num_streams(42); + assert!(matches!(cfg, Config(Either::Left(Config012 { .. })))); + } +} diff --git a/protocols/autonat/CHANGELOG.md b/protocols/autonat/CHANGELOG.md index 1259dd01fd4..75a40b8c5ad 100644 --- a/protocols/autonat/CHANGELOG.md +++ b/protocols/autonat/CHANGELOG.md @@ -1,3 +1,29 @@ +## 0.13.1 + +- Verify that an incoming AutoNAT dial comes from a connected peer. See [PR 5597](https://github.com/libp2p/rust-libp2p/pull/5597). +- Deprecate `void` crate. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). +- Update to `libp2p-request-response` `v0.28.0`. + +## 0.13.0 + +- Due to the refactor of `Transport` it's no longer required to create a seperate transport for +AutoNAT where port reuse is disabled. This information is now passed by the behaviour. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568). +- Introduce the new AutoNATv2 protocol. + It's split into a client and a server part, represented in their respective modules + Features: + - The server now always dials back over a newly allocated port. + This more accurately reflects the reachability state for other peers and avoids accidental hole punching. + - The server can now test addresses different from the observed address (i.e., the connection to the server was made through a `p2p-circuit`). To mitigate against DDoS attacks, the client has to send more data to the server than the dial-back costs. + See [PR 5526](https://github.com/libp2p/rust-libp2p/pull/5526). + + + +## 0.12.1 +- Use `web-time` instead of `instant`. + See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). + ## 0.12.0 - Remove `Clone`, `PartialEq` and `Eq` implementations on `Event` and its sub-structs. diff --git a/protocols/autonat/Cargo.toml b/protocols/autonat/Cargo.toml index a1ecae7ccab..8ad4492fbff 100644 --- a/protocols/autonat/Cargo.toml +++ b/protocols/autonat/Cargo.toml @@ -3,39 +3,53 @@ name = "libp2p-autonat" edition = "2021" rust-version = { workspace = true } description = "NAT and firewall detection for libp2p" -version = "0.12.0" -authors = ["David Craven ", "Elena Frank "] +version = "0.13.1" +authors = [ + "David Craven ", + "Elena Frank ", + "Hannes Furmans ", +] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] + [dependencies] -async-trait = "0.1" -futures = "0.3" +async-trait = { version = "0.1", optional = true } +asynchronous-codec = { workspace = true } +either = { version = "1.9.0", optional = true } +futures = { workspace = true } +futures-bounded = { workspace = true, optional = true } futures-timer = "3.0" -instant = "0.1" +web-time = { workspace = true, optional = true } libp2p-core = { workspace = true } -libp2p-swarm = { workspace = true } -libp2p-request-response = { workspace = true } libp2p-identity = { workspace = true } +libp2p-request-response = { workspace = true, optional = true } +libp2p-swarm = { workspace = true } quick-protobuf = "0.8" -rand = "0.8" -tracing = "0.1.37" +tracing = { workspace = true } quick-protobuf-codec = { workspace = true } -asynchronous-codec = { workspace = true } +rand = "0.8" +rand_core = { version = "0.6", optional = true } +thiserror = { workspace = true, optional = true } [dev-dependencies] -async-std = { version = "1.10", features = ["attributes"] } +tokio = { workspace = true, features = ["macros", "rt", "sync"] } libp2p-swarm-test = { path = "../../swarm-test" } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +libp2p-test-utils = { workspace = true } +libp2p-identify = { workspace = true } +libp2p-swarm = { workspace = true, features = ["macros"] } + +[features] +default = ["v1", "v2"] +v1 = ["dep:libp2p-request-response", "dep:web-time", "dep:async-trait"] +v2 = ["dep:either", "dep:futures-bounded", "dep:thiserror", "dep:rand_core"] # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/autonat/src/lib.rs b/protocols/autonat/src/lib.rs index 10c87b1e984..e49eaadcb83 100644 --- a/protocols/autonat/src/lib.rs +++ b/protocols/autonat/src/lib.rs @@ -1,41 +1,10 @@ -// Copyright 2021 Protocol Labs. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +#![cfg_attr(docsrs, feature(doc_auto_cfg))] -//! Implementation of the [AutoNAT](https://github.com/libp2p/specs/blob/master/autonat/README.md) protocol. +#[cfg(feature = "v1")] +pub mod v1; -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#[cfg(feature = "v2")] +pub mod v2; -mod behaviour; -mod protocol; - -pub use self::{ - behaviour::{ - Behaviour, Config, Event, InboundProbeError, InboundProbeEvent, NatStatus, - OutboundProbeError, OutboundProbeEvent, ProbeId, - }, - protocol::{ResponseError, DEFAULT_PROTOCOL_NAME}, -}; -pub use libp2p_request_response::{InboundFailure, OutboundFailure}; - -mod proto { - #![allow(unreachable_pub)] - include!("generated/mod.rs"); - pub(crate) use self::structs::{mod_Message::*, Message}; -} +#[cfg(feature = "v1")] +pub use v1::*; diff --git a/protocols/autonat/src/v1.rs b/protocols/autonat/src/v1.rs new file mode 100644 index 00000000000..4de601c5df5 --- /dev/null +++ b/protocols/autonat/src/v1.rs @@ -0,0 +1,46 @@ +// Copyright 2021 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Implementation of the [AutoNAT](https://github.com/libp2p/specs/blob/master/autonat/README.md) protocol. +//! +//! ## Eventual Deprecation +//! This version of the protocol will eventually be deprecated in favor of [v2](crate::v2). +//! We recommend using v2 for new projects. + +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +pub(crate) mod behaviour; +pub(crate) mod protocol; + +pub use libp2p_request_response::{InboundFailure, OutboundFailure}; + +pub use self::{ + behaviour::{ + Behaviour, Config, Event, InboundProbeError, InboundProbeEvent, NatStatus, + OutboundProbeError, OutboundProbeEvent, ProbeId, + }, + protocol::{ResponseError, DEFAULT_PROTOCOL_NAME}, +}; + +pub(crate) mod proto { + #![allow(unreachable_pub)] + include!("v1/generated/mod.rs"); + pub(crate) use self::structs::{mod_Message::*, Message}; +} diff --git a/protocols/autonat/src/behaviour.rs b/protocols/autonat/src/v1/behaviour.rs similarity index 83% rename from protocols/autonat/src/behaviour.rs rename to protocols/autonat/src/v1/behaviour.rs index 0b80a079c0f..24ec1b13be7 100644 --- a/protocols/autonat/src/behaviour.rs +++ b/protocols/autonat/src/v1/behaviour.rs @@ -21,32 +21,33 @@ mod as_client; mod as_server; -use crate::protocol::{AutoNatCodec, DialRequest, DialResponse, ResponseError}; -use crate::DEFAULT_PROTOCOL_NAME; +use std::{ + collections::{HashMap, HashSet, VecDeque}, + iter, + task::{Context, Poll}, + time::Duration, +}; + use as_client::AsClient; pub use as_client::{OutboundProbeError, OutboundProbeEvent}; use as_server::AsServer; pub use as_server::{InboundProbeError, InboundProbeEvent}; use futures_timer::Delay; -use instant::Instant; -use libp2p_core::{multiaddr::Protocol, ConnectedPoint, Endpoint, Multiaddr}; +use libp2p_core::{multiaddr::Protocol, transport::PortUse, ConnectedPoint, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_request_response::{ self as request_response, InboundRequestId, OutboundRequestId, ProtocolSupport, ResponseChannel, }; use libp2p_swarm::{ - behaviour::{ - AddressChange, ConnectionClosed, ConnectionEstablished, DialFailure, ExpiredListenAddr, - ExternalAddrExpired, FromSwarm, - }, - ConnectionDenied, ConnectionId, ListenAddresses, NetworkBehaviour, NewExternalAddrCandidate, - THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, + behaviour::{AddressChange, ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm}, + ConnectionDenied, ConnectionId, ListenAddresses, NetworkBehaviour, THandler, THandlerInEvent, + THandlerOutEvent, ToSwarm, }; -use std::{ - collections::{HashMap, HashSet, VecDeque}, - iter, - task::{Context, Poll}, - time::Duration, +use web_time::Instant; + +use crate::{ + protocol::{AutoNatCodec, DialRequest, DialResponse, ResponseError}, + DEFAULT_PROTOCOL_NAME, }; /// Config for the [`Behaviour`]. @@ -150,17 +151,18 @@ pub enum Event { /// [`NetworkBehaviour`] for AutoNAT. /// -/// The behaviour frequently runs probes to determine whether the local peer is behind NAT and/ or a firewall, or -/// publicly reachable. -/// In a probe, a dial-back request is sent to a peer that is randomly selected from the list of fixed servers and -/// connected peers. Upon receiving a dial-back request, the remote tries to dial the included addresses. When a -/// first address was successfully dialed, a status Ok will be send back together with the dialed address. If no address -/// can be reached a dial-error is send back. +/// The behaviour frequently runs probes to determine whether the local peer is behind NAT and/ or a +/// firewall, or publicly reachable. +/// In a probe, a dial-back request is sent to a peer that is randomly selected from the list of +/// fixed servers and connected peers. Upon receiving a dial-back request, the remote tries to dial +/// the included addresses. When a first address was successfully dialed, a status Ok will be send +/// back together with the dialed address. If no address can be reached a dial-error is send back. /// Based on the received response, the sender assumes themselves to be public or private. -/// The status is retried in a frequency of [`Config::retry_interval`] or [`Config::retry_interval`], depending on whether -/// enough confidence in the assumed NAT status was reached or not. -/// The confidence increases each time a probe confirms the assumed status, and decreases if a different status is reported. -/// If the confidence is 0, the status is flipped and the Behaviour will report the new status in an `OutEvent`. +/// The status is retried in a frequency of [`Config::retry_interval`] or +/// [`Config::retry_interval`], depending on whether enough confidence in the assumed NAT status was +/// reached or not. The confidence increases each time a probe confirms the assumed status, and +/// decreases if a different status is reported. If the confidence is 0, the status is flipped and +/// the Behaviour will report the new status in an `OutEvent`. pub struct Behaviour { // Local peer id local_peer_id: PeerId, @@ -197,11 +199,12 @@ pub struct Behaviour { ongoing_outbound: HashMap, // Connected peers with the observed address of each connection. - // If the endpoint of a connection is relayed or not global (in case of Config::only_global_ips), - // the observed address is `None`. + // If the endpoint of a connection is relayed or not global (in case of + // Config::only_global_ips), the observed address is `None`. connected: HashMap>>, - // Used servers in recent outbound probes that are throttled through Config::throttle_server_period. + // Used servers in recent outbound probes that are throttled through + // Config::throttle_server_period. throttled_servers: Vec<(PeerId, Instant)>, // Recent probes done for clients @@ -266,11 +269,12 @@ impl Behaviour { } /// Add a peer to the list over servers that may be used for probes. - /// These peers are used for dial-request even if they are currently not connection, in which case a connection will be - /// establish before sending the dial-request. + /// These peers are used for dial-request even if they are currently not connection, in which + /// case a connection will be establish before sending the dial-request. pub fn add_server(&mut self, peer: PeerId, address: Option) { self.servers.insert(peer); if let Some(addr) = address { + #[allow(deprecated)] self.inner.add_address(&peer, addr); } } @@ -340,6 +344,7 @@ impl Behaviour { ConnectedPoint::Dialer { address, role_override: Endpoint::Dialer, + port_use: _, } => { if let Some(event) = self.as_server().on_outbound_connection(&peer, address) { self.pending_actions @@ -349,6 +354,7 @@ impl Behaviour { ConnectedPoint::Dialer { address: _, role_override: Endpoint::Listener, + port_use: _, } => { // Outgoing connection was dialed as a listener. In other words outgoing connection // was dialed as part of a hole punch. `libp2p-autonat` never attempts to hole @@ -363,18 +369,10 @@ impl Behaviour { ConnectionClosed { peer_id, connection_id, - endpoint, remaining_established, + .. }: ConnectionClosed, ) { - self.inner - .on_swarm_event(FromSwarm::ConnectionClosed(ConnectionClosed { - peer_id, - connection_id, - endpoint, - remaining_established, - })); - if remaining_established == 0 { self.connected.remove(&peer_id); } else { @@ -386,20 +384,7 @@ impl Behaviour { } } - fn on_dial_failure( - &mut self, - DialFailure { - peer_id, - connection_id, - error, - }: DialFailure, - ) { - self.inner - .on_swarm_event(FromSwarm::DialFailure(DialFailure { - peer_id, - connection_id, - error, - })); + fn on_dial_failure(&mut self, DialFailure { peer_id, error, .. }: DialFailure) { if let Some(event) = self.as_server().on_outbound_dial_error(peer_id, error) { self.pending_actions .push_back(ToSwarm::GenerateEvent(Event::InboundProbe(event))); @@ -535,64 +520,38 @@ impl NetworkBehaviour for Behaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { - self.inner - .handle_established_outbound_connection(connection_id, peer, addr, role_override) + self.inner.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) } fn on_swarm_event(&mut self, event: FromSwarm) { self.listen_addresses.on_swarm_event(&event); + self.inner.on_swarm_event(event); match event { - FromSwarm::ConnectionEstablished(connection_established) => { - self.inner - .on_swarm_event(FromSwarm::ConnectionEstablished(connection_established)); - self.on_connection_established(connection_established) - } - FromSwarm::ConnectionClosed(connection_closed) => { - self.on_connection_closed(connection_closed) - } - FromSwarm::DialFailure(dial_failure) => self.on_dial_failure(dial_failure), - FromSwarm::AddressChange(address_change) => { - self.inner - .on_swarm_event(FromSwarm::AddressChange(address_change)); - self.on_address_change(address_change) - } - listen_addr @ FromSwarm::NewListenAddr(_) => { - self.inner.on_swarm_event(listen_addr); + FromSwarm::ConnectionEstablished(e) => self.on_connection_established(e), + FromSwarm::ConnectionClosed(e) => self.on_connection_closed(e), + FromSwarm::DialFailure(e) => self.on_dial_failure(e), + FromSwarm::AddressChange(e) => self.on_address_change(e), + FromSwarm::NewListenAddr(_) => { self.as_client().on_new_address(); } - FromSwarm::ExpiredListenAddr(ExpiredListenAddr { listener_id, addr }) => { - self.inner - .on_swarm_event(FromSwarm::ExpiredListenAddr(ExpiredListenAddr { - listener_id, - addr, - })); - self.as_client().on_expired_address(addr); - } - FromSwarm::ExternalAddrExpired(ExternalAddrExpired { addr }) => { - self.inner - .on_swarm_event(FromSwarm::ExternalAddrExpired(ExternalAddrExpired { addr })); - self.as_client().on_expired_address(addr); - } - FromSwarm::NewExternalAddrCandidate(NewExternalAddrCandidate { addr }) => { - self.inner - .on_swarm_event(FromSwarm::NewExternalAddrCandidate( - NewExternalAddrCandidate { addr }, - )); - self.probe_address(addr.to_owned()); - } - listen_failure @ FromSwarm::ListenFailure(_) => { - self.inner.on_swarm_event(listen_failure) + FromSwarm::ExpiredListenAddr(e) => { + self.as_client().on_expired_address(e.addr); } - new_listener @ FromSwarm::NewListener(_) => self.inner.on_swarm_event(new_listener), - listener_error @ FromSwarm::ListenerError(_) => { - self.inner.on_swarm_event(listener_error) + FromSwarm::ExternalAddrExpired(e) => { + self.as_client().on_expired_address(e.addr); } - listener_closed @ FromSwarm::ListenerClosed(_) => { - self.inner.on_swarm_event(listener_closed) + FromSwarm::NewExternalAddrCandidate(e) => { + self.probe_address(e.addr.to_owned()); } - confirmed @ FromSwarm::ExternalAddrConfirmed(_) => self.inner.on_swarm_event(confirmed), _ => {} } } @@ -610,7 +569,8 @@ impl NetworkBehaviour for Behaviour { type Action = ToSwarm<::ToSwarm, THandlerInEvent>; -// Trait implemented for `AsClient` and `AsServer` to handle events from the inner [`request_response::Behaviour`] Protocol. +// Trait implemented for `AsClient` and `AsServer` to handle events from the inner +// [`request_response::Behaviour`] Protocol. trait HandleInnerEvent { fn handle_event( &mut self, @@ -717,7 +677,8 @@ impl GlobalIp for std::net::Ipv6Addr { // Variation of unstable method [`std::net::Ipv6Addr::multicast_scope`] that instead of the // `Ipv6MulticastScope` just returns if the scope is global or not. - // Equivalent to `Ipv6Addr::multicast_scope(..).map(|scope| matches!(scope, Ipv6MulticastScope::Global))`. + // Equivalent to `Ipv6Addr::multicast_scope(..).map(|scope| matches!(scope, + // Ipv6MulticastScope::Global))`. fn is_multicast_scope_global(addr: &std::net::Ipv6Addr) -> Option { match addr.segments()[0] & 0x000f { 14 => Some(true), // Global multicast scope. diff --git a/protocols/autonat/src/behaviour/as_client.rs b/protocols/autonat/src/v1/behaviour/as_client.rs similarity index 99% rename from protocols/autonat/src/behaviour/as_client.rs rename to protocols/autonat/src/v1/behaviour/as_client.rs index 668f3b93719..ca8daf6e1ac 100644 --- a/protocols/autonat/src/behaviour/as_client.rs +++ b/protocols/autonat/src/v1/behaviour/as_client.rs @@ -18,25 +18,26 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::ResponseError; - -use super::{ - Action, AutoNatCodec, Config, DialRequest, DialResponse, Event, HandleInnerEvent, NatStatus, - ProbeId, +use std::{ + collections::{HashMap, HashSet, VecDeque}, + task::{Context, Poll}, + time::Duration, }; + use futures::FutureExt; use futures_timer::Delay; -use instant::Instant; use libp2p_core::Multiaddr; use libp2p_identity::PeerId; use libp2p_request_response::{self as request_response, OutboundFailure, OutboundRequestId}; use libp2p_swarm::{ConnectionId, ListenAddresses, ToSwarm}; use rand::{seq::SliceRandom, thread_rng}; -use std::{ - collections::{HashMap, HashSet, VecDeque}, - task::{Context, Poll}, - time::Duration, +use web_time::Instant; + +use super::{ + Action, AutoNatCodec, Config, DialRequest, DialResponse, Event, HandleInnerEvent, NatStatus, + ProbeId, }; +use crate::ResponseError; /// Outbound probe failed or was aborted. #[derive(Debug)] @@ -98,7 +99,7 @@ pub(crate) struct AsClient<'a> { pub(crate) other_candidates: &'a HashSet, } -impl<'a> HandleInnerEvent for AsClient<'a> { +impl HandleInnerEvent for AsClient<'_> { fn handle_event( &mut self, event: request_response::Event, @@ -111,6 +112,7 @@ impl<'a> HandleInnerEvent for AsClient<'a> { request_id, response, }, + .. } => { tracing::debug!(?response, "Outbound dial-back request returned response"); @@ -153,6 +155,7 @@ impl<'a> HandleInnerEvent for AsClient<'a> { peer, error, request_id, + .. } => { tracing::debug!( %peer, @@ -179,7 +182,7 @@ impl<'a> HandleInnerEvent for AsClient<'a> { } } -impl<'a> AsClient<'a> { +impl AsClient<'_> { pub(crate) fn poll_auto_probe(&mut self, cx: &mut Context<'_>) -> Poll { match self.schedule_probe.poll_unpin(cx) { Poll::Ready(()) => { diff --git a/protocols/autonat/src/behaviour/as_server.rs b/protocols/autonat/src/v1/behaviour/as_server.rs similarity index 94% rename from protocols/autonat/src/behaviour/as_server.rs rename to protocols/autonat/src/v1/behaviour/as_server.rs index 878fd713dda..32b4120c552 100644 --- a/protocols/autonat/src/behaviour/as_server.rs +++ b/protocols/autonat/src/v1/behaviour/as_server.rs @@ -17,12 +17,11 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. - -use super::{ - Action, AutoNatCodec, Config, DialRequest, DialResponse, Event, HandleInnerEvent, ProbeId, - ResponseError, +use std::{ + collections::{HashMap, HashSet, VecDeque}, + num::NonZeroU8, }; -use instant::Instant; + use libp2p_core::{multiaddr::Protocol, Multiaddr}; use libp2p_identity::PeerId; use libp2p_request_response::{ @@ -32,9 +31,11 @@ use libp2p_swarm::{ dial_opts::{DialOpts, PeerCondition}, ConnectionId, DialError, ToSwarm, }; -use std::{ - collections::{HashMap, HashSet, VecDeque}, - num::NonZeroU8, +use web_time::Instant; + +use super::{ + Action, AutoNatCodec, Config, DialRequest, DialResponse, Event, HandleInnerEvent, ProbeId, + ResponseError, }; /// Inbound probe failed. @@ -92,7 +93,7 @@ pub(crate) struct AsServer<'a> { >, } -impl<'a> HandleInnerEvent for AsServer<'a> { +impl HandleInnerEvent for AsServer<'_> { fn handle_event( &mut self, event: request_response::Event, @@ -106,8 +107,24 @@ impl<'a> HandleInnerEvent for AsServer<'a> { request, channel, }, + .. } => { let probe_id = self.probe_id.next(); + if !self.connected.contains_key(&peer) { + tracing::debug!( + %peer, + "Reject inbound dial request from peer since it is not connected" + ); + + return VecDeque::from([ToSwarm::GenerateEvent(Event::InboundProbe( + InboundProbeEvent::Error { + probe_id, + peer, + error: InboundProbeError::Response(ResponseError::DialRefused), + }, + ))]); + } + match self.resolve_inbound_request(peer, request) { Ok(addrs) => { tracing::debug!( @@ -135,6 +152,7 @@ impl<'a> HandleInnerEvent for AsServer<'a> { NonZeroU8::new(1).expect("1 > 0"), ) .addresses(addrs) + .allocate_new_port() .build(), }, ]) @@ -166,6 +184,7 @@ impl<'a> HandleInnerEvent for AsServer<'a> { peer, error, request_id, + .. } => { tracing::debug!( %peer, @@ -193,7 +212,7 @@ impl<'a> HandleInnerEvent for AsServer<'a> { } } -impl<'a> AsServer<'a> { +impl AsServer<'_> { pub(crate) fn on_outbound_connection( &mut self, peer: &PeerId, @@ -364,10 +383,10 @@ impl<'a> AsServer<'a> { #[cfg(test)] mod test { - use super::*; - use std::net::Ipv4Addr; + use super::*; + fn random_ip<'a>() -> Protocol<'a> { Protocol::Ip4(Ipv4Addr::new( rand::random(), diff --git a/protocols/autonat/src/generated/mod.rs b/protocols/autonat/src/v1/generated/mod.rs similarity index 100% rename from protocols/autonat/src/generated/mod.rs rename to protocols/autonat/src/v1/generated/mod.rs diff --git a/protocols/autonat/src/generated/structs.proto b/protocols/autonat/src/v1/generated/structs.proto similarity index 100% rename from protocols/autonat/src/generated/structs.proto rename to protocols/autonat/src/v1/generated/structs.proto diff --git a/protocols/autonat/src/generated/structs.rs b/protocols/autonat/src/v1/generated/structs.rs similarity index 100% rename from protocols/autonat/src/generated/structs.rs rename to protocols/autonat/src/v1/generated/structs.rs diff --git a/protocols/autonat/src/protocol.rs b/protocols/autonat/src/v1/protocol.rs similarity index 97% rename from protocols/autonat/src/protocol.rs rename to protocols/autonat/src/v1/protocol.rs index c1862058400..6aa0c99167b 100644 --- a/protocols/autonat/src/protocol.rs +++ b/protocols/autonat/src/v1/protocol.rs @@ -18,16 +18,20 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::proto; +use std::io; + use async_trait::async_trait; use asynchronous_codec::{FramedRead, FramedWrite}; -use futures::io::{AsyncRead, AsyncWrite}; -use futures::{SinkExt, StreamExt}; +use futures::{ + io::{AsyncRead, AsyncWrite}, + SinkExt, StreamExt, +}; use libp2p_core::Multiaddr; use libp2p_identity::PeerId; use libp2p_request_response::{self as request_response}; use libp2p_swarm::StreamProtocol; -use std::{convert::TryFrom, io}; + +use crate::proto; /// The protocol name used for negotiating with multistream-select. pub const DEFAULT_PROTOCOL_NAME: StreamProtocol = StreamProtocol::new("/libp2p/autonat/1.0.0"); @@ -121,12 +125,8 @@ impl DialRequest { } let peer_id_result = msg.dial.and_then(|dial| { - dial.peer.and_then(|peer_info| { - let Some(peer_id) = peer_info.id else { - return None; - }; - Some((peer_id, peer_info.addrs)) - }) + dial.peer + .and_then(|peer_info| peer_info.id.map(|peer_id| (peer_id, peer_info.addrs))) }); let (peer_id, addrs) = peer_id_result diff --git a/protocols/autonat/src/v2.rs b/protocols/autonat/src/v2.rs new file mode 100644 index 00000000000..94decf50a55 --- /dev/null +++ b/protocols/autonat/src/v2.rs @@ -0,0 +1,37 @@ +//! The second version of the autonat protocol. +//! +//! The implementation follows the [libp2p spec](https://github.com/libp2p/specs/blob/03718ef0f2dea4a756a85ba716ee33f97e4a6d6c/autonat/autonat-v2.md). +//! +//! The new version fixes the issues of the first version: +//! - The server now always dials back over a newly allocated port. This greatly reduces the risk of +//! false positives that often occurred in the first version, when the clinet-server connection +//! occurred over a hole-punched port. +//! - The server protects against DoS attacks by requiring the client to send more data to the +//! server then the dial back puts on the client, thus making the protocol unatractive for an +//! attacker. +//! +//! The protocol is separated into two parts: +//! - The client part, which is implemented in the `client` module. (The client is the party that +//! wants to check if it is reachable from the outside.) +//! - The server part, which is implemented in the `server` module. (The server is the party +//! performing reachability checks on behalf of the client.) +//! +//! The two can be used together. + +use libp2p_swarm::StreamProtocol; + +pub mod client; +pub(crate) mod protocol; +pub mod server; + +pub(crate) mod generated { + #![allow(unreachable_pub)] + include!("v2/generated/mod.rs"); +} + +pub(crate) const DIAL_REQUEST_PROTOCOL: StreamProtocol = + StreamProtocol::new("/libp2p/autonat/2/dial-request"); +pub(crate) const DIAL_BACK_PROTOCOL: StreamProtocol = + StreamProtocol::new("/libp2p/autonat/2/dial-back"); + +type Nonce = u64; diff --git a/protocols/autonat/src/v2/client.rs b/protocols/autonat/src/v2/client.rs new file mode 100644 index 00000000000..11ddb792839 --- /dev/null +++ b/protocols/autonat/src/v2/client.rs @@ -0,0 +1,4 @@ +mod behaviour; +mod handler; + +pub use behaviour::{Behaviour, Config, Event}; diff --git a/protocols/autonat/src/v2/client/behaviour.rs b/protocols/autonat/src/v2/client/behaviour.rs new file mode 100644 index 00000000000..8e238fc9be4 --- /dev/null +++ b/protocols/autonat/src/v2/client/behaviour.rs @@ -0,0 +1,442 @@ +use std::{ + collections::{HashMap, VecDeque}, + fmt::{Debug, Display, Formatter}, + task::{Context, Poll}, + time::Duration, +}; + +use either::Either; +use futures::FutureExt; +use futures_timer::Delay; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; +use libp2p_identity::PeerId; +use libp2p_swarm::{ + behaviour::ConnectionEstablished, ConnectionClosed, ConnectionDenied, ConnectionHandler, + ConnectionId, FromSwarm, NetworkBehaviour, NewExternalAddrCandidate, NotifyHandler, ToSwarm, +}; +use rand::prelude::*; +use rand_core::OsRng; + +use super::handler::{ + dial_back::{self, IncomingNonce}, + dial_request, +}; +use crate::v2::{protocol::DialRequest, Nonce}; + +#[derive(Debug, Clone, Copy)] +pub struct Config { + /// How many candidates we will test at most. + pub(crate) max_candidates: usize, + + /// The interval at which we will attempt to confirm candidates as external addresses. + pub(crate) probe_interval: Duration, +} + +impl Config { + pub fn with_max_candidates(self, max_candidates: usize) -> Self { + Self { + max_candidates, + ..self + } + } + + pub fn with_probe_interval(self, probe_interval: Duration) -> Self { + Self { + probe_interval, + ..self + } + } +} + +impl Default for Config { + fn default() -> Self { + Self { + max_candidates: 10, + probe_interval: Duration::from_secs(5), + } + } +} + +pub struct Behaviour +where + R: RngCore + 'static, +{ + rng: R, + config: Config, + pending_events: VecDeque< + ToSwarm< + ::ToSwarm, + <::ConnectionHandler as ConnectionHandler>::FromBehaviour, + >, + >, + address_candidates: HashMap, + next_tick: Delay, + peer_info: HashMap, +} + +impl NetworkBehaviour for Behaviour +where + R: RngCore + 'static, +{ + type ConnectionHandler = Either; + + type ToSwarm = Event; + + fn handle_established_inbound_connection( + &mut self, + _: ConnectionId, + _: PeerId, + _: &Multiaddr, + _: &Multiaddr, + ) -> Result<::ConnectionHandler, ConnectionDenied> { + Ok(Either::Right(dial_back::Handler::new())) + } + + fn handle_established_outbound_connection( + &mut self, + _: ConnectionId, + _: PeerId, + _: &Multiaddr, + _: Endpoint, + _: PortUse, + ) -> Result<::ConnectionHandler, ConnectionDenied> { + Ok(Either::Left(dial_request::Handler::new())) + } + + fn on_swarm_event(&mut self, event: FromSwarm) { + match event { + FromSwarm::NewExternalAddrCandidate(NewExternalAddrCandidate { addr }) => { + self.address_candidates + .entry(addr.clone()) + .or_default() + .score += 1; + } + FromSwarm::ConnectionEstablished(ConnectionEstablished { + peer_id, + connection_id, + endpoint: _, + .. + }) => { + self.peer_info.insert( + connection_id, + ConnectionInfo { + peer_id, + supports_autonat: false, + }, + ); + } + FromSwarm::ConnectionClosed(ConnectionClosed { + peer_id, + connection_id, + .. + }) => { + let info = self + .peer_info + .remove(&connection_id) + .expect("inconsistent state"); + + if info.supports_autonat { + tracing::debug!(%peer_id, "Disconnected from AutoNAT server"); + } + } + _ => {} + } + } + + fn on_connection_handler_event( + &mut self, + peer_id: PeerId, + connection_id: ConnectionId, + event: ::ToBehaviour, + ) { + let (nonce, outcome) = match event { + Either::Right(IncomingNonce { nonce, sender }) => { + let Some((_, info)) = self + .address_candidates + .iter_mut() + .find(|(_, info)| info.is_pending_with_nonce(nonce)) + else { + let _ = sender.send(Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Received unexpected nonce: {nonce} from {peer_id}"), + ))); + return; + }; + + info.status = TestStatus::Received(nonce); + tracing::debug!(%peer_id, %nonce, "Successful dial-back"); + + let _ = sender.send(Ok(())); + + return; + } + Either::Left(dial_request::ToBehaviour::PeerHasServerSupport) => { + self.peer_info + .get_mut(&connection_id) + .expect("inconsistent state") + .supports_autonat = true; + return; + } + Either::Left(dial_request::ToBehaviour::TestOutcome { nonce, outcome }) => { + (nonce, outcome) + } + }; + + let ((tested_addr, bytes_sent), result) = match outcome { + Ok(address) => { + let received_dial_back = self + .address_candidates + .iter_mut() + .any(|(_, info)| info.is_received_with_nonce(nonce)); + + if !received_dial_back { + tracing::warn!( + %peer_id, + %nonce, + "Server reported reachbility but we never received a dial-back" + ); + return; + } + + self.pending_events + .push_back(ToSwarm::ExternalAddrConfirmed(address.0.clone())); + + (address, Ok(())) + } + Err(dial_request::Error::UnsupportedProtocol) => { + self.peer_info + .get_mut(&connection_id) + .expect("inconsistent state") + .supports_autonat = false; + + self.reset_status_to(nonce, TestStatus::Untested); // Reset so it will be tried again. + + return; + } + Err(dial_request::Error::Io(e)) => { + tracing::debug!( + %peer_id, + %nonce, + "Failed to complete AutoNAT probe: {e}" + ); + + self.reset_status_to(nonce, TestStatus::Untested); // Reset so it will be tried again. + + return; + } + Err(dial_request::Error::AddressNotReachable { + address, + bytes_sent, + error, + }) => { + self.reset_status_to(nonce, TestStatus::Failed); + + ((address, bytes_sent), Err(error)) + } + }; + + self.pending_events.push_back(ToSwarm::GenerateEvent(Event { + tested_addr, + bytes_sent, + server: peer_id, + result: result.map_err(|e| Error { inner: e }), + })); + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll::FromBehaviour>> + { + loop { + if let Some(event) = self.pending_events.pop_front() { + return Poll::Ready(event); + } + + if self.next_tick.poll_unpin(cx).is_ready() { + self.next_tick.reset(self.config.probe_interval); + + self.issue_dial_requests_for_untested_candidates(); + continue; + } + + return Poll::Pending; + } + } +} + +impl Behaviour +where + R: RngCore + 'static, +{ + pub fn new(rng: R, config: Config) -> Self { + Self { + rng, + next_tick: Delay::new(config.probe_interval), + config, + pending_events: VecDeque::new(), + address_candidates: HashMap::new(), + peer_info: HashMap::new(), + } + } + + /// Issues dial requests to random AutoNAT servers for the most frequently reported, untested + /// candidates. + /// + /// In the current implementation, we only send a single address to each AutoNAT server. + /// This spreads our candidates out across all servers we are connected to which should give us + /// pretty fast feedback on all of them. + fn issue_dial_requests_for_untested_candidates(&mut self) { + for addr in self.untested_candidates() { + let Some((conn_id, peer_id)) = self.random_autonat_server() else { + tracing::debug!("Not connected to any AutoNAT servers"); + return; + }; + + let nonce = self.rng.gen(); + self.address_candidates + .get_mut(&addr) + .expect("only emit candidates") + .status = TestStatus::Pending(nonce); + + self.pending_events.push_back(ToSwarm::NotifyHandler { + peer_id, + handler: NotifyHandler::One(conn_id), + event: Either::Left(DialRequest { + nonce, + addrs: vec![addr], + }), + }); + } + } + + /// Returns all untested candidates, sorted by the frequency they were reported at. + /// + /// More frequently reported candidates are considered to more likely be external addresses and + /// thus tested first. + fn untested_candidates(&self) -> impl Iterator { + let mut entries = self + .address_candidates + .iter() + .filter(|(_, info)| info.status == TestStatus::Untested) + .map(|(addr, count)| (addr.clone(), *count)) + .collect::>(); + + entries.sort_unstable_by_key(|(_, info)| info.score); + + if entries.is_empty() { + tracing::debug!("No untested address candidates"); + } + + entries + .into_iter() + .rev() // `sort_unstable` is ascending + .take(self.config.max_candidates) + .map(|(addr, _)| addr) + } + + /// Chooses an active connection to one of our peers that reported support for the + /// [`DIAL_REQUEST_PROTOCOL`](crate::v2::DIAL_REQUEST_PROTOCOL) protocol. + fn random_autonat_server(&mut self) -> Option<(ConnectionId, PeerId)> { + let (conn_id, info) = self + .peer_info + .iter() + .filter(|(_, info)| info.supports_autonat) + .choose(&mut self.rng)?; + + Some((*conn_id, info.peer_id)) + } + + fn reset_status_to(&mut self, nonce: Nonce, new_status: TestStatus) { + let Some((_, info)) = self + .address_candidates + .iter_mut() + .find(|(_, i)| i.is_pending_with_nonce(nonce) || i.is_received_with_nonce(nonce)) + else { + return; + }; + + info.status = new_status; + } + + // FIXME: We don't want test-only APIs in our public API. + #[doc(hidden)] + pub fn validate_addr(&mut self, addr: &Multiaddr) { + if let Some(info) = self.address_candidates.get_mut(addr) { + info.status = TestStatus::Received(self.rng.next_u64()); + } + } +} + +impl Default for Behaviour { + fn default() -> Self { + Self::new(OsRng, Config::default()) + } +} + +pub struct Error { + pub(crate) inner: dial_request::DialBackError, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.inner, f) + } +} + +impl Debug for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.inner, f) + } +} + +#[derive(Debug)] +pub struct Event { + /// The address that was selected for testing. + pub tested_addr: Multiaddr, + /// The amount of data that was sent to the server. + /// Is 0 if it wasn't necessary to send any data. + /// Otherwise it's a number between 30.000 and 100.000. + pub bytes_sent: usize, + /// The peer id of the server that was selected for testing. + pub server: PeerId, + /// The result of the test. If the test was successful, this is `Ok(())`. + /// Otherwise it's an error. + pub result: Result<(), Error>, +} + +struct ConnectionInfo { + peer_id: PeerId, + supports_autonat: bool, +} + +#[derive(Copy, Clone, Default)] +struct AddressInfo { + score: usize, + status: TestStatus, +} + +impl AddressInfo { + fn is_pending_with_nonce(&self, nonce: Nonce) -> bool { + match self.status { + TestStatus::Pending(c) => c == nonce, + _ => false, + } + } + + fn is_received_with_nonce(&self, nonce: Nonce) -> bool { + match self.status { + TestStatus::Received(c) => c == nonce, + _ => false, + } + } +} + +#[derive(Clone, Copy, Default, PartialEq)] +enum TestStatus { + #[default] + Untested, + Pending(Nonce), + Failed, + Received(Nonce), +} diff --git a/protocols/autonat/src/v2/client/handler.rs b/protocols/autonat/src/v2/client/handler.rs new file mode 100644 index 00000000000..e526c2fb44c --- /dev/null +++ b/protocols/autonat/src/v2/client/handler.rs @@ -0,0 +1,2 @@ +pub(crate) mod dial_back; +pub(crate) mod dial_request; diff --git a/protocols/autonat/src/v2/client/handler/dial_back.rs b/protocols/autonat/src/v2/client/handler/dial_back.rs new file mode 100644 index 00000000000..ef544a4c77a --- /dev/null +++ b/protocols/autonat/src/v2/client/handler/dial_back.rs @@ -0,0 +1,143 @@ +use std::{ + convert::Infallible, + io, + task::{Context, Poll}, + time::Duration, +}; + +use futures::channel::oneshot; +use futures_bounded::StreamSet; +use libp2p_core::upgrade::{DeniedUpgrade, ReadyUpgrade}; +use libp2p_swarm::{ + handler::{ConnectionEvent, FullyNegotiatedInbound, ListenUpgradeError}, + ConnectionHandler, ConnectionHandlerEvent, StreamProtocol, SubstreamProtocol, +}; + +use crate::v2::{protocol, Nonce, DIAL_BACK_PROTOCOL}; + +pub struct Handler { + inbound: StreamSet>, +} + +impl Handler { + pub(crate) fn new() -> Self { + Self { + inbound: StreamSet::new(Duration::from_secs(5), 2), + } + } +} + +impl ConnectionHandler for Handler { + type FromBehaviour = Infallible; + type ToBehaviour = IncomingNonce; + type InboundProtocol = ReadyUpgrade; + type OutboundProtocol = DeniedUpgrade; + type InboundOpenInfo = (); + type OutboundOpenInfo = (); + + fn listen_protocol(&self) -> SubstreamProtocol { + SubstreamProtocol::new(ReadyUpgrade::new(DIAL_BACK_PROTOCOL), ()) + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll< + ConnectionHandlerEvent, + > { + loop { + match self.inbound.poll_next_unpin(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(None) => continue, + Poll::Ready(Some(Err(err))) => { + tracing::debug!("Stream timed out: {err}"); + continue; + } + Poll::Ready(Some(Ok(Err(err)))) => { + tracing::debug!("Dial back handler failed with: {err:?}"); + continue; + } + Poll::Ready(Some(Ok(Ok(incoming_nonce)))) => { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(incoming_nonce)); + } + } + } + } + + fn on_behaviour_event(&mut self, _event: Self::FromBehaviour) {} + + fn on_connection_event( + &mut self, + event: ConnectionEvent< + Self::InboundProtocol, + Self::OutboundProtocol, + Self::InboundOpenInfo, + Self::OutboundOpenInfo, + >, + ) { + match event { + ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { + protocol, .. + }) => { + if self.inbound.try_push(perform_dial_back(protocol)).is_err() { + tracing::warn!("Dial back request dropped, too many requests in flight"); + } + } + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + ConnectionEvent::ListenUpgradeError(ListenUpgradeError { error, .. }) => { + libp2p_core::util::unreachable(error); + } + _ => {} + } + } +} + +struct State { + stream: libp2p_swarm::Stream, + oneshot: Option>>, +} + +#[derive(Debug)] +pub struct IncomingNonce { + pub nonce: Nonce, + pub sender: oneshot::Sender>, +} + +fn perform_dial_back( + stream: libp2p_swarm::Stream, +) -> impl futures::Stream> { + let state = State { + stream, + oneshot: None, + }; + futures::stream::unfold(state, |mut state| async move { + if let Some(ref mut receiver) = state.oneshot { + match receiver.await { + Ok(Ok(())) => {} + Ok(Err(e)) => return Some((Err(e), state)), + Err(_) => { + return Some(( + Err(io::Error::new(io::ErrorKind::Other, "Sender got cancelled")), + state, + )); + } + } + if let Err(e) = protocol::dial_back_response(&mut state.stream).await { + return Some((Err(e), state)); + } + return None; + } + + let nonce = match protocol::recv_dial_back(&mut state.stream).await { + Ok(nonce) => nonce, + Err(err) => { + return Some((Err(err), state)); + } + }; + + let (sender, receiver) = oneshot::channel(); + state.oneshot = Some(receiver); + Some((Ok(IncomingNonce { nonce, sender }), state)) + }) +} diff --git a/protocols/autonat/src/v2/client/handler/dial_request.rs b/protocols/autonat/src/v2/client/handler/dial_request.rs new file mode 100644 index 00000000000..fff83ad9453 --- /dev/null +++ b/protocols/autonat/src/v2/client/handler/dial_request.rs @@ -0,0 +1,348 @@ +use std::{ + collections::VecDeque, + convert::Infallible, + io, + iter::{once, repeat}, + task::{Context, Poll}, + time::Duration, +}; + +use futures::{channel::oneshot, AsyncWrite}; +use futures_bounded::FuturesMap; +use libp2p_core::{ + upgrade::{DeniedUpgrade, ReadyUpgrade}, + Multiaddr, +}; +use libp2p_swarm::{ + handler::{ + ConnectionEvent, DialUpgradeError, FullyNegotiatedOutbound, OutboundUpgradeSend, + ProtocolsChange, + }, + ConnectionHandler, ConnectionHandlerEvent, Stream, StreamProtocol, StreamUpgradeError, + SubstreamProtocol, +}; + +use crate::v2::{ + generated::structs::{mod_DialResponse::ResponseStatus, DialStatus}, + protocol::{ + Coder, DialDataRequest, DialDataResponse, DialRequest, Response, + DATA_FIELD_LEN_UPPER_BOUND, DATA_LEN_LOWER_BOUND, DATA_LEN_UPPER_BOUND, + }, + Nonce, DIAL_REQUEST_PROTOCOL, +}; + +#[derive(Debug)] +pub enum ToBehaviour { + TestOutcome { + nonce: Nonce, + outcome: Result<(Multiaddr, usize), Error>, + }, + PeerHasServerSupport, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Address is not reachable: {error}")] + AddressNotReachable { + address: Multiaddr, + bytes_sent: usize, + error: DialBackError, + }, + #[error("Peer does not support AutoNAT dial-request protocol")] + UnsupportedProtocol, + #[error("IO error: {0}")] + Io(io::Error), +} + +impl From for Error { + fn from(value: io::Error) -> Self { + Self::Io(value) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum DialBackError { + #[error("server failed to establish a connection")] + NoConnection, + #[error("dial back stream failed")] + StreamFailed, +} + +pub struct Handler { + queued_events: VecDeque< + ConnectionHandlerEvent< + ::OutboundProtocol, + ::OutboundOpenInfo, + ::ToBehaviour, + >, + >, + outbound: FuturesMap>, + queued_streams: VecDeque< + oneshot::Sender< + Result< + Stream, + StreamUpgradeError< as OutboundUpgradeSend>::Error>, + >, + >, + >, +} + +impl Handler { + pub(crate) fn new() -> Self { + Self { + queued_events: VecDeque::new(), + outbound: FuturesMap::new(Duration::from_secs(10), 10), + queued_streams: VecDeque::default(), + } + } + + fn perform_request(&mut self, req: DialRequest) { + let (tx, rx) = oneshot::channel(); + self.queued_streams.push_back(tx); + self.queued_events + .push_back(ConnectionHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new(ReadyUpgrade::new(DIAL_REQUEST_PROTOCOL), ()), + }); + if self + .outbound + .try_push(req.nonce, start_stream_handle(req, rx)) + .is_err() + { + tracing::debug!("Dial request dropped, too many requests in flight"); + } + } +} + +impl ConnectionHandler for Handler { + type FromBehaviour = DialRequest; + type ToBehaviour = ToBehaviour; + type InboundProtocol = DeniedUpgrade; + type OutboundProtocol = ReadyUpgrade; + type InboundOpenInfo = (); + type OutboundOpenInfo = (); + + fn listen_protocol(&self) -> SubstreamProtocol { + SubstreamProtocol::new(DeniedUpgrade, ()) + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll< + ConnectionHandlerEvent, + > { + if let Some(event) = self.queued_events.pop_front() { + return Poll::Ready(event); + } + + match self.outbound.poll_unpin(cx) { + Poll::Ready((nonce, Ok(outcome))) => { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + ToBehaviour::TestOutcome { nonce, outcome }, + )) + } + Poll::Ready((nonce, Err(_))) => { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + ToBehaviour::TestOutcome { + nonce, + outcome: Err(Error::Io(io::ErrorKind::TimedOut.into())), + }, + )); + } + Poll::Pending => {} + } + + Poll::Pending + } + + fn on_behaviour_event(&mut self, event: Self::FromBehaviour) { + self.perform_request(event); + } + + fn on_connection_event( + &mut self, + event: ConnectionEvent< + Self::InboundProtocol, + Self::OutboundProtocol, + Self::InboundOpenInfo, + Self::OutboundOpenInfo, + >, + ) { + match event { + ConnectionEvent::DialUpgradeError(DialUpgradeError { error, .. }) => { + tracing::debug!("Dial request failed: {}", error); + match self.queued_streams.pop_front() { + Some(stream_tx) => { + let _ = stream_tx.send(Err(error)); + } + None => { + tracing::warn!( + "Opened unexpected substream without a pending dial request" + ); + } + } + } + ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound { + protocol, .. + }) => match self.queued_streams.pop_front() { + Some(stream_tx) => { + if stream_tx.send(Ok(protocol)).is_err() { + tracing::debug!("Failed to send stream to dead handler"); + } + } + None => { + tracing::warn!("Opened unexpected substream without a pending dial request"); + } + }, + ConnectionEvent::RemoteProtocolsChange(ProtocolsChange::Added(mut added)) => { + if added.any(|p| p.as_ref() == DIAL_REQUEST_PROTOCOL) { + self.queued_events + .push_back(ConnectionHandlerEvent::NotifyBehaviour( + ToBehaviour::PeerHasServerSupport, + )); + } + } + _ => {} + } + } +} + +async fn start_stream_handle( + req: DialRequest, + stream_recv: oneshot::Receiver>>, +) -> Result<(Multiaddr, usize), Error> { + let stream = stream_recv + .await + .map_err(|_| io::Error::from(io::ErrorKind::BrokenPipe))? + .map_err(|e| match e { + StreamUpgradeError::NegotiationFailed => Error::UnsupportedProtocol, + StreamUpgradeError::Timeout => Error::Io(io::ErrorKind::TimedOut.into()), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + StreamUpgradeError::Apply(v) => libp2p_core::util::unreachable(v), + StreamUpgradeError::Io(e) => Error::Io(e), + })?; + + let mut coder = Coder::new(stream); + coder.send(req.clone()).await?; + + let (res, bytes_sent) = match coder.next().await? { + Response::Data(DialDataRequest { + addr_idx, + num_bytes, + }) => { + if addr_idx >= req.addrs.len() { + return Err(Error::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "address index out of bounds", + ))); + } + if !(DATA_LEN_LOWER_BOUND..=DATA_LEN_UPPER_BOUND).contains(&num_bytes) { + return Err(Error::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "requested bytes out of bounds", + ))); + } + + send_aap_data(&mut coder, num_bytes).await?; + + let Response::Dial(dial_response) = coder.next().await? else { + return Err(Error::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "expected message", + ))); + }; + + (dial_response, num_bytes) + } + Response::Dial(dial_response) => (dial_response, 0), + }; + match coder.close().await { + Ok(_) => {} + Err(err) => { + if err.kind() == io::ErrorKind::ConnectionReset { + // The AutoNAT server may have already closed the stream + // (this is normal because the probe is finished), + // in this case we have this error: + // Err(Custom { kind: ConnectionReset, error: Stopped(0) }) + // so we silently ignore this error + } else { + return Err(err.into()); + } + } + } + + match res.status { + ResponseStatus::E_REQUEST_REJECTED => { + return Err(Error::Io(io::Error::new( + io::ErrorKind::Other, + "server rejected request", + ))) + } + ResponseStatus::E_DIAL_REFUSED => { + return Err(Error::Io(io::Error::new( + io::ErrorKind::Other, + "server refused dial", + ))) + } + ResponseStatus::E_INTERNAL_ERROR => { + return Err(Error::Io(io::Error::new( + io::ErrorKind::Other, + "server encountered internal error", + ))) + } + ResponseStatus::OK => {} + } + + let tested_address = req + .addrs + .get(res.addr_idx) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "address index out of bounds"))? + .clone(); + + match res.dial_status { + DialStatus::UNUSED => { + return Err(Error::Io(io::Error::new( + io::ErrorKind::InvalidInput, + "unexpected message", + ))) + } + DialStatus::E_DIAL_ERROR => { + return Err(Error::AddressNotReachable { + address: tested_address, + bytes_sent, + error: DialBackError::NoConnection, + }) + } + DialStatus::E_DIAL_BACK_ERROR => { + return Err(Error::AddressNotReachable { + address: tested_address, + bytes_sent, + error: DialBackError::StreamFailed, + }) + } + DialStatus::OK => {} + } + + Ok((tested_address, bytes_sent)) +} + +async fn send_aap_data(stream: &mut Coder, num_bytes: usize) -> io::Result<()> +where + I: AsyncWrite + Unpin, +{ + let count_full = num_bytes / DATA_FIELD_LEN_UPPER_BOUND; + let partial_len = num_bytes % DATA_FIELD_LEN_UPPER_BOUND; + for req in repeat(DATA_FIELD_LEN_UPPER_BOUND) + .take(count_full) + .chain(once(partial_len)) + .filter(|e| *e > 0) + .map(|data_count| { + DialDataResponse::new(data_count).expect("data count is unexpectedly too big") + }) + { + stream.send(req).await?; + } + + Ok(()) +} diff --git a/protocols/autonat/src/v2/generated/mod.rs b/protocols/autonat/src/v2/generated/mod.rs new file mode 100644 index 00000000000..e52c5a80bc0 --- /dev/null +++ b/protocols/autonat/src/v2/generated/mod.rs @@ -0,0 +1,2 @@ +// Automatically generated mod.rs +pub mod structs; diff --git a/protocols/autonat/src/v2/generated/structs.proto b/protocols/autonat/src/v2/generated/structs.proto new file mode 100644 index 00000000000..d298f43d047 --- /dev/null +++ b/protocols/autonat/src/v2/generated/structs.proto @@ -0,0 +1,54 @@ +syntax = "proto3"; + +package structs; + +message Message { + oneof msg { + DialRequest dialRequest = 1; + DialResponse dialResponse = 2; + DialDataRequest dialDataRequest = 3; + DialDataResponse dialDataResponse = 4; + } +} + +message DialRequest { + repeated bytes addrs = 1; + fixed64 nonce = 2; +} + +message DialDataRequest { + uint32 addrIdx = 1; + uint64 numBytes = 2; +} + +enum DialStatus { + UNUSED = 0; + E_DIAL_ERROR = 100; + E_DIAL_BACK_ERROR = 101; + OK = 200; +} + +message DialResponse { + enum ResponseStatus { + E_INTERNAL_ERROR = 0; + E_REQUEST_REJECTED = 100; + E_DIAL_REFUSED = 101; + OK = 200; + } + + ResponseStatus status = 1; + uint32 addrIdx = 2; + DialStatus dialStatus = 3; +} + +message DialDataResponse { bytes data = 1; } + +message DialBack { fixed64 nonce = 1; } + +message DialBackResponse { + enum DialBackStatus { + OK = 0; + } + + DialBackStatus status = 1; +} diff --git a/protocols/autonat/src/v2/generated/structs.rs b/protocols/autonat/src/v2/generated/structs.rs new file mode 100644 index 00000000000..e188adb8a42 --- /dev/null +++ b/protocols/autonat/src/v2/generated/structs.rs @@ -0,0 +1,403 @@ +// Automatically generated rust module for 'structs.proto' file + +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(unused_imports)] +#![allow(unknown_lints)] +#![allow(clippy::all)] +#![cfg_attr(rustfmt, rustfmt_skip)] + + +use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; +use quick_protobuf::sizeofs::*; +use super::*; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum DialStatus { + UNUSED = 0, + E_DIAL_ERROR = 100, + E_DIAL_BACK_ERROR = 101, + OK = 200, +} + +impl Default for DialStatus { + fn default() -> Self { + DialStatus::UNUSED + } +} + +impl From for DialStatus { + fn from(i: i32) -> Self { + match i { + 0 => DialStatus::UNUSED, + 100 => DialStatus::E_DIAL_ERROR, + 101 => DialStatus::E_DIAL_BACK_ERROR, + 200 => DialStatus::OK, + _ => Self::default(), + } + } +} + +impl<'a> From<&'a str> for DialStatus { + fn from(s: &'a str) -> Self { + match s { + "UNUSED" => DialStatus::UNUSED, + "E_DIAL_ERROR" => DialStatus::E_DIAL_ERROR, + "E_DIAL_BACK_ERROR" => DialStatus::E_DIAL_BACK_ERROR, + "OK" => DialStatus::OK, + _ => Self::default(), + } + } +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct Message { + pub msg: structs::mod_Message::OneOfmsg, +} + +impl<'a> MessageRead<'a> for Message { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(10) => msg.msg = structs::mod_Message::OneOfmsg::dialRequest(r.read_message::(bytes)?), + Ok(18) => msg.msg = structs::mod_Message::OneOfmsg::dialResponse(r.read_message::(bytes)?), + Ok(26) => msg.msg = structs::mod_Message::OneOfmsg::dialDataRequest(r.read_message::(bytes)?), + Ok(34) => msg.msg = structs::mod_Message::OneOfmsg::dialDataResponse(r.read_message::(bytes)?), + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for Message { + fn get_size(&self) -> usize { + 0 + + match self.msg { + structs::mod_Message::OneOfmsg::dialRequest(ref m) => 1 + sizeof_len((m).get_size()), + structs::mod_Message::OneOfmsg::dialResponse(ref m) => 1 + sizeof_len((m).get_size()), + structs::mod_Message::OneOfmsg::dialDataRequest(ref m) => 1 + sizeof_len((m).get_size()), + structs::mod_Message::OneOfmsg::dialDataResponse(ref m) => 1 + sizeof_len((m).get_size()), + structs::mod_Message::OneOfmsg::None => 0, + } } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + match self.msg { structs::mod_Message::OneOfmsg::dialRequest(ref m) => { w.write_with_tag(10, |w| w.write_message(m))? }, + structs::mod_Message::OneOfmsg::dialResponse(ref m) => { w.write_with_tag(18, |w| w.write_message(m))? }, + structs::mod_Message::OneOfmsg::dialDataRequest(ref m) => { w.write_with_tag(26, |w| w.write_message(m))? }, + structs::mod_Message::OneOfmsg::dialDataResponse(ref m) => { w.write_with_tag(34, |w| w.write_message(m))? }, + structs::mod_Message::OneOfmsg::None => {}, + } Ok(()) + } +} + +pub mod mod_Message { + +use super::*; + +#[derive(Debug, PartialEq, Clone)] +pub enum OneOfmsg { + dialRequest(structs::DialRequest), + dialResponse(structs::DialResponse), + dialDataRequest(structs::DialDataRequest), + dialDataResponse(structs::DialDataResponse), + None, +} + +impl Default for OneOfmsg { + fn default() -> Self { + OneOfmsg::None + } +} + +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct DialRequest { + pub addrs: Vec>, + pub nonce: u64, +} + +impl<'a> MessageRead<'a> for DialRequest { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(10) => msg.addrs.push(r.read_bytes(bytes)?.to_owned()), + Ok(17) => msg.nonce = r.read_fixed64(bytes)?, + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for DialRequest { + fn get_size(&self) -> usize { + 0 + + self.addrs.iter().map(|s| 1 + sizeof_len((s).len())).sum::() + + if self.nonce == 0u64 { 0 } else { 1 + 8 } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + for s in &self.addrs { w.write_with_tag(10, |w| w.write_bytes(&**s))?; } + if self.nonce != 0u64 { w.write_with_tag(17, |w| w.write_fixed64(*&self.nonce))?; } + Ok(()) + } +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct DialDataRequest { + pub addrIdx: u32, + pub numBytes: u64, +} + +impl<'a> MessageRead<'a> for DialDataRequest { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(8) => msg.addrIdx = r.read_uint32(bytes)?, + Ok(16) => msg.numBytes = r.read_uint64(bytes)?, + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for DialDataRequest { + fn get_size(&self) -> usize { + 0 + + if self.addrIdx == 0u32 { 0 } else { 1 + sizeof_varint(*(&self.addrIdx) as u64) } + + if self.numBytes == 0u64 { 0 } else { 1 + sizeof_varint(*(&self.numBytes) as u64) } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if self.addrIdx != 0u32 { w.write_with_tag(8, |w| w.write_uint32(*&self.addrIdx))?; } + if self.numBytes != 0u64 { w.write_with_tag(16, |w| w.write_uint64(*&self.numBytes))?; } + Ok(()) + } +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct DialResponse { + pub status: structs::mod_DialResponse::ResponseStatus, + pub addrIdx: u32, + pub dialStatus: structs::DialStatus, +} + +impl<'a> MessageRead<'a> for DialResponse { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(8) => msg.status = r.read_enum(bytes)?, + Ok(16) => msg.addrIdx = r.read_uint32(bytes)?, + Ok(24) => msg.dialStatus = r.read_enum(bytes)?, + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for DialResponse { + fn get_size(&self) -> usize { + 0 + + if self.status == structs::mod_DialResponse::ResponseStatus::E_INTERNAL_ERROR { 0 } else { 1 + sizeof_varint(*(&self.status) as u64) } + + if self.addrIdx == 0u32 { 0 } else { 1 + sizeof_varint(*(&self.addrIdx) as u64) } + + if self.dialStatus == structs::DialStatus::UNUSED { 0 } else { 1 + sizeof_varint(*(&self.dialStatus) as u64) } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if self.status != structs::mod_DialResponse::ResponseStatus::E_INTERNAL_ERROR { w.write_with_tag(8, |w| w.write_enum(*&self.status as i32))?; } + if self.addrIdx != 0u32 { w.write_with_tag(16, |w| w.write_uint32(*&self.addrIdx))?; } + if self.dialStatus != structs::DialStatus::UNUSED { w.write_with_tag(24, |w| w.write_enum(*&self.dialStatus as i32))?; } + Ok(()) + } +} + +pub mod mod_DialResponse { + + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ResponseStatus { + E_INTERNAL_ERROR = 0, + E_REQUEST_REJECTED = 100, + E_DIAL_REFUSED = 101, + OK = 200, +} + +impl Default for ResponseStatus { + fn default() -> Self { + ResponseStatus::E_INTERNAL_ERROR + } +} + +impl From for ResponseStatus { + fn from(i: i32) -> Self { + match i { + 0 => ResponseStatus::E_INTERNAL_ERROR, + 100 => ResponseStatus::E_REQUEST_REJECTED, + 101 => ResponseStatus::E_DIAL_REFUSED, + 200 => ResponseStatus::OK, + _ => Self::default(), + } + } +} + +impl<'a> From<&'a str> for ResponseStatus { + fn from(s: &'a str) -> Self { + match s { + "E_INTERNAL_ERROR" => ResponseStatus::E_INTERNAL_ERROR, + "E_REQUEST_REJECTED" => ResponseStatus::E_REQUEST_REJECTED, + "E_DIAL_REFUSED" => ResponseStatus::E_DIAL_REFUSED, + "OK" => ResponseStatus::OK, + _ => Self::default(), + } + } +} + +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct DialDataResponse { + pub data: Vec, +} + +impl<'a> MessageRead<'a> for DialDataResponse { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(10) => msg.data = r.read_bytes(bytes)?.to_owned(), + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for DialDataResponse { + fn get_size(&self) -> usize { + 0 + + if self.data.is_empty() { 0 } else { 1 + sizeof_len((&self.data).len()) } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if !self.data.is_empty() { w.write_with_tag(10, |w| w.write_bytes(&**&self.data))?; } + Ok(()) + } +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct DialBack { + pub nonce: u64, +} + +impl<'a> MessageRead<'a> for DialBack { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(9) => msg.nonce = r.read_fixed64(bytes)?, + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for DialBack { + fn get_size(&self) -> usize { + 0 + + if self.nonce == 0u64 { 0 } else { 1 + 8 } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if self.nonce != 0u64 { w.write_with_tag(9, |w| w.write_fixed64(*&self.nonce))?; } + Ok(()) + } +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct DialBackResponse { + pub status: structs::mod_DialBackResponse::DialBackStatus, +} + +impl<'a> MessageRead<'a> for DialBackResponse { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(8) => msg.status = r.read_enum(bytes)?, + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for DialBackResponse { + fn get_size(&self) -> usize { + 0 + + if self.status == structs::mod_DialBackResponse::DialBackStatus::OK { 0 } else { 1 + sizeof_varint(*(&self.status) as u64) } + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if self.status != structs::mod_DialBackResponse::DialBackStatus::OK { w.write_with_tag(8, |w| w.write_enum(*&self.status as i32))?; } + Ok(()) + } +} + +pub mod mod_DialBackResponse { + + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum DialBackStatus { + OK = 0, +} + +impl Default for DialBackStatus { + fn default() -> Self { + DialBackStatus::OK + } +} + +impl From for DialBackStatus { + fn from(i: i32) -> Self { + match i { + 0 => DialBackStatus::OK, + _ => Self::default(), + } + } +} + +impl<'a> From<&'a str> for DialBackStatus { + fn from(s: &'a str) -> Self { + match s { + "OK" => DialBackStatus::OK, + _ => Self::default(), + } + } +} + +} + diff --git a/protocols/autonat/src/v2/protocol.rs b/protocols/autonat/src/v2/protocol.rs new file mode 100644 index 00000000000..70f9f8c37af --- /dev/null +++ b/protocols/autonat/src/v2/protocol.rs @@ -0,0 +1,337 @@ +// change to quick-protobuf-codec + +use std::{io, io::ErrorKind}; + +use asynchronous_codec::{Framed, FramedRead, FramedWrite}; +use futures::{AsyncRead, AsyncWrite, SinkExt, StreamExt}; +use libp2p_core::Multiaddr; +use quick_protobuf_codec::Codec; +use rand::Rng; + +use crate::v2::{generated::structs as proto, Nonce}; + +const REQUEST_MAX_SIZE: usize = 4104; +pub(super) const DATA_LEN_LOWER_BOUND: usize = 30_000u32 as usize; +pub(super) const DATA_LEN_UPPER_BOUND: usize = 100_000u32 as usize; +pub(super) const DATA_FIELD_LEN_UPPER_BOUND: usize = 4096; + +fn new_io_invalid_data_err(msg: impl Into) -> io::Error { + io::Error::new(io::ErrorKind::InvalidData, msg.into()) +} + +pub(crate) struct Coder { + inner: Framed>, +} + +impl Coder +where + I: AsyncWrite + AsyncRead + Unpin, +{ + pub(crate) fn new(io: I) -> Self { + Self { + inner: Framed::new(io, Codec::new(REQUEST_MAX_SIZE)), + } + } + pub(crate) async fn close(mut self) -> io::Result<()> { + self.inner.close().await?; + Ok(()) + } +} + +impl Coder +where + I: AsyncRead + Unpin, +{ + pub(crate) async fn next(&mut self) -> io::Result + where + proto::Message: TryInto, + io::Error: From, + { + Ok(self.next_msg().await?.try_into()?) + } + + async fn next_msg(&mut self) -> io::Result { + self.inner + .next() + .await + .ok_or(io::Error::new( + ErrorKind::UnexpectedEof, + "no request to read", + ))? + .map_err(|e| io::Error::new(ErrorKind::InvalidData, e)) + } +} + +impl Coder +where + I: AsyncWrite + Unpin, +{ + pub(crate) async fn send(&mut self, msg: M) -> io::Result<()> + where + M: Into, + { + self.inner.send(msg.into()).await?; + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum Request { + Dial(DialRequest), + Data(DialDataResponse), +} + +impl From for proto::Message { + fn from(val: DialRequest) -> Self { + let addrs = val.addrs.iter().map(|e| e.to_vec()).collect(); + let nonce = val.nonce; + + proto::Message { + msg: proto::mod_Message::OneOfmsg::dialRequest(proto::DialRequest { addrs, nonce }), + } + } +} + +impl From for proto::Message { + fn from(val: DialDataResponse) -> Self { + debug_assert!( + val.data_count <= DATA_FIELD_LEN_UPPER_BOUND, + "data_count too large" + ); + proto::Message { + msg: proto::mod_Message::OneOfmsg::dialDataResponse(proto::DialDataResponse { + // One could use Cow::Borrowed here, but it will + // require a modification of the generated code + // and that will fail the CI + data: vec![0; val.data_count], + }), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct DialRequest { + pub(crate) addrs: Vec, + pub(crate) nonce: u64, +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct DialDataResponse { + data_count: usize, +} + +impl DialDataResponse { + pub(crate) fn new(data_count: usize) -> Option { + if data_count <= DATA_FIELD_LEN_UPPER_BOUND { + Some(Self { data_count }) + } else { + None + } + } + + pub(crate) fn get_data_count(&self) -> usize { + self.data_count + } +} + +impl TryFrom for Request { + type Error = io::Error; + + fn try_from(msg: proto::Message) -> Result { + match msg.msg { + proto::mod_Message::OneOfmsg::dialRequest(proto::DialRequest { addrs, nonce }) => { + let addrs = addrs + .into_iter() + .map(|e| e.to_vec()) + .map(|e| { + Multiaddr::try_from(e).map_err(|err| { + new_io_invalid_data_err(format!("invalid multiaddr: {}", err)) + }) + }) + .collect::, io::Error>>()?; + Ok(Self::Dial(DialRequest { addrs, nonce })) + } + proto::mod_Message::OneOfmsg::dialDataResponse(proto::DialDataResponse { data }) => { + let data_count = data.len(); + Ok(Self::Data(DialDataResponse { data_count })) + } + _ => Err(new_io_invalid_data_err( + "expected dialResponse or dialDataRequest", + )), + } + } +} + +#[derive(Debug, Clone)] +pub(crate) enum Response { + Dial(DialResponse), + Data(DialDataRequest), +} + +#[derive(Debug, Clone)] +pub(crate) struct DialDataRequest { + pub(crate) addr_idx: usize, + pub(crate) num_bytes: usize, +} + +#[derive(Debug, Clone)] +pub(crate) struct DialResponse { + pub(crate) status: proto::mod_DialResponse::ResponseStatus, + pub(crate) addr_idx: usize, + pub(crate) dial_status: proto::DialStatus, +} + +impl TryFrom for Response { + type Error = io::Error; + + fn try_from(msg: proto::Message) -> Result { + match msg.msg { + proto::mod_Message::OneOfmsg::dialResponse(proto::DialResponse { + status, + addrIdx, + dialStatus, + }) => Ok(Response::Dial(DialResponse { + status, + addr_idx: addrIdx as usize, + dial_status: dialStatus, + })), + proto::mod_Message::OneOfmsg::dialDataRequest(proto::DialDataRequest { + addrIdx, + numBytes, + }) => Ok(Self::Data(DialDataRequest { + addr_idx: addrIdx as usize, + num_bytes: numBytes as usize, + })), + _ => Err(new_io_invalid_data_err( + "invalid message type, expected dialResponse or dialDataRequest", + )), + } + } +} + +impl From for proto::Message { + fn from(val: Response) -> Self { + match val { + Response::Dial(DialResponse { + status, + addr_idx, + dial_status, + }) => proto::Message { + msg: proto::mod_Message::OneOfmsg::dialResponse(proto::DialResponse { + status, + addrIdx: addr_idx as u32, + dialStatus: dial_status, + }), + }, + Response::Data(DialDataRequest { + addr_idx, + num_bytes, + }) => proto::Message { + msg: proto::mod_Message::OneOfmsg::dialDataRequest(proto::DialDataRequest { + addrIdx: addr_idx as u32, + numBytes: num_bytes as u64, + }), + }, + } + } +} + +impl DialDataRequest { + pub(crate) fn from_rng(addr_idx: usize, mut rng: R) -> Self { + let num_bytes = rng.gen_range(DATA_LEN_LOWER_BOUND..=DATA_LEN_UPPER_BOUND); + Self { + addr_idx, + num_bytes, + } + } +} + +const DIAL_BACK_MAX_SIZE: usize = 10; + +pub(crate) async fn dial_back(stream: impl AsyncWrite + Unpin, nonce: Nonce) -> io::Result<()> { + let msg = proto::DialBack { nonce }; + let mut framed = FramedWrite::new(stream, Codec::::new(DIAL_BACK_MAX_SIZE)); + + framed + .send(msg) + .await + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + + Ok(()) +} + +pub(crate) async fn recv_dial_back(stream: impl AsyncRead + Unpin) -> io::Result { + let framed = &mut FramedRead::new(stream, Codec::::new(DIAL_BACK_MAX_SIZE)); + let proto::DialBack { nonce } = framed + .next() + .await + .ok_or(io::Error::from(io::ErrorKind::UnexpectedEof))??; + Ok(nonce) +} + +pub(crate) async fn dial_back_response(stream: impl AsyncWrite + Unpin) -> io::Result<()> { + let msg = proto::DialBackResponse { + status: proto::mod_DialBackResponse::DialBackStatus::OK, + }; + let mut framed = FramedWrite::new( + stream, + Codec::::new(DIAL_BACK_MAX_SIZE), + ); + framed + .send(msg) + .await + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + + Ok(()) +} + +pub(crate) async fn recv_dial_back_response( + stream: impl AsyncRead + AsyncWrite + Unpin, +) -> io::Result<()> { + let framed = &mut FramedRead::new( + stream, + Codec::::new(DIAL_BACK_MAX_SIZE), + ); + let proto::DialBackResponse { status } = framed + .next() + .await + .ok_or(io::Error::from(io::ErrorKind::UnexpectedEof))??; + + if proto::mod_DialBackResponse::DialBackStatus::OK == status { + Ok(()) + } else { + Err(io::Error::new( + io::ErrorKind::InvalidData, + "invalid dial back response", + )) + } +} + +#[cfg(test)] +mod tests { + use crate::v2::generated::structs::{ + mod_Message::OneOfmsg, DialDataResponse as GenDialDataResponse, Message, + }; + + #[test] + fn message_correct_max_size() { + let message_bytes = quick_protobuf::serialize_into_vec(&Message { + msg: OneOfmsg::dialDataResponse(GenDialDataResponse { + data: vec![0; 4096], + }), + }) + .unwrap(); + assert_eq!(message_bytes.len(), super::REQUEST_MAX_SIZE); + } + + #[test] + fn dial_back_correct_size() { + let dial_back = super::proto::DialBack { nonce: 0 }; + let buf = quick_protobuf::serialize_into_vec(&dial_back).unwrap(); + assert!(buf.len() <= super::DIAL_BACK_MAX_SIZE); + + let dial_back_max_nonce = super::proto::DialBack { nonce: u64::MAX }; + let buf = quick_protobuf::serialize_into_vec(&dial_back_max_nonce).unwrap(); + assert!(buf.len() <= super::DIAL_BACK_MAX_SIZE); + } +} diff --git a/protocols/autonat/src/v2/server.rs b/protocols/autonat/src/v2/server.rs new file mode 100644 index 00000000000..cd9b1e46b18 --- /dev/null +++ b/protocols/autonat/src/v2/server.rs @@ -0,0 +1,4 @@ +mod behaviour; +mod handler; + +pub use behaviour::{Behaviour, Event}; diff --git a/protocols/autonat/src/v2/server/behaviour.rs b/protocols/autonat/src/v2/server/behaviour.rs new file mode 100644 index 00000000000..125955cb53a --- /dev/null +++ b/protocols/autonat/src/v2/server/behaviour.rs @@ -0,0 +1,157 @@ +use std::{ + collections::{HashMap, VecDeque}, + io, + task::{Context, Poll}, +}; + +use either::Either; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; +use libp2p_identity::PeerId; +use libp2p_swarm::{ + dial_opts::{DialOpts, PeerCondition}, + dummy, ConnectionDenied, ConnectionHandler, ConnectionId, DialFailure, FromSwarm, + NetworkBehaviour, ToSwarm, +}; +use rand_core::{OsRng, RngCore}; + +use crate::v2::server::handler::{ + dial_back, + dial_request::{self, DialBackCommand, DialBackStatus}, + Handler, +}; + +pub struct Behaviour +where + R: Clone + Send + RngCore + 'static, +{ + dialing_dial_back: HashMap, + pending_events: VecDeque< + ToSwarm< + ::ToSwarm, + <::ConnectionHandler as ConnectionHandler>::FromBehaviour, + >, + >, + rng: R, +} + +impl Default for Behaviour { + fn default() -> Self { + Self::new(OsRng) + } +} + +impl Behaviour +where + R: RngCore + Send + Clone + 'static, +{ + pub fn new(rng: R) -> Self { + Self { + dialing_dial_back: HashMap::new(), + pending_events: VecDeque::new(), + rng, + } + } +} + +impl NetworkBehaviour for Behaviour +where + R: RngCore + Send + Clone + 'static, +{ + type ConnectionHandler = Handler; + + type ToSwarm = Event; + + fn handle_established_inbound_connection( + &mut self, + _connection_id: ConnectionId, + peer: PeerId, + _local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result<::ConnectionHandler, ConnectionDenied> { + Ok(Either::Right(dial_request::Handler::new( + peer, + remote_addr.clone(), + self.rng.clone(), + ))) + } + + fn handle_established_outbound_connection( + &mut self, + connection_id: ConnectionId, + _peer: PeerId, + _addr: &Multiaddr, + _role_override: Endpoint, + _port_use: PortUse, + ) -> Result<::ConnectionHandler, ConnectionDenied> { + Ok(match self.dialing_dial_back.remove(&connection_id) { + Some(cmd) => Either::Left(Either::Left(dial_back::Handler::new(cmd))), + None => Either::Left(Either::Right(dummy::ConnectionHandler)), + }) + } + + fn on_swarm_event(&mut self, event: FromSwarm) { + if let FromSwarm::DialFailure(DialFailure { connection_id, .. }) = event { + if let Some(DialBackCommand { back_channel, .. }) = + self.dialing_dial_back.remove(&connection_id) + { + let dial_back_status = DialBackStatus::DialErr; + let _ = back_channel.send(Err(dial_back_status)); + } + } + } + + fn on_connection_handler_event( + &mut self, + peer_id: PeerId, + _connection_id: ConnectionId, + event: as ConnectionHandler>::ToBehaviour, + ) { + match event { + Either::Left(Either::Left(Ok(_))) => {} + Either::Left(Either::Left(Err(e))) => { + tracing::debug!("dial back error: {e:?}"); + } + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + Either::Left(Either::Right(v)) => libp2p_core::util::unreachable(v), + Either::Right(Either::Left(cmd)) => { + let addr = cmd.addr.clone(); + let opts = DialOpts::peer_id(peer_id) + .addresses(Vec::from([addr])) + .condition(PeerCondition::Always) + .allocate_new_port() + .build(); + let conn_id = opts.connection_id(); + self.dialing_dial_back.insert(conn_id, cmd); + self.pending_events.push_back(ToSwarm::Dial { opts }); + } + Either::Right(Either::Right(status_update)) => self + .pending_events + .push_back(ToSwarm::GenerateEvent(status_update)), + } + } + + fn poll( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll as ConnectionHandler>::FromBehaviour>> { + if let Some(event) = self.pending_events.pop_front() { + return Poll::Ready(event); + } + Poll::Pending + } +} + +#[derive(Debug)] +pub struct Event { + /// All address that were submitted for testing. + pub all_addrs: Vec, + /// The address that was eventually tested. + pub tested_addr: Multiaddr, + /// The peer id of the client that submitted addresses for testing. + pub client: PeerId, + /// The amount of data that was requested by the server and was transmitted. + pub data_amount: usize, + /// The result of the test. + pub result: Result<(), io::Error>, +} diff --git a/protocols/autonat/src/v2/server/handler.rs b/protocols/autonat/src/v2/server/handler.rs new file mode 100644 index 00000000000..ffdad69c86f --- /dev/null +++ b/protocols/autonat/src/v2/server/handler.rs @@ -0,0 +1,8 @@ +use either::Either; +use libp2p_swarm::dummy; + +pub(crate) mod dial_back; +pub(crate) mod dial_request; + +pub(crate) type Handler = + Either, dial_request::Handler>; diff --git a/protocols/autonat/src/v2/server/handler/dial_back.rs b/protocols/autonat/src/v2/server/handler/dial_back.rs new file mode 100644 index 00000000000..61593da318d --- /dev/null +++ b/protocols/autonat/src/v2/server/handler/dial_back.rs @@ -0,0 +1,139 @@ +use std::{ + convert::identity, + io, + task::{Context, Poll}, + time::Duration, +}; + +use futures::{AsyncRead, AsyncWrite}; +use futures_bounded::FuturesSet; +use libp2p_core::upgrade::{DeniedUpgrade, ReadyUpgrade}; +use libp2p_swarm::{ + handler::{ConnectionEvent, DialUpgradeError, FullyNegotiatedOutbound}, + ConnectionHandler, ConnectionHandlerEvent, StreamProtocol, StreamUpgradeError, + SubstreamProtocol, +}; + +use super::dial_request::{DialBackCommand, DialBackStatus as DialBackRes}; +use crate::v2::{ + protocol::{dial_back, recv_dial_back_response}, + DIAL_BACK_PROTOCOL, +}; + +pub(crate) type ToBehaviour = io::Result<()>; + +pub struct Handler { + pending_nonce: Option, + requested_substream_nonce: Option, + outbound: FuturesSet, +} + +impl Handler { + pub(crate) fn new(cmd: DialBackCommand) -> Self { + Self { + pending_nonce: Some(cmd), + requested_substream_nonce: None, + outbound: FuturesSet::new(Duration::from_secs(10), 5), + } + } +} + +impl ConnectionHandler for Handler { + type FromBehaviour = (); + type ToBehaviour = ToBehaviour; + type InboundProtocol = DeniedUpgrade; + type OutboundProtocol = ReadyUpgrade; + type InboundOpenInfo = (); + type OutboundOpenInfo = (); + + fn listen_protocol(&self) -> SubstreamProtocol { + SubstreamProtocol::new(DeniedUpgrade, ()) + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll< + ConnectionHandlerEvent, + > { + if let Poll::Ready(result) = self.outbound.poll_unpin(cx) { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + result + .map_err(|timeout| io::Error::new(io::ErrorKind::TimedOut, timeout)) + .and_then(identity), + )); + } + if let Some(cmd) = self.pending_nonce.take() { + self.requested_substream_nonce = Some(cmd); + return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new(ReadyUpgrade::new(DIAL_BACK_PROTOCOL), ()), + }); + } + Poll::Pending + } + + fn on_behaviour_event(&mut self, _event: Self::FromBehaviour) {} + + fn on_connection_event( + &mut self, + event: ConnectionEvent< + Self::InboundProtocol, + Self::OutboundProtocol, + Self::InboundOpenInfo, + Self::OutboundOpenInfo, + >, + ) { + match event { + ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound { + protocol, .. + }) => { + if let Some(cmd) = self.requested_substream_nonce.take() { + if self + .outbound + .try_push(perform_dial_back(protocol, cmd)) + .is_err() + { + tracing::warn!("Dial back dropped, too many requests in flight"); + } + } else { + tracing::warn!("received dial back substream without nonce"); + } + } + ConnectionEvent::DialUpgradeError(DialUpgradeError { + error: StreamUpgradeError::NegotiationFailed | StreamUpgradeError::Timeout, + .. + }) => { + if let Some(cmd) = self.requested_substream_nonce.take() { + let _ = cmd.back_channel.send(Err(DialBackRes::DialBackErr)); + } + } + _ => {} + } + } +} + +async fn perform_dial_back( + mut stream: impl AsyncRead + AsyncWrite + Unpin, + DialBackCommand { + nonce, + back_channel, + .. + }: DialBackCommand, +) -> io::Result<()> { + let res = dial_back(&mut stream, nonce) + .await + .map_err(|_| DialBackRes::DialBackErr) + .map(|_| ()); + + let res = match res { + Ok(()) => recv_dial_back_response(stream) + .await + .map_err(|_| DialBackRes::DialBackErr) + .map(|_| ()), + Err(e) => Err(e), + }; + back_channel + .send(res) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "send error"))?; + Ok(()) +} diff --git a/protocols/autonat/src/v2/server/handler/dial_request.rs b/protocols/autonat/src/v2/server/handler/dial_request.rs new file mode 100644 index 00000000000..5058e0f3f42 --- /dev/null +++ b/protocols/autonat/src/v2/server/handler/dial_request.rs @@ -0,0 +1,335 @@ +use std::{ + convert::Infallible, + io, + task::{Context, Poll}, + time::Duration, +}; + +use either::Either; +use futures::{ + channel::{mpsc, oneshot}, + AsyncRead, AsyncWrite, SinkExt, StreamExt, +}; +use futures_bounded::FuturesSet; +use libp2p_core::{ + upgrade::{DeniedUpgrade, ReadyUpgrade}, + Multiaddr, +}; +use libp2p_identity::PeerId; +use libp2p_swarm::{ + handler::{ConnectionEvent, FullyNegotiatedInbound, ListenUpgradeError}, + ConnectionHandler, ConnectionHandlerEvent, StreamProtocol, SubstreamProtocol, +}; +use rand_core::RngCore; + +use crate::v2::{ + generated::structs::{mod_DialResponse::ResponseStatus, DialStatus}, + protocol::{Coder, DialDataRequest, DialRequest, DialResponse, Request, Response}, + server::behaviour::Event, + Nonce, DIAL_REQUEST_PROTOCOL, +}; + +#[derive(Debug, PartialEq)] +pub(crate) enum DialBackStatus { + /// Failure during dial + DialErr, + /// Failure during dial back + DialBackErr, +} + +#[derive(Debug)] +pub struct DialBackCommand { + pub(crate) addr: Multiaddr, + pub(crate) nonce: Nonce, + pub(crate) back_channel: oneshot::Sender>, +} + +pub struct Handler { + client_id: PeerId, + observed_multiaddr: Multiaddr, + dial_back_cmd_sender: mpsc::Sender, + dial_back_cmd_receiver: mpsc::Receiver, + inbound: FuturesSet, + rng: R, +} + +impl Handler +where + R: RngCore, +{ + pub(crate) fn new(client_id: PeerId, observed_multiaddr: Multiaddr, rng: R) -> Self { + let (dial_back_cmd_sender, dial_back_cmd_receiver) = mpsc::channel(10); + Self { + client_id, + observed_multiaddr, + dial_back_cmd_sender, + dial_back_cmd_receiver, + inbound: FuturesSet::new(Duration::from_secs(10), 10), + rng, + } + } +} + +impl ConnectionHandler for Handler +where + R: RngCore + Send + Clone + 'static, +{ + type FromBehaviour = Infallible; + type ToBehaviour = Either; + type InboundProtocol = ReadyUpgrade; + type OutboundProtocol = DeniedUpgrade; + type InboundOpenInfo = (); + type OutboundOpenInfo = (); + + fn listen_protocol(&self) -> SubstreamProtocol { + SubstreamProtocol::new(ReadyUpgrade::new(DIAL_REQUEST_PROTOCOL), ()) + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll< + ConnectionHandlerEvent, + > { + loop { + match self.inbound.poll_unpin(cx) { + Poll::Ready(Ok(event)) => { + if let Err(e) = &event.result { + tracing::warn!("inbound request handle failed: {:?}", e); + } + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(Either::Right( + event, + ))); + } + Poll::Ready(Err(e)) => { + tracing::warn!("inbound request handle timed out {e:?}"); + } + Poll::Pending => break, + } + } + if let Poll::Ready(Some(cmd)) = self.dial_back_cmd_receiver.poll_next_unpin(cx) { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(Either::Left(cmd))); + } + Poll::Pending + } + + fn on_behaviour_event(&mut self, _event: Self::FromBehaviour) {} + + fn on_connection_event( + &mut self, + event: ConnectionEvent< + Self::InboundProtocol, + Self::OutboundProtocol, + Self::InboundOpenInfo, + Self::OutboundOpenInfo, + >, + ) { + match event { + ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { + protocol, .. + }) => { + if self + .inbound + .try_push(handle_request( + protocol, + self.observed_multiaddr.clone(), + self.client_id, + self.dial_back_cmd_sender.clone(), + self.rng.clone(), + )) + .is_err() + { + tracing::warn!( + "failed to push inbound request handler, too many requests in flight" + ); + } + } + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + ConnectionEvent::ListenUpgradeError(ListenUpgradeError { error, .. }) => { + tracing::debug!("inbound request failed: {:?}", error); + } + _ => {} + } + } +} + +enum HandleFail { + InternalError(usize), + RequestRejected, + DialRefused, + DialBack { + idx: usize, + result: Result<(), DialBackStatus>, + }, +} + +impl From for DialResponse { + fn from(value: HandleFail) -> Self { + match value { + HandleFail::InternalError(addr_idx) => Self { + status: ResponseStatus::E_INTERNAL_ERROR, + addr_idx, + dial_status: DialStatus::UNUSED, + }, + HandleFail::RequestRejected => Self { + status: ResponseStatus::E_REQUEST_REJECTED, + addr_idx: 0, + dial_status: DialStatus::UNUSED, + }, + HandleFail::DialRefused => Self { + status: ResponseStatus::E_DIAL_REFUSED, + addr_idx: 0, + dial_status: DialStatus::UNUSED, + }, + HandleFail::DialBack { idx, result } => Self { + status: ResponseStatus::OK, + addr_idx: idx, + dial_status: match result { + Err(DialBackStatus::DialErr) => DialStatus::E_DIAL_ERROR, + Err(DialBackStatus::DialBackErr) => DialStatus::E_DIAL_BACK_ERROR, + Ok(()) => DialStatus::OK, + }, + }, + } + } +} + +async fn handle_request( + stream: impl AsyncRead + AsyncWrite + Unpin, + observed_multiaddr: Multiaddr, + client: PeerId, + dial_back_cmd_sender: mpsc::Sender, + rng: impl RngCore, +) -> Event { + let mut coder = Coder::new(stream); + let mut all_addrs = Vec::new(); + let mut tested_addr_opt = None; + let mut data_amount = 0; + let response = handle_request_internal( + &mut coder, + observed_multiaddr.clone(), + dial_back_cmd_sender, + rng, + &mut all_addrs, + &mut tested_addr_opt, + &mut data_amount, + ) + .await + .unwrap_or_else(|e| e.into()); + let Some(tested_addr) = tested_addr_opt else { + return Event { + all_addrs, + tested_addr: observed_multiaddr, + client, + data_amount, + result: Err(io::Error::new( + io::ErrorKind::Other, + "client is not conformint to protocol. the tested address is not the observed address", + )), + }; + }; + if let Err(e) = coder.send(Response::Dial(response)).await { + return Event { + all_addrs, + tested_addr, + client, + data_amount, + result: Err(e), + }; + } + if let Err(e) = coder.close().await { + return Event { + all_addrs, + tested_addr, + client, + data_amount, + result: Err(e), + }; + } + Event { + all_addrs, + tested_addr, + client, + data_amount, + result: Ok(()), + } +} + +async fn handle_request_internal( + coder: &mut Coder, + observed_multiaddr: Multiaddr, + dial_back_cmd_sender: mpsc::Sender, + mut rng: impl RngCore, + all_addrs: &mut Vec, + tested_addrs: &mut Option, + data_amount: &mut usize, +) -> Result +where + I: AsyncRead + AsyncWrite + Unpin, +{ + let DialRequest { mut addrs, nonce } = match coder + .next() + .await + .map_err(|_| HandleFail::InternalError(0))? + { + Request::Dial(dial_request) => dial_request, + Request::Data(_) => { + return Err(HandleFail::RequestRejected); + } + }; + all_addrs.clone_from(&addrs); + let idx = 0; + let addr = addrs.pop().ok_or(HandleFail::DialRefused)?; + *tested_addrs = Some(addr.clone()); + *data_amount = 0; + if addr != observed_multiaddr { + let dial_data_request = DialDataRequest::from_rng(idx, &mut rng); + let mut rem_data = dial_data_request.num_bytes; + coder + .send(Response::Data(dial_data_request)) + .await + .map_err(|_| HandleFail::InternalError(idx))?; + while rem_data > 0 { + let data_count = match coder + .next() + .await + .map_err(|_e| HandleFail::InternalError(idx))? + { + Request::Dial(_) => { + return Err(HandleFail::RequestRejected); + } + Request::Data(dial_data_response) => dial_data_response.get_data_count(), + }; + rem_data = rem_data.saturating_sub(data_count); + *data_amount += data_count; + } + } + let (back_channel, rx) = oneshot::channel(); + let dial_back_cmd = DialBackCommand { + addr, + nonce, + back_channel, + }; + dial_back_cmd_sender + .clone() + .send(dial_back_cmd) + .await + .map_err(|_| HandleFail::DialBack { + idx, + result: Err(DialBackStatus::DialErr), + })?; + + let dial_back = rx.await.map_err(|_e| HandleFail::InternalError(idx))?; + if let Err(err) = dial_back { + return Err(HandleFail::DialBack { + idx, + result: Err(err), + }); + } + Ok(DialResponse { + status: ResponseStatus::OK, + addr_idx: idx, + dial_status: DialStatus::OK, + }) +} diff --git a/protocols/autonat/tests/autonatv2.rs b/protocols/autonat/tests/autonatv2.rs new file mode 100644 index 00000000000..1e278f5554f --- /dev/null +++ b/protocols/autonat/tests/autonatv2.rs @@ -0,0 +1,554 @@ +use std::{sync::Arc, time::Duration}; + +use libp2p_autonat::v2::{ + client::{self, Config}, + server, +}; +use libp2p_core::{multiaddr::Protocol, transport::TransportError, Multiaddr}; +use libp2p_swarm::{ + DialError, FromSwarm, NetworkBehaviour, NewExternalAddrCandidate, Swarm, SwarmEvent, +}; +use libp2p_swarm_test::SwarmExt; +use rand_core::OsRng; +use tokio::sync::oneshot; + +#[tokio::test] +async fn confirm_successful() { + libp2p_test_utils::with_default_env_filter(); + let (mut alice, mut bob) = start_and_connect().await; + + let cor_server_peer = *alice.local_peer_id(); + let cor_client_peer = *bob.local_peer_id(); + let bob_tcp_listeners = Arc::new(tcp_listeners(&bob)); + let alice_bob_tcp_listeners = bob_tcp_listeners.clone(); + + let alice_task = async { + let (dialed_peer_id, dialed_connection_id) = alice + .wait(|event| match event { + SwarmEvent::Dialing { + peer_id, + connection_id, + .. + } => peer_id.map(|peer_id| (peer_id, connection_id)), + _ => None, + }) + .await; + + assert_eq!(dialed_peer_id, cor_client_peer); + + let _ = alice + .wait(|event| match event { + SwarmEvent::ConnectionEstablished { + peer_id, + connection_id, + .. + } if peer_id == dialed_peer_id + && peer_id == cor_client_peer + && connection_id == dialed_connection_id => + { + Some(()) + } + _ => None, + }) + .await; + + let server::Event { + all_addrs, + tested_addr, + client, + data_amount, + result, + } = alice + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedServerEvent::Autonat(status_update)) => { + Some(status_update) + } + _ => None, + }) + .await; + + assert_eq!(tested_addr, bob_tcp_listeners.first().cloned().unwrap()); + assert_eq!(data_amount, 0); + assert_eq!(client, cor_client_peer); + assert_eq!(&all_addrs[..], &bob_tcp_listeners[..]); + assert!(result.is_ok(), "Result: {result:?}"); + }; + + let bob_task = async { + bob.wait(|event| match event { + SwarmEvent::NewExternalAddrCandidate { address } => Some(address), + _ => None, + }) + .await; + let incoming_conn_id = bob + .wait(|event| match event { + SwarmEvent::IncomingConnection { connection_id, .. } => Some(connection_id), + _ => None, + }) + .await; + + let _ = bob + .wait(|event| match event { + SwarmEvent::ConnectionEstablished { + connection_id, + peer_id, + .. + } if incoming_conn_id == connection_id && peer_id == cor_server_peer => Some(()), + _ => None, + }) + .await; + + let client::Event { + tested_addr, + bytes_sent, + server, + result, + } = bob + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedClientEvent::Autonat(status_update)) => { + Some(status_update) + } + _ => None, + }) + .await; + assert_eq!( + tested_addr, + alice_bob_tcp_listeners.first().cloned().unwrap() + ); + assert_eq!(bytes_sent, 0); + assert_eq!(server, cor_server_peer); + assert!(result.is_ok(), "Result is {result:?}"); + }; + + tokio::join!(alice_task, bob_task); +} + +#[tokio::test] +async fn dial_back_to_unsupported_protocol() { + libp2p_test_utils::with_default_env_filter(); + let (mut alice, mut bob) = bootstrap().await; + + let alice_peer_id = *alice.local_peer_id(); + + let test_addr: Multiaddr = "/ip4/127.0.0.1/udp/1234/quic/webtransport".parse().unwrap(); + let bob_test_addr = test_addr.clone(); + bob.behaviour_mut() + .autonat + .on_swarm_event(FromSwarm::NewExternalAddrCandidate( + NewExternalAddrCandidate { addr: &test_addr }, + )); + + let (bob_done_tx, bob_done_rx) = oneshot::channel(); + + let alice_task = async { + let (alice_dialing_peer, alice_conn_id) = alice + .wait(|event| match event { + SwarmEvent::Dialing { + peer_id, + connection_id, + } => peer_id.map(|e| (e, connection_id)), + _ => None, + }) + .await; + let mut outgoing_conn_error = alice + .wait(|event| match event { + SwarmEvent::OutgoingConnectionError { + connection_id, + peer_id: Some(peer_id), + error: DialError::Transport(transport_errs), + } if connection_id == alice_conn_id && alice_dialing_peer == peer_id => { + Some(transport_errs) + } + _ => None, + }) + .await; + if let Some((multiaddr, TransportError::MultiaddrNotSupported(not_supported_addr))) = + outgoing_conn_error.pop() + { + assert_eq!( + multiaddr, + test_addr.clone().with_p2p(alice_dialing_peer).unwrap() + ); + assert_eq!(not_supported_addr, multiaddr,); + } else { + panic!("Peers are empty"); + } + assert_eq!(outgoing_conn_error.len(), 0); + let data_amount = alice + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedServerEvent::Autonat(server::Event { + all_addrs, + tested_addr, + client, + data_amount, + result: Ok(()), + })) if all_addrs == vec![test_addr.clone()] + && tested_addr == test_addr.clone() + && client == alice_dialing_peer => + { + Some(data_amount) + } + _ => None, + }) + .await; + + let handler = tokio::spawn(async move { + alice.loop_on_next().await; + }); + let _ = bob_done_rx.await; + handler.abort(); + data_amount + }; + + let bob_task = async { + let data_amount = bob + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedClientEvent::Autonat(client::Event { + tested_addr, + bytes_sent, + server, + result: Err(_), + })) if server == alice_peer_id && tested_addr == bob_test_addr => Some(bytes_sent), + _ => None, + }) + .await; + bob_done_tx.send(()).unwrap(); + data_amount + }; + let (alice_amount, bob_amount) = tokio::join!(alice_task, bob_task); + assert_eq!(alice_amount, bob_amount); +} + +#[tokio::test] +async fn dial_back_to_non_libp2p() { + libp2p_test_utils::with_default_env_filter(); + let (mut alice, mut bob) = bootstrap().await; + let alice_peer_id = *alice.local_peer_id(); + + let addr_str = "/ip6/::1/tcp/1000"; + let addr: Multiaddr = addr_str.parse().unwrap(); + let bob_addr = addr.clone(); + bob.behaviour_mut() + .autonat + .on_swarm_event(FromSwarm::NewExternalAddrCandidate( + NewExternalAddrCandidate { addr: &addr }, + )); + + let alice_task = async { + let (alice_dialing_peer, alice_conn_id) = alice + .wait(|event| match event { + SwarmEvent::Dialing { + peer_id, + connection_id, + } => peer_id.map(|p| (p, connection_id)), + _ => None, + }) + .await; + let mut outgoing_conn_error = alice + .wait(|event| match event { + SwarmEvent::OutgoingConnectionError { + connection_id, + peer_id: Some(peer_id), + error: DialError::Transport(peers), + } if connection_id == alice_conn_id && peer_id == alice_dialing_peer => Some(peers), + _ => None, + }) + .await; + + if let Some((multiaddr, TransportError::Other(o))) = outgoing_conn_error.pop() { + assert_eq!( + multiaddr, + addr.clone().with_p2p(alice_dialing_peer).unwrap() + ); + let error_string = o.to_string(); + assert!( + error_string.contains("Connection refused"), + "Correct error string: {error_string} for {addr_str}" + ); + } else { + panic!("No outgoing connection errors"); + } + + alice + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedServerEvent::Autonat(server::Event { + all_addrs, + tested_addr, + client, + data_amount, + result: Ok(()), + })) if all_addrs == vec![addr.clone()] + && tested_addr == addr + && alice_dialing_peer == client => + { + Some(data_amount) + } + _ => None, + }) + .await + }; + let bob_task = async { + bob.wait(|event| match event { + SwarmEvent::Behaviour(CombinedClientEvent::Autonat(client::Event { + tested_addr, + bytes_sent, + server, + result: Err(_), + })) if tested_addr == bob_addr && server == alice_peer_id => Some(bytes_sent), + _ => None, + }) + .await + }; + + let (alice_bytes_sent, bob_bytes_sent) = tokio::join!(alice_task, bob_task); + assert_eq!(alice_bytes_sent, bob_bytes_sent); + bob.behaviour_mut().autonat.validate_addr(&addr); +} + +#[tokio::test] +async fn dial_back_to_not_supporting() { + libp2p_test_utils::with_default_env_filter(); + + let (mut alice, mut bob) = bootstrap().await; + let alice_peer_id = *alice.local_peer_id(); + + let (bob_done_tx, bob_done_rx) = oneshot::channel(); + + let hannes = new_dummy().await; + let hannes_peer_id = *hannes.local_peer_id(); + let unreachable_address = hannes.external_addresses().next().unwrap().clone(); + let bob_unreachable_address = unreachable_address.clone(); + bob.behaviour_mut() + .autonat + .on_swarm_event(FromSwarm::NewExternalAddrCandidate( + NewExternalAddrCandidate { + addr: &unreachable_address, + }, + )); + + let handler = tokio::spawn(async { hannes.loop_on_next().await }); + + let alice_task = async { + let (alice_dialing_peer, alice_conn_id) = alice + .wait(|event| match event { + SwarmEvent::Dialing { + peer_id, + connection_id, + } => peer_id.map(|p| (p, connection_id)), + _ => None, + }) + .await; + alice + .wait(|event| match event { + SwarmEvent::OutgoingConnectionError { + connection_id, + peer_id: Some(peer_id), + error: DialError::WrongPeerId { obtained, .. }, + } if connection_id == alice_conn_id + && peer_id == alice_dialing_peer + && obtained == hannes_peer_id => + { + Some(()) + } + _ => None, + }) + .await; + + let data_amount = alice + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedServerEvent::Autonat(server::Event { + all_addrs, + tested_addr, + client, + data_amount, + result: Ok(()), + })) if all_addrs == vec![unreachable_address.clone()] + && tested_addr == unreachable_address + && alice_dialing_peer == client => + { + Some(data_amount) + } + _ => None, + }) + .await; + tokio::select! { + _ = bob_done_rx => { + data_amount + } + _ = alice.loop_on_next() => { + unreachable!(); + } + } + }; + + let bob_task = async { + let bytes_sent = bob + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedClientEvent::Autonat(client::Event { + tested_addr, + bytes_sent, + server, + result: Err(_), + })) if tested_addr == bob_unreachable_address && server == alice_peer_id => { + Some(bytes_sent) + } + _ => None, + }) + .await; + bob_done_tx.send(()).unwrap(); + bytes_sent + }; + + let (alice_bytes_sent, bob_bytes_sent) = tokio::join!(alice_task, bob_task); + assert_eq!(alice_bytes_sent, bob_bytes_sent); + handler.abort(); +} + +async fn new_server() -> Swarm { + let mut node = Swarm::new_ephemeral(|identity| CombinedServer { + autonat: libp2p_autonat::v2::server::Behaviour::default(), + identify: libp2p_identify::Behaviour::new(libp2p_identify::Config::new( + "/libp2p-test/1.0.0".into(), + identity.public().clone(), + )), + }); + node.listen().with_tcp_addr_external().await; + + node +} + +async fn new_client() -> Swarm { + let mut node = Swarm::new_ephemeral(|identity| CombinedClient { + autonat: libp2p_autonat::v2::client::Behaviour::new( + OsRng, + Config::default().with_probe_interval(Duration::from_millis(100)), + ), + identify: libp2p_identify::Behaviour::new(libp2p_identify::Config::new( + "/libp2p-test/1.0.0".into(), + identity.public().clone(), + )), + }); + node.listen().await; + node +} + +#[derive(libp2p_swarm::NetworkBehaviour)] +#[behaviour(prelude = "libp2p_swarm::derive_prelude")] +struct CombinedServer { + autonat: libp2p_autonat::v2::server::Behaviour, + identify: libp2p_identify::Behaviour, +} + +#[derive(libp2p_swarm::NetworkBehaviour)] +#[behaviour(prelude = "libp2p_swarm::derive_prelude")] +struct CombinedClient { + autonat: libp2p_autonat::v2::client::Behaviour, + identify: libp2p_identify::Behaviour, +} + +async fn new_dummy() -> Swarm { + let mut node = Swarm::new_ephemeral(|identity| { + libp2p_identify::Behaviour::new(libp2p_identify::Config::new( + "/libp2p-test/1.0.0".into(), + identity.public().clone(), + )) + }); + node.listen().with_tcp_addr_external().await; + node +} + +async fn start_and_connect() -> (Swarm, Swarm) { + let mut alice = new_server().await; + let mut bob = new_client().await; + + bob.connect(&mut alice).await; + (alice, bob) +} + +async fn bootstrap() -> (Swarm, Swarm) { + let (mut alice, mut bob) = start_and_connect().await; + + let cor_server_peer = *alice.local_peer_id(); + let cor_client_peer = *bob.local_peer_id(); + + let alice_task = async { + let (dialed_peer_id, dialed_connection_id) = alice + .wait(|event| match event { + SwarmEvent::Dialing { + peer_id, + connection_id, + .. + } => peer_id.map(|peer_id| (peer_id, connection_id)), + _ => None, + }) + .await; + + let _ = alice + .wait(|event| match event { + SwarmEvent::ConnectionEstablished { + peer_id, + connection_id, + .. + } if peer_id == dialed_peer_id + && peer_id == cor_client_peer + && connection_id == dialed_connection_id => + { + Some(()) + } + _ => None, + }) + .await; + + alice + .wait(|event| match event { + SwarmEvent::Behaviour(CombinedServerEvent::Autonat(_)) => Some(()), + _ => None, + }) + .await; + }; + + let bob_task = async { + bob.wait(|event| match event { + SwarmEvent::NewExternalAddrCandidate { address } => Some(address), + _ => None, + }) + .await; + let incoming_conn_id = bob + .wait(|event| match event { + SwarmEvent::IncomingConnection { connection_id, .. } => Some(connection_id), + _ => None, + }) + .await; + + let _ = bob + .wait(|event| match event { + SwarmEvent::ConnectionEstablished { + connection_id, + peer_id, + .. + } if incoming_conn_id == connection_id && peer_id == cor_server_peer => Some(()), + _ => None, + }) + .await; + + bob.wait(|event| match event { + SwarmEvent::Behaviour(CombinedClientEvent::Autonat(_)) => Some(()), + _ => None, + }) + .await; + }; + + tokio::join!(alice_task, bob_task); + (alice, bob) +} + +fn tcp_listeners(swarm: &Swarm) -> Vec { + swarm + .listeners() + .filter(|addr| { + addr.iter() + .any(|protocol| matches!(protocol, Protocol::Tcp(_))) + }) + .cloned() + .collect::>() +} diff --git a/protocols/autonat/tests/test_client.rs b/protocols/autonat/tests/test_client.rs index 7509d3ef425..49c6c483514 100644 --- a/protocols/autonat/tests/test_client.rs +++ b/protocols/autonat/tests/test_client.rs @@ -18,7 +18,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use async_std::task::JoinHandle; +use std::time::Duration; + use libp2p_autonat::{ Behaviour, Config, Event, NatStatus, OutboundProbeError, OutboundProbeEvent, ResponseError, }; @@ -26,13 +27,13 @@ use libp2p_core::Multiaddr; use libp2p_identity::PeerId; use libp2p_swarm::{Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt as _; -use std::time::Duration; +use tokio::task::JoinHandle; const MAX_CONFIDENCE: usize = 3; const TEST_RETRY_INTERVAL: Duration = Duration::from_secs(1); const TEST_REFRESH_INTERVAL: Duration = Duration::from_secs(2); -#[async_std::test] +#[tokio::test] async fn test_auto_probe() { let mut client = Swarm::new_ephemeral(|key| { Behaviour::new( @@ -116,7 +117,8 @@ async fn test_auto_probe() { // It can happen that the server observed the established connection and // returned a response before the inbound established connection was reported at the client. - // In this (rare) case the `ConnectionEstablished` event occurs after the `OutboundProbeEvent::Response`. + // In this (rare) case the `ConnectionEstablished` event + // occurs after the `OutboundProbeEvent::Response`. if !had_connection_event { match client.next_swarm_event().await { SwarmEvent::ConnectionEstablished { @@ -133,7 +135,7 @@ async fn test_auto_probe() { assert!(client.behaviour().public_address().is_some()); } -#[async_std::test] +#[tokio::test] async fn test_confidence() { let mut client = Swarm::new_ephemeral(|key| { Behaviour::new( @@ -217,7 +219,7 @@ async fn test_confidence() { } } -#[async_std::test] +#[tokio::test] async fn test_throttle_server_period() { let mut client = Swarm::new_ephemeral(|key| { Behaviour::new( @@ -268,7 +270,7 @@ async fn test_throttle_server_period() { assert_eq!(client.behaviour().confidence(), 0); } -#[async_std::test] +#[tokio::test] async fn test_use_connected_as_server() { let mut client = Swarm::new_ephemeral(|key| { Behaviour::new( @@ -306,7 +308,7 @@ async fn test_use_connected_as_server() { } } -#[async_std::test] +#[tokio::test] async fn test_outbound_failure() { let mut client = Swarm::new_ephemeral(|key| { Behaviour::new( @@ -351,7 +353,7 @@ async fn test_outbound_failure() { let mut inactive_servers = Vec::new(); for (id, handle) in servers.split_off(1) { - handle.cancel().await; + handle.abort(); inactive_servers.push(id); } @@ -375,7 +377,7 @@ async fn test_outbound_failure() { } } -#[async_std::test] +#[tokio::test] async fn test_global_ips_config() { let mut client = Swarm::new_ephemeral(|key| { Behaviour::new( @@ -426,7 +428,7 @@ async fn new_server_swarm() -> (PeerId, Multiaddr, JoinHandle<()>) { let (_, multiaddr) = swarm.listen().await; let peer_id = *swarm.local_peer_id(); - let task = async_std::task::spawn(swarm.loop_on_next()); + let task = tokio::spawn(swarm.loop_on_next()); (peer_id, multiaddr, task) } diff --git a/protocols/autonat/tests/test_server.rs b/protocols/autonat/tests/test_server.rs index b0610ef59a4..944c4301b20 100644 --- a/protocols/autonat/tests/test_server.rs +++ b/protocols/autonat/tests/test_server.rs @@ -18,22 +18,22 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::{num::NonZeroU32, time::Duration}; + use libp2p_autonat::{ Behaviour, Config, Event, InboundProbeError, InboundProbeEvent, ResponseError, }; use libp2p_core::{multiaddr::Protocol, ConnectedPoint, Endpoint, Multiaddr}; use libp2p_identity::PeerId; -use libp2p_swarm::DialError; -use libp2p_swarm::{Swarm, SwarmEvent}; +use libp2p_swarm::{DialError, Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt as _; -use std::{num::NonZeroU32, time::Duration}; -#[async_std::test] +#[tokio::test] async fn test_dial_back() { let (mut server, server_id, server_addr) = new_server_swarm(None).await; let (mut client, client_id) = new_client_swarm(server_id, server_addr).await; let (_, client_addr) = client.listen().await; - async_std::task::spawn(client.loop_on_next()); + tokio::spawn(client.loop_on_next()); let client_port = client_addr .into_iter() @@ -92,6 +92,7 @@ async fn test_dial_back() { ConnectedPoint::Dialer { address, role_override: Endpoint::Dialer, + .. }, num_established, concurrent_dial_errors, @@ -127,14 +128,14 @@ async fn test_dial_back() { } } -#[async_std::test] +#[tokio::test] async fn test_dial_error() { let (mut server, server_id, server_addr) = new_server_swarm(None).await; let (mut client, client_id) = new_client_swarm(server_id, server_addr).await; client .behaviour_mut() .probe_address("/ip4/127.0.0.1/tcp/12345".parse().unwrap()); - async_std::task::spawn(client.loop_on_next()); + tokio::spawn(client.loop_on_next()); let request_probe_id = match server.next_behaviour_event().await { Event::InboundProbe(InboundProbeEvent::Request { peer, probe_id, .. }) => { @@ -177,7 +178,7 @@ async fn test_dial_error() { } } -#[async_std::test] +#[tokio::test] async fn test_throttle_global_max() { let (mut server, server_id, server_addr) = new_server_swarm(Some(Config { throttle_clients_global_max: 1, @@ -189,7 +190,7 @@ async fn test_throttle_global_max() { for _ in 0..2 { let (mut client, _) = new_client_swarm(server_id, server_addr.clone()).await; client.listen().await; - async_std::task::spawn(client.loop_on_next()); + tokio::spawn(client.loop_on_next()); } let (first_probe_id, first_peer_id) = match server.next_behaviour_event().await { @@ -217,7 +218,7 @@ async fn test_throttle_global_max() { } } -#[async_std::test] +#[tokio::test] async fn test_throttle_peer_max() { let (mut server, server_id, server_addr) = new_server_swarm(Some(Config { throttle_clients_peer_max: 1, @@ -229,7 +230,7 @@ async fn test_throttle_peer_max() { let (mut client, client_id) = new_client_swarm(server_id, server_addr.clone()).await; client.listen().await; - async_std::task::spawn(client.loop_on_next()); + tokio::spawn(client.loop_on_next()); let first_probe_id = match server.next_behaviour_event().await { Event::InboundProbe(InboundProbeEvent::Request { peer, probe_id, .. }) => { @@ -264,7 +265,7 @@ async fn test_throttle_peer_max() { }; } -#[async_std::test] +#[tokio::test] async fn test_dial_multiple_addr() { let (mut server, server_id, server_addr) = new_server_swarm(Some(Config { throttle_clients_peer_max: 1, @@ -279,7 +280,7 @@ async fn test_dial_multiple_addr() { client .behaviour_mut() .probe_address("/ip4/127.0.0.1/tcp/12345".parse().unwrap()); - async_std::task::spawn(client.loop_on_next()); + tokio::spawn(client.loop_on_next()); let dial_addresses = match server.next_behaviour_event().await { Event::InboundProbe(InboundProbeEvent::Request { @@ -300,6 +301,7 @@ async fn test_dial_multiple_addr() { ConnectedPoint::Dialer { address, role_override: Endpoint::Dialer, + .. }, concurrent_dial_errors, .. @@ -325,7 +327,7 @@ async fn test_dial_multiple_addr() { } } -#[async_std::test] +#[tokio::test] async fn test_global_ips_config() { let (mut server, server_id, server_addr) = new_server_swarm(Some(Config { // Enforce that only clients outside of the local network are qualified for dial-backs. @@ -336,9 +338,10 @@ async fn test_global_ips_config() { let (mut client, _) = new_client_swarm(server_id, server_addr.clone()).await; client.listen().await; - async_std::task::spawn(client.loop_on_next()); + tokio::spawn(client.loop_on_next()); - // Expect the probe to be refused as both peers run on the same machine and thus in the same local network. + // Expect the probe to be refused as both peers run + // on the same machine and thus in the same local network. match server.next_behaviour_event().await { Event::InboundProbe(InboundProbeEvent::Error { error, .. }) => assert!(matches!( error, diff --git a/protocols/dcutr/CHANGELOG.md b/protocols/dcutr/CHANGELOG.md index d3857373658..80cac37321e 100644 --- a/protocols/dcutr/CHANGELOG.md +++ b/protocols/dcutr/CHANGELOG.md @@ -1,3 +1,16 @@ +## 0.12.1 + +- Deprecate `void` crate. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). + +## 0.12.0 + + + +## 0.11.1 +- Use `web-time` instead of `instant`. + See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). + ## 0.11.0 - Add `ConnectionId` to `Event::DirectConnectionUpgradeSucceeded` and `Event::DirectConnectionUpgradeFailed`. diff --git a/protocols/dcutr/Cargo.toml b/protocols/dcutr/Cargo.toml index 1fc4b367dc8..7bc05671aa2 100644 --- a/protocols/dcutr/Cargo.toml +++ b/protocols/dcutr/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-dcutr" edition = "2021" rust-version = { workspace = true } description = "Direct connection upgrade through relay" -version = "0.11.0" +version = "0.12.1" authors = ["Max Inden "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -12,43 +12,35 @@ categories = ["network-programming", "asynchronous"] [dependencies] asynchronous-codec = { workspace = true } -either = "1.9.0" -futures = "0.3.29" +either = "1.11.0" +futures = { workspace = true } futures-timer = "3.0" -instant = "0.1.12" +web-time = { workspace = true } libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true } quick-protobuf = "0.8" quick-protobuf-codec = { workspace = true } -thiserror = "1.0" -tracing = "0.1.37" -void = "1" -lru = "0.12.1" +thiserror = { workspace = true } +tracing = { workspace = true } +lru = "0.12.3" futures-bounded = { workspace = true } [dev-dependencies] -async-std = { version = "1.12.0", features = ["attributes"] } -clap = { version = "4.4.10", features = ["derive"] } -libp2p-dns = { workspace = true, features = ["async-std"] } libp2p-identify = { workspace = true } -libp2p-noise = { workspace = true } -libp2p-ping = { workspace = true } libp2p-plaintext = { workspace = true } libp2p-relay = { workspace = true } libp2p-swarm = { workspace = true, features = ["macros"] } libp2p-swarm-test = { path = "../../swarm-test" } libp2p-tcp = { workspace = true, features = ["async-io"] } libp2p-yamux = { workspace = true } -rand = "0.8" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +libp2p-test-utils = { workspace = true } +tokio = { workspace = true, features = ["rt", "macros"] } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/dcutr/src/behaviour.rs b/protocols/dcutr/src/behaviour.rs index 3742eb512f5..989635c02ba 100644 --- a/protocols/dcutr/src/behaviour.rs +++ b/protocols/dcutr/src/behaviour.rs @@ -20,25 +20,28 @@ //! [`NetworkBehaviour`] to act as a direct connection upgrade through relay node. -use crate::{handler, protocol}; +use std::{ + collections::{HashMap, HashSet, VecDeque}, + convert::Infallible, + num::NonZeroUsize, + task::{Context, Poll}, +}; + use either::Either; -use libp2p_core::connection::ConnectedPoint; -use libp2p_core::multiaddr::Protocol; -use libp2p_core::{Endpoint, Multiaddr}; +use libp2p_core::{ + connection::ConnectedPoint, multiaddr::Protocol, transport::PortUse, Endpoint, Multiaddr, +}; use libp2p_identity::PeerId; -use libp2p_swarm::behaviour::{ConnectionClosed, DialFailure, FromSwarm}; -use libp2p_swarm::dial_opts::{self, DialOpts}; use libp2p_swarm::{ - dummy, ConnectionDenied, ConnectionHandler, ConnectionId, NewExternalAddrCandidate, THandler, - THandlerOutEvent, + behaviour::{ConnectionClosed, DialFailure, FromSwarm}, + dial_opts::{self, DialOpts}, + dummy, ConnectionDenied, ConnectionHandler, ConnectionId, NetworkBehaviour, + NewExternalAddrCandidate, NotifyHandler, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; -use libp2p_swarm::{NetworkBehaviour, NotifyHandler, THandlerInEvent, ToSwarm}; use lru::LruCache; -use std::collections::{HashMap, HashSet, VecDeque}; -use std::num::NonZeroUsize; -use std::task::{Context, Poll}; use thiserror::Error; -use void::Void; + +use crate::{handler, protocol}; pub(crate) const MAX_NUMBER_OF_UPGRADE_ATTEMPTS: u8 = 3; @@ -67,7 +70,7 @@ enum InnerError { pub struct Behaviour { /// Queue of actions to return when polled. - queued_events: VecDeque>>, + queued_events: VecDeque>>, /// All direct (non-relayed) connections. direct_connections: HashMap>, @@ -183,7 +186,8 @@ impl NetworkBehaviour for Behaviour { handler::relayed::Handler::new(connected_point, self.observed_addresses()); handler.on_behaviour_event(handler::relayed::Command::Connect); - return Ok(Either::Left(handler)); // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. + // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. + return Ok(Either::Left(handler)); } self.direct_connections .entry(peer) @@ -191,9 +195,9 @@ impl NetworkBehaviour for Behaviour { .insert(connection_id); assert!( - self.direct_to_relayed_connections - .get(&connection_id) - .is_none(), + !self + .direct_to_relayed_connections + .contains_key(&connection_id), "state mismatch" ); @@ -206,15 +210,18 @@ impl NetworkBehaviour for Behaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { if is_relayed(addr) { return Ok(Either::Left(handler::relayed::Handler::new( ConnectedPoint::Dialer { address: addr.clone(), role_override, + port_use, }, self.observed_addresses(), - ))); // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. + ))); // TODO: We could make two `handler::relayed::Handler` here, one inbound one + // outbound. } self.direct_connections @@ -252,7 +259,8 @@ impl NetworkBehaviour for Behaviour { Either::Left(_) => connection_id, Either::Right(_) => match self.direct_to_relayed_connections.get(&connection_id) { None => { - // If the connection ID is unknown to us, it means we didn't create it so ignore any event coming from it. + // If the connection ID is unknown to us, it means we didn't create it so ignore + // any event coming from it. return; } Some(relayed_connection_id) => *relayed_connection_id, @@ -311,7 +319,9 @@ impl NetworkBehaviour for Behaviour { .or_default() += 1; self.queued_events.push_back(ToSwarm::Dial { opts }); } - Either::Right(never) => void::unreachable(never), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + Either::Right(never) => libp2p_core::util::unreachable(never), }; } @@ -342,8 +352,9 @@ impl NetworkBehaviour for Behaviour { /// /// We use an [`LruCache`] to favor addresses that are reported more often. /// When attempting a hole-punch, we will try more frequent addresses first. -/// Most of these addresses will come from observations by other nodes (via e.g. the identify protocol). -/// More common observations mean a more likely stable port-mapping and thus a higher chance of a successful hole-punch. +/// Most of these addresses will come from observations by other nodes (via e.g. the identify +/// protocol). More common observations mean a more likely stable port-mapping and thus a higher +/// chance of a successful hole-punch. struct Candidates { inner: LruCache, me: PeerId, diff --git a/protocols/dcutr/src/handler/relayed.rs b/protocols/dcutr/src/handler/relayed.rs index eba58f89313..0d6e1b5e889 100644 --- a/protocols/dcutr/src/handler/relayed.rs +++ b/protocols/dcutr/src/handler/relayed.rs @@ -20,26 +20,31 @@ //! [`ConnectionHandler`] handling relayed connection potentially upgraded to a direct connection. -use crate::behaviour::MAX_NUMBER_OF_UPGRADE_ATTEMPTS; -use crate::{protocol, PROTOCOL_NAME}; +use std::{ + collections::VecDeque, + io, + task::{Context, Poll}, + time::Duration, +}; + use either::Either; use futures::future; -use libp2p_core::multiaddr::Multiaddr; -use libp2p_core::upgrade::{DeniedUpgrade, ReadyUpgrade}; -use libp2p_core::ConnectedPoint; -use libp2p_swarm::handler::{ - ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, - ListenUpgradeError, +use libp2p_core::{ + multiaddr::Multiaddr, + upgrade::{DeniedUpgrade, ReadyUpgrade}, + ConnectedPoint, }; use libp2p_swarm::{ + handler::{ + ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, + ListenUpgradeError, + }, ConnectionHandler, ConnectionHandlerEvent, StreamProtocol, StreamUpgradeError, SubstreamProtocol, }; use protocol::{inbound, outbound}; -use std::collections::VecDeque; -use std::io; -use std::task::{Context, Poll}; -use std::time::Duration; + +use crate::{behaviour::MAX_NUMBER_OF_UPGRADE_ATTEMPTS, protocol, PROTOCOL_NAME}; #[derive(Debug)] pub enum Command { @@ -114,8 +119,10 @@ impl Handler { } self.attempts += 1; } - // A connection listener denies all incoming substreams, thus none can ever be fully negotiated. - future::Either::Right(output) => void::unreachable(output), + // A connection listener denies all incoming substreams, thus none can ever be fully + // negotiated. TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + future::Either::Right(output) => libp2p_core::util::unreachable(output), } } @@ -153,7 +160,9 @@ impl Handler { ::InboundProtocol, >, ) { - void::unreachable(error.into_inner()); + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(error.into_inner()); } fn on_dial_upgrade_error( @@ -164,7 +173,9 @@ impl Handler { >, ) { let error = match error { - StreamUpgradeError::Apply(v) => void::unreachable(v), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + StreamUpgradeError::Apply(v) => libp2p_core::util::unreachable(v), StreamUpgradeError::NegotiationFailed => outbound::Error::Unsupported, StreamUpgradeError::Io(e) => outbound::Error::Io(e), StreamUpgradeError::Timeout => outbound::Error::Io(io::ErrorKind::TimedOut.into()), @@ -298,6 +309,8 @@ impl ConnectionHandler for Handler { ConnectionEvent::FullyNegotiatedOutbound(fully_negotiated_outbound) => { self.on_fully_negotiated_outbound(fully_negotiated_outbound) } + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::ListenUpgradeError(listen_upgrade_error) => { self.on_listen_upgrade_error(listen_upgrade_error) } diff --git a/protocols/dcutr/src/protocol/inbound.rs b/protocols/dcutr/src/protocol/inbound.rs index b8f90daf3a1..c5209930ca2 100644 --- a/protocols/dcutr/src/protocol/inbound.rs +++ b/protocols/dcutr/src/protocol/inbound.rs @@ -18,15 +18,16 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::proto; +use std::io; + use asynchronous_codec::Framed; use futures::prelude::*; use libp2p_core::{multiaddr::Protocol, Multiaddr}; use libp2p_swarm::Stream; -use std::convert::TryFrom; -use std::io; use thiserror::Error; +use crate::proto; + pub(crate) async fn handshake( stream: Stream, candidates: Vec, diff --git a/protocols/dcutr/src/protocol/outbound.rs b/protocols/dcutr/src/protocol/outbound.rs index d9cb60a01f6..cdd3d5fbf0b 100644 --- a/protocols/dcutr/src/protocol/outbound.rs +++ b/protocols/dcutr/src/protocol/outbound.rs @@ -18,17 +18,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::proto; -use crate::PROTOCOL_NAME; +use std::io; + use asynchronous_codec::Framed; use futures::prelude::*; use futures_timer::Delay; -use instant::Instant; use libp2p_core::{multiaddr::Protocol, Multiaddr}; use libp2p_swarm::Stream; -use std::convert::TryFrom; -use std::io; use thiserror::Error; +use web_time::Instant; + +use crate::{proto, PROTOCOL_NAME}; pub(crate) async fn handshake( stream: Stream, diff --git a/protocols/dcutr/tests/lib.rs b/protocols/dcutr/tests/lib.rs index 9e1f0591e6d..ce7119cebcf 100644 --- a/protocols/dcutr/tests/lib.rs +++ b/protocols/dcutr/tests/lib.rs @@ -18,9 +18,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use libp2p_core::multiaddr::{Multiaddr, Protocol}; -use libp2p_core::transport::upgrade::Version; -use libp2p_core::transport::{MemoryTransport, Transport}; +use std::time::Duration; + +use libp2p_core::{ + multiaddr::{Multiaddr, Protocol}, + transport::{upgrade::Version, MemoryTransport, Transport}, +}; use libp2p_dcutr as dcutr; use libp2p_identify as identify; use libp2p_identity as identity; @@ -29,14 +32,10 @@ use libp2p_plaintext as plaintext; use libp2p_relay as relay; use libp2p_swarm::{Config, NetworkBehaviour, Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt as _; -use std::time::Duration; -use tracing_subscriber::EnvFilter; -#[async_std::test] +#[tokio::test] async fn connect() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut relay = build_relay(); let mut dst = build_client(); @@ -53,7 +52,7 @@ async fn connect() { let relay_peer_id = *relay.local_peer_id(); let dst_peer_id = *dst.local_peer_id(); - async_std::task::spawn(relay.loop_on_next()); + tokio::spawn(relay.loop_on_next()); let dst_relayed_addr = relay_tcp_addr .with(Protocol::P2p(relay_peer_id)) @@ -68,7 +67,7 @@ async fn connect() { false, // No renewal. ) .await; - async_std::task::spawn(dst.loop_on_next()); + tokio::spawn(dst.loop_on_next()); src.dial_and_wait(dst_relayed_addr.clone()).await; @@ -203,6 +202,7 @@ async fn wait_for_reservation( SwarmEvent::ExternalAddrConfirmed { address } if !is_renewal => { assert_eq!(address, client_addr); } + SwarmEvent::NewExternalAddrOfPeer { .. } => {} e => panic!("{e:?}"), } } diff --git a/protocols/floodsub/CHANGELOG.md b/protocols/floodsub/CHANGELOG.md index 8e3cb70ddf1..4192e0ea58d 100644 --- a/protocols/floodsub/CHANGELOG.md +++ b/protocols/floodsub/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.45.0 + + + ## 0.44.0 - Change publish to require `data: impl Into` to internally avoid any costly cloning / allocation. diff --git a/protocols/floodsub/Cargo.toml b/protocols/floodsub/Cargo.toml index 87f0e636272..dcfde6383cc 100644 --- a/protocols/floodsub/Cargo.toml +++ b/protocols/floodsub/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-floodsub" edition = "2021" rust-version = { workspace = true } description = "Floodsub protocol for libp2p" -version = "0.44.0" +version = "0.45.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -14,24 +14,22 @@ categories = ["network-programming", "asynchronous"] asynchronous-codec = { workspace = true } cuckoofilter = "0.5.0" fnv = "1.0" -bytes = "1.5" -futures = "0.3.29" +bytes = "1.6" +futures = { workspace = true } libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true } quick-protobuf = "0.8" quick-protobuf-codec = { workspace = true } rand = "0.8" -smallvec = "1.11.2" -thiserror = "1.0.50" -tracing = "0.1.37" +smallvec = "1.13.2" +thiserror = { workspace = true } +tracing = { workspace = true } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/floodsub/src/layer.rs b/protocols/floodsub/src/layer.rs index 35711408a8d..477172b42c0 100644 --- a/protocols/floodsub/src/layer.rs +++ b/protocols/floodsub/src/layer.rs @@ -18,26 +18,36 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::protocol::{ - FloodsubMessage, FloodsubProtocol, FloodsubRpc, FloodsubSubscription, - FloodsubSubscriptionAction, +use std::{ + collections::{ + hash_map::{DefaultHasher, HashMap}, + VecDeque, + }, + iter, + task::{Context, Poll}, }; -use crate::topic::Topic; -use crate::FloodsubConfig; + use bytes::Bytes; use cuckoofilter::{CuckooError, CuckooFilter}; use fnv::FnvHashSet; -use libp2p_core::{Endpoint, Multiaddr}; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; use libp2p_identity::PeerId; -use libp2p_swarm::behaviour::{ConnectionClosed, ConnectionEstablished, FromSwarm}; use libp2p_swarm::{ - dial_opts::DialOpts, CloseConnection, ConnectionDenied, ConnectionId, NetworkBehaviour, - NotifyHandler, OneShotHandler, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, + behaviour::{ConnectionClosed, ConnectionEstablished, FromSwarm}, + dial_opts::DialOpts, + CloseConnection, ConnectionDenied, ConnectionId, NetworkBehaviour, NotifyHandler, + OneShotHandler, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; use smallvec::SmallVec; -use std::collections::hash_map::{DefaultHasher, HashMap}; -use std::task::{Context, Poll}; -use std::{collections::VecDeque, iter}; + +use crate::{ + protocol::{ + FloodsubMessage, FloodsubProtocol, FloodsubRpc, FloodsubSubscription, + FloodsubSubscriptionAction, + }, + topic::Topic, + FloodsubConfig, +}; /// Network behaviour that handles the floodsub protocol. pub struct Floodsub { @@ -191,7 +201,8 @@ impl Floodsub { self.publish_many_inner(topic, data, true) } - /// Publishes a message with multiple topics to the network, even if we're not subscribed to any of the topics. + /// Publishes a message with multiple topics to the network, even if we're not subscribed to any + /// of the topics. pub fn publish_many_any( &mut self, topic: impl IntoIterator>, @@ -346,6 +357,7 @@ impl NetworkBehaviour for Floodsub { _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(Default::default()) } diff --git a/protocols/floodsub/src/lib.rs b/protocols/floodsub/src/lib.rs index 94766d5fdca..d43b0c88788 100644 --- a/protocols/floodsub/src/lib.rs +++ b/protocols/floodsub/src/lib.rs @@ -35,9 +35,11 @@ mod proto { pub(crate) use self::floodsub::pb::{mod_RPC::SubOpts, Message, RPC}; } -pub use self::layer::{Floodsub, FloodsubEvent}; -pub use self::protocol::{FloodsubMessage, FloodsubRpc}; -pub use self::topic::Topic; +pub use self::{ + layer::{Floodsub, FloodsubEvent}, + protocol::{FloodsubMessage, FloodsubRpc}, + topic::Topic, +}; /// Configuration options for the Floodsub protocol. #[derive(Debug, Clone)] diff --git a/protocols/floodsub/src/protocol.rs b/protocols/floodsub/src/protocol.rs index edc842be8ce..69cfcbd9dc7 100644 --- a/protocols/floodsub/src/protocol.rs +++ b/protocols/floodsub/src/protocol.rs @@ -18,19 +18,19 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::proto; -use crate::topic::Topic; +use std::{io, iter, pin::Pin}; + use asynchronous_codec::Framed; use bytes::Bytes; use futures::{ io::{AsyncRead, AsyncWrite}, - Future, + Future, SinkExt, StreamExt, }; -use futures::{SinkExt, StreamExt}; use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use libp2p_identity::PeerId; use libp2p_swarm::StreamProtocol; -use std::{io, iter, pin::Pin}; + +use crate::{proto, topic::Topic}; const MAX_MESSAGE_LEN_BYTES: usize = 2048; diff --git a/protocols/gossipsub/CHANGELOG.md b/protocols/gossipsub/CHANGELOG.md index 5ff4cfa27d6..d5054bea945 100644 --- a/protocols/gossipsub/CHANGELOG.md +++ b/protocols/gossipsub/CHANGELOG.md @@ -1,7 +1,50 @@ +## 0.48.0 +- Add configurable `idontwant_message_size_threshold` parameter. + See [PR 5770](https://github.com/libp2p/rust-libp2p/pull/5770) + +- Introduce Gossipsub v1.2 [spec](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.2.md). + See [PR 5697](https://github.com/libp2p/rust-libp2p/pull/5697) + +- Correct state inconsistencies with the mesh and fanout when unsubscribing. + See [PR 5690](https://github.com/libp2p/rust-libp2p/pull/5690) + +- Deprecate `futures-ticker` and use `futures-timer` instead. + See [PR 5674](https://github.com/libp2p/rust-libp2p/pull/5674). + +- Apply `max_transmit_size` to the inner message instead of the final payload. + See [PR 5642](https://github.com/libp2p/rust-libp2p/pull/5642). + +- Deprecate `void` crate. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). + +- Attempt to publish to at least mesh_n peers when flood publish is disabled. + See [PR 5578](https://github.com/libp2p/rust-libp2p/pull/5578). + +- Introduce back pressure and penalize slow peers. Drop stale messages that timeout before being + delivered. + See [PR 5595](https://github.com/libp2p/rust-libp2p/pull/5595). + +- Change `Behaviour::unsubscribe` and `Behaviour::report_message_validation_result` + to `bool` they don't need to be a `Result`. + See [PR 5595](https://github.com/libp2p/rust-libp2p/pull/5595). + +- Fix `cargo clippy` warnings in `rustc 1.84.0-beta.1`. + See [PR 5700](https://github.com/libp2p/rust-libp2p/pull/5700). + +## 0.47.0 + + +- Add ConnectionError to FromSwarm::ConnectionClosed. + See [PR 5485](https://github.com/libp2p/rust-libp2p/pull/5485). + +## 0.46.2 +- Use `web-time` instead of `instant`. + See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). + ## 0.46.1 - Deprecate `Rpc` in preparation for removing it from the public API because it is an internal type. - See [PR 4833](https://github.com/libp2p/rust-libp2p/pull/4833). + See [PR 4833](https://github.com/libp2p/rust-libp2p/pull/4833). ## 0.46.0 @@ -208,7 +251,7 @@ - Move from `open-metrics-client` to `prometheus-client` (see [PR 2442]). -- Emit gossip of all non empty topics (see [PR 2481]). +- Emit gossip of all non-empty topics (see [PR 2481]). - Merge NetworkBehaviour's inject_\* paired methods (see [PR 2445]). diff --git a/protocols/gossipsub/Cargo.toml b/protocols/gossipsub/Cargo.toml index 37873de39f9..328d4367204 100644 --- a/protocols/gossipsub/Cargo.toml +++ b/protocols/gossipsub/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-gossipsub" edition = "2021" rust-version = { workspace = true } description = "Gossipsub protocol for libp2p" -version = "0.46.1" +version = "0.48.0" authors = ["Age Manning "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,52 +11,47 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [features] -wasm-bindgen = ["getrandom/js", "instant/wasm-bindgen"] +wasm-bindgen = ["getrandom/js", "futures-timer/wasm-bindgen"] [dependencies] +async-channel = "2.3.1" asynchronous-codec = { workspace = true } -base64 = "0.21.5" +base64 = "0.22.1" byteorder = "1.5.0" -bytes = "1.5" -either = "1.9" +bytes = "1.6" +either = "1.11" fnv = "1.0.7" -futures = "0.3.29" -futures-ticker = "0.0.3" -getrandom = "0.2.11" +futures = { workspace = true } +futures-timer = "3.0.2" +getrandom = { workspace = true } +hashlink = { workspace = true} hex_fmt = "0.3.0" -instant = "0.1.12" +web-time = { workspace = true } libp2p-core = { workspace = true } libp2p-identity = { workspace = true, features = ["rand"] } libp2p-swarm = { workspace = true } quick-protobuf = "0.8" quick-protobuf-codec = { workspace = true } rand = "0.8" -regex = "1.10.2" +regex = "1.10.5" serde = { version = "1", optional = true, features = ["derive"] } sha2 = "0.10.8" -smallvec = "1.11.2" -tracing = "0.1.37" -void = "1.0.2" +tracing = { workspace = true } # Metrics dependencies prometheus-client = { workspace = true } [dev-dependencies] -async-std = { version = "1.6.3", features = ["unstable"] } -hex = "0.4.2" libp2p-core = { workspace = true } -libp2p-yamux = { workspace = true } -libp2p-noise = { workspace = true } libp2p-swarm-test = { path = "../../swarm-test" } quickcheck = { workspace = true } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +libp2p-test-utils = { workspace = true } +tokio = { workspace = true, features = ["rt", "rt-multi-thread", "time", "macros"] } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/gossipsub/src/backoff.rs b/protocols/gossipsub/src/backoff.rs index b4a40b91a74..ee600d22098 100644 --- a/protocols/gossipsub/src/backoff.rs +++ b/protocols/gossipsub/src/backoff.rs @@ -19,14 +19,18 @@ // DEALINGS IN THE SOFTWARE. //! Data structure for efficiently storing known back-off's when pruning peers. -use crate::topic::TopicHash; -use instant::Instant; -use libp2p_identity::PeerId; -use std::collections::{ - hash_map::{Entry, HashMap}, - HashSet, +use std::{ + collections::{ + hash_map::{Entry, HashMap}, + HashSet, + }, + time::Duration, }; -use std::time::Duration; + +use libp2p_identity::PeerId; +use web_time::Instant; + +use crate::topic::TopicHash; #[derive(Copy, Clone)] struct HeartbeatIndex(usize); @@ -48,8 +52,7 @@ pub(crate) struct BackoffStorage { impl BackoffStorage { fn heartbeats(d: &Duration, heartbeat_interval: &Duration) -> usize { - ((d.as_nanos() + heartbeat_interval.as_nanos() - 1) / heartbeat_interval.as_nanos()) - as usize + d.as_nanos().div_ceil(heartbeat_interval.as_nanos()) as usize } pub(crate) fn new( @@ -69,8 +72,8 @@ impl BackoffStorage { } } - /// Updates the backoff for a peer (if there is already a more restrictive backoff then this call - /// doesn't change anything). + /// Updates the backoff for a peer (if there is already a more restrictive backoff then this + /// call doesn't change anything). pub(crate) fn update_backoff(&mut self, topic: &TopicHash, peer: &PeerId, time: Duration) { let instant = Instant::now() + time; let insert_into_backoffs_by_heartbeat = @@ -125,7 +128,7 @@ impl BackoffStorage { pub(crate) fn is_backoff_with_slack(&self, topic: &TopicHash, peer: &PeerId) -> bool { self.backoffs .get(topic) - .map_or(false, |m| m.contains_key(peer)) + .is_some_and(|m| m.contains_key(peer)) } pub(crate) fn get_backoff_time(&self, topic: &TopicHash, peer: &PeerId) -> Option { @@ -156,7 +159,7 @@ impl BackoffStorage { None => false, }; if !keep { - //remove from backoffs + // remove from backoffs if let Entry::Occupied(mut m) = backoffs.entry(topic.clone()) { if m.get_mut().remove(peer).is_some() && m.get().is_empty() { m.remove(); diff --git a/protocols/gossipsub/src/behaviour.rs b/protocols/gossipsub/src/behaviour.rs index 24a32de4cc7..4de314d7423 100644 --- a/protocols/gossipsub/src/behaviour.rs +++ b/protocols/gossipsub/src/behaviour.rs @@ -19,58 +19,67 @@ // DEALINGS IN THE SOFTWARE. use std::{ - cmp::{max, Ordering}, - collections::HashSet, - collections::VecDeque, - collections::{BTreeSet, HashMap}, + cmp::{max, Ordering, Ordering::Equal}, + collections::{BTreeSet, HashMap, HashSet, VecDeque}, fmt, + fmt::Debug, net::IpAddr, task::{Context, Poll}, time::Duration, }; -use futures::StreamExt; -use futures_ticker::Ticker; -use prometheus_client::registry::Registry; -use rand::{seq::SliceRandom, thread_rng}; - -use instant::Instant; -use libp2p_core::{multiaddr::Protocol::Ip4, multiaddr::Protocol::Ip6, Endpoint, Multiaddr}; -use libp2p_identity::Keypair; -use libp2p_identity::PeerId; +use futures::FutureExt; +use futures_timer::Delay; +use hashlink::LinkedHashMap; +use libp2p_core::{ + multiaddr::Protocol::{Ip4, Ip6}, + transport::PortUse, + Endpoint, Multiaddr, +}; +use libp2p_identity::{Keypair, PeerId}; use libp2p_swarm::{ behaviour::{AddressChange, ConnectionClosed, ConnectionEstablished, FromSwarm}, dial_opts::DialOpts, ConnectionDenied, ConnectionId, NetworkBehaviour, NotifyHandler, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; - -use crate::backoff::BackoffStorage; -use crate::config::{Config, ValidationMode}; -use crate::gossip_promises::GossipPromises; -use crate::handler::{Handler, HandlerEvent, HandlerIn}; -use crate::mcache::MessageCache; -use crate::metrics::{Churn, Config as MetricsConfig, Inclusion, Metrics, Penalty}; -use crate::peer_score::{PeerScore, PeerScoreParams, PeerScoreThresholds, RejectReason}; -use crate::protocol::SIGNING_PREFIX; -use crate::subscription_filter::{AllowAllSubscriptionFilter, TopicSubscriptionFilter}; -use crate::time_cache::DuplicateCache; -use crate::topic::{Hasher, Topic, TopicHash}; -use crate::transform::{DataTransform, IdentityTransform}; -use crate::types::{ - ControlAction, Message, MessageAcceptance, MessageId, PeerInfo, RawMessage, Subscription, - SubscriptionAction, -}; -use crate::types::{PeerConnections, PeerKind, RpcOut}; -use crate::{rpc_proto::proto, TopicScoreParams}; -use crate::{PublishError, SubscriptionError, ValidationError}; -use instant::SystemTime; +use prometheus_client::registry::Registry; use quick_protobuf::{MessageWrite, Writer}; -use std::{cmp::Ordering::Equal, fmt::Debug}; +use rand::{seq::SliceRandom, thread_rng}; +use web_time::{Instant, SystemTime}; + +use crate::{ + backoff::BackoffStorage, + config::{Config, ValidationMode}, + gossip_promises::GossipPromises, + handler::{Handler, HandlerEvent, HandlerIn}, + mcache::MessageCache, + metrics::{Churn, Config as MetricsConfig, Inclusion, Metrics, Penalty}, + peer_score::{PeerScore, PeerScoreParams, PeerScoreThresholds, RejectReason}, + protocol::SIGNING_PREFIX, + rpc::Sender, + rpc_proto::proto, + subscription_filter::{AllowAllSubscriptionFilter, TopicSubscriptionFilter}, + time_cache::DuplicateCache, + topic::{Hasher, Topic, TopicHash}, + transform::{DataTransform, IdentityTransform}, + types::{ + ControlAction, Graft, IDontWant, IHave, IWant, Message, MessageAcceptance, MessageId, + PeerConnections, PeerInfo, PeerKind, Prune, RawMessage, RpcOut, Subscription, + SubscriptionAction, + }, + FailedMessages, PublishError, SubscriptionError, TopicScoreParams, ValidationError, +}; #[cfg(test)] mod tests; +/// IDONTWANT cache capacity. +const IDONTWANT_CAP: usize = 10_000; + +/// IDONTWANT timeout before removal. +const IDONTWANT_TIMEOUT: Duration = Duration::new(3, 0); + /// Determines if published messages should be signed or not. /// /// Without signing, a number of privacy preserving modes can be selected. @@ -144,6 +153,13 @@ pub enum Event { }, /// A peer that does not support gossipsub has connected. GossipsubNotSupported { peer_id: PeerId }, + /// A peer is not able to download messages in time. + SlowPeer { + /// The peer_id + peer_id: PeerId, + /// The types and amounts of failed messages that are occurring for this peer. + failed_messages: FailedMessages, + }, } /// A data structure for storing configuration for publishing messages. See [`MessageAuthenticity`] @@ -204,8 +220,9 @@ impl From for PublishConfig { let public_key = keypair.public(); let key_enc = public_key.encode_protobuf(); let key = if key_enc.len() <= 42 { - // The public key can be inlined in [`rpc_proto::proto::::Message::from`], so we don't include it - // specifically in the [`rpc_proto::proto::Message::key`] field. + // The public key can be inlined in [`rpc_proto::proto::::Message::from`], so we + // don't include it specifically in the + // [`rpc_proto::proto::Message::key`] field. None } else { // Include the protobuf encoding of the public key in the message. @@ -244,9 +261,6 @@ pub struct Behaviour { /// Events that need to be yielded to the outside when polling. events: VecDeque>, - /// Pools non-urgent control messages between heartbeats. - control_pool: HashMap>, - /// Information used for publishing messages. publish_config: PublishConfig, @@ -258,12 +272,6 @@ pub struct Behaviour { /// the set of [`ConnectionId`]s. connected_peers: HashMap, - /// A map of all connected peers - A map of topic hash to a list of gossipsub peer Ids. - topic_peers: HashMap>, - - /// A map of all connected peers to their subscribed topics. - peer_topics: HashMap>, - /// A set of all explicit peers. These are peers that remain connected and we unconditionally /// forward messages to, outside of the scoring system. explicit_peers: HashSet, @@ -281,14 +289,14 @@ pub struct Behaviour { /// The last publish time for fanout topics. fanout_last_pub: HashMap, - ///Storage for backoffs + /// Storage for backoffs backoffs: BackoffStorage, /// Message cache for the last few heartbeats. mcache: MessageCache, /// Heartbeat interval stream. - heartbeat: Ticker, + heartbeat: Delay, /// Number of heartbeats since the beginning of time; this allows us to amortize some resource /// clean up -- eg backoff clean up. @@ -306,7 +314,7 @@ pub struct Behaviour { /// Stores optional peer score data together with thresholds, decay interval and gossip /// promises. - peer_score: Option<(PeerScore, PeerScoreThresholds, Ticker, GossipPromises)>, + peer_score: Option<(PeerScore, PeerScoreThresholds, Delay)>, /// Counts the number of `IHAVE` received from each peer since the last heartbeat. count_received_ihave: HashMap, @@ -314,10 +322,6 @@ pub struct Behaviour { /// Counts the number of `IWANT` that we sent the each peer since the last heartbeat. count_sent_iwant: HashMap, - /// Keeps track of IWANT messages that we are awaiting to send. - /// This is used to prevent sending duplicate IWANT messages for the same message. - pending_iwant_msgs: HashSet, - /// Short term cache for published message ids. This is used for penalizing peers sending /// our own messages back if the messages are anonymous or use a random author. published_message_ids: DuplicateCache, @@ -332,6 +336,12 @@ pub struct Behaviour { /// Keep track of a set of internal metrics relating to gossipsub. metrics: Option, + + /// Tracks the numbers of failed messages per peer-id. + failed_messages: HashMap, + + /// Tracks recently sent `IWANT` messages and checks if peers respond to them. + gossip_promises: GossipPromises, } impl Behaviour @@ -439,11 +449,8 @@ where Ok(Behaviour { metrics: metrics.map(|(registry, cfg)| Metrics::new(registry, cfg)), events: VecDeque::new(), - control_pool: HashMap::new(), publish_config: privacy.into(), duplicate_cache: DuplicateCache::new(config.duplicate_cache_time()), - topic_peers: HashMap::new(), - peer_topics: HashMap::new(), explicit_peers: HashSet::new(), blacklisted_peers: HashSet::new(), mesh: HashMap::new(), @@ -455,22 +462,20 @@ where config.backoff_slack(), ), mcache: MessageCache::new(config.history_gossip(), config.history_length()), - heartbeat: Ticker::new_with_next( - config.heartbeat_interval(), - config.heartbeat_initial_delay(), - ), + heartbeat: Delay::new(config.heartbeat_interval() + config.heartbeat_initial_delay()), heartbeat_ticks: 0, px_peers: HashSet::new(), outbound_peers: HashSet::new(), peer_score: None, count_received_ihave: HashMap::new(), count_sent_iwant: HashMap::new(), - pending_iwant_msgs: HashSet::new(), connected_peers: HashMap::new(), published_message_ids: DuplicateCache::new(config.published_message_ids_cache_time()), config, subscription_filter, data_transform, + failed_messages: Default::default(), + gossip_promises: Default::default(), }) } } @@ -500,9 +505,9 @@ where /// Lists all known peers and their associated subscribed topics. pub fn all_peers(&self) -> impl Iterator)> { - self.peer_topics + self.connected_peers .iter() - .map(|(peer_id, topic_set)| (peer_id, topic_set.iter().collect())) + .map(|(peer_id, peer)| (peer_id, peer.topics.iter().collect())) } /// Lists all known peers and their associated protocol. @@ -528,16 +533,16 @@ where return Err(SubscriptionError::NotAllowed); } - if self.mesh.get(&topic_hash).is_some() { + if self.mesh.contains_key(&topic_hash) { tracing::debug!(%topic, "Topic is already in the mesh"); return Ok(false); } // send subscription request to all peers - for peer in self.peer_topics.keys().copied().collect::>() { - tracing::debug!(%peer, "Sending SUBSCRIBE to peer"); + for peer_id in self.connected_peers.keys().copied().collect::>() { + tracing::debug!(%peer_id, "Sending SUBSCRIBE to peer"); let event = RpcOut::Subscribe(topic_hash.clone()); - self.send_message(peer, event); + self.send_message(peer_id, event); } // call JOIN(topic) @@ -549,19 +554,19 @@ where /// Unsubscribes from a topic. /// - /// Returns [`Ok(true)`] if we were subscribed to this topic. - pub fn unsubscribe(&mut self, topic: &Topic) -> Result { + /// Returns `true` if we were subscribed to this topic. + pub fn unsubscribe(&mut self, topic: &Topic) -> bool { tracing::debug!(%topic, "Unsubscribing from topic"); let topic_hash = topic.hash(); - if self.mesh.get(&topic_hash).is_none() { + if !self.mesh.contains_key(&topic_hash) { tracing::debug!(topic=%topic_hash, "Already unsubscribed from topic"); // we are not subscribed - return Ok(false); + return false; } // announce to all peers - for peer in self.peer_topics.keys().copied().collect::>() { + for peer in self.connected_peers.keys().copied().collect::>() { tracing::debug!(%peer, "Sending UNSUBSCRIBE to peer"); let event = RpcOut::Unsubscribe(topic_hash.clone()); self.send_message(peer, event); @@ -572,7 +577,7 @@ where self.leave(&topic_hash); tracing::debug!(topic=%topic_hash, "Unsubscribed from topic"); - Ok(true) + true } /// Publishes a message with multiple topics to the network. @@ -589,6 +594,11 @@ where .data_transform .outbound_transform(&topic, data.clone())?; + // check that the size doesn't exceed the max transmission size. + if transformed_data.len() > self.config.max_transmit_size() { + return Err(PublishError::MessageTooLarge); + } + let raw_message = self.build_raw_message(topic, transformed_data)?; // calculate the message id from the un-transformed data @@ -599,11 +609,6 @@ where topic: raw_message.topic.clone(), }); - // check that the size doesn't exceed the max transmission size - if raw_message.raw_protobuf_len() > self.config.max_transmit_size() { - return Err(PublishError::MessageTooLarge); - } - // Check the if the message has been published before if self.duplicate_cache.contains(&msg_id) { // This message has already been seen. We don't re-publish messages that have already @@ -619,84 +624,103 @@ where let topic_hash = raw_message.topic.clone(); + let mut peers_on_topic = self + .connected_peers + .iter() + .filter(|(_, p)| p.topics.contains(&topic_hash)) + .map(|(peer_id, _)| peer_id) + .peekable(); + + if peers_on_topic.peek().is_none() { + return Err(PublishError::InsufficientPeers); + } + let mut recipient_peers = HashSet::new(); - if let Some(set) = self.topic_peers.get(&topic_hash) { - if self.config.flood_publish() { - // Forward to all peers above score and all explicit peers - recipient_peers.extend(set.iter().filter(|p| { - self.explicit_peers.contains(*p) - || !self.score_below_threshold(p, |ts| ts.publish_threshold).0 - })); - } else { - match self.mesh.get(&raw_message.topic) { - // Mesh peers - Some(mesh_peers) => { - recipient_peers.extend(mesh_peers); + if self.config.flood_publish() { + // Forward to all peers above score and all explicit peers + recipient_peers.extend(peers_on_topic.filter(|p| { + self.explicit_peers.contains(*p) + || !self.score_below_threshold(p, |ts| ts.publish_threshold).0 + })); + } else { + match self.mesh.get(&topic_hash) { + // Mesh peers + Some(mesh_peers) => { + // We have a mesh set. We want to make sure to publish to at least `mesh_n` + // peers (if possible). + let needed_extra_peers = self.config.mesh_n().saturating_sub(mesh_peers.len()); + + if needed_extra_peers > 0 { + // We don't have `mesh_n` peers in our mesh, we will randomly select extras + // and publish to them. + + // Get a random set of peers that are appropriate to send messages too. + let peer_list = get_random_peers( + &self.connected_peers, + &topic_hash, + needed_extra_peers, + |peer| { + !mesh_peers.contains(peer) + && !self.explicit_peers.contains(peer) + && !self + .score_below_threshold(peer, |pst| pst.publish_threshold) + .0 + }, + ); + recipient_peers.extend(peer_list); } - // Gossipsub peers - None => { - tracing::debug!(topic=%topic_hash, "Topic not in the mesh"); - // If we have fanout peers add them to the map. - if self.fanout.contains_key(&topic_hash) { - for peer in self.fanout.get(&topic_hash).expect("Topic must exist") { - recipient_peers.insert(*peer); - } - } else { - // We have no fanout peers, select mesh_n of them and add them to the fanout - let mesh_n = self.config.mesh_n(); - let new_peers = get_random_peers( - &self.topic_peers, - &self.connected_peers, - &topic_hash, - mesh_n, - { - |p| { - !self.explicit_peers.contains(p) - && !self - .score_below_threshold(p, |pst| { - pst.publish_threshold - }) - .0 - } - }, - ); - // Add the new peers to the fanout and recipient peers - self.fanout.insert(topic_hash.clone(), new_peers.clone()); - for peer in new_peers { - tracing::debug!(%peer, "Peer added to fanout"); - recipient_peers.insert(peer); - } + + recipient_peers.extend(mesh_peers); + } + // Gossipsub peers + None => { + tracing::debug!(topic=%topic_hash, "Topic not in the mesh"); + // If we have fanout peers add them to the map. + if self.fanout.contains_key(&topic_hash) { + for peer in self.fanout.get(&topic_hash).expect("Topic must exist") { + recipient_peers.insert(*peer); + } + } else { + // We have no fanout peers, select mesh_n of them and add them to the fanout + let mesh_n = self.config.mesh_n(); + let new_peers = + get_random_peers(&self.connected_peers, &topic_hash, mesh_n, { + |p| { + !self.explicit_peers.contains(p) + && !self + .score_below_threshold(p, |pst| pst.publish_threshold) + .0 + } + }); + // Add the new peers to the fanout and recipient peers + self.fanout.insert(topic_hash.clone(), new_peers.clone()); + for peer in new_peers { + tracing::debug!(%peer, "Peer added to fanout"); + recipient_peers.insert(peer); } - // We are publishing to fanout peers - update the time we published - self.fanout_last_pub - .insert(topic_hash.clone(), Instant::now()); } + // We are publishing to fanout peers - update the time we published + self.fanout_last_pub + .insert(topic_hash.clone(), Instant::now()); } + } - // Explicit peers - for peer in &self.explicit_peers { - if set.contains(peer) { - recipient_peers.insert(*peer); - } - } + // Explicit peers that are part of the topic + recipient_peers + .extend(peers_on_topic.filter(|peer_id| self.explicit_peers.contains(peer_id))); - // Floodsub peers - for (peer, connections) in &self.connected_peers { - if connections.kind == PeerKind::Floodsub - && !self - .score_below_threshold(peer, |ts| ts.publish_threshold) - .0 - { - recipient_peers.insert(*peer); - } + // Floodsub peers + for (peer, connections) in &self.connected_peers { + if connections.kind == PeerKind::Floodsub + && !self + .score_below_threshold(peer, |ts| ts.publish_threshold) + .0 + { + recipient_peers.insert(*peer); } } } - if recipient_peers.is_empty() { - return Err(PublishError::InsufficientPeers); - } - // If the message isn't a duplicate and we have sent it to some peers add it to the // duplicate cache and memcache. self.duplicate_cache.insert(msg_id.clone()); @@ -711,9 +735,26 @@ where } // Send to peers we know are subscribed to the topic. + let mut publish_failed = true; for peer_id in recipient_peers.iter() { tracing::trace!(peer=%peer_id, "Sending message to peer"); - self.send_message(*peer_id, RpcOut::Publish(raw_message.clone())); + if self.send_message( + *peer_id, + RpcOut::Publish { + message: raw_message.clone(), + timeout: Delay::new(self.config.publish_queue_duration()), + }, + ) { + publish_failed = false + } + } + + if recipient_peers.is_empty() { + return Err(PublishError::InsufficientPeers); + } + + if publish_failed { + return Err(PublishError::AllQueuesFull(recipient_peers.len())); } tracing::debug!(message=%msg_id, "Published message"); @@ -749,7 +790,7 @@ where msg_id: &MessageId, propagation_source: &PeerId, acceptance: MessageAcceptance, - ) -> Result { + ) -> bool { let reject_reason = match acceptance { MessageAcceptance::Accept => { let (raw_message, originating_peers) = match self.mcache.validate(msg_id) { @@ -764,7 +805,7 @@ where if let Some(metrics) = self.metrics.as_mut() { metrics.memcache_miss(); } - return Ok(false); + return false; } }; @@ -777,8 +818,8 @@ where raw_message, Some(propagation_source), originating_peers, - )?; - return Ok(true); + ); + return true; } MessageAcceptance::Reject => RejectReason::ValidationFailed, MessageAcceptance::Ignore => RejectReason::ValidationIgnored, @@ -802,10 +843,10 @@ where peer_score.reject_message(peer, msg_id, &raw_message.topic, reject_reason); } } - Ok(true) + true } else { tracing::warn!(message=%msg_id, "Rejected message not in cache"); - Ok(false) + false } } @@ -866,9 +907,9 @@ where return Err("Peer score set twice".into()); } - let interval = Ticker::new(params.decay_interval); + let interval = Delay::new(params.decay_interval); let peer_score = PeerScore::new_with_message_delivery_time_callback(params, callback); - self.peer_score = Some((peer_score, threshold, interval, GossipPromises::default())); + self.peer_score = Some((peer_score, threshold, interval)); Ok(()) } @@ -934,7 +975,7 @@ where && !self.backoffs.is_backoff_with_slack(topic_hash, p) }); - // Add up to mesh_n of them them to the mesh + // Add up to mesh_n of them to the mesh // NOTE: These aren't randomly added, currently FIFO let add_peers = std::cmp::min(peers.len(), self.config.mesh_n()); tracing::debug!( @@ -962,7 +1003,6 @@ where if added_peers.len() < self.config.mesh_n() { // get the peers let new_peers = get_random_peers( - &self.topic_peers, &self.connected_peers, topic_hash, self.config.mesh_n() - added_peers.len(), @@ -994,12 +1034,11 @@ where if let Some((peer_score, ..)) = &mut self.peer_score { peer_score.graft(&peer_id, topic_hash.clone()); } - Self::control_pool_add( - &mut self.control_pool, + self.send_message( peer_id, - ControlAction::Graft { + RpcOut::Graft(Graft { topic_hash: topic_hash.clone(), - }, + }), ); // If the peer did not previously exist in any mesh, inform the handler @@ -1007,7 +1046,6 @@ where peer_id, vec![topic_hash], &self.mesh, - self.peer_topics.get(&peer_id), &mut self.events, &self.connected_peers, ); @@ -1028,7 +1066,7 @@ where peer: &PeerId, do_px: bool, on_unsubscribe: bool, - ) -> ControlAction { + ) -> Prune { if let Some((peer_score, ..)) = &mut self.peer_score { peer_score.prune(peer, topic_hash.clone()); } @@ -1039,7 +1077,7 @@ where } Some(PeerKind::Gossipsub) => { // GossipSub v1.0 -- no peer exchange, the peer won't be able to parse it anyway - return ControlAction::Prune { + return Prune { topic_hash: topic_hash.clone(), peers: Vec::new(), backoff: None, @@ -1054,7 +1092,6 @@ where // Select peers for peer exchange let peers = if do_px { get_random_peers( - &self.topic_peers, &self.connected_peers, topic_hash, self.config.prune_peers(), @@ -1076,7 +1113,7 @@ where // update backoff self.backoffs.update_backoff(topic_hash, peer, backoff); - ControlAction::Prune { + Prune { topic_hash: topic_hash.clone(), peers, backoff: Some(backoff.as_secs()), @@ -1092,20 +1129,20 @@ where if let Some(m) = self.metrics.as_mut() { m.left(topic_hash) } - for peer in peers { + for peer_id in peers { // Send a PRUNE control message - tracing::debug!(%peer, "LEAVE: Sending PRUNE to peer"); + tracing::debug!(%peer_id, "LEAVE: Sending PRUNE to peer"); + let on_unsubscribe = true; - let control = - self.make_prune(topic_hash, &peer, self.config.do_px(), on_unsubscribe); - Self::control_pool_add(&mut self.control_pool, peer, control); + let prune = + self.make_prune(topic_hash, &peer_id, self.config.do_px(), on_unsubscribe); + self.send_message(peer_id, RpcOut::Prune(prune)); // If the peer did not previously exist in any mesh, inform the handler peer_removed_from_mesh( - peer, + peer_id, topic_hash, &self.mesh, - self.peer_topics.get(&peer), &mut self.events, &self.connected_peers, ); @@ -1116,7 +1153,7 @@ where /// Checks if the given peer is still connected and if not dials the peer again. fn check_explicit_peer_connection(&mut self, peer_id: &PeerId) { - if !self.peer_topics.contains_key(peer_id) { + if !self.connected_peers.contains_key(peer_id) { // Connect to peer tracing::debug!(peer=%peer_id, "Connecting to explicit peer"); self.events.push_back(ToSwarm::Dial { @@ -1136,7 +1173,7 @@ where } fn score_below_threshold_from_scores( - peer_score: &Option<(PeerScore, PeerScoreThresholds, Ticker, GossipPromises)>, + peer_score: &Option<(PeerScore, PeerScoreThresholds, Delay)>, peer_id: &PeerId, threshold: impl Fn(&PeerScoreThresholds) -> f64, ) -> (bool, f64) { @@ -1197,14 +1234,7 @@ where return false; } - if self.pending_iwant_msgs.contains(id) { - return false; - } - - self.peer_score - .as_ref() - .map(|(_, _, _, promises)| !promises.contains(id)) - .unwrap_or(true) + !self.gossip_promises.contains(id) }; for (topic, ids) in ihave_msgs { @@ -1251,30 +1281,22 @@ where iwant_ids_vec.truncate(iask); *iasked += iask; - for message_id in &iwant_ids_vec { - // Add all messages to the pending list - self.pending_iwant_msgs.insert(message_id.clone()); - } - - if let Some((_, _, _, gossip_promises)) = &mut self.peer_score { - gossip_promises.add_promise( - *peer_id, - &iwant_ids_vec, - Instant::now() + self.config.iwant_followup_time(), - ); - } + self.gossip_promises.add_promise( + *peer_id, + &iwant_ids_vec, + Instant::now() + self.config.iwant_followup_time(), + ); tracing::trace!( peer=%peer_id, "IHAVE: Asking for the following messages from peer: {:?}", iwant_ids_vec ); - Self::control_pool_add( - &mut self.control_pool, + self.send_message( *peer_id, - ControlAction::IWant { + RpcOut::IWant(IWant { message_ids: iwant_ids_vec, - }, + }), ); } tracing::trace!(peer=%peer_id, "Completed IHAVE handling for peer"); @@ -1297,7 +1319,7 @@ where for id in iwant_msgs { // If we have it and the IHAVE count is not above the threshold, - // foward the message. + // forward the message. if let Some((msg, count)) = self .mcache .get_with_iwant_counts(&id, peer_id) @@ -1311,7 +1333,13 @@ where ); } else { tracing::debug!(peer=%peer_id, "IWANT: Sending cached messages to peer"); - self.send_message(*peer_id, RpcOut::Forward(msg)); + self.send_message( + *peer_id, + RpcOut::Forward { + message: msg, + timeout: Delay::new(self.config.forward_queue_duration()), + }, + ); } } } @@ -1327,17 +1355,19 @@ where let mut do_px = self.config.do_px(); + let Some(connected_peer) = self.connected_peers.get_mut(peer_id) else { + tracing::error!(peer_id = %peer_id, "Peer non-existent when handling graft"); + return; + }; + // For each topic, if a peer has grafted us, then we necessarily must be in their mesh // and they must be subscribed to the topic. Ensure we have recorded the mapping. for topic in &topics { - self.peer_topics - .entry(*peer_id) - .or_default() - .insert(topic.clone()); - self.topic_peers - .entry(topic.clone()) - .or_default() - .insert(*peer_id); + if connected_peer.topics.insert(topic.clone()) { + if let Some(m) = self.metrics.as_mut() { + m.inc_topic_peers(topic); + } + } } // we don't GRAFT to/from explicit peers; complain loudly if this happens @@ -1384,7 +1414,7 @@ where + self.config.graft_flood_threshold()) - self.config.prune_backoff(); if flood_cutoff > now { - //extra penalty + // extra penalty peer_score.add_penalty(peer_id, 1); } } @@ -1405,15 +1435,16 @@ where topic=%topic_hash, "GRAFT: ignoring peer with negative score" ); - // we do send them PRUNE however, because it's a matter of protocol correctness + // we do send them PRUNE however, because it's a matter of protocol + // correctness to_prune_topics.insert(topic_hash.clone()); // but we won't PX to them do_px = false; continue; } - // check mesh upper bound and only allow graft if the upper bound is not reached or - // if it is an outbound peer + // check mesh upper bound and only allow graft if the upper bound is not reached + // or if it is an outbound peer if peers.len() >= self.config.mesh_n_high() && !self.outbound_peers.contains(peer_id) { @@ -1439,7 +1470,6 @@ where *peer_id, vec![&topic_hash], &self.mesh, - self.peer_topics.get(peer_id), &mut self.events, &self.connected_peers, ); @@ -1464,12 +1494,13 @@ where if !to_prune_topics.is_empty() { // build the prune messages to send let on_unsubscribe = false; - for action in to_prune_topics + + for prune in to_prune_topics .iter() .map(|t| self.make_prune(t, peer_id, do_px, on_unsubscribe)) .collect::>() { - self.send_message(*peer_id, RpcOut::Control(action)); + self.send_message(*peer_id, RpcOut::Prune(prune)); } // Send the prune messages to the peer tracing::debug!( @@ -1512,7 +1543,6 @@ where *peer_id, topic_hash, &self.mesh, - self.peer_topics.get(peer_id), &mut self.events, &self.connected_peers, ); @@ -1542,7 +1572,7 @@ where self.remove_peer_from_mesh(peer_id, &topic_hash, backoff, true, Churn::Prune); if self.mesh.contains_key(&topic_hash) { - //connect to px peers + // connect to px peers if !px.is_empty() { // we ignore PX from peers with insufficient score if below_threshold { @@ -1574,7 +1604,7 @@ where let n = self.config.prune_peers(); // Ignore peerInfo with no ID // - //TODO: Once signed records are spec'd: Can we use peerInfo without any IDs if they have a + // TODO: Once signed records are spec'd: Can we use peerInfo without any IDs if they have a // signed peer record? px.retain(|p| p.peer_id.is_some()); if px.len() > n { @@ -1619,14 +1649,15 @@ where peer=%propagation_source, "Rejecting message from blacklisted peer" ); - if let Some((peer_score, .., gossip_promises)) = &mut self.peer_score { + self.gossip_promises + .reject_message(msg_id, &RejectReason::BlackListedPeer); + if let Some((peer_score, ..)) = &mut self.peer_score { peer_score.reject_message( propagation_source, msg_id, &raw_message.topic, RejectReason::BlackListedPeer, ); - gossip_promises.reject_message(msg_id, &RejectReason::BlackListedPeer); } return false; } @@ -1659,7 +1690,7 @@ where let self_published = !self.config.allow_self_origin() && if let Some(own_id) = self.publish_config.get_own_id() { own_id != propagation_source - && raw_message.source.as_ref().map_or(false, |s| s == own_id) + && raw_message.source.as_ref().is_some_and(|s| s == own_id) } else { self.published_message_ids.contains(msg_id) }; @@ -1708,6 +1739,11 @@ where // Calculate the message id on the transformed data. let msg_id = self.config.message_id(&message); + // Broadcast IDONTWANT messages + if raw_message.raw_protobuf_len() > self.config.idontwant_message_size_threshold() { + self.send_idontwant(&raw_message, &msg_id, propagation_source); + } + // Check the validity of the message // Peers get penalized if this message is invalid. We don't add it to the duplicate cache // and instead continually penalize peers that repeatedly send this message. @@ -1723,6 +1759,7 @@ where self.mcache.observe_duplicate(&msg_id, propagation_source); return; } + tracing::debug!( message=%msg_id, "Put message in duplicate_cache and resolve promises" @@ -1735,9 +1772,11 @@ where // Tells score that message arrived (but is maybe not fully validated yet). // Consider the message as delivered for gossip promises. - if let Some((peer_score, .., gossip_promises)) = &mut self.peer_score { + self.gossip_promises.message_delivered(&msg_id); + + // Tells score that message arrived (but is maybe not fully validated yet). + if let Some((peer_score, ..)) = &mut self.peer_score { peer_score.validate_message(propagation_source, &msg_id, &message.topic); - gossip_promises.message_delivered(&msg_id); } // Add the message to our memcache @@ -1762,17 +1801,12 @@ where // forward the message to mesh peers, if no validation is required if !self.config.validate_messages() { - if self - .forward_msg( - &msg_id, - raw_message, - Some(propagation_source), - HashSet::new(), - ) - .is_err() - { - tracing::error!("Failed to forward message. Too large"); - } + self.forward_msg( + &msg_id, + raw_message, + Some(propagation_source), + HashSet::new(), + ); tracing::debug!(message=%msg_id, "Completed message handling for message"); } } @@ -1784,12 +1818,14 @@ where raw_message: &RawMessage, reject_reason: RejectReason, ) { - if let Some((peer_score, .., gossip_promises)) = &mut self.peer_score { - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_invalid_message(&raw_message.topic); - } + if let Some(metrics) = self.metrics.as_mut() { + metrics.register_invalid_message(&raw_message.topic); + } - if let Ok(message) = self.data_transform.inbound_transform(raw_message.clone()) { + let message = self.data_transform.inbound_transform(raw_message.clone()); + + match (&mut self.peer_score, message) { + (Some((peer_score, ..)), Ok(message)) => { let message_id = self.config.message_id(&message); peer_score.reject_message( @@ -1799,13 +1835,22 @@ where reject_reason, ); - gossip_promises.reject_message(&message_id, &reject_reason); - } else { + self.gossip_promises + .reject_message(&message_id, &reject_reason); + } + (Some((peer_score, ..)), Err(_)) => { // The message is invalid, we reject it ignoring any gossip promises. If a peer is // advertising this message via an IHAVE and it's invalid it will be double // penalized, one for sending us an invalid and again for breaking a promise. peer_score.reject_invalid_message(propagation_source, &raw_message.topic); } + (None, Ok(message)) => { + // Valid transformation without peer scoring + let message_id = self.config.message_id(&message); + self.gossip_promises + .reject_message(&message_id, &reject_reason); + } + (None, Err(_)) => {} } } @@ -1823,7 +1868,7 @@ where let mut unsubscribed_peers = Vec::new(); - let Some(subscribed_topics) = self.peer_topics.get_mut(propagation_source) else { + let Some(peer) = self.connected_peers.get_mut(propagation_source) else { tracing::error!( peer=%propagation_source, "Subscription by unknown peer" @@ -1839,7 +1884,7 @@ where let filtered_topics = match self .subscription_filter - .filter_incoming_subscriptions(subscriptions, subscribed_topics) + .filter_incoming_subscriptions(subscriptions, &peer.topics) { Ok(topics) => topics, Err(s) => { @@ -1855,29 +1900,24 @@ where for subscription in filtered_topics { // get the peers from the mapping, or insert empty lists if the topic doesn't exist let topic_hash = &subscription.topic_hash; - let peer_list = self.topic_peers.entry(topic_hash.clone()).or_default(); match subscription.action { SubscriptionAction::Subscribe => { - if peer_list.insert(*propagation_source) { + if peer.topics.insert(topic_hash.clone()) { tracing::debug!( peer=%propagation_source, topic=%topic_hash, "SUBSCRIPTION: Adding gossip peer to topic" ); - } - // add to the peer_topics mapping - subscribed_topics.insert(topic_hash.clone()); + if let Some(m) = self.metrics.as_mut() { + m.inc_topic_peers(topic_hash); + } + } // if the mesh needs peers add the peer to the mesh if !self.explicit_peers.contains(propagation_source) - && matches!( - self.connected_peers - .get(propagation_source) - .map(|v| &v.kind), - Some(PeerKind::Gossipsubv1_1) | Some(PeerKind::Gossipsub) - ) + && peer.kind.is_gossipsub() && !Self::score_below_threshold_from_scores( &self.peer_score, propagation_source, @@ -1920,16 +1960,18 @@ where })); } SubscriptionAction::Unsubscribe => { - if peer_list.remove(propagation_source) { + if peer.topics.remove(topic_hash) { tracing::debug!( peer=%propagation_source, topic=%topic_hash, "SUBSCRIPTION: Removing gossip peer from topic" ); + + if let Some(m) = self.metrics.as_mut() { + m.dec_topic_peers(topic_hash); + } } - // remove topic from the peer_topics mapping - subscribed_topics.remove(topic_hash); unsubscribed_peers.push((*propagation_source, topic_hash.clone())); // generate an unsubscribe event to be polled application_event.push(ToSwarm::GenerateEvent(Event::Unsubscribed { @@ -1938,14 +1980,13 @@ where })); } } - - if let Some(m) = self.metrics.as_mut() { - m.set_topic_peers(topic_hash, peer_list.len()); - } } - // remove unsubscribed peers from the mesh if it exists + // remove unsubscribed peers from the mesh and fanout if they exist there. for (peer_id, topic_hash) in unsubscribed_peers { + self.fanout + .get_mut(&topic_hash) + .map(|peers| peers.remove(&peer_id)); self.remove_peer_from_mesh(&peer_id, &topic_hash, None, false, Churn::Unsub); } @@ -1956,7 +1997,6 @@ where *propagation_source, topics_joined, &self.mesh, - self.peer_topics.get(propagation_source), &mut self.events, &self.connected_peers, ); @@ -1964,12 +2004,8 @@ where // If we need to send grafts to peer, do so immediately, rather than waiting for the // heartbeat. - for action in topics_to_graft - .into_iter() - .map(|topic_hash| ControlAction::Graft { topic_hash }) - .collect::>() - { - self.send_message(*propagation_source, RpcOut::Control(action)) + for topic_hash in topics_to_graft.into_iter() { + self.send_message(*propagation_source, RpcOut::Graft(Graft { topic_hash })); } // Notify the application of the subscriptions @@ -1985,8 +2021,8 @@ where /// Applies penalties to peers that did not respond to our IWANT requests. fn apply_iwant_penalties(&mut self) { - if let Some((peer_score, .., gossip_promises)) = &mut self.peer_score { - for (peer, count) in gossip_promises.get_broken_promises() { + if let Some((peer_score, ..)) = &mut self.peer_score { + for (peer, count) in self.gossip_promises.get_broken_promises() { peer_score.add_penalty(&peer, count); if let Some(metrics) = self.metrics.as_mut() { metrics.register_score_penalty(Penalty::BrokenPromise); @@ -2000,6 +2036,16 @@ where tracing::debug!("Starting heartbeat"); let start = Instant::now(); + // Every heartbeat we sample the send queues to add to our metrics. We do this intentionally + // before we add all the gossip from this heartbeat in order to gain a true measure of + // steady-state size of the queues. + if let Some(m) = &mut self.metrics { + for sender_queue in self.connected_peers.values().map(|v| &v.sender) { + m.observe_priority_queue_size(sender_queue.priority_queue_len()); + m.observe_non_priority_queue_size(sender_queue.non_priority_queue_len()); + } + } + self.heartbeat_ticks += 1; let mut to_graft = HashMap::new(); @@ -2037,7 +2083,6 @@ where for (topic_hash, peers) in self.mesh.iter_mut() { let explicit_peers = &self.explicit_peers; let backoffs = &self.backoffs; - let topic_peers = &self.topic_peers; let outbound_peers = &self.outbound_peers; // drop all peers with negative score, without PX @@ -2085,18 +2130,13 @@ where ); // not enough peers - get mesh_n - current_length more let desired_peers = self.config.mesh_n() - peers.len(); - let peer_list = get_random_peers( - topic_peers, - &self.connected_peers, - topic_hash, - desired_peers, - |peer| { + let peer_list = + get_random_peers(&self.connected_peers, topic_hash, desired_peers, |peer| { !peers.contains(peer) && !explicit_peers.contains(peer) && !backoffs.is_backoff_with_slack(topic_hash, peer) && *scores.get(peer).unwrap_or(&0.0) >= 0.0 - }, - ); + }); for peer in &peer_list { let current_topic = to_graft.entry(*peer).or_insert_with(Vec::new); current_topic.push(topic_hash.clone()); @@ -2152,10 +2192,9 @@ where if outbound <= self.config.mesh_outbound_min() { // do not remove anymore outbound peers continue; - } else { - // an outbound peer gets removed - outbound -= 1; } + // an outbound peer gets removed + outbound -= 1; } // remove the peer @@ -2178,19 +2217,14 @@ where // if we have not enough outbound peers, graft to some new outbound peers if outbound < self.config.mesh_outbound_min() { let needed = self.config.mesh_outbound_min() - outbound; - let peer_list = get_random_peers( - topic_peers, - &self.connected_peers, - topic_hash, - needed, - |peer| { + let peer_list = + get_random_peers(&self.connected_peers, topic_hash, needed, |peer| { !peers.contains(peer) && !explicit_peers.contains(peer) && !backoffs.is_backoff_with_slack(topic_hash, peer) && *scores.get(peer).unwrap_or(&0.0) >= 0.0 && outbound_peers.contains(peer) - }, - ); + }); for peer in &peer_list { let current_topic = to_graft.entry(*peer).or_insert_with(Vec::new); current_topic.push(topic_hash.clone()); @@ -2209,7 +2243,7 @@ where && peers.len() > 1 && self.peer_score.is_some() { - if let Some((_, thresholds, _, _)) = &self.peer_score { + if let Some((_, thresholds, _)) = &self.peer_score { // Opportunistic grafting works as follows: we check the median score of peers // in the mesh; if this score is below the opportunisticGraftThreshold, we // select a few peers at random with score over the median. @@ -2247,7 +2281,6 @@ where // GRAFT if median < thresholds.opportunistic_graft_threshold { let peer_list = get_random_peers( - topic_peers, &self.connected_peers, topic_hash, self.config.opportunistic_graft_peers(), @@ -2303,25 +2336,25 @@ where for (topic_hash, peers) in self.fanout.iter_mut() { let mut to_remove_peers = Vec::new(); let publish_threshold = match &self.peer_score { - Some((_, thresholds, _, _)) => thresholds.publish_threshold, + Some((_, thresholds, _)) => thresholds.publish_threshold, _ => 0.0, }; - for peer in peers.iter() { + for peer_id in peers.iter() { // is the peer still subscribed to the topic? - let peer_score = *scores.get(peer).unwrap_or(&0.0); - match self.peer_topics.get(peer) { - Some(topics) => { - if !topics.contains(topic_hash) || peer_score < publish_threshold { + let peer_score = *scores.get(peer_id).unwrap_or(&0.0); + match self.connected_peers.get(peer_id) { + Some(peer) => { + if !peer.topics.contains(topic_hash) || peer_score < publish_threshold { tracing::debug!( topic=%topic_hash, "HEARTBEAT: Peer removed from fanout for topic" ); - to_remove_peers.push(*peer); + to_remove_peers.push(*peer_id); } } None => { // remove if the peer has disconnected - to_remove_peers.push(*peer); + to_remove_peers.push(*peer_id); } } } @@ -2338,17 +2371,12 @@ where ); let needed_peers = self.config.mesh_n() - peers.len(); let explicit_peers = &self.explicit_peers; - let new_peers = get_random_peers( - &self.topic_peers, - &self.connected_peers, - topic_hash, - needed_peers, - |peer_id| { + let new_peers = + get_random_peers(&self.connected_peers, topic_hash, needed_peers, |peer_id| { !peers.contains(peer_id) && !explicit_peers.contains(peer_id) && *scores.get(peer_id).unwrap_or(&0.0) < publish_threshold - }, - ); + }); peers.extend(new_peers); } } @@ -2387,12 +2415,31 @@ where self.send_graft_prune(to_graft, to_prune, no_px); } - // piggyback pooled control messages - self.flush_control_pool(); - // shift the memcache self.mcache.shift(); + // Report expired messages + for (peer_id, failed_messages) in self.failed_messages.drain() { + tracing::debug!("Peer couldn't consume messages: {:?}", failed_messages); + self.events + .push_back(ToSwarm::GenerateEvent(Event::SlowPeer { + peer_id, + failed_messages, + })); + } + self.failed_messages.shrink_to_fit(); + + // Flush stale IDONTWANTs. + for peer in self.connected_peers.values_mut() { + while let Some((_front, instant)) = peer.dont_send.front() { + if (*instant + IDONTWANT_TIMEOUT) >= Instant::now() { + break; + } else { + peer.dont_send.pop_front(); + } + } + } + tracing::debug!("Completed Heartbeat"); if let Some(metrics) = self.metrics.as_mut() { let duration = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX); @@ -2404,6 +2451,7 @@ where /// and fanout peers fn emit_gossip(&mut self) { let mut rng = thread_rng(); + let mut messages = Vec::new(); for (topic_hash, peers) in self.mesh.iter().chain(self.fanout.iter()) { let mut message_ids = self.mcache.get_gossip_message_ids(topic_hash); if message_ids.is_empty() { @@ -2430,21 +2478,16 @@ where ) }; // get gossip_lazy random peers - let to_msg_peers = get_random_peers_dynamic( - &self.topic_peers, - &self.connected_peers, - topic_hash, - n_map, - |peer| { + let to_msg_peers = + get_random_peers_dynamic(&self.connected_peers, topic_hash, n_map, |peer| { !peers.contains(peer) && !self.explicit_peers.contains(peer) && !self.score_below_threshold(peer, |ts| ts.gossip_threshold).0 - }, - ); + }); tracing::debug!("Gossiping IHAVE to {} peers", to_msg_peers.len()); - for peer in to_msg_peers { + for peer_id in to_msg_peers { let mut peer_message_ids = message_ids.clone(); if peer_message_ids.len() > self.config.max_ihave_length() { @@ -2456,16 +2499,18 @@ where } // send an IHAVE message - Self::control_pool_add( - &mut self.control_pool, - peer, - ControlAction::IHave { + messages.push(( + peer_id, + RpcOut::IHave(IHave { topic_hash: topic_hash.clone(), message_ids: peer_message_ids, - }, - ); + }), + )); } } + for (peer_id, message) in messages { + self.send_message(peer_id, message); + } } /// Handles multiple GRAFT/PRUNE messages and coalesces them into chunked gossip control @@ -2477,26 +2522,27 @@ where no_px: HashSet, ) { // handle the grafts and overlapping prunes per peer - for (peer, topics) in to_graft.into_iter() { + for (peer_id, topics) in to_graft.into_iter() { for topic in &topics { // inform scoring of graft if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.graft(&peer, topic.clone()); + peer_score.graft(&peer_id, topic.clone()); } // inform the handler of the peer being added to the mesh // If the peer did not previously exist in any mesh, inform the handler peer_added_to_mesh( - peer, + peer_id, vec![topic], &self.mesh, - self.peer_topics.get(&peer), &mut self.events, &self.connected_peers, ); } - let control_msgs = topics.iter().map(|topic_hash| ControlAction::Graft { - topic_hash: topic_hash.clone(), + let rpc_msgs = topics.iter().map(|topic_hash| { + RpcOut::Graft(Graft { + topic_hash: topic_hash.clone(), + }) }); // If there are prunes associated with the same peer add them. @@ -2505,43 +2551,43 @@ where // of its removal from another. // The following prunes are not due to unsubscribing. - let prunes = to_prune - .remove(&peer) + let prune_msgs = to_prune + .remove(&peer_id) .into_iter() .flatten() .map(|topic_hash| { - self.make_prune( + let prune = self.make_prune( &topic_hash, - &peer, - self.config.do_px() && !no_px.contains(&peer), + &peer_id, + self.config.do_px() && !no_px.contains(&peer_id), false, - ) + ); + RpcOut::Prune(prune) }); - // send the control messages - for msg in control_msgs.chain(prunes).collect::>() { - self.send_message(peer, RpcOut::Control(msg)); + // send the rpc messages + for msg in rpc_msgs.chain(prune_msgs).collect::>() { + self.send_message(peer_id, msg); } } // handle the remaining prunes // The following prunes are not due to unsubscribing. - for (peer, topics) in to_prune.iter() { + for (peer_id, topics) in to_prune.iter() { for topic_hash in topics { let prune = self.make_prune( topic_hash, - peer, - self.config.do_px() && !no_px.contains(peer), + peer_id, + self.config.do_px() && !no_px.contains(peer_id), false, ); - self.send_message(*peer, RpcOut::Control(prune)); + self.send_message(*peer_id, RpcOut::Prune(prune)); // inform the handler peer_removed_from_mesh( - *peer, + *peer_id, topic_hash, &self.mesh, - self.peer_topics.get(peer), &mut self.events, &self.connected_peers, ); @@ -2549,6 +2595,49 @@ where } } + /// Helper function which sends an IDONTWANT message to mesh\[topic\] peers. + fn send_idontwant( + &mut self, + message: &RawMessage, + msg_id: &MessageId, + propagation_source: &PeerId, + ) { + let Some(mesh_peers) = self.mesh.get(&message.topic) else { + return; + }; + + let iwant_peers = self.gossip_promises.peers_for_message(msg_id); + + let recipient_peers: Vec = mesh_peers + .iter() + .chain(iwant_peers.iter()) + .filter(|peer_id| { + *peer_id != propagation_source && Some(*peer_id) != message.source.as_ref() + }) + .cloned() + .collect(); + + for peer_id in recipient_peers { + let Some(peer) = self.connected_peers.get_mut(&peer_id) else { + tracing::error!(peer = %peer_id, + "Could not IDONTWANT, peer doesn't exist in connected peer list"); + continue; + }; + + // Only gossipsub 1.2 peers support IDONTWANT. + if peer.kind != PeerKind::Gossipsubv1_2 { + continue; + } + + self.send_message( + peer_id, + RpcOut::IDontWant(IDontWant { + message_ids: vec![msg_id.clone()], + }), + ); + } + } + /// Helper function which forwards a message to mesh\[topic\] peers. /// /// Returns true if at least one peer was messaged. @@ -2558,7 +2647,7 @@ where message: RawMessage, propagation_source: Option<&PeerId>, originating_peers: HashSet, - ) -> Result { + ) -> bool { // message is fully validated inform peer_score if let Some((peer_score, ..)) = &mut self.peer_score { if let Some(peer) = propagation_source { @@ -2569,50 +2658,61 @@ where tracing::debug!(message=%msg_id, "Forwarding message"); let mut recipient_peers = HashSet::new(); - { - // Populate the recipient peers mapping - - // Add explicit peers - for peer_id in &self.explicit_peers { - if let Some(topics) = self.peer_topics.get(peer_id) { - if Some(peer_id) != propagation_source - && !originating_peers.contains(peer_id) - && Some(peer_id) != message.source.as_ref() - && topics.contains(&message.topic) - { - recipient_peers.insert(*peer_id); - } - } + // Populate the recipient peers mapping + + // Add explicit peers + for peer_id in &self.explicit_peers { + let Some(peer) = self.connected_peers.get(peer_id) else { + continue; + }; + if Some(peer_id) != propagation_source + && !originating_peers.contains(peer_id) + && Some(peer_id) != message.source.as_ref() + && peer.topics.contains(&message.topic) + { + recipient_peers.insert(*peer_id); } + } - // add mesh peers - let topic = &message.topic; - // mesh - if let Some(mesh_peers) = self.mesh.get(topic) { - for peer_id in mesh_peers { - if Some(peer_id) != propagation_source - && !originating_peers.contains(peer_id) - && Some(peer_id) != message.source.as_ref() - { - recipient_peers.insert(*peer_id); - } + // add mesh peers + let topic = &message.topic; + // mesh + if let Some(mesh_peers) = self.mesh.get(topic) { + for peer_id in mesh_peers { + if Some(peer_id) != propagation_source + && !originating_peers.contains(peer_id) + && Some(peer_id) != message.source.as_ref() + { + recipient_peers.insert(*peer_id); } } } + if recipient_peers.is_empty() { + return false; + } + // forward the message to peers - if !recipient_peers.is_empty() { - let event = RpcOut::Forward(message.clone()); + for peer_id in recipient_peers.iter() { + if let Some(peer) = self.connected_peers.get_mut(peer_id) { + if peer.dont_send.contains_key(msg_id) { + tracing::debug!(%peer_id, message=%msg_id, "Peer doesn't want message"); + continue; + } - for peer in recipient_peers.iter() { - tracing::debug!(%peer, message=%msg_id, "Sending message to peer"); - self.send_message(*peer, event.clone()); + tracing::debug!(%peer_id, message=%msg_id, "Sending message to peer"); + + self.send_message( + *peer_id, + RpcOut::Forward { + message: message.clone(), + timeout: Delay::new(self.config.forward_queue_duration()), + }, + ); } - tracing::debug!("Completed forwarding message"); - Ok(true) - } else { - Ok(false) } + tracing::debug!("Completed forwarding message"); + true } /// Constructs a [`RawMessage`] performing message signing if required. @@ -2707,49 +2807,69 @@ where } } - // adds a control action to control_pool - fn control_pool_add( - control_pool: &mut HashMap>, - peer: PeerId, - control: ControlAction, - ) { - control_pool.entry(peer).or_default().push(control); - } - - /// Takes each control action mapping and turns it into a message - fn flush_control_pool(&mut self) { - for (peer, controls) in self.control_pool.drain().collect::>() { - for msg in controls { - self.send_message(peer, RpcOut::Control(msg)); - } - } - - // This clears all pending IWANT messages - self.pending_iwant_msgs.clear(); - } - - /// Send a [`RpcOut`] message to a peer. This will wrap the message in an arc if it - /// is not already an arc. - fn send_message(&mut self, peer_id: PeerId, rpc: RpcOut) { + /// Send a [`RpcOut`] message to a peer. + /// + /// Returns `true` if sending was successful, `false` otherwise. + /// The method will update the peer score and failed message counter if + /// sending the message failed due to the channel to the connection handler being + /// full (which indicates a slow peer). + fn send_message(&mut self, peer_id: PeerId, rpc: RpcOut) -> bool { if let Some(m) = self.metrics.as_mut() { - if let RpcOut::Publish(ref message) | RpcOut::Forward(ref message) = rpc { + if let RpcOut::Publish { ref message, .. } | RpcOut::Forward { ref message, .. } = rpc { // register bytes sent on the internal metrics. m.msg_sent(&message.topic, message.raw_protobuf_len()); } } - self.events.push_back(ToSwarm::NotifyHandler { - peer_id, - event: HandlerIn::Message(rpc), - handler: NotifyHandler::Any, - }); + let Some(peer) = &mut self.connected_peers.get_mut(&peer_id) else { + tracing::error!(peer = %peer_id, + "Could not send rpc to connection handler, peer doesn't exist in connected peer list"); + return false; + }; + + // Try sending the message to the connection handler. + match peer.sender.send_message(rpc) { + Ok(()) => true, + Err(rpc) => { + // Sending failed because the channel is full. + tracing::warn!(peer=%peer_id, "Send Queue full. Could not send {:?}.", rpc); + + // Update failed message counter. + let failed_messages = self.failed_messages.entry(peer_id).or_default(); + match rpc { + RpcOut::Publish { .. } => { + failed_messages.priority += 1; + failed_messages.publish += 1; + } + RpcOut::Forward { .. } => { + failed_messages.non_priority += 1; + failed_messages.forward += 1; + } + RpcOut::IWant(_) | RpcOut::IHave(_) | RpcOut::IDontWant(_) => { + failed_messages.non_priority += 1; + } + RpcOut::Graft(_) + | RpcOut::Prune(_) + | RpcOut::Subscribe(_) + | RpcOut::Unsubscribe(_) => { + unreachable!("Channel for highpriority control messages is unbounded and should always be open.") + } + } + + // Update peer score. + if let Some((peer_score, ..)) = &mut self.peer_score { + peer_score.failed_message_slow_peer(&peer_id); + } + + false + } + } } fn on_connection_established( &mut self, ConnectionEstablished { peer_id, - connection_id, endpoint, other_established, .. @@ -2777,27 +2897,10 @@ where } } - // By default we assume a peer is only a floodsub peer. - // - // The protocol negotiation occurs once a message is sent/received. Once this happens we - // update the type of peer that this is in order to determine which kind of routing should - // occur. - self.connected_peers - .entry(peer_id) - .or_insert(PeerConnections { - kind: PeerKind::Floodsub, - connections: vec![], - }) - .connections - .push(connection_id); - if other_established > 0 { return; // Not our first connection to this peer, hence nothing to do. } - // Insert an empty set of the topics of this peer until known. - self.peer_topics.insert(peer_id, Default::default()); - if let Some((peer_score, ..)) = &mut self.peer_score { peer_score.add_peer(peer_id); } @@ -2840,28 +2943,26 @@ where if remaining_established != 0 { // Remove the connection from the list - if let Some(connections) = self.connected_peers.get_mut(&peer_id) { - let index = connections + if let Some(peer) = self.connected_peers.get_mut(&peer_id) { + let index = peer .connections .iter() .position(|v| v == &connection_id) .expect("Previously established connection to peer must be present"); - connections.connections.remove(index); - - // If there are more connections and this peer is in a mesh, inform the first connection - // handler. - if !connections.connections.is_empty() { - if let Some(topics) = self.peer_topics.get(&peer_id) { - for topic in topics { - if let Some(mesh_peers) = self.mesh.get(topic) { - if mesh_peers.contains(&peer_id) { - self.events.push_back(ToSwarm::NotifyHandler { - peer_id, - event: HandlerIn::JoinedMesh, - handler: NotifyHandler::One(connections.connections[0]), - }); - break; - } + peer.connections.remove(index); + + // If there are more connections and this peer is in a mesh, inform the first + // connection handler. + if !peer.connections.is_empty() { + for topic in &peer.topics { + if let Some(mesh_peers) = self.mesh.get(topic) { + if mesh_peers.contains(&peer_id) { + self.events.push_back(ToSwarm::NotifyHandler { + peer_id, + event: HandlerIn::JoinedMesh, + handler: NotifyHandler::One(peer.connections[0]), + }); + break; } } } @@ -2870,72 +2971,41 @@ where } else { // remove from mesh, topic_peers, peer_topic and the fanout tracing::debug!(peer=%peer_id, "Peer disconnected"); - { - let Some(topics) = self.peer_topics.get(&peer_id) else { - debug_assert!( - self.blacklisted_peers.contains(&peer_id), - "Disconnected node not in connected list" - ); - return; - }; - - // remove peer from all mappings - for topic in topics { - // check the mesh for the topic - if let Some(mesh_peers) = self.mesh.get_mut(topic) { - // check if the peer is in the mesh and remove it - if mesh_peers.remove(&peer_id) { - if let Some(m) = self.metrics.as_mut() { - m.peers_removed(topic, Churn::Dc, 1); - m.set_mesh_peers(topic, mesh_peers.len()); - } - }; - } + let Some(connected_peer) = self.connected_peers.get(&peer_id) else { + tracing::error!(peer_id = %peer_id, "Peer non-existent when handling disconnection"); + return; + }; - // remove from topic_peers - if let Some(peer_list) = self.topic_peers.get_mut(topic) { - if !peer_list.remove(&peer_id) { - // debugging purposes - tracing::warn!( - peer=%peer_id, - "Disconnected node: peer not in topic_peers" - ); - } + // remove peer from all mappings + for topic in &connected_peer.topics { + // check the mesh for the topic + if let Some(mesh_peers) = self.mesh.get_mut(topic) { + // check if the peer is in the mesh and remove it + if mesh_peers.remove(&peer_id) { if let Some(m) = self.metrics.as_mut() { - m.set_topic_peers(topic, peer_list.len()) + m.peers_removed(topic, Churn::Dc, 1); + m.set_mesh_peers(topic, mesh_peers.len()); } - } else { - tracing::warn!( - peer=%peer_id, - topic=%topic, - "Disconnected node: peer with topic not in topic_peers" - ); - } + }; + } - // remove from fanout - self.fanout - .get_mut(topic) - .map(|peers| peers.remove(&peer_id)); + if let Some(m) = self.metrics.as_mut() { + m.dec_topic_peers(topic); } + + // remove from fanout + self.fanout + .get_mut(topic) + .map(|peers| peers.remove(&peer_id)); } // Forget px and outbound status for this peer self.px_peers.remove(&peer_id); self.outbound_peers.remove(&peer_id); - // Remove peer from peer_topics and connected_peers - // NOTE: It is possible the peer has already been removed from all mappings if it does not - // support the protocol. - self.peer_topics.remove(&peer_id); - // If metrics are enabled, register the disconnection of a peer based on its protocol. if let Some(metrics) = self.metrics.as_mut() { - let peer_kind = &self - .connected_peers - .get(&peer_id) - .expect("Connected peer must be registered") - .kind; - metrics.peer_protocol_disconnected(peer_kind.clone()); + metrics.peer_protocol_disconnected(connected_peer.kind); } self.connected_peers.remove(&peer_id); @@ -2997,22 +3067,60 @@ where fn handle_established_inbound_connection( &mut self, - _: ConnectionId, - _: PeerId, + connection_id: ConnectionId, + peer_id: PeerId, _: &Multiaddr, _: &Multiaddr, ) -> Result, ConnectionDenied> { - Ok(Handler::new(self.config.protocol_config())) + // By default we assume a peer is only a floodsub peer. + // + // The protocol negotiation occurs once a message is sent/received. Once this happens we + // update the type of peer that this is in order to determine which kind of routing should + // occur. + let connected_peer = self + .connected_peers + .entry(peer_id) + .or_insert(PeerConnections { + kind: PeerKind::Floodsub, + connections: vec![], + sender: Sender::new(self.config.connection_handler_queue_len()), + topics: Default::default(), + dont_send: LinkedHashMap::new(), + }); + // Add the new connection + connected_peer.connections.push(connection_id); + + Ok(Handler::new( + self.config.protocol_config(), + connected_peer.sender.new_receiver(), + )) } fn handle_established_outbound_connection( &mut self, - _: ConnectionId, - _: PeerId, + connection_id: ConnectionId, + peer_id: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { - Ok(Handler::new(self.config.protocol_config())) + let connected_peer = self + .connected_peers + .entry(peer_id) + .or_insert(PeerConnections { + kind: PeerKind::Floodsub, + connections: vec![], + sender: Sender::new(self.config.connection_handler_queue_len()), + topics: Default::default(), + dont_send: LinkedHashMap::new(), + }); + // Add the new connection + connected_peer.connections.push(connection_id); + + Ok(Handler::new( + self.config.protocol_config(), + connected_peer.sender.new_receiver(), + )) } fn on_connection_handler_event( @@ -3026,7 +3134,7 @@ where // We have identified the protocol this peer is using if let Some(metrics) = self.metrics.as_mut() { - metrics.peer_protocol_connected(kind.clone()); + metrics.peer_protocol_connected(kind); } if let PeerKind::NotSupported = kind { @@ -3052,6 +3160,40 @@ where } } } + HandlerEvent::MessageDropped(rpc) => { + // Account for this in the scoring logic + if let Some((peer_score, _, _)) = &mut self.peer_score { + peer_score.failed_message_slow_peer(&propagation_source); + } + + // Keep track of expired messages for the application layer. + let failed_messages = self.failed_messages.entry(propagation_source).or_default(); + failed_messages.timeout += 1; + match rpc { + RpcOut::Publish { .. } => { + failed_messages.publish += 1; + } + RpcOut::Forward { .. } => { + failed_messages.forward += 1; + } + _ => {} + } + + // Record metrics on the failure. + if let Some(metrics) = self.metrics.as_mut() { + match rpc { + RpcOut::Publish { message, .. } => { + metrics.publish_msg_dropped(&message.topic); + metrics.timeout_msg_dropped(&message.topic); + } + RpcOut::Forward { message, .. } => { + metrics.forward_msg_dropped(&message.topic); + metrics.timeout_msg_dropped(&message.topic); + } + _ => {} + } + } + } HandlerEvent::Message { rpc, invalid_messages, @@ -3106,27 +3248,46 @@ where } // Handle control messages - // group some control messages, this minimises SendEvents (code is simplified to handle each event at a time however) + // group some control messages, this minimises SendEvents (code is simplified to + // handle each event at a time however) let mut ihave_msgs = vec![]; let mut graft_msgs = vec![]; let mut prune_msgs = vec![]; for control_msg in rpc.control_msgs { match control_msg { - ControlAction::IHave { + ControlAction::IHave(IHave { topic_hash, message_ids, - } => { + }) => { ihave_msgs.push((topic_hash, message_ids)); } - ControlAction::IWant { message_ids } => { + ControlAction::IWant(IWant { message_ids }) => { self.handle_iwant(&propagation_source, message_ids) } - ControlAction::Graft { topic_hash } => graft_msgs.push(topic_hash), - ControlAction::Prune { + ControlAction::Graft(Graft { topic_hash }) => graft_msgs.push(topic_hash), + ControlAction::Prune(Prune { topic_hash, peers, backoff, - } => prune_msgs.push((topic_hash, peers, backoff)), + }) => prune_msgs.push((topic_hash, peers, backoff)), + ControlAction::IDontWant(IDontWant { message_ids }) => { + let Some(peer) = self.connected_peers.get_mut(&propagation_source) + else { + tracing::error!(peer = %propagation_source, + "Could not handle IDONTWANT, peer doesn't exist in connected peer list"); + continue; + }; + if let Some(metrics) = self.metrics.as_mut() { + metrics.register_idontwant(message_ids.len()); + } + for message_id in message_ids { + peer.dont_send.insert(message_id, Instant::now()); + // Don't exceed capacity. + if peer.dont_send.len() > IDONTWANT_CAP { + peer.dont_send.pop_front(); + } + } + } } } if !ihave_msgs.is_empty() { @@ -3152,14 +3313,16 @@ where } // update scores - if let Some((peer_score, _, interval, _)) = &mut self.peer_score { - while let Poll::Ready(Some(_)) = interval.poll_next_unpin(cx) { + if let Some((peer_score, _, delay)) = &mut self.peer_score { + if delay.poll_unpin(cx).is_ready() { peer_score.refresh_scores(); + delay.reset(peer_score.params.decay_interval); } } - while let Poll::Ready(Some(_)) = self.heartbeat.poll_next_unpin(cx) { + if self.heartbeat.poll_unpin(cx).is_ready() { self.heartbeat(); + self.heartbeat.reset(self.config.heartbeat_interval()); } Poll::Pending @@ -3186,22 +3349,23 @@ fn peer_added_to_mesh( peer_id: PeerId, new_topics: Vec<&TopicHash>, mesh: &HashMap>, - known_topics: Option<&BTreeSet>, events: &mut VecDeque>, connections: &HashMap, ) { // Ensure there is an active connection - let connection_id = { - let conn = connections.get(&peer_id).expect("To be connected to peer."); - assert!( - !conn.connections.is_empty(), - "Must have at least one connection" - ); - conn.connections[0] + let connection_id = match connections.get(&peer_id) { + Some(p) => p + .connections + .first() + .expect("There should be at least one connection to a peer."), + None => { + tracing::error!(peer_id=%peer_id, "Peer not existent when added to the mesh"); + return; + } }; - if let Some(topics) = known_topics { - for topic in topics { + if let Some(peer) = connections.get(&peer_id) { + for topic in &peer.topics { if !new_topics.contains(&topic) { if let Some(mesh_peers) = mesh.get(topic) { if mesh_peers.contains(&peer_id) { @@ -3216,7 +3380,7 @@ fn peer_added_to_mesh( events.push_back(ToSwarm::NotifyHandler { peer_id, event: HandlerIn::JoinedMesh, - handler: NotifyHandler::One(connection_id), + handler: NotifyHandler::One(*connection_id), }); } @@ -3227,20 +3391,23 @@ fn peer_removed_from_mesh( peer_id: PeerId, old_topic: &TopicHash, mesh: &HashMap>, - known_topics: Option<&BTreeSet>, events: &mut VecDeque>, connections: &HashMap, ) { // Ensure there is an active connection - let connection_id = connections - .get(&peer_id) - .expect("To be connected to peer.") - .connections - .first() - .expect("There should be at least one connection to a peer."); - - if let Some(topics) = known_topics { - for topic in topics { + let connection_id = match connections.get(&peer_id) { + Some(p) => p + .connections + .first() + .expect("There should be at least one connection to a peer."), + None => { + tracing::error!(peer_id=%peer_id, "Peer not existent when removed from mesh"); + return; + } + }; + + if let Some(peer) = connections.get(&peer_id) { + for topic in &peer.topics { if topic != old_topic { if let Some(mesh_peers) = mesh.get(topic) { if mesh_peers.contains(&peer_id) { @@ -3263,28 +3430,19 @@ fn peer_removed_from_mesh( /// filtered by the function `f`. The number of peers to get equals the output of `n_map` /// that gets as input the number of filtered peers. fn get_random_peers_dynamic( - topic_peers: &HashMap>, connected_peers: &HashMap, topic_hash: &TopicHash, // maps the number of total peers to the number of selected peers n_map: impl Fn(usize) -> usize, mut f: impl FnMut(&PeerId) -> bool, ) -> BTreeSet { - let mut gossip_peers = match topic_peers.get(topic_hash) { - // if they exist, filter the peers by `f` - Some(peer_list) => peer_list - .iter() - .copied() - .filter(|p| { - f(p) && match connected_peers.get(p) { - Some(connections) if connections.kind == PeerKind::Gossipsub => true, - Some(connections) if connections.kind == PeerKind::Gossipsubv1_1 => true, - _ => false, - } - }) - .collect(), - None => Vec::new(), - }; + let mut gossip_peers = connected_peers + .iter() + .filter(|(_, p)| p.topics.contains(topic_hash)) + .filter(|(peer_id, _)| f(peer_id)) + .filter(|(_, p)| p.kind.is_gossipsub()) + .map(|(peer_id, _)| *peer_id) + .collect::>(); // if we have less than needed, return them let n = n_map(gossip_peers.len()); @@ -3305,13 +3463,12 @@ fn get_random_peers_dynamic( /// Helper function to get a set of `n` random gossipsub peers for a `topic_hash` /// filtered by the function `f`. fn get_random_peers( - topic_peers: &HashMap>, connected_peers: &HashMap, topic_hash: &TopicHash, n: usize, f: impl FnMut(&PeerId) -> bool, ) -> BTreeSet { - get_random_peers_dynamic(topic_peers, connected_peers, topic_hash, |_| n, f) + get_random_peers_dynamic(connected_peers, topic_hash, |_| n, f) } /// Validates the combination of signing, privacy and message validation to ensure the @@ -3349,10 +3506,7 @@ impl fmt::Debug for Behaviour RawMessage { - RawMessage { - source: Some(PeerId::random()), - data: vec![0; 100], - sequence_number: None, - topic: TopicHash::from_raw("test_topic"), - signature: None, - key: None, - validated: false, - } - } - - fn test_control() -> ControlAction { - ControlAction::IHave { - topic_hash: IdentTopic::new("TestTopic").hash(), - message_ids: vec![MessageId(vec![12u8]); 5], - } - } - - impl Arbitrary for RpcOut { - fn arbitrary(g: &mut Gen) -> Self { - match u8::arbitrary(g) % 5 { - 0 => RpcOut::Subscribe(IdentTopic::new("TestTopic").hash()), - 1 => RpcOut::Unsubscribe(IdentTopic::new("TestTopic").hash()), - 2 => RpcOut::Publish(test_message()), - 3 => RpcOut::Forward(test_message()), - 4 => RpcOut::Control(test_control()), - _ => panic!("outside range"), - } - } - } -} diff --git a/protocols/gossipsub/src/behaviour/tests.rs b/protocols/gossipsub/src/behaviour/tests.rs index 570cdf43f90..bed74ecdce7 100644 --- a/protocols/gossipsub/src/behaviour/tests.rs +++ b/protocols/gossipsub/src/behaviour/tests.rs @@ -20,28 +20,20 @@ // Collection of tests for the gossipsub network behaviour +use std::{future, net::Ipv4Addr, thread::sleep}; + +use byteorder::{BigEndian, ByteOrder}; +use libp2p_core::ConnectedPoint; +use rand::Rng; + use super::*; -use crate::subscription_filter::WhitelistSubscriptionFilter; -use crate::transform::{DataTransform, IdentityTransform}; -use crate::ValidationError; use crate::{ - config::Config, config::ConfigBuilder, types::Rpc, IdentTopic as Topic, TopicScoreParams, + config::ConfigBuilder, rpc::Receiver, subscription_filter::WhitelistSubscriptionFilter, + types::Rpc, IdentTopic as Topic, }; -use async_std::net::Ipv4Addr; -use byteorder::{BigEndian, ByteOrder}; -use libp2p_core::{ConnectedPoint, Endpoint}; -use rand::Rng; -use std::thread::sleep; -use std::time::Duration; #[derive(Default, Debug)] -struct InjectNodes -// TODO: remove trait bound Default when this issue is fixed: -// https://github.com/colin-kiegel/rust-derive-builder/issues/93 -where - D: DataTransform + Default + Clone + Send + 'static, - F: TopicSubscriptionFilter + Clone + Default + Send + 'static, -{ +struct InjectNodes { peer_no: usize, topics: Vec, to_subscribe: bool, @@ -51,6 +43,7 @@ where scoring: Option<(PeerScoreParams, PeerScoreThresholds)>, data_transform: D, subscription_filter: F, + peer_kind: Option, } impl InjectNodes @@ -58,7 +51,15 @@ where D: DataTransform + Default + Clone + Send + 'static, F: TopicSubscriptionFilter + Clone + Default + Send + 'static, { - pub(crate) fn create_network(self) -> (Behaviour, Vec, Vec) { + #[allow(clippy::type_complexity)] + pub(crate) fn create_network( + self, + ) -> ( + Behaviour, + Vec, + HashMap, + Vec, + ) { let keypair = libp2p_identity::Keypair::generate_ed25519(); // create a gossipsub struct let mut gs: Behaviour = Behaviour::new_with_subscription_filter_and_transform( @@ -86,10 +87,11 @@ where // build and connect peer_no random peers let mut peers = vec![]; + let mut receivers = HashMap::new(); let empty = vec![]; for i in 0..self.peer_no { - peers.push(add_peer( + let (peer, receiver) = add_peer_with_addr_and_kind( &mut gs, if self.to_subscribe { &topic_hashes @@ -98,10 +100,14 @@ where }, i < self.outbound, i < self.explicit, - )); + Multiaddr::empty(), + self.peer_kind.or(Some(PeerKind::Gossipsubv1_1)), + ); + peers.push(peer); + receivers.insert(peer, receiver); } - (gs, peers, topic_hashes) + (gs, peers, receivers, topic_hashes) } fn peer_no(mut self, peer_no: usize) -> Self { @@ -144,6 +150,11 @@ where self.subscription_filter = subscription_filter; self } + + fn peer_kind(mut self, peer_kind: PeerKind) -> Self { + self.peer_kind = Some(peer_kind); + self + } } fn inject_nodes() -> InjectNodes @@ -162,10 +173,10 @@ fn inject_nodes1() -> InjectNodes fn add_peer( gs: &mut Behaviour, - topic_hashes: &Vec, + topic_hashes: &[TopicHash], outbound: bool, explicit: bool, -) -> PeerId +) -> (PeerId, Receiver) where D: DataTransform + Default + Clone + Send + 'static, F: TopicSubscriptionFilter + Clone + Default + Send + 'static, @@ -175,11 +186,11 @@ where fn add_peer_with_addr( gs: &mut Behaviour, - topic_hashes: &Vec, + topic_hashes: &[TopicHash], outbound: bool, explicit: bool, address: Multiaddr, -) -> PeerId +) -> (PeerId, Receiver) where D: DataTransform + Default + Clone + Send + 'static, F: TopicSubscriptionFilter + Clone + Default + Send + 'static, @@ -196,12 +207,12 @@ where fn add_peer_with_addr_and_kind( gs: &mut Behaviour, - topic_hashes: &Vec, + topic_hashes: &[TopicHash], outbound: bool, explicit: bool, address: Multiaddr, kind: Option, -) -> PeerId +) -> (PeerId, Receiver) where D: DataTransform + Default + Clone + Send + 'static, F: TopicSubscriptionFilter + Clone + Default + Send + 'static, @@ -211,6 +222,7 @@ where ConnectedPoint::Dialer { address, role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, } } else { ConnectedPoint::Listener { @@ -219,9 +231,23 @@ where } }; + let sender = Sender::new(gs.config.connection_handler_queue_len()); + let receiver = sender.new_receiver(); + let connection_id = ConnectionId::new_unchecked(0); + gs.connected_peers.insert( + peer, + PeerConnections { + kind: kind.unwrap_or(PeerKind::Floodsub), + connections: vec![connection_id], + topics: Default::default(), + sender, + dont_send: LinkedHashMap::new(), + }, + ); + gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { peer_id: peer, - connection_id: ConnectionId::new_unchecked(0), + connection_id, endpoint: &endpoint, failed_addresses: &[], other_established: 0, // first connection @@ -249,7 +275,7 @@ where &peer, ); } - peer + (peer, receiver) } fn disconnect_peer(gs: &mut Behaviour, peer_id: &PeerId) @@ -261,6 +287,7 @@ where let fake_endpoint = ConnectedPoint::Dialer { address: Multiaddr::empty(), role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }; // this is not relevant // peer_connections.connections should never be empty. @@ -273,6 +300,7 @@ where connection_id, endpoint: &fake_endpoint, remaining_established: active_connections, + cause: None, })); } } @@ -287,7 +315,8 @@ fn proto_to_message(rpc: &proto::RPC) -> Rpc { messages.push(RawMessage { source: message.from.map(|x| PeerId::from_bytes(&x).unwrap()), data: message.data.unwrap_or_default(), - sequence_number: message.seqno.map(|x| BigEndian::read_u64(&x)), // don't inform the application + sequence_number: message.seqno.map(|x| BigEndian::read_u64(&x)), /* don't inform the + * application */ topic: TopicHash::from_raw(message.topic), signature: message.signature, // don't inform the application key: None, @@ -300,33 +329,39 @@ fn proto_to_message(rpc: &proto::RPC) -> Rpc { let ihave_msgs: Vec = rpc_control .ihave .into_iter() - .map(|ihave| ControlAction::IHave { - topic_hash: TopicHash::from_raw(ihave.topic_id.unwrap_or_default()), - message_ids: ihave - .message_ids - .into_iter() - .map(MessageId::from) - .collect::>(), + .map(|ihave| { + ControlAction::IHave(IHave { + topic_hash: TopicHash::from_raw(ihave.topic_id.unwrap_or_default()), + message_ids: ihave + .message_ids + .into_iter() + .map(MessageId::from) + .collect::>(), + }) }) .collect(); let iwant_msgs: Vec = rpc_control .iwant .into_iter() - .map(|iwant| ControlAction::IWant { - message_ids: iwant - .message_ids - .into_iter() - .map(MessageId::from) - .collect::>(), + .map(|iwant| { + ControlAction::IWant(IWant { + message_ids: iwant + .message_ids + .into_iter() + .map(MessageId::from) + .collect::>(), + }) }) .collect(); let graft_msgs: Vec = rpc_control .graft .into_iter() - .map(|graft| ControlAction::Graft { - topic_hash: TopicHash::from_raw(graft.topic_id.unwrap_or_default()), + .map(|graft| { + ControlAction::Graft(Graft { + topic_hash: TopicHash::from_raw(graft.topic_id.unwrap_or_default()), + }) }) .collect(); @@ -349,11 +384,11 @@ fn proto_to_message(rpc: &proto::RPC) -> Rpc { .collect::>(); let topic_hash = TopicHash::from_raw(prune.topic_id.unwrap_or_default()); - prune_msgs.push(ControlAction::Prune { + prune_msgs.push(ControlAction::Prune(Prune { topic_hash, peers, backoff: prune.backoff, - }); + })); } control_msgs.extend(ihave_msgs); @@ -389,38 +424,36 @@ fn test_subscribe() { // - run JOIN(topic) let subscribe_topic = vec![String::from("test_subscribe")]; - let (gs, _, topic_hashes) = inject_nodes1() + let (gs, _, receivers, topic_hashes) = inject_nodes1() .peer_no(20) .topics(subscribe_topic) .to_subscribe(true) .create_network(); assert!( - gs.mesh.get(&topic_hashes[0]).is_some(), + gs.mesh.contains_key(&topic_hashes[0]), "Subscribe should add a new entry to the mesh[topic] hashmap" ); // collect all the subscriptions - let subscriptions = gs - .events - .iter() - .filter(|e| { - matches!( - e, - ToSwarm::NotifyHandler { - event: HandlerIn::Message(RpcOut::Subscribe(_)), - .. + let subscriptions = receivers + .into_values() + .fold(0, |mut collected_subscriptions, c| { + let priority = c.priority.get_ref(); + while !priority.is_empty() { + if let Ok(RpcOut::Subscribe(_)) = priority.try_recv() { + collected_subscriptions += 1 } - ) - }) - .count(); + } + collected_subscriptions + }); // we sent a subscribe to all known peers assert_eq!(subscriptions, 20); } -#[test] /// Test unsubscribe. +#[test] fn test_unsubscribe() { // Unsubscribe should: // - Remove the mesh entry for topic @@ -434,7 +467,7 @@ fn test_unsubscribe() { .collect::>(); // subscribe to topic_strings - let (mut gs, _, topic_hashes) = inject_nodes1() + let (mut gs, _, receivers, topic_hashes) = inject_nodes1() .peer_no(20) .topics(topic_strings) .to_subscribe(true) @@ -442,35 +475,38 @@ fn test_unsubscribe() { for topic_hash in &topic_hashes { assert!( - gs.topic_peers.get(topic_hash).is_some(), + gs.connected_peers + .values() + .any(|p| p.topics.contains(topic_hash)), "Topic_peers contain a topic entry" ); assert!( - gs.mesh.get(topic_hash).is_some(), + gs.mesh.contains_key(topic_hash), "mesh should contain a topic entry" ); } // unsubscribe from both topics assert!( - gs.unsubscribe(&topics[0]).unwrap(), + gs.unsubscribe(&topics[0]), "should be able to unsubscribe successfully from each topic", ); assert!( - gs.unsubscribe(&topics[1]).unwrap(), + gs.unsubscribe(&topics[1]), "should be able to unsubscribe successfully from each topic", ); // collect all the subscriptions - let subscriptions = gs - .events - .iter() - .fold(0, |collected_subscriptions, e| match e { - ToSwarm::NotifyHandler { - event: HandlerIn::Message(RpcOut::Subscribe(_)), - .. - } => collected_subscriptions + 1, - _ => collected_subscriptions, + let subscriptions = receivers + .into_values() + .fold(0, |mut collected_subscriptions, c| { + let priority = c.priority.get_ref(); + while !priority.is_empty() { + if let Ok(RpcOut::Subscribe(_)) = priority.try_recv() { + collected_subscriptions += 1 + } + } + collected_subscriptions }); // we sent a unsubscribe to all known peers, for two topics @@ -479,14 +515,14 @@ fn test_unsubscribe() { // check we clean up internal structures for topic_hash in &topic_hashes { assert!( - gs.mesh.get(topic_hash).is_none(), + !gs.mesh.contains_key(topic_hash), "All topics should have been removed from the mesh" ); } } -#[test] /// Test JOIN(topic) functionality. +#[test] fn test_join() { // The Join function should: // - Remove peers from fanout[topic] @@ -503,19 +539,22 @@ fn test_join() { .map(|t| Topic::new(t.clone())) .collect::>(); - let (mut gs, _, topic_hashes) = inject_nodes1() + let (mut gs, _, mut receivers, topic_hashes) = inject_nodes1() .peer_no(20) .topics(topic_strings) .to_subscribe(true) .create_network(); + // Flush previous GRAFT messages. + receivers = flush_events(&mut gs, receivers); + // unsubscribe, then call join to invoke functionality assert!( - gs.unsubscribe(&topics[0]).unwrap(), + gs.unsubscribe(&topics[0]), "should be able to unsubscribe successfully" ); assert!( - gs.unsubscribe(&topics[1]).unwrap(), + gs.unsubscribe(&topics[1]), "should be able to unsubscribe successfully" ); @@ -531,24 +570,34 @@ fn test_join() { "Should have added 6 nodes to the mesh" ); - fn collect_grafts( - mut collected_grafts: Vec, - (_, controls): (&PeerId, &Vec), - ) -> Vec { - for c in controls.iter() { - if let ControlAction::Graft { topic_hash: _ } = c { - collected_grafts.push(c.clone()) + fn count_grafts(receivers: HashMap) -> (usize, HashMap) { + let mut new_receivers = HashMap::new(); + let mut acc = 0; + + for (peer_id, c) in receivers.into_iter() { + let priority = c.priority.get_ref(); + while !priority.is_empty() { + if let Ok(RpcOut::Graft(_)) = priority.try_recv() { + acc += 1; + } } + new_receivers.insert( + peer_id, + Receiver { + priority_queue_len: c.priority_queue_len, + priority: c.priority, + non_priority: c.non_priority, + }, + ); } - collected_grafts + (acc, new_receivers) } // there should be mesh_n GRAFT messages. - let graft_messages = gs.control_pool.iter().fold(vec![], collect_grafts); + let (graft_messages, mut receivers) = count_grafts(receivers); assert_eq!( - graft_messages.len(), - 6, + graft_messages, 6, "There should be 6 grafts messages sent to peers" ); @@ -557,15 +606,40 @@ fn test_join() { gs.fanout .insert(topic_hashes[1].clone(), Default::default()); let mut new_peers: Vec = vec![]; + for _ in 0..3 { let random_peer = PeerId::random(); // inform the behaviour of a new peer + let address = "/ip4/127.0.0.1".parse::().unwrap(); + gs.handle_established_inbound_connection( + ConnectionId::new_unchecked(0), + random_peer, + &address, + &address, + ) + .unwrap(); + let sender = Sender::new(gs.config.connection_handler_queue_len()); + let receiver = sender.new_receiver(); + let connection_id = ConnectionId::new_unchecked(0); + gs.connected_peers.insert( + random_peer, + PeerConnections { + kind: PeerKind::Floodsub, + connections: vec![connection_id], + topics: Default::default(), + sender, + dont_send: LinkedHashMap::new(), + }, + ); + receivers.insert(random_peer, receiver); + gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { peer_id: random_peer, - connection_id: ConnectionId::new_unchecked(0), + connection_id, endpoint: &ConnectedPoint::Dialer { - address: "/ip4/127.0.0.1".parse::().unwrap(), + address, role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }, failed_addresses: &[], other_established: 0, @@ -593,12 +667,12 @@ fn test_join() { ); } - // there should now be 12 graft messages to be sent - let graft_messages = gs.control_pool.iter().fold(vec![], collect_grafts); + // there should now 6 graft messages to be sent + let (graft_messages, _) = count_grafts(receivers); - assert!( - graft_messages.len() == 12, - "There should be 12 grafts messages sent to peers" + assert_eq!( + graft_messages, 6, + "There should be 6 grafts messages sent to peers" ); } @@ -609,14 +683,14 @@ fn test_publish_without_flood_publishing() { // - Send publish message to all peers // - Insert message into gs.mcache and gs.received - //turn off flood publish to test old behaviour + // turn off flood publish to test old behaviour let config = ConfigBuilder::default() .flood_publish(false) .build() .unwrap(); let publish_topic = String::from("test_publish"); - let (mut gs, _, topic_hashes) = inject_nodes1() + let (mut gs, _, receivers, topic_hashes) = inject_nodes1() .peer_no(20) .topics(vec![publish_topic.clone()]) .to_subscribe(true) @@ -624,14 +698,17 @@ fn test_publish_without_flood_publishing() { .create_network(); assert!( - gs.mesh.get(&topic_hashes[0]).is_some(), + gs.mesh.contains_key(&topic_hashes[0]), "Subscribe should add a new entry to the mesh[topic] hashmap" ); // all peers should be subscribed to the topic assert_eq!( - gs.topic_peers.get(&topic_hashes[0]).map(|p| p.len()), - Some(20), + gs.connected_peers + .values() + .filter(|p| p.topics.contains(&topic_hashes[0])) + .count(), + 20, "Peers should be subscribed to the topic" ); @@ -640,18 +717,16 @@ fn test_publish_without_flood_publishing() { gs.publish(Topic::new(publish_topic), publish_data).unwrap(); // Collect all publish messages - let publishes = gs - .events - .into_iter() - .fold(vec![], |mut collected_publish, e| match e { - ToSwarm::NotifyHandler { - event: HandlerIn::Message(RpcOut::Publish(message)), - .. - } => { - collected_publish.push(message); - collected_publish + let publishes = receivers + .into_values() + .fold(vec![], |mut collected_publish, c| { + let priority = c.priority.get_ref(); + while !priority.is_empty() { + if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { + collected_publish.push(message); + } } - _ => collected_publish, + collected_publish }); // Transform the inbound message @@ -670,8 +745,8 @@ fn test_publish_without_flood_publishing() { let config: Config = Config::default(); assert_eq!( publishes.len(), - config.mesh_n_low(), - "Should send a publish message to all known peers" + config.mesh_n(), + "Should send a publish message to at least mesh_n peers" ); assert!( @@ -688,14 +763,14 @@ fn test_fanout() { // - Send publish message to fanout peers // - Insert message into gs.mcache and gs.received - //turn off flood publish to test fanout behaviour + // turn off flood publish to test fanout behaviour let config = ConfigBuilder::default() .flood_publish(false) .build() .unwrap(); let fanout_topic = String::from("test_fanout"); - let (mut gs, _, topic_hashes) = inject_nodes1() + let (mut gs, _, receivers, topic_hashes) = inject_nodes1() .peer_no(20) .topics(vec![fanout_topic.clone()]) .to_subscribe(true) @@ -703,12 +778,12 @@ fn test_fanout() { .create_network(); assert!( - gs.mesh.get(&topic_hashes[0]).is_some(), + gs.mesh.contains_key(&topic_hashes[0]), "Subscribe should add a new entry to the mesh[topic] hashmap" ); // Unsubscribe from topic assert!( - gs.unsubscribe(&Topic::new(fanout_topic.clone())).unwrap(), + gs.unsubscribe(&Topic::new(fanout_topic.clone())), "should be able to unsubscribe successfully from topic" ); @@ -727,18 +802,16 @@ fn test_fanout() { ); // Collect all publish messages - let publishes = gs - .events - .into_iter() - .fold(vec![], |mut collected_publish, e| match e { - ToSwarm::NotifyHandler { - event: HandlerIn::Message(RpcOut::Publish(message)), - .. - } => { - collected_publish.push(message); - collected_publish + let publishes = receivers + .into_values() + .fold(vec![], |mut collected_publish, c| { + let priority = c.priority.get_ref(); + while !priority.is_empty() { + if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { + collected_publish.push(message); + } } - _ => collected_publish, + collected_publish }); // Transform the inbound message @@ -766,10 +839,10 @@ fn test_fanout() { ); } -#[test] /// Test the gossipsub NetworkBehaviour peer connection logic. +#[test] fn test_inject_connected() { - let (gs, peers, topic_hashes) = inject_nodes1() + let (gs, peers, receivers, topic_hashes) = inject_nodes1() .peer_no(20) .topics(vec![String::from("topic1"), String::from("topic2")]) .to_subscribe(true) @@ -777,26 +850,20 @@ fn test_inject_connected() { // check that our subscriptions are sent to each of the peers // collect all the SendEvents - let subscriptions = gs - .events - .into_iter() - .filter_map(|e| match e { - ToSwarm::NotifyHandler { - event: HandlerIn::Message(RpcOut::Subscribe(topic)), - peer_id, - .. - } => Some((peer_id, topic)), - _ => None, - }) - .fold( - HashMap::>::new(), - |mut subs, (peer, sub)| { - let mut peer_subs = subs.remove(&peer).unwrap_or_default(); - peer_subs.push(sub.into_string()); - subs.insert(peer, peer_subs); - subs - }, - ); + let subscriptions = receivers.into_iter().fold( + HashMap::>::new(), + |mut collected_subscriptions, (peer, c)| { + let priority = c.priority.get_ref(); + while !priority.is_empty() { + if let Ok(RpcOut::Subscribe(topic)) = priority.try_recv() { + let mut peer_subs = collected_subscriptions.remove(&peer).unwrap_or_default(); + peer_subs.push(topic.into_string()); + collected_subscriptions.insert(peer, peer_subs); + } + } + collected_subscriptions + }, + ); // check that there are two subscriptions sent to each peer for peer_subs in subscriptions.values() { @@ -810,16 +877,16 @@ fn test_inject_connected() { // should add the new peers to `peer_topics` with an empty vec as a gossipsub node for peer in peers { - let known_topics = gs.peer_topics.get(&peer).unwrap(); + let peer = gs.connected_peers.get(&peer).unwrap(); assert!( - known_topics == &topic_hashes.iter().cloned().collect(), + peer.topics == topic_hashes.iter().cloned().collect(), "The topics for each node should all topics" ); } } -#[test] /// Test subscription handling +#[test] fn test_handle_received_subscriptions() { // For every subscription: // SUBSCRIBE: - Add subscribed topic to peer_topics for peer. @@ -831,7 +898,7 @@ fn test_handle_received_subscriptions() { .iter() .map(|&t| String::from(t)) .collect(); - let (mut gs, peers, topic_hashes) = inject_nodes1() + let (mut gs, peers, _receivers, topic_hashes) = inject_nodes1() .peer_no(20) .topics(topics) .to_subscribe(false) @@ -861,24 +928,39 @@ fn test_handle_received_subscriptions() { // verify the result - let peer_topics = gs.peer_topics.get(&peers[0]).unwrap().clone(); + let peer = gs.connected_peers.get(&peers[0]).unwrap(); assert!( - peer_topics == topic_hashes.iter().take(3).cloned().collect(), + peer.topics + == topic_hashes + .iter() + .take(3) + .cloned() + .collect::>(), "First peer should be subscribed to three topics" ); - let peer_topics = gs.peer_topics.get(&peers[1]).unwrap().clone(); + let peer1 = gs.connected_peers.get(&peers[1]).unwrap(); assert!( - peer_topics == topic_hashes.iter().take(3).cloned().collect(), + peer1.topics + == topic_hashes + .iter() + .take(3) + .cloned() + .collect::>(), "Second peer should be subscribed to three topics" ); assert!( - gs.peer_topics.get(&unknown_peer).is_none(), + !gs.connected_peers.contains_key(&unknown_peer), "Unknown peer should not have been added" ); for topic_hash in topic_hashes[..3].iter() { - let topic_peers = gs.topic_peers.get(topic_hash).unwrap().clone(); + let topic_peers = gs + .connected_peers + .iter() + .filter(|(_, p)| p.topics.contains(topic_hash)) + .map(|(peer_id, _)| *peer_id) + .collect::>(); assert!( topic_peers == peers[..2].iter().cloned().collect(), "Two peers should be added to the first three topics" @@ -895,21 +977,28 @@ fn test_handle_received_subscriptions() { &peers[0], ); - let peer_topics = gs.peer_topics.get(&peers[0]).unwrap().clone(); + let peer = gs.connected_peers.get(&peers[0]).unwrap(); assert!( - peer_topics == topic_hashes[1..3].iter().cloned().collect(), + peer.topics == topic_hashes[1..3].iter().cloned().collect::>(), "Peer should be subscribed to two topics" ); - let topic_peers = gs.topic_peers.get(&topic_hashes[0]).unwrap().clone(); // only gossipsub at the moment + // only gossipsub at the moment + let topic_peers = gs + .connected_peers + .iter() + .filter(|(_, p)| p.topics.contains(&topic_hashes[0])) + .map(|(peer_id, _)| *peer_id) + .collect::>(); + assert!( topic_peers == peers[1..2].iter().cloned().collect(), "Only the second peers should be in the first topic" ); } -#[test] /// Test Gossipsub.get_random_peers() function +#[test] fn test_get_random_peers() { // generate a default Config let gs_config = ConfigBuilder::default() @@ -922,67 +1011,44 @@ fn test_get_random_peers() { // create a topic and fill it with some peers let topic_hash = Topic::new("Test").hash(); let mut peers = vec![]; + let mut topics = BTreeSet::new(); + topics.insert(topic_hash.clone()); + for _ in 0..20 { - peers.push(PeerId::random()) + let peer_id = PeerId::random(); + peers.push(peer_id); + gs.connected_peers.insert( + peer_id, + PeerConnections { + kind: PeerKind::Gossipsubv1_1, + connections: vec![ConnectionId::new_unchecked(0)], + topics: topics.clone(), + sender: Sender::new(gs.config.connection_handler_queue_len()), + dont_send: LinkedHashMap::new(), + }, + ); } - gs.topic_peers - .insert(topic_hash.clone(), peers.iter().cloned().collect()); - - gs.connected_peers = peers - .iter() - .map(|p| { - ( - *p, - PeerConnections { - kind: PeerKind::Gossipsubv1_1, - connections: vec![ConnectionId::new_unchecked(0)], - }, - ) - }) - .collect(); - - let random_peers = - get_random_peers(&gs.topic_peers, &gs.connected_peers, &topic_hash, 5, |_| { - true - }); + let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 5, |_| true); assert_eq!(random_peers.len(), 5, "Expected 5 peers to be returned"); - let random_peers = get_random_peers( - &gs.topic_peers, - &gs.connected_peers, - &topic_hash, - 30, - |_| true, - ); + let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 30, |_| true); assert!(random_peers.len() == 20, "Expected 20 peers to be returned"); assert!( random_peers == peers.iter().cloned().collect(), "Expected no shuffling" ); - let random_peers = get_random_peers( - &gs.topic_peers, - &gs.connected_peers, - &topic_hash, - 20, - |_| true, - ); + let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 20, |_| true); assert!(random_peers.len() == 20, "Expected 20 peers to be returned"); assert!( random_peers == peers.iter().cloned().collect(), "Expected no shuffling" ); - let random_peers = - get_random_peers(&gs.topic_peers, &gs.connected_peers, &topic_hash, 0, |_| { - true - }); + let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 0, |_| true); assert!(random_peers.is_empty(), "Expected 0 peers to be returned"); // test the filter - let random_peers = - get_random_peers(&gs.topic_peers, &gs.connected_peers, &topic_hash, 5, |_| { - false - }); + let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 5, |_| false); assert!(random_peers.is_empty(), "Expected 0 peers to be returned"); - let random_peers = get_random_peers(&gs.topic_peers, &gs.connected_peers, &topic_hash, 10, { + let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 10, { |peer| peers.contains(peer) }); assert!(random_peers.len() == 10, "Expected 10 peers to be returned"); @@ -991,7 +1057,7 @@ fn test_get_random_peers() { /// Tests that the correct message is sent when a peer asks for a message in our cache. #[test] fn test_handle_iwant_msg_cached() { - let (mut gs, peers, _) = inject_nodes1() + let (mut gs, peers, receivers, _) = inject_nodes1() .peer_no(20) .topics(Vec::new()) .to_subscribe(true) @@ -1019,18 +1085,17 @@ fn test_handle_iwant_msg_cached() { gs.handle_iwant(&peers[7], vec![msg_id.clone()]); // the messages we are sending - let sent_messages = gs.events.into_iter().fold( - Vec::::new(), - |mut collected_messages, e| match e { - ToSwarm::NotifyHandler { event, .. } => { - if let HandlerIn::Message(RpcOut::Forward(message)) = event { - collected_messages.push(message); + let sent_messages = receivers + .into_values() + .fold(vec![], |mut collected_messages, c| { + let non_priority = c.non_priority.get_ref(); + while !non_priority.is_empty() { + if let Ok(RpcOut::Forward { message, .. }) = non_priority.try_recv() { + collected_messages.push(message) } - collected_messages } - _ => collected_messages, - }, - ); + collected_messages + }); assert!( sent_messages @@ -1044,7 +1109,7 @@ fn test_handle_iwant_msg_cached() { /// Tests that messages are sent correctly depending on the shifting of the message cache. #[test] fn test_handle_iwant_msg_cached_shifted() { - let (mut gs, peers, _) = inject_nodes1() + let (mut gs, peers, mut receivers, _) = inject_nodes1() .peer_no(20) .topics(Vec::new()) .to_subscribe(true) @@ -1077,19 +1142,29 @@ fn test_handle_iwant_msg_cached_shifted() { gs.handle_iwant(&peers[7], vec![msg_id.clone()]); // is the message is being sent? - let message_exists = gs.events.iter().any(|e| match e { - ToSwarm::NotifyHandler { - event: HandlerIn::Message(RpcOut::Forward(message)), - .. - } => { - gs.config.message_id( - &gs.data_transform - .inbound_transform(message.clone()) - .unwrap(), - ) == msg_id + let mut message_exists = false; + receivers = receivers.into_iter().map(|(peer_id, c)| { + let non_priority = c.non_priority.get_ref(); + while !non_priority.is_empty() { + if matches!(non_priority.try_recv(), Ok(RpcOut::Forward{message, timeout: _ }) if + gs.config.message_id( + &gs.data_transform + .inbound_transform(message.clone()) + .unwrap(), + ) == msg_id) + { + message_exists = true; + } } - _ => false, - }); + ( + peer_id, + Receiver { + priority_queue_len: c.priority_queue_len, + priority: c.priority, + non_priority: c.non_priority, + }, + ) + }).collect(); // default history_length is 5, expect no messages after shift > 5 if shift < 5 { assert!( @@ -1105,10 +1180,10 @@ fn test_handle_iwant_msg_cached_shifted() { } } +/// tests that an event is not created when a peers asks for a message not in our cache #[test] -// tests that an event is not created when a peers asks for a message not in our cache fn test_handle_iwant_msg_not_cached() { - let (mut gs, peers, _) = inject_nodes1() + let (mut gs, peers, _, _) = inject_nodes1() .peer_no(20) .topics(Vec::new()) .to_subscribe(true) @@ -1124,10 +1199,10 @@ fn test_handle_iwant_msg_not_cached() { ); } +/// tests that an event is created when a peer shares that it has a message we want #[test] -// tests that an event is created when a peer shares that it has a message we want fn test_handle_ihave_subscribed_and_msg_not_cached() { - let (mut gs, peers, topic_hashes) = inject_nodes1() + let (mut gs, peers, mut receivers, topic_hashes) = inject_nodes1() .peer_no(20) .topics(vec![String::from("topic1")]) .to_subscribe(true) @@ -1139,27 +1214,32 @@ fn test_handle_ihave_subscribed_and_msg_not_cached() { ); // check that we sent an IWANT request for `unknown id` - let iwant_exists = match gs.control_pool.get(&peers[7]) { - Some(controls) => controls.iter().any(|c| match c { - ControlAction::IWant { message_ids } => message_ids + let mut iwant_exists = false; + let receiver = receivers.remove(&peers[7]).unwrap(); + let non_priority = receiver.non_priority.get_ref(); + while !non_priority.is_empty() { + if let Ok(RpcOut::IWant(IWant { message_ids })) = non_priority.try_recv() { + if message_ids .iter() - .any(|m| *m == MessageId::new(b"unknown id")), - _ => false, - }), - _ => false, - }; + .any(|m| *m == MessageId::new(b"unknown id")) + { + iwant_exists = true; + break; + } + } + } assert!( iwant_exists, - "Expected to send an IWANT control message for unkown message id" + "Expected to send an IWANT control message for unknown message id" ); } +/// tests that an event is not created when a peer shares that it has a message that +/// we already have #[test] -// tests that an event is not created when a peer shares that it has a message that -// we already have fn test_handle_ihave_subscribed_and_msg_cached() { - let (mut gs, peers, topic_hashes) = inject_nodes1() + let (mut gs, peers, _, topic_hashes) = inject_nodes1() .peer_no(20) .topics(vec![String::from("topic1")]) .to_subscribe(true) @@ -1177,11 +1257,11 @@ fn test_handle_ihave_subscribed_and_msg_cached() { ) } +/// test that an event is not created when a peer shares that it has a message in +/// a topic that we are not subscribed to #[test] -// test that an event is not created when a peer shares that it has a message in -// a topic that we are not subscribed to fn test_handle_ihave_not_subscribed() { - let (mut gs, peers, _) = inject_nodes1() + let (mut gs, peers, _, _) = inject_nodes1() .peer_no(20) .topics(vec![]) .to_subscribe(true) @@ -1203,11 +1283,11 @@ fn test_handle_ihave_not_subscribed() { ) } +/// tests that a peer is added to our mesh when we are both subscribed +/// to the same topic #[test] -// tests that a peer is added to our mesh when we are both subscribed -// to the same topic fn test_handle_graft_is_subscribed() { - let (mut gs, peers, topic_hashes) = inject_nodes1() + let (mut gs, peers, _, topic_hashes) = inject_nodes1() .peer_no(20) .topics(vec![String::from("topic1")]) .to_subscribe(true) @@ -1221,11 +1301,11 @@ fn test_handle_graft_is_subscribed() { ); } +/// tests that a peer is not added to our mesh when they are subscribed to +/// a topic that we are not #[test] -// tests that a peer is not added to our mesh when they are subscribed to -// a topic that we are not fn test_handle_graft_is_not_subscribed() { - let (mut gs, peers, topic_hashes) = inject_nodes1() + let (mut gs, peers, _, topic_hashes) = inject_nodes1() .peer_no(20) .topics(vec![String::from("topic1")]) .to_subscribe(true) @@ -1242,15 +1322,15 @@ fn test_handle_graft_is_not_subscribed() { ); } +/// tests multiple topics in a single graft message #[test] -// tests multiple topics in a single graft message fn test_handle_graft_multiple_topics() { let topics: Vec = ["topic1", "topic2", "topic3", "topic4"] .iter() .map(|&t| String::from(t)) .collect(); - let (mut gs, peers, topic_hashes) = inject_nodes1() + let (mut gs, peers, _, topic_hashes) = inject_nodes1() .peer_no(20) .topics(topics) .to_subscribe(true) @@ -1272,15 +1352,15 @@ fn test_handle_graft_multiple_topics() { } assert!( - gs.mesh.get(&topic_hashes[2]).is_none(), + !gs.mesh.contains_key(&topic_hashes[2]), "Expected the second topic to not be in the mesh" ); } +/// tests that a peer is removed from our mesh #[test] -// tests that a peer is removed from our mesh fn test_handle_prune_peer_in_mesh() { - let (mut gs, peers, topic_hashes) = inject_nodes1() + let (mut gs, peers, _, topic_hashes) = inject_nodes1() .peer_no(20) .topics(vec![String::from("topic1")]) .to_subscribe(true) @@ -1307,45 +1387,77 @@ fn test_handle_prune_peer_in_mesh() { ); } -fn count_control_msgs( - gs: &Behaviour, - mut filter: impl FnMut(&PeerId, &ControlAction) -> bool, -) -> usize { - gs.control_pool - .iter() - .map(|(peer_id, actions)| actions.iter().filter(|m| filter(peer_id, m)).count()) - .sum::() - + gs.events - .iter() - .filter(|e| match e { - ToSwarm::NotifyHandler { - peer_id, - event: HandlerIn::Message(RpcOut::Control(action)), - .. - } => filter(peer_id, action), - _ => false, - }) - .count() +fn count_control_msgs( + receivers: HashMap, + mut filter: impl FnMut(&PeerId, &RpcOut) -> bool, +) -> (usize, HashMap) { + let mut new_receivers = HashMap::new(); + let mut collected_messages = 0; + for (peer_id, c) in receivers.into_iter() { + let priority = c.priority.get_ref(); + let non_priority = c.non_priority.get_ref(); + while !priority.is_empty() || !non_priority.is_empty() { + if let Ok(rpc) = priority.try_recv() { + if filter(&peer_id, &rpc) { + collected_messages += 1; + } + } + if let Ok(rpc) = non_priority.try_recv() { + if filter(&peer_id, &rpc) { + collected_messages += 1; + } + } + } + new_receivers.insert( + peer_id, + Receiver { + priority_queue_len: c.priority_queue_len, + priority: c.priority, + non_priority: c.non_priority, + }, + ); + } + (collected_messages, new_receivers) } -fn flush_events(gs: &mut Behaviour) { - gs.control_pool.clear(); +fn flush_events( + gs: &mut Behaviour, + receivers: HashMap, +) -> HashMap { gs.events.clear(); + let mut new_receivers = HashMap::new(); + for (peer_id, c) in receivers.into_iter() { + let priority = c.priority.get_ref(); + let non_priority = c.non_priority.get_ref(); + while !priority.is_empty() || !non_priority.is_empty() { + let _ = priority.try_recv(); + let _ = non_priority.try_recv(); + } + new_receivers.insert( + peer_id, + Receiver { + priority_queue_len: c.priority_queue_len, + priority: c.priority, + non_priority: c.non_priority, + }, + ); + } + new_receivers } +/// tests that a peer added as explicit peer gets connected to #[test] -// tests that a peer added as explicit peer gets connected to fn test_explicit_peer_gets_connected() { - let (mut gs, _, _) = inject_nodes1() + let (mut gs, _, _, _) = inject_nodes1() .peer_no(0) .topics(Vec::new()) .to_subscribe(true) .create_network(); - //create new peer + // create new peer let peer = PeerId::random(); - //add peer as explicit peer + // add peer as explicit peer gs.add_explicit_peer(&peer); let num_events = gs @@ -1369,7 +1481,7 @@ fn test_explicit_peer_reconnects() { .check_explicit_peers_ticks(2) .build() .unwrap(); - let (mut gs, others, _) = inject_nodes1() + let (mut gs, others, receivers, _) = inject_nodes1() .peer_no(1) .topics(Vec::new()) .to_subscribe(true) @@ -1378,17 +1490,17 @@ fn test_explicit_peer_reconnects() { let peer = others.first().unwrap(); - //add peer as explicit peer + // add peer as explicit peer gs.add_explicit_peer(peer); - flush_events(&mut gs); + flush_events(&mut gs, receivers); - //disconnect peer + // disconnect peer disconnect_peer(&mut gs, peer); gs.heartbeat(); - //check that no reconnect after first heartbeat since `explicit_peer_ticks == 2` + // check that no reconnect after first heartbeat since `explicit_peer_ticks == 2` assert_eq!( gs.events .iter() @@ -1403,7 +1515,7 @@ fn test_explicit_peer_reconnects() { gs.heartbeat(); - //check that there is a reconnect after second heartbeat + // check that there is a reconnect after second heartbeat assert!( gs.events .iter() @@ -1419,7 +1531,7 @@ fn test_explicit_peer_reconnects() { #[test] fn test_handle_graft_explicit_peer() { - let (mut gs, peers, topic_hashes) = inject_nodes1() + let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() .peer_no(1) .topics(vec![String::from("topic1"), String::from("topic2")]) .to_subscribe(true) @@ -1431,26 +1543,29 @@ fn test_handle_graft_explicit_peer() { gs.handle_graft(peer, topic_hashes.clone()); - //peer got not added to mesh + // peer got not added to mesh assert!(gs.mesh[&topic_hashes[0]].is_empty()); assert!(gs.mesh[&topic_hashes[1]].is_empty()); - //check prunes - assert!( - count_control_msgs(&gs, |peer_id, m| peer_id == peer + // check prunes + let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { + peer_id == peer && match m { - ControlAction::Prune { topic_hash, .. } => - topic_hash == &topic_hashes[0] || topic_hash == &topic_hashes[1], + RpcOut::Prune(Prune { topic_hash, .. }) => { + topic_hash == &topic_hashes[0] || topic_hash == &topic_hashes[1] + } _ => false, - }) - >= 2, + } + }); + assert!( + control_msgs >= 2, "Not enough prunes sent when grafting from explicit peer" ); } #[test] fn explicit_peers_not_added_to_mesh_on_receiving_subscription() { - let (gs, peers, topic_hashes) = inject_nodes1() + let (gs, peers, receivers, topic_hashes) = inject_nodes1() .peer_no(2) .topics(vec![String::from("topic1")]) .to_subscribe(true) @@ -1458,32 +1573,34 @@ fn explicit_peers_not_added_to_mesh_on_receiving_subscription() { .explicit(1) .create_network(); - //only peer 1 is in the mesh not peer 0 (which is an explicit peer) + // only peer 1 is in the mesh not peer 0 (which is an explicit peer) assert_eq!( gs.mesh[&topic_hashes[0]], vec![peers[1]].into_iter().collect() ); - //assert that graft gets created to non-explicit peer + // assert that graft gets created to non-explicit peer + let (control_msgs, receivers) = count_control_msgs(receivers, |peer_id, m| { + peer_id == &peers[1] && matches!(m, RpcOut::Graft { .. }) + }); assert!( - count_control_msgs(&gs, |peer_id, m| peer_id == &peers[1] - && matches!(m, ControlAction::Graft { .. })) - >= 1, + control_msgs >= 1, "No graft message got created to non-explicit peer" ); - //assert that no graft gets created to explicit peer + // assert that no graft gets created to explicit peer + let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { + peer_id == &peers[0] && matches!(m, RpcOut::Graft { .. }) + }); assert_eq!( - count_control_msgs(&gs, |peer_id, m| peer_id == &peers[0] - && matches!(m, ControlAction::Graft { .. })), - 0, + control_msgs, 0, "A graft message got created to an explicit peer" ); } #[test] fn do_not_graft_explicit_peer() { - let (mut gs, others, topic_hashes) = inject_nodes1() + let (mut gs, others, receivers, topic_hashes) = inject_nodes1() .peer_no(1) .topics(vec![String::from("topic")]) .to_subscribe(true) @@ -1493,21 +1610,22 @@ fn do_not_graft_explicit_peer() { gs.heartbeat(); - //mesh stays empty + // mesh stays empty assert_eq!(gs.mesh[&topic_hashes[0]], BTreeSet::new()); - //assert that no graft gets created to explicit peer + // assert that no graft gets created to explicit peer + let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { + peer_id == &others[0] && matches!(m, RpcOut::Graft { .. }) + }); assert_eq!( - count_control_msgs(&gs, |peer_id, m| peer_id == &others[0] - && matches!(m, ControlAction::Graft { .. })), - 0, + control_msgs, 0, "A graft message got created to an explicit peer" ); } #[test] fn do_forward_messages_to_explicit_peers() { - let (mut gs, peers, topic_hashes) = inject_nodes1() + let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() .peer_no(2) .topics(vec![String::from("topic1"), String::from("topic2")]) .to_subscribe(true) @@ -1527,21 +1645,16 @@ fn do_forward_messages_to_explicit_peers() { validated: true, }; gs.handle_received_message(message.clone(), &local_id); - assert_eq!( - gs.events - .iter() - .filter(|e| match e { - ToSwarm::NotifyHandler { - peer_id, - event: HandlerIn::Message(RpcOut::Forward(m)), - .. - } => { - peer_id == &peers[0] && m.data == message.data + receivers.into_iter().fold(0, |mut fwds, (peer_id, c)| { + let non_priority = c.non_priority.get_ref(); + while !non_priority.is_empty() { + if matches!(non_priority.try_recv(), Ok(RpcOut::Forward{message: m, timeout: _}) if peer_id == peers[0] && m.data == message.data) { + fwds +=1; + } } - _ => false, - }) - .count(), + fwds + }), 1, "The message did not get forwarded to the explicit peer" ); @@ -1549,7 +1662,7 @@ fn do_forward_messages_to_explicit_peers() { #[test] fn explicit_peers_not_added_to_mesh_on_subscribe() { - let (mut gs, peers, _) = inject_nodes1() + let (mut gs, peers, receivers, _) = inject_nodes1() .peer_no(2) .topics(Vec::new()) .to_subscribe(true) @@ -1557,7 +1670,7 @@ fn explicit_peers_not_added_to_mesh_on_subscribe() { .explicit(1) .create_network(); - //create new topic, both peers subscribing to it but we do not subscribe to it + // create new topic, both peers subscribing to it but we do not subscribe to it let topic = Topic::new(String::from("t")); let topic_hash = topic.hash(); for peer in peers.iter().take(2) { @@ -1570,32 +1683,34 @@ fn explicit_peers_not_added_to_mesh_on_subscribe() { ); } - //subscribe now to topic + // subscribe now to topic gs.subscribe(&topic).unwrap(); - //only peer 1 is in the mesh not peer 0 (which is an explicit peer) + // only peer 1 is in the mesh not peer 0 (which is an explicit peer) assert_eq!(gs.mesh[&topic_hash], vec![peers[1]].into_iter().collect()); - //assert that graft gets created to non-explicit peer + // assert that graft gets created to non-explicit peer + let (control_msgs, receivers) = count_control_msgs(receivers, |peer_id, m| { + peer_id == &peers[1] && matches!(m, RpcOut::Graft { .. }) + }); assert!( - count_control_msgs(&gs, |peer_id, m| peer_id == &peers[1] - && matches!(m, ControlAction::Graft { .. })) - > 0, + control_msgs > 0, "No graft message got created to non-explicit peer" ); - //assert that no graft gets created to explicit peer + // assert that no graft gets created to explicit peer + let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { + peer_id == &peers[0] && matches!(m, RpcOut::Graft { .. }) + }); assert_eq!( - count_control_msgs(&gs, |peer_id, m| peer_id == &peers[0] - && matches!(m, ControlAction::Graft { .. })), - 0, + control_msgs, 0, "A graft message got created to an explicit peer" ); } #[test] fn explicit_peers_not_added_to_mesh_from_fanout_on_subscribe() { - let (mut gs, peers, _) = inject_nodes1() + let (mut gs, peers, receivers, _) = inject_nodes1() .peer_no(2) .topics(Vec::new()) .to_subscribe(true) @@ -1603,7 +1718,7 @@ fn explicit_peers_not_added_to_mesh_from_fanout_on_subscribe() { .explicit(1) .create_network(); - //create new topic, both peers subscribing to it but we do not subscribe to it + // create new topic, both peers subscribing to it but we do not subscribe to it let topic = Topic::new(String::from("t")); let topic_hash = topic.hash(); for peer in peers.iter().take(2) { @@ -1616,35 +1731,37 @@ fn explicit_peers_not_added_to_mesh_from_fanout_on_subscribe() { ); } - //we send a message for this topic => this will initialize the fanout + // we send a message for this topic => this will initialize the fanout gs.publish(topic.clone(), vec![1, 2, 3]).unwrap(); - //subscribe now to topic + // subscribe now to topic gs.subscribe(&topic).unwrap(); - //only peer 1 is in the mesh not peer 0 (which is an explicit peer) + // only peer 1 is in the mesh not peer 0 (which is an explicit peer) assert_eq!(gs.mesh[&topic_hash], vec![peers[1]].into_iter().collect()); - //assert that graft gets created to non-explicit peer + // assert that graft gets created to non-explicit peer + let (control_msgs, receivers) = count_control_msgs(receivers, |peer_id, m| { + peer_id == &peers[1] && matches!(m, RpcOut::Graft { .. }) + }); assert!( - count_control_msgs(&gs, |peer_id, m| peer_id == &peers[1] - && matches!(m, ControlAction::Graft { .. })) - >= 1, + control_msgs >= 1, "No graft message got created to non-explicit peer" ); - //assert that no graft gets created to explicit peer + // assert that no graft gets created to explicit peer + let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { + peer_id == &peers[0] && matches!(m, RpcOut::Graft { .. }) + }); assert_eq!( - count_control_msgs(&gs, |peer_id, m| peer_id == &peers[0] - && matches!(m, ControlAction::Graft { .. })), - 0, + control_msgs, 0, "A graft message got created to an explicit peer" ); } #[test] fn no_gossip_gets_sent_to_explicit_peers() { - let (mut gs, peers, topic_hashes) = inject_nodes1() + let (mut gs, peers, mut receivers, topic_hashes) = inject_nodes1() .peer_no(2) .topics(vec![String::from("topic1"), String::from("topic2")]) .to_subscribe(true) @@ -1664,34 +1781,33 @@ fn no_gossip_gets_sent_to_explicit_peers() { validated: true, }; - //forward the message + // forward the message gs.handle_received_message(message, &local_id); - //simulate multiple gossip calls (for randomness) + // simulate multiple gossip calls (for randomness) for _ in 0..3 { gs.emit_gossip(); } - //assert that no gossip gets sent to explicit peer - assert_eq!( - gs.control_pool - .get(&peers[0]) - .unwrap_or(&Vec::new()) - .iter() - .filter(|m| matches!(m, ControlAction::IHave { .. })) - .count(), - 0, - "Gossip got emitted to explicit peer" - ); + // assert that no gossip gets sent to explicit peer + let receiver = receivers.remove(&peers[0]).unwrap(); + let mut gossips = 0; + let non_priority = receiver.non_priority.get_ref(); + while !non_priority.is_empty() { + if let Ok(RpcOut::IHave(_)) = non_priority.try_recv() { + gossips += 1; + } + } + assert_eq!(gossips, 0, "Gossip got emitted to explicit peer"); } -// Tests the mesh maintenance addition +/// Tests the mesh maintenance addition #[test] fn test_mesh_addition() { let config: Config = Config::default(); // Adds mesh_low peers and PRUNE 2 giving us a deficit. - let (mut gs, peers, topics) = inject_nodes1() + let (mut gs, peers, _receivers, topics) = inject_nodes1() .peer_no(config.mesh_n() + 1) .topics(vec!["test".into()]) .to_subscribe(true) @@ -1719,15 +1835,15 @@ fn test_mesh_addition() { assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), config.mesh_n()); } -// Tests the mesh maintenance subtraction +/// Tests the mesh maintenance subtraction #[test] fn test_mesh_subtraction() { let config = Config::default(); // Adds mesh_low peers and PRUNE 2 giving us a deficit. let n = config.mesh_n_high() + 10; - //make all outbound connections so that we allow grafting to all - let (mut gs, peers, topics) = inject_nodes1() + // make all outbound connections so that we allow grafting to all + let (mut gs, peers, _receivers, topics) = inject_nodes1() .peer_no(n) .topics(vec!["test".into()]) .to_subscribe(true) @@ -1751,16 +1867,16 @@ fn test_mesh_subtraction() { fn test_connect_to_px_peers_on_handle_prune() { let config: Config = Config::default(); - let (mut gs, peers, topics) = inject_nodes1() + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) .create_network(); - //handle prune from single peer with px peers + // handle prune from single peer with px peers let mut px = Vec::new(); - //propose more px peers than config.prune_peers() + // propose more px peers than config.prune_peers() for _ in 0..config.prune_peers() + 5 { px.push(PeerInfo { peer_id: Some(PeerId::random()), @@ -1776,7 +1892,7 @@ fn test_connect_to_px_peers_on_handle_prune() { )], ); - //Check DialPeer events for px peers + // Check DialPeer events for px peers let dials: Vec<_> = gs .events .iter() @@ -1794,7 +1910,7 @@ fn test_connect_to_px_peers_on_handle_prune() { // No duplicates assert_eq!(dials_set.len(), config.prune_peers()); - //all dial peers must be in px + // all dial peers must be in px assert!(dials_set.is_subset( &px.iter() .map(|i| *i.peer_id.as_ref().unwrap()) @@ -1806,14 +1922,14 @@ fn test_connect_to_px_peers_on_handle_prune() { fn test_send_px_and_backoff_in_prune() { let config: Config = Config::default(); - //build mesh with enough peers for px - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with enough peers for px + let (mut gs, peers, receivers, topics) = inject_nodes1() .peer_no(config.prune_peers() + 1) .topics(vec!["test".into()]) .to_subscribe(true) .create_network(); - //send prune to peer + // send prune to peer gs.send_graft_prune( HashMap::new(), vec![(peers[0], vec![topics[0].clone()])] @@ -1822,39 +1938,40 @@ fn test_send_px_and_backoff_in_prune() { HashSet::new(), ); - //check prune message - assert_eq!( - count_control_msgs(&gs, |peer_id, m| peer_id == &peers[0] + // check prune message + let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { + peer_id == &peers[0] && match m { - ControlAction::Prune { + RpcOut::Prune(Prune { topic_hash, peers, backoff, - } => + }) => { topic_hash == &topics[0] && peers.len() == config.prune_peers() && //all peers are different peers.iter().collect::>().len() == config.prune_peers() && - backoff.unwrap() == config.prune_backoff().as_secs(), + backoff.unwrap() == config.prune_backoff().as_secs() + } _ => false, - }), - 1 - ); + } + }); + assert_eq!(control_msgs, 1); } #[test] fn test_prune_backoffed_peer_on_graft() { let config: Config = Config::default(); - //build mesh with enough peers for px - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with enough peers for px + let (mut gs, peers, receivers, topics) = inject_nodes1() .peer_no(config.prune_peers() + 1) .topics(vec!["test".into()]) .to_subscribe(true) .create_network(); - //remove peer from mesh and send prune to peer => this adds a backoff for this peer + // remove peer from mesh and send prune to peer => this adds a backoff for this peer gs.mesh.get_mut(&topics[0]).unwrap().remove(&peers[0]); gs.send_graft_prune( HashMap::new(), @@ -1864,29 +1981,30 @@ fn test_prune_backoffed_peer_on_graft() { HashSet::new(), ); - //ignore all messages until now - gs.events.clear(); + // ignore all messages until now + let receivers = flush_events(&mut gs, receivers); - //handle graft + // handle graft gs.handle_graft(&peers[0], vec![topics[0].clone()]); - //check prune message - assert_eq!( - count_control_msgs(&gs, |peer_id, m| peer_id == &peers[0] + // check prune message + let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { + peer_id == &peers[0] && match m { - ControlAction::Prune { + RpcOut::Prune(Prune { topic_hash, peers, backoff, - } => + }) => { topic_hash == &topics[0] && //no px in this case peers.is_empty() && - backoff.unwrap() == config.prune_backoff().as_secs(), + backoff.unwrap() == config.prune_backoff().as_secs() + } _ => false, - }), - 1 - ); + } + }); + assert_eq!(control_msgs, 1); } #[test] @@ -1896,93 +2014,97 @@ fn test_do_not_graft_within_backoff_period() { .heartbeat_interval(Duration::from_millis(100)) .build() .unwrap(); - //only one peer => mesh too small and will try to regraft as early as possible - let (mut gs, peers, topics) = inject_nodes1() + // only one peer => mesh too small and will try to regraft as early as possible + let (mut gs, peers, receivers, topics) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) .gs_config(config) .create_network(); - //handle prune from peer with backoff of one second + // handle prune from peer with backoff of one second gs.handle_prune(&peers[0], vec![(topics[0].clone(), Vec::new(), Some(1))]); - //forget all events until now - flush_events(&mut gs); + // forget all events until now + let receivers = flush_events(&mut gs, receivers); - //call heartbeat + // call heartbeat gs.heartbeat(); - //Sleep for one second and apply 10 regular heartbeats (interval = 100ms). + // Sleep for one second and apply 10 regular heartbeats (interval = 100ms). for _ in 0..10 { sleep(Duration::from_millis(100)); gs.heartbeat(); } - //Check that no graft got created (we have backoff_slack = 1 therefore one more heartbeat + // Check that no graft got created (we have backoff_slack = 1 therefore one more heartbeat // is needed). + let (control_msgs, receivers) = + count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); assert_eq!( - count_control_msgs(&gs, |_, m| matches!(m, ControlAction::Graft { .. })), - 0, + control_msgs, 0, "Graft message created too early within backoff period" ); - //Heartbeat one more time this should graft now + // Heartbeat one more time this should graft now sleep(Duration::from_millis(100)); gs.heartbeat(); - //check that graft got created + // check that graft got created + let (control_msgs, _) = count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); assert!( - count_control_msgs(&gs, |_, m| matches!(m, ControlAction::Graft { .. })) > 0, + control_msgs > 0, "No graft message was created after backoff period" ); } #[test] fn test_do_not_graft_within_default_backoff_period_after_receiving_prune_without_backoff() { - //set default backoff period to 1 second + // set default backoff period to 1 second let config = ConfigBuilder::default() .prune_backoff(Duration::from_millis(90)) .backoff_slack(1) .heartbeat_interval(Duration::from_millis(100)) .build() .unwrap(); - //only one peer => mesh too small and will try to regraft as early as possible - let (mut gs, peers, topics) = inject_nodes1() + // only one peer => mesh too small and will try to regraft as early as possible + let (mut gs, peers, receivers, topics) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) .gs_config(config) .create_network(); - //handle prune from peer without a specified backoff + // handle prune from peer without a specified backoff gs.handle_prune(&peers[0], vec![(topics[0].clone(), Vec::new(), None)]); - //forget all events until now - flush_events(&mut gs); + // forget all events until now + let receivers = flush_events(&mut gs, receivers); - //call heartbeat + // call heartbeat gs.heartbeat(); - //Apply one more heartbeat + // Apply one more heartbeat sleep(Duration::from_millis(100)); gs.heartbeat(); - //Check that no graft got created (we have backoff_slack = 1 therefore one more heartbeat + // Check that no graft got created (we have backoff_slack = 1 therefore one more heartbeat // is needed). + let (control_msgs, receivers) = + count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); assert_eq!( - count_control_msgs(&gs, |_, m| matches!(m, ControlAction::Graft { .. })), - 0, + control_msgs, 0, "Graft message created too early within backoff period" ); - //Heartbeat one more time this should graft now + // Heartbeat one more time this should graft now sleep(Duration::from_millis(100)); gs.heartbeat(); - //check that graft got created + // check that graft got created + let (control_msgs, _) = count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); assert!( - count_control_msgs(&gs, |_, m| matches!(m, ControlAction::Graft { .. })) > 0, + control_msgs > 0, "No graft message was created after backoff period" ); } @@ -2001,7 +2123,7 @@ fn test_unsubscribe_backoff() { let topic = String::from("test"); // only one peer => mesh too small and will try to regraft as early as possible - let (mut gs, _, topics) = inject_nodes1() + let (mut gs, _, receivers, topics) = inject_nodes1() .peer_no(1) .topics(vec![topic.clone()]) .to_subscribe(true) @@ -2010,19 +2132,19 @@ fn test_unsubscribe_backoff() { let _ = gs.unsubscribe(&Topic::new(topic)); + let (control_msgs, receivers) = count_control_msgs(receivers, |_, m| match m { + RpcOut::Prune(Prune { backoff, .. }) => backoff == &Some(1), + _ => false, + }); assert_eq!( - count_control_msgs(&gs, |_, m| match m { - ControlAction::Prune { backoff, .. } => backoff == &Some(1), - _ => false, - }), - 1, + control_msgs, 1, "Peer should be pruned with `unsubscribe_backoff`." ); let _ = gs.subscribe(&Topic::new(topics[0].to_string())); // forget all events until now - flush_events(&mut gs); + let receivers = flush_events(&mut gs, receivers); // call heartbeat gs.heartbeat(); @@ -2035,9 +2157,10 @@ fn test_unsubscribe_backoff() { // Check that no graft got created (we have backoff_slack = 1 therefore one more heartbeat // is needed). + let (control_msgs, receivers) = + count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); assert_eq!( - count_control_msgs(&gs, |_, m| matches!(m, ControlAction::Graft { .. })), - 0, + control_msgs, 0, "Graft message created too early within backoff period" ); @@ -2046,8 +2169,9 @@ fn test_unsubscribe_backoff() { gs.heartbeat(); // check that graft got created + let (control_msgs, _) = count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); assert!( - count_control_msgs(&gs, |_, m| matches!(m, ControlAction::Graft { .. })) > 0, + control_msgs > 0, "No graft message was created after backoff period" ); } @@ -2058,28 +2182,27 @@ fn test_flood_publish() { let topic = "test"; // Adds more peers than mesh can hold to test flood publishing - let (mut gs, _, _) = inject_nodes1() + let (mut gs, _, receivers, _) = inject_nodes1() .peer_no(config.mesh_n_high() + 10) .topics(vec![topic.into()]) .to_subscribe(true) .create_network(); - //publish message + // publish message let publish_data = vec![0; 42]; gs.publish(Topic::new(topic), publish_data).unwrap(); // Collect all publish messages - let publishes = gs - .events - .into_iter() - .fold(vec![], |mut collected_publish, e| match e { - ToSwarm::NotifyHandler { event, .. } => { - if let HandlerIn::Message(RpcOut::Publish(message)) = event { + let publishes = receivers + .into_values() + .fold(vec![], |mut collected_publish, c| { + let priority = c.priority.get_ref(); + while !priority.is_empty() { + if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { collected_publish.push(message); } - collected_publish } - _ => collected_publish, + collected_publish }); // Transform the inbound message @@ -2112,15 +2235,15 @@ fn test_flood_publish() { fn test_gossip_to_at_least_gossip_lazy_peers() { let config: Config = Config::default(); - //add more peers than in mesh to test gossipping - //by default only mesh_n_low peers will get added to mesh - let (mut gs, _, topic_hashes) = inject_nodes1() + // add more peers than in mesh to test gossipping + // by default only mesh_n_low peers will get added to mesh + let (mut gs, _, receivers, topic_hashes) = inject_nodes1() .peer_no(config.mesh_n_low() + config.gossip_lazy() + 1) .topics(vec!["topic".into()]) .to_subscribe(true) .create_network(); - //receive message + // receive message let raw_message = RawMessage { source: Some(PeerId::random()), data: vec![], @@ -2132,7 +2255,7 @@ fn test_gossip_to_at_least_gossip_lazy_peers() { }; gs.handle_received_message(raw_message.clone(), &PeerId::random()); - //emit gossip + // emit gossip gs.emit_gossip(); // Transform the inbound message @@ -2140,32 +2263,30 @@ fn test_gossip_to_at_least_gossip_lazy_peers() { let msg_id = gs.config.message_id(message); - //check that exactly config.gossip_lazy() many gossip messages were sent. - assert_eq!( - count_control_msgs(&gs, |_, action| match action { - ControlAction::IHave { - topic_hash, - message_ids, - } => topic_hash == &topic_hashes[0] && message_ids.iter().any(|id| id == &msg_id), - _ => false, - }), - config.gossip_lazy() - ); + // check that exactly config.gossip_lazy() many gossip messages were sent. + let (control_msgs, _) = count_control_msgs(receivers, |_, action| match action { + RpcOut::IHave(IHave { + topic_hash, + message_ids, + }) => topic_hash == &topic_hashes[0] && message_ids.iter().any(|id| id == &msg_id), + _ => false, + }); + assert_eq!(control_msgs, config.gossip_lazy()); } #[test] fn test_gossip_to_at_most_gossip_factor_peers() { let config: Config = Config::default(); - //add a lot of peers + // add a lot of peers let m = config.mesh_n_low() + config.gossip_lazy() * (2.0 / config.gossip_factor()) as usize; - let (mut gs, _, topic_hashes) = inject_nodes1() + let (mut gs, _, receivers, topic_hashes) = inject_nodes1() .peer_no(m) .topics(vec!["topic".into()]) .to_subscribe(true) .create_network(); - //receive message + // receive message let raw_message = RawMessage { source: Some(PeerId::random()), data: vec![], @@ -2177,22 +2298,23 @@ fn test_gossip_to_at_most_gossip_factor_peers() { }; gs.handle_received_message(raw_message.clone(), &PeerId::random()); - //emit gossip + // emit gossip gs.emit_gossip(); // Transform the inbound message let message = &gs.data_transform.inbound_transform(raw_message).unwrap(); let msg_id = gs.config.message_id(message); - //check that exactly config.gossip_lazy() many gossip messages were sent. + // check that exactly config.gossip_lazy() many gossip messages were sent. + let (control_msgs, _) = count_control_msgs(receivers, |_, action| match action { + RpcOut::IHave(IHave { + topic_hash, + message_ids, + }) => topic_hash == &topic_hashes[0] && message_ids.iter().any(|id| id == &msg_id), + _ => false, + }); assert_eq!( - count_control_msgs(&gs, |_, action| match action { - ControlAction::IHave { - topic_hash, - message_ids, - } => topic_hash == &topic_hashes[0] && message_ids.iter().any(|id| id == &msg_id), - _ => false, - }), + control_msgs, ((m - config.mesh_n_low()) as f64 * config.gossip_factor()) as usize ); } @@ -2201,8 +2323,8 @@ fn test_gossip_to_at_most_gossip_factor_peers() { fn test_accept_only_outbound_peer_grafts_when_mesh_full() { let config: Config = Config::default(); - //enough peers to fill the mesh - let (mut gs, peers, topics) = inject_nodes1() + // enough peers to fill the mesh + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(config.mesh_n_high()) .topics(vec!["test".into()]) .to_subscribe(true) @@ -2213,30 +2335,30 @@ fn test_accept_only_outbound_peer_grafts_when_mesh_full() { gs.handle_graft(&peer, topics.clone()); } - //assert current mesh size + // assert current mesh size assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n_high()); - //create an outbound and an inbound peer - let inbound = add_peer(&mut gs, &topics, false, false); - let outbound = add_peer(&mut gs, &topics, true, false); + // create an outbound and an inbound peer + let (inbound, _in_reciver) = add_peer(&mut gs, &topics, false, false); + let (outbound, _out_receiver) = add_peer(&mut gs, &topics, true, false); - //send grafts + // send grafts gs.handle_graft(&inbound, vec![topics[0].clone()]); gs.handle_graft(&outbound, vec![topics[0].clone()]); - //assert mesh size + // assert mesh size assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n_high() + 1); - //inbound is not in mesh + // inbound is not in mesh assert!(!gs.mesh[&topics[0]].contains(&inbound)); - //outbound is in mesh + // outbound is in mesh assert!(gs.mesh[&topics[0]].contains(&outbound)); } #[test] fn test_do_not_remove_too_many_outbound_peers() { - //use an extreme case to catch errors with high probability + // use an extreme case to catch errors with high probability let m = 50; let n = 2 * m; let config = ConfigBuilder::default() @@ -2247,8 +2369,8 @@ fn test_do_not_remove_too_many_outbound_peers() { .build() .unwrap(); - //fill the mesh with inbound connections - let (mut gs, peers, topics) = inject_nodes1() + // fill the mesh with inbound connections + let (mut gs, peers, _receivers, topics) = inject_nodes1() .peer_no(n) .topics(vec!["test".into()]) .to_subscribe(true) @@ -2260,15 +2382,15 @@ fn test_do_not_remove_too_many_outbound_peers() { gs.handle_graft(&peer, topics.clone()); } - //create m outbound connections and graft (we will accept the graft) + // create m outbound connections and graft (we will accept the graft) let mut outbound = HashSet::new(); for _ in 0..m { - let peer = add_peer(&mut gs, &topics, true, false); + let (peer, _) = add_peer(&mut gs, &topics, true, false); outbound.insert(peer); gs.handle_graft(&peer, topics.clone()); } - //mesh is overly full + // mesh is overly full assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), n + m); // run a heartbeat @@ -2277,7 +2399,7 @@ fn test_do_not_remove_too_many_outbound_peers() { // Peers should be removed to reach n assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), n); - //all outbound peers are still in the mesh + // all outbound peers are still in the mesh assert!(outbound.iter().all(|p| gs.mesh[&topics[0]].contains(p))); } @@ -2286,7 +2408,7 @@ fn test_add_outbound_peers_if_min_is_not_satisfied() { let config: Config = Config::default(); // Fill full mesh with inbound peers - let (mut gs, peers, topics) = inject_nodes1() + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(config.mesh_n_high()) .topics(vec!["test".into()]) .to_subscribe(true) @@ -2297,9 +2419,10 @@ fn test_add_outbound_peers_if_min_is_not_satisfied() { gs.handle_graft(&peer, topics.clone()); } - //create config.mesh_outbound_min() many outbound connections without grafting + // create config.mesh_outbound_min() many outbound connections without grafting + let mut peers = vec![]; for _ in 0..config.mesh_outbound_min() { - add_peer(&mut gs, &topics, true, false); + peers.push(add_peer(&mut gs, &topics, true, false)); } // Nothing changed in the mesh yet @@ -2319,8 +2442,8 @@ fn test_add_outbound_peers_if_min_is_not_satisfied() { fn test_prune_negative_scored_peers() { let config = Config::default(); - //build mesh with one peer - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with one peer + let (mut gs, peers, receivers, topics) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) @@ -2333,39 +2456,40 @@ fn test_prune_negative_scored_peers() { ))) .create_network(); - //add penalty to peer + // add penalty to peer gs.peer_score.as_mut().unwrap().0.add_penalty(&peers[0], 1); - //execute heartbeat + // execute heartbeat gs.heartbeat(); - //peer should not be in mesh anymore + // peer should not be in mesh anymore assert!(gs.mesh[&topics[0]].is_empty()); - //check prune message - assert_eq!( - count_control_msgs(&gs, |peer_id, m| peer_id == &peers[0] + // check prune message + let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { + peer_id == &peers[0] && match m { - ControlAction::Prune { + RpcOut::Prune(Prune { topic_hash, peers, backoff, - } => + }) => { topic_hash == &topics[0] && //no px in this case peers.is_empty() && - backoff.unwrap() == config.prune_backoff().as_secs(), + backoff.unwrap() == config.prune_backoff().as_secs() + } _ => false, - }), - 1 - ); + } + }); + assert_eq!(control_msgs, 1); } #[test] fn test_dont_graft_to_negative_scored_peers() { let config = Config::default(); - //init full mesh - let (mut gs, peers, topics) = inject_nodes1() + // init full mesh + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(config.mesh_n_high()) .topics(vec!["test".into()]) .to_subscribe(true) @@ -2376,35 +2500,35 @@ fn test_dont_graft_to_negative_scored_peers() { ))) .create_network(); - //add two additional peers that will not be part of the mesh - let p1 = add_peer(&mut gs, &topics, false, false); - let p2 = add_peer(&mut gs, &topics, false, false); + // add two additional peers that will not be part of the mesh + let (p1, _receiver1) = add_peer(&mut gs, &topics, false, false); + let (p2, _receiver2) = add_peer(&mut gs, &topics, false, false); - //reduce score of p1 to negative + // reduce score of p1 to negative gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 1); - //handle prunes of all other peers + // handle prunes of all other peers for p in peers { gs.handle_prune(&p, vec![(topics[0].clone(), Vec::new(), None)]); } - //heartbeat + // heartbeat gs.heartbeat(); - //assert that mesh only contains p2 + // assert that mesh only contains p2 assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), 1); assert!(gs.mesh.get(&topics[0]).unwrap().contains(&p2)); } -///Note that in this test also without a penalty the px would be ignored because of the -/// acceptPXThreshold, but the spec still explicitely states the rule that px from negative +/// Note that in this test also without a penalty the px would be ignored because of the +/// acceptPXThreshold, but the spec still explicitly states the rule that px from negative /// peers should get ignored, therefore we test it here. #[test] fn test_ignore_px_from_negative_scored_peer() { let config = Config::default(); - //build mesh with one peer - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with one peer + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) @@ -2415,10 +2539,10 @@ fn test_ignore_px_from_negative_scored_peer() { ))) .create_network(); - //penalize peer + // penalize peer gs.peer_score.as_mut().unwrap().0.add_penalty(&peers[0], 1); - //handle prune from single peer with px peers + // handle prune from single peer with px peers let px = vec![PeerInfo { peer_id: Some(PeerId::random()), }]; @@ -2432,7 +2556,7 @@ fn test_ignore_px_from_negative_scored_peer() { )], ); - //assert no dials + // assert no dials assert_eq!( gs.events .iter() @@ -2451,7 +2575,7 @@ fn test_only_send_nonnegative_scoring_peers_in_px() { .unwrap(); // Build mesh with three peer - let (mut gs, peers, topics) = inject_nodes1() + let (mut gs, peers, receivers, topics) = inject_nodes1() .peer_no(3) .topics(vec!["test".into()]) .to_subscribe(true) @@ -2477,21 +2601,22 @@ fn test_only_send_nonnegative_scoring_peers_in_px() { ); // Check that px in prune message only contains third peer - assert_eq!( - count_control_msgs(&gs, |peer_id, m| peer_id == &peers[1] + let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { + peer_id == &peers[1] && match m { - ControlAction::Prune { + RpcOut::Prune(Prune { topic_hash, peers: px, .. - } => + }) => { topic_hash == &topics[0] && px.len() == 1 - && px[0].peer_id.as_ref().unwrap() == &peers[2], + && px[0].peer_id.as_ref().unwrap() == &peers[2] + } _ => false, - }), - 1 - ); + } + }); + assert_eq!(control_msgs, 1); } #[test] @@ -2504,7 +2629,7 @@ fn test_do_not_gossip_to_peers_below_gossip_threshold() { }; // Build full mesh - let (mut gs, peers, topics) = inject_nodes1() + let (mut gs, peers, mut receivers, topics) = inject_nodes1() .peer_no(config.mesh_n_high()) .topics(vec!["test".into()]) .to_subscribe(true) @@ -2518,8 +2643,10 @@ fn test_do_not_gossip_to_peers_below_gossip_threshold() { } // Add two additional peers that will not be part of the mesh - let p1 = add_peer(&mut gs, &topics, false, false); - let p2 = add_peer(&mut gs, &topics, false, false); + let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); + receivers.insert(p1, receiver1); + let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); + receivers.insert(p2, receiver2); // Reduce score of p1 below peer_score_thresholds.gossip_threshold // note that penalties get squared so two penalties means a score of @@ -2550,23 +2677,21 @@ fn test_do_not_gossip_to_peers_below_gossip_threshold() { gs.emit_gossip(); // Check that exactly one gossip messages got sent and it got sent to p2 - assert_eq!( - count_control_msgs(&gs, |peer, action| match action { - ControlAction::IHave { - topic_hash, - message_ids, - } => { - if topic_hash == &topics[0] && message_ids.iter().any(|id| id == &msg_id) { - assert_eq!(peer, &p2); - true - } else { - false - } + let (control_msgs, _) = count_control_msgs(receivers, |peer, action| match action { + RpcOut::IHave(IHave { + topic_hash, + message_ids, + }) => { + if topic_hash == &topics[0] && message_ids.iter().any(|id| id == &msg_id) { + assert_eq!(peer, &p2); + true + } else { + false } - _ => false, - }), - 1 - ); + } + _ => false, + }); + assert_eq!(control_msgs, 1); } #[test] @@ -2579,7 +2704,7 @@ fn test_iwant_msg_from_peer_below_gossip_threshold_gets_ignored() { }; // Build full mesh - let (mut gs, peers, topics) = inject_nodes1() + let (mut gs, peers, mut receivers, topics) = inject_nodes1() .peer_no(config.mesh_n_high()) .topics(vec!["test".into()]) .to_subscribe(true) @@ -2595,8 +2720,10 @@ fn test_iwant_msg_from_peer_below_gossip_threshold_gets_ignored() { } // Add two additional peers that will not be part of the mesh - let p1 = add_peer(&mut gs, &topics, false, false); - let p2 = add_peer(&mut gs, &topics, false, false); + let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); + receivers.insert(p1, receiver1); + let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); + receivers.insert(p2, receiver2); // Reduce score of p1 below peer_score_thresholds.gossip_threshold // note that penalties get squared so two penalties means a score of @@ -2627,20 +2754,20 @@ fn test_iwant_msg_from_peer_below_gossip_threshold_gets_ignored() { gs.handle_iwant(&p2, vec![msg_id.clone()]); // the messages we are sending - let sent_messages = gs - .events - .into_iter() - .fold(vec![], |mut collected_messages, e| match e { - ToSwarm::NotifyHandler { event, peer_id, .. } => { - if let HandlerIn::Message(RpcOut::Forward(message)) = event { - collected_messages.push((peer_id, message)); + let sent_messages = + receivers + .into_iter() + .fold(vec![], |mut collected_messages, (peer_id, c)| { + let non_priority = c.non_priority.get_ref(); + while !non_priority.is_empty() { + if let Ok(RpcOut::Forward { message, .. }) = non_priority.try_recv() { + collected_messages.push((peer_id, message)); + } } collected_messages - } - _ => collected_messages, - }); + }); - //the message got sent to p2 + // the message got sent to p2 assert!(sent_messages .iter() .map(|(peer_id, msg)| ( @@ -2648,7 +2775,7 @@ fn test_iwant_msg_from_peer_below_gossip_threshold_gets_ignored() { gs.data_transform.inbound_transform(msg.clone()).unwrap() )) .any(|(peer_id, msg)| peer_id == &p2 && gs.config.message_id(&msg) == msg_id)); - //the message got not sent to p1 + // the message got not sent to p1 assert!(sent_messages .iter() .map(|(peer_id, msg)| ( @@ -2666,8 +2793,8 @@ fn test_ihave_msg_from_peer_below_gossip_threshold_gets_ignored() { gossip_threshold: 3.0 * peer_score_params.behaviour_penalty_weight, ..PeerScoreThresholds::default() }; - //build full mesh - let (mut gs, peers, topics) = inject_nodes1() + // build full mesh + let (mut gs, peers, mut receivers, topics) = inject_nodes1() .peer_no(config.mesh_n_high()) .topics(vec!["test".into()]) .to_subscribe(true) @@ -2682,19 +2809,21 @@ fn test_ihave_msg_from_peer_below_gossip_threshold_gets_ignored() { gs.handle_graft(&peer, topics.clone()); } - //add two additional peers that will not be part of the mesh - let p1 = add_peer(&mut gs, &topics, false, false); - let p2 = add_peer(&mut gs, &topics, false, false); + // add two additional peers that will not be part of the mesh + let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); + receivers.insert(p1, receiver1); + let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); + receivers.insert(p2, receiver2); - //reduce score of p1 below peer_score_thresholds.gossip_threshold - //note that penalties get squared so two penalties means a score of + // reduce score of p1 below peer_score_thresholds.gossip_threshold + // note that penalties get squared so two penalties means a score of // 4 * peer_score_params.behaviour_penalty_weight. gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - //reduce score of p2 below 0 but not below peer_score_thresholds.gossip_threshold + // reduce score of p2 below 0 but not below peer_score_thresholds.gossip_threshold gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - //message that other peers have + // message that other peers have let raw_message = RawMessage { source: Some(PeerId::random()), data: vec![], @@ -2714,19 +2843,18 @@ fn test_ihave_msg_from_peer_below_gossip_threshold_gets_ignored() { gs.handle_ihave(&p2, vec![(topics[0].clone(), vec![msg_id.clone()])]); // check that we sent exactly one IWANT request to p2 - assert_eq!( - count_control_msgs(&gs, |peer, c| match c { - ControlAction::IWant { message_ids } => - if message_ids.iter().any(|m| m == &msg_id) { - assert_eq!(peer, &p2); - true - } else { - false - }, - _ => false, - }), - 1 - ); + let (control_msgs, _) = count_control_msgs(receivers, |peer, c| match c { + RpcOut::IWant(IWant { message_ids }) => { + if message_ids.iter().any(|m| m == &msg_id) { + assert_eq!(peer, &p2); + true + } else { + false + } + } + _ => false, + }); + assert_eq!(control_msgs, 1); } #[test] @@ -2742,29 +2870,31 @@ fn test_do_not_publish_to_peer_below_publish_threshold() { ..PeerScoreThresholds::default() }; - //build mesh with no peers and no subscribed topics - let (mut gs, _, _) = inject_nodes1() + // build mesh with no peers and no subscribed topics + let (mut gs, _, mut receivers, _) = inject_nodes1() .gs_config(config) .scoring(Some((peer_score_params, peer_score_thresholds))) .create_network(); - //create a new topic for which we are not subscribed + // create a new topic for which we are not subscribed let topic = Topic::new("test"); let topics = vec![topic.hash()]; - //add two additional peers that will be added to the mesh - let p1 = add_peer(&mut gs, &topics, false, false); - let p2 = add_peer(&mut gs, &topics, false, false); + // add two additional peers that will be added to the mesh + let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); + receivers.insert(p1, receiver1); + let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); + receivers.insert(p2, receiver2); - //reduce score of p1 below peer_score_thresholds.publish_threshold - //note that penalties get squared so two penalties means a score of + // reduce score of p1 below peer_score_thresholds.publish_threshold + // note that penalties get squared so two penalties means a score of // 4 * peer_score_params.behaviour_penalty_weight. gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - //reduce score of p2 below 0 but not below peer_score_thresholds.publish_threshold + // reduce score of p2 below 0 but not below peer_score_thresholds.publish_threshold gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - //a heartbeat will remove the peers from the mesh + // a heartbeat will remove the peers from the mesh gs.heartbeat(); // publish on topic @@ -2772,20 +2902,19 @@ fn test_do_not_publish_to_peer_below_publish_threshold() { gs.publish(topic, publish_data).unwrap(); // Collect all publish messages - let publishes = gs - .events + let publishes = receivers .into_iter() - .fold(vec![], |mut collected_publish, e| match e { - ToSwarm::NotifyHandler { event, peer_id, .. } => { - if let HandlerIn::Message(RpcOut::Publish(message)) = event { + .fold(vec![], |mut collected_publish, (peer_id, c)| { + let priority = c.priority.get_ref(); + while !priority.is_empty() { + if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { collected_publish.push((peer_id, message)); } - collected_publish } - _ => collected_publish, + collected_publish }); - //assert only published to p2 + // assert only published to p2 assert_eq!(publishes.len(), 1); assert_eq!(publishes[0].0, p2); } @@ -2799,26 +2928,28 @@ fn test_do_not_flood_publish_to_peer_below_publish_threshold() { publish_threshold: 3.0 * peer_score_params.behaviour_penalty_weight, ..PeerScoreThresholds::default() }; - //build mesh with no peers - let (mut gs, _, topics) = inject_nodes1() + // build mesh with no peers + let (mut gs, _, mut receivers, topics) = inject_nodes1() .topics(vec!["test".into()]) .gs_config(config) .scoring(Some((peer_score_params, peer_score_thresholds))) .create_network(); - //add two additional peers that will be added to the mesh - let p1 = add_peer(&mut gs, &topics, false, false); - let p2 = add_peer(&mut gs, &topics, false, false); + // add two additional peers that will be added to the mesh + let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); + receivers.insert(p1, receiver1); + let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); + receivers.insert(p2, receiver2); - //reduce score of p1 below peer_score_thresholds.publish_threshold - //note that penalties get squared so two penalties means a score of + // reduce score of p1 below peer_score_thresholds.publish_threshold + // note that penalties get squared so two penalties means a score of // 4 * peer_score_params.behaviour_penalty_weight. gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - //reduce score of p2 below 0 but not below peer_score_thresholds.publish_threshold + // reduce score of p2 below 0 but not below peer_score_thresholds.publish_threshold gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - //a heartbeat will remove the peers from the mesh + // a heartbeat will remove the peers from the mesh gs.heartbeat(); // publish on topic @@ -2826,20 +2957,19 @@ fn test_do_not_flood_publish_to_peer_below_publish_threshold() { gs.publish(Topic::new("test"), publish_data).unwrap(); // Collect all publish messages - let publishes = gs - .events + let publishes = receivers .into_iter() - .fold(vec![], |mut collected_publish, e| match e { - ToSwarm::NotifyHandler { event, peer_id, .. } => { - if let HandlerIn::Message(RpcOut::Publish(message)) = event { - collected_publish.push((peer_id, message)); + .fold(vec![], |mut collected_publish, (peer_id, c)| { + let priority = c.priority.get_ref(); + while !priority.is_empty() { + if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { + collected_publish.push((peer_id, message)) } - collected_publish } - _ => collected_publish, + collected_publish }); - //assert only published to p2 + // assert only published to p2 assert_eq!(publishes.len(), 1); assert!(publishes[0].0 == p2); } @@ -2855,23 +2985,23 @@ fn test_ignore_rpc_from_peers_below_graylist_threshold() { ..PeerScoreThresholds::default() }; - //build mesh with no peers - let (mut gs, _, topics) = inject_nodes1() + // build mesh with no peers + let (mut gs, _, _, topics) = inject_nodes1() .topics(vec!["test".into()]) .gs_config(config.clone()) .scoring(Some((peer_score_params, peer_score_thresholds))) .create_network(); - //add two additional peers that will be added to the mesh - let p1 = add_peer(&mut gs, &topics, false, false); - let p2 = add_peer(&mut gs, &topics, false, false); + // add two additional peers that will be added to the mesh + let (p1, _receiver1) = add_peer(&mut gs, &topics, false, false); + let (p2, _receiver2) = add_peer(&mut gs, &topics, false, false); - //reduce score of p1 below peer_score_thresholds.graylist_threshold - //note that penalties get squared so two penalties means a score of + // reduce score of p1 below peer_score_thresholds.graylist_threshold + // note that penalties get squared so two penalties means a score of // 4 * peer_score_params.behaviour_penalty_weight. gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - //reduce score of p2 below publish_threshold but not below graylist_threshold + // reduce score of p2 below publish_threshold but not below graylist_threshold gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); let raw_message1 = RawMessage { @@ -2925,15 +3055,15 @@ fn test_ignore_rpc_from_peers_below_graylist_threshold() { topic_hash: topics[0].clone(), }; - let control_action = ControlAction::IHave { + let control_action = ControlAction::IHave(IHave { topic_hash: topics[0].clone(), message_ids: vec![config.message_id(message2)], - }; + }); - //clear events + // clear events gs.events.clear(); - //receive from p1 + // receive from p1 gs.on_connection_handler_event( p1, ConnectionId::new_unchecked(0), @@ -2947,19 +3077,19 @@ fn test_ignore_rpc_from_peers_below_graylist_threshold() { }, ); - //only the subscription event gets processed, the rest is dropped + // only the subscription event gets processed, the rest is dropped assert_eq!(gs.events.len(), 1); assert!(matches!( gs.events[0], ToSwarm::GenerateEvent(Event::Subscribed { .. }) )); - let control_action = ControlAction::IHave { + let control_action = ControlAction::IHave(IHave { topic_hash: topics[0].clone(), message_ids: vec![config.message_id(message4)], - }; + }); - //receive from p2 + // receive from p2 gs.on_connection_handler_event( p2, ConnectionId::new_unchecked(0), @@ -2973,7 +3103,7 @@ fn test_ignore_rpc_from_peers_below_graylist_threshold() { }, ); - //events got processed + // events got processed assert!(gs.events.len() > 1); } @@ -2986,7 +3116,7 @@ fn test_ignore_px_from_peers_below_accept_px_threshold() { ..PeerScoreThresholds::default() }; // Build mesh with two peers - let (mut gs, peers, topics) = inject_nodes1() + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(2) .topics(vec!["test".into()]) .to_subscribe(true) @@ -3022,7 +3152,7 @@ fn test_ignore_px_from_peers_below_accept_px_threshold() { 0 ); - //handle prune from peer peers[1] with px peers + // handle prune from peer peers[1] with px peers let px = vec![PeerInfo { peer_id: Some(PeerId::random()), }]; @@ -3035,7 +3165,7 @@ fn test_ignore_px_from_peers_below_accept_px_threshold() { )], ); - //assert there are dials now + // assert there are dials now assert!( gs.events .iter() @@ -3055,9 +3185,9 @@ fn test_keep_best_scoring_peers_on_oversubscription() { .build() .unwrap(); - //build mesh with more peers than mesh can hold + // build mesh with more peers than mesh can hold let n = config.mesh_n_high() + 1; - let (mut gs, peers, topics) = inject_nodes1() + let (mut gs, peers, _receivers, topics) = inject_nodes1() .peer_no(n) .topics(vec!["test".into()]) .to_subscribe(true) @@ -3075,21 +3205,21 @@ fn test_keep_best_scoring_peers_on_oversubscription() { gs.handle_graft(peer, topics.clone()); } - //assign scores to peers equalling their index + // assign scores to peers equalling their index - //set random positive scores + // set random positive scores for (index, peer) in peers.iter().enumerate() { gs.set_application_score(peer, index as f64); } assert_eq!(gs.mesh[&topics[0]].len(), n); - //heartbeat to prune some peers + // heartbeat to prune some peers gs.heartbeat(); assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n()); - //mesh contains retain_scores best peers + // mesh contains retain_scores best peers assert!(gs.mesh[&topics[0]].is_superset( &peers[(n - config.retain_scores())..] .iter() @@ -3116,8 +3246,8 @@ fn test_scoring_p1() { .insert(topic_hash, topic_params.clone()); let peer_score_thresholds = PeerScoreThresholds::default(); - //build mesh with one peer - let (mut gs, peers, _) = inject_nodes1() + // build mesh with one peer + let (mut gs, peers, _, _) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) @@ -3127,9 +3257,9 @@ fn test_scoring_p1() { .scoring(Some((peer_score_params, peer_score_thresholds))) .create_network(); - //sleep for 2 times the mesh_quantum + // sleep for 2 times the mesh_quantum sleep(topic_params.time_in_mesh_quantum * 2); - //refresh scores + // refresh scores gs.peer_score.as_mut().unwrap().0.refresh_scores(); assert!( gs.peer_score.as_ref().unwrap().0.score(&peers[0]) @@ -3142,9 +3272,9 @@ fn test_scoring_p1() { "score should be less than 3 * time_in_mesh_weight * topic_weight" ); - //sleep again for 2 times the mesh_quantum + // sleep again for 2 times the mesh_quantum sleep(topic_params.time_in_mesh_quantum * 2); - //refresh scores + // refresh scores gs.peer_score.as_mut().unwrap().0.refresh_scores(); assert!( gs.peer_score.as_ref().unwrap().0.score(&peers[0]) @@ -3152,9 +3282,9 @@ fn test_scoring_p1() { "score should be at least 4 * time_in_mesh_weight * topic_weight" ); - //sleep for enough periods to reach maximum + // sleep for enough periods to reach maximum sleep(topic_params.time_in_mesh_quantum * (topic_params.time_in_mesh_cap - 3.0) as u32); - //refresh scores + // refresh scores gs.peer_score.as_mut().unwrap().0.refresh_scores(); assert_eq!( gs.peer_score.as_ref().unwrap().0.score(&peers[0]), @@ -3165,7 +3295,7 @@ fn test_scoring_p1() { ); } -fn random_message(seq: &mut u64, topics: &Vec) -> RawMessage { +fn random_message(seq: &mut u64, topics: &[TopicHash]) -> RawMessage { let mut rng = rand::thread_rng(); *seq += 1; RawMessage { @@ -3186,7 +3316,7 @@ fn test_scoring_p2() { let topic = Topic::new("test"); let topic_hash = topic.hash(); let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh + time_in_mesh_weight: 0.0, // deactivate time in mesh first_message_deliveries_weight: 2.0, first_message_deliveries_cap: 10.0, first_message_deliveries_decay: 0.9, @@ -3198,8 +3328,8 @@ fn test_scoring_p2() { .insert(topic_hash, topic_params.clone()); let peer_score_thresholds = PeerScoreThresholds::default(); - //build mesh with one peer - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with one peer + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(2) .topics(vec!["test".into()]) .to_subscribe(true) @@ -3215,9 +3345,9 @@ fn test_scoring_p2() { }; let m1 = random_message(&mut seq, &topics); - //peer 0 delivers message first + // peer 0 delivers message first deliver_message(&mut gs, 0, m1.clone()); - //peer 1 delivers message second + // peer 1 delivers message second deliver_message(&mut gs, 1, m1); assert_eq!( @@ -3232,7 +3362,7 @@ fn test_scoring_p2() { "there should be no score for second message deliveries * topic_weight" ); - //peer 2 delivers two new messages + // peer 2 delivers two new messages deliver_message(&mut gs, 1, random_message(&mut seq, &topics)); deliver_message(&mut gs, 1, random_message(&mut seq, &topics)); assert_eq!( @@ -3241,7 +3371,7 @@ fn test_scoring_p2() { "score should be exactly 2 * first_message_deliveries_weight * topic_weight" ); - //test decaying + // test decaying gs.peer_score.as_mut().unwrap().0.refresh_scores(); assert_eq!( @@ -3262,7 +3392,7 @@ fn test_scoring_p2() { first_message_deliveries_weight * topic_weight" ); - //test cap + // test cap for _ in 0..topic_params.first_message_deliveries_cap as u64 { deliver_message(&mut gs, 1, random_message(&mut seq, &topics)); } @@ -3284,8 +3414,8 @@ fn test_scoring_p3() { let topic = Topic::new("test"); let topic_hash = topic.hash(); let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries + time_in_mesh_weight: 0.0, // deactivate time in mesh + first_message_deliveries_weight: 0.0, // deactivate first time deliveries mesh_message_deliveries_weight: -2.0, mesh_message_deliveries_decay: 0.9, mesh_message_deliveries_cap: 10.0, @@ -3298,8 +3428,8 @@ fn test_scoring_p3() { peer_score_params.topics.insert(topic_hash, topic_params); let peer_score_thresholds = PeerScoreThresholds::default(); - //build mesh with two peers - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with two peers + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(2) .topics(vec!["test".into()]) .to_subscribe(true) @@ -3316,35 +3446,35 @@ fn test_scoring_p3() { let mut expected_message_deliveries = 0.0; - //messages used to test window + // messages used to test window let m1 = random_message(&mut seq, &topics); let m2 = random_message(&mut seq, &topics); - //peer 1 delivers m1 + // peer 1 delivers m1 deliver_message(&mut gs, 1, m1.clone()); - //peer 0 delivers two message + // peer 0 delivers two message deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); expected_message_deliveries += 2.0; sleep(Duration::from_millis(60)); - //peer 1 delivers m2 + // peer 1 delivers m2 deliver_message(&mut gs, 1, m2.clone()); sleep(Duration::from_millis(70)); - //peer 0 delivers m1 and m2 only m2 gets counted + // peer 0 delivers m1 and m2 only m2 gets counted deliver_message(&mut gs, 0, m1); deliver_message(&mut gs, 0, m2); expected_message_deliveries += 1.0; sleep(Duration::from_millis(900)); - //message deliveries penalties get activated, peer 0 has only delivered 3 messages and + // message deliveries penalties get activated, peer 0 has only delivered 3 messages and // therefore gets a penalty gs.peer_score.as_mut().unwrap().0.refresh_scores(); - expected_message_deliveries *= 0.9; //decay + expected_message_deliveries *= 0.9; // decay assert_eq!( gs.peer_score.as_ref().unwrap().0.score(&peers[0]), @@ -3360,10 +3490,10 @@ fn test_scoring_p3() { assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - //apply 10 decays + // apply 10 decays for _ in 0..10 { gs.peer_score.as_mut().unwrap().0.refresh_scores(); - expected_message_deliveries *= 0.9; //decay + expected_message_deliveries *= 0.9; // decay } assert_eq!( @@ -3382,8 +3512,8 @@ fn test_scoring_p3b() { let topic = Topic::new("test"); let topic_hash = topic.hash(); let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries + time_in_mesh_weight: 0.0, // deactivate time in mesh + first_message_deliveries_weight: 0.0, // deactivate first time deliveries mesh_message_deliveries_weight: -2.0, mesh_message_deliveries_decay: 0.9, mesh_message_deliveries_cap: 10.0, @@ -3399,8 +3529,8 @@ fn test_scoring_p3b() { peer_score_params.app_specific_weight = 1.0; let peer_score_thresholds = PeerScoreThresholds::default(); - //build mesh with one peer - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with one peer + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) @@ -3417,49 +3547,49 @@ fn test_scoring_p3b() { let mut expected_message_deliveries = 0.0; - //add some positive score + // add some positive score gs.peer_score .as_mut() .unwrap() .0 .set_application_score(&peers[0], 100.0); - //peer 0 delivers two message + // peer 0 delivers two message deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); expected_message_deliveries += 2.0; sleep(Duration::from_millis(1050)); - //activation kicks in + // activation kicks in gs.peer_score.as_mut().unwrap().0.refresh_scores(); - expected_message_deliveries *= 0.9; //decay + expected_message_deliveries *= 0.9; // decay - //prune peer + // prune peer gs.handle_prune(&peers[0], vec![(topics[0].clone(), vec![], None)]); - //wait backoff + // wait backoff sleep(Duration::from_millis(130)); - //regraft peer + // regraft peer gs.handle_graft(&peers[0], topics.clone()); - //the score should now consider p3b + // the score should now consider p3b let mut expected_b3 = (5f64 - expected_message_deliveries).powi(2); assert_eq!( gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 100.0 + expected_b3 * -3.0 * 0.7 ); - //we can also add a new p3 to the score + // we can also add a new p3 to the score - //peer 0 delivers one message + // peer 0 delivers one message deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); expected_message_deliveries += 1.0; sleep(Duration::from_millis(1050)); gs.peer_score.as_mut().unwrap().0.refresh_scores(); - expected_message_deliveries *= 0.9; //decay + expected_message_deliveries *= 0.9; // decay expected_b3 *= 0.95; assert_eq!( @@ -3478,10 +3608,14 @@ fn test_scoring_p4_valid_message() { let topic = Topic::new("test"); let topic_hash = topic.hash(); let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties + // deactivate time in mesh + time_in_mesh_weight: 0.0, + // deactivate first time deliveries + first_message_deliveries_weight: 0.0, + // deactivate message deliveries + mesh_message_deliveries_weight: 0.0, + // deactivate mesh failure penalties + mesh_failure_penalty_weight: 0.0, invalid_message_deliveries_weight: -2.0, invalid_message_deliveries_decay: 0.9, topic_weight: 0.7, @@ -3491,8 +3625,8 @@ fn test_scoring_p4_valid_message() { peer_score_params.app_specific_weight = 1.0; let peer_score_thresholds = PeerScoreThresholds::default(); - //build mesh with two peers - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with two peers + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) @@ -3507,7 +3641,7 @@ fn test_scoring_p4_valid_message() { gs.handle_received_message(msg, &peers[index]); }; - //peer 0 delivers valid message + // peer 0 delivers valid message let m1 = random_message(&mut seq, &topics); deliver_message(&mut gs, 0, m1.clone()); @@ -3516,13 +3650,12 @@ fn test_scoring_p4_valid_message() { assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - //message m1 gets validated + // message m1 gets validated gs.report_message_validation_result( &config.message_id(message1), &peers[0], MessageAcceptance::Accept, - ) - .unwrap(); + ); assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); } @@ -3537,10 +3670,14 @@ fn test_scoring_p4_invalid_signature() { let topic = Topic::new("test"); let topic_hash = topic.hash(); let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties + // deactivate time in mesh + time_in_mesh_weight: 0.0, + // deactivate first time deliveries + first_message_deliveries_weight: 0.0, + // deactivate message deliveries + mesh_message_deliveries_weight: 0.0, + // deactivate mesh failure penalties + mesh_failure_penalty_weight: 0.0, invalid_message_deliveries_weight: -2.0, invalid_message_deliveries_decay: 0.9, topic_weight: 0.7, @@ -3550,8 +3687,8 @@ fn test_scoring_p4_invalid_signature() { peer_score_params.app_specific_weight = 1.0; let peer_score_thresholds = PeerScoreThresholds::default(); - //build mesh with one peer - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with one peer + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) @@ -3563,7 +3700,7 @@ fn test_scoring_p4_invalid_signature() { let mut seq = 0; - //peer 0 delivers message with invalid signature + // peer 0 delivers message with invalid signature let m = random_message(&mut seq, &topics); gs.on_connection_handler_event( @@ -3595,10 +3732,14 @@ fn test_scoring_p4_message_from_self() { let topic = Topic::new("test"); let topic_hash = topic.hash(); let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties + // deactivate time in mesh + time_in_mesh_weight: 0.0, + // deactivate first time deliveries + first_message_deliveries_weight: 0.0, + // deactivate message deliveries + mesh_message_deliveries_weight: 0.0, + // deactivate mesh failure penalties + mesh_failure_penalty_weight: 0.0, invalid_message_deliveries_weight: -2.0, invalid_message_deliveries_decay: 0.9, topic_weight: 0.7, @@ -3608,8 +3749,8 @@ fn test_scoring_p4_message_from_self() { peer_score_params.app_specific_weight = 1.0; let peer_score_thresholds = PeerScoreThresholds::default(); - //build mesh with two peers - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with two peers + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) @@ -3624,7 +3765,7 @@ fn test_scoring_p4_message_from_self() { gs.handle_received_message(msg, &peers[index]); }; - //peer 0 delivers invalid message from self + // peer 0 delivers invalid message from self let mut m = random_message(&mut seq, &topics); m.source = Some(*gs.publish_config.get_own_id().unwrap()); @@ -3645,10 +3786,14 @@ fn test_scoring_p4_ignored_message() { let topic = Topic::new("test"); let topic_hash = topic.hash(); let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties + // deactivate time in mesh + time_in_mesh_weight: 0.0, + // deactivate first time deliveries + first_message_deliveries_weight: 0.0, + // deactivate message deliveries + mesh_message_deliveries_weight: 0.0, + // deactivate mesh failure penalties + mesh_failure_penalty_weight: 0.0, invalid_message_deliveries_weight: -2.0, invalid_message_deliveries_decay: 0.9, topic_weight: 0.7, @@ -3658,8 +3803,8 @@ fn test_scoring_p4_ignored_message() { peer_score_params.app_specific_weight = 1.0; let peer_score_thresholds = PeerScoreThresholds::default(); - //build mesh with two peers - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with two peers + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) @@ -3674,7 +3819,7 @@ fn test_scoring_p4_ignored_message() { gs.handle_received_message(msg, &peers[index]); }; - //peer 0 delivers ignored message + // peer 0 delivers ignored message let m1 = random_message(&mut seq, &topics); deliver_message(&mut gs, 0, m1.clone()); @@ -3683,13 +3828,12 @@ fn test_scoring_p4_ignored_message() { // Transform the inbound message let message1 = &gs.data_transform.inbound_transform(m1).unwrap(); - //message m1 gets ignored + // message m1 gets ignored gs.report_message_validation_result( &config.message_id(message1), &peers[0], MessageAcceptance::Ignore, - ) - .unwrap(); + ); assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); } @@ -3704,10 +3848,14 @@ fn test_scoring_p4_application_invalidated_message() { let topic = Topic::new("test"); let topic_hash = topic.hash(); let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties + // deactivate time in mesh + time_in_mesh_weight: 0.0, + // deactivate first time deliveries + first_message_deliveries_weight: 0.0, + // deactivate message deliveries + mesh_message_deliveries_weight: 0.0, + // deactivate mesh failure penalties + mesh_failure_penalty_weight: 0.0, invalid_message_deliveries_weight: -2.0, invalid_message_deliveries_decay: 0.9, topic_weight: 0.7, @@ -3717,8 +3865,8 @@ fn test_scoring_p4_application_invalidated_message() { peer_score_params.app_specific_weight = 1.0; let peer_score_thresholds = PeerScoreThresholds::default(); - //build mesh with two peers - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with two peers + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) @@ -3733,7 +3881,7 @@ fn test_scoring_p4_application_invalidated_message() { gs.handle_received_message(msg, &peers[index]); }; - //peer 0 delivers invalid message + // peer 0 delivers invalid message let m1 = random_message(&mut seq, &topics); deliver_message(&mut gs, 0, m1.clone()); @@ -3742,13 +3890,12 @@ fn test_scoring_p4_application_invalidated_message() { // Transform the inbound message let message1 = &gs.data_transform.inbound_transform(m1).unwrap(); - //message m1 gets rejected + // message m1 gets rejected gs.report_message_validation_result( &config.message_id(message1), &peers[0], MessageAcceptance::Reject, - ) - .unwrap(); + ); assert_eq!( gs.peer_score.as_ref().unwrap().0.score(&peers[0]), @@ -3766,10 +3913,14 @@ fn test_scoring_p4_application_invalid_message_from_two_peers() { let topic = Topic::new("test"); let topic_hash = topic.hash(); let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties + // deactivate time in mesh + time_in_mesh_weight: 0.0, + // deactivate first time deliveries + first_message_deliveries_weight: 0.0, + // deactivate message deliveries + mesh_message_deliveries_weight: 0.0, + // deactivate mesh failure penalties + mesh_failure_penalty_weight: 0.0, invalid_message_deliveries_weight: -2.0, invalid_message_deliveries_decay: 0.9, topic_weight: 0.7, @@ -3779,8 +3930,8 @@ fn test_scoring_p4_application_invalid_message_from_two_peers() { peer_score_params.app_specific_weight = 1.0; let peer_score_thresholds = PeerScoreThresholds::default(); - //build mesh with two peers - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with two peers + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(2) .topics(vec!["test".into()]) .to_subscribe(true) @@ -3795,26 +3946,25 @@ fn test_scoring_p4_application_invalid_message_from_two_peers() { gs.handle_received_message(msg, &peers[index]); }; - //peer 0 delivers invalid message + // peer 0 delivers invalid message let m1 = random_message(&mut seq, &topics); deliver_message(&mut gs, 0, m1.clone()); // Transform the inbound message let message1 = &gs.data_transform.inbound_transform(m1.clone()).unwrap(); - //peer 1 delivers same message + // peer 1 delivers same message deliver_message(&mut gs, 1, m1); assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[1]), 0.0); - //message m1 gets rejected + // message m1 gets rejected gs.report_message_validation_result( &config.message_id(message1), &peers[0], MessageAcceptance::Reject, - ) - .unwrap(); + ); assert_eq!( gs.peer_score.as_ref().unwrap().0.score(&peers[0]), @@ -3836,10 +3986,14 @@ fn test_scoring_p4_three_application_invalid_messages() { let topic = Topic::new("test"); let topic_hash = topic.hash(); let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties + // deactivate time in mesh + time_in_mesh_weight: 0.0, + // deactivate first time deliveries + first_message_deliveries_weight: 0.0, + // deactivate message deliveries + mesh_message_deliveries_weight: 0.0, + // deactivate mesh failure penalties + mesh_failure_penalty_weight: 0.0, invalid_message_deliveries_weight: -2.0, invalid_message_deliveries_decay: 0.9, topic_weight: 0.7, @@ -3849,8 +4003,8 @@ fn test_scoring_p4_three_application_invalid_messages() { peer_score_params.app_specific_weight = 1.0; let peer_score_thresholds = PeerScoreThresholds::default(); - //build mesh with one peer - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with one peer + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) @@ -3865,7 +4019,7 @@ fn test_scoring_p4_three_application_invalid_messages() { gs.handle_received_message(msg, &peers[index]); }; - //peer 0 delivers two invalid message + // peer 0 delivers two invalid message let m1 = random_message(&mut seq, &topics); let m2 = random_message(&mut seq, &topics); let m3 = random_message(&mut seq, &topics); @@ -3883,27 +4037,26 @@ fn test_scoring_p4_three_application_invalid_messages() { assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - //messages gets rejected + // messages gets rejected gs.report_message_validation_result( &config.message_id(message1), &peers[0], MessageAcceptance::Reject, - ) - .unwrap(); + ); + gs.report_message_validation_result( &config.message_id(message2), &peers[0], MessageAcceptance::Reject, - ) - .unwrap(); + ); + gs.report_message_validation_result( &config.message_id(message3), &peers[0], MessageAcceptance::Reject, - ) - .unwrap(); + ); - //number of invalid messages gets squared + // number of invalid messages gets squared assert_eq!( gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 9.0 * -2.0 * 0.7 @@ -3920,10 +4073,14 @@ fn test_scoring_p4_decay() { let topic = Topic::new("test"); let topic_hash = topic.hash(); let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties + // deactivate time in mesh + time_in_mesh_weight: 0.0, + // deactivate first time deliveries + first_message_deliveries_weight: 0.0, + // deactivate message deliveries + mesh_message_deliveries_weight: 0.0, + // deactivate mesh failure penalties + mesh_failure_penalty_weight: 0.0, invalid_message_deliveries_weight: -2.0, invalid_message_deliveries_decay: 0.9, topic_weight: 0.7, @@ -3933,8 +4090,8 @@ fn test_scoring_p4_decay() { peer_score_params.app_specific_weight = 1.0; let peer_score_thresholds = PeerScoreThresholds::default(); - //build mesh with one peer - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with one peer + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) @@ -3949,7 +4106,7 @@ fn test_scoring_p4_decay() { gs.handle_received_message(msg, &peers[index]); }; - //peer 0 delivers invalid message + // peer 0 delivers invalid message let m1 = random_message(&mut seq, &topics); deliver_message(&mut gs, 0, m1.clone()); @@ -3957,20 +4114,19 @@ fn test_scoring_p4_decay() { let message1 = &gs.data_transform.inbound_transform(m1).unwrap(); assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - //message m1 gets rejected + // message m1 gets rejected gs.report_message_validation_result( &config.message_id(message1), &peers[0], MessageAcceptance::Reject, - ) - .unwrap(); + ); assert_eq!( gs.peer_score.as_ref().unwrap().0.score(&peers[0]), -2.0 * 0.7 ); - //we decay + // we decay gs.peer_score.as_mut().unwrap().0.refresh_scores(); // the number of invalids gets decayed to 0.9 and then squared in the score @@ -3987,8 +4143,8 @@ fn test_scoring_p5() { ..PeerScoreParams::default() }; - //build mesh with one peer - let (mut gs, peers, _) = inject_nodes1() + // build mesh with one peer + let (mut gs, peers, _, _) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) @@ -4014,7 +4170,7 @@ fn test_scoring_p6() { ..Default::default() }; - let (mut gs, _, _) = inject_nodes1() + let (mut gs, _, _, _) = inject_nodes1() .peer_no(0) .topics(vec![]) .to_subscribe(false) @@ -4024,31 +4180,31 @@ fn test_scoring_p6() { .scoring(Some((peer_score_params, PeerScoreThresholds::default()))) .create_network(); - //create 5 peers with the same ip + // create 5 peers with the same ip let addr = Multiaddr::from(Ipv4Addr::new(10, 1, 2, 3)); let peers = vec![ - add_peer_with_addr(&mut gs, &vec![], false, false, addr.clone()), - add_peer_with_addr(&mut gs, &vec![], false, false, addr.clone()), - add_peer_with_addr(&mut gs, &vec![], true, false, addr.clone()), - add_peer_with_addr(&mut gs, &vec![], true, false, addr.clone()), - add_peer_with_addr(&mut gs, &vec![], true, true, addr.clone()), + add_peer_with_addr(&mut gs, &[], false, false, addr.clone()).0, + add_peer_with_addr(&mut gs, &[], false, false, addr.clone()).0, + add_peer_with_addr(&mut gs, &[], true, false, addr.clone()).0, + add_peer_with_addr(&mut gs, &[], true, false, addr.clone()).0, + add_peer_with_addr(&mut gs, &[], true, true, addr.clone()).0, ]; - //create 4 other peers with other ip + // create 4 other peers with other ip let addr2 = Multiaddr::from(Ipv4Addr::new(10, 1, 2, 4)); let others = vec![ - add_peer_with_addr(&mut gs, &vec![], false, false, addr2.clone()), - add_peer_with_addr(&mut gs, &vec![], false, false, addr2.clone()), - add_peer_with_addr(&mut gs, &vec![], true, false, addr2.clone()), - add_peer_with_addr(&mut gs, &vec![], true, false, addr2.clone()), + add_peer_with_addr(&mut gs, &[], false, false, addr2.clone()).0, + add_peer_with_addr(&mut gs, &[], false, false, addr2.clone()).0, + add_peer_with_addr(&mut gs, &[], true, false, addr2.clone()).0, + add_peer_with_addr(&mut gs, &[], true, false, addr2.clone()).0, ]; - //no penalties yet + // no penalties yet for peer in peers.iter().chain(others.iter()) { assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 0.0); } - //add additional connection for 3 others with addr + // add additional connection for 3 others with addr for id in others.iter().take(3) { gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { peer_id: *id, @@ -4056,20 +4212,21 @@ fn test_scoring_p6() { endpoint: &ConnectedPoint::Dialer { address: addr.clone(), role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }, failed_addresses: &[], other_established: 0, })); } - //penalties apply squared + // penalties apply squared for peer in peers.iter().chain(others.iter().take(3)) { assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 9.0 * -2.0); } - //fourth other peer still no penalty + // fourth other peer still no penalty assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&others[3]), 0.0); - //add additional connection for 3 of the peers to addr2 + // add additional connection for 3 of the peers to addr2 for peer in peers.iter().take(3) { gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { peer_id: *peer, @@ -4077,13 +4234,14 @@ fn test_scoring_p6() { endpoint: &ConnectedPoint::Dialer { address: addr2.clone(), role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }, failed_addresses: &[], other_established: 1, })); } - //double penalties for the first three of each + // double penalties for the first three of each for peer in peers.iter().take(3).chain(others.iter().take(3)) { assert_eq!( gs.peer_score.as_ref().unwrap().0.score(peer), @@ -4091,7 +4249,7 @@ fn test_scoring_p6() { ); } - //single penalties for the rest + // single penalties for the rest for peer in peers.iter().skip(3) { assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 9.0 * -2.0); } @@ -4100,20 +4258,21 @@ fn test_scoring_p6() { 4.0 * -2.0 ); - //two times same ip doesn't count twice + // two times same ip doesn't count twice gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { peer_id: peers[0], connection_id: ConnectionId::new_unchecked(0), endpoint: &ConnectedPoint::Dialer { address: addr, role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }, failed_addresses: &[], other_established: 2, })); - //nothing changed - //double penalties for the first three of each + // nothing changed + // double penalties for the first three of each for peer in peers.iter().take(3).chain(others.iter().take(3)) { assert_eq!( gs.peer_score.as_ref().unwrap().0.score(peer), @@ -4121,7 +4280,7 @@ fn test_scoring_p6() { ); } - //single penalties for the rest + // single penalties for the rest for peer in peers.iter().skip(3) { assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 9.0 * -2.0); } @@ -4144,7 +4303,7 @@ fn test_scoring_p7_grafts_before_backoff() { ..Default::default() }; - let (mut gs, peers, topics) = inject_nodes1() + let (mut gs, peers, _receivers, topics) = inject_nodes1() .peer_no(2) .topics(vec!["test".into()]) .to_subscribe(false) @@ -4154,7 +4313,7 @@ fn test_scoring_p7_grafts_before_backoff() { .scoring(Some((peer_score_params, PeerScoreThresholds::default()))) .create_network(); - //remove peers from mesh and send prune to them => this adds a backoff for the peers + // remove peers from mesh and send prune to them => this adds a backoff for the peers for peer in peers.iter().take(2) { gs.mesh.get_mut(&topics[0]).unwrap().remove(peer); gs.send_graft_prune( @@ -4164,31 +4323,31 @@ fn test_scoring_p7_grafts_before_backoff() { ); } - //wait 50 millisecs + // wait 50 millisecs sleep(Duration::from_millis(50)); - //first peer tries to graft + // first peer tries to graft gs.handle_graft(&peers[0], vec![topics[0].clone()]); - //double behaviour penalty for first peer (squared) + // double behaviour penalty for first peer (squared) assert_eq!( gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 4.0 * -2.0 ); - //wait 100 millisecs + // wait 100 millisecs sleep(Duration::from_millis(100)); - //second peer tries to graft + // second peer tries to graft gs.handle_graft(&peers[1], vec![topics[0].clone()]); - //single behaviour penalty for second peer + // single behaviour penalty for second peer assert_eq!( gs.peer_score.as_ref().unwrap().0.score(&peers[1]), 1.0 * -2.0 ); - //test decay + // test decay gs.peer_score.as_mut().unwrap().0.refresh_scores(); assert_eq!( @@ -4207,7 +4366,7 @@ fn test_opportunistic_grafting() { .mesh_n_low(3) .mesh_n(5) .mesh_n_high(7) - .mesh_outbound_min(0) //deactivate outbound handling + .mesh_outbound_min(0) // deactivate outbound handling .opportunistic_graft_ticks(2) .opportunistic_graft_peers(2) .build() @@ -4221,7 +4380,7 @@ fn test_opportunistic_grafting() { ..Default::default() }; - let (mut gs, peers, topics) = inject_nodes1() + let (mut gs, peers, _receivers, topics) = inject_nodes1() .peer_no(5) .topics(vec!["test".into()]) .to_subscribe(false) @@ -4231,30 +4390,30 @@ fn test_opportunistic_grafting() { .scoring(Some((peer_score_params, thresholds))) .create_network(); - //fill mesh with 5 peers + // fill mesh with 5 peers for peer in &peers { gs.handle_graft(peer, topics.clone()); } - //add additional 5 peers + // add additional 5 peers let others: Vec<_> = (0..5) .map(|_| add_peer(&mut gs, &topics, false, false)) .collect(); - //currently mesh equals peers + // currently mesh equals peers assert_eq!(gs.mesh[&topics[0]], peers.iter().cloned().collect()); - //give others high scores (but the first two have not high enough scores) + // give others high scores (but the first two have not high enough scores) for (i, peer) in peers.iter().enumerate().take(5) { gs.set_application_score(peer, 0.0 + i as f64); } - //set scores for peers in the mesh - for (i, peer) in others.iter().enumerate().take(5) { + // set scores for peers in the mesh + for (i, (peer, _receiver)) in others.iter().enumerate().take(5) { gs.set_application_score(peer, 0.0 + i as f64); } - //this gives a median of exactly 2.0 => should not apply opportunistic grafting + // this gives a median of exactly 2.0 => should not apply opportunistic grafting gs.heartbeat(); gs.heartbeat(); @@ -4264,10 +4423,10 @@ fn test_opportunistic_grafting() { "should not apply opportunistic grafting" ); - //reduce middle score to 1.0 giving a median of 1.0 + // reduce middle score to 1.0 giving a median of 1.0 gs.set_application_score(&peers[2], 1.0); - //opportunistic grafting after two heartbeats + // opportunistic grafting after two heartbeats gs.heartbeat(); assert_eq!( @@ -4290,27 +4449,27 @@ fn test_opportunistic_grafting() { ); assert!( - gs.mesh[&topics[0]].is_disjoint(&others.iter().cloned().take(2).collect()), + gs.mesh[&topics[0]].is_disjoint(&others.iter().map(|(p, _)| p).cloned().take(2).collect()), "peers below or equal to median should not be added in opportunistic grafting" ); } #[test] fn test_ignore_graft_from_unknown_topic() { - //build gossipsub without subscribing to any topics - let (mut gs, _, _) = inject_nodes1() - .peer_no(0) + // build gossipsub without subscribing to any topics + let (mut gs, peers, receivers, _) = inject_nodes1() + .peer_no(1) .topics(vec![]) .to_subscribe(false) .create_network(); - //handle an incoming graft for some topic - gs.handle_graft(&PeerId::random(), vec![Topic::new("test").hash()]); + // handle an incoming graft for some topic + gs.handle_graft(&peers[0], vec![Topic::new("test").hash()]); - //assert that no prune got created + // assert that no prune got created + let (control_msgs, _) = count_control_msgs(receivers, |_, a| matches!(a, RpcOut::Prune { .. })); assert_eq!( - count_control_msgs(&gs, |_, a| matches!(a, ControlAction::Prune { .. })), - 0, + control_msgs, 0, "we should not prune after graft in unknown topic" ); } @@ -4318,17 +4477,18 @@ fn test_ignore_graft_from_unknown_topic() { #[test] fn test_ignore_too_many_iwants_from_same_peer_for_same_message() { let config = Config::default(); - //build gossipsub with full mesh - let (mut gs, _, topics) = inject_nodes1() + // build gossipsub with full mesh + let (mut gs, _, mut receivers, topics) = inject_nodes1() .peer_no(config.mesh_n_high()) .topics(vec!["test".into()]) .to_subscribe(false) .create_network(); - //add another peer not in the mesh - let peer = add_peer(&mut gs, &topics, false, false); + // add another peer not in the mesh + let (peer, receiver) = add_peer(&mut gs, &topics, false, false); + receivers.insert(peer, receiver); - //receive a message + // receive a message let mut seq = 0; let m1 = random_message(&mut seq, &topics); @@ -4339,26 +4499,25 @@ fn test_ignore_too_many_iwants_from_same_peer_for_same_message() { gs.handle_received_message(m1, &PeerId::random()); - //clear events - gs.events.clear(); + // clear events + let receivers = flush_events(&mut gs, receivers); - //the first gossip_retransimission many iwants return the valid message, all others are + // the first gossip_retransimission many iwants return the valid message, all others are // ignored. for _ in 0..(2 * config.gossip_retransimission() + 10) { gs.handle_iwant(&peer, vec![id.clone()]); } assert_eq!( - gs.events - .iter() - .filter(|e| matches!( - e, - ToSwarm::NotifyHandler { - event: HandlerIn::Message(RpcOut::Forward(_)), - .. + receivers.into_values().fold(0, |mut fwds, c| { + let non_priority = c.non_priority.get_ref(); + while !non_priority.is_empty() { + if let Ok(RpcOut::Forward { .. }) = non_priority.try_recv() { + fwds += 1; } - )) - .count(), + } + fwds + }), config.gossip_retransimission() as usize, "not more then gossip_retransmission many messages get sent back" ); @@ -4370,22 +4529,23 @@ fn test_ignore_too_many_ihaves() { .max_ihave_messages(10) .build() .unwrap(); - //build gossipsub with full mesh - let (mut gs, _, topics) = inject_nodes1() + // build gossipsub with full mesh + let (mut gs, _, mut receivers, topics) = inject_nodes1() .peer_no(config.mesh_n_high()) .topics(vec!["test".into()]) .to_subscribe(false) .gs_config(config.clone()) .create_network(); - //add another peer not in the mesh - let peer = add_peer(&mut gs, &topics, false, false); + // add another peer not in the mesh + let (peer, receiver) = add_peer(&mut gs, &topics, false, false); + receivers.insert(peer, receiver); - //peer has 20 messages + // peer has 20 messages let mut seq = 0; let messages: Vec<_> = (0..20).map(|_| random_message(&mut seq, &topics)).collect(); - //peer sends us one ihave for each message in order + // peer sends us one ihave for each message in order for raw_message in &messages { // Transform the inbound message let message = &gs @@ -4406,16 +4566,19 @@ fn test_ignore_too_many_ihaves() { .map(|m| config.message_id(&m)) .collect(); - //we send iwant only for the first 10 messages + // we send iwant only for the first 10 messages + let (control_msgs, receivers) = count_control_msgs(receivers, |p, action| { + p == &peer + && matches!(action, RpcOut::IWant(IWant { message_ids }) if message_ids.len() == 1 && first_ten.contains(&message_ids[0])) + }); assert_eq!( - count_control_msgs(&gs, |p, action| p == &peer - && matches!(action, ControlAction::IWant { message_ids } if message_ids.len() == 1 && first_ten.contains(&message_ids[0]))), - 10, + control_msgs, 10, "exactly the first ten ihaves should be processed and one iwant for each created" ); - //after a heartbeat everything is forgotten + // after a heartbeat everything is forgotten gs.heartbeat(); + for raw_message in messages[10..].iter() { // Transform the inbound message let message = &gs @@ -4429,13 +4592,12 @@ fn test_ignore_too_many_ihaves() { ); } - //we sent iwant for all 20 messages - assert_eq!( - count_control_msgs(&gs, |p, action| p == &peer - && matches!(action, ControlAction::IWant { message_ids } if message_ids.len() == 1)), - 20, - "all 20 should get sent" - ); + // we sent iwant for all 10 messages + let (control_msgs, _) = count_control_msgs(receivers, |p, action| { + p == &peer + && matches!(action, RpcOut::IWant(IWant { message_ids }) if message_ids.len() == 1) + }); + assert_eq!(control_msgs, 10, "all 20 should get sent"); } #[test] @@ -4445,26 +4607,27 @@ fn test_ignore_too_many_messages_in_ihave() { .max_ihave_length(10) .build() .unwrap(); - //build gossipsub with full mesh - let (mut gs, _, topics) = inject_nodes1() + // build gossipsub with full mesh + let (mut gs, _, mut receivers, topics) = inject_nodes1() .peer_no(config.mesh_n_high()) .topics(vec!["test".into()]) .to_subscribe(false) .gs_config(config.clone()) .create_network(); - //add another peer not in the mesh - let peer = add_peer(&mut gs, &topics, false, false); + // add another peer not in the mesh + let (peer, receiver) = add_peer(&mut gs, &topics, false, false); + receivers.insert(peer, receiver); - //peer has 20 messages + // peer has 30 messages let mut seq = 0; - let message_ids: Vec<_> = (0..20) + let message_ids: Vec<_> = (0..30) .map(|_| random_message(&mut seq, &topics)) .map(|msg| gs.data_transform.inbound_transform(msg).unwrap()) .map(|msg| config.message_id(&msg)) .collect(); - //peer sends us three ihaves + // peer sends us three ihaves gs.handle_ihave(&peer, vec![(topics[0].clone(), message_ids[0..8].to_vec())]); gs.handle_ihave( &peer, @@ -4477,45 +4640,45 @@ fn test_ignore_too_many_messages_in_ihave() { let first_twelve: HashSet<_> = message_ids.iter().take(12).collect(); - //we send iwant only for the first 10 messages + // we send iwant only for the first 10 messages let mut sum = 0; + let (control_msgs, receivers) = count_control_msgs(receivers, |p, rpc| match rpc { + RpcOut::IWant(IWant { message_ids }) => { + p == &peer && { + assert!(first_twelve.is_superset(&message_ids.iter().collect())); + sum += message_ids.len(); + true + } + } + _ => false, + }); assert_eq!( - count_control_msgs(&gs, |p, action| match action { - ControlAction::IWant { message_ids } => - p == &peer && { - assert!(first_twelve.is_superset(&message_ids.iter().collect())); - sum += message_ids.len(); - true - }, - _ => false, - }), - 2, + control_msgs, 2, "the third ihave should get ignored and no iwant sent" ); assert_eq!(sum, 10, "exactly the first ten ihaves should be processed"); - //after a heartbeat everything is forgotten + // after a heartbeat everything is forgotten gs.heartbeat(); gs.handle_ihave( &peer, - vec![(topics[0].clone(), message_ids[10..20].to_vec())], + vec![(topics[0].clone(), message_ids[20..30].to_vec())], ); - //we sent 20 iwant messages + // we sent 10 iwant messages ids via a IWANT rpc. let mut sum = 0; - assert_eq!( - count_control_msgs(&gs, |p, action| match action { - ControlAction::IWant { message_ids } => - p == &peer && { - sum += message_ids.len(); - true - }, - _ => false, - }), - 3 - ); - assert_eq!(sum, 20, "exactly 20 iwants should get sent"); + let (control_msgs, _) = count_control_msgs(receivers, |p, rpc| match rpc { + RpcOut::IWant(IWant { message_ids }) => { + p == &peer && { + sum += message_ids.len(); + true + } + } + _ => false, + }); + assert_eq!(control_msgs, 1); + assert_eq!(sum, 10, "exactly 20 iwants should get sent"); } #[test] @@ -4525,55 +4688,57 @@ fn test_limit_number_of_message_ids_inside_ihave() { .max_ihave_length(100) .build() .unwrap(); - //build gossipsub with full mesh - let (mut gs, peers, topics) = inject_nodes1() + // build gossipsub with full mesh + let (mut gs, peers, mut receivers, topics) = inject_nodes1() .peer_no(config.mesh_n_high()) .topics(vec!["test".into()]) .to_subscribe(false) .gs_config(config) .create_network(); - //graft to all peers to really fill the mesh with all the peers + // graft to all peers to really fill the mesh with all the peers for peer in peers { gs.handle_graft(&peer, topics.clone()); } - //add two other peers not in the mesh - let p1 = add_peer(&mut gs, &topics, false, false); - let p2 = add_peer(&mut gs, &topics, false, false); + // add two other peers not in the mesh + let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); + receivers.insert(p1, receiver1); + let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); + receivers.insert(p2, receiver2); - //receive 200 messages from another peer + // receive 200 messages from another peer let mut seq = 0; for _ in 0..200 { gs.handle_received_message(random_message(&mut seq, &topics), &PeerId::random()); } - //emit gossip + // emit gossip gs.emit_gossip(); - // both peers should have gotten 100 random ihave messages, to asser the randomness, we + // both peers should have gotten 100 random ihave messages, to assert the randomness, we // assert that both have not gotten the same set of messages, but have an intersection // (which is the case with very high probability, the probabiltity of failure is < 10^-58). let mut ihaves1 = HashSet::new(); let mut ihaves2 = HashSet::new(); - assert_eq!( - count_control_msgs(&gs, |p, action| match action { - ControlAction::IHave { message_ids, .. } => { - if p == &p1 { - ihaves1 = message_ids.iter().cloned().collect(); - true - } else if p == &p2 { - ihaves2 = message_ids.iter().cloned().collect(); - true - } else { - false - } + let (control_msgs, _) = count_control_msgs(receivers, |p, action| match action { + RpcOut::IHave(IHave { message_ids, .. }) => { + if p == &p1 { + ihaves1 = message_ids.iter().cloned().collect(); + true + } else if p == &p2 { + ihaves2 = message_ids.iter().cloned().collect(); + true + } else { + false } - _ => false, - }), - 2, + } + _ => false, + }); + assert_eq!( + control_msgs, 2, "should have emitted one ihave to p1 and one to p2" ); @@ -4601,11 +4766,7 @@ fn test_limit_number_of_message_ids_inside_ihave() { #[test] fn test_iwant_penalties() { - use tracing_subscriber::EnvFilter; - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - + libp2p_test_utils::with_default_env_filter(); let config = ConfigBuilder::default() .iwant_followup_time(Duration::from_secs(4)) .build() @@ -4616,7 +4777,7 @@ fn test_iwant_penalties() { }; // fill the mesh - let (mut gs, peers, topics) = inject_nodes1() + let (mut gs, peers, _, topics) = inject_nodes1() .peer_no(2) .topics(vec!["test".into()]) .to_subscribe(false) @@ -4640,7 +4801,7 @@ fn test_iwant_penalties() { let mut first_messages = Vec::new(); let mut second_messages = Vec::new(); let mut seq = 0; - for peer in &other_peers { + for (peer, _receiver) in &other_peers { let msg1 = random_message(&mut seq, &topics); let msg2 = random_message(&mut seq, &topics); @@ -4663,19 +4824,19 @@ fn test_iwant_penalties() { } // the peers send us all the first message ids in time - for (index, peer) in other_peers.iter().enumerate() { + for (index, (peer, _receiver)) in other_peers.iter().enumerate() { gs.handle_received_message(first_messages[index].clone(), peer); } // now we do a heartbeat no penalization should have been applied yet gs.heartbeat(); - for peer in &other_peers { + for (peer, _receiver) in &other_peers { assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 0.0); } // receive the first twenty of the other peers then send their response - for (index, peer) in other_peers.iter().enumerate().take(20) { + for (index, (peer, _receiver)) in other_peers.iter().enumerate().take(20) { gs.handle_received_message(second_messages[index].clone(), peer); } @@ -4686,7 +4847,7 @@ fn test_iwant_penalties() { gs.heartbeat(); // now we get the second messages from the last 80 peers. - for (index, peer) in other_peers.iter().enumerate() { + for (index, (peer, _receiver)) in other_peers.iter().enumerate() { if index > 19 { gs.handle_received_message(second_messages[index].clone(), peer); } @@ -4700,7 +4861,7 @@ fn test_iwant_penalties() { let mut single_penalized = 0; let mut double_penalized = 0; - for (i, peer) in other_peers.iter().enumerate() { + for (i, (peer, _receiver)) in other_peers.iter().enumerate() { let score = gs.peer_score.as_ref().unwrap().0.score(peer); if score == 0.0 { not_penalized += 1; @@ -4728,15 +4889,15 @@ fn test_publish_to_floodsub_peers_without_flood_publish() { .flood_publish(false) .build() .unwrap(); - let (mut gs, _, topics) = inject_nodes1() + let (mut gs, _, mut receivers, topics) = inject_nodes1() .peer_no(config.mesh_n_low() - 1) .topics(vec!["test".into()]) .to_subscribe(false) .gs_config(config) .create_network(); - //add two floodsub peer, one explicit, one implicit - let p1 = add_peer_with_addr_and_kind( + // add two floodsub peer, one explicit, one implicit + let (p1, receiver1) = add_peer_with_addr_and_kind( &mut gs, &topics, false, @@ -4744,34 +4905,36 @@ fn test_publish_to_floodsub_peers_without_flood_publish() { Multiaddr::empty(), Some(PeerKind::Floodsub), ); - let p2 = add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None); + receivers.insert(p1, receiver1); - //p1 and p2 are not in the mesh + let (p2, receiver2) = + add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None); + receivers.insert(p2, receiver2); + + // p1 and p2 are not in the mesh assert!(!gs.mesh[&topics[0]].contains(&p1) && !gs.mesh[&topics[0]].contains(&p2)); - //publish a message + // publish a message let publish_data = vec![0; 42]; gs.publish(Topic::new("test"), publish_data).unwrap(); // Collect publish messages to floodsub peers - let publishes = gs - .events - .iter() - .fold(vec![], |mut collected_publish, e| match e { - ToSwarm::NotifyHandler { peer_id, event, .. } => { - if peer_id == &p1 || peer_id == &p2 { - if let HandlerIn::Message(RpcOut::Publish(message)) = event { - collected_publish.push(message); - } + let publishes = receivers + .into_iter() + .fold(0, |mut collected_publish, (peer_id, c)| { + let priority = c.priority.get_ref(); + while !priority.is_empty() { + if matches!(priority.try_recv(), + Ok(RpcOut::Publish{..}) if peer_id == p1 || peer_id == p2) + { + collected_publish += 1; } - collected_publish } - _ => collected_publish, + collected_publish }); assert_eq!( - publishes.len(), - 2, + publishes, 2, "Should send a publish message to all floodsub peers" ); } @@ -4782,7 +4945,7 @@ fn test_do_not_use_floodsub_in_fanout() { .flood_publish(false) .build() .unwrap(); - let (mut gs, _, _) = inject_nodes1() + let (mut gs, _, mut receivers, _) = inject_nodes1() .peer_no(config.mesh_n_low() - 1) .topics(Vec::new()) .to_subscribe(false) @@ -4792,8 +4955,8 @@ fn test_do_not_use_floodsub_in_fanout() { let topic = Topic::new("test"); let topics = vec![topic.hash()]; - //add two floodsub peer, one explicit, one implicit - let p1 = add_peer_with_addr_and_kind( + // add two floodsub peer, one explicit, one implicit + let (p1, receiver1) = add_peer_with_addr_and_kind( &mut gs, &topics, false, @@ -4801,31 +4964,33 @@ fn test_do_not_use_floodsub_in_fanout() { Multiaddr::empty(), Some(PeerKind::Floodsub), ); - let p2 = add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None); - //publish a message + receivers.insert(p1, receiver1); + let (p2, receiver2) = + add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None); + + receivers.insert(p2, receiver2); + // publish a message let publish_data = vec![0; 42]; gs.publish(Topic::new("test"), publish_data).unwrap(); // Collect publish messages to floodsub peers - let publishes = gs - .events - .iter() - .fold(vec![], |mut collected_publish, e| match e { - ToSwarm::NotifyHandler { peer_id, event, .. } => { - if peer_id == &p1 || peer_id == &p2 { - if let HandlerIn::Message(RpcOut::Publish(message)) = event { - collected_publish.push(message); - } + let publishes = receivers + .into_iter() + .fold(0, |mut collected_publish, (peer_id, c)| { + let priority = c.priority.get_ref(); + while !priority.is_empty() { + if matches!(priority.try_recv(), + Ok(RpcOut::Publish{..}) if peer_id == p1 || peer_id == p2) + { + collected_publish += 1; } - collected_publish } - _ => collected_publish, + collected_publish }); assert_eq!( - publishes.len(), - 2, + publishes, 2, "Should send a publish message to all floodsub peers" ); @@ -4837,7 +5002,7 @@ fn test_do_not_use_floodsub_in_fanout() { #[test] fn test_dont_add_floodsub_peers_to_mesh_on_join() { - let (mut gs, _, _) = inject_nodes1() + let (mut gs, _, _, _) = inject_nodes1() .peer_no(0) .topics(Vec::new()) .to_subscribe(false) @@ -4846,7 +5011,7 @@ fn test_dont_add_floodsub_peers_to_mesh_on_join() { let topic = Topic::new("test"); let topics = vec![topic.hash()]; - //add two floodsub peer, one explicit, one implicit + // add two floodsub peer, one explicit, one implicit let _p1 = add_peer_with_addr_and_kind( &mut gs, &topics, @@ -4867,14 +5032,14 @@ fn test_dont_add_floodsub_peers_to_mesh_on_join() { #[test] fn test_dont_send_px_to_old_gossipsub_peers() { - let (mut gs, _, topics) = inject_nodes1() + let (mut gs, _, receivers, topics) = inject_nodes1() .peer_no(0) .topics(vec!["test".into()]) .to_subscribe(false) .create_network(); - //add an old gossipsub peer - let p1 = add_peer_with_addr_and_kind( + // add an old gossipsub peer + let (p1, _receiver1) = add_peer_with_addr_and_kind( &mut gs, &topics, false, @@ -4883,34 +5048,31 @@ fn test_dont_send_px_to_old_gossipsub_peers() { Some(PeerKind::Gossipsub), ); - //prune the peer + // prune the peer gs.send_graft_prune( HashMap::new(), vec![(p1, topics.clone())].into_iter().collect(), HashSet::new(), ); - //check that prune does not contain px - assert_eq!( - count_control_msgs(&gs, |_, m| match m { - ControlAction::Prune { peers: px, .. } => !px.is_empty(), - _ => false, - }), - 0, - "Should not send px to floodsub peers" - ); + // check that prune does not contain px + let (control_msgs, _) = count_control_msgs(receivers, |_, m| match m { + RpcOut::Prune(Prune { peers: px, .. }) => !px.is_empty(), + _ => false, + }); + assert_eq!(control_msgs, 0, "Should not send px to floodsub peers"); } #[test] fn test_dont_send_floodsub_peers_in_px() { - //build mesh with one peer - let (mut gs, peers, topics) = inject_nodes1() + // build mesh with one peer + let (mut gs, peers, receivers, topics) = inject_nodes1() .peer_no(1) .topics(vec!["test".into()]) .to_subscribe(true) .create_network(); - //add two floodsub peers + // add two floodsub peers let _p1 = add_peer_with_addr_and_kind( &mut gs, &topics, @@ -4921,33 +5083,30 @@ fn test_dont_send_floodsub_peers_in_px() { ); let _p2 = add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None); - //prune only mesh node + // prune only mesh node gs.send_graft_prune( HashMap::new(), vec![(peers[0], topics.clone())].into_iter().collect(), HashSet::new(), ); - //check that px in prune message is empty - assert_eq!( - count_control_msgs(&gs, |_, m| match m { - ControlAction::Prune { peers: px, .. } => !px.is_empty(), - _ => false, - }), - 0, - "Should not include floodsub peers in px" - ); + // check that px in prune message is empty + let (control_msgs, _) = count_control_msgs(receivers, |_, m| match m { + RpcOut::Prune(Prune { peers: px, .. }) => !px.is_empty(), + _ => false, + }); + assert_eq!(control_msgs, 0, "Should not include floodsub peers in px"); } #[test] fn test_dont_add_floodsub_peers_to_mesh_in_heartbeat() { - let (mut gs, _, topics) = inject_nodes1() + let (mut gs, _, _, topics) = inject_nodes1() .peer_no(0) .topics(vec!["test".into()]) .to_subscribe(false) .create_network(); - //add two floodsub peer, one explicit, one implicit + // add two floodsub peer, one explicit, one implicit let _p1 = add_peer_with_addr_and_kind( &mut gs, &topics, @@ -4969,7 +5128,7 @@ fn test_dont_add_floodsub_peers_to_mesh_in_heartbeat() { // Some very basic test of public api methods. #[test] fn test_public_api() { - let (gs, peers, topic_hashes) = inject_nodes1() + let (gs, peers, _, topic_hashes) = inject_nodes1() .peer_no(4) .topics(vec![String::from("topic1")]) .to_subscribe(true) @@ -5001,7 +5160,7 @@ fn test_public_api() { fn test_subscribe_to_invalid_topic() { let t1 = Topic::new("t1"); let t2 = Topic::new("t2"); - let (mut gs, _, _) = inject_nodes::() + let (mut gs, _, _, _) = inject_nodes::() .subscription_filter(WhitelistSubscriptionFilter( vec![t1.hash()].into_iter().collect(), )) @@ -5014,8 +5173,8 @@ fn test_subscribe_to_invalid_topic() { #[test] fn test_subscribe_and_graft_with_negative_score() { - //simulate a communication between two gossipsub instances - let (mut gs1, _, topic_hashes) = inject_nodes1() + // simulate a communication between two gossipsub instances + let (mut gs1, _, _, topic_hashes) = inject_nodes1() .topics(vec!["test".into()]) .scoring(Some(( PeerScoreParams::default(), @@ -5023,62 +5182,60 @@ fn test_subscribe_and_graft_with_negative_score() { ))) .create_network(); - let (mut gs2, _, _) = inject_nodes1().create_network(); + let (mut gs2, _, receivers, _) = inject_nodes1().create_network(); let connection_id = ConnectionId::new_unchecked(0); let topic = Topic::new("test"); - let p2 = add_peer(&mut gs1, &Vec::new(), true, false); - let p1 = add_peer(&mut gs2, &topic_hashes, false, false); + let (p2, _receiver1) = add_peer(&mut gs1, &Vec::new(), true, false); + let (p1, _receiver2) = add_peer(&mut gs2, &topic_hashes, false, false); - //add penalty to peer p2 + // add penalty to peer p2 gs1.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); let original_score = gs1.peer_score.as_ref().unwrap().0.score(&p2); - //subscribe to topic in gs2 + // subscribe to topic in gs2 gs2.subscribe(&topic).unwrap(); - let forward_messages_to_p1 = |gs1: &mut Behaviour<_, _>, gs2: &mut Behaviour<_, _>| { - //collect messages to p1 - let messages_to_p1 = gs2.events.drain(..).filter_map(|e| match e { - ToSwarm::NotifyHandler { peer_id, event, .. } => { - if peer_id == p1 { - if let HandlerIn::Message(m) = event { - Some(m) - } else { - None - } - } else { - None + let forward_messages_to_p1 = |gs1: &mut Behaviour<_, _>, + p1: PeerId, + p2: PeerId, + connection_id: ConnectionId, + receivers: HashMap| + -> HashMap { + let new_receivers = HashMap::new(); + for (peer_id, receiver) in receivers.into_iter() { + let non_priority = receiver.non_priority.get_ref(); + match non_priority.try_recv() { + Ok(rpc) if peer_id == p1 => { + gs1.on_connection_handler_event( + p2, + connection_id, + HandlerEvent::Message { + rpc: proto_to_message(&rpc.into_protobuf()), + invalid_messages: vec![], + }, + ); } + _ => {} } - _ => None, - }); - for message in messages_to_p1 { - gs1.on_connection_handler_event( - p2, - connection_id, - HandlerEvent::Message { - rpc: proto_to_message(&message.into_protobuf()), - invalid_messages: vec![], - }, - ); } + new_receivers }; - //forward the subscribe message - forward_messages_to_p1(&mut gs1, &mut gs2); + // forward the subscribe message + let receivers = forward_messages_to_p1(&mut gs1, p1, p2, connection_id, receivers); - //heartbeats on both + // heartbeats on both gs1.heartbeat(); gs2.heartbeat(); - //forward messages again - forward_messages_to_p1(&mut gs1, &mut gs2); + // forward messages again + forward_messages_to_p1(&mut gs1, p1, p2, connection_id, receivers); - //nobody got penalized + // nobody got penalized assert!(gs1.peer_score.as_ref().unwrap().0.score(&p2) >= original_score); } @@ -5093,14 +5250,14 @@ fn test_graft_without_subscribe() { let topic = String::from("test_subscribe"); let subscribe_topic = vec![topic.clone()]; let subscribe_topic_hash = vec![Topic::new(topic.clone()).hash()]; - let (mut gs, peers, topic_hashes) = inject_nodes1() + let (mut gs, peers, _, topic_hashes) = inject_nodes1() .peer_no(1) .topics(subscribe_topic) .to_subscribe(false) .create_network(); assert!( - gs.mesh.get(&topic_hashes[0]).is_some(), + gs.mesh.contains_key(&topic_hashes[0]), "Subscribe should add a new entry to the mesh[topic] hashmap" ); @@ -5113,3 +5270,730 @@ fn test_graft_without_subscribe() { // We unsubscribe from the topic. let _ = gs.unsubscribe(&Topic::new(topic)); } + +/// Test that a node sends IDONTWANT messages to the mesh peers +/// that run Gossipsub v1.2. +#[test] +fn sends_idontwant() { + let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() + .peer_no(5) + .topics(vec![String::from("topic1")]) + .to_subscribe(true) + .gs_config(Config::default()) + .explicit(1) + .peer_kind(PeerKind::Gossipsubv1_2) + .create_network(); + + let local_id = PeerId::random(); + + let message = RawMessage { + source: Some(peers[1]), + data: vec![12u8; 1024], + sequence_number: Some(0), + topic: topic_hashes[0].clone(), + signature: None, + key: None, + validated: true, + }; + gs.handle_received_message(message.clone(), &local_id); + assert_eq!( + receivers + .into_iter() + .fold(0, |mut idontwants, (peer_id, c)| { + let non_priority = c.non_priority.get_ref(); + while !non_priority.is_empty() { + if let Ok(RpcOut::IDontWant(_)) = non_priority.try_recv() { + assert_ne!(peer_id, peers[1]); + idontwants += 1; + } + } + idontwants + }), + 3, + "IDONTWANT was not sent" + ); +} + +#[test] +fn doesnt_sends_idontwant_for_lower_message_size() { + let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() + .peer_no(5) + .topics(vec![String::from("topic1")]) + .to_subscribe(true) + .gs_config(Config::default()) + .explicit(1) + .peer_kind(PeerKind::Gossipsubv1_2) + .create_network(); + + let local_id = PeerId::random(); + + let message = RawMessage { + source: Some(peers[1]), + data: vec![12], + sequence_number: Some(0), + topic: topic_hashes[0].clone(), + signature: None, + key: None, + validated: true, + }; + + gs.handle_received_message(message.clone(), &local_id); + assert_eq!( + receivers + .into_iter() + .fold(0, |mut idontwants, (peer_id, c)| { + let non_priority = c.non_priority.get_ref(); + while !non_priority.is_empty() { + if let Ok(RpcOut::IDontWant(_)) = non_priority.try_recv() { + assert_ne!(peer_id, peers[1]); + idontwants += 1; + } + } + idontwants + }), + 0, + "IDONTWANT was sent" + ); +} + +/// Test that a node doesn't send IDONTWANT messages to the mesh peers +/// that don't run Gossipsub v1.2. +#[test] +fn doesnt_send_idontwant() { + let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() + .peer_no(5) + .topics(vec![String::from("topic1")]) + .to_subscribe(true) + .gs_config(Config::default()) + .explicit(1) + .peer_kind(PeerKind::Gossipsubv1_1) + .create_network(); + + let local_id = PeerId::random(); + + let message = RawMessage { + source: Some(peers[1]), + data: vec![12], + sequence_number: Some(0), + topic: topic_hashes[0].clone(), + signature: None, + key: None, + validated: true, + }; + gs.handle_received_message(message.clone(), &local_id); + assert_eq!( + receivers + .into_iter() + .fold(0, |mut idontwants, (peer_id, c)| { + let non_priority = c.non_priority.get_ref(); + while !non_priority.is_empty() { + if matches!(non_priority.try_recv(), Ok(RpcOut::IDontWant(_)) if peer_id != peers[1]) { + idontwants += 1; + } + } + idontwants + }), + 0, + "IDONTWANT were sent" + ); +} + +/// Test that a node doesn't forward a messages to the mesh peers +/// that sent IDONTWANT. +#[test] +fn doesnt_forward_idontwant() { + let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() + .peer_no(4) + .topics(vec![String::from("topic1")]) + .to_subscribe(true) + .gs_config(Config::default()) + .explicit(1) + .peer_kind(PeerKind::Gossipsubv1_2) + .create_network(); + + let local_id = PeerId::random(); + + let raw_message = RawMessage { + source: Some(peers[1]), + data: vec![12], + sequence_number: Some(0), + topic: topic_hashes[0].clone(), + signature: None, + key: None, + validated: true, + }; + let message = gs + .data_transform + .inbound_transform(raw_message.clone()) + .unwrap(); + let message_id = gs.config.message_id(&message); + let peer = gs.connected_peers.get_mut(&peers[2]).unwrap(); + peer.dont_send.insert(message_id, Instant::now()); + + gs.handle_received_message(raw_message.clone(), &local_id); + assert_eq!( + receivers.into_iter().fold(0, |mut fwds, (peer_id, c)| { + let non_priority = c.non_priority.get_ref(); + while !non_priority.is_empty() { + if let Ok(RpcOut::Forward { .. }) = non_priority.try_recv() { + assert_ne!(peer_id, peers[2]); + fwds += 1; + } + } + fwds + }), + 2, + "IDONTWANT was not sent" + ); +} + +/// Test that a node parses an +/// IDONTWANT message to the respective peer. +#[test] +fn parses_idontwant() { + let (mut gs, peers, _receivers, _topic_hashes) = inject_nodes1() + .peer_no(2) + .topics(vec![String::from("topic1")]) + .to_subscribe(true) + .gs_config(Config::default()) + .explicit(1) + .peer_kind(PeerKind::Gossipsubv1_2) + .create_network(); + + let message_id = MessageId::new(&[0, 1, 2, 3]); + let rpc = Rpc { + messages: vec![], + subscriptions: vec![], + control_msgs: vec![ControlAction::IDontWant(IDontWant { + message_ids: vec![message_id.clone()], + })], + }; + gs.on_connection_handler_event( + peers[1], + ConnectionId::new_unchecked(0), + HandlerEvent::Message { + rpc, + invalid_messages: vec![], + }, + ); + let peer = gs.connected_peers.get_mut(&peers[1]).unwrap(); + assert!(peer.dont_send.get(&message_id).is_some()); +} + +/// Test that a node clears stale IDONTWANT messages. +#[test] +fn clear_stale_idontwant() { + let (mut gs, peers, _receivers, _topic_hashes) = inject_nodes1() + .peer_no(4) + .topics(vec![String::from("topic1")]) + .to_subscribe(true) + .gs_config(Config::default()) + .explicit(1) + .peer_kind(PeerKind::Gossipsubv1_2) + .create_network(); + + let peer = gs.connected_peers.get_mut(&peers[2]).unwrap(); + peer.dont_send + .insert(MessageId::new(&[1, 2, 3, 4]), Instant::now()); + std::thread::sleep(Duration::from_secs(3)); + gs.heartbeat(); + let peer = gs.connected_peers.get_mut(&peers[2]).unwrap(); + assert!(peer.dont_send.is_empty()); +} + +#[test] +fn test_all_queues_full() { + let gs_config = ConfigBuilder::default() + .validation_mode(ValidationMode::Permissive) + .build() + .unwrap(); + + let mut gs: Behaviour = Behaviour::new(MessageAuthenticity::RandomAuthor, gs_config).unwrap(); + + let topic_hash = Topic::new("Test").hash(); + let mut peers = vec![]; + let mut topics = BTreeSet::new(); + topics.insert(topic_hash.clone()); + + let peer_id = PeerId::random(); + peers.push(peer_id); + gs.connected_peers.insert( + peer_id, + PeerConnections { + kind: PeerKind::Gossipsubv1_1, + connections: vec![ConnectionId::new_unchecked(0)], + topics: topics.clone(), + sender: Sender::new(2), + dont_send: LinkedHashMap::new(), + }, + ); + + let publish_data = vec![0; 42]; + gs.publish(topic_hash.clone(), publish_data.clone()) + .unwrap(); + let publish_data = vec![2; 59]; + let err = gs.publish(topic_hash, publish_data).unwrap_err(); + assert!(matches!(err, PublishError::AllQueuesFull(f) if f == 1)); +} + +#[test] +fn test_slow_peer_returns_failed_publish() { + let gs_config = ConfigBuilder::default() + .validation_mode(ValidationMode::Permissive) + .build() + .unwrap(); + + let mut gs: Behaviour = Behaviour::new(MessageAuthenticity::RandomAuthor, gs_config).unwrap(); + + let topic_hash = Topic::new("Test").hash(); + let mut peers = vec![]; + let mut topics = BTreeSet::new(); + topics.insert(topic_hash.clone()); + + let slow_peer_id = PeerId::random(); + peers.push(slow_peer_id); + gs.connected_peers.insert( + slow_peer_id, + PeerConnections { + kind: PeerKind::Gossipsubv1_1, + connections: vec![ConnectionId::new_unchecked(0)], + topics: topics.clone(), + sender: Sender::new(2), + dont_send: LinkedHashMap::new(), + }, + ); + let peer_id = PeerId::random(); + peers.push(peer_id); + gs.connected_peers.insert( + peer_id, + PeerConnections { + kind: PeerKind::Gossipsubv1_1, + connections: vec![ConnectionId::new_unchecked(0)], + topics: topics.clone(), + sender: Sender::new(gs.config.connection_handler_queue_len()), + dont_send: LinkedHashMap::new(), + }, + ); + + let publish_data = vec![0; 42]; + gs.publish(topic_hash.clone(), publish_data.clone()) + .unwrap(); + let publish_data = vec![2; 59]; + gs.publish(topic_hash.clone(), publish_data).unwrap(); + gs.heartbeat(); + + gs.heartbeat(); + + let slow_peer_failed_messages = match gs.events.pop_front().unwrap() { + ToSwarm::GenerateEvent(Event::SlowPeer { + peer_id, + failed_messages, + }) if peer_id == slow_peer_id => failed_messages, + _ => panic!("invalid event"), + }; + + let failed_messages = FailedMessages { + publish: 1, + forward: 0, + priority: 1, + non_priority: 0, + timeout: 0, + }; + + assert_eq!(slow_peer_failed_messages.priority, failed_messages.priority); + assert_eq!( + slow_peer_failed_messages.non_priority, + failed_messages.non_priority + ); + assert_eq!(slow_peer_failed_messages.publish, failed_messages.publish); + assert_eq!(slow_peer_failed_messages.forward, failed_messages.forward); +} + +#[test] +fn test_slow_peer_returns_failed_ihave_handling() { + let gs_config = ConfigBuilder::default() + .validation_mode(ValidationMode::Permissive) + .build() + .unwrap(); + + let mut gs: Behaviour = Behaviour::new(MessageAuthenticity::RandomAuthor, gs_config).unwrap(); + + let topic_hash = Topic::new("Test").hash(); + let mut peers = vec![]; + let mut topics = BTreeSet::new(); + topics.insert(topic_hash.clone()); + + let slow_peer_id = PeerId::random(); + gs.connected_peers.insert( + slow_peer_id, + PeerConnections { + kind: PeerKind::Gossipsubv1_1, + connections: vec![ConnectionId::new_unchecked(0)], + topics: topics.clone(), + sender: Sender::new(2), + dont_send: LinkedHashMap::new(), + }, + ); + peers.push(slow_peer_id); + let mesh = gs.mesh.entry(topic_hash.clone()).or_default(); + mesh.insert(slow_peer_id); + + let peer_id = PeerId::random(); + peers.push(peer_id); + gs.connected_peers.insert( + peer_id, + PeerConnections { + kind: PeerKind::Gossipsubv1_1, + connections: vec![ConnectionId::new_unchecked(0)], + topics: topics.clone(), + sender: Sender::new(gs.config.connection_handler_queue_len()), + dont_send: LinkedHashMap::new(), + }, + ); + + // First message. + let publish_data = vec![1; 59]; + let transformed = gs + .data_transform + .outbound_transform(&topic_hash, publish_data.clone()) + .unwrap(); + let raw_message = gs + .build_raw_message(topic_hash.clone(), transformed) + .unwrap(); + let msg_id = gs.config.message_id(&Message { + source: raw_message.source, + data: publish_data, + sequence_number: raw_message.sequence_number, + topic: raw_message.topic.clone(), + }); + + gs.handle_ihave( + &slow_peer_id, + vec![(topic_hash.clone(), vec![msg_id.clone()])], + ); + + // Second message. + let publish_data = vec![2; 59]; + let transformed = gs + .data_transform + .outbound_transform(&topic_hash, publish_data.clone()) + .unwrap(); + let raw_message = gs + .build_raw_message(topic_hash.clone(), transformed) + .unwrap(); + let msg_id = gs.config.message_id(&Message { + source: raw_message.source, + data: publish_data, + sequence_number: raw_message.sequence_number, + topic: raw_message.topic.clone(), + }); + gs.handle_ihave(&slow_peer_id, vec![(topic_hash, vec![msg_id.clone()])]); + + gs.heartbeat(); + + let slow_peer_failed_messages = gs + .events + .into_iter() + .find_map(|e| match e { + ToSwarm::GenerateEvent(Event::SlowPeer { + peer_id, + failed_messages, + }) if peer_id == slow_peer_id => Some(failed_messages), + _ => None, + }) + .unwrap(); + + let failed_messages = FailedMessages { + publish: 0, + forward: 0, + priority: 0, + non_priority: 1, + timeout: 0, + }; + + assert_eq!(slow_peer_failed_messages.priority, failed_messages.priority); + assert_eq!( + slow_peer_failed_messages.non_priority, + failed_messages.non_priority + ); + assert_eq!(slow_peer_failed_messages.publish, failed_messages.publish); + assert_eq!(slow_peer_failed_messages.forward, failed_messages.forward); +} + +#[test] +fn test_slow_peer_returns_failed_iwant_handling() { + let gs_config = ConfigBuilder::default() + .validation_mode(ValidationMode::Permissive) + .build() + .unwrap(); + + let mut gs: Behaviour = Behaviour::new(MessageAuthenticity::RandomAuthor, gs_config).unwrap(); + + let topic_hash = Topic::new("Test").hash(); + let mut peers = vec![]; + let mut topics = BTreeSet::new(); + topics.insert(topic_hash.clone()); + + let slow_peer_id = PeerId::random(); + peers.push(slow_peer_id); + gs.connected_peers.insert( + slow_peer_id, + PeerConnections { + kind: PeerKind::Gossipsubv1_1, + connections: vec![ConnectionId::new_unchecked(0)], + topics: topics.clone(), + sender: Sender::new(2), + dont_send: LinkedHashMap::new(), + }, + ); + peers.push(slow_peer_id); + let mesh = gs.mesh.entry(topic_hash.clone()).or_default(); + mesh.insert(slow_peer_id); + + let peer_id = PeerId::random(); + peers.push(peer_id); + gs.connected_peers.insert( + peer_id, + PeerConnections { + kind: PeerKind::Gossipsubv1_1, + connections: vec![ConnectionId::new_unchecked(0)], + topics: topics.clone(), + sender: Sender::new(gs.config.connection_handler_queue_len()), + dont_send: LinkedHashMap::new(), + }, + ); + + let publish_data = vec![1; 59]; + let transformed = gs + .data_transform + .outbound_transform(&topic_hash, publish_data.clone()) + .unwrap(); + let raw_message = gs + .build_raw_message(topic_hash.clone(), transformed) + .unwrap(); + let msg_id = gs.config.message_id(&Message { + source: raw_message.source, + data: publish_data, + sequence_number: raw_message.sequence_number, + topic: raw_message.topic.clone(), + }); + + gs.mcache.put(&msg_id, raw_message); + gs.handle_iwant(&slow_peer_id, vec![msg_id.clone(), msg_id]); + + gs.heartbeat(); + + let slow_peer_failed_messages = gs + .events + .into_iter() + .find_map(|e| match e { + ToSwarm::GenerateEvent(Event::SlowPeer { + peer_id, + failed_messages, + }) if peer_id == slow_peer_id => Some(failed_messages), + _ => None, + }) + .unwrap(); + + let failed_messages = FailedMessages { + publish: 0, + forward: 1, + priority: 0, + non_priority: 1, + timeout: 0, + }; + + assert_eq!(slow_peer_failed_messages.priority, failed_messages.priority); + assert_eq!( + slow_peer_failed_messages.non_priority, + failed_messages.non_priority + ); + assert_eq!(slow_peer_failed_messages.publish, failed_messages.publish); + assert_eq!(slow_peer_failed_messages.forward, failed_messages.forward); +} + +#[test] +fn test_slow_peer_returns_failed_forward() { + let gs_config = ConfigBuilder::default() + .validation_mode(ValidationMode::Permissive) + .build() + .unwrap(); + + let mut gs: Behaviour = Behaviour::new(MessageAuthenticity::RandomAuthor, gs_config).unwrap(); + + let topic_hash = Topic::new("Test").hash(); + let mut peers = vec![]; + let mut topics = BTreeSet::new(); + topics.insert(topic_hash.clone()); + + let slow_peer_id = PeerId::random(); + peers.push(slow_peer_id); + gs.connected_peers.insert( + slow_peer_id, + PeerConnections { + kind: PeerKind::Gossipsubv1_1, + connections: vec![ConnectionId::new_unchecked(0)], + topics: topics.clone(), + sender: Sender::new(2), + dont_send: LinkedHashMap::new(), + }, + ); + peers.push(slow_peer_id); + let mesh = gs.mesh.entry(topic_hash.clone()).or_default(); + mesh.insert(slow_peer_id); + + let peer_id = PeerId::random(); + peers.push(peer_id); + gs.connected_peers.insert( + peer_id, + PeerConnections { + kind: PeerKind::Gossipsubv1_1, + connections: vec![ConnectionId::new_unchecked(0)], + topics: topics.clone(), + sender: Sender::new(gs.config.connection_handler_queue_len()), + dont_send: LinkedHashMap::new(), + }, + ); + + let publish_data = vec![1; 59]; + let transformed = gs + .data_transform + .outbound_transform(&topic_hash, publish_data.clone()) + .unwrap(); + let raw_message = gs + .build_raw_message(topic_hash.clone(), transformed) + .unwrap(); + let msg_id = gs.config.message_id(&Message { + source: raw_message.source, + data: publish_data, + sequence_number: raw_message.sequence_number, + topic: raw_message.topic.clone(), + }); + + gs.forward_msg(&msg_id, raw_message.clone(), None, HashSet::new()); + gs.forward_msg(&msg_id, raw_message, None, HashSet::new()); + + gs.heartbeat(); + + let slow_peer_failed_messages = gs + .events + .into_iter() + .find_map(|e| match e { + ToSwarm::GenerateEvent(Event::SlowPeer { + peer_id, + failed_messages, + }) if peer_id == slow_peer_id => Some(failed_messages), + _ => None, + }) + .unwrap(); + + let failed_messages = FailedMessages { + publish: 0, + forward: 1, + priority: 0, + non_priority: 1, + timeout: 0, + }; + + assert_eq!(slow_peer_failed_messages.priority, failed_messages.priority); + assert_eq!( + slow_peer_failed_messages.non_priority, + failed_messages.non_priority + ); + assert_eq!(slow_peer_failed_messages.publish, failed_messages.publish); + assert_eq!(slow_peer_failed_messages.forward, failed_messages.forward); +} + +#[test] +fn test_slow_peer_is_downscored_on_publish() { + let gs_config = ConfigBuilder::default() + .validation_mode(ValidationMode::Permissive) + .build() + .unwrap(); + + let mut gs: Behaviour = Behaviour::new(MessageAuthenticity::RandomAuthor, gs_config).unwrap(); + let slow_peer_params = PeerScoreParams::default(); + gs.with_peer_score(slow_peer_params.clone(), PeerScoreThresholds::default()) + .unwrap(); + + let topic_hash = Topic::new("Test").hash(); + let mut peers = vec![]; + let mut topics = BTreeSet::new(); + topics.insert(topic_hash.clone()); + + let slow_peer_id = PeerId::random(); + peers.push(slow_peer_id); + let mesh = gs.mesh.entry(topic_hash.clone()).or_default(); + mesh.insert(slow_peer_id); + gs.connected_peers.insert( + slow_peer_id, + PeerConnections { + kind: PeerKind::Gossipsubv1_1, + connections: vec![ConnectionId::new_unchecked(0)], + topics: topics.clone(), + sender: Sender::new(2), + dont_send: LinkedHashMap::new(), + }, + ); + gs.peer_score.as_mut().unwrap().0.add_peer(slow_peer_id); + let peer_id = PeerId::random(); + peers.push(peer_id); + gs.connected_peers.insert( + peer_id, + PeerConnections { + kind: PeerKind::Gossipsubv1_1, + connections: vec![ConnectionId::new_unchecked(0)], + topics: topics.clone(), + sender: Sender::new(gs.config.connection_handler_queue_len()), + dont_send: LinkedHashMap::new(), + }, + ); + + let publish_data = vec![0; 42]; + gs.publish(topic_hash.clone(), publish_data.clone()) + .unwrap(); + let publish_data = vec![2; 59]; + gs.publish(topic_hash.clone(), publish_data).unwrap(); + gs.heartbeat(); + let slow_peer_score = gs.peer_score(&slow_peer_id).unwrap(); + assert_eq!(slow_peer_score, slow_peer_params.slow_peer_weight); +} + +#[tokio::test] +async fn test_timedout_messages_are_reported() { + let gs_config = ConfigBuilder::default() + .validation_mode(ValidationMode::Permissive) + .build() + .unwrap(); + + let mut gs: Behaviour = Behaviour::new(MessageAuthenticity::RandomAuthor, gs_config).unwrap(); + + let sender = Sender::new(2); + let topic_hash = Topic::new("Test").hash(); + let publish_data = vec![2; 59]; + let raw_message = gs.build_raw_message(topic_hash, publish_data).unwrap(); + + sender + .send_message(RpcOut::Publish { + message: raw_message, + timeout: Delay::new(Duration::from_nanos(1)), + }) + .unwrap(); + let mut receiver = sender.new_receiver(); + let stale = future::poll_fn(|cx| receiver.poll_stale(cx)).await.unwrap(); + assert!(matches!(stale, RpcOut::Publish { .. })); +} + +#[test] +fn test_priority_messages_are_always_sent() { + let sender = Sender::new(2); + let topic_hash = Topic::new("Test").hash(); + // Fill the buffer with the first message. + assert!(sender + .send_message(RpcOut::Subscribe(topic_hash.clone())) + .is_ok()); + assert!(sender + .send_message(RpcOut::Subscribe(topic_hash.clone())) + .is_ok()); + assert!(sender.send_message(RpcOut::Unsubscribe(topic_hash)).is_ok()); +} diff --git a/protocols/gossipsub/src/config.rs b/protocols/gossipsub/src/config.rs index 7e79912cc4a..7bd6c9c583d 100644 --- a/protocols/gossipsub/src/config.rs +++ b/protocols/gossipsub/src/config.rs @@ -18,22 +18,22 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::borrow::Cow; -use std::sync::Arc; -use std::time::Duration; - -use crate::error::ConfigBuilderError; -use crate::protocol::{ProtocolConfig, ProtocolId, FLOODSUB_PROTOCOL}; -use crate::types::{Message, MessageId, PeerKind}; +use std::{borrow::Cow, sync::Arc, time::Duration}; use libp2p_identity::PeerId; use libp2p_swarm::StreamProtocol; +use crate::{ + error::ConfigBuilderError, + protocol::{ProtocolConfig, ProtocolId, FLOODSUB_PROTOCOL}, + types::{Message, MessageId, PeerKind}, +}; + /// The types of message validation that can be employed by gossipsub. #[derive(Debug, Clone)] pub enum ValidationMode { - /// This is the default setting. This requires the message author to be a valid [`PeerId`] and to - /// be present as well as the sequence number. All messages must have valid signatures. + /// This is the default setting. This requires the message author to be a valid [`PeerId`] and + /// to be present as well as the sequence number. All messages must have valid signatures. /// /// NOTE: This setting will reject messages from nodes using /// [`crate::behaviour::MessageAuthenticity::Anonymous`] and all messages that do not have @@ -95,6 +95,10 @@ pub struct Config { max_ihave_messages: usize, iwant_followup_time: Duration, published_message_ids_cache_time: Duration, + connection_handler_queue_len: usize, + connection_handler_publish_duration: Duration, + connection_handler_forward_duration: Duration, + idontwant_message_size_threshold: usize, } impl Config { @@ -131,8 +135,8 @@ impl Config { /// Affects how peers are selected when pruning a mesh due to over subscription. /// - /// At least `retain_scores` of the retained peers will be high-scoring, while the remainder are - /// chosen randomly (D_score in the spec, default is 4). + /// At least `retain_scores` of the retained peers will be high-scoring, while the remainder + /// are chosen randomly (D_score in the spec, default is 4). pub fn retain_scores(&self) -> usize { self.retain_scores } @@ -174,7 +178,8 @@ impl Config { /// The maximum byte size for each gossipsub RPC (default is 65536 bytes). /// - /// This represents the maximum size of the entire protobuf payload. It must be at least + /// This represents the maximum size of the published message. It is additionally wrapped + /// in a protobuf struct, so the actual wire size may be a bit larger. It must be at least /// large enough to support basic control messages. If Peer eXchange is enabled, this /// must be large enough to transmit the desired peer information on pruning. It must be at /// least 100 bytes. Default is 65536 bytes. @@ -262,9 +267,9 @@ impl Config { self.unsubscribe_backoff } - /// Number of heartbeat slots considered as slack for backoffs. This gurantees that we wait + /// Number of heartbeat slots considered as slack for backoffs. This guarantees that we wait /// at least backoff_slack heartbeats after a backoff is over before we try to graft. This - /// solves problems occuring through high latencies. In particular if + /// solves problems occurring through high latencies. In particular if /// `backoff_slack * heartbeat_interval` is longer than any latencies between processing /// prunes on our side and processing prunes on the receiving side this guarantees that we /// get not punished for too early grafting. The default is 1. @@ -292,7 +297,7 @@ impl Config { self.mesh_outbound_min } - /// Number of heartbeat ticks that specifcy the interval in which opportunistic grafting is + /// Number of heartbeat ticks that specify the interval in which opportunistic grafting is /// applied. Every `opportunistic_graft_ticks` we will attempt to select some high-scoring mesh /// peers to replace lower-scoring ones, if the median score of our mesh peers falls below a /// threshold (see ). @@ -350,6 +355,33 @@ impl Config { pub fn published_message_ids_cache_time(&self) -> Duration { self.published_message_ids_cache_time } + + /// The max number of messages a `ConnectionHandler` can buffer. The default is 5000. + pub fn connection_handler_queue_len(&self) -> usize { + self.connection_handler_queue_len + } + + /// The duration a message to be published can wait to be sent before it is abandoned. The + /// default is 5 seconds. + pub fn publish_queue_duration(&self) -> Duration { + self.connection_handler_publish_duration + } + + /// The duration a message to be forwarded can wait to be sent before it is abandoned. The + /// default is 1s. + pub fn forward_queue_duration(&self) -> Duration { + self.connection_handler_forward_duration + } + + // The message size threshold for which IDONTWANT messages are sent. + // Sending IDONTWANT messages for small messages can have a negative effect to the overall + // traffic and CPU load. This acts as a lower bound cutoff for the message size to which + // IDONTWANT won't be sent to peers. Only works if the peers support Gossipsub1.2 + // (see https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.2.md#idontwant-message) + // default is 1kB + pub fn idontwant_message_size_threshold(&self) -> usize { + self.idontwant_message_size_threshold + } } impl Default for Config { @@ -402,7 +434,9 @@ impl Default for ConfigBuilder { }), allow_self_origin: false, do_px: false, - prune_peers: 0, // NOTE: Increasing this currently has little effect until Signed records are implemented. + // NOTE: Increasing this currently has little effect until Signed + // records are implemented. + prune_peers: 0, prune_backoff: Duration::from_secs(60), unsubscribe_backoff: Duration::from_secs(10), backoff_slack: 1, @@ -417,6 +451,10 @@ impl Default for ConfigBuilder { max_ihave_messages: 10, iwant_followup_time: Duration::from_secs(3), published_message_ids_cache_time: Duration::from_secs(10), + connection_handler_queue_len: 5000, + connection_handler_publish_duration: Duration::from_secs(5), + connection_handler_forward_duration: Duration::from_secs(1), + idontwant_message_size_threshold: 1000, }, invalid_protocol: false, } @@ -433,7 +471,8 @@ impl From for ConfigBuilder { } impl ConfigBuilder { - /// The protocol id prefix to negotiate this protocol (default is `/meshsub/1.1.0` and `/meshsub/1.0.0`). + /// The protocol id prefix to negotiate this protocol (default is `/meshsub/1.1.0` and + /// `/meshsub/1.0.0`). pub fn protocol_id_prefix( &mut self, protocol_id_prefix: impl Into>, @@ -523,8 +562,8 @@ impl ConfigBuilder { /// Affects how peers are selected when pruning a mesh due to over subscription. /// - /// At least [`Self::retain_scores`] of the retained peers will be high-scoring, while the remainder are - /// chosen randomly (D_score in the spec, default is 4). + /// At least [`Self::retain_scores`] of the retained peers will be high-scoring, while the + /// remainder are chosen randomly (D_score in the spec, default is 4). pub fn retain_scores(&mut self, retain_scores: usize) -> &mut Self { self.config.retain_scores = retain_scores; self @@ -660,9 +699,9 @@ impl ConfigBuilder { self } - /// Number of heartbeat slots considered as slack for backoffs. This gurantees that we wait + /// Number of heartbeat slots considered as slack for backoffs. This guarantees that we wait /// at least backoff_slack heartbeats after a backoff is over before we try to graft. This - /// solves problems occuring through high latencies. In particular if + /// solves problems occurring through high latencies. In particular if /// `backoff_slack * heartbeat_interval` is longer than any latencies between processing /// prunes on our side and processing prunes on the receiving side this guarantees that we /// get not punished for too early grafting. The default is 1. @@ -694,7 +733,7 @@ impl ConfigBuilder { self } - /// Number of heartbeat ticks that specifcy the interval in which opportunistic grafting is + /// Number of heartbeat ticks that specify the interval in which opportunistic grafting is /// applied. Every `opportunistic_graft_ticks` we will attempt to select some high-scoring mesh /// peers to replace lower-scoring ones, if the median score of our mesh peers falls below a /// threshold (see ). @@ -782,6 +821,37 @@ impl ConfigBuilder { self } + /// The max number of messages a `ConnectionHandler` can buffer. The default is 5000. + pub fn connection_handler_queue_len(&mut self, len: usize) -> &mut Self { + self.config.connection_handler_queue_len = len; + self + } + + /// The duration a message to be published can wait to be sent before it is abandoned. The + /// default is 5 seconds. + pub fn publish_queue_duration(&mut self, duration: Duration) -> &mut Self { + self.config.connection_handler_publish_duration = duration; + self + } + + /// The duration a message to be forwarded can wait to be sent before it is abandoned. The + /// default is 1s. + pub fn forward_queue_duration(&mut self, duration: Duration) -> &mut Self { + self.config.connection_handler_forward_duration = duration; + self + } + + // The message size threshold for which IDONTWANT messages are sent. + // Sending IDONTWANT messages for small messages can have a negative effect to the overall + // traffic and CPU load. This acts as a lower bound cutoff for the message size to which + // IDONTWANT won't be sent to peers. Only works if the peers support Gossipsub1.2 + // (see https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.2.md#idontwant-message) + // default is 1kB + pub fn idontwant_message_size_threshold(&mut self, size: usize) -> &mut Self { + self.config.idontwant_message_size_threshold = size; + self + } + /// Constructs a [`Config`] from the given configuration and validates the settings. pub fn build(&self) -> Result { // check all constraints on config @@ -852,20 +922,25 @@ impl std::fmt::Debug for Config { "published_message_ids_cache_time", &self.published_message_ids_cache_time, ); + let _ = builder.field( + "idontwant_message_size_threhold", + &self.idontwant_message_size_threshold, + ); builder.finish() } } #[cfg(test)] mod test { - use super::*; - use crate::topic::IdentityHash; - use crate::types::PeerKind; - use crate::Topic; + use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + }; + use libp2p_core::UpgradeInfo; - use libp2p_swarm::StreamProtocol; - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; + + use super::*; + use crate::{topic::IdentityHash, Topic}; #[test] fn create_config_with_message_id_as_plain_function() { diff --git a/protocols/gossipsub/src/error.rs b/protocols/gossipsub/src/error.rs index c461abc0d17..eae4c51214e 100644 --- a/protocols/gossipsub/src/error.rs +++ b/protocols/gossipsub/src/error.rs @@ -36,6 +36,9 @@ pub enum PublishError { MessageTooLarge, /// The compression algorithm failed. TransformFailed(std::io::Error), + /// Messages could not be sent because the queues for all peers were full. The usize represents + /// the number of peers that were attempted. + AllQueuesFull(usize), } impl std::fmt::Display for PublishError { @@ -126,7 +129,7 @@ impl From for PublishError { pub enum ConfigBuilderError { /// Maximum transmission size is too small. MaxTransmissionSizeTooSmall, - /// Histroy length less than history gossip length. + /// History length less than history gossip length. HistoryLengthTooSmall, /// The ineauality doesn't hold mesh_outbound_min <= mesh_n_low <= mesh_n <= mesh_n_high MeshParametersInvalid, @@ -146,7 +149,7 @@ impl std::fmt::Display for ConfigBuilderError { Self::MaxTransmissionSizeTooSmall => { write!(f, "Maximum transmission size is too small") } - Self::HistoryLengthTooSmall => write!(f, "Histroy length less than history gossip length"), + Self::HistoryLengthTooSmall => write!(f, "History length less than history gossip length"), Self::MeshParametersInvalid => write!(f, "The ineauality doesn't hold mesh_outbound_min <= mesh_n_low <= mesh_n <= mesh_n_high"), Self::MeshOutboundInvalid => write!(f, "The inequality doesn't hold mesh_outbound_min <= self.config.mesh_n / 2"), Self::UnsubscribeBackoffIsZero => write!(f, "unsubscribe_backoff is zero"), diff --git a/protocols/gossipsub/src/generated/gossipsub/pb.rs b/protocols/gossipsub/src/generated/gossipsub/pb.rs index 9a074fd61fc..24ac80d2755 100644 --- a/protocols/gossipsub/src/generated/gossipsub/pb.rs +++ b/protocols/gossipsub/src/generated/gossipsub/pb.rs @@ -154,6 +154,7 @@ pub struct ControlMessage { pub iwant: Vec, pub graft: Vec, pub prune: Vec, + pub idontwant: Vec, } impl<'a> MessageRead<'a> for ControlMessage { @@ -165,6 +166,7 @@ impl<'a> MessageRead<'a> for ControlMessage { Ok(18) => msg.iwant.push(r.read_message::(bytes)?), Ok(26) => msg.graft.push(r.read_message::(bytes)?), Ok(34) => msg.prune.push(r.read_message::(bytes)?), + Ok(42) => msg.idontwant.push(r.read_message::(bytes)?), Ok(t) => { r.read_unknown(bytes, t)?; } Err(e) => return Err(e), } @@ -180,6 +182,7 @@ impl MessageWrite for ControlMessage { + self.iwant.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() + self.graft.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() + self.prune.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() + + self.idontwant.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() } fn write_message(&self, w: &mut Writer) -> Result<()> { @@ -187,6 +190,7 @@ impl MessageWrite for ControlMessage { for s in &self.iwant { w.write_with_tag(18, |w| w.write_message(s))?; } for s in &self.graft { w.write_with_tag(26, |w| w.write_message(s))?; } for s in &self.prune { w.write_with_tag(34, |w| w.write_message(s))?; } + for s in &self.idontwant { w.write_with_tag(42, |w| w.write_message(s))?; } Ok(()) } } @@ -331,6 +335,38 @@ impl MessageWrite for ControlPrune { } } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct ControlIDontWant { + pub message_ids: Vec>, +} + +impl<'a> MessageRead<'a> for ControlIDontWant { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(10) => msg.message_ids.push(r.read_bytes(bytes)?.to_owned()), + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl MessageWrite for ControlIDontWant { + fn get_size(&self) -> usize { + 0 + + self.message_ids.iter().map(|s| 1 + sizeof_len((s).len())).sum::() + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + for s in &self.message_ids { w.write_with_tag(10, |w| w.write_bytes(&**s))?; } + Ok(()) + } +} + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Debug, Default, PartialEq, Clone)] pub struct PeerInfo { diff --git a/protocols/gossipsub/src/generated/rpc.proto b/protocols/gossipsub/src/generated/rpc.proto index 2ce12f3f37f..fe4d3bc9366 100644 --- a/protocols/gossipsub/src/generated/rpc.proto +++ b/protocols/gossipsub/src/generated/rpc.proto @@ -19,8 +19,8 @@ message Message { optional bytes data = 2; optional bytes seqno = 3; required string topic = 4; - optional bytes signature = 5; - optional bytes key = 6; + optional bytes signature = 5; + optional bytes key = 6; } message ControlMessage { @@ -28,6 +28,7 @@ message ControlMessage { repeated ControlIWant iwant = 2; repeated ControlGraft graft = 3; repeated ControlPrune prune = 4; + repeated ControlIDontWant idontwant = 5; } message ControlIHave { @@ -49,6 +50,10 @@ message ControlPrune { optional uint64 backoff = 3; // gossipsub v1.1 backoff time (in seconds) } +message ControlIDontWant { + repeated bytes message_ids = 1; +} + message PeerInfo { optional bytes peer_id = 1; optional bytes signed_peer_record = 2; diff --git a/protocols/gossipsub/src/gossip_promises.rs b/protocols/gossipsub/src/gossip_promises.rs index 9538622c0dc..284ba7cab01 100644 --- a/protocols/gossipsub/src/gossip_promises.rs +++ b/protocols/gossipsub/src/gossip_promises.rs @@ -18,13 +18,13 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::peer_score::RejectReason; -use crate::MessageId; -use crate::ValidationError; -use instant::Instant; -use libp2p_identity::PeerId; use std::collections::HashMap; +use libp2p_identity::PeerId; +use web_time::Instant; + +use crate::{peer_score::RejectReason, MessageId, ValidationError}; + /// Tracks recently sent `IWANT` messages and checks if peers respond to them. #[derive(Default)] pub(crate) struct GossipPromises { @@ -41,6 +41,14 @@ impl GossipPromises { self.promises.contains_key(message) } + /// Get the peers we sent IWANT the input message id. + pub(crate) fn peers_for_message(&self, message_id: &MessageId) -> Vec { + self.promises + .get(message_id) + .map(|peers| peers.keys().copied().collect()) + .unwrap_or_default() + } + /// Track a promise to deliver a message from a list of [`MessageId`]s we are requesting. pub(crate) fn add_promise(&mut self, peer: PeerId, messages: &[MessageId], expires: Instant) { for message_id in messages { diff --git a/protocols/gossipsub/src/handler.rs b/protocols/gossipsub/src/handler.rs index e91f81776e7..e66b606896b 100644 --- a/protocols/gossipsub/src/handler.rs +++ b/protocols/gossipsub/src/handler.rs @@ -18,27 +18,31 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::protocol::{GossipsubCodec, ProtocolConfig}; -use crate::rpc_proto::proto; -use crate::types::{PeerKind, RawMessage, Rpc, RpcOut}; -use crate::ValidationError; -use asynchronous_codec::Framed; -use futures::future::Either; -use futures::prelude::*; -use futures::StreamExt; -use instant::Instant; -use libp2p_core::upgrade::DeniedUpgrade; -use libp2p_swarm::handler::{ - ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, DialUpgradeError, - FullyNegotiatedInbound, FullyNegotiatedOutbound, StreamUpgradeError, SubstreamProtocol, -}; -use libp2p_swarm::Stream; -use smallvec::SmallVec; use std::{ pin::Pin, task::{Context, Poll}, }; +use asynchronous_codec::Framed; +use futures::{future::Either, prelude::*, StreamExt}; +use libp2p_core::upgrade::DeniedUpgrade; +use libp2p_swarm::{ + handler::{ + ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, DialUpgradeError, + FullyNegotiatedInbound, FullyNegotiatedOutbound, StreamUpgradeError, SubstreamProtocol, + }, + Stream, +}; +use web_time::Instant; + +use crate::{ + protocol::{GossipsubCodec, ProtocolConfig}, + rpc::Receiver, + rpc_proto::proto, + types::{PeerKind, RawMessage, Rpc, RpcOut}, + ValidationError, +}; + /// The event emitted by the Handler. This informs the behaviour of various events created /// by the handler. #[derive(Debug)] @@ -55,14 +59,14 @@ pub enum HandlerEvent { /// An inbound or outbound substream has been established with the peer and this informs over /// which protocol. This message only occurs once per connection. PeerKind(PeerKind), + /// A message to be published was dropped because it could not be sent in time. + MessageDropped(RpcOut), } /// A message sent from the behaviour to the handler. #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum HandlerIn { - /// A gossipsub message to send. - Message(RpcOut), /// The peer has joined the mesh. JoinedMesh, /// The peer has left the mesh. @@ -94,8 +98,8 @@ pub struct EnabledHandler { /// The single long-lived inbound substream. inbound_substream: Option, - /// Queue of values that we want to send to the remote. - send_queue: SmallVec<[proto::RPC; 16]>, + /// Queue of values that we want to send to the remote + send_queue: Receiver, /// Flag indicating that an outbound substream is being established to prevent duplicate /// requests. @@ -111,7 +115,6 @@ pub struct EnabledHandler { peer_kind: Option, /// Keeps track on whether we have sent the peer kind to the behaviour. - // // NOTE: Use this flag rather than checking the substream count each poll. peer_kind_sent: bool, @@ -159,7 +162,7 @@ enum OutboundSubstreamState { impl Handler { /// Builds a new [`Handler`]. - pub fn new(protocol_config: ProtocolConfig) -> Self { + pub fn new(protocol_config: ProtocolConfig, message_queue: Receiver) -> Self { Handler::Enabled(EnabledHandler { listen_protocol: protocol_config, inbound_substream: None, @@ -167,7 +170,7 @@ impl Handler { outbound_substream_establishing: false, outbound_substream_attempts: 0, inbound_substream_attempts: 0, - send_queue: SmallVec::new(), + send_queue: message_queue, peer_kind: None, peer_kind_sent: false, last_io_activity: Instant::now(), @@ -226,13 +229,13 @@ impl EnabledHandler { if let Some(peer_kind) = self.peer_kind.as_ref() { self.peer_kind_sent = true; return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - HandlerEvent::PeerKind(peer_kind.clone()), + HandlerEvent::PeerKind(*peer_kind), )); } } // determine if we need to create the outbound stream - if !self.send_queue.is_empty() + if !self.send_queue.poll_is_empty(cx) && self.outbound_substream.is_none() && !self.outbound_substream_establishing { @@ -250,10 +253,31 @@ impl EnabledHandler { ) { // outbound idle state Some(OutboundSubstreamState::WaitingOutput(substream)) => { - if let Some(message) = self.send_queue.pop() { - self.send_queue.shrink_to_fit(); - self.outbound_substream = - Some(OutboundSubstreamState::PendingSend(substream, message)); + if let Poll::Ready(Some(mut message)) = self.send_queue.poll_next_unpin(cx) { + match message { + RpcOut::Publish { + message: _, + ref mut timeout, + } + | RpcOut::Forward { + message: _, + ref mut timeout, + } => { + if Pin::new(timeout).poll(cx).is_ready() { + // Inform the behaviour and end the poll. + self.outbound_substream = + Some(OutboundSubstreamState::WaitingOutput(substream)); + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + HandlerEvent::MessageDropped(message), + )); + } + } + _ => {} // All other messages are not time-bound. + } + self.outbound_substream = Some(OutboundSubstreamState::PendingSend( + substream, + message.into_protobuf(), + )); continue; } @@ -319,6 +343,7 @@ impl EnabledHandler { } } + // Handle inbound messages. loop { match std::mem::replace( &mut self.inbound_substream, @@ -383,6 +408,13 @@ impl EnabledHandler { } } + // Drop the next message in queue if it's stale. + if let Poll::Ready(Some(rpc)) = self.send_queue.poll_stale(cx) { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + HandlerEvent::MessageDropped(rpc), + )); + } + Poll::Pending } } @@ -409,7 +441,6 @@ impl ConnectionHandler for Handler { fn on_behaviour_event(&mut self, message: HandlerIn) { match self { Handler::Enabled(handler) => match message { - HandlerIn::Message(m) => handler.send_queue.push(m.into_protobuf()), HandlerIn::JoinedMesh => { handler.in_mesh = true; } @@ -493,7 +524,9 @@ impl ConnectionHandler for Handler { .. }) => match protocol { Either::Left(protocol) => handler.on_fully_negotiated_inbound(protocol), - Either::Right(v) => void::unreachable(v), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + Either::Right(v) => libp2p_core::util::unreachable(v), }, ConnectionEvent::FullyNegotiatedOutbound(fully_negotiated_outbound) => { handler.on_fully_negotiated_outbound(fully_negotiated_outbound) @@ -504,10 +537,12 @@ impl ConnectionHandler for Handler { }) => { tracing::debug!("Dial upgrade error: Protocol negotiation timeout"); } + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::DialUpgradeError(DialUpgradeError { error: StreamUpgradeError::Apply(e), .. - }) => void::unreachable(e), + }) => libp2p_core::util::unreachable(e), ConnectionEvent::DialUpgradeError(DialUpgradeError { error: StreamUpgradeError::NegotiationFailed, .. diff --git a/protocols/gossipsub/src/lib.rs b/protocols/gossipsub/src/lib.rs index 15db5eba21d..87db1b771d1 100644 --- a/protocols/gossipsub/src/lib.rs +++ b/protocols/gossipsub/src/lib.rs @@ -43,22 +43,23 @@ //! implementations, due to undefined elements in the current specification. //! //! - **Topics** - In gossipsub, topics configurable by the `hash_topics` configuration parameter. -//! Topics are of type [`TopicHash`]. The current go implementation uses raw utf-8 strings, and this -//! is default configuration in rust-libp2p. Topics can be hashed (SHA256 hashed then base64 -//! encoded) by setting the `hash_topics` configuration parameter to true. +//! Topics are of type [`TopicHash`]. The current go implementation uses raw utf-8 strings, and +//! this is default configuration in rust-libp2p. Topics can be hashed (SHA256 hashed then base64 +//! encoded) by setting the `hash_topics` configuration parameter to true. //! //! - **Sequence Numbers** - A message on the gossipsub network is identified by the source -//! [`PeerId`](libp2p_identity::PeerId) and a nonce (sequence number) of the message. The sequence numbers in -//! this implementation are sent as raw bytes across the wire. They are 64-bit big-endian unsigned -//! integers. When messages are signed, they are monotonically increasing integers starting from a -//! random value and wrapping around u64::MAX. When messages are unsigned, they are chosen at random. -//! NOTE: These numbers are sequential in the current go implementation. +//! [`PeerId`](libp2p_identity::PeerId) and a nonce (sequence number) of the message. The sequence +//! numbers in this implementation are sent as raw bytes across the wire. They are 64-bit +//! big-endian unsigned integers. When messages are signed, they are monotonically increasing +//! integers starting from a random value and wrapping around u64::MAX. When messages are +//! unsigned, they are chosen at random. NOTE: These numbers are sequential in the current go +//! implementation. //! //! # Peer Discovery //! //! Gossipsub does not provide peer discovery by itself. Peer discovery is the process by which -//! peers in a p2p network exchange information about each other among other reasons to become resistant -//! against the failure or replacement of the +//! peers in a p2p network exchange information about each other among other reasons to become +//! resistant against the failure or replacement of the //! [boot nodes](https://docs.libp2p.io/reference/glossary/#boot-node) of the network. //! //! Peer @@ -103,6 +104,7 @@ mod mcache; mod metrics; mod peer_score; mod protocol; +mod rpc; mod rpc_proto; mod subscription_filter; mod time_cache; @@ -110,22 +112,24 @@ mod topic; mod transform; mod types; -pub use self::behaviour::{Behaviour, Event, MessageAuthenticity}; -pub use self::config::{Config, ConfigBuilder, ValidationMode, Version}; -pub use self::error::{ConfigBuilderError, PublishError, SubscriptionError, ValidationError}; -pub use self::metrics::Config as MetricsConfig; -pub use self::peer_score::{ - score_parameter_decay, score_parameter_decay_with_base, PeerScoreParams, PeerScoreThresholds, - TopicScoreParams, +pub use self::{ + behaviour::{Behaviour, Event, MessageAuthenticity}, + config::{Config, ConfigBuilder, ValidationMode, Version}, + error::{ConfigBuilderError, PublishError, SubscriptionError, ValidationError}, + metrics::Config as MetricsConfig, + peer_score::{ + score_parameter_decay, score_parameter_decay_with_base, PeerScoreParams, + PeerScoreThresholds, TopicScoreParams, + }, + subscription_filter::{ + AllowAllSubscriptionFilter, CallbackSubscriptionFilter, CombinedSubscriptionFilters, + MaxCountSubscriptionFilter, RegexSubscriptionFilter, TopicSubscriptionFilter, + WhitelistSubscriptionFilter, + }, + topic::{Hasher, Topic, TopicHash}, + transform::{DataTransform, IdentityTransform}, + types::{FailedMessages, Message, MessageAcceptance, MessageId, RawMessage}, }; -pub use self::subscription_filter::{ - AllowAllSubscriptionFilter, CallbackSubscriptionFilter, CombinedSubscriptionFilters, - MaxCountSubscriptionFilter, RegexSubscriptionFilter, TopicSubscriptionFilter, - WhitelistSubscriptionFilter, -}; -pub use self::topic::{Hasher, Topic, TopicHash}; -pub use self::transform::{DataTransform, IdentityTransform}; -pub use self::types::{Message, MessageAcceptance, MessageId, RawMessage}; #[deprecated(note = "Will be removed from the public API.")] pub type Rpc = self::types::Rpc; diff --git a/protocols/gossipsub/src/mcache.rs b/protocols/gossipsub/src/mcache.rs index ef4a93bc936..8ed71ea07f2 100644 --- a/protocols/gossipsub/src/mcache.rs +++ b/protocols/gossipsub/src/mcache.rs @@ -18,14 +18,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::topic::TopicHash; -use crate::types::{MessageId, RawMessage}; -use libp2p_identity::PeerId; -use std::collections::hash_map::Entry; -use std::fmt::Debug; use std::{ - collections::{HashMap, HashSet}, + collections::{hash_map::Entry, HashMap, HashSet}, fmt, + fmt::Debug, +}; + +use libp2p_identity::PeerId; + +use crate::{ + topic::TopicHash, + types::{MessageId, RawMessage}, }; /// CacheEntry stored in the history. @@ -210,7 +213,7 @@ impl MessageCache { &mut self, message_id: &MessageId, ) -> Option<(RawMessage, HashSet)> { - //We only remove the message from msgs and iwant_count and keep the message_id in the + // We only remove the message from msgs and iwant_count and keep the message_id in the // history vector. Zhe id in the history vector will simply be ignored on popping. self.iwant_counts.remove(message_id); @@ -221,9 +224,7 @@ impl MessageCache { #[cfg(test)] mod tests { use super::*; - use crate::types::RawMessage; - use crate::{IdentTopic as Topic, TopicHash}; - use libp2p_identity::PeerId; + use crate::IdentTopic as Topic; fn gen_testm(x: u64, topic: TopicHash) -> (MessageId, RawMessage) { let default_id = |message: &RawMessage| { diff --git a/protocols/gossipsub/src/metrics.rs b/protocols/gossipsub/src/metrics.rs index e044ca67e71..42dedc000b7 100644 --- a/protocols/gossipsub/src/metrics.rs +++ b/protocols/gossipsub/src/metrics.rs @@ -23,15 +23,21 @@ use std::collections::HashMap; -use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; -use prometheus_client::metrics::counter::Counter; -use prometheus_client::metrics::family::{Family, MetricConstructor}; -use prometheus_client::metrics::gauge::Gauge; -use prometheus_client::metrics::histogram::{linear_buckets, Histogram}; -use prometheus_client::registry::Registry; - -use crate::topic::TopicHash; -use crate::types::{MessageAcceptance, PeerKind}; +use prometheus_client::{ + encoding::{EncodeLabelSet, EncodeLabelValue}, + metrics::{ + counter::Counter, + family::{Family, MetricConstructor}, + gauge::Gauge, + histogram::{linear_buckets, Histogram}, + }, + registry::Registry, +}; + +use crate::{ + topic::TopicHash, + types::{MessageAcceptance, PeerKind}, +}; // Default value that limits for how many topics do we store metrics. const DEFAULT_MAX_TOPICS: usize = 300; @@ -100,7 +106,7 @@ type EverSubscribed = bool; /// A collection of metrics used throughout the Gossipsub behaviour. pub(crate) struct Metrics { - /* Configuration parameters */ + // Configuration parameters /// Maximum number of topics for which we store metrics. This helps keep the metrics bounded. max_topics: usize, /// Maximum number of topics for which we store metrics, where the topic in not one to which we @@ -108,11 +114,11 @@ pub(crate) struct Metrics { /// from received messages and not explicit application subscriptions. max_never_subscribed_topics: usize, - /* Auxiliary variables */ + // Auxiliary variables /// Information needed to decide if a topic is allowed or not. topic_info: HashMap, - /* Metrics per known topic */ + // Metrics per known topic /// Status of our subscription to this topic. This metric allows analyzing other topic metrics /// filtered by our current subscription status. topic_subscription_status: Family, @@ -127,8 +133,14 @@ pub(crate) struct Metrics { ignored_messages: Family, /// The number of messages rejected by the application (validation result). rejected_messages: Family, - - /* Metrics regarding mesh state */ + /// The number of publish messages dropped by the sender. + publish_messages_dropped: Family, + /// The number of forward messages dropped by the sender. + forward_messages_dropped: Family, + /// The number of messages that timed out and could not be sent. + timedout_messages_dropped: Family, + + // Metrics regarding mesh state /// Number of peers in our mesh. This metric should be updated with the count of peers for a /// topic in the mesh regardless of inclusion and churn events. mesh_peer_counts: Family, @@ -137,7 +149,7 @@ pub(crate) struct Metrics { /// Number of times we remove peers in a topic mesh for different reasons. mesh_peer_churn_events: Family, - /* Metrics regarding messages sent/received */ + // Metrics regarding messages sent/received /// Number of gossip messages sent to each topic. topic_msg_sent_counts: Family, /// Bytes from gossip messages sent to each topic. @@ -152,13 +164,13 @@ pub(crate) struct Metrics { /// Bytes received from gossip messages for each topic. topic_msg_recv_bytes: Family, - /* Metrics related to scoring */ + // Metrics related to scoring /// Histogram of the scores for each mesh topic. score_per_mesh: Family, /// A counter of the kind of penalties being applied to peers. scoring_penalties: Family, - /* General Metrics */ + // General Metrics /// Gossipsub supports floodsub, gossipsub v1.0 and gossipsub v1.1. Peers are classified based /// on which protocol they support. This metric keeps track of the number of peers that are /// connected of each type. @@ -166,7 +178,7 @@ pub(crate) struct Metrics { /// The time it takes to complete one iteration of the heartbeat. heartbeat_duration: Histogram, - /* Performance metrics */ + // Performance metrics /// When the user validates a message, it tries to re propagate it to its mesh peers. If the /// message expires from the memcache before it can be validated, we count this a cache miss /// and it is an indicator that the memcache size should be increased. @@ -174,6 +186,17 @@ pub(crate) struct Metrics { /// The number of times we have decided that an IWANT control message is required for this /// topic. A very high metric might indicate an underperforming network. topic_iwant_msgs: Family, + + /// The number of times we have received an IDONTWANT control message. + idontwant_msgs: Counter, + + /// The number of msg_id's we have received in every IDONTWANT control message. + idontwant_msgs_ids: Counter, + + /// The size of the priority queue. + priority_queue_size: Histogram, + /// The size of the non-priority queue. + non_priority_queue_size: Histogram, } impl Metrics { @@ -222,6 +245,21 @@ impl Metrics { "Number of rejected messages received for each topic" ); + let publish_messages_dropped = register_family!( + "publish_messages_dropped_per_topic", + "Number of publish messages dropped per topic" + ); + + let forward_messages_dropped = register_family!( + "forward_messages_dropped_per_topic", + "Number of forward messages dropped per topic" + ); + + let timedout_messages_dropped = register_family!( + "timedout_messages_dropped_per_topic", + "Number of timedout messages dropped per topic" + ); + let mesh_peer_counts = register_family!( "mesh_peer_counts", "Number of peers in each topic in our mesh" @@ -292,6 +330,27 @@ impl Metrics { "topic_iwant_msgs", "Number of times we have decided an IWANT is required for this topic" ); + + let idontwant_msgs = { + let metric = Counter::default(); + registry.register( + "idontwant_msgs", + "The number of times we have received an IDONTWANT control message", + metric.clone(), + ); + metric + }; + + let idontwant_msgs_ids = { + let metric = Counter::default(); + registry.register( + "idontwant_msgs_ids", + "The number of msg_id's we have received in every total of all IDONTWANT control message.", + metric.clone(), + ); + metric + }; + let memcache_misses = { let metric = Counter::default(); registry.register( @@ -302,6 +361,20 @@ impl Metrics { metric }; + let priority_queue_size = Histogram::new(linear_buckets(0.0, 25.0, 100)); + registry.register( + "priority_queue_size", + "Histogram of observed priority queue sizes", + priority_queue_size.clone(), + ); + + let non_priority_queue_size = Histogram::new(linear_buckets(0.0, 25.0, 100)); + registry.register( + "non_priority_queue_size", + "Histogram of observed non-priority queue sizes", + non_priority_queue_size.clone(), + ); + Self { max_topics, max_never_subscribed_topics, @@ -312,6 +385,9 @@ impl Metrics { accepted_messages, ignored_messages, rejected_messages, + publish_messages_dropped, + forward_messages_dropped, + timedout_messages_dropped, mesh_peer_counts, mesh_peer_inclusion_events, mesh_peer_churn_events, @@ -327,6 +403,10 @@ impl Metrics { heartbeat_duration, memcache_misses, topic_iwant_msgs, + idontwant_msgs, + idontwant_msgs_ids, + priority_queue_size, + non_priority_queue_size, } } @@ -355,16 +435,21 @@ impl Metrics { } } - /// Register how many peers do we known are subscribed to this topic. - pub(crate) fn set_topic_peers(&mut self, topic: &TopicHash, count: usize) { + /// Increase the number of peers that are subscribed to this topic. + pub(crate) fn inc_topic_peers(&mut self, topic: &TopicHash) { if self.register_topic(topic).is_ok() { - self.topic_peers_count - .get_or_create(topic) - .set(count as i64); + self.topic_peers_count.get_or_create(topic).inc(); + } + } + + /// Decrease the number of peers that are subscribed to this topic. + pub(crate) fn dec_topic_peers(&mut self, topic: &TopicHash) { + if self.register_topic(topic).is_ok() { + self.topic_peers_count.get_or_create(topic).dec(); } } - /* Mesh related methods */ + // Mesh related methods /// Registers the subscription to a topic if the configured limits allow it. /// Sets the registered number of peers in the mesh to 0. @@ -452,6 +537,27 @@ impl Metrics { } } + /// Register dropping a Publish message over a topic. + pub(crate) fn publish_msg_dropped(&mut self, topic: &TopicHash) { + if self.register_topic(topic).is_ok() { + self.publish_messages_dropped.get_or_create(topic).inc(); + } + } + + /// Register dropping a Forward message over a topic. + pub(crate) fn forward_msg_dropped(&mut self, topic: &TopicHash) { + if self.register_topic(topic).is_ok() { + self.forward_messages_dropped.get_or_create(topic).inc(); + } + } + + /// Register dropping a message that timedout over a topic. + pub(crate) fn timeout_msg_dropped(&mut self, topic: &TopicHash) { + if self.register_topic(topic).is_ok() { + self.timedout_messages_dropped.get_or_create(topic).inc(); + } + } + /// Register that a message was received (and was not a duplicate). pub(crate) fn msg_recvd(&mut self, topic: &TopicHash) { if self.register_topic(topic).is_ok() { @@ -497,11 +603,27 @@ impl Metrics { } } + /// Register receiving an IDONTWANT msg for this topic. + pub(crate) fn register_idontwant(&mut self, msgs: usize) { + self.idontwant_msgs.inc(); + self.idontwant_msgs_ids.inc_by(msgs as u64); + } + /// Observes a heartbeat duration. pub(crate) fn observe_heartbeat_duration(&mut self, millis: u64) { self.heartbeat_duration.observe(millis as f64); } + /// Observes a priority queue size. + pub(crate) fn observe_priority_queue_size(&mut self, len: usize) { + self.priority_queue_size.observe(len as f64); + } + + /// Observes a non-priority queue size. + pub(crate) fn observe_non_priority_queue_size(&mut self, len: usize) { + self.non_priority_queue_size.observe(len as f64); + } + /// Observe a score of a mesh peer. pub(crate) fn observe_mesh_peers_score(&mut self, topic: &TopicHash, score: f64) { if self.register_topic(topic).is_ok() { diff --git a/protocols/gossipsub/src/peer_score.rs b/protocols/gossipsub/src/peer_score.rs index b1ea9bfae95..33573ebeacc 100644 --- a/protocols/gossipsub/src/peer_score.rs +++ b/protocols/gossipsub/src/peer_score.rs @@ -18,25 +18,31 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! //! Manages and stores the Scoring logic of a particular peer on the gossipsub behaviour. -use crate::metrics::{Metrics, Penalty}; -use crate::time_cache::TimeCache; -use crate::{MessageId, TopicHash}; -use instant::Instant; +use std::{ + collections::{hash_map, HashMap, HashSet}, + net::IpAddr, + time::Duration, +}; + use libp2p_identity::PeerId; -use std::collections::{hash_map, HashMap, HashSet}; -use std::net::IpAddr; -use std::time::Duration; +use web_time::Instant; + +use crate::{ + metrics::{Metrics, Penalty}, + time_cache::TimeCache, + MessageId, TopicHash, +}; mod params; -use crate::ValidationError; pub use params::{ score_parameter_decay, score_parameter_decay_with_base, PeerScoreParams, PeerScoreThresholds, TopicScoreParams, }; +use crate::ValidationError; + #[cfg(test)] mod tests; @@ -44,14 +50,15 @@ mod tests; const TIME_CACHE_DURATION: u64 = 120; pub(crate) struct PeerScore { - params: PeerScoreParams, /// The score parameters. + pub(crate) params: PeerScoreParams, + /// The stats per PeerId. peer_stats: HashMap, /// Tracking peers per IP. peer_ips: HashMap>, /// Message delivery tracking. This is a time-cache of [`DeliveryRecord`]s. deliveries: TimeCache, - /// callback for monitoring message delivery times + /// Callback for monitoring message delivery times. message_delivery_time_callback: Option, } @@ -67,6 +74,8 @@ struct PeerStats { behaviour_penalty: f64, /// Application specific score. Can be manipulated by calling PeerScore::set_application_score application_score: f64, + /// Scoring based on how whether this peer consumes messages fast enough or not. + slow_peer_penalty: f64, } enum ConnectionStatus { @@ -87,19 +96,21 @@ impl Default for PeerStats { known_ips: HashSet::new(), behaviour_penalty: 0f64, application_score: 0f64, + slow_peer_penalty: 0f64, } } } impl PeerStats { - /// Returns a mutable reference to topic stats if they exist, otherwise if the supplied parameters score the - /// topic, inserts the default stats and returns a reference to those. If neither apply, returns None. + /// Returns a mutable reference to topic stats if they exist, otherwise if the supplied + /// parameters score the topic, inserts the default stats and returns a reference to those. + /// If neither apply, returns None. pub(crate) fn stats_or_default_mut( &mut self, topic_hash: TopicHash, params: &PeerScoreParams, ) -> Option<&mut TopicStats> { - if params.topics.get(&topic_hash).is_some() { + if params.topics.contains_key(&topic_hash) { Some(self.topics.entry(topic_hash).or_default()) } else { self.topics.get_mut(&topic_hash) @@ -281,12 +292,14 @@ impl PeerScore { } // P3b: - // NOTE: the weight of P3b is negative (validated in TopicScoreParams.validate), so this detracts. + // NOTE: the weight of P3b is negative (validated in TopicScoreParams.validate), so + // this detracts. let p3b = topic_stats.mesh_failure_penalty; topic_score += p3b * topic_params.mesh_failure_penalty_weight; // P4: invalid messages - // NOTE: the weight of P4 is negative (validated in TopicScoreParams.validate), so this detracts. + // NOTE: the weight of P4 is negative (validated in TopicScoreParams.validate), so + // this detracts. let p4 = topic_stats.invalid_message_deliveries * topic_stats.invalid_message_deliveries; topic_score += p4 * topic_params.invalid_message_deliveries_weight; @@ -307,7 +320,7 @@ impl PeerScore { // P6: IP collocation factor for ip in peer_stats.known_ips.iter() { - if self.params.ip_colocation_factor_whitelist.get(ip).is_some() { + if self.params.ip_colocation_factor_whitelist.contains(ip) { continue; } @@ -333,12 +346,19 @@ impl PeerScore { } } - // P7: behavioural pattern penalty + // P7: behavioural pattern penalty. if peer_stats.behaviour_penalty > self.params.behaviour_penalty_threshold { let excess = peer_stats.behaviour_penalty - self.params.behaviour_penalty_threshold; let p7 = excess * excess; score += p7 * self.params.behaviour_penalty_weight; } + + // Slow peer weighting. + if peer_stats.slow_peer_penalty > self.params.slow_peer_threshold { + let excess = peer_stats.slow_peer_penalty - self.params.slow_peer_threshold; + score += excess * self.params.slow_peer_weight; + } + score } @@ -380,8 +400,8 @@ impl PeerScore { } // we don't decay retained scores, as the peer is not active. - // this way the peer cannot reset a negative score by simply disconnecting and reconnecting, - // unless the retention period has elapsed. + // this way the peer cannot reset a negative score by simply disconnecting and + // reconnecting, unless the retention period has elapsed. // similarly, a well behaved peer does not lose its score by getting disconnected. return true; } @@ -428,6 +448,13 @@ impl PeerScore { if peer_stats.behaviour_penalty < params_ref.decay_to_zero { peer_stats.behaviour_penalty = 0.0; } + + // decay slow peer score + peer_stats.slow_peer_penalty *= params_ref.slow_peer_decay; + if peer_stats.slow_peer_penalty < params_ref.decay_to_zero { + peer_stats.slow_peer_penalty = 0.0; + } + true }); } @@ -455,6 +482,14 @@ impl PeerScore { self.peer_ips.entry(ip).or_default().insert(*peer_id); } + /// Indicate that a peer has been too slow to consume a message. + pub(crate) fn failed_message_slow_peer(&mut self, peer_id: &PeerId) { + if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { + peer_stats.slow_peer_penalty += 1.0; + tracing::debug!(peer=%peer_id, %peer_stats.slow_peer_penalty, "[Penalty] Expired message penalty."); + } + } + /// Removes an ip from a peer pub(crate) fn remove_ip(&mut self, peer_id: &PeerId, ip: &IpAddr) { if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { @@ -612,7 +647,8 @@ impl PeerScore { } } - /// Similar to `reject_message` except does not require the message id or reason for an invalid message. + /// Similar to `reject_message` except does not require the message id or reason for an invalid + /// message. pub(crate) fn reject_invalid_message(&mut self, from: &PeerId, topic_hash: &TopicHash) { tracing::debug!( peer=%from, @@ -653,8 +689,8 @@ impl PeerScore { } if let RejectReason::ValidationIgnored = reason { - // we were explicitly instructed by the validator to ignore the message but not penalize - // the peer + // we were explicitly instructed by the validator to ignore the message but not + // penalize the peer record.status = DeliveryStatus::Ignored; record.peers.clear(); return; @@ -680,7 +716,7 @@ impl PeerScore { ) { let record = self.deliveries.entry(msg_id.clone()).or_default(); - if record.peers.get(from).is_some() { + if record.peers.contains(from) { // we have already seen this duplicate! return; } @@ -856,13 +892,14 @@ impl PeerScore { .get(topic_hash) .expect("Topic must exist if there are known topic_stats"); - // check against the mesh delivery window -- if the validated time is passed as 0, then - // the message was received before we finished validation and thus falls within the mesh + // check against the mesh delivery window -- if the validated time is passed as + // 0, then the message was received before we finished + // validation and thus falls within the mesh // delivery window. let mut falls_in_mesh_deliver_window = true; if let Some(validated_time) = validated_time { if let Some(now) = &now { - //should always be true + // should always be true let window_time = validated_time .checked_add(topic_params.mesh_message_deliveries_window) .unwrap_or(*now); diff --git a/protocols/gossipsub/src/peer_score/params.rs b/protocols/gossipsub/src/peer_score/params.rs index 35bea0e4353..cc48df8f61b 100644 --- a/protocols/gossipsub/src/peer_score/params.rs +++ b/protocols/gossipsub/src/peer_score/params.rs @@ -18,10 +18,13 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::{ + collections::{HashMap, HashSet}, + net::IpAddr, + time::Duration, +}; + use crate::TopicHash; -use std::collections::{HashMap, HashSet}; -use std::net::IpAddr; -use std::time::Duration; /// The default number of seconds for a decay interval. const DEFAULT_DECAY_INTERVAL: u64 = 1; @@ -117,12 +120,13 @@ pub struct PeerScoreParams { /// P6: IP-colocation factor. /// The parameter has an associated counter which counts the number of peers with the same IP. - /// If the number of peers in the same IP exceeds `ip_colocation_factor_threshold, then the value - /// is the square of the difference, ie `(peers_in_same_ip - ip_colocation_threshold)^2`. - /// If the number of peers in the same IP is less than the threshold, then the value is 0. - /// The weight of the parameter MUST be negative, unless you want to disable for testing. - /// Note: In order to simulate many IPs in a manageable manner when testing, you can set the weight to 0 - /// thus disabling the IP colocation penalty. + /// If the number of peers in the same IP exceeds `ip_colocation_factor_threshold, then the + /// value is the square of the difference, ie `(peers_in_same_ip - + /// ip_colocation_threshold)^2`. If the number of peers in the same IP is less than the + /// threshold, then the value is 0. The weight of the parameter MUST be negative, unless + /// you want to disable for testing. Note: In order to simulate many IPs in a manageable + /// manner when testing, you can set the weight to 0 thus disabling the IP + /// colocation penalty. pub ip_colocation_factor_weight: f64, pub ip_colocation_factor_threshold: f64, pub ip_colocation_factor_whitelist: HashSet, @@ -148,6 +152,13 @@ pub struct PeerScoreParams { /// Time to remember counters for a disconnected peer. pub retain_score: Duration, + + /// Slow peer penalty conditions, + /// by default `slow_peer_weight` is 50 times lower than `behaviour_penalty_weight` + /// i.e. 50 slow peer penalties match 1 behaviour penalty. + pub slow_peer_weight: f64, + pub slow_peer_threshold: f64, + pub slow_peer_decay: f64, } impl Default for PeerScoreParams { @@ -165,6 +176,9 @@ impl Default for PeerScoreParams { decay_interval: Duration::from_secs(DEFAULT_DECAY_INTERVAL), decay_to_zero: DEFAULT_DECAY_TO_ZERO, retain_score: Duration::from_secs(3600), + slow_peer_weight: -0.2, + slow_peer_threshold: 0.0, + slow_peer_decay: 0.2, } } } @@ -229,16 +243,16 @@ pub struct TopicScoreParams { /// P1: time in the mesh /// This is the time the peer has been grafted in the mesh. - /// The value of of the parameter is the `time/time_in_mesh_quantum`, capped by `time_in_mesh_cap` - /// The weight of the parameter must be positive (or zero to disable). + /// The value of the parameter is the `time/time_in_mesh_quantum`, capped by + /// `time_in_mesh_cap` The weight of the parameter must be positive (or zero to disable). pub time_in_mesh_weight: f64, pub time_in_mesh_quantum: Duration, pub time_in_mesh_cap: f64, /// P2: first message deliveries /// This is the number of message deliveries in the topic. - /// The value of the parameter is a counter, decaying with `first_message_deliveries_decay`, and capped - /// by `first_message_deliveries_cap`. + /// The value of the parameter is a counter, decaying with `first_message_deliveries_decay`, + /// and capped by `first_message_deliveries_cap`. /// The weight of the parameter MUST be positive (or zero to disable). pub first_message_deliveries_weight: f64, pub first_message_deliveries_decay: f64, @@ -254,8 +268,8 @@ pub struct TopicScoreParams { /// before we have forwarded it to them. /// The parameter has an associated counter, decaying with `mesh_message_deliveries_decay`. /// If the counter exceeds the threshold, its value is 0. - /// If the counter is below the `mesh_message_deliveries_threshold`, the value is the square of - /// the deficit, ie (`message_deliveries_threshold - counter)^2` + /// If the counter is below the `mesh_message_deliveries_threshold`, the value is the square + /// of the deficit, ie (`message_deliveries_threshold - counter)^2` /// The penalty is only activated after `mesh_message_deliveries_activation` time in the mesh. /// The weight of the parameter MUST be negative (or zero to disable). pub mesh_message_deliveries_weight: f64, diff --git a/protocols/gossipsub/src/peer_score/tests.rs b/protocols/gossipsub/src/peer_score/tests.rs index 064e277eed7..9e20cea2dde 100644 --- a/protocols/gossipsub/src/peer_score/tests.rs +++ b/protocols/gossipsub/src/peer_score/tests.rs @@ -20,9 +20,7 @@ /// A collection of unit tests mostly ported from the go implementation. use super::*; - -use crate::types::RawMessage; -use crate::{IdentTopic as Topic, Message}; +use crate::{types::RawMessage, IdentTopic as Topic, Message}; // estimates a value within variance fn within_variance(value: f64, expected: f64, variance: f64) -> bool { @@ -447,7 +445,8 @@ fn test_score_mesh_message_deliveries_decay() { } let score_a = peer_score.score(&peer_id_a); - // the penalty is the difference between the threshold and the (decayed) mesh deliveries, squared. + // the penalty is the difference between the threshold and the (decayed) + // mesh deliveries, squared. let deficit = topic_params.mesh_message_deliveries_threshold - decayed_delivery_count; let penalty = deficit * deficit; let expected = diff --git a/protocols/gossipsub/src/protocol.rs b/protocols/gossipsub/src/protocol.rs index e9600a4d8d8..7ee6d5c8245 100644 --- a/protocols/gossipsub/src/protocol.rs +++ b/protocols/gossipsub/src/protocol.rs @@ -18,28 +18,36 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::config::ValidationMode; -use crate::handler::HandlerEvent; -use crate::rpc_proto::proto; -use crate::topic::TopicHash; -use crate::types::{ - ControlAction, MessageId, PeerInfo, PeerKind, RawMessage, Rpc, Subscription, SubscriptionAction, -}; -use crate::ValidationError; +use std::{convert::Infallible, pin::Pin}; + use asynchronous_codec::{Decoder, Encoder, Framed}; use byteorder::{BigEndian, ByteOrder}; use bytes::BytesMut; -use futures::future; use futures::prelude::*; use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use libp2p_identity::{PeerId, PublicKey}; use libp2p_swarm::StreamProtocol; use quick_protobuf::Writer; -use std::pin::Pin; -use void::Void; + +use crate::{ + config::ValidationMode, + handler::HandlerEvent, + rpc_proto::proto, + topic::TopicHash, + types::{ + ControlAction, Graft, IDontWant, IHave, IWant, MessageId, PeerInfo, PeerKind, Prune, + RawMessage, Rpc, Subscription, SubscriptionAction, + }, + ValidationError, +}; pub(crate) const SIGNING_PREFIX: &[u8] = b"libp2p-pubsub:"; +pub(crate) const GOSSIPSUB_1_2_0_PROTOCOL: ProtocolId = ProtocolId { + protocol: StreamProtocol::new("/meshsub/1.2.0"), + kind: PeerKind::Gossipsubv1_2, +}; + pub(crate) const GOSSIPSUB_1_1_0_PROTOCOL: ProtocolId = ProtocolId { protocol: StreamProtocol::new("/meshsub/1.1.0"), kind: PeerKind::Gossipsubv1_1, @@ -69,7 +77,11 @@ impl Default for ProtocolConfig { Self { max_transmit_size: 65536, validation_mode: ValidationMode::Strict, - protocol_ids: vec![GOSSIPSUB_1_1_0_PROTOCOL, GOSSIPSUB_1_0_0_PROTOCOL], + protocol_ids: vec![ + GOSSIPSUB_1_2_0_PROTOCOL, + GOSSIPSUB_1_1_0_PROTOCOL, + GOSSIPSUB_1_0_0_PROTOCOL, + ], } } } @@ -103,7 +115,7 @@ where TSocket: AsyncRead + AsyncWrite + Unpin + Send + 'static, { type Output = (Framed, PeerKind); - type Error = Void; + type Error = Infallible; type Future = Pin> + Send>>; fn upgrade_inbound(self, socket: TSocket, protocol_id: Self::Info) -> Self::Future { @@ -122,7 +134,7 @@ where TSocket: AsyncWrite + AsyncRead + Unpin + Send + 'static, { type Output = (Framed, PeerKind); - type Error = Void; + type Error = Infallible; type Future = Pin> + Send>>; fn upgrade_outbound(self, socket: TSocket, protocol_id: Self::Info) -> Self::Future { @@ -136,7 +148,7 @@ where } } -/* Gossip codec for the framing */ +// Gossip codec for the framing pub struct GossipsubCodec { /// Determines the level of validation performed on incoming messages. @@ -413,33 +425,39 @@ impl Decoder for GossipsubCodec { let ihave_msgs: Vec = rpc_control .ihave .into_iter() - .map(|ihave| ControlAction::IHave { - topic_hash: TopicHash::from_raw(ihave.topic_id.unwrap_or_default()), - message_ids: ihave - .message_ids - .into_iter() - .map(MessageId::from) - .collect::>(), + .map(|ihave| { + ControlAction::IHave(IHave { + topic_hash: TopicHash::from_raw(ihave.topic_id.unwrap_or_default()), + message_ids: ihave + .message_ids + .into_iter() + .map(MessageId::from) + .collect::>(), + }) }) .collect(); let iwant_msgs: Vec = rpc_control .iwant .into_iter() - .map(|iwant| ControlAction::IWant { - message_ids: iwant - .message_ids - .into_iter() - .map(MessageId::from) - .collect::>(), + .map(|iwant| { + ControlAction::IWant(IWant { + message_ids: iwant + .message_ids + .into_iter() + .map(MessageId::from) + .collect::>(), + }) }) .collect(); let graft_msgs: Vec = rpc_control .graft .into_iter() - .map(|graft| ControlAction::Graft { - topic_hash: TopicHash::from_raw(graft.topic_id.unwrap_or_default()), + .map(|graft| { + ControlAction::Graft(Graft { + topic_hash: TopicHash::from_raw(graft.topic_id.unwrap_or_default()), + }) }) .collect(); @@ -463,17 +481,32 @@ impl Decoder for GossipsubCodec { .collect::>(); let topic_hash = TopicHash::from_raw(prune.topic_id.unwrap_or_default()); - prune_msgs.push(ControlAction::Prune { + prune_msgs.push(ControlAction::Prune(Prune { topic_hash, peers, backoff: prune.backoff, - }); + })); } + let idontwant_msgs: Vec = rpc_control + .idontwant + .into_iter() + .map(|idontwant| { + ControlAction::IDontWant(IDontWant { + message_ids: idontwant + .message_ids + .into_iter() + .map(MessageId::from) + .collect::>(), + }) + }) + .collect(); + control_msgs.extend(ihave_msgs); control_msgs.extend(iwant_msgs); control_msgs.extend(graft_msgs); control_msgs.extend(prune_msgs); + control_msgs.extend(idontwant_msgs); } Ok(Some(HandlerEvent::Message { @@ -500,13 +533,14 @@ impl Decoder for GossipsubCodec { #[cfg(test)] mod tests { - use super::*; - use crate::config::Config; - use crate::{Behaviour, ConfigBuilder}; - use crate::{IdentTopic as Topic, Version}; use libp2p_identity::Keypair; use quickcheck::*; + use super::*; + use crate::{ + config::Config, Behaviour, ConfigBuilder, IdentTopic as Topic, MessageAuthenticity, Version, + }; + #[derive(Clone, Debug)] struct Message(RawMessage); @@ -517,8 +551,9 @@ mod tests { // generate an arbitrary GossipsubMessage using the behaviour signing functionality let config = Config::default(); let mut gs: Behaviour = - Behaviour::new(crate::MessageAuthenticity::Signed(keypair.0), config).unwrap(); - let data = (0..g.gen_range(10..10024u32)) + Behaviour::new(MessageAuthenticity::Signed(keypair.0), config).unwrap(); + let mut data_g = quickcheck::Gen::new(10024); + let data = (0..u8::arbitrary(&mut data_g)) .map(|_| u8::arbitrary(g)) .collect::>(); let topic_id = TopicId::arbitrary(g).0; @@ -531,7 +566,8 @@ mod tests { impl Arbitrary for TopicId { fn arbitrary(g: &mut Gen) -> Self { - let topic_string: String = (0..g.gen_range(20..1024u32)) + let mut data_g = quickcheck::Gen::new(1024); + let topic_string: String = (0..u8::arbitrary(&mut data_g)) .map(|_| char::arbitrary(g)) .collect::(); TopicId(Topic::new(topic_string).into()) @@ -542,20 +578,6 @@ mod tests { struct TestKeypair(Keypair); impl Arbitrary for TestKeypair { - #[cfg(feature = "rsa")] - fn arbitrary(g: &mut Gen) -> Self { - let keypair = if bool::arbitrary(g) { - // Small enough to be inlined. - Keypair::generate_ed25519() - } else { - // Too large to be inlined. - let mut rsa_key = hex::decode("308204bd020100300d06092a864886f70d0101010500048204a7308204a30201000282010100ef930f41a71288b643c1cbecbf5f72ab53992249e2b00835bf07390b6745419f3848cbcc5b030faa127bc88cdcda1c1d6f3ff699f0524c15ab9d2c9d8015f5d4bd09881069aad4e9f91b8b0d2964d215cdbbae83ddd31a7622a8228acee07079f6e501aea95508fa26c6122816ef7b00ac526d422bd12aed347c37fff6c1c307f3ba57bb28a7f28609e0bdcc839da4eedca39f5d2fa855ba4b0f9c763e9764937db929a1839054642175312a3de2d3405c9d27bdf6505ef471ce85c5e015eee85bf7874b3d512f715de58d0794fd8afe021c197fbd385bb88a930342fac8da31c27166e2edab00fa55dc1c3814448ba38363077f4e8fe2bdea1c081f85f1aa6f02030100010282010028ff427a1aac1a470e7b4879601a6656193d3857ea79f33db74df61e14730e92bf9ffd78200efb0c40937c3356cbe049cd32e5f15be5c96d5febcaa9bd3484d7fded76a25062d282a3856a1b3b7d2c525cdd8434beae147628e21adf241dd64198d5819f310d033743915ba40ea0b6acdbd0533022ad6daa1ff42de51885f9e8bab2306c6ef1181902d1cd7709006eba1ab0587842b724e0519f295c24f6d848907f772ae9a0953fc931f4af16a07df450fb8bfa94572562437056613647818c238a6ff3f606cffa0533e4b8755da33418dfbc64a85110b1a036623c947400a536bb8df65e5ebe46f2dfd0cfc86e7aeeddd7574c253e8fbf755562b3669525d902818100f9fff30c6677b78dd31ec7a634361438457e80be7a7faf390903067ea8355faa78a1204a82b6e99cb7d9058d23c1ecf6cfe4a900137a00cecc0113fd68c5931602980267ea9a95d182d48ba0a6b4d5dd32fdac685cb2e5d8b42509b2eb59c9579ea6a67ccc7547427e2bd1fb1f23b0ccb4dd6ba7d206c8dd93253d70a451701302818100f5530dfef678d73ce6a401ae47043af10a2e3f224c71ae933035ecd68ccbc4df52d72bc6ca2b17e8faf3e548b483a2506c0369ab80df3b137b54d53fac98f95547c2bc245b416e650ce617e0d29db36066f1335a9ba02ad3e0edf9dc3d58fd835835042663edebce81803972696c789012847cb1f854ab2ac0a1bd3867ac7fb502818029c53010d456105f2bf52a9a8482bca2224a5eac74bf3cc1a4d5d291fafcdffd15a6a6448cce8efdd661f6617ca5fc37c8c885cc3374e109ac6049bcbf72b37eabf44602a2da2d4a1237fd145c863e6d75059976de762d9d258c42b0984e2a2befa01c95217c3ee9c736ff209c355466ff99375194eff943bc402ea1d172a1ed02818027175bf493bbbfb8719c12b47d967bf9eac061c90a5b5711172e9095c38bb8cc493c063abffe4bea110b0a2f22ac9311b3947ba31b7ef6bfecf8209eebd6d86c316a2366bbafda7279b2b47d5bb24b6202254f249205dcad347b574433f6593733b806f84316276c1990a016ce1bbdbe5f650325acc7791aefe515ecc60063bd02818100b6a2077f4adcf15a17092d9c4a346d6022ac48f3861b73cf714f84c440a07419a7ce75a73b9cbff4597c53c128bf81e87b272d70428a272d99f90cd9b9ea1033298e108f919c6477400145a102df3fb5601ffc4588203cf710002517bfa24e6ad32f4d09c6b1a995fa28a3104131bedd9072f3b4fb4a5c2056232643d310453f").unwrap(); - Keypair::rsa_from_pkcs8(&mut rsa_key).unwrap() - }; - TestKeypair(keypair) - } - - #[cfg(not(feature = "rsa"))] fn arbitrary(_g: &mut Gen) -> Self { // Small enough to be inlined. TestKeypair(Keypair::generate_ed25519()) diff --git a/protocols/gossipsub/src/rpc.rs b/protocols/gossipsub/src/rpc.rs new file mode 100644 index 00000000000..41b338267e9 --- /dev/null +++ b/protocols/gossipsub/src/rpc.rs @@ -0,0 +1,193 @@ +// Copyright 2020 Sigma Prime Pty Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use std::{ + future::Future, + pin::Pin, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, + task::{Context, Poll}, +}; + +use futures::{stream::Peekable, Stream, StreamExt}; + +use crate::types::RpcOut; + +/// `RpcOut` sender that is priority aware. +#[derive(Debug)] +pub(crate) struct Sender { + /// Capacity of the priority channel for `Publish` messages. + priority_cap: usize, + len: Arc, + pub(crate) priority_sender: async_channel::Sender, + pub(crate) non_priority_sender: async_channel::Sender, + priority_receiver: async_channel::Receiver, + non_priority_receiver: async_channel::Receiver, +} + +impl Sender { + /// Create a RpcSender. + pub(crate) fn new(cap: usize) -> Sender { + // We intentionally do not bound the channel, as we still need to send control messages + // such as `GRAFT`, `PRUNE`, `SUBSCRIBE`, and `UNSUBSCRIBE`. + // That's also why we define `cap` and divide it by two, + // to ensure there is capacity for both priority and non_priority messages. + let (priority_sender, priority_receiver) = async_channel::unbounded(); + let (non_priority_sender, non_priority_receiver) = async_channel::bounded(cap / 2); + let len = Arc::new(AtomicUsize::new(0)); + Sender { + priority_cap: cap / 2, + len, + priority_sender, + non_priority_sender, + priority_receiver, + non_priority_receiver, + } + } + + /// Create a new Receiver to the sender. + pub(crate) fn new_receiver(&self) -> Receiver { + Receiver { + priority_queue_len: self.len.clone(), + priority: Box::pin(self.priority_receiver.clone().peekable()), + non_priority: Box::pin(self.non_priority_receiver.clone().peekable()), + } + } + + #[allow(clippy::result_large_err)] + pub(crate) fn send_message(&self, rpc: RpcOut) -> Result<(), RpcOut> { + if let RpcOut::Publish { .. } = rpc { + // Update number of publish message in queue. + let len = self.len.load(Ordering::Relaxed); + if len >= self.priority_cap { + return Err(rpc); + } + self.len.store(len + 1, Ordering::Relaxed); + } + let sender = match rpc { + RpcOut::Publish { .. } + | RpcOut::Graft(_) + | RpcOut::Prune(_) + | RpcOut::Subscribe(_) + | RpcOut::Unsubscribe(_) => &self.priority_sender, + RpcOut::Forward { .. } | RpcOut::IHave(_) | RpcOut::IWant(_) | RpcOut::IDontWant(_) => { + &self.non_priority_sender + } + }; + sender.try_send(rpc).map_err(|err| err.into_inner()) + } + + /// Returns the current size of the priority queue. + pub(crate) fn priority_queue_len(&self) -> usize { + self.len.load(Ordering::Relaxed) + } + + /// Returns the current size of the non-priority queue. + pub(crate) fn non_priority_queue_len(&self) -> usize { + self.non_priority_sender.len() + } +} + +/// `RpcOut` sender that is priority aware. +#[derive(Debug)] +pub struct Receiver { + /// The maximum length of the priority queue. + pub(crate) priority_queue_len: Arc, + /// The priority queue receiver. + pub(crate) priority: Pin>>>, + /// The non priority queue receiver. + pub(crate) non_priority: Pin>>>, +} + +impl Receiver { + // Peek the next message in the queues and return it if its timeout has elapsed. + // Returns `None` if there aren't any more messages on the stream or none is stale. + pub(crate) fn poll_stale(&mut self, cx: &mut Context<'_>) -> Poll> { + // Peek priority queue. + let priority = match self.priority.as_mut().poll_peek_mut(cx) { + Poll::Ready(Some(RpcOut::Publish { + message: _, + ref mut timeout, + })) => { + if Pin::new(timeout).poll(cx).is_ready() { + // Return the message. + let dropped = futures::ready!(self.priority.poll_next_unpin(cx)) + .expect("There should be a message"); + return Poll::Ready(Some(dropped)); + } + Poll::Ready(None) + } + poll => poll, + }; + + let non_priority = match self.non_priority.as_mut().poll_peek_mut(cx) { + Poll::Ready(Some(RpcOut::Forward { + message: _, + ref mut timeout, + })) => { + if Pin::new(timeout).poll(cx).is_ready() { + // Return the message. + let dropped = futures::ready!(self.non_priority.poll_next_unpin(cx)) + .expect("There should be a message"); + return Poll::Ready(Some(dropped)); + } + Poll::Ready(None) + } + poll => poll, + }; + + match (priority, non_priority) { + (Poll::Ready(None), Poll::Ready(None)) => Poll::Ready(None), + _ => Poll::Pending, + } + } + + /// Poll queues and return true if both are empty. + pub(crate) fn poll_is_empty(&mut self, cx: &mut Context<'_>) -> bool { + matches!( + ( + self.priority.as_mut().poll_peek(cx), + self.non_priority.as_mut().poll_peek(cx), + ), + (Poll::Ready(None), Poll::Ready(None)) + ) + } +} + +impl Stream for Receiver { + type Item = RpcOut; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + // The priority queue is first polled. + if let Poll::Ready(rpc) = Pin::new(&mut self.priority).poll_next(cx) { + if let Some(RpcOut::Publish { .. }) = rpc { + self.priority_queue_len.fetch_sub(1, Ordering::Relaxed); + } + return Poll::Ready(rpc); + } + // Then we poll the non priority. + Pin::new(&mut self.non_priority).poll_next(cx) + } +} diff --git a/protocols/gossipsub/src/rpc_proto.rs b/protocols/gossipsub/src/rpc_proto.rs index 94c7aafbc3e..2f6832a01a1 100644 --- a/protocols/gossipsub/src/rpc_proto.rs +++ b/protocols/gossipsub/src/rpc_proto.rs @@ -26,12 +26,12 @@ pub(crate) mod proto { #[cfg(test)] mod test { - use crate::rpc_proto::proto::compat; - use crate::IdentTopic as Topic; use libp2p_identity::PeerId; use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; use rand::Rng; + use crate::{rpc_proto::proto::compat, IdentTopic as Topic}; + #[test] fn test_multi_topic_message_compatibility() { let topic1 = Topic::new("t1").hash(); diff --git a/protocols/gossipsub/src/subscription_filter.rs b/protocols/gossipsub/src/subscription_filter.rs index 09c323d7904..c051b6c333b 100644 --- a/protocols/gossipsub/src/subscription_filter.rs +++ b/protocols/gossipsub/src/subscription_filter.rs @@ -18,10 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::types::Subscription; -use crate::TopicHash; use std::collections::{BTreeSet, HashMap, HashSet}; +use crate::{types::Subscription, TopicHash}; + pub trait TopicSubscriptionFilter { /// Returns true iff the topic is of interest and we can subscribe to it. fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool; @@ -82,7 +82,7 @@ pub trait TopicSubscriptionFilter { } } -//some useful implementers +// some useful implementers /// Allows all subscriptions #[derive(Default, Clone)] @@ -199,7 +199,7 @@ where } } -///A subscription filter that filters topics based on a regular expression. +/// A subscription filter that filters topics based on a regular expression. pub struct RegexSubscriptionFilter(pub regex::Regex); impl TopicSubscriptionFilter for RegexSubscriptionFilter { @@ -212,7 +212,6 @@ impl TopicSubscriptionFilter for RegexSubscriptionFilter { mod test { use super::*; use crate::types::SubscriptionAction::*; - use std::iter::FromIterator; #[test] fn test_filter_incoming_allow_all_with_duplicates() { diff --git a/protocols/gossipsub/src/time_cache.rs b/protocols/gossipsub/src/time_cache.rs index 89fd4afee09..ace02606e88 100644 --- a/protocols/gossipsub/src/time_cache.rs +++ b/protocols/gossipsub/src/time_cache.rs @@ -20,14 +20,19 @@ //! This implements a time-based LRU cache for checking gossipsub message duplicates. -use fnv::FnvHashMap; -use instant::Instant; -use std::collections::hash_map::{ - self, - Entry::{Occupied, Vacant}, +use std::{ + collections::{ + hash_map::{ + self, + Entry::{Occupied, Vacant}, + }, + VecDeque, + }, + time::Duration, }; -use std::collections::VecDeque; -use std::time::Duration; + +use fnv::FnvHashMap; +use web_time::Instant; struct ExpiringElement { /// The element that expires @@ -206,7 +211,7 @@ mod test { cache.insert("t"); assert!(!cache.insert("t")); cache.insert("e"); - //assert!(!cache.insert("t")); + // assert!(!cache.insert("t")); assert!(!cache.insert("e")); // sleep until cache expiry std::thread::sleep(Duration::from_millis(101)); diff --git a/protocols/gossipsub/src/topic.rs b/protocols/gossipsub/src/topic.rs index a73496b53f2..4793c23a8e1 100644 --- a/protocols/gossipsub/src/topic.rs +++ b/protocols/gossipsub/src/topic.rs @@ -18,12 +18,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::rpc_proto::proto; +use std::fmt; + use base64::prelude::*; use prometheus_client::encoding::EncodeLabelSet; use quick_protobuf::Writer; use sha2::{Digest, Sha256}; -use std::fmt; + +use crate::rpc_proto::proto; /// A generic trait that can be extended for various hashing types for a topic. pub trait Hasher { diff --git a/protocols/gossipsub/src/transform.rs b/protocols/gossipsub/src/transform.rs index 6f57d9fc46b..4831f9781b0 100644 --- a/protocols/gossipsub/src/transform.rs +++ b/protocols/gossipsub/src/transform.rs @@ -28,6 +28,7 @@ use crate::{Message, RawMessage, TopicHash}; /// A general trait of transforming a [`RawMessage`] into a [`Message`]. The +/// /// [`RawMessage`] is obtained from the wire and the [`Message`] is used to /// calculate the [`crate::MessageId`] of the message and is what is sent to the application. /// diff --git a/protocols/gossipsub/src/types.rs b/protocols/gossipsub/src/types.rs index d1b92ff0ba8..6681eca1d93 100644 --- a/protocols/gossipsub/src/types.rs +++ b/protocols/gossipsub/src/types.rs @@ -19,17 +19,47 @@ // DEALINGS IN THE SOFTWARE. //! A collection of types using the Gossipsub system. -use crate::TopicHash; +use std::{collections::BTreeSet, fmt, fmt::Debug}; + +use futures_timer::Delay; +use hashlink::LinkedHashMap; use libp2p_identity::PeerId; use libp2p_swarm::ConnectionId; use prometheus_client::encoding::EncodeLabelValue; use quick_protobuf::MessageWrite; -use std::fmt; -use std::fmt::Debug; - -use crate::rpc_proto::proto; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use web_time::Instant; + +use crate::{rpc::Sender, rpc_proto::proto, TopicHash}; + +/// Messages that have expired while attempting to be sent to a peer. +#[derive(Clone, Debug, Default)] +pub struct FailedMessages { + /// The number of publish messages that failed to be published in a heartbeat. + pub publish: usize, + /// The number of forward messages that failed to be published in a heartbeat. + pub forward: usize, + /// The number of messages that were failed to be sent to the priority queue as it was full. + pub priority: usize, + /// The number of messages that were failed to be sent to the non-priority queue as it was + /// full. + pub non_priority: usize, + /// The number of messages that timed out and could not be sent. + pub timeout: usize, +} + +impl FailedMessages { + /// The total number of messages that failed due to the queue being full. + pub fn total_queue_full(&self) -> usize { + self.priority + self.non_priority + } + + /// The total failed messages in a heartbeat. + pub fn total(&self) -> usize { + self.priority + self.non_priority + } +} #[derive(Debug)] /// Validation kinds from the application for received messages. @@ -71,17 +101,25 @@ impl std::fmt::Debug for MessageId { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug)] pub(crate) struct PeerConnections { /// The kind of protocol the peer supports. pub(crate) kind: PeerKind, /// Its current connections. pub(crate) connections: Vec, + /// Subscribed topics. + pub(crate) topics: BTreeSet, + /// The rpc sender to the connection handler(s). + pub(crate) sender: Sender, + /// Don't send messages. + pub(crate) dont_send: LinkedHashMap, } /// Describes the types of peers that can exist in the gossipsub context. -#[derive(Debug, Clone, PartialEq, Hash, EncodeLabelValue, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Hash, EncodeLabelValue, Eq)] pub enum PeerKind { + /// A gossipsub 1.2 peer. + Gossipsubv1_2, /// A gossipsub 1.1 peer. Gossipsubv1_1, /// A gossipsub 1.0 peer. @@ -117,6 +155,16 @@ pub struct RawMessage { pub validated: bool, } +impl PeerKind { + /// Returns true if peer speaks any gossipsub version. + pub(crate) fn is_gossipsub(&self) -> bool { + matches!( + self, + Self::Gossipsubv1_2 | Self::Gossipsubv1_1 | Self::Gossipsub + ) + } +} + impl RawMessage { /// Calculates the encoded length of this message (used for calculating metrics). pub fn raw_protobuf_len(&self) -> usize { @@ -195,57 +243,95 @@ pub enum SubscriptionAction { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PeerInfo { - pub peer_id: Option, - //TODO add this when RFC: Signed Address Records got added to the spec (see pull request +pub(crate) struct PeerInfo { + pub(crate) peer_id: Option, + // TODO add this when RFC: Signed Address Records got added to the spec (see pull request // https://github.com/libp2p/specs/pull/217) - //pub signed_peer_record: ?, + // pub signed_peer_record: ?, } /// A Control message received by the gossipsub system. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ControlAction { /// Node broadcasts known messages per topic - IHave control message. - IHave { - /// The topic of the messages. - topic_hash: TopicHash, - /// A list of known message ids (peer_id + sequence _number) as a string. - message_ids: Vec, - }, - /// The node requests specific message ids (peer_id + sequence _number) - IWant control message. - IWant { - /// A list of known message ids (peer_id + sequence _number) as a string. - message_ids: Vec, - }, + IHave(IHave), + /// The node requests specific message ids (peer_id + sequence _number) - IWant control + /// message. + IWant(IWant), /// The node has been added to the mesh - Graft control message. - Graft { - /// The mesh topic the peer should be added to. - topic_hash: TopicHash, - }, + Graft(Graft), /// The node has been removed from the mesh - Prune control message. - Prune { - /// The mesh topic the peer should be removed from. - topic_hash: TopicHash, - /// A list of peers to be proposed to the removed peer as peer exchange - peers: Vec, - /// The backoff time in seconds before we allow to reconnect - backoff: Option, - }, + Prune(Prune), + /// The node requests us to not forward message ids (peer_id + sequence _number) - IDontWant + /// control message. + IDontWant(IDontWant), } -/// A Gossipsub RPC message sent. +/// Node broadcasts known messages per topic - IHave control message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct IHave { + /// The topic of the messages. + pub(crate) topic_hash: TopicHash, + /// A list of known message ids (peer_id + sequence _number) as a string. + pub(crate) message_ids: Vec, +} + +/// The node requests specific message ids (peer_id + sequence _number) - IWant control message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct IWant { + /// A list of known message ids (peer_id + sequence _number) as a string. + pub(crate) message_ids: Vec, +} + +/// The node has been added to the mesh - Graft control message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Graft { + /// The mesh topic the peer should be added to. + pub(crate) topic_hash: TopicHash, +} + +/// The node has been removed from the mesh - Prune control message. #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Prune { + /// The mesh topic the peer should be removed from. + pub(crate) topic_hash: TopicHash, + /// A list of peers to be proposed to the removed peer as peer exchange + pub(crate) peers: Vec, + /// The backoff time in seconds before we allow to reconnect + pub(crate) backoff: Option, +} + +/// The node requests us to not forward message ids - IDontWant control message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct IDontWant { + /// A list of known message ids. + pub(crate) message_ids: Vec, +} + +/// A Gossipsub RPC message sent. +#[derive(Debug)] pub enum RpcOut { - /// Publish a Gossipsub message on network. - Publish(RawMessage), - /// Forward a Gossipsub message to the network. - Forward(RawMessage), + /// Publish a Gossipsub message on network.`timeout` limits the duration the message + /// can wait to be sent before it is abandoned. + Publish { message: RawMessage, timeout: Delay }, + /// Forward a Gossipsub message on network. `timeout` limits the duration the message + /// can wait to be sent before it is abandoned. + Forward { message: RawMessage, timeout: Delay }, /// Subscribe a topic. Subscribe(TopicHash), /// Unsubscribe a topic. Unsubscribe(TopicHash), - /// List of Gossipsub control messages. - Control(ControlAction), + /// Send a GRAFT control message. + Graft(Graft), + /// Send a PRUNE control message. + Prune(Prune), + /// Send a IHave control message. + IHave(IHave), + /// Send a IWant control message. + IWant(IWant), + /// The node requests us to not forward message ids (peer_id + sequence _number) - IDontWant + /// control message. + IDontWant(IDontWant), } impl RpcOut { @@ -260,12 +346,18 @@ impl From for proto::RPC { /// Converts the RPC into protobuf format. fn from(rpc: RpcOut) -> Self { match rpc { - RpcOut::Publish(message) => proto::RPC { + RpcOut::Publish { + message, + timeout: _, + } => proto::RPC { subscriptions: Vec::new(), publish: vec![message.into()], control: None, }, - RpcOut::Forward(message) => proto::RPC { + RpcOut::Forward { + message, + timeout: _, + } => proto::RPC { publish: vec![message.into()], subscriptions: Vec::new(), control: None, @@ -286,7 +378,7 @@ impl From for proto::RPC { }], control: None, }, - RpcOut::Control(ControlAction::IHave { + RpcOut::IHave(IHave { topic_hash, message_ids, }) => proto::RPC { @@ -300,9 +392,10 @@ impl From for proto::RPC { iwant: vec![], graft: vec![], prune: vec![], + idontwant: vec![], }), }, - RpcOut::Control(ControlAction::IWant { message_ids }) => proto::RPC { + RpcOut::IWant(IWant { message_ids }) => proto::RPC { publish: Vec::new(), subscriptions: Vec::new(), control: Some(proto::ControlMessage { @@ -312,9 +405,10 @@ impl From for proto::RPC { }], graft: vec![], prune: vec![], + idontwant: vec![], }), }, - RpcOut::Control(ControlAction::Graft { topic_hash }) => proto::RPC { + RpcOut::Graft(Graft { topic_hash }) => proto::RPC { publish: Vec::new(), subscriptions: vec![], control: Some(proto::ControlMessage { @@ -324,9 +418,10 @@ impl From for proto::RPC { topic_id: Some(topic_hash.into_string()), }], prune: vec![], + idontwant: vec![], }), }, - RpcOut::Control(ControlAction::Prune { + RpcOut::Prune(Prune { topic_hash, peers, backoff, @@ -350,9 +445,23 @@ impl From for proto::RPC { .collect(), backoff, }], + idontwant: vec![], }), } } + RpcOut::IDontWant(IDontWant { message_ids }) => proto::RPC { + publish: Vec::new(), + subscriptions: Vec::new(), + control: Some(proto::ControlMessage { + ihave: vec![], + iwant: vec![], + graft: vec![], + prune: vec![], + idontwant: vec![proto::ControlIDontWant { + message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), + }], + }), + }, } } } @@ -411,6 +520,7 @@ impl From for proto::RPC { iwant: Vec::new(), graft: Vec::new(), prune: Vec::new(), + idontwant: Vec::new(), }; let empty_control_msg = rpc.control_msgs.is_empty(); @@ -418,33 +528,33 @@ impl From for proto::RPC { for action in rpc.control_msgs { match action { // collect all ihave messages - ControlAction::IHave { + ControlAction::IHave(IHave { topic_hash, message_ids, - } => { + }) => { let rpc_ihave = proto::ControlIHave { topic_id: Some(topic_hash.into_string()), message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), }; control.ihave.push(rpc_ihave); } - ControlAction::IWant { message_ids } => { + ControlAction::IWant(IWant { message_ids }) => { let rpc_iwant = proto::ControlIWant { message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), }; control.iwant.push(rpc_iwant); } - ControlAction::Graft { topic_hash } => { + ControlAction::Graft(Graft { topic_hash }) => { let rpc_graft = proto::ControlGraft { topic_id: Some(topic_hash.into_string()), }; control.graft.push(rpc_graft); } - ControlAction::Prune { + ControlAction::Prune(Prune { topic_hash, peers, backoff, - } => { + }) => { let rpc_prune = proto::ControlPrune { topic_id: Some(topic_hash.into_string()), peers: peers @@ -459,6 +569,12 @@ impl From for proto::RPC { }; control.prune.push(rpc_prune); } + ControlAction::IDontWant(IDontWant { message_ids }) => { + let rpc_idontwant = proto::ControlIDontWant { + message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), + }; + control.idontwant.push(rpc_idontwant); + } } } @@ -497,6 +613,7 @@ impl PeerKind { Self::Floodsub => "Floodsub", Self::Gossipsub => "Gossipsub v1.0", Self::Gossipsubv1_1 => "Gossipsub v1.1", + Self::Gossipsubv1_2 => "Gossipsub v1.2", } } } diff --git a/protocols/gossipsub/tests/smoke.rs b/protocols/gossipsub/tests/smoke.rs index c8876428b4e..d5fec2c1985 100644 --- a/protocols/gossipsub/tests/smoke.rs +++ b/protocols/gossipsub/tests/smoke.rs @@ -18,17 +18,20 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use async_std::prelude::FutureExt; -use futures::stream::{FuturesUnordered, SelectAll}; -use futures::StreamExt; +use std::{task::Poll, time::Duration}; + +use futures::{ + stream::{FuturesUnordered, SelectAll}, + StreamExt, +}; use libp2p_gossipsub as gossipsub; use libp2p_gossipsub::{MessageAuthenticity, ValidationMode}; use libp2p_swarm::Swarm; use libp2p_swarm_test::SwarmExt as _; use quickcheck::{QuickCheck, TestResult}; use rand::{seq::SliceRandom, SeedableRng}; -use std::{task::Poll, time::Duration}; -use tracing_subscriber::EnvFilter; +use tokio::{runtime::Runtime, time}; + struct Graph { nodes: SelectAll>, } @@ -84,7 +87,7 @@ impl Graph { } }; - match condition.timeout(Duration::from_secs(10)).await { + match time::timeout(Duration::from_secs(10), condition).await { Ok(()) => true, Err(_) => false, } @@ -98,7 +101,7 @@ impl Graph { Poll::Pending => return Poll::Ready(()), } }); - fut.timeout(Duration::from_secs(10)).await.unwrap(); + time::timeout(Duration::from_secs(10), fut).await.unwrap(); } } @@ -128,9 +131,7 @@ async fn build_node() -> Swarm { #[test] fn multi_hop_propagation() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); fn prop(num_nodes: u8, seed: u64) -> TestResult { if !(2..=50).contains(&num_nodes) { @@ -139,7 +140,9 @@ fn multi_hop_propagation() { tracing::debug!(number_of_nodes=%num_nodes, seed=%seed); - async_std::task::block_on(async move { + let rt = Runtime::new().unwrap(); + + rt.block_on(async move { let mut graph = Graph::new_connected(num_nodes as usize, seed).await; let number_nodes = graph.nodes.len(); diff --git a/protocols/identify/CHANGELOG.md b/protocols/identify/CHANGELOG.md index 22c74b28cae..e9f7b017574 100644 --- a/protocols/identify/CHANGELOG.md +++ b/protocols/identify/CHANGELOG.md @@ -1,3 +1,32 @@ +## 0.46.0 + +- Make `identify::Config` fields private and add getter functions. + See [PR 5663](https://github.com/libp2p/rust-libp2p/pull/5663). +- Discard `Info`s received from remote peers that contain a public key that doesn't match their peer ID. + See [PR 5707](https://github.com/libp2p/rust-libp2p/pull/5707). + +## 0.45.1 + +- Add `hide_listen_addrs` option to prevent leaking (local) listen addresses. + See [PR 5507](https://github.com/libp2p/rust-libp2p/pull/5507). + +## 0.45.0 + +- Address translation is moved here from `libp2p-core`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) + + + +- Add `ConnectionId` in `Event`. + See [PR 4981](https://github.com/libp2p/rust-libp2p/pull/4981). + +## 0.44.2 + +- Emit `ToSwarm::NewExternalAddrOfPeer` for all external addresses of remote peers. + For this work, the address cache must be enabled via `identify::Config::with_cache_size`. + The default is 0, i.e. disabled. + See [PR 4371](https://github.com/libp2p/rust-libp2p/pull/4371). + ## 0.44.1 - Ensure `Multiaddr` handled and returned by `Behaviour` are `/p2p` terminated. diff --git a/protocols/identify/Cargo.toml b/protocols/identify/Cargo.toml index 8168a3a3fc6..9c4f8ea3707 100644 --- a/protocols/identify/Cargo.toml +++ b/protocols/identify/Cargo.toml @@ -2,8 +2,8 @@ name = "libp2p-identify" edition = "2021" rust-version = { workspace = true } -description = "Nodes identifcation protocol for libp2p" -version = "0.44.1" +description = "Nodes identification protocol for libp2p" +version = "0.46.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -12,33 +12,29 @@ categories = ["network-programming", "asynchronous"] [dependencies] asynchronous-codec = { workspace = true } -futures = "0.3.29" -futures-timer = "3.0.2" +futures = { workspace = true } +futures-timer = "3.0.3" futures-bounded = { workspace = true } libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true } -lru = "0.12.1" quick-protobuf-codec = { workspace = true } quick-protobuf = "0.8" -smallvec = "1.11.2" -thiserror = "1.0" -tracing = "0.1.37" -void = "1.0" -either = "1.9.0" +smallvec = "1.13.2" +thiserror = { workspace = true } +tracing = { workspace = true } +either = "1.12.0" [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } libp2p-swarm-test = { path = "../../swarm-test" } libp2p-swarm = { workspace = true, features = ["macros"] } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +libp2p-test-utils = { workspace = true } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/identify/src/behaviour.rs b/protocols/identify/src/behaviour.rs index e4da898f44c..0cd27d90717 100644 --- a/protocols/identify/src/behaviour.rs +++ b/protocols/identify/src/behaviour.rs @@ -18,28 +18,73 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::handler::{self, Handler, InEvent}; -use crate::protocol::{Info, UpgradeError}; -use libp2p_core::{multiaddr, ConnectedPoint, Endpoint, Multiaddr}; -use libp2p_identity::PeerId; -use libp2p_identity::PublicKey; -use libp2p_swarm::behaviour::{ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm}; -use libp2p_swarm::{ - ConnectionDenied, DialError, ExternalAddresses, ListenAddresses, NetworkBehaviour, - NotifyHandler, StreamUpgradeError, THandlerInEvent, ToSwarm, -}; -use libp2p_swarm::{ConnectionId, THandler, THandlerOutEvent}; -use lru::LruCache; -use std::collections::hash_map::Entry; -use std::num::NonZeroUsize; use std::{ - collections::{HashMap, HashSet, VecDeque}, - iter::FromIterator, - task::Context, - task::Poll, + collections::{hash_map::Entry, HashMap, HashSet, VecDeque}, + num::NonZeroUsize, + task::{Context, Poll}, time::Duration, }; +use libp2p_core::{ + multiaddr, multiaddr::Protocol, transport::PortUse, ConnectedPoint, Endpoint, Multiaddr, +}; +use libp2p_identity::{PeerId, PublicKey}; +use libp2p_swarm::{ + behaviour::{ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm}, + ConnectionDenied, ConnectionId, DialError, ExternalAddresses, ListenAddresses, + NetworkBehaviour, NotifyHandler, PeerAddresses, StreamUpgradeError, THandler, THandlerInEvent, + THandlerOutEvent, ToSwarm, _address_translation, +}; + +use crate::{ + handler::{self, Handler, InEvent}, + protocol::{Info, UpgradeError}, +}; + +/// Whether an [`Multiaddr`] is a valid for the QUIC transport. +fn is_quic_addr(addr: &Multiaddr, v1: bool) -> bool { + use Protocol::*; + let mut iter = addr.iter(); + let Some(first) = iter.next() else { + return false; + }; + let Some(second) = iter.next() else { + return false; + }; + let Some(third) = iter.next() else { + return false; + }; + let fourth = iter.next(); + let fifth = iter.next(); + + matches!(first, Ip4(_) | Ip6(_) | Dns(_) | Dns4(_) | Dns6(_)) + && matches!(second, Udp(_)) + && if v1 { + matches!(third, QuicV1) + } else { + matches!(third, Quic) + } + && matches!(fourth, Some(P2p(_)) | None) + && fifth.is_none() +} + +fn is_tcp_addr(addr: &Multiaddr) -> bool { + use Protocol::*; + + let mut iter = addr.iter(); + + let first = match iter.next() { + None => return false, + Some(p) => p, + }; + let second = match iter.next() { + None => return false, + Some(p) => p, + }; + + matches!(first, Ip4(_) | Ip6(_) | Dns(_) | Dns4(_) | Dns6(_)) && matches!(second, Tcp(_)) +} + /// Network behaviour that automatically identifies nodes periodically, returns information /// about them, and answers identify queries from other nodes. /// @@ -53,6 +98,9 @@ pub struct Behaviour { /// The address a remote observed for us. our_observed_addresses: HashMap, + /// The outbound connections established without port reuse (require translation) + outbound_connections_with_ephemeral_port: HashSet, + /// Pending events to be emitted when polled. events: VecDeque>, /// The addresses of all peers that we have discovered. @@ -68,20 +116,20 @@ pub struct Behaviour { pub struct Config { /// Application-specific version of the protocol family used by the peer, /// e.g. `ipfs/1.0.0` or `polkadot/1.0.0`. - pub protocol_version: String, + protocol_version: String, /// The public key of the local node. To report on the wire. - pub local_public_key: PublicKey, + local_public_key: PublicKey, /// Name and version of the local peer implementation, similar to the /// `User-Agent` header in the HTTP protocol. /// /// Defaults to `rust-libp2p/`. - pub agent_version: String, + agent_version: String, /// The interval at which identification requests are sent to /// the remote on established connections after the first request, /// i.e. the delay between identification requests. /// /// Defaults to 5 minutes. - pub interval: Duration, + interval: Duration, /// Whether new or expired listen addresses of the local node should /// trigger an active push of an identify message to all connected peers. @@ -91,13 +139,19 @@ pub struct Config { /// i.e. before the next periodic identify request with each peer. /// /// Disabled by default. - pub push_listen_addr_updates: bool, + push_listen_addr_updates: bool, /// How many entries of discovered peers to keep before we discard /// the least-recently used one. /// /// Disabled by default. - pub cache_size: usize, + cache_size: usize, + + /// Whether to include our listen addresses in our responses. If enabled, + /// we will effectively only share our external addresses. + /// + /// Disabled by default. + hide_listen_addrs: bool, } impl Config { @@ -111,6 +165,7 @@ impl Config { interval: Duration::from_secs(5 * 60), push_listen_addr_updates: false, cache_size: 100, + hide_listen_addrs: false, } } @@ -140,6 +195,47 @@ impl Config { self.cache_size = cache_size; self } + + /// Configures whether we prevent sending out our listen addresses. + pub fn with_hide_listen_addrs(mut self, b: bool) -> Self { + self.hide_listen_addrs = b; + self + } + + /// Get the protocol version of the Config. + pub fn protocol_version(&self) -> &str { + &self.protocol_version + } + + /// Get the local public key of the Config. + pub fn local_public_key(&self) -> &PublicKey { + &self.local_public_key + } + + /// Get the agent version of the Config. + pub fn agent_version(&self) -> &str { + &self.agent_version + } + + /// Get the interval of the Config. + pub fn interval(&self) -> Duration { + self.interval + } + + /// Get the push listen address updates boolean value of the Config. + pub fn push_listen_addr_updates(&self) -> bool { + self.push_listen_addr_updates + } + + /// Get the cache size of the Config. + pub fn cache_size(&self) -> usize { + self.cache_size + } + + /// Get the hide listen address boolean value of the Config. + pub fn hide_listen_addrs(&self) -> bool { + self.hide_listen_addrs + } } impl Behaviour { @@ -154,6 +250,7 @@ impl Behaviour { config, connected: HashMap::new(), our_observed_addresses: Default::default(), + outbound_connections_with_ephemeral_port: Default::default(), events: VecDeque::new(), discovered_peers, listen_addresses: Default::default(), @@ -200,19 +297,72 @@ impl Behaviour { .or_default() .insert(conn, addr); - if let Some(entry) = self.discovered_peers.get_mut(&peer_id) { + if let Some(cache) = self.discovered_peers.0.as_mut() { for addr in failed_addresses { - entry.remove(addr); + cache.remove(&peer_id, addr); } } } fn all_addresses(&self) -> HashSet { - self.listen_addresses - .iter() - .chain(self.external_addresses.iter()) - .cloned() - .collect() + let mut addrs = HashSet::from_iter(self.external_addresses.iter().cloned()); + if !self.config.hide_listen_addrs { + addrs.extend(self.listen_addresses.iter().cloned()); + }; + addrs + } + + fn emit_new_external_addr_candidate_event( + &mut self, + connection_id: ConnectionId, + observed: &Multiaddr, + ) { + if self + .outbound_connections_with_ephemeral_port + .contains(&connection_id) + { + // Apply address translation to the candidate address. + // For TCP without port-reuse, the observed address contains an ephemeral port which + // needs to be replaced by the port of a listen address. + let translated_addresses = { + let mut addrs: Vec<_> = self + .listen_addresses + .iter() + .filter_map(|server| { + if (is_tcp_addr(server) && is_tcp_addr(observed)) + || (is_quic_addr(server, true) && is_quic_addr(observed, true)) + || (is_quic_addr(server, false) && is_quic_addr(observed, false)) + { + _address_translation(server, observed) + } else { + None + } + }) + .collect(); + + // remove duplicates + addrs.sort_unstable(); + addrs.dedup(); + addrs + }; + + // If address translation yielded nothing, broadcast the original candidate address. + if translated_addresses.is_empty() { + self.events + .push_back(ToSwarm::NewExternalAddrCandidate(observed.clone())); + } else { + for addr in translated_addresses { + self.events + .push_back(ToSwarm::NewExternalAddrCandidate(addr)); + } + } + return; + } + + // outgoing connection dialed with port reuse + // incoming connection + self.events + .push_back(ToSwarm::NewExternalAddrCandidate(observed.clone())); } } @@ -240,18 +390,35 @@ impl NetworkBehaviour for Behaviour { fn handle_established_outbound_connection( &mut self, - _: ConnectionId, + connection_id: ConnectionId, peer: PeerId, addr: &Multiaddr, _: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { + // Contrary to inbound events, outbound events are full-p2p qualified + // so we remove /p2p/ in order to be homogeneous + // this will avoid Autonatv2 to probe twice the same address (fully-p2p-qualified + not + // fully-p2p-qualified) + let mut addr = addr.clone(); + if matches!(addr.iter().last(), Some(multiaddr::Protocol::P2p(_))) { + addr.pop(); + } + + if port_use == PortUse::New { + self.outbound_connections_with_ephemeral_port + .insert(connection_id); + } + Ok(Handler::new( self.config.interval, peer, self.config.local_public_key.clone(), self.config.protocol_version.clone(), self.config.agent_version.clone(), - addr.clone(), // TODO: This is weird? That is the public address we dialed, shouldn't need to tell the other party? + // TODO: This is weird? That is the public address we dialed, + // shouldn't need to tell the other party? + addr.clone(), self.all_addresses(), )) } @@ -259,7 +426,7 @@ impl NetworkBehaviour for Behaviour { fn on_connection_handler_event( &mut self, peer_id: PeerId, - id: ConnectionId, + connection_id: ConnectionId, event: THandlerOutEvent, ) { match event { @@ -268,19 +435,29 @@ impl NetworkBehaviour for Behaviour { info.listen_addrs .retain(|addr| multiaddr_matches_peer_id(addr, &peer_id)); - // Replace existing addresses to prevent other peer from filling up our memory. - self.discovered_peers - .put(peer_id, info.listen_addrs.iter().cloned()); - let observed = info.observed_addr.clone(); self.events - .push_back(ToSwarm::GenerateEvent(Event::Received { peer_id, info })); + .push_back(ToSwarm::GenerateEvent(Event::Received { + connection_id, + peer_id, + info: info.clone(), + })); + + if let Some(ref mut discovered_peers) = self.discovered_peers.0 { + for address in &info.listen_addrs { + if discovered_peers.add(peer_id, address.clone()) { + self.events.push_back(ToSwarm::NewExternalAddrOfPeer { + peer_id, + address: address.clone(), + }); + } + } + } - match self.our_observed_addresses.entry(id) { + match self.our_observed_addresses.entry(connection_id) { Entry::Vacant(not_yet_observed) => { not_yet_observed.insert(observed.clone()); - self.events - .push_back(ToSwarm::NewExternalAddrCandidate(observed)); + self.emit_new_external_addr_candidate_event(connection_id, &observed); } Entry::Occupied(already_observed) if already_observed.get() == &observed => { // No-op, we already observed this address. @@ -289,26 +466,33 @@ impl NetworkBehaviour for Behaviour { tracing::info!( old_address=%already_observed.get(), new_address=%observed, - "Our observed address on connection {id} changed", + "Our observed address on connection {connection_id} changed", ); *already_observed.get_mut() = observed.clone(); - self.events - .push_back(ToSwarm::NewExternalAddrCandidate(observed)); + self.emit_new_external_addr_candidate_event(connection_id, &observed); } } } handler::Event::Identification => { - self.events - .push_back(ToSwarm::GenerateEvent(Event::Sent { peer_id })); + self.events.push_back(ToSwarm::GenerateEvent(Event::Sent { + connection_id, + peer_id, + })); } handler::Event::IdentificationPushed(info) => { - self.events - .push_back(ToSwarm::GenerateEvent(Event::Pushed { peer_id, info })); + self.events.push_back(ToSwarm::GenerateEvent(Event::Pushed { + connection_id, + peer_id, + info, + })); } handler::Event::IdentificationError(error) => { - self.events - .push_back(ToSwarm::GenerateEvent(Event::Error { peer_id, error })); + self.events.push_back(ToSwarm::GenerateEvent(Event::Error { + connection_id, + peer_id, + error, + })); } } } @@ -385,13 +569,15 @@ impl NetworkBehaviour for Behaviour { } self.our_observed_addresses.remove(&connection_id); + self.outbound_connections_with_ephemeral_port + .remove(&connection_id); } FromSwarm::DialFailure(DialFailure { peer_id, error, .. }) => { - if let Some(entry) = peer_id.and_then(|id| self.discovered_peers.get_mut(&id)) { - if let DialError::Transport(errors) = error { - for (addr, _error) in errors { - entry.remove(addr); - } + if let (Some(peer_id), Some(cache), DialError::Transport(errors)) = + (peer_id, self.discovered_peers.0.as_mut(), error) + { + for (addr, _error) in errors { + cache.remove(&peer_id, addr); } } } @@ -406,6 +592,8 @@ impl NetworkBehaviour for Behaviour { pub enum Event { /// Identification information has been received from a peer. Received { + /// Identifier of the connection. + connection_id: ConnectionId, /// The peer that has been identified. peer_id: PeerId, /// The information provided by the peer. @@ -414,12 +602,16 @@ pub enum Event { /// Identification information of the local node has been sent to a peer in /// response to an identification request. Sent { + /// Identifier of the connection. + connection_id: ConnectionId, /// The peer that the information has been sent to. peer_id: PeerId, }, /// Identification information of the local node has been actively pushed to /// a peer. Pushed { + /// Identifier of the connection. + connection_id: ConnectionId, /// The peer that the information has been sent to. peer_id: PeerId, /// The full Info struct we pushed to the remote peer. Clients must @@ -428,6 +620,8 @@ pub enum Event { }, /// Error while attempting to identify the remote. Error { + /// Identifier of the connection. + connection_id: ConnectionId, /// The peer with whom the error originated. peer_id: PeerId, /// The error that occurred. @@ -435,6 +629,17 @@ pub enum Event { }, } +impl Event { + pub fn connection_id(&self) -> ConnectionId { + match self { + Event::Received { connection_id, .. } + | Event::Sent { connection_id, .. } + | Event::Pushed { connection_id, .. } + | Event::Error { connection_id, .. } => *connection_id, + } + } +} + /// If there is a given peer_id in the multiaddr, make sure it is the same as /// the given peer_id. If there is no peer_id for the peer in the mutiaddr, this returns true. fn multiaddr_matches_peer_id(addr: &Multiaddr, peer_id: &PeerId) -> bool { @@ -445,7 +650,7 @@ fn multiaddr_matches_peer_id(addr: &Multiaddr, peer_id: &PeerId) -> bool { true } -struct PeerCache(Option>>); +struct PeerCache(Option); impl PeerCache { fn disabled() -> Self { @@ -453,34 +658,15 @@ impl PeerCache { } fn enabled(size: NonZeroUsize) -> Self { - Self(Some(LruCache::new(size))) - } - - fn get_mut(&mut self, peer: &PeerId) -> Option<&mut HashSet> { - self.0.as_mut()?.get_mut(peer) - } - - fn put(&mut self, peer: PeerId, addresses: impl Iterator) { - let cache = match self.0.as_mut() { - None => return, - Some(cache) => cache, - }; - - let addresses = addresses.filter_map(|a| a.with_p2p(peer).ok()); - cache.put(peer, HashSet::from_iter(addresses)); + Self(Some(PeerAddresses::new(size))) } fn get(&mut self, peer: &PeerId) -> Vec { - let cache = match self.0.as_mut() { - None => return Vec::new(), - Some(cache) => cache, - }; - - cache - .get(peer) - .cloned() - .map(Vec::from_iter) - .unwrap_or_default() + if let Some(cache) = self.0.as_mut() { + cache.get(peer).collect() + } else { + Vec::new() + } } } diff --git a/protocols/identify/src/handler.rs b/protocols/identify/src/handler.rs index f9b77e0b63a..6e5af290cd2 100644 --- a/protocols/identify/src/handler.rs +++ b/protocols/identify/src/handler.rs @@ -18,29 +18,38 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::protocol::{Info, PushInfo, UpgradeError}; -use crate::{protocol, PROTOCOL_NAME, PUSH_PROTOCOL_NAME}; +use std::{ + collections::HashSet, + task::{Context, Poll}, + time::Duration, +}; + use either::Either; use futures::prelude::*; use futures_bounded::Timeout; use futures_timer::Delay; -use libp2p_core::upgrade::{ReadyUpgrade, SelectUpgrade}; -use libp2p_core::Multiaddr; -use libp2p_identity::PeerId; -use libp2p_identity::PublicKey; -use libp2p_swarm::handler::{ - ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, - ProtocolSupport, +use libp2p_core::{ + upgrade::{ReadyUpgrade, SelectUpgrade}, + Multiaddr, }; +use libp2p_identity::{PeerId, PublicKey}; use libp2p_swarm::{ + handler::{ + ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, + ProtocolSupport, + }, ConnectionHandler, ConnectionHandlerEvent, StreamProtocol, StreamUpgradeError, SubstreamProtocol, SupportedProtocols, }; use smallvec::SmallVec; -use std::collections::HashSet; -use std::{task::Context, task::Poll, time::Duration}; use tracing::Level; +use crate::{ + protocol, + protocol::{Info, PushInfo, UpgradeError}, + PROTOCOL_NAME, PUSH_PROTOCOL_NAME, +}; + const STREAM_TIMEOUT: Duration = Duration::from_secs(60); const MAX_CONCURRENT_STREAMS_PER_CONNECTION: usize = 10; @@ -233,10 +242,17 @@ impl Handler { } } - fn handle_incoming_info(&mut self, info: &Info) { + /// If the public key matches the remote peer, handles the given `info` and returns `true`. + fn handle_incoming_info(&mut self, info: &Info) -> bool { + let derived_peer_id = info.public_key.to_peer_id(); + if self.remote_peer_id != derived_peer_id { + return false; + } + self.remote_info.replace(info.clone()); self.update_supported_protocols_for_remote(info); + true } fn update_supported_protocols_for_remote(&mut self, remote_info: &Info) { @@ -335,45 +351,61 @@ impl ConnectionHandler for Handler { return Poll::Ready(event); } - match self.active_streams.poll_unpin(cx) { - Poll::Ready(Ok(Ok(Success::ReceivedIdentify(remote_info)))) => { - self.handle_incoming_info(&remote_info); - - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(Event::Identified( - remote_info, - ))); - } - Poll::Ready(Ok(Ok(Success::SentIdentifyPush(info)))) => { - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - Event::IdentificationPushed(info), - )); - } - Poll::Ready(Ok(Ok(Success::SentIdentify))) => { - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - Event::Identification, - )); - } - Poll::Ready(Ok(Ok(Success::ReceivedIdentifyPush(remote_push_info)))) => { - if let Some(mut info) = self.remote_info.clone() { - info.merge(remote_push_info); - self.handle_incoming_info(&info); - + while let Poll::Ready(ready) = self.active_streams.poll_unpin(cx) { + match ready { + Ok(Ok(Success::ReceivedIdentify(remote_info))) => { + if self.handle_incoming_info(&remote_info) { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + Event::Identified(remote_info), + )); + } else { + tracing::warn!( + %self.remote_peer_id, + ?remote_info.public_key, + derived_peer_id=%remote_info.public_key.to_peer_id(), + "Discarding received identify message as public key does not match remote peer ID", + ); + } + } + Ok(Ok(Success::SentIdentifyPush(info))) => { return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - Event::Identified(info), + Event::IdentificationPushed(info), )); - }; - } - Poll::Ready(Ok(Err(e))) => { - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - Event::IdentificationError(StreamUpgradeError::Apply(e)), - )); - } - Poll::Ready(Err(Timeout { .. })) => { - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - Event::IdentificationError(StreamUpgradeError::Timeout), - )); + } + Ok(Ok(Success::SentIdentify)) => { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + Event::Identification, + )); + } + Ok(Ok(Success::ReceivedIdentifyPush(remote_push_info))) => { + if let Some(mut info) = self.remote_info.clone() { + info.merge(remote_push_info); + + if self.handle_incoming_info(&info) { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + Event::Identified(info), + )); + } else { + tracing::warn!( + %self.remote_peer_id, + ?info.public_key, + derived_peer_id=%info.public_key.to_peer_id(), + "Discarding received identify message as public key does not match remote peer ID", + ); + } + } + } + Ok(Err(e)) => { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + Event::IdentificationError(StreamUpgradeError::Apply(e)), + )); + } + Err(Timeout { .. }) => { + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + Event::IdentificationError(StreamUpgradeError::Timeout), + )); + } } - Poll::Pending => {} } Poll::Pending @@ -398,7 +430,7 @@ impl ConnectionHandler for Handler { ConnectionEvent::DialUpgradeError(DialUpgradeError { error, .. }) => { self.events.push(ConnectionHandlerEvent::NotifyBehaviour( Event::IdentificationError( - error.map_upgrade_err(|e| void::unreachable(e.into_inner())), + error.map_upgrade_err(|e| libp2p_core::util::unreachable(e.into_inner())), ), )); self.trigger_next_identify.reset(self.interval); diff --git a/protocols/identify/src/lib.rs b/protocols/identify/src/lib.rs index 34928d5d7df..868ace87aeb 100644 --- a/protocols/identify/src/lib.rs +++ b/protocols/identify/src/lib.rs @@ -28,10 +28,10 @@ //! //! # Important Discrepancies //! -//! - **Using Identify with other protocols** Unlike some other libp2p implementations, -//! rust-libp2p does not treat Identify as a core protocol. This means that other protocols cannot -//! rely upon the existence of Identify, and need to be manually hooked up to Identify in order to -//! make use of its capabilities. +//! - **Using Identify with other protocols** Unlike some other libp2p implementations, rust-libp2p +//! does not treat Identify as a core protocol. This means that other protocols cannot rely upon +//! the existence of Identify, and need to be manually hooked up to Identify in order to make use +//! of its capabilities. //! //! # Usage //! @@ -41,8 +41,10 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -pub use self::behaviour::{Behaviour, Config, Event}; -pub use self::protocol::{Info, UpgradeError, PROTOCOL_NAME, PUSH_PROTOCOL_NAME}; +pub use self::{ + behaviour::{Behaviour, Config, Event}, + protocol::{Info, UpgradeError, PROTOCOL_NAME, PUSH_PROTOCOL_NAME}, +}; mod behaviour; mod handler; diff --git a/protocols/identify/src/protocol.rs b/protocols/identify/src/protocol.rs index c6b22b00c0a..257ec1f88d2 100644 --- a/protocols/identify/src/protocol.rs +++ b/protocols/identify/src/protocol.rs @@ -18,17 +18,18 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::proto; +use std::io; + use asynchronous_codec::{FramedRead, FramedWrite}; use futures::prelude::*; use libp2p_core::{multiaddr, Multiaddr}; use libp2p_identity as identity; use libp2p_identity::PublicKey; use libp2p_swarm::StreamProtocol; -use std::convert::TryFrom; -use std::io; use thiserror::Error; +use crate::proto; + const MAX_MESSAGE_SIZE_BYTES: usize = 4096; pub const PROTOCOL_NAME: StreamProtocol = StreamProtocol::new("/ipfs/id/1.0.0"); @@ -38,7 +39,7 @@ pub const PUSH_PROTOCOL_NAME: StreamProtocol = StreamProtocol::new("/ipfs/id/pus /// Identify information of a peer sent in protocol messages. #[derive(Debug, Clone)] pub struct Info { - /// The public key of the local peer. + /// The public key of the peer. pub public_key: PublicKey, /// Application-specific version of the protocol family used by the peer, /// e.g. `ipfs/1.0.0` or `polkadot/1.0.0`. @@ -78,7 +79,8 @@ impl Info { } /// Identify push information of a peer sent in protocol messages. -/// Note that missing fields should be ignored, as peers may choose to send partial updates containing only the fields whose values have changed. +/// Note that missing fields should be ignored, as peers may choose to send partial updates +/// containing only the fields whose values have changed. #[derive(Debug, Clone)] pub struct PushInfo { pub public_key: Option, @@ -265,9 +267,10 @@ pub enum UpgradeError { #[cfg(test)] mod tests { - use super::*; use libp2p_identity as identity; + use super::*; + #[test] fn skip_invalid_multiaddr() { let valid_multiaddr: Multiaddr = "/ip6/2001:db8::/tcp/1234".parse().unwrap(); diff --git a/protocols/identify/tests/smoke.rs b/protocols/identify/tests/smoke.rs index 5cccc09d863..a152bd75b19 100644 --- a/protocols/identify/tests/smoke.rs +++ b/protocols/identify/tests/smoke.rs @@ -1,18 +1,18 @@ +use std::{ + collections::HashSet, + iter, + time::{Duration, Instant}, +}; + use futures::StreamExt; -use libp2p_core::multiaddr::Protocol; use libp2p_identify as identify; +use libp2p_identity::Keypair; use libp2p_swarm::{Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt; -use std::collections::HashSet; -use std::iter; -use std::time::{Duration, Instant}; -use tracing_subscriber::EnvFilter; #[async_std::test] async fn periodic_identify() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut swarm1 = Swarm::new_ephemeral(|identity| { identify::Behaviour::new( @@ -35,8 +35,7 @@ async fn periodic_identify() { let (swarm2_memory_listen, swarm2_tcp_listen_addr) = swarm2.listen().await; swarm2.connect(&mut swarm1).await; - use identify::Event::Received; - use identify::Event::Sent; + use identify::Event::{Received, Sent}; match libp2p_swarm_test::drive(&mut swarm1, &mut swarm2).await { ( @@ -59,12 +58,7 @@ async fn periodic_identify() { assert_eq!(s1_info.protocol_version, "c"); assert_eq!(s1_info.agent_version, "d"); assert!(!s1_info.protocols.is_empty()); - assert_eq!( - s1_info.observed_addr, - swarm1_memory_listen - .clone() - .with(Protocol::P2p(swarm1_peer_id)) - ); + assert_eq!(s1_info.observed_addr, swarm1_memory_listen); assert!(s1_info.listen_addrs.contains(&swarm2_tcp_listen_addr)); assert!(s1_info.listen_addrs.contains(&swarm2_memory_listen)); @@ -73,7 +67,8 @@ async fn periodic_identify() { assert_eq!(s2_info.agent_version, "b"); assert!(!s2_info.protocols.is_empty()); - // Cannot assert observed address of dialer because memory transport uses ephemeral, outgoing ports. + // Cannot assert observed address of dialer because memory transport uses ephemeral, + // outgoing ports. // assert_eq!( // s2_info.observed_addr, // swarm2_memory_listen.with(Protocol::P2p(swarm2_peer_id.into())) @@ -86,9 +81,7 @@ async fn periodic_identify() { } #[async_std::test] async fn only_emits_address_candidate_once_per_connection() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut swarm1 = Swarm::new_ephemeral(|identity| { identify::Behaviour::new( @@ -110,7 +103,7 @@ async fn only_emits_address_candidate_once_per_connection() { async_std::task::spawn(swarm2.loop_on_next()); let swarm_events = futures::stream::poll_fn(|cx| swarm1.poll_next_unpin(cx)) - .take(5) + .take(8) .collect::>() .await; @@ -156,11 +149,148 @@ async fn only_emits_address_candidate_once_per_connection() { ); } +#[async_std::test] +async fn emits_unique_listen_addresses() { + libp2p_test_utils::with_default_env_filter(); + + let mut swarm1 = Swarm::new_ephemeral(|identity| { + identify::Behaviour::new( + identify::Config::new("a".to_string(), identity.public()) + .with_agent_version("b".to_string()) + .with_interval(Duration::from_secs(1)) + .with_cache_size(10), + ) + }); + let mut swarm2 = Swarm::new_ephemeral(|identity| { + identify::Behaviour::new( + identify::Config::new("c".to_string(), identity.public()) + .with_agent_version("d".to_string()), + ) + }); + + let (swarm2_mem_listen_addr, swarm2_tcp_listen_addr) = + swarm2.listen().with_memory_addr_external().await; + let swarm2_peer_id = *swarm2.local_peer_id(); + swarm1.connect(&mut swarm2).await; + + async_std::task::spawn(swarm2.loop_on_next()); + + let swarm_events = futures::stream::poll_fn(|cx| swarm1.poll_next_unpin(cx)) + .take(8) + .collect::>() + .await; + + let infos = swarm_events + .iter() + .filter_map(|e| match e { + SwarmEvent::Behaviour(identify::Event::Received { info, .. }) => Some(info.clone()), + _ => None, + }) + .collect::>(); + + assert!( + infos.len() > 1, + "should exchange identify payload more than once" + ); + + let listen_addrs = infos + .iter() + .map(|i| i.listen_addrs.clone()) + .collect::>(); + + for addrs in listen_addrs { + assert_eq!(addrs.len(), 2); + assert!(addrs.contains(&swarm2_mem_listen_addr)); + assert!(addrs.contains(&swarm2_tcp_listen_addr)); + } + + let reported_addrs = swarm_events + .iter() + .filter_map(|e| match e { + SwarmEvent::NewExternalAddrOfPeer { peer_id, address } => { + Some((*peer_id, address.clone())) + } + _ => None, + }) + .collect::>(); + + assert_eq!(reported_addrs.len(), 2, "To have two addresses of remote"); + assert!(reported_addrs.contains(&(swarm2_peer_id, swarm2_mem_listen_addr))); + assert!(reported_addrs.contains(&(swarm2_peer_id, swarm2_tcp_listen_addr))); +} + +#[async_std::test] +async fn hides_listen_addresses() { + libp2p_test_utils::with_default_env_filter(); + + let mut swarm1 = Swarm::new_ephemeral(|identity| { + identify::Behaviour::new( + identify::Config::new("a".to_string(), identity.public()) + .with_agent_version("b".to_string()) + .with_interval(Duration::from_secs(1)) + .with_cache_size(10), + ) + }); + let mut swarm2 = Swarm::new_ephemeral(|identity| { + identify::Behaviour::new( + identify::Config::new("c".to_string(), identity.public()) + .with_agent_version("d".to_string()) + .with_hide_listen_addrs(true), + ) + }); + + let (_swarm2_mem_listen_addr, swarm2_tcp_listen_addr) = + swarm2.listen().with_tcp_addr_external().await; + let swarm2_peer_id = *swarm2.local_peer_id(); + swarm1.connect(&mut swarm2).await; + + async_std::task::spawn(swarm2.loop_on_next()); + + let swarm_events = futures::stream::poll_fn(|cx| swarm1.poll_next_unpin(cx)) + .take(8) + .collect::>() + .await; + + let infos = swarm_events + .iter() + .filter_map(|e| match e { + SwarmEvent::Behaviour(identify::Event::Received { info, .. }) => Some(info.clone()), + _ => None, + }) + .collect::>(); + + assert!( + infos.len() > 1, + "should exchange identify payload more than once" + ); + + let listen_addrs = infos + .iter() + .map(|i| i.listen_addrs.clone()) + .collect::>(); + + for addrs in listen_addrs { + assert_eq!(addrs.len(), 1); + assert!(addrs.contains(&swarm2_tcp_listen_addr)); + } + + let reported_addrs = swarm_events + .iter() + .filter_map(|e| match e { + SwarmEvent::NewExternalAddrOfPeer { peer_id, address } => { + Some((*peer_id, address.clone())) + } + _ => None, + }) + .collect::>(); + + assert_eq!(reported_addrs.len(), 1, "To have one TCP address of remote"); + assert!(reported_addrs.contains(&(swarm2_peer_id, swarm2_tcp_listen_addr))); +} + #[async_std::test] async fn identify_push() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut swarm1 = Swarm::new_ephemeral(|identity| { identify::Behaviour::new(identify::Config::new("a".to_string(), identity.public())) @@ -210,9 +340,7 @@ async fn identify_push() { #[async_std::test] async fn discover_peer_after_disconnect() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut swarm1 = Swarm::new_ephemeral(|identity| { identify::Behaviour::new(identify::Config::new("a".to_string(), identity.public())) @@ -263,9 +391,7 @@ async fn discover_peer_after_disconnect() { #[async_std::test] async fn configured_interval_starts_after_first_identify() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let identify_interval = Duration::from_secs(5); @@ -300,3 +426,43 @@ async fn configured_interval_starts_after_first_identify() { assert!(time_to_first_identify < identify_interval) } + +#[async_std::test] +async fn reject_mismatched_public_key() { + libp2p_test_utils::with_default_env_filter(); + + let mut honest_swarm = Swarm::new_ephemeral(|identity| { + identify::Behaviour::new( + identify::Config::new("a".to_string(), identity.public()) + .with_interval(Duration::from_secs(1)), + ) + }); + let mut spoofing_swarm = Swarm::new_ephemeral(|_unused_identity| { + let arbitrary_public_key = Keypair::generate_ed25519().public(); + identify::Behaviour::new( + identify::Config::new("a".to_string(), arbitrary_public_key) + .with_interval(Duration::from_secs(1)), + ) + }); + + honest_swarm.listen().with_memory_addr_external().await; + spoofing_swarm.connect(&mut honest_swarm).await; + + spoofing_swarm + .wait(|event| { + matches!(event, SwarmEvent::Behaviour(identify::Event::Sent { .. })).then_some(()) + }) + .await; + + let honest_swarm_events = futures::stream::poll_fn(|cx| honest_swarm.poll_next_unpin(cx)) + .take(4) + .collect::>() + .await; + + assert!( + !honest_swarm_events + .iter() + .any(|e| matches!(e, SwarmEvent::Behaviour(identify::Event::Received { .. }))), + "should emit no received events as received public key won't match remote peer", + ); +} diff --git a/protocols/kad/CHANGELOG.md b/protocols/kad/CHANGELOG.md index b27a6943659..0c6e460afcd 100644 --- a/protocols/kad/CHANGELOG.md +++ b/protocols/kad/CHANGELOG.md @@ -1,3 +1,63 @@ +## 0.47.0 + +- Expose a kad query facility allowing specify num_results dynamicaly. + See [PR 5555](https://github.com/libp2p/rust-libp2p/pull/5555). +- Add `mode` getter on `Behaviour`. + See [PR 5573](https://github.com/libp2p/rust-libp2p/pull/5573). +- Add `Behavior::find_closest_local_peers()`. + See [PR 5645](https://github.com/libp2p/rust-libp2p/pull/5645). +- Fix `cargo clippy` warnings in `rustc 1.84.0-beta.1`. + See [PR 5700](https://github.com/libp2p/rust-libp2p/pull/5700). +- Expose Distance private field U256 to public. + See [PR 5705](https://github.com/libp2p/rust-libp2p/pull/5705). +- Fix systematic memory allocation when iterating over `KBuckets`. + See [PR 5715](https://github.com/libp2p/rust-libp2p/pull/5715). +- Remove deprecated default constructor for `ProtocolConfig`. + See [PR 5774](https://github.com/libp2p/rust-libp2p/pull/5774). + +## 0.46.2 + +- Emit `ToSwarm::NewExternalAddrOfPeer`. + See [PR 5549](https://github.com/libp2p/rust-libp2p/pull/5549) + +## 0.46.1 + +- Use new provider record update strategy to prevent Sybil attack. + See [PR 5536](https://github.com/libp2p/rust-libp2p/pull/5536). + +## 0.46.0 + +- Included multiaddresses of found peers alongside peer IDs in `GetClosestPeers` query results. + See [PR 5475](https://github.com/libp2p/rust-libp2p/pull/5475) +- Changed `FIND_NODE` response: now includes a list of closest peers when querying the recipient peer ID. Previously, this request yielded an empty response. + See [PR 5270](https://github.com/libp2p/rust-libp2p/pull/5270). +- Update to DHT republish interval and expiration time defaults to 22h and 48h respectively, rationale in [libp2p/specs#451](https://github.com/libp2p/specs/pull/451). + See [PR 3230](https://github.com/libp2p/rust-libp2p/pull/3230). +- Use default dial conditions more consistently. + See [PR 4957](https://github.com/libp2p/rust-libp2p/pull/4957) +- QueryClose progress whenever closer in range, instead of having to be the closest. + See [PR 4934](https://github.com/libp2p/rust-libp2p/pull/4934). +- Add periodic and automatic bootstrap. + See [PR 4838](https://github.com/libp2p/rust-libp2p/pull/4838). +- Make it mandatory to provide protocol names when creating a `kad::Config`. + Deprecate `kad::Config::default()`, replaced by `kad::Config::new(StreamProtocol)`. + See [PR 5122](https://github.com/libp2p/rust-libp2p/pull/5122). +- Compute `jobs_query_capacity` accurately. + See [PR 5148](https://github.com/libp2p/rust-libp2p/pull/5148). +- Derive `Copy` for `kbucket::key::Key`. + See [PR 5317](https://github.com/libp2p/rust-libp2p/pull/5317). +⁻ `KBucket` size can now be modified without changing the `K_VALUE`. + See [PR 5414](https://github.com/libp2p/rust-libp2p/pull/5414). +- Use `web-time` instead of `instant`. + See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). + +- Correctly handle the `NoKnownPeers` error on automatic bootstrap. + See [PR 5349](https://github.com/libp2p/rust-libp2p/pull/5349). +- Improve automatic bootstrap triggering conditions: + trigger when the routing table is updated and we have less that `K_VALUE` peers in it, + trigger when a new listen address is discovered and we have no connected peers. + See [PR 5474](https://github.com/libp2p/rust-libp2p/pull/5474). + ## 0.45.3 - The progress of the close query iterator shall be decided by ANY of the new peers. @@ -51,6 +111,7 @@ --> ## 0.44.5 + - Migrate to `quick-protobuf-codec` crate for codec logic. See [PR 4501]. @@ -176,7 +237,7 @@ - Instead of a single event `OutboundQueryCompleted`, there are now multiple events emitted, allowing the user to process them as they come in (via the new `OutboundQueryProgressed`). See `ProgressStep` to identify the final `OutboundQueryProgressed` of a single query. - To finish a query early, i.e. before the final `OutboundQueryProgressed` of the query, a caller needs to call `query.finish()`. - There is no more automatic caching of records. The user has to manually call `put_record_to` on the `QueryInfo::GetRecord.cache_candidates` to cache a record to a close peer that did not return the record on the foregone query. - See [PR 2712]. + See [PR 2712]. [PR 3085]: https://github.com/libp2p/rust-libp2p/pull/3085 [PR 3011]: https://github.com/libp2p/rust-libp2p/pull/3011 @@ -255,7 +316,7 @@ - Require owned key in `get_record()` method (see [PR 2477]). -- Merge NetworkBehaviour's inject_\* paired methods (see PR 2445). +- Merge NetworkBehaviour's inject\_\* paired methods (see PR 2445). [PR 2477]: https://github.com/libp2p/rust-libp2p/pull/2477 [PR 2445]: https://github.com/libp2p/rust-libp2p/pull/2445 diff --git a/protocols/kad/Cargo.toml b/protocols/kad/Cargo.toml index f4ad83972b4..757c0aed189 100644 --- a/protocols/kad/Cargo.toml +++ b/protocols/kad/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-kad" edition = "2021" rust-version = { workspace = true } description = "Kademlia protocol for libp2p" -version = "0.45.3" +version = "0.47.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,12 +11,11 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -arrayvec = "0.7.4" bytes = "1" -either = "1.9" +either = "1.11" fnv = "1.0" asynchronous-codec = { workspace = true } -futures = "0.3.29" +futures = { workspace = true } libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } futures-bounded = { workspace = true } @@ -25,14 +24,13 @@ quick-protobuf-codec = { workspace = true } libp2p-identity = { workspace = true, features = ["rand"] } rand = "0.8" sha2 = "0.10.8" -smallvec = "1.11.2" -uint = "0.9" -void = "1.0" -futures-timer = "3.0.2" -instant = "0.1.12" +smallvec = "1.13.2" +uint = "0.10" +futures-timer = "3.0.3" +web-time = { workspace = true } serde = { version = "1.0", optional = true, features = ["derive"] } -thiserror = "1" -tracing = "0.1.37" +thiserror = { workspace = true } +tracing = { workspace = true } [dev-dependencies] async-std = { version = "1.12.0", features = ["attributes"] } @@ -43,7 +41,7 @@ libp2p-swarm = { path = "../../swarm", features = ["macros"] } libp2p-swarm-test = { path = "../../swarm-test" } libp2p-yamux = { workspace = true } quickcheck = { workspace = true } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +libp2p-test-utils = { workspace = true } [features] serde = ["dep:serde", "bytes/serde"] @@ -52,8 +50,6 @@ serde = ["dep:serde", "bytes/serde"] # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/kad/src/addresses.rs b/protocols/kad/src/addresses.rs index 0b3dc71e649..c2168be661e 100644 --- a/protocols/kad/src/addresses.rs +++ b/protocols/kad/src/addresses.rs @@ -18,9 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::fmt; + use libp2p_core::Multiaddr; use smallvec::SmallVec; -use std::fmt; /// A non-empty list of (unique) addresses of a peer in the routing table. /// Every address must be a fully-qualified /p2p address. diff --git a/protocols/kad/src/behaviour.rs b/protocols/kad/src/behaviour.rs index cde4fbb8536..04ebe7d8174 100644 --- a/protocols/kad/src/behaviour.rs +++ b/protocols/kad/src/behaviour.rs @@ -22,42 +22,46 @@ mod test; -use crate::addresses::Addresses; -use crate::handler::{Handler, HandlerEvent, HandlerIn, RequestId}; -use crate::jobs::*; -use crate::kbucket::{self, Distance, KBucketsTable, NodeStatus}; -use crate::protocol::{ConnectionType, KadPeer, ProtocolConfig}; -use crate::query::{Query, QueryConfig, QueryId, QueryPool, QueryPoolState}; -use crate::record::{ - self, - store::{self, RecordStore}, - ProviderRecord, Record, +use std::{ + collections::{BTreeMap, HashMap, HashSet, VecDeque}, + fmt, + num::NonZeroUsize, + task::{Context, Poll, Waker}, + time::Duration, + vec, }; -use crate::K_VALUE; -use fnv::{FnvHashMap, FnvHashSet}; -use instant::Instant; -use libp2p_core::{ConnectedPoint, Endpoint, Multiaddr}; + +use fnv::FnvHashSet; +use libp2p_core::{transport::PortUse, ConnectedPoint, Endpoint, Multiaddr}; use libp2p_identity::PeerId; -use libp2p_swarm::behaviour::{ - AddressChange, ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm, -}; use libp2p_swarm::{ + behaviour::{AddressChange, ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm}, dial_opts::{self, DialOpts}, ConnectionDenied, ConnectionHandler, ConnectionId, DialError, ExternalAddresses, ListenAddresses, NetworkBehaviour, NotifyHandler, StreamProtocol, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; -use smallvec::SmallVec; -use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; -use std::fmt; -use std::num::NonZeroUsize; -use std::task::{Context, Poll, Waker}; -use std::time::Duration; -use std::vec; use thiserror::Error; use tracing::Level; +use web_time::Instant; pub use crate::query::QueryStats; +use crate::{ + addresses::Addresses, + bootstrap, + handler::{Handler, HandlerEvent, HandlerIn, RequestId}, + jobs::*, + kbucket::{self, Distance, KBucketConfig, KBucketsTable, NodeStatus}, + protocol, + protocol::{ConnectionType, KadPeer, ProtocolConfig}, + query::{Query, QueryConfig, QueryId, QueryPool, QueryPoolState}, + record::{ + self, + store::{self, RecordStore}, + ProviderRecord, Record, + }, + K_VALUE, +}; /// `Behaviour` is a `NetworkBehaviour` that implements the libp2p /// Kademlia protocol. @@ -75,7 +79,7 @@ pub struct Behaviour { record_filtering: StoreInserts, /// The currently active (i.e. in-progress) queries. - queries: QueryPool, + queries: QueryPool, /// The currently connected peers. /// @@ -116,6 +120,9 @@ pub struct Behaviour { /// The record storage. store: TStore, + + /// Tracks the status of the current bootstrap. + bootstrap_status: bootstrap::Status, } /// The configurable strategies for the insertion of peers @@ -155,8 +162,9 @@ pub enum StoreInserts { /// the record is forwarded immediately to the [`RecordStore`]. Unfiltered, /// Whenever a (provider) record is received, an event is emitted. - /// Provider records generate a [`InboundRequest::AddProvider`] under [`Event::InboundRequest`], - /// normal records generate a [`InboundRequest::PutRecord`] under [`Event::InboundRequest`]. + /// Provider records generate a [`InboundRequest::AddProvider`] under + /// [`Event::InboundRequest`], normal records generate a [`InboundRequest::PutRecord`] + /// under [`Event::InboundRequest`]. /// /// When deemed valid, a (provider) record needs to be explicitly stored in /// the [`RecordStore`] via [`RecordStore::put`] or [`RecordStore::add_provider`], @@ -170,7 +178,7 @@ pub enum StoreInserts { /// The configuration is consumed by [`Behaviour::new`]. #[derive(Debug, Clone)] pub struct Config { - kbucket_pending_timeout: Duration, + kbucket_config: KBucketConfig, query_config: QueryConfig, protocol_config: ProtocolConfig, record_ttl: Option, @@ -181,23 +189,16 @@ pub struct Config { provider_publication_interval: Option, kbucket_inserts: BucketInserts, caching: Caching, + periodic_bootstrap_interval: Option, + automatic_bootstrap_throttle: Option, } impl Default for Config { + /// Returns the default configuration. + /// + /// Deprecated: use `Config::new` instead. fn default() -> Self { - Config { - kbucket_pending_timeout: Duration::from_secs(60), - query_config: QueryConfig::default(), - protocol_config: Default::default(), - record_ttl: Some(Duration::from_secs(36 * 60 * 60)), - record_replication_interval: Some(Duration::from_secs(60 * 60)), - record_publication_interval: Some(Duration::from_secs(24 * 60 * 60)), - record_filtering: StoreInserts::Unfiltered, - provider_publication_interval: Some(Duration::from_secs(12 * 60 * 60)), - provider_record_ttl: Some(Duration::from_secs(24 * 60 * 60)), - kbucket_inserts: BucketInserts::OnConnected, - caching: Caching::Enabled { max_peers: 1 }, - } + Self::new(protocol::DEFAULT_PROTO_NAME) } } @@ -210,25 +211,31 @@ pub enum Caching { /// [`GetRecordOk::FinishedWithNoAdditionalRecord`] is always empty. Disabled, /// Up to `max_peers` peers not returning a record that are closest to the key - /// being looked up are tracked and returned in [`GetRecordOk::FinishedWithNoAdditionalRecord`]. - /// The write-back operation must be performed explicitly, if - /// desired and after choosing a record from the results, via [`Behaviour::put_record_to`]. + /// being looked up are tracked and returned in + /// [`GetRecordOk::FinishedWithNoAdditionalRecord`]. The write-back operation must be + /// performed explicitly, if desired and after choosing a record from the results, via + /// [`Behaviour::put_record_to`]. Enabled { max_peers: u16 }, } impl Config { - /// Sets custom protocol names. - /// - /// Kademlia nodes only communicate with other nodes using the same protocol - /// name. Using custom name(s) therefore allows to segregate the DHT from - /// others, if that is desired. - /// - /// More than one protocol name can be supplied. In this case the node will - /// be able to talk to other nodes supporting any of the provided names. - /// Multiple names must be used with caution to avoid network partitioning. - pub fn set_protocol_names(&mut self, names: Vec) -> &mut Self { - self.protocol_config.set_protocol_names(names); - self + /// Builds a new `Config` with the given protocol name. + pub fn new(protocol_name: StreamProtocol) -> Self { + Config { + kbucket_config: KBucketConfig::default(), + query_config: QueryConfig::default(), + protocol_config: ProtocolConfig::new(protocol_name), + record_ttl: Some(Duration::from_secs(48 * 60 * 60)), + record_replication_interval: Some(Duration::from_secs(60 * 60)), + record_publication_interval: Some(Duration::from_secs(22 * 60 * 60)), + record_filtering: StoreInserts::Unfiltered, + provider_publication_interval: Some(Duration::from_secs(12 * 60 * 60)), + provider_record_ttl: Some(Duration::from_secs(48 * 60 * 60)), + kbucket_inserts: BucketInserts::OnConnected, + caching: Caching::Enabled { max_peers: 1 }, + periodic_bootstrap_interval: Some(Duration::from_secs(5 * 60)), + automatic_bootstrap_throttle: Some(bootstrap::DEFAULT_AUTOMATIC_THROTTLE), + } } /// Sets the timeout for a single query. @@ -245,7 +252,7 @@ impl Config { /// Sets the replication factor to use. /// /// The replication factor determines to how many closest peers - /// a record is replicated. The default is [`K_VALUE`]. + /// a record is replicated. The default is [`crate::K_VALUE`]. pub fn set_replication_factor(&mut self, replication_factor: NonZeroUsize) -> &mut Self { self.query_config.replication_factor = replication_factor; self @@ -391,6 +398,55 @@ impl Config { self.caching = c; self } + + /// Sets the interval on which [`Behaviour::bootstrap`] is called periodically. + /// + /// * Default to `5` minutes. + /// * Set to `None` to disable periodic bootstrap. + pub fn set_periodic_bootstrap_interval(&mut self, interval: Option) -> &mut Self { + self.periodic_bootstrap_interval = interval; + self + } + + /// Sets the configuration for the k-buckets. + /// + /// * Default to K_VALUE. + /// + /// **WARNING**: setting a `size` higher that `K_VALUE` may imply additional memory allocations. + pub fn set_kbucket_size(&mut self, size: NonZeroUsize) -> &mut Self { + self.kbucket_config.set_bucket_size(size); + self + } + + /// Sets the timeout duration after creation of a pending entry after which + /// it becomes eligible for insertion into a full bucket, replacing the + /// least-recently (dis)connected node. + /// + /// * Default to `60` s. + pub fn set_kbucket_pending_timeout(&mut self, timeout: Duration) -> &mut Self { + self.kbucket_config.set_pending_timeout(timeout); + self + } + + /// Sets the time to wait before calling [`Behaviour::bootstrap`] after a new peer is inserted + /// in the routing table. This prevent cascading bootstrap requests when multiple peers are + /// inserted into the routing table "at the same time". This also allows to wait a little + /// bit for other potential peers to be inserted into the routing table before triggering a + /// bootstrap, giving more context to the future bootstrap request. + /// + /// * Default to `500` ms. + /// * Set to `Some(Duration::ZERO)` to never wait before triggering a bootstrap request when a + /// new peer is inserted in the routing table. + /// * Set to `None` to disable automatic bootstrap (no bootstrap request will be triggered when + /// a new peer is inserted in the routing table). + #[cfg(test)] + pub(crate) fn set_automatic_bootstrap_throttle( + &mut self, + duration: Option, + ) -> &mut Self { + self.automatic_bootstrap_throttle = duration; + self + } } impl Behaviour @@ -430,7 +486,7 @@ where Behaviour { store, caching: config.caching, - kbuckets: KBucketsTable::new(local_key, config.kbucket_pending_timeout), + kbuckets: KBucketsTable::new(local_key, config.kbucket_config), kbucket_inserts: config.kbucket_inserts, protocol_config: config.protocol_config, record_filtering: config.record_filtering, @@ -448,6 +504,10 @@ where mode: Mode::Client, auto_mode: true, no_events_waker: None, + bootstrap_status: bootstrap::Status::new( + config.periodic_bootstrap_interval, + config.automatic_bootstrap_throttle, + ), } } @@ -500,15 +560,13 @@ where /// /// Explicitly adding addresses of peers serves two purposes: /// - /// 1. In order for a node to join the DHT, it must know about at least - /// one other node of the DHT. + /// 1. In order for a node to join the DHT, it must know about at least one other node of the + /// DHT. /// - /// 2. When a remote peer initiates a connection and that peer is not - /// yet in the routing table, the `Kademlia` behaviour must be - /// informed of an address on which that peer is listening for - /// connections before it can be added to the routing table - /// from where it can subsequently be discovered by all peers - /// in the DHT. + /// 2. When a remote peer initiates a connection and that peer is not yet in the routing + /// table, the `Kademlia` behaviour must be informed of an address on which that peer is + /// listening for connections before it can be added to the routing table from where it can + /// subsequently be discovered by all peers in the DHT. /// /// If the routing table has been updated as a result of this operation, /// a [`Event::RoutingUpdated`] event is emitted. @@ -519,7 +577,7 @@ where }; let key = kbucket::Key::from(*peer); match self.kbuckets.entry(&key) { - kbucket::Entry::Present(mut entry, _) => { + Some(kbucket::Entry::Present(mut entry, _)) => { if entry.value().insert(address) { self.queued_events .push_back(ToSwarm::GenerateEvent(Event::RoutingUpdated { @@ -536,11 +594,11 @@ where } RoutingUpdate::Success } - kbucket::Entry::Pending(mut entry, _) => { + Some(kbucket::Entry::Pending(mut entry, _)) => { entry.value().insert(address); RoutingUpdate::Pending } - kbucket::Entry::Absent(entry) => { + Some(kbucket::Entry::Absent(entry)) => { let addresses = Addresses::new(address); let status = if self.connected_peers.contains(peer) { NodeStatus::Connected @@ -549,6 +607,8 @@ where }; match entry.insert(addresses.clone(), status) { kbucket::InsertResult::Inserted => { + self.bootstrap_on_low_peers(); + self.queued_events.push_back(ToSwarm::GenerateEvent( Event::RoutingUpdated { peer: *peer, @@ -570,15 +630,13 @@ where } kbucket::InsertResult::Pending { disconnected } => { self.queued_events.push_back(ToSwarm::Dial { - opts: DialOpts::peer_id(disconnected.into_preimage()) - .condition(dial_opts::PeerCondition::NotDialing) - .build(), + opts: DialOpts::peer_id(disconnected.into_preimage()).build(), }); RoutingUpdate::Pending } } } - kbucket::Entry::SelfEntry => RoutingUpdate::Failed, + None => RoutingUpdate::Failed, } } @@ -599,7 +657,7 @@ where ) -> Option, Addresses>> { let address = &address.to_owned().with_p2p(*peer).ok()?; let key = kbucket::Key::from(*peer); - match self.kbuckets.entry(&key) { + match self.kbuckets.entry(&key)? { kbucket::Entry::Present(mut entry, _) => { if entry.value().remove(address).is_err() { Some(entry.remove()) // it is the last address, thus remove the peer. @@ -614,7 +672,7 @@ where None } } - kbucket::Entry::Absent(..) | kbucket::Entry::SelfEntry => None, + kbucket::Entry::Absent(..) => None, } } @@ -627,10 +685,10 @@ where peer: &PeerId, ) -> Option, Addresses>> { let key = kbucket::Key::from(*peer); - match self.kbuckets.entry(&key) { + match self.kbuckets.entry(&key)? { kbucket::Entry::Present(entry, _) => Some(entry.remove()), kbucket::Entry::Pending(entry, _) => Some(entry.remove()), - kbucket::Entry::Absent(..) | kbucket::Entry::SelfEntry => None, + kbucket::Entry::Absent(..) => None, } } @@ -659,6 +717,31 @@ where /// The result of the query is delivered in a /// [`Event::OutboundQueryProgressed{QueryResult::GetClosestPeers}`]. pub fn get_closest_peers(&mut self, key: K) -> QueryId + where + K: Into> + Into> + Clone, + { + self.get_closest_peers_inner(key, None) + } + + /// Initiates an iterative query for the closest peers to the given key. + /// The expected responding peers is specified by `num_results` + /// Note that the result is capped after exceeds K_VALUE + /// + /// The result of the query is delivered in a + /// [`Event::OutboundQueryProgressed{QueryResult::GetClosestPeers}`]. + pub fn get_n_closest_peers(&mut self, key: K, num_results: NonZeroUsize) -> QueryId + where + K: Into> + Into> + Clone, + { + // The inner code never expect higher than K_VALUE results to be returned. + // And removing such cap will be tricky, + // since it would involve forging a new key and additional requests. + // Hence bound to K_VALUE here to set clear expectation and prevent unexpected behaviour. + let capped_num_results = std::cmp::min(num_results, K_VALUE); + self.get_closest_peers_inner(key, Some(capped_num_results)) + } + + fn get_closest_peers_inner(&mut self, key: K, num_results: Option) -> QueryId where K: Into> + Into> + Clone, { @@ -667,13 +750,14 @@ where let info = QueryInfo::GetClosestPeers { key, step: ProgressStep::first(), + num_results, }; let peer_keys: Vec> = self.kbuckets.closest_keys(&target).collect(); - let inner = QueryInner::new(info); - self.queries.add_iter_closest(target, peer_keys, inner) + self.queries.add_iter_closest(target, peer_keys, info) } - /// Returns closest peers to the given key; takes peers from local routing table only. + /// Returns all peers ordered by distance to the given key; takes peers from local routing table + /// only. pub fn get_closest_local_peers<'a, K: Clone>( &'a mut self, key: &'a kbucket::Key, @@ -681,6 +765,23 @@ where self.kbuckets.closest_keys(key) } + /// Finds the closest peers to a `key` in the context of a request by the `source` peer, such + /// that the `source` peer is never included in the result. + /// + /// Takes peers from local routing table only. Only returns number of peers equal to configured + /// replication factor. + pub fn find_closest_local_peers<'a, K: Clone>( + &'a mut self, + key: &'a kbucket::Key, + source: &'a PeerId, + ) -> impl Iterator + 'a { + self.kbuckets + .closest(key) + .filter(move |e| e.node.key.preimage() != source) + .take(self.queries.config().replication_factor.get()) + .map(KadPeer::from) + } + /// Performs a lookup for a record in the DHT. /// /// The result of this operation is delivered in a @@ -719,8 +820,7 @@ where } }; let peers = self.kbuckets.closest_keys(&target); - let inner = QueryInner::new(info); - let id = self.queries.add_iter_closest(target.clone(), peers, inner); + let id = self.queries.add_iter_closest(target.clone(), peers, info); // No queries were actually done for the results yet. let stats = QueryStats::empty(); @@ -776,8 +876,7 @@ where quorum, phase: PutRecordPhase::GetClosestPeers, }; - let inner = QueryInner::new(info); - Ok(self.queries.add_iter_closest(target.clone(), peers, inner)) + Ok(self.queries.add_iter_closest(target.clone(), peers, info)) } /// Stores a record at specific peers, without storing it locally. @@ -822,8 +921,7 @@ where get_closest_peers_stats: QueryStats::empty(), }, }; - let inner = QueryInner::new(info); - self.queries.add_fixed(peers, inner) + self.queries.add_fixed(peers, info) } /// Removes the record with the given key from _local_ storage, @@ -867,8 +965,16 @@ where /// /// > **Note**: Bootstrapping requires at least one node of the DHT to be known. /// > See [`Behaviour::add_address`]. + /// + /// > **Note**: Bootstrap does not require to be called manually. It is periodically + /// > invoked at regular intervals based on the configured `periodic_bootstrap_interval` (see + /// > [`Config::set_periodic_bootstrap_interval`] for details) and it is also automatically + /// > invoked + /// > when a new peer is inserted in the routing table. + /// > This parameter is used to call [`Behaviour::bootstrap`] periodically and automatically + /// > to ensure a healthy routing table. pub fn bootstrap(&mut self) -> Result { - let local_key = self.kbuckets.local_key().clone(); + let local_key = *self.kbuckets.local_key(); let info = QueryInfo::Bootstrap { peer: *local_key.preimage(), remaining: None, @@ -876,10 +982,11 @@ where }; let peers = self.kbuckets.closest_keys(&local_key).collect::>(); if peers.is_empty() { + self.bootstrap_status.reset_timers(); Err(NoKnownPeers()) } else { - let inner = QueryInner::new(info); - Ok(self.queries.add_iter_closest(local_key, peers, inner)) + self.bootstrap_status.on_started(); + Ok(self.queries.add_iter_closest(local_key, peers, info)) } } @@ -924,8 +1031,7 @@ where key, phase: AddProviderPhase::GetClosestPeers, }; - let inner = QueryInner::new(info); - let id = self.queries.add_iter_closest(target.clone(), peers, inner); + let id = self.queries.add_iter_closest(target.clone(), peers, info); Ok(id) } @@ -965,8 +1071,7 @@ where let target = kbucket::Key::new(key.clone()); let peers = self.kbuckets.closest_keys(&target); - let inner = QueryInner::new(info); - let id = self.queries.add_iter_closest(target.clone(), peers, inner); + let id = self.queries.add_iter_closest(target.clone(), peers, info); // No queries were actually done for the results yet. let stats = QueryStats::empty(); @@ -988,10 +1093,12 @@ where /// Set the [`Mode`] in which we should operate. /// - /// By default, we are in [`Mode::Client`] and will swap into [`Mode::Server`] as soon as we have a confirmed, external address via [`FromSwarm::ExternalAddrConfirmed`]. + /// By default, we are in [`Mode::Client`] and will swap into [`Mode::Server`] as soon as we + /// have a confirmed, external address via [`FromSwarm::ExternalAddrConfirmed`]. /// - /// Setting a mode via this function disables this automatic behaviour and unconditionally operates in the specified mode. - /// To reactivate the automatic configuration, pass [`None`] instead. + /// Setting a mode via this function disables this automatic behaviour and unconditionally + /// operates in the specified mode. To reactivate the automatic configuration, pass [`None`] + /// instead. pub fn set_mode(&mut self, mode: Option) { match mode { Some(mode) => { @@ -1010,6 +1117,11 @@ where } } + /// Get the [`Mode`] in which the DHT is currently operating. + pub fn mode(&self) -> Mode { + self.mode + } + fn reconfigure_mode(&mut self) { if self.connections.is_empty() { return; @@ -1067,8 +1179,8 @@ where "Previous match arm handled empty list" ); - // Previously, server-mode, now also server-mode because > 1 external address. Don't log anything to avoid spam. - + // Previously, server-mode, now also server-mode because > 1 external address. + // Don't log anything to avoid spam. Mode::Server } }; @@ -1100,32 +1212,12 @@ where "Peer reported by source in query" ); let addrs = peer.multiaddrs.iter().cloned().collect(); - query.inner.addresses.insert(peer.node_id, addrs); + query.peers.addresses.insert(peer.node_id, addrs); } query.on_success(source, others_iter.cloned().map(|kp| kp.node_id)) } } - /// Finds the closest peers to a `target` in the context of a request by - /// the `source` peer, such that the `source` peer is never included in the - /// result. - fn find_closest( - &mut self, - target: &kbucket::Key, - source: &PeerId, - ) -> Vec { - if target == self.kbuckets.local_key() { - Vec::new() - } else { - self.kbuckets - .closest(target) - .filter(|e| e.node.key.preimage() != source) - .take(self.queries.config().replication_factor.get()) - .map(KadPeer::from) - .collect() - } - } - /// Collects all peers who are known to be providers of the value for a given `Multihash`. fn provider_peers(&mut self, key: &record::Key, source: &PeerId) -> Vec { let kbuckets = &mut self.kbuckets; @@ -1164,7 +1256,8 @@ where let key = kbucket::Key::from(node_id); kbuckets .entry(&key) - .view() + .as_mut() + .and_then(|e| e.view()) .map(|e| e.node.value.clone().into_vec()) } } else { @@ -1192,8 +1285,7 @@ where }; let target = kbucket::Key::new(key); let peers = self.kbuckets.closest_keys(&target); - let inner = QueryInner::new(info); - self.queries.add_iter_closest(target.clone(), peers, inner); + self.queries.add_iter_closest(target.clone(), peers, info); } /// Starts an iterative `PUT_VALUE` query for the given record. @@ -1207,8 +1299,7 @@ where context, phase: PutRecordPhase::GetClosestPeers, }; - let inner = QueryInner::new(info); - self.queries.add_iter_closest(target.clone(), peers, inner); + self.queries.add_iter_closest(target.clone(), peers, info); } /// Updates the routing table with a new connection status and address of a peer. @@ -1220,7 +1311,7 @@ where ) { let key = kbucket::Key::from(peer); match self.kbuckets.entry(&key) { - kbucket::Entry::Present(mut entry, old_status) => { + Some(kbucket::Entry::Present(mut entry, old_status)) => { if old_status != new_status { entry.update(new_status) } @@ -1243,7 +1334,7 @@ where } } - kbucket::Entry::Pending(mut entry, old_status) => { + Some(kbucket::Entry::Pending(mut entry, old_status)) => { if let Some(address) = address { entry.value().insert(address); } @@ -1252,7 +1343,7 @@ where } } - kbucket::Entry::Absent(entry) => { + Some(kbucket::Entry::Absent(entry)) => { // Only connected nodes with a known address are newly inserted. if new_status != NodeStatus::Connected { return; @@ -1273,6 +1364,8 @@ where let addresses = Addresses::new(a); match entry.insert(addresses.clone(), new_status) { kbucket::InsertResult::Inserted => { + self.bootstrap_on_low_peers(); + let event = Event::RoutingUpdated { peer, is_new_peer: true, @@ -1311,7 +1404,6 @@ where if !self.connected_peers.contains(disconnected.preimage()) { self.queued_events.push_back(ToSwarm::Dial { opts: DialOpts::peer_id(disconnected.into_preimage()) - .condition(dial_opts::PeerCondition::NotDialing) .build(), }) } @@ -1324,18 +1416,31 @@ where } } + /// A new peer has been inserted in the routing table but we check if the routing + /// table is currently small (less that `K_VALUE` peers are present) and only + /// trigger a bootstrap in that case + fn bootstrap_on_low_peers(&mut self) { + if self + .kbuckets() + .map(|kbucket| kbucket.num_entries()) + .sum::() + < K_VALUE.get() + { + self.bootstrap_status.trigger(); + } + } + /// Handles a finished (i.e. successful) query. - fn query_finished(&mut self, q: Query) -> Option { + fn query_finished(&mut self, q: Query) -> Option { let query_id = q.id(); tracing::trace!(query=?query_id, "Query finished"); - let result = q.into_result(); - match result.inner.info { + match q.info { QueryInfo::Bootstrap { peer, remaining, mut step, } => { - let local_key = self.kbuckets.local_key().clone(); + let local_key = *self.kbuckets.local_key(); let mut remaining = remaining.unwrap_or_else(|| { debug_assert_eq!(&peer, local_key.preimage()); // The lookup for the local key finished. To complete the bootstrap process, @@ -1383,16 +1488,16 @@ where step: step.next(), }; let peers = self.kbuckets.closest_keys(&target); - let inner = QueryInner::new(info); self.queries - .continue_iter_closest(query_id, target.clone(), peers, inner); + .continue_iter_closest(query_id, target, peers, info); } else { step.last = true; + self.bootstrap_status.on_finish(); }; Some(Event::OutboundQueryProgressed { id: query_id, - stats: result.stats, + stats: q.stats, result: QueryResult::Bootstrap(Ok(BootstrapOk { peer, num_remaining, @@ -1401,15 +1506,15 @@ where }) } - QueryInfo::GetClosestPeers { key, mut step } => { + QueryInfo::GetClosestPeers { key, mut step, .. } => { step.last = true; Some(Event::OutboundQueryProgressed { id: query_id, - stats: result.stats, + stats: q.stats, result: QueryResult::GetClosestPeers(Ok(GetClosestPeersOk { key, - peers: result.peers.collect(), + peers: q.peers.into_peerinfos_iter().collect(), })), step, }) @@ -1420,10 +1525,10 @@ where Some(Event::OutboundQueryProgressed { id: query_id, - stats: result.stats, + stats: q.stats, result: QueryResult::GetProviders(Ok( GetProvidersOk::FinishedWithNoAdditionalRecord { - closest_peers: result.peers.collect(), + closest_peers: q.peers.into_peerids_iter().collect(), }, )), step, @@ -1437,16 +1542,17 @@ where } => { let provider_id = self.local_peer_id; let external_addresses = self.external_addresses.iter().cloned().collect(); - let inner = QueryInner::new(QueryInfo::AddProvider { + let info = QueryInfo::AddProvider { context, key, phase: AddProviderPhase::AddProvider { provider_id, external_addresses, - get_closest_peers_stats: result.stats, + get_closest_peers_stats: q.stats, }, - }); - self.queries.continue_fixed(query_id, result.peers, inner); + }; + self.queries + .continue_fixed(query_id, q.peers.into_peerids_iter(), info); None } @@ -1461,13 +1567,13 @@ where } => match context { AddProviderContext::Publish => Some(Event::OutboundQueryProgressed { id: query_id, - stats: get_closest_peers_stats.merge(result.stats), + stats: get_closest_peers_stats.merge(q.stats), result: QueryResult::StartProviding(Ok(AddProviderOk { key })), step: ProgressStep::first_and_last(), }), AddProviderContext::Republish => Some(Event::OutboundQueryProgressed { id: query_id, - stats: get_closest_peers_stats.merge(result.stats), + stats: get_closest_peers_stats.merge(q.stats), result: QueryResult::RepublishProvider(Ok(AddProviderOk { key })), step: ProgressStep::first_and_last(), }), @@ -1486,12 +1592,12 @@ where } else { Err(GetRecordError::NotFound { key, - closest_peers: result.peers.collect(), + closest_peers: q.peers.into_peerids_iter().collect(), }) }; Some(Event::OutboundQueryProgressed { id: query_id, - stats: result.stats, + stats: q.stats, result: QueryResult::GetRecord(results), step, }) @@ -1509,11 +1615,11 @@ where quorum, phase: PutRecordPhase::PutRecord { success: vec![], - get_closest_peers_stats: result.stats, + get_closest_peers_stats: q.stats, }, }; - let inner = QueryInner::new(info); - self.queries.continue_fixed(query_id, result.peers, inner); + self.queries + .continue_fixed(query_id, q.peers.into_peerids_iter(), info); None } @@ -1542,14 +1648,14 @@ where PutRecordContext::Publish | PutRecordContext::Custom => { Some(Event::OutboundQueryProgressed { id: query_id, - stats: get_closest_peers_stats.merge(result.stats), + stats: get_closest_peers_stats.merge(q.stats), result: QueryResult::PutRecord(mk_result(record.key)), step: ProgressStep::first_and_last(), }) } PutRecordContext::Republish => Some(Event::OutboundQueryProgressed { id: query_id, - stats: get_closest_peers_stats.merge(result.stats), + stats: get_closest_peers_stats.merge(q.stats), result: QueryResult::RepublishRecord(mk_result(record.key)), step: ProgressStep::first_and_last(), }), @@ -1563,11 +1669,10 @@ where } /// Handles a query that timed out. - fn query_timeout(&mut self, query: Query) -> Option { + fn query_timeout(&mut self, query: Query) -> Option { let query_id = query.id(); tracing::trace!(query=?query_id, "Query timed out"); - let result = query.into_result(); - match result.inner.info { + match query.info { QueryInfo::Bootstrap { peer, mut remaining, @@ -1580,21 +1685,21 @@ where remaining.take().and_then(|mut r| Some((r.next()?, r))) { let info = QueryInfo::Bootstrap { - peer: target.clone().into_preimage(), + peer: target.into_preimage(), remaining: Some(remaining), step: step.next(), }; let peers = self.kbuckets.closest_keys(&target); - let inner = QueryInner::new(info); self.queries - .continue_iter_closest(query_id, target.clone(), peers, inner); + .continue_iter_closest(query_id, target, peers, info); } else { step.last = true; + self.bootstrap_status.on_finish(); } Some(Event::OutboundQueryProgressed { id: query_id, - stats: result.stats, + stats: query.stats, result: QueryResult::Bootstrap(Err(BootstrapError::Timeout { peer, num_remaining, @@ -1606,27 +1711,26 @@ where QueryInfo::AddProvider { context, key, .. } => Some(match context { AddProviderContext::Publish => Event::OutboundQueryProgressed { id: query_id, - stats: result.stats, + stats: query.stats, result: QueryResult::StartProviding(Err(AddProviderError::Timeout { key })), step: ProgressStep::first_and_last(), }, AddProviderContext::Republish => Event::OutboundQueryProgressed { id: query_id, - stats: result.stats, + stats: query.stats, result: QueryResult::RepublishProvider(Err(AddProviderError::Timeout { key })), step: ProgressStep::first_and_last(), }, }), - QueryInfo::GetClosestPeers { key, mut step } => { + QueryInfo::GetClosestPeers { key, mut step, .. } => { step.last = true; - Some(Event::OutboundQueryProgressed { id: query_id, - stats: result.stats, + stats: query.stats, result: QueryResult::GetClosestPeers(Err(GetClosestPeersError::Timeout { key, - peers: result.peers.collect(), + peers: query.peers.into_peerinfos_iter().collect(), })), step, }) @@ -1650,14 +1754,14 @@ where PutRecordContext::Publish | PutRecordContext::Custom => { Some(Event::OutboundQueryProgressed { id: query_id, - stats: result.stats, + stats: query.stats, result: QueryResult::PutRecord(err), step: ProgressStep::first_and_last(), }) } PutRecordContext::Republish => Some(Event::OutboundQueryProgressed { id: query_id, - stats: result.stats, + stats: query.stats, result: QueryResult::RepublishRecord(err), step: ProgressStep::first_and_last(), }), @@ -1682,7 +1786,7 @@ where Some(Event::OutboundQueryProgressed { id: query_id, - stats: result.stats, + stats: query.stats, result: QueryResult::GetRecord(Err(GetRecordError::Timeout { key })), step, }) @@ -1693,10 +1797,10 @@ where Some(Event::OutboundQueryProgressed { id: query_id, - stats: result.stats, + stats: query.stats, result: QueryResult::GetProviders(Err(GetProvidersError::Timeout { key, - closest_peers: result.peers.collect(), + closest_peers: query.peers.into_peerids_iter().collect(), })), step, }) @@ -1863,7 +1967,7 @@ where fn address_failed(&mut self, peer_id: PeerId, address: &Multiaddr) { let key = kbucket::Key::from(peer_id); - if let Some(addrs) = self.kbuckets.entry(&key).value() { + if let Some(addrs) = self.kbuckets.entry(&key).as_mut().and_then(|e| e.value()) { // TODO: Ideally, the address should only be removed if the error can // be classified as "permanent" but since `err` is currently a borrowed // trait object without a `'static` bound, even downcasting for inspection @@ -1894,7 +1998,7 @@ where } for query in self.queries.iter_mut() { - if let Some(addrs) = query.inner.addresses.get_mut(&peer_id) { + if let Some(addrs) = query.peers.addresses.get_mut(&peer_id) { addrs.retain(|a| a != address); } } @@ -1931,7 +2035,12 @@ where let (old, new) = (old.get_remote_address(), new.get_remote_address()); // Update routing table. - if let Some(addrs) = self.kbuckets.entry(&kbucket::Key::from(peer)).value() { + if let Some(addrs) = self + .kbuckets + .entry(&kbucket::Key::from(peer)) + .as_mut() + .and_then(|e| e.value()) + { if addrs.replace(old, new) { tracing::debug!( %peer, @@ -1961,10 +2070,10 @@ where // // Given two connected nodes: local node A and remote node B. Say node B // is not in node A's routing table. Additionally node B is part of the - // `QueryInner::addresses` list of an ongoing query on node A. Say Node + // `Query::addresses` list of an ongoing query on node A. Say Node // B triggers an address change and then disconnects. Later on the // earlier mentioned query on node A would like to connect to node B. - // Without replacing the address in the `QueryInner::addresses` set node + // Without replacing the address in the `Query::addresses` set node // A would attempt to dial the old and not the new address. // // While upholding correctness, iterating through all discovered @@ -1972,7 +2081,7 @@ where // large performance impact. If so, the code below might be worth // revisiting. for query in self.queries.iter_mut() { - if let Some(addrs) = query.inner.addresses.get_mut(&peer) { + if let Some(addrs) = query.peers.addresses.get_mut(&peer) { for addr in addrs.iter_mut() { if addr == old { *addr = new.clone(); @@ -2036,7 +2145,8 @@ where } } - /// Preloads a new [`Handler`] with requests that are waiting to be sent to the newly connected peer. + /// Preloads a new [`Handler`] with requests that are waiting + /// to be sent to the newly connected peer. fn preload_new_handler( &mut self, handler: &mut Handler, @@ -2047,11 +2157,10 @@ where // Queue events for sending pending RPCs to the connected peer. // There can be only one pending RPC for a particular peer and query per definition. for (_peer_id, event) in self.queries.iter_mut().filter_map(|q| { - q.inner - .pending_rpcs + q.pending_rpcs .iter() .position(|(p, _)| p == &peer) - .map(|p| q.inner.pending_rpcs.remove(p)) + .map(|p| q.pending_rpcs.remove(p)) }) { handler.on_behaviour_event(event) } @@ -2099,10 +2208,12 @@ where peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { let connected_point = ConnectedPoint::Dialer { address: addr.clone(), role_override, + port_use, }; let mut handler = Handler::new( @@ -2128,11 +2239,11 @@ where Some(peer) => peer, }; - // We should order addresses from decreasing likelyhood of connectivity, so start with + // We should order addresses from decreasing likelihood of connectivity, so start with // the addresses of that peer in the k-buckets. let key = kbucket::Key::from(peer_id); let mut peer_addrs = - if let kbucket::Entry::Present(mut entry, _) = self.kbuckets.entry(&key) { + if let Some(kbucket::Entry::Present(mut entry, _)) = self.kbuckets.entry(&key) { let addrs = entry.value().iter().cloned().collect::>(); debug_assert!(!addrs.is_empty(), "Empty peer addresses in routing table."); addrs @@ -2142,7 +2253,7 @@ where // We add to that a temporary list of addresses from the ongoing queries. for query in self.queries.iter() { - if let Some(addrs) = query.inner.addresses.get(&peer_id) { + if let Some(addrs) = query.peers.addresses.get(&peer_id) { peer_addrs.extend(addrs.iter().cloned()) } } @@ -2180,7 +2291,9 @@ where } HandlerEvent::FindNodeReq { key, request_id } => { - let closer_peers = self.find_closest(&kbucket::Key::new(key), &source); + let closer_peers = self + .find_closest_local_peers(&kbucket::Key::new(key), &source) + .collect::>(); self.queued_events .push_back(ToSwarm::GenerateEvent(Event::InboundRequest { @@ -2208,7 +2321,9 @@ where HandlerEvent::GetProvidersReq { key, request_id } => { let provider_peers = self.provider_peers(&key, &source); - let closer_peers = self.find_closest(&kbucket::Key::new(key), &source); + let closer_peers = self + .find_closest_local_peers(&kbucket::Key::new(key), &source) + .collect::>(); self.queued_events .push_back(ToSwarm::GenerateEvent(Event::InboundRequest { @@ -2243,7 +2358,7 @@ where ref mut providers_found, ref mut step, .. - } = query.inner.info + } = query.info { *providers_found += provider_peers.len(); let providers = provider_peers.iter().map(|p| p.node_id).collect(); @@ -2302,7 +2417,9 @@ where None => None, }; - let closer_peers = self.find_closest(&kbucket::Key::new(key), &source); + let closer_peers = self + .find_closest_local_peers(&kbucket::Key::new(key), &source) + .collect::>(); self.queued_events .push_back(ToSwarm::GenerateEvent(Event::InboundRequest { @@ -2335,7 +2452,7 @@ where ref mut step, ref mut found_a_record, cache_candidates, - } = &mut query.inner.info + } = &mut query.info { if let Some(record) = record { *found_a_record = true; @@ -2389,7 +2506,7 @@ where phase: PutRecordPhase::PutRecord { success, .. }, quorum, .. - } = &mut query.inner.info + } = &mut query.info { success.push(source); @@ -2427,14 +2544,14 @@ where // Run the periodic provider announcement job. if let Some(mut job) = self.add_provider_job.take() { let num = usize::min(JOBS_MAX_NEW_QUERIES, jobs_query_capacity); - for _ in 0..num { + for i in 0..num { if let Poll::Ready(r) = job.poll(cx, &mut self.store, now) { self.start_add_provider(r.key, AddProviderContext::Republish) } else { + jobs_query_capacity -= i; break; } } - jobs_query_capacity -= num; self.add_provider_job = Some(job); } @@ -2457,6 +2574,13 @@ where self.put_record_job = Some(job); } + // Poll bootstrap periodically and automatically. + if let Poll::Ready(()) = self.bootstrap_status.poll_next_bootstrap(cx) { + if let Err(e) = self.bootstrap() { + tracing::warn!("Failed to trigger bootstrap: {e}"); + } + } + loop { // Drain queued events first. if let Some(event) = self.queued_events.pop_front() { @@ -2466,13 +2590,19 @@ where // Drain applied pending entries from the routing table. if let Some(entry) = self.kbuckets.take_applied_pending() { let kbucket::Node { key, value } = entry.inserted; + let peer_id = key.into_preimage(); + self.queued_events + .push_back(ToSwarm::NewExternalAddrOfPeer { + peer_id, + address: value.first().clone(), + }); let event = Event::RoutingUpdated { bucket_range: self .kbuckets .bucket(&key) .map(|b| b.range()) .expect("Self to never be applied from pending."), - peer: key.into_preimage(), + peer: peer_id, is_new_peer: true, addresses: value, old_peer: entry.evicted.map(|n| n.key.into_preimage()), @@ -2494,7 +2624,7 @@ where } } QueryPoolState::Waiting(Some((query, peer_id))) => { - let event = query.inner.info.to_request(query.id()); + let event = query.info.to_request(query.id()); // TODO: AddProvider requests yield no response, so the query completes // as soon as all requests have been sent. However, the handler should // better emit an event when the request has been sent (and report @@ -2503,7 +2633,7 @@ where if let QueryInfo::AddProvider { phase: AddProviderPhase::AddProvider { .. }, .. - } = &query.inner.info + } = &query.info { query.on_success(&peer_id, vec![]) } @@ -2515,11 +2645,9 @@ where handler: NotifyHandler::Any, }); } else if &peer_id != self.kbuckets.local_key().preimage() { - query.inner.pending_rpcs.push((peer_id, event)); + query.pending_rpcs.push((peer_id, event)); self.queued_events.push_back(ToSwarm::Dial { - opts: DialOpts::peer_id(peer_id) - .condition(dial_opts::PeerCondition::NotDialing) - .build(), + opts: DialOpts::peer_id(peer_id).build(), }); } } @@ -2555,11 +2683,24 @@ where } FromSwarm::DialFailure(dial_failure) => self.on_dial_failure(dial_failure), FromSwarm::AddressChange(address_change) => self.on_address_change(address_change), + FromSwarm::NewListenAddr(_) if self.connected_peers.is_empty() => { + // A new listen addr was just discovered and we have no connected peers, + // it can mean that our network interfaces were not up but they are now + // so it might be a good idea to trigger a bootstrap. + self.bootstrap_status.trigger(); + } _ => {} } } } +/// Peer Info combines a Peer ID with a set of multiaddrs that the peer is listening on. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PeerInfo { + pub peer_id: PeerId, + pub addrs: Vec, +} + /// A quorum w.r.t. the configured replication factor specifies the minimum /// number of distinct nodes that must be successfully contacted in order /// for a query to succeed. @@ -2603,7 +2744,6 @@ pub struct PeerRecord { #[allow(clippy::large_enum_variant)] pub enum Event { /// An inbound request has been received and handled. - // // Note on the difference between 'request' and 'query': A request is a // single request-response style exchange with a single remote peer. A query // is made of multiple requests across multiple remote peers. @@ -2904,14 +3044,14 @@ pub type GetClosestPeersResult = Result #[derive(Debug, Clone)] pub struct GetClosestPeersOk { pub key: Vec, - pub peers: Vec, + pub peers: Vec, } /// The error result of [`Behaviour::get_closest_peers`]. #[derive(Debug, Clone, Error)] pub enum GetClosestPeersError { #[error("the request timed out")] - Timeout { key: Vec, peers: Vec }, + Timeout { key: Vec, peers: Vec }, } impl GetClosestPeersError { @@ -3019,31 +3159,6 @@ impl From, Addresses>> for KadPeer { } } -////////////////////////////////////////////////////////////////////////////// -// Internal query state - -struct QueryInner { - /// The query-specific state. - info: QueryInfo, - /// Addresses of peers discovered during a query. - addresses: FnvHashMap>, - /// A map of pending requests to peers. - /// - /// A request is pending if the targeted peer is not currently connected - /// and these requests are sent as soon as a connection to the peer is established. - pending_rpcs: SmallVec<[(PeerId, HandlerIn); K_VALUE.get()]>, -} - -impl QueryInner { - fn new(info: QueryInfo) -> Self { - QueryInner { - info, - addresses: Default::default(), - pending_rpcs: SmallVec::default(), - } - } -} - /// The context of a [`QueryInfo::AddProvider`] query. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum AddProviderContext { @@ -3093,6 +3208,8 @@ pub enum QueryInfo { key: Vec, /// Current index of events. step: ProgressStep, + /// If required, `num_results` specifies expected responding peers + num_results: Option, }, /// A (repeated) query initiated by [`Behaviour::get_providers`]. @@ -3229,17 +3346,17 @@ pub enum PutRecordPhase { /// A mutable reference to a running query. pub struct QueryMut<'a> { - query: &'a mut Query, + query: &'a mut Query, } -impl<'a> QueryMut<'a> { +impl QueryMut<'_> { pub fn id(&self) -> QueryId { self.query.id() } /// Gets information about the type and state of the query. pub fn info(&self) -> &QueryInfo { - &self.query.inner.info + &self.query.info } /// Gets execution statistics about the query. @@ -3259,17 +3376,17 @@ impl<'a> QueryMut<'a> { /// An immutable reference to a running query. pub struct QueryRef<'a> { - query: &'a Query, + query: &'a Query, } -impl<'a> QueryRef<'a> { +impl QueryRef<'_> { pub fn id(&self) -> QueryId { self.query.id() } /// Gets information about the type and state of the query. pub fn info(&self) -> &QueryInfo { - &self.query.inner.info + &self.query.info } /// Gets execution statistics about the query. diff --git a/protocols/kad/src/behaviour/test.rs b/protocols/kad/src/behaviour/test.rs index 522eebcba92..ab8c980c30c 100644 --- a/protocols/kad/src/behaviour/test.rs +++ b/protocols/kad/src/behaviour/test.rs @@ -20,33 +20,25 @@ #![cfg(test)] -use super::*; - -use crate::kbucket::Distance; -use crate::record::{store::MemoryStore, Key}; -use crate::{K_VALUE, SHA_256_MH}; use futures::{executor::block_on, future::poll_fn, prelude::*}; use futures_timer::Delay; use libp2p_core::{ - connection::ConnectedPoint, - multiaddr::{multiaddr, Multiaddr, Protocol}, + multiaddr::{multiaddr, Protocol}, multihash::Multihash, transport::MemoryTransport, - upgrade, Endpoint, Transport, + upgrade, Transport, }; use libp2p_identity as identity; -use libp2p_identity::PeerId; use libp2p_noise as noise; -use libp2p_swarm::behaviour::ConnectionEstablished; -use libp2p_swarm::{self as swarm, ConnectionId, Swarm, SwarmEvent}; +use libp2p_swarm::{self as swarm, Swarm, SwarmEvent}; use libp2p_yamux as yamux; use quickcheck::*; use rand::{random, rngs::StdRng, thread_rng, Rng, SeedableRng}; -use std::{ - collections::{HashMap, HashSet}, - num::NonZeroUsize, - time::Duration, - u64, + +use super::*; +use crate::{ + record::{store::MemoryStore, Key}, + K_VALUE, PROTOCOL_NAME, SHA_256_MH, }; type TestSwarm = Swarm>; @@ -72,8 +64,7 @@ fn build_node_with_config(cfg: Config) -> (Multiaddr, TestSwarm) { transport, behaviour, local_id, - swarm::Config::with_async_std_executor() - .with_idle_connection_timeout(Duration::from_secs(5)), + swarm::Config::with_async_std_executor(), ); let address: Multiaddr = Protocol::Memory(random::()).into(); @@ -173,7 +164,11 @@ fn bootstrap() { // or smaller than K_VALUE. let num_group = rng.gen_range(1..(num_total % K_VALUE.get()) + 2); - let mut cfg = Config::default(); + let mut cfg = Config::new(PROTOCOL_NAME); + // Disabling periodic bootstrap and automatic bootstrap to prevent the bootstrap from + // triggering automatically. + cfg.set_periodic_bootstrap_interval(None); + cfg.set_automatic_bootstrap_throttle(None); if rng.gen() { cfg.disjoint_query_paths(true); } @@ -252,7 +247,12 @@ fn query_iter() { fn run(rng: &mut impl Rng) { let num_total = rng.gen_range(2..20); - let mut swarms = build_connected_nodes(num_total, 1) + let mut config = Config::new(PROTOCOL_NAME); + // Disabling periodic bootstrap and automatic bootstrap to prevent the bootstrap from + // triggering automatically. + config.set_periodic_bootstrap_interval(None); + config.set_automatic_bootstrap_throttle(None); + let mut swarms = build_connected_nodes_with_config(num_total, 1, config) .into_iter() .map(|(_a, s)| s) .collect::>(); @@ -266,7 +266,7 @@ fn query_iter() { match swarms[0].behaviour_mut().query(&qid) { Some(q) => match q.info() { - QueryInfo::GetClosestPeers { key, step } => { + QueryInfo::GetClosestPeers { key, step, .. } => { assert_eq!(&key[..], search_target.to_bytes().as_slice()); assert_eq!(usize::from(step.count), 1); } @@ -297,9 +297,11 @@ fn query_iter() { assert_eq!(&ok.key[..], search_target.to_bytes().as_slice()); assert_eq!(swarm_ids[i], expected_swarm_id); assert_eq!(swarm.behaviour_mut().queries.size(), 0); - assert!(expected_peer_ids.iter().all(|p| ok.peers.contains(p))); + let peer_ids = + ok.peers.into_iter().map(|p| p.peer_id).collect::>(); + assert!(expected_peer_ids.iter().all(|p| peer_ids.contains(p))); let key = kbucket::Key::new(ok.key); - assert_eq!(expected_distances, distances(&key, ok.peers)); + assert_eq!(expected_distances, distances(&key, peer_ids)); return Poll::Ready(()); } // Ignore any other event. @@ -321,9 +323,7 @@ fn query_iter() { #[test] fn unresponsive_not_returned_direct() { - let _ = tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); // Build one node. It contains fake addresses to non-existing nodes. We ask it to find a // random peer. We make sure that no fake address is returned. @@ -411,7 +411,69 @@ fn unresponsive_not_returned_indirect() { }))) => { assert_eq!(&ok.key[..], search_target.to_bytes().as_slice()); assert_eq!(ok.peers.len(), 1); - assert_eq!(ok.peers[0], first_peer_id); + assert_eq!(ok.peers[0].peer_id, first_peer_id); + return Poll::Ready(()); + } + // Ignore any other event. + Poll::Ready(Some(_)) => (), + e @ Poll::Ready(_) => panic!("Unexpected return value: {e:?}"), + Poll::Pending => break, + } + } + } + + Poll::Pending + })) +} + +// Test the result of get_closest_peers with different num_results +// Note that the result is capped after exceeds K_VALUE +#[test] +fn get_closest_with_different_num_results() { + let k_value = K_VALUE.get(); + for replication_factor in [5, k_value / 2, k_value] { + for num_results in k_value / 2..k_value * 2 { + get_closest_with_different_num_results_inner(num_results, replication_factor) + } + } +} + +fn get_closest_with_different_num_results_inner(num_results: usize, replication_factor: usize) { + let k_value = K_VALUE.get(); + let num_of_nodes = 3 * k_value; + let mut cfg = Config::new(PROTOCOL_NAME); + cfg.set_replication_factor(NonZeroUsize::new(replication_factor).unwrap()); + let swarms = build_connected_nodes_with_config(num_of_nodes, replication_factor - 1, cfg); + + let mut swarms = swarms + .into_iter() + .map(|(_addr, swarm)| swarm) + .collect::>(); + + // Ask first to search a random value. + let search_target = PeerId::random(); + let Some(num_results_nonzero) = std::num::NonZeroUsize::new(num_results) else { + panic!("Unexpected NonZeroUsize val of {num_results}"); + }; + swarms[0] + .behaviour_mut() + .get_n_closest_peers(search_target, num_results_nonzero); + + block_on(poll_fn(move |ctx| { + for swarm in &mut swarms { + loop { + match swarm.poll_next_unpin(ctx) { + Poll::Ready(Some(SwarmEvent::Behaviour(Event::OutboundQueryProgressed { + result: QueryResult::GetClosestPeers(Ok(ok)), + .. + }))) => { + assert_eq!(&ok.key[..], search_target.to_bytes().as_slice()); + if num_results > k_value { + assert_eq!(ok.peers.len(), k_value, "Failed with replication_factor: {replication_factor}, num_results: {num_results}"); + } else { + assert_eq!(ok.peers.len(), num_results, "Failed with replication_factor: {replication_factor}, num_results: {num_results}"); + } + return Poll::Ready(()); } // Ignore any other event. @@ -498,8 +560,12 @@ fn put_record() { // At least 4 nodes, 1 under test + 3 bootnodes. let num_total = usize::max(4, replication_factor.get() * 2); - let mut config = Config::default(); + let mut config = Config::new(PROTOCOL_NAME); config.set_replication_factor(replication_factor); + // Disabling periodic bootstrap and automatic bootstrap to prevent the bootstrap from + // triggering automatically. + config.set_periodic_bootstrap_interval(None); + config.set_automatic_bootstrap_throttle(None); if rng.gen() { config.disjoint_query_paths(true); } @@ -867,8 +933,12 @@ fn add_provider() { // At least 4 nodes, 1 under test + 3 bootnodes. let num_total = usize::max(4, replication_factor.get() * 2); - let mut config = Config::default(); + let mut config = Config::new(PROTOCOL_NAME); config.set_replication_factor(replication_factor); + // Disabling periodic bootstrap and automatic bootstrap to prevent the bootstrap from + // triggering automatically. + config.set_periodic_bootstrap_interval(None); + config.set_automatic_bootstrap_throttle(None); if rng.gen() { config.disjoint_query_paths(true); } @@ -1083,17 +1153,21 @@ fn exp_decr_expiration_overflow() { } // Right shifting a u64 by >63 results in a panic. - prop_no_panic(Config::default().record_ttl.unwrap(), 64); + prop_no_panic(Config::new(PROTOCOL_NAME).record_ttl.unwrap(), 64); quickcheck(prop_no_panic as fn(_, _)) } #[test] fn disjoint_query_does_not_finish_before_all_paths_did() { - let mut config = Config::default(); + let mut config = Config::new(PROTOCOL_NAME); config.disjoint_query_paths(true); // I.e. setting the amount disjoint paths to be explored to 2. config.set_parallelism(NonZeroUsize::new(2).unwrap()); + // Disabling periodic bootstrap and automatic bootstrap to prevent the bootstrap from triggering + // automatically. + config.set_periodic_bootstrap_interval(None); + config.set_automatic_bootstrap_throttle(None); let mut alice = build_node_with_config(config); let mut trudy = build_node(); // Trudy the intrudor, an adversary. @@ -1179,7 +1253,7 @@ fn disjoint_query_does_not_finish_before_all_paths_did() { .behaviour() .queries .iter() - .for_each(|q| match &q.inner.info { + .for_each(|q| match &q.info { QueryInfo::GetRecord { step, .. } => { assert_eq!(usize::from(step.count), 2); } @@ -1238,7 +1312,7 @@ fn disjoint_query_does_not_finish_before_all_paths_did() { /// the routing table with `BucketInserts::Manual`. #[test] fn manual_bucket_inserts() { - let mut cfg = Config::default(); + let mut cfg = Config::new(PROTOCOL_NAME); cfg.set_kbucket_inserts(BucketInserts::Manual); // 1 -> 2 -> [3 -> ...] let mut swarms = build_connected_nodes_with_config(3, 1, cfg); @@ -1302,9 +1376,10 @@ fn network_behaviour_on_address_change() { let endpoint = ConnectedPoint::Dialer { address: old_address.clone(), role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }; - // Mimick a connection being established. + // Mimic a connection being established. kademlia.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { peer_id: remote_peer_id, connection_id, @@ -1326,7 +1401,7 @@ fn network_behaviour_on_address_change() { .unwrap() .is_empty()); - // Mimick the connection handler confirming the protocol for + // Mimic the connection handler confirming the protocol for // the test connection, so that the peer is added to the routing table. kademlia.on_connection_handler_event( remote_peer_id, @@ -1352,10 +1427,12 @@ fn network_behaviour_on_address_change() { old: &ConnectedPoint::Dialer { address: old_address, role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }, new: &ConnectedPoint::Dialer { address: new_address.clone(), role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, }, })); diff --git a/protocols/kad/src/bootstrap.rs b/protocols/kad/src/bootstrap.rs new file mode 100644 index 00000000000..d6576a3ef54 --- /dev/null +++ b/protocols/kad/src/bootstrap.rs @@ -0,0 +1,363 @@ +use std::{ + task::{Context, Poll, Waker}, + time::Duration, +}; + +use futures::FutureExt; +use futures_timer::Delay; + +/// Default value chosen at ``. +pub(crate) const DEFAULT_AUTOMATIC_THROTTLE: Duration = Duration::from_millis(500); + +#[derive(Debug)] +pub(crate) struct Status { + /// If the user did not disable periodic bootstrap (by providing `None` for + /// `periodic_interval`) this is the periodic interval and the delay of the current period. + /// When `Delay` finishes, a bootstrap will be triggered and the `Delay` will be reset. + interval_and_delay: Option<(Duration, Delay)>, + + /// Configured duration to wait before triggering a bootstrap when a new peer + /// is inserted in the routing table. `None` if automatic bootstrap is disabled. + automatic_throttle: Option, + /// Timer that will be set (if automatic bootstrap is not disabled) when a new peer is inserted + /// in the routing table. When it finishes, it will trigger a bootstrap and will be set to + /// `None` again. If an other new peer is inserted in the routing table before this timer + /// finishes, the timer is reset. + throttle_timer: Option, + + /// Number of bootstrap requests currently in progress. We ensure neither periodic bootstrap + /// or automatic bootstrap trigger new requests when there is still some running. + current_bootstrap_requests: usize, + /// Waker to wake up the `poll` method if progress is ready to be made. + waker: Option, +} + +impl Status { + pub(crate) fn new( + periodic_interval: Option, + automatic_throttle: Option, + ) -> Self { + Self { + interval_and_delay: periodic_interval.map(|interval| (interval, Delay::new(interval))), + waker: None, + automatic_throttle, + throttle_timer: None, + current_bootstrap_requests: 0, + } + } + + /// Trigger a bootstrap now or after the configured `automatic_throttle` if configured. + pub(crate) fn trigger(&mut self) { + // Registering `self.throttle_timer` means scheduling a bootstrap. + // A bootstrap will be triggered when `self.throttle_timer` finishes. + // A `throttle_timer` is useful to not trigger a batch of bootstraps when a + // batch of peers is inserted into the routing table. + if let Some(throttle_duration) = self.automatic_throttle { + self.throttle_timer = Some(throttle_duration.into()); + } else { + // The user disabled bootstrapping on new peer in the routing table. + } + + // Waking up the waker that could have been registered. + if let Some(waker) = self.waker.take() { + waker.wake() + } + } + + pub(crate) fn reset_timers(&mut self) { + // Canceling the `throttle_timer` if any and resetting the `delay` if any. + self.throttle_timer = None; + + if let Some((interval, delay)) = self.interval_and_delay.as_mut() { + delay.reset(*interval); + } + } + + pub(crate) fn on_started(&mut self) { + // No periodic or automatic bootstrap will be triggered as long as + // `self.current_bootstrap_requests > 0` but the user could still manually + // trigger a bootstrap. + self.current_bootstrap_requests += 1; + + // Resetting the Status timers since a bootstrap request is being triggered right now. + self.reset_timers(); + } + + pub(crate) fn on_finish(&mut self) { + if let Some(value) = self.current_bootstrap_requests.checked_sub(1) { + self.current_bootstrap_requests = value; + } else { + debug_assert!( + false, + "Could not decrement current_bootstrap_requests because it's already 0" + ); + } + + // Waking up the waker that could have been registered. + if let Some(waker) = self.waker.take() { + waker.wake(); + } + } + + pub(crate) fn poll_next_bootstrap(&mut self, cx: &mut Context<'_>) -> Poll<()> { + if self.current_bootstrap_requests > 0 { + // Some bootstrap request(s) is(are) currently running. + self.waker = Some(cx.waker().clone()); + return Poll::Pending; + } + + if let Some(throttle_delay) = &mut self.throttle_timer { + // A `throttle_timer` has been registered. It means one or more peers have been + // inserted into the routing table and that a bootstrap request should be triggered. + // However, to not risk cascading bootstrap requests, we wait a little time to ensure + // the user will not add more peers in the routing table in the next "throttle_timer" + // remaining. + if throttle_delay.poll_unpin(cx).is_ready() { + // The `throttle_timer` is finished, triggering bootstrap right now. + // The call to `on_started` will reset `throttle_delay`. + return Poll::Ready(()); + } + + // The `throttle_timer` is not finished but the periodic interval for triggering + // bootstrap might be reached. + } else { + // No new peer has recently been inserted into the routing table or automatic bootstrap + // is disabled. + } + + // Checking if the user has enabled the periodic bootstrap feature. + if let Some((_, delay)) = self.interval_and_delay.as_mut() { + if let Poll::Ready(()) = delay.poll_unpin(cx) { + // It is time to run the periodic bootstrap. + // The call to `on_started` will reset `delay`. + return Poll::Ready(()); + } + } else { + // The user disabled periodic bootstrap. + } + + // Registering the `waker` so that we can wake up when calling + // `on_new_peer_in_routing_table`. + self.waker = Some(cx.waker().clone()); + Poll::Pending + } + + #[cfg(test)] + async fn next(&mut self) { + std::future::poll_fn(|cx| self.poll_next_bootstrap(cx)).await + } +} + +/// Simple enum to indicate when the throttle timer resolves. +/// A dedicated `Immediate` variant is necessary because creating +/// `Delay::new(Duration::ZERO)` does not always actually resolve +/// immediately. +#[derive(Debug)] +enum ThrottleTimer { + Immediate, + Delay(Delay), +} + +impl From for ThrottleTimer { + fn from(value: Duration) -> Self { + if value.is_zero() { + Self::Immediate + } else { + Self::Delay(Delay::new(value)) + } + } +} + +impl futures::Future for ThrottleTimer { + type Output = (); + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.get_mut() { + Self::Immediate => Poll::Ready(()), + Self::Delay(delay) => delay.poll_unpin(cx), + } + } +} + +#[cfg(test)] +mod tests { + use web_time::Instant; + + use super::*; + + const MS_5: Duration = Duration::from_millis(5); + const MS_100: Duration = Duration::from_millis(100); + + fn do_bootstrap(status: &mut Status) { + status.on_started(); + status.on_finish(); + } + + async fn await_and_do_bootstrap(status: &mut Status) { + status.next().await; + do_bootstrap(status); + } + + #[async_std::test] + async fn immediate_automatic_bootstrap_is_triggered_immediately() { + let mut status = Status::new(Some(Duration::from_secs(1)), Some(Duration::ZERO)); + + await_and_do_bootstrap(&mut status).await; // Wait for periodic bootstrap + + assert!( + status.next().now_or_never().is_none(), + "bootstrap to not be triggered immediately because periodic bootstrap is in ~1s" + ); + + status.trigger(); // Connected to a new peer though! + assert!( + status.next().now_or_never().is_some(), + "bootstrap to be triggered immediately because we connected to a new peer" + ); + + assert!( + async_std::future::timeout(Duration::from_millis(500), status.next()) + .await + .is_ok(), + "bootstrap to be triggered in less then the configured delay because we connected to a new peer" + ); + } + + #[async_std::test] + async fn delayed_automatic_bootstrap_is_triggered_before_periodic_bootstrap() { + let mut status = Status::new(Some(Duration::from_secs(1)), Some(MS_5)); + + await_and_do_bootstrap(&mut status).await; // Wait for periodic bootstrap + + assert!( + status.next().now_or_never().is_none(), + "bootstrap to not be triggered immediately because periodic bootstrap is in ~1s" + ); + + status.trigger(); // Connected to a new peer though! + assert!( + status.next().now_or_never().is_none(), + "bootstrap to not be triggered immediately because throttle is 5ms" + ); + + assert!( + async_std::future::timeout(MS_5 * 2, status.next()) + .await + .is_ok(), + "bootstrap to be triggered in less then the configured periodic delay because we connected to a new peer" + ); + } + + #[test] + fn given_no_periodic_bootstrap_and_immediate_automatic_bootstrap_try_on_next_connection() { + let mut status = Status::new(None, Some(Duration::ZERO)); + + // User manually triggered a bootstrap + do_bootstrap(&mut status); + + status.trigger(); // Connected to a new peer though! + + assert!( + status.next().now_or_never().is_some(), + "bootstrap to be triggered immediately because we connected to a new peer" + ) + } + + #[async_std::test] + async fn given_periodic_bootstrap_when_routing_table_updated_then_wont_bootstrap_until_next_interval( + ) { + let mut status = Status::new(Some(MS_100), Some(MS_5)); + + status.trigger(); + + let start = Instant::now(); + await_and_do_bootstrap(&mut status).await; + let elapsed = Instant::now().duration_since(start); + + assert!(elapsed < MS_5 * 2); + + let start = Instant::now(); + await_and_do_bootstrap(&mut status).await; + let elapsed = Instant::now().duration_since(start); + + assert!(elapsed > MS_100); + } + + #[async_std::test] + async fn given_no_periodic_bootstrap_and_automatic_bootstrap_when_new_entry_then_will_bootstrap( + ) { + let mut status = Status::new(None, Some(Duration::ZERO)); + + status.trigger(); + + status.next().await; + } + + #[async_std::test] + async fn given_periodic_bootstrap_and_no_automatic_bootstrap_triggers_periodically() { + let mut status = Status::new(Some(MS_100), None); + + let start = Instant::now(); + for i in 1..6 { + await_and_do_bootstrap(&mut status).await; + + let elapsed = Instant::now().duration_since(start); + + // Subtract 10ms to avoid flakes. + assert!(elapsed > (i * MS_100 - Duration::from_millis(10))); + } + } + + #[async_std::test] + async fn given_no_periodic_bootstrap_and_automatic_bootstrap_reset_throttle_when_multiple_peers( + ) { + let mut status = Status::new(None, Some(MS_100)); + + status.trigger(); + for _ in 0..10 { + Delay::new(MS_100 / 2).await; + // should reset throttle_timer + status.trigger(); + } + assert!( + status.next().now_or_never().is_none(), + "bootstrap to not be triggered immediately because throttle has been reset" + ); + + Delay::new(MS_100 - MS_5).await; + + assert!( + async_std::future::timeout(MS_5*2, status.next()) + .await + .is_ok(), + "bootstrap to be triggered in the configured throttle delay because we connected to a new peer" + ); + } + + #[async_std::test] + async fn given_periodic_bootstrap_and_no_automatic_bootstrap_manually_triggering_prevent_periodic( + ) { + let mut status = Status::new(Some(MS_100), None); + + // first manually triggering + status.on_started(); + // second manually triggering + status.on_started(); + // one finishes + status.on_finish(); + + assert!( + async_std::future::timeout(10 * MS_100, status.next()) + .await + .is_err(), + "periodic bootstrap to never be triggered because one is still being run" + ); + + status.on_finish(); // all manual bootstrap finished + + assert!( + status.next().now_or_never().is_some(), + "bootstrap to be triggered immediately because no more bootstrap requests are running" + ) + } +} diff --git a/protocols/kad/src/handler.rs b/protocols/kad/src/handler.rs index 318261d8d21..6837e3d499e 100644 --- a/protocols/kad/src/handler.rs +++ b/protocols/kad/src/handler.rs @@ -18,27 +18,33 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::behaviour::Mode; -use crate::protocol::{ - KadInStreamSink, KadOutStreamSink, KadPeer, KadRequestMsg, KadResponseMsg, ProtocolConfig, +use std::{ + collections::VecDeque, + error, fmt, io, + marker::PhantomData, + pin::Pin, + task::{Context, Poll, Waker}, + time::Duration, }; -use crate::record::{self, Record}; -use crate::QueryId; + use either::Either; -use futures::channel::oneshot; -use futures::prelude::*; -use futures::stream::SelectAll; +use futures::{channel::oneshot, prelude::*, stream::SelectAll}; use libp2p_core::{upgrade, ConnectedPoint}; use libp2p_identity::PeerId; -use libp2p_swarm::handler::{ConnectionEvent, FullyNegotiatedInbound, FullyNegotiatedOutbound}; use libp2p_swarm::{ + handler::{ConnectionEvent, FullyNegotiatedInbound, FullyNegotiatedOutbound}, ConnectionHandler, ConnectionHandlerEvent, Stream, StreamUpgradeError, SubstreamProtocol, SupportedProtocols, }; -use std::collections::VecDeque; -use std::task::Waker; -use std::time::Duration; -use std::{error, fmt, io, marker::PhantomData, pin::Pin, task::Context, task::Poll}; + +use crate::{ + behaviour::Mode, + protocol::{ + KadInStreamSink, KadOutStreamSink, KadPeer, KadRequestMsg, KadResponseMsg, ProtocolConfig, + }, + record::{self, Record}, + QueryId, +}; const MAX_NUM_STREAMS: usize = 32; @@ -60,7 +66,8 @@ pub struct Handler { next_connec_unique_id: UniqueConnecId, /// List of active outbound streams. - outbound_substreams: futures_bounded::FuturesMap>>, + outbound_substreams: + futures_bounded::FuturesTupleSet>, QueryId>, /// Contains one [`oneshot::Sender`] per outbound stream that we have requested. pending_streams: @@ -453,7 +460,7 @@ impl Handler { remote_peer_id, next_connec_unique_id: UniqueConnecId(0), inbound_substreams: Default::default(), - outbound_substreams: futures_bounded::FuturesMap::new( + outbound_substreams: futures_bounded::FuturesTupleSet::new( Duration::from_secs(10), MAX_NUM_STREAMS, ), @@ -497,10 +504,12 @@ impl Handler { >, ) { // If `self.allow_listening` is false, then we produced a `DeniedUpgrade` and `protocol` - // is a `Void`. + // is a `Infallible`. let protocol = match protocol { future::Either::Left(p) => p, - future::Either::Right(p) => void::unreachable(p), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + future::Either::Right(p) => libp2p_core::util::unreachable(p), }; if self.protocol_status.is_none() { @@ -547,37 +556,42 @@ impl Handler { }); } - /// Takes the given [`KadRequestMsg`] and composes it into an outbound request-response protocol handshake using a [`oneshot::channel`]. + /// Takes the given [`KadRequestMsg`] and composes it into an outbound request-response protocol + /// handshake using a [`oneshot::channel`]. fn queue_new_stream(&mut self, id: QueryId, msg: KadRequestMsg) { let (sender, receiver) = oneshot::channel(); self.pending_streams.push_back(sender); - let result = self.outbound_substreams.try_push(id, async move { - let mut stream = receiver - .await - .map_err(|_| io::Error::from(io::ErrorKind::BrokenPipe))? - .map_err(|e| match e { - StreamUpgradeError::Timeout => io::ErrorKind::TimedOut.into(), - StreamUpgradeError::Apply(e) => e, - StreamUpgradeError::NegotiationFailed => { - io::Error::new(io::ErrorKind::ConnectionRefused, "protocol not supported") - } - StreamUpgradeError::Io(e) => e, - })?; - - let has_answer = !matches!(msg, KadRequestMsg::AddProvider { .. }); - - stream.send(msg).await?; - stream.close().await?; - - if !has_answer { - return Ok(None); - } + let result = self.outbound_substreams.try_push( + async move { + let mut stream = receiver + .await + .map_err(|_| io::Error::from(io::ErrorKind::BrokenPipe))? + .map_err(|e| match e { + StreamUpgradeError::Timeout => io::ErrorKind::TimedOut.into(), + StreamUpgradeError::Apply(e) => e, + StreamUpgradeError::NegotiationFailed => io::Error::new( + io::ErrorKind::ConnectionRefused, + "protocol not supported", + ), + StreamUpgradeError::Io(e) => e, + })?; + + let has_answer = !matches!(msg, KadRequestMsg::AddProvider { .. }); + + stream.send(msg).await?; + stream.close().await?; + + if !has_answer { + return Ok(None); + } - let msg = stream.next().await.ok_or(io::ErrorKind::UnexpectedEof)??; + let msg = stream.next().await.ok_or(io::ErrorKind::UnexpectedEof)??; - Ok(Some(msg)) - }); + Ok(Some(msg)) + }, + id, + ); debug_assert!( result.is_ok(), @@ -728,15 +742,15 @@ impl ConnectionHandler for Handler { } match self.outbound_substreams.poll_unpin(cx) { - Poll::Ready((query, Ok(Ok(Some(response))))) => { + Poll::Ready((Ok(Ok(Some(response))), query_id)) => { return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - process_kad_response(response, query), + process_kad_response(response, query_id), )) } - Poll::Ready((_, Ok(Ok(None)))) => { + Poll::Ready((Ok(Ok(None)), _)) => { continue; } - Poll::Ready((query_id, Ok(Err(e)))) => { + Poll::Ready((Ok(Err(e)), query_id)) => { return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( HandlerEvent::QueryError { error: HandlerQueryErr::Io(e), @@ -744,7 +758,7 @@ impl ConnectionHandler for Handler { }, )) } - Poll::Ready((query_id, Err(_timeout))) => { + Poll::Ready((Err(_timeout), query_id)) => { return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( HandlerEvent::QueryError { error: HandlerQueryErr::Io(io::ErrorKind::TimedOut.into()), @@ -1053,9 +1067,9 @@ fn process_kad_response(event: KadResponseMsg, query_id: QueryId) -> HandlerEven #[cfg(test)] mod tests { - use super::*; use quickcheck::{Arbitrary, Gen}; - use tracing_subscriber::EnvFilter; + + use super::*; impl Arbitrary for ProtocolStatus { fn arbitrary(g: &mut Gen) -> Self { @@ -1068,9 +1082,7 @@ mod tests { #[test] fn compute_next_protocol_status_test() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); fn prop(now_supported: bool, current: Option) { let new = compute_new_protocol_status(now_supported, current); diff --git a/protocols/kad/src/jobs.rs b/protocols/kad/src/jobs.rs index f1631ed6ad1..56b3e080d96 100644 --- a/protocols/kad/src/jobs.rs +++ b/protocols/kad/src/jobs.rs @@ -25,12 +25,11 @@ //! To ensure persistence of records in the DHT, a Kademlia node //! must periodically (re-)publish and (re-)replicate its records: //! -//! 1. (Re-)publishing: The original publisher or provider of a record -//! must regularly re-publish in order to prolong the expiration. +//! 1. (Re-)publishing: The original publisher or provider of a record must regularly re-publish +//! in order to prolong the expiration. //! -//! 2. (Re-)replication: Every node storing a replica of a record must -//! regularly re-replicate it to the closest nodes to the key in -//! order to ensure the record is present at these nodes. +//! 2. (Re-)replication: Every node storing a replica of a record must regularly re-replicate it +//! to the closest nodes to the key in order to ensure the record is present at these nodes. //! //! Re-publishing primarily ensures persistence of the record beyond its //! initial TTL, for as long as the publisher stores (or provides) the record, @@ -41,11 +40,10 @@ //! //! This module implements two periodic jobs: //! -//! * [`PutRecordJob`]: For (re-)publication and (re-)replication of -//! regular (value-)records. +//! * [`PutRecordJob`]: For (re-)publication and (re-)replication of regular (value-)records. //! -//! * [`AddProviderJob`]: For (re-)publication of provider records. -//! Provider records currently have no separate replication mechanism. +//! * [`AddProviderJob`]: For (re-)publication of provider records. Provider records currently +//! have no separate replication mechanism. //! //! A periodic job is driven like a `Future` or `Stream` by `poll`ing it. //! Once a job starts running it emits records to send to the `k` closest @@ -61,16 +59,20 @@ //! > to the size of all stored records. As a job runs, the records are moved //! > out of the job to the consumer, where they can be dropped after being sent. -use crate::record::{self, store::RecordStore, ProviderRecord, Record}; +use std::{ + collections::HashSet, + pin::Pin, + task::{Context, Poll}, + time::Duration, + vec, +}; + use futures::prelude::*; use futures_timer::Delay; -use instant::Instant; use libp2p_identity::PeerId; -use std::collections::HashSet; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::time::Duration; -use std::vec; +use web_time::Instant; + +use crate::record::{self, store::RecordStore, ProviderRecord, Record}; /// The maximum number of queries towards which background jobs /// are allowed to start new queries on an invocation of @@ -203,7 +205,7 @@ impl PutRecordJob { T: RecordStore, { if self.inner.check_ready(cx, now) { - let publish = self.next_publish.map_or(false, |t_pub| now >= t_pub); + let publish = self.next_publish.is_some_and(|t_pub| now >= t_pub); let records = store .records() .filter_map(|r| { @@ -335,12 +337,13 @@ impl AddProviderJob { #[cfg(test)] mod tests { - use super::*; - use crate::record::store::MemoryStore; use futures::{executor::block_on, future::poll_fn}; use quickcheck::*; use rand::Rng; + use super::*; + use crate::record::store::MemoryStore; + fn rand_put_record_job() -> PutRecordJob { let mut rng = rand::thread_rng(); let id = PeerId::random(); diff --git a/protocols/kad/src/kbucket.rs b/protocols/kad/src/kbucket.rs index b42806fcf3c..f32c34e9bb7 100644 --- a/protocols/kad/src/kbucket.rs +++ b/protocols/kad/src/kbucket.rs @@ -72,17 +72,51 @@ mod entry; #[allow(clippy::assign_op_pattern)] mod key; -pub use bucket::NodeStatus; -pub use entry::*; +use std::{collections::VecDeque, num::NonZeroUsize, time::Duration}; -use arrayvec::{self, ArrayVec}; use bucket::KBucket; -use std::collections::VecDeque; -use std::time::{Duration, Instant}; +pub use bucket::NodeStatus; +pub use entry::*; +use smallvec::SmallVec; +use web_time::Instant; /// Maximum number of k-buckets. const NUM_BUCKETS: usize = 256; +/// The configuration for `KBucketsTable`. +#[derive(Debug, Clone, Copy)] +pub(crate) struct KBucketConfig { + /// Maximal number of nodes that a bucket can contain. + bucket_size: usize, + /// Specifies the duration after creation of a [`PendingEntry`] after which + /// it becomes eligible for insertion into a full bucket, replacing the + /// least-recently (dis)connected node. + pending_timeout: Duration, +} + +impl Default for KBucketConfig { + fn default() -> Self { + KBucketConfig { + bucket_size: K_VALUE.get(), + pending_timeout: Duration::from_secs(60), + } + } +} + +impl KBucketConfig { + /// Modifies the maximal number of nodes that a bucket can contain. + pub(crate) fn set_bucket_size(&mut self, bucket_size: NonZeroUsize) { + self.bucket_size = bucket_size.get(); + } + + /// Modifies the duration after creation of a [`PendingEntry`] after which + /// it becomes eligible for insertion into a full bucket, replacing the + /// least-recently (dis)connected node. + pub(crate) fn set_pending_timeout(&mut self, pending_timeout: Duration) { + self.pending_timeout = pending_timeout; + } +} + /// A `KBucketsTable` represents a Kademlia routing table. #[derive(Debug, Clone)] pub(crate) struct KBucketsTable { @@ -90,6 +124,8 @@ pub(crate) struct KBucketsTable { local_key: TKey, /// The buckets comprising the routing table. buckets: Vec>, + /// The maximal number of nodes that a bucket can contain. + bucket_size: usize, /// The list of evicted entries that have been replaced with pending /// entries since the last call to [`KBucketsTable::take_applied_pending`]. applied_pending: VecDeque>, @@ -140,7 +176,7 @@ impl BucketIndex { let lower = usize::pow(2, rem); let upper = usize::pow(2, rem + 1); bytes[31 - quot] = rng.gen_range(lower..upper) as u8; - Distance(U256::from(bytes)) + Distance(U256::from_big_endian(bytes.as_slice())) } } @@ -150,17 +186,12 @@ where TVal: Clone, { /// Creates a new, empty Kademlia routing table with entries partitioned - /// into buckets as per the Kademlia protocol. - /// - /// The given `pending_timeout` specifies the duration after creation of - /// a [`PendingEntry`] after which it becomes eligible for insertion into - /// a full bucket, replacing the least-recently (dis)connected node. - pub(crate) fn new(local_key: TKey, pending_timeout: Duration) -> Self { + /// into buckets as per the Kademlia protocol using the provided config. + pub(crate) fn new(local_key: TKey, config: KBucketConfig) -> Self { KBucketsTable { local_key, - buckets: (0..NUM_BUCKETS) - .map(|_| KBucket::new(pending_timeout)) - .collect(), + buckets: (0..NUM_BUCKETS).map(|_| KBucket::new(config)).collect(), + bucket_size: config.bucket_size, applied_pending: VecDeque::new(), } } @@ -172,17 +203,16 @@ where /// Returns an `Entry` for the given key, representing the state of the entry /// in the routing table. - pub(crate) fn entry<'a>(&'a mut self, key: &'a TKey) -> Entry<'a, TKey, TVal> { - let index = BucketIndex::new(&self.local_key.as_ref().distance(key)); - if let Some(i) = index { - let bucket = &mut self.buckets[i.get()]; - if let Some(applied) = bucket.apply_pending() { - self.applied_pending.push_back(applied) - } - Entry::new(bucket, key) - } else { - Entry::SelfEntry + /// + /// Returns `None` in case the key points to the local node. + pub(crate) fn entry<'a>(&'a mut self, key: &'a TKey) -> Option> { + let index = BucketIndex::new(&self.local_key.as_ref().distance(key))?; + + let bucket = &mut self.buckets[index.get()]; + if let Some(applied) = bucket.apply_pending() { + self.applied_pending.push_back(applied) } + Some(Entry::new(bucket, key)) } /// Returns an iterator over all buckets. @@ -247,14 +277,14 @@ where T: AsRef, { let distance = self.local_key.as_ref().distance(target); + let bucket_size = self.bucket_size; ClosestIter { target, iter: None, table: self, buckets_iter: ClosestBucketsIter::new(distance), - fmap: |b: &KBucket| -> ArrayVec<_, { K_VALUE.get() }> { - b.iter().map(|(n, _)| n.key.clone()).collect() - }, + fmap: |(n, _status): (&Node, NodeStatus)| n.key.clone(), + bucket_size, } } @@ -269,19 +299,17 @@ where TVal: Clone, { let distance = self.local_key.as_ref().distance(target); + let bucket_size = self.bucket_size; ClosestIter { target, iter: None, table: self, buckets_iter: ClosestBucketsIter::new(distance), - fmap: |b: &KBucket<_, TVal>| -> ArrayVec<_, { K_VALUE.get() }> { - b.iter() - .map(|(n, status)| EntryView { - node: n.clone(), - status, - }) - .collect() + fmap: |(n, status): (&Node, NodeStatus)| EntryView { + node: n.clone(), + status, }, + bucket_size, } } @@ -324,10 +352,12 @@ struct ClosestIter<'a, TTarget, TKey, TVal, TMap, TOut> { /// distance of the local key to the target. buckets_iter: ClosestBucketsIter, /// The iterator over the entries in the currently traversed bucket. - iter: Option>, + iter: Option>, /// The projection function / mapping applied on each bucket as /// it is encountered, producing the next `iter`ator. fmap: TMap, + /// The maximal number of nodes that a bucket can contain. + bucket_size: usize, } /// An iterator over the bucket indices, in the order determined by the `Distance` of @@ -345,7 +375,7 @@ enum ClosestBucketsIterState { /// The starting state of the iterator yields the first bucket index and /// then transitions to `ZoomIn`. Start(BucketIndex), - /// The iterator "zooms in" to to yield the next bucket cotaining nodes that + /// The iterator "zooms in" to yield the next bucket containing nodes that /// are incrementally closer to the local node but further from the `target`. /// These buckets are identified by a `1` in the corresponding bit position /// of the distance bit string. When bucket `0` is reached, the iterator @@ -429,41 +459,80 @@ where TTarget: AsRef, TKey: Clone + AsRef, TVal: Clone, - TMap: Fn(&KBucket) -> ArrayVec, + TMap: Fn((&Node, NodeStatus)) -> TOut, TOut: AsRef, { type Item = TOut; fn next(&mut self) -> Option { loop { - match &mut self.iter { - Some(iter) => match iter.next() { - Some(k) => return Some(k), - None => self.iter = None, - }, - None => { - if let Some(i) = self.buckets_iter.next() { - let bucket = &mut self.table.buckets[i.get()]; - if let Some(applied) = bucket.apply_pending() { - self.table.applied_pending.push_back(applied) - } - let mut v = (self.fmap)(bucket); - v.sort_by(|a, b| { - self.target - .as_ref() - .distance(a.as_ref()) - .cmp(&self.target.as_ref().distance(b.as_ref())) - }); - self.iter = Some(v.into_iter()); - } else { - return None; - } + let (mut buffer, bucket_index) = if let Some(mut iter) = self.iter.take() { + if let Some(next) = iter.next() { + self.iter = Some(iter); + return Some(next); } + + let bucket_index = self.buckets_iter.next()?; + + // Reusing the same buffer so if there were any allocation, it only happen once over + // a `ClosestIter` life. + iter.buffer.clear(); + + (iter.buffer, bucket_index) + } else { + let bucket_index = self.buckets_iter.next()?; + + // Allocation only occurs if `kbucket_size` is greater than `K_VALUE`. + (SmallVec::with_capacity(self.bucket_size), bucket_index) + }; + + let bucket = &mut self.table.buckets[bucket_index.get()]; + if let Some(applied) = bucket.apply_pending() { + self.table.applied_pending.push_back(applied) } + + buffer.extend( + bucket + .iter() + .take(self.bucket_size) + .map(|e| (self.fmap)(e)) + .map(Some), + ); + buffer.sort_by(|a, b| { + let a = a.as_ref().expect("just initialized"); + let b = b.as_ref().expect("just initialized"); + self.target + .as_ref() + .distance(a.as_ref()) + .cmp(&self.target.as_ref().distance(b.as_ref())) + }); + + self.iter = Some(ClosestIterBuffer::new(buffer)); } } } +struct ClosestIterBuffer { + buffer: SmallVec<[Option; K_VALUE.get()]>, + index: usize, +} + +impl ClosestIterBuffer { + fn new(buffer: SmallVec<[Option; K_VALUE.get()]>) -> Self { + Self { buffer, index: 0 } + } +} + +impl Iterator for ClosestIterBuffer { + type Item = TOut; + + fn next(&mut self) -> Option { + let entry = self.buffer.get_mut(self.index)?; + self.index += 1; + entry.take() + } +} + /// A reference to a bucket. pub struct KBucketRef<'a, TKey, TVal> { index: BucketIndex, @@ -493,12 +562,12 @@ where /// Returns true if the bucket has a pending node. pub fn has_pending(&self) -> bool { - self.bucket.pending().map_or(false, |n| !n.is_ready()) + self.bucket.pending().is_some_and(|n| !n.is_ready()) } /// Tests whether the given distance falls into this bucket. pub fn contains(&self, d: &Distance) -> bool { - BucketIndex::new(d).map_or(false, |i| i == self.index) + BucketIndex::new(d).is_some_and(|i| i == self.index) } /// Generates a random distance that falls into this bucket. @@ -525,29 +594,30 @@ where #[cfg(test)] mod tests { - use super::*; use libp2p_identity::PeerId; use quickcheck::*; + use super::*; + type TestTable = KBucketsTable; impl Arbitrary for TestTable { fn arbitrary(g: &mut Gen) -> TestTable { let local_key = Key::from(PeerId::random()); let timeout = Duration::from_secs(g.gen_range(1..360)); - let mut table = TestTable::new(local_key.clone().into(), timeout); + let mut config = KBucketConfig::default(); + config.set_pending_timeout(timeout); + let bucket_size = config.bucket_size; + let mut table = TestTable::new(local_key.into(), config); let mut num_total = g.gen_range(0..100); for (i, b) in &mut table.buckets.iter_mut().enumerate().rev() { let ix = BucketIndex(i); - let num = g.gen_range(0..usize::min(K_VALUE.get(), num_total) + 1); + let num = g.gen_range(0..usize::min(bucket_size, num_total) + 1); num_total -= num; for _ in 0..num { let distance = ix.rand_distance(&mut rand::thread_rng()); let key = local_key.for_distance(distance); - let node = Node { - key: key.clone(), - value: (), - }; + let node = Node { key, value: () }; let status = NodeStatus::arbitrary(g); match b.insert(node, status) { InsertResult::Inserted => {} @@ -563,7 +633,9 @@ mod tests { fn buckets_are_non_overlapping_and_exhaustive() { let local_key = Key::from(PeerId::random()); let timeout = Duration::from_secs(0); - let mut table = KBucketsTable::::new(local_key.into(), timeout); + let mut config = KBucketConfig::default(); + config.set_pending_timeout(timeout); + let mut table = KBucketsTable::::new(local_key.into(), config); let mut prev_max = U256::from(0); @@ -580,7 +652,9 @@ mod tests { fn bucket_contains_range() { fn prop(ix: u8) { let index = BucketIndex(ix as usize); - let mut bucket = KBucket::, ()>::new(Duration::from_secs(0)); + let mut config = KBucketConfig::default(); + config.set_pending_timeout(Duration::from_secs(0)); + let mut bucket = KBucket::, ()>::new(config); let bucket_ref = KBucketRef { index, bucket: &mut bucket, @@ -598,7 +672,7 @@ mod tests { assert!(!bucket_ref.contains(&Distance(min.0 - 1))); } - if max != Distance(U256::max_value()) { + if max != Distance(U256::MAX) { // ^ avoid overflow assert!(!bucket_ref.contains(&Distance(max.0 + 1))); } @@ -611,7 +685,7 @@ mod tests { fn rand_distance() { fn prop(ix: u8) -> bool { let d = BucketIndex(ix as usize).rand_distance(&mut rand::thread_rng()); - let n = U256::from(<[u8; 32]>::from(d.0)); + let n = d.0; let b = U256::from(2); let e = U256::from(ix); let lower = b.pow(e); @@ -626,8 +700,8 @@ mod tests { let local_key = Key::from(PeerId::random()); let other_id = Key::from(PeerId::random()); - let mut table = KBucketsTable::<_, ()>::new(local_key, Duration::from_secs(5)); - if let Entry::Absent(entry) = table.entry(&other_id) { + let mut table = KBucketsTable::<_, ()>::new(local_key, KBucketConfig::default()); + if let Some(Entry::Absent(entry)) = table.entry(&other_id) { match entry.insert((), NodeStatus::Connected) { InsertResult::Inserted => (), _ => panic!(), @@ -644,24 +718,22 @@ mod tests { #[test] fn entry_self() { let local_key = Key::from(PeerId::random()); - let mut table = KBucketsTable::<_, ()>::new(local_key.clone(), Duration::from_secs(5)); - match table.entry(&local_key) { - Entry::SelfEntry => (), - _ => panic!(), - } + let mut table = KBucketsTable::<_, ()>::new(local_key, KBucketConfig::default()); + + assert!(table.entry(&local_key).is_none()) } #[test] fn closest() { let local_key = Key::from(PeerId::random()); - let mut table = KBucketsTable::<_, ()>::new(local_key, Duration::from_secs(5)); + let mut table = KBucketsTable::<_, ()>::new(local_key, KBucketConfig::default()); let mut count = 0; loop { if count == 100 { break; } let key = Key::from(PeerId::random()); - if let Entry::Absent(e) = table.entry(&key) { + if let Some(Entry::Absent(e)) = table.entry(&key) { match e.insert((), NodeStatus::Connected) { InsertResult::Inserted => count += 1, _ => continue, @@ -674,7 +746,7 @@ mod tests { let mut expected_keys: Vec<_> = table .buckets .iter() - .flat_map(|t| t.iter().map(|(n, _)| n.key.clone())) + .flat_map(|t| t.iter().map(|(n, _)| n.key)) .collect(); for _ in 0..10 { @@ -689,22 +761,21 @@ mod tests { #[test] fn applied_pending() { let local_key = Key::from(PeerId::random()); - let mut table = KBucketsTable::<_, ()>::new(local_key.clone(), Duration::from_millis(1)); + let mut config = KBucketConfig::default(); + config.set_pending_timeout(Duration::from_millis(1)); + let mut table = KBucketsTable::<_, ()>::new(local_key, config); let expected_applied; let full_bucket_index; loop { let key = Key::from(PeerId::random()); - if let Entry::Absent(e) = table.entry(&key) { + if let Some(Entry::Absent(e)) = table.entry(&key) { match e.insert((), NodeStatus::Disconnected) { InsertResult::Full => { - if let Entry::Absent(e) = table.entry(&key) { + if let Some(Entry::Absent(e)) = table.entry(&key) { match e.insert((), NodeStatus::Connected) { InsertResult::Pending { disconnected } => { expected_applied = AppliedPending { - inserted: Node { - key: key.clone(), - value: (), - }, + inserted: Node { key, value: () }, evicted: Some(Node { key: disconnected, value: (), @@ -732,12 +803,12 @@ mod tests { full_bucket.pending_mut().unwrap().set_ready_at(elapsed); match table.entry(&expected_applied.inserted.key) { - Entry::Present(_, NodeStatus::Connected) => {} + Some(Entry::Present(_, NodeStatus::Connected)) => {} x => panic!("Unexpected entry: {x:?}"), } match table.entry(&expected_applied.evicted.as_ref().unwrap().key) { - Entry::Absent(_) => {} + Some(Entry::Absent(_)) => {} x => panic!("Unexpected entry: {x:?}"), } diff --git a/protocols/kad/src/kbucket/bucket.rs b/protocols/kad/src/kbucket/bucket.rs index d70161919e1..244525238ec 100644 --- a/protocols/kad/src/kbucket/bucket.rs +++ b/protocols/kad/src/kbucket/bucket.rs @@ -88,15 +88,17 @@ pub struct Node { } /// The position of a node in a `KBucket`, i.e. a non-negative integer -/// in the range `[0, K_VALUE)`. +/// in the range `[0, bucket_size)`. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub(crate) struct Position(usize); -/// A `KBucket` is a list of up to `K_VALUE` keys and associated values, +/// A `KBucket` is a list of up to `capacity` keys and associated values, /// ordered from least-recently connected to most-recently connected. #[derive(Debug, Clone)] pub(crate) struct KBucket { /// The nodes contained in the bucket. - nodes: ArrayVec, { K_VALUE.get() }>, + nodes: Vec>, + /// The maximal number of nodes that a bucket can contain. + capacity: usize, /// The position (index) in `nodes` that marks the first connected node. /// @@ -104,7 +106,7 @@ pub(crate) struct KBucket { /// most-recently connected, all entries above this index are also considered /// connected, i.e. the range `[0, first_connected_pos)` marks the sub-list of entries /// that are considered disconnected and the range - /// `[first_connected_pos, K_VALUE)` marks sub-list of entries that are + /// `[first_connected_pos, capacity)` marks sub-list of entries that are /// considered connected. /// /// `None` indicates that there are no connected entries in the bucket, i.e. @@ -156,18 +158,31 @@ pub(crate) struct AppliedPending { pub(crate) evicted: Option>, } +impl Default for KBucket { + fn default() -> Self { + KBucket { + nodes: Vec::with_capacity(K_VALUE.get()), + capacity: K_VALUE.get(), + first_connected_pos: None, + pending: None, + pending_timeout: Duration::from_secs(60), + } + } +} + impl KBucket where TKey: Clone + AsRef, TVal: Clone, { - /// Creates a new `KBucket` with the given timeout for pending entries. - pub(crate) fn new(pending_timeout: Duration) -> Self { + /// Creates a new `KBucket` with the given configuration. + pub(crate) fn new(config: KBucketConfig) -> Self { KBucket { - nodes: ArrayVec::new(), + nodes: Vec::with_capacity(config.bucket_size), + capacity: config.bucket_size, first_connected_pos: None, pending: None, - pending_timeout, + pending_timeout: config.pending_timeout, } } @@ -205,7 +220,7 @@ where pub(crate) fn apply_pending(&mut self) -> Option> { if let Some(pending) = self.pending.take() { if pending.replace <= Instant::now() { - if self.nodes.is_full() { + if self.nodes.len() >= self.capacity { if self.status(Position(0)) == NodeStatus::Connected { // The bucket is full with connected nodes. Drop the pending node. return None; @@ -296,19 +311,18 @@ where /// /// The status of the node to insert determines the result as follows: /// - /// * `NodeStatus::Connected`: If the bucket is full and either all nodes are connected - /// or there is already a pending node, insertion fails with `InsertResult::Full`. - /// If the bucket is full but at least one node is disconnected and there is no pending - /// node, the new node is inserted as pending, yielding `InsertResult::Pending`. - /// Otherwise the bucket has free slots and the new node is added to the end of the - /// bucket as the most-recently connected node. + /// * `NodeStatus::Connected`: If the bucket is full and either all nodes are connected or + /// there is already a pending node, insertion fails with `InsertResult::Full`. If the + /// bucket is full but at least one node is disconnected and there is no pending node, the + /// new node is inserted as pending, yielding `InsertResult::Pending`. Otherwise the bucket + /// has free slots and the new node is added to the end of the bucket as the most-recently + /// connected node. /// /// * `NodeStatus::Disconnected`: If the bucket is full, insertion fails with - /// `InsertResult::Full`. Otherwise the bucket has free slots and the new node - /// is inserted at the position preceding the first connected node, - /// i.e. as the most-recently disconnected node. If there are no connected nodes, - /// the new node is added as the last element of the bucket. - /// + /// `InsertResult::Full`. Otherwise the bucket has free slots and the new node is inserted + /// at the position preceding the first connected node, i.e. as the most-recently + /// disconnected node. If there are no connected nodes, the new node is added as the last + /// element of the bucket. pub(crate) fn insert( &mut self, node: Node, @@ -316,7 +330,7 @@ where ) -> InsertResult { match status { NodeStatus::Connected => { - if self.nodes.is_full() { + if self.nodes.len() >= self.capacity { if self.first_connected_pos == Some(0) || self.pending.is_some() { return InsertResult::Full; } else { @@ -336,7 +350,7 @@ where InsertResult::Inserted } NodeStatus::Disconnected => { - if self.nodes.is_full() { + if self.nodes.len() >= self.capacity { return InsertResult::Full; } if let Some(ref mut p) = self.first_connected_pos { @@ -362,7 +376,7 @@ where // Adjust `first_connected_pos` accordingly. match status { NodeStatus::Connected => { - if self.first_connected_pos.map_or(false, |p| p == pos.0) + if self.first_connected_pos.is_some_and(|p| p == pos.0) && pos.0 == self.nodes.len() { // It was the last connected node. @@ -383,7 +397,7 @@ where /// Returns the status of the node at the given position. pub(crate) fn status(&self, pos: Position) -> NodeStatus { - if self.first_connected_pos.map_or(false, |i| pos.0 >= i) { + if self.first_connected_pos.is_some_and(|i| pos.0 >= i) { NodeStatus::Connected } else { NodeStatus::Disconnected @@ -428,22 +442,21 @@ where #[cfg(test)] mod tests { - use super::*; use libp2p_identity::PeerId; use quickcheck::*; - use std::collections::VecDeque; + + use super::*; impl Arbitrary for KBucket, ()> { fn arbitrary(g: &mut Gen) -> KBucket, ()> { let timeout = Duration::from_secs(g.gen_range(1..g.size()) as u64); - let mut bucket = KBucket::, ()>::new(timeout); - let num_nodes = g.gen_range(1..K_VALUE.get() + 1); + let mut config = KBucketConfig::default(); + config.set_pending_timeout(timeout); + let mut bucket = KBucket::, ()>::new(config); + let num_nodes = g.gen_range(1..bucket.capacity + 1); for _ in 0..num_nodes { let key = Key::from(PeerId::random()); - let node = Node { - key: key.clone(), - value: (), - }; + let node = Node { key, value: () }; let status = NodeStatus::arbitrary(g); match bucket.insert(node, status) { InsertResult::Inserted => {} @@ -473,7 +486,7 @@ mod tests { // Fill a bucket with random nodes with the given status. fn fill_bucket(bucket: &mut KBucket, ()>, status: NodeStatus) { let num_entries_start = bucket.num_entries(); - for i in 0..K_VALUE.get() - num_entries_start { + for i in 0..bucket.capacity - num_entries_start { let key = Key::from(PeerId::random()); let node = Node { key, value: () }; assert_eq!(InsertResult::Inserted, bucket.insert(node, status)); @@ -484,7 +497,7 @@ mod tests { #[test] fn ordering() { fn prop(status: Vec) -> bool { - let mut bucket = KBucket::, ()>::new(Duration::from_secs(1)); + let mut bucket = KBucket::, ()>::default(); // The expected lists of connected and disconnected nodes. let mut connected = VecDeque::new(); @@ -493,11 +506,8 @@ mod tests { // Fill the bucket, thereby populating the expected lists in insertion order. for status in status { let key = Key::from(PeerId::random()); - let node = Node { - key: key.clone(), - value: (), - }; - let full = bucket.num_entries() == K_VALUE.get(); + let node = Node { key, value: () }; + let full = bucket.num_entries() == bucket.capacity; if let InsertResult::Inserted = bucket.insert(node, status) { let vec = match status { NodeStatus::Connected => &mut connected, @@ -506,15 +516,12 @@ mod tests { if full { vec.pop_front(); } - vec.push_back((status, key.clone())); + vec.push_back((status, key)); } } // Get all nodes from the bucket, together with their status. - let mut nodes = bucket - .iter() - .map(|(n, s)| (s, n.key.clone())) - .collect::>(); + let mut nodes = bucket.iter().map(|(n, s)| (s, n.key)).collect::>(); // Split the list of nodes at the first connected node. let first_connected_pos = nodes.iter().position(|(s, _)| *s == NodeStatus::Connected); @@ -532,7 +539,7 @@ mod tests { #[test] fn full_bucket() { - let mut bucket = KBucket::, ()>::new(Duration::from_secs(1)); + let mut bucket = KBucket::, ()>::default(); // Fill the bucket with disconnected nodes. fill_bucket(&mut bucket, NodeStatus::Disconnected); @@ -546,7 +553,7 @@ mod tests { } // One-by-one fill the bucket with connected nodes, replacing the disconnected ones. - for i in 0..K_VALUE.get() { + for i in 0..bucket.capacity { let (first, first_status) = bucket.iter().next().unwrap(); let first_disconnected = first.clone(); assert_eq!(first_status, NodeStatus::Disconnected); @@ -554,10 +561,7 @@ mod tests { // Add a connected node, which is expected to be pending, scheduled to // replace the first (i.e. least-recently connected) node. let key = Key::from(PeerId::random()); - let node = Node { - key: key.clone(), - value: (), - }; + let node = Node { key, value: () }; match bucket.insert(node.clone(), NodeStatus::Connected) { InsertResult::Pending { disconnected } => { assert_eq!(disconnected, first_disconnected.key) @@ -586,11 +590,11 @@ mod tests { ); assert_eq!(Some((&node, NodeStatus::Connected)), bucket.iter().last()); assert!(bucket.pending().is_none()); - assert_eq!(Some(K_VALUE.get() - (i + 1)), bucket.first_connected_pos); + assert_eq!(Some(bucket.capacity - (i + 1)), bucket.first_connected_pos); } assert!(bucket.pending().is_none()); - assert_eq!(K_VALUE.get(), bucket.num_entries()); + assert_eq!(bucket.capacity, bucket.num_entries()); // Trying to insert another connected node fails. let key = Key::from(PeerId::random()); @@ -603,17 +607,14 @@ mod tests { #[test] fn full_bucket_discard_pending() { - let mut bucket = KBucket::, ()>::new(Duration::from_secs(1)); + let mut bucket = KBucket::, ()>::default(); fill_bucket(&mut bucket, NodeStatus::Disconnected); let (first, _) = bucket.iter().next().unwrap(); let first_disconnected = first.clone(); // Add a connected pending node. let key = Key::from(PeerId::random()); - let node = Node { - key: key.clone(), - value: (), - }; + let node = Node { key, value: () }; if let InsertResult::Pending { disconnected } = bucket.insert(node, NodeStatus::Connected) { assert_eq!(&disconnected, &first_disconnected.key); } else { @@ -638,7 +639,7 @@ mod tests { bucket.first_connected_pos ); assert_eq!(1, bucket.num_connected()); - assert_eq!(K_VALUE.get() - 1, bucket.num_disconnected()); + assert_eq!(bucket.capacity - 1, bucket.num_disconnected()); } #[test] @@ -648,13 +649,10 @@ mod tests { // Capture position and key of the random node to update. let pos = pos.0 % num_nodes; - let key = bucket.nodes[pos].key.clone(); + let key = bucket.nodes[pos].key; // Record the (ordered) list of status of all nodes in the bucket. - let mut expected = bucket - .iter() - .map(|(n, s)| (n.key.clone(), s)) - .collect::>(); + let mut expected = bucket.iter().map(|(n, s)| (n.key, s)).collect::>(); // Update the node in the bucket. bucket.update(&key, status); @@ -666,14 +664,28 @@ mod tests { NodeStatus::Disconnected => bucket.first_connected_pos.unwrap_or(num_nodes) - 1, }; expected.remove(pos); - expected.insert(expected_pos, (key.clone(), status)); - let actual = bucket - .iter() - .map(|(n, s)| (n.key.clone(), s)) - .collect::>(); + expected.insert(expected_pos, (key, status)); + let actual = bucket.iter().map(|(n, s)| (n.key, s)).collect::>(); expected == actual } quickcheck(prop as fn(_, _, _) -> _); } + + #[test] + fn test_custom_bucket_size() { + let bucket_sizes: [NonZeroUsize; 4] = [ + NonZeroUsize::new(2).unwrap(), + NonZeroUsize::new(20).unwrap(), + NonZeroUsize::new(200).unwrap(), + NonZeroUsize::new(2000).unwrap(), + ]; + for &size in &bucket_sizes { + let mut config = KBucketConfig::default(); + config.set_bucket_size(size); + let mut bucket = KBucket::, ()>::new(config); + fill_bucket(&mut bucket, NodeStatus::Disconnected); + assert_eq!(size.get(), bucket.num_entries()); + } + } } diff --git a/protocols/kad/src/kbucket/entry.rs b/protocols/kad/src/kbucket/entry.rs index c38aac4a483..bdf8b9b5a18 100644 --- a/protocols/kad/src/kbucket/entry.rs +++ b/protocols/kad/src/kbucket/entry.rs @@ -18,12 +18,11 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! The `Entry` API for quering and modifying the entries of a `KBucketsTable` +//! The `Entry` API for querying and modifying the entries of a `KBucketsTable` //! representing the nodes participating in the Kademlia DHT. -pub(crate) use super::bucket::{AppliedPending, InsertResult, Node, NodeStatus, K_VALUE}; +pub(crate) use super::bucket::{AppliedPending, InsertResult, Node, K_VALUE}; pub use super::key::*; - use super::*; /// An immutable by-reference view of a bucket entry. @@ -81,8 +80,6 @@ pub(crate) enum Entry<'a, TPeerId, TVal> { Pending(PendingEntry<'a, TPeerId, TVal>, NodeStatus), /// The entry is absent and may be inserted. Absent(AbsentEntry<'a, TPeerId, TVal>), - /// The entry represents the local node. - SelfEntry, } /// The internal representation of the different states of an `Entry`, @@ -144,7 +141,6 @@ where Entry::Present(entry, _) => Some(entry.value()), Entry::Pending(entry, _) => Some(entry.value()), Entry::Absent(_) => None, - Entry::SelfEntry => None, } } } diff --git a/protocols/kad/src/kbucket/key.rs b/protocols/kad/src/kbucket/key.rs index bc5d6a53750..ce14a3f779a 100644 --- a/protocols/kad/src/kbucket/key.rs +++ b/protocols/kad/src/kbucket/key.rs @@ -18,18 +18,24 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::record; +use std::{ + borrow::Borrow, + hash::{Hash, Hasher}, +}; + use libp2p_core::multihash::Multihash; use libp2p_identity::PeerId; -use sha2::digest::generic_array::{typenum::U32, GenericArray}; -use sha2::{Digest, Sha256}; -use std::borrow::Borrow; -use std::hash::{Hash, Hasher}; +use sha2::{ + digest::generic_array::{typenum::U32, GenericArray}, + Digest, Sha256, +}; use uint::*; +use crate::record; + construct_uint! { /// 256-bit unsigned integer. - pub(super) struct U256(4); + pub struct U256(4); } /// A `Key` in the DHT keyspace with preserved preimage. @@ -39,7 +45,7 @@ construct_uint! { /// /// `Key`s have an XOR metric as defined in the Kademlia paper, i.e. the bitwise XOR of /// the hash digests, interpreted as an integer. See [`Key::distance`]. -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub struct Key { preimage: T, bytes: KeyBytes, @@ -145,7 +151,7 @@ impl Hash for Key { } /// The raw bytes of a key in the DHT keyspace. -#[derive(PartialEq, Eq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] pub struct KeyBytes(GenericArray); impl KeyBytes { @@ -163,8 +169,8 @@ impl KeyBytes { where U: AsRef, { - let a = U256::from(self.0.as_slice()); - let b = U256::from(other.as_ref().0.as_slice()); + let a = U256::from_big_endian(self.0.as_slice()); + let b = U256::from_big_endian(other.as_ref().0.as_slice()); Distance(a ^ b) } @@ -174,8 +180,8 @@ impl KeyBytes { /// /// `self xor other = distance <==> other = self xor distance` pub fn for_distance(&self, d: Distance) -> KeyBytes { - let key_int = U256::from(self.0.as_slice()) ^ d.0; - KeyBytes(GenericArray::from(<[u8; 32]>::from(key_int))) + let key_int = U256::from_big_endian(self.0.as_slice()) ^ d.0; + KeyBytes(GenericArray::from(key_int.to_big_endian())) } } @@ -187,7 +193,7 @@ impl AsRef for KeyBytes { /// A distance between two keys in the DHT keyspace. #[derive(Copy, Clone, PartialEq, Eq, Default, PartialOrd, Ord, Debug)] -pub struct Distance(pub(super) U256); +pub struct Distance(pub U256); impl Distance { /// Returns the integer part of the base 2 logarithm of the [`Distance`]. @@ -200,9 +206,10 @@ impl Distance { #[cfg(test)] mod tests { + use quickcheck::*; + use super::*; use crate::SHA_256_MH; - use quickcheck::*; impl Arbitrary for Key { fn arbitrary(_: &mut Gen) -> Key { diff --git a/protocols/kad/src/lib.rs b/protocols/kad/src/lib.rs index 519b67f9d7a..91983b9aaf7 100644 --- a/protocols/kad/src/lib.rs +++ b/protocols/kad/src/lib.rs @@ -23,20 +23,21 @@ //! # Important Discrepancies //! //! - **Peer Discovery with Identify** In other libp2p implementations, the -//! [Identify](https://github.com/libp2p/specs/tree/master/identify) protocol might be seen as a core protocol. Rust-libp2p -//! tries to stay as generic as possible, and does not make this assumption. -//! This means that the Identify protocol must be manually hooked up to Kademlia through calls -//! to [`Behaviour::add_address`]. -//! If you choose not to use the Identify protocol, and do not provide an alternative peer -//! discovery mechanism, a Kademlia node will not discover nodes beyond the network's -//! [boot nodes](https://docs.libp2p.io/concepts/glossary/#boot-node). Without the Identify protocol, -//! existing nodes in the kademlia network cannot obtain the listen addresses -//! of nodes querying them, and thus will not be able to add them to their routing table. +//! [Identify](https://github.com/libp2p/specs/tree/master/identify) protocol might be seen as a core protocol. Rust-libp2p +//! tries to stay as generic as possible, and does not make this assumption. +//! This means that the Identify protocol must be manually hooked up to Kademlia through calls +//! to [`Behaviour::add_address`]. +//! If you choose not to use the Identify protocol, and do not provide an alternative peer +//! discovery mechanism, a Kademlia node will not discover nodes beyond the network's +//! [boot nodes](https://docs.libp2p.io/concepts/glossary/#boot-node). Without the Identify protocol, +//! existing nodes in the kademlia network cannot obtain the listen addresses +//! of nodes querying them, and thus will not be able to add them to their routing table. #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] mod addresses; mod behaviour; +mod bootstrap; mod handler; mod jobs; mod kbucket; @@ -53,36 +54,34 @@ mod proto { }; } +use std::num::NonZeroUsize; + pub use addresses::Addresses; pub use behaviour::{ AddProviderContext, AddProviderError, AddProviderOk, AddProviderPhase, AddProviderResult, - BootstrapError, BootstrapOk, BootstrapResult, GetClosestPeersError, GetClosestPeersOk, - GetClosestPeersResult, GetProvidersError, GetProvidersOk, GetProvidersResult, GetRecordError, - GetRecordOk, GetRecordResult, InboundRequest, Mode, NoKnownPeers, PeerRecord, PutRecordContext, + Behaviour, BootstrapError, BootstrapOk, BootstrapResult, BucketInserts, Caching, Config, Event, + GetClosestPeersError, GetClosestPeersOk, GetClosestPeersResult, GetProvidersError, + GetProvidersOk, GetProvidersResult, GetRecordError, GetRecordOk, GetRecordResult, + InboundRequest, Mode, NoKnownPeers, PeerInfo, PeerRecord, ProgressStep, PutRecordContext, PutRecordError, PutRecordOk, PutRecordPhase, PutRecordResult, QueryInfo, QueryMut, QueryRef, - QueryResult, QueryStats, RoutingUpdate, -}; -pub use behaviour::{ - Behaviour, BucketInserts, Caching, Config, Event, ProgressStep, Quorum, StoreInserts, + QueryResult, QueryStats, Quorum, RoutingUpdate, StoreInserts, }; pub use kbucket::{ - Distance as KBucketDistance, EntryView, KBucketRef, Key as KBucketKey, NodeStatus, + Distance as KBucketDistance, EntryView, KBucketRef, Key as KBucketKey, NodeStatus, U256, }; -pub use protocol::ConnectionType; +use libp2p_swarm::StreamProtocol; +pub use protocol::{ConnectionType, KadPeer}; pub use query::QueryId; pub use record::{store, Key as RecordKey, ProviderRecord, Record}; -use libp2p_swarm::StreamProtocol; -use std::num::NonZeroUsize; - /// The `k` parameter of the Kademlia specification. /// /// This parameter determines: /// /// 1) The (fixed) maximum number of nodes in a bucket. -/// 2) The (default) replication factor, which in turn determines: -/// a) The number of closer peers returned in response to a request. -/// b) The number of closest peers to a key to search for in an iterative query. +/// 2) The (default) replication factor, which in turn determines: a) The number of closer peers +/// returned in response to a request. b) The number of closest peers to a key to search for in +/// an iterative query. /// /// The choice of (1) is fixed to this constant. The replication factor is configurable /// but should generally be no greater than `K_VALUE`. All nodes in a Kademlia diff --git a/protocols/kad/src/protocol.rs b/protocols/kad/src/protocol.rs index 7fe2d1130b1..059b6ae6fd1 100644 --- a/protocols/kad/src/protocol.rs +++ b/protocols/kad/src/protocol.rs @@ -26,20 +26,24 @@ //! to poll the underlying transport for incoming messages, and the `Sink` component //! is used to send messages to remote peers. -use crate::proto; -use crate::record::{self, Record}; +use std::{io, marker::PhantomData, time::Duration}; + use asynchronous_codec::{Decoder, Encoder, Framed}; use bytes::BytesMut; use futures::prelude::*; -use instant::Instant; -use libp2p_core::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; -use libp2p_core::Multiaddr; +use libp2p_core::{ + upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}, + Multiaddr, +}; use libp2p_identity::PeerId; use libp2p_swarm::StreamProtocol; -use std::marker::PhantomData; -use std::{convert::TryFrom, time::Duration}; -use std::{io, iter}; use tracing::debug; +use web_time::Instant; + +use crate::{ + proto, + record::{self, Record}, +}; /// The protocol name used for negotiating with multistream-select. pub(crate) const DEFAULT_PROTO_NAME: StreamProtocol = StreamProtocol::new("/ipfs/kad/1.0.0"); @@ -144,32 +148,25 @@ pub struct ProtocolConfig { } impl ProtocolConfig { + /// Builds a new `ProtocolConfig` with the given protocol name. + pub fn new(protocol_name: StreamProtocol) -> Self { + ProtocolConfig { + protocol_names: vec![protocol_name], + max_packet_size: DEFAULT_MAX_PACKET_SIZE, + } + } + /// Returns the configured protocol name. pub fn protocol_names(&self) -> &[StreamProtocol] { &self.protocol_names } - /// Modifies the protocol names used on the wire. Can be used to create incompatibilities - /// between networks on purpose. - pub fn set_protocol_names(&mut self, names: Vec) { - self.protocol_names = names; - } - /// Modifies the maximum allowed size of a single Kademlia packet. pub fn set_max_packet_size(&mut self, size: usize) { self.max_packet_size = size; } } -impl Default for ProtocolConfig { - fn default() -> Self { - ProtocolConfig { - protocol_names: iter::once(DEFAULT_PROTO_NAME).collect(), - max_packet_size: DEFAULT_MAX_PACKET_SIZE, - } - } -} - impl UpgradeInfo for ProtocolConfig { type Info = StreamProtocol; type InfoIter = std::vec::IntoIter; @@ -648,92 +645,92 @@ mod tests { assert_eq!(peer.multiaddrs, vec![valid_multiaddr]) } - /*// TODO: restore - use self::libp2p_tcp::TcpTransport; - use self::tokio::runtime::current_thread::Runtime; - use futures::{Future, Sink, Stream}; - use libp2p_core::{PeerId, PublicKey, Transport}; - use multihash::{encode, Hash}; - use protocol::{ConnectionType, KadPeer, ProtocolConfig}; - use std::sync::mpsc; - use std::thread; - - #[test] - fn correct_transfer() { - // We open a server and a client, send a message between the two, and check that they were - // successfully received. - - test_one(KadMsg::Ping); - test_one(KadMsg::FindNodeReq { - key: PeerId::random(), - }); - test_one(KadMsg::FindNodeRes { - closer_peers: vec![KadPeer { - node_id: PeerId::random(), - multiaddrs: vec!["/ip4/100.101.102.103/tcp/20105".parse().unwrap()], - connection_ty: ConnectionType::Connected, - }], - }); - test_one(KadMsg::GetProvidersReq { - key: encode(Hash::SHA2256, &[9, 12, 0, 245, 245, 201, 28, 95]).unwrap(), - }); - test_one(KadMsg::GetProvidersRes { - closer_peers: vec![KadPeer { - node_id: PeerId::random(), - multiaddrs: vec!["/ip4/100.101.102.103/tcp/20105".parse().unwrap()], - connection_ty: ConnectionType::Connected, - }], - provider_peers: vec![KadPeer { - node_id: PeerId::random(), - multiaddrs: vec!["/ip4/200.201.202.203/tcp/1999".parse().unwrap()], - connection_ty: ConnectionType::NotConnected, - }], - }); - test_one(KadMsg::AddProvider { - key: encode(Hash::SHA2256, &[9, 12, 0, 245, 245, 201, 28, 95]).unwrap(), - provider_peer: KadPeer { - node_id: PeerId::random(), - multiaddrs: vec!["/ip4/9.1.2.3/udp/23".parse().unwrap()], - connection_ty: ConnectionType::Connected, - }, - }); - // TODO: all messages - - fn test_one(msg_server: KadMsg) { - let msg_client = msg_server.clone(); - let (tx, rx) = mpsc::channel(); - - let bg_thread = thread::spawn(move || { - let transport = TcpTransport::default().with_upgrade(ProtocolConfig); - - let (listener, addr) = transport - .listen_on( "/ip4/127.0.0.1/tcp/0".parse().unwrap()) - .unwrap(); - tx.send(addr).unwrap(); - - let future = listener - .into_future() - .map_err(|(err, _)| err) - .and_then(|(client, _)| client.unwrap().0) - .and_then(|proto| proto.into_future().map_err(|(err, _)| err).map(|(v, _)| v)) - .map(|recv_msg| { - assert_eq!(recv_msg.unwrap(), msg_server); - () - }); - let mut rt = Runtime::new().unwrap(); - let _ = rt.block_on(future).unwrap(); - }); - - let transport = TcpTransport::default().with_upgrade(ProtocolConfig); - - let future = transport - .dial(rx.recv().unwrap()) - .unwrap() - .and_then(|proto| proto.send(msg_client)) - .map(|_| ()); - let mut rt = Runtime::new().unwrap(); - let _ = rt.block_on(future).unwrap(); - bg_thread.join().unwrap(); - } - }*/ + // // TODO: restore + // use self::libp2p_tcp::TcpTransport; + // use self::tokio::runtime::current_thread::Runtime; + // use futures::{Future, Sink, Stream}; + // use libp2p_core::{PeerId, PublicKey, Transport}; + // use multihash::{encode, Hash}; + // use protocol::{ConnectionType, KadPeer, ProtocolConfig}; + // use std::sync::mpsc; + // use std::thread; + // + // #[test] + // fn correct_transfer() { + // We open a server and a client, send a message between the two, and check that they were + // successfully received. + // + // test_one(KadMsg::Ping); + // test_one(KadMsg::FindNodeReq { + // key: PeerId::random(), + // }); + // test_one(KadMsg::FindNodeRes { + // closer_peers: vec![KadPeer { + // node_id: PeerId::random(), + // multiaddrs: vec!["/ip4/100.101.102.103/tcp/20105".parse().unwrap()], + // connection_ty: ConnectionType::Connected, + // }], + // }); + // test_one(KadMsg::GetProvidersReq { + // key: encode(Hash::SHA2256, &[9, 12, 0, 245, 245, 201, 28, 95]).unwrap(), + // }); + // test_one(KadMsg::GetProvidersRes { + // closer_peers: vec![KadPeer { + // node_id: PeerId::random(), + // multiaddrs: vec!["/ip4/100.101.102.103/tcp/20105".parse().unwrap()], + // connection_ty: ConnectionType::Connected, + // }], + // provider_peers: vec![KadPeer { + // node_id: PeerId::random(), + // multiaddrs: vec!["/ip4/200.201.202.203/tcp/1999".parse().unwrap()], + // connection_ty: ConnectionType::NotConnected, + // }], + // }); + // test_one(KadMsg::AddProvider { + // key: encode(Hash::SHA2256, &[9, 12, 0, 245, 245, 201, 28, 95]).unwrap(), + // provider_peer: KadPeer { + // node_id: PeerId::random(), + // multiaddrs: vec!["/ip4/9.1.2.3/udp/23".parse().unwrap()], + // connection_ty: ConnectionType::Connected, + // }, + // }); + // TODO: all messages + // + // fn test_one(msg_server: KadMsg) { + // let msg_client = msg_server.clone(); + // let (tx, rx) = mpsc::channel(); + // + // let bg_thread = thread::spawn(move || { + // let transport = TcpTransport::default().with_upgrade(ProtocolConfig); + // + // let (listener, addr) = transport + // .listen_on( "/ip4/127.0.0.1/tcp/0".parse().unwrap()) + // .unwrap(); + // tx.send(addr).unwrap(); + // + // let future = listener + // .into_future() + // .map_err(|(err, _)| err) + // .and_then(|(client, _)| client.unwrap().0) + // .and_then(|proto| proto.into_future().map_err(|(err, _)| err).map(|(v, _)| v)) + // .map(|recv_msg| { + // assert_eq!(recv_msg.unwrap(), msg_server); + // () + // }); + // let mut rt = Runtime::new().unwrap(); + // let _ = rt.block_on(future).unwrap(); + // }); + // + // let transport = TcpTransport::default().with_upgrade(ProtocolConfig); + // + // let future = transport + // .dial(rx.recv().unwrap()) + // .unwrap() + // .and_then(|proto| proto.send(msg_client)) + // .map(|_| ()); + // let mut rt = Runtime::new().unwrap(); + // let _ = rt.block_on(future).unwrap(); + // bg_thread.join().unwrap(); + // } + // } } diff --git a/protocols/kad/src/query.rs b/protocols/kad/src/query.rs index bb240d5864a..69257f73b26 100644 --- a/protocols/kad/src/query.rs +++ b/protocols/kad/src/query.rs @@ -20,45 +20,52 @@ mod peers; -use peers::closest::{ - disjoint::ClosestDisjointPeersIter, ClosestPeersIter, ClosestPeersIterConfig, -}; -use peers::fixed::FixedPeersIter; -use peers::PeersIterState; +use std::{num::NonZeroUsize, time::Duration}; -use crate::kbucket::{Key, KeyBytes}; -use crate::{ALPHA_VALUE, K_VALUE}; use either::Either; use fnv::FnvHashMap; -use instant::Instant; +use libp2p_core::Multiaddr; use libp2p_identity::PeerId; -use std::{num::NonZeroUsize, time::Duration}; +use peers::{ + closest::{disjoint::ClosestDisjointPeersIter, ClosestPeersIter, ClosestPeersIterConfig}, + fixed::FixedPeersIter, + PeersIterState, +}; +use smallvec::SmallVec; +use web_time::Instant; + +use crate::{ + behaviour::PeerInfo, + handler::HandlerIn, + kbucket::{Key, KeyBytes}, + QueryInfo, ALPHA_VALUE, K_VALUE, +}; /// A `QueryPool` provides an aggregate state machine for driving `Query`s to completion. /// /// Internally, a `Query` is in turn driven by an underlying `QueryPeerIter` /// that determines the peer selection strategy, i.e. the order in which the /// peers involved in the query should be contacted. -pub(crate) struct QueryPool { +pub(crate) struct QueryPool { next_id: usize, config: QueryConfig, - queries: FnvHashMap>, + queries: FnvHashMap, } /// The observable states emitted by [`QueryPool::poll`]. -pub(crate) enum QueryPoolState<'a, TInner> { +pub(crate) enum QueryPoolState<'a> { /// The pool is idle, i.e. there are no queries to process. Idle, /// At least one query is waiting for results. `Some(request)` indicates /// that a new request is now being waited on. - Waiting(Option<(&'a mut Query, PeerId)>), + Waiting(Option<(&'a mut Query, PeerId)>), /// A query has finished. - Finished(Query), + Finished(Query), /// A query has timed out. - Timeout(Query), + Timeout(Query), } -impl QueryPool { +impl QueryPool { /// Creates a new `QueryPool` with the given configuration. pub(crate) fn new(config: QueryConfig) -> Self { QueryPool { @@ -74,7 +81,7 @@ impl QueryPool { } /// Returns an iterator over the queries in the pool. - pub(crate) fn iter(&self) -> impl Iterator> { + pub(crate) fn iter(&self) -> impl Iterator { self.queries.values() } @@ -84,42 +91,42 @@ impl QueryPool { } /// Returns an iterator that allows modifying each query in the pool. - pub(crate) fn iter_mut(&mut self) -> impl Iterator> { + pub(crate) fn iter_mut(&mut self) -> impl Iterator { self.queries.values_mut() } /// Adds a query to the pool that contacts a fixed set of peers. - pub(crate) fn add_fixed(&mut self, peers: I, inner: TInner) -> QueryId + pub(crate) fn add_fixed(&mut self, peers: I, info: QueryInfo) -> QueryId where I: IntoIterator, { let id = self.next_query_id(); - self.continue_fixed(id, peers, inner); + self.continue_fixed(id, peers, info); id } /// Continues an earlier query with a fixed set of peers, reusing /// the given query ID, which must be from a query that finished /// earlier. - pub(crate) fn continue_fixed(&mut self, id: QueryId, peers: I, inner: TInner) + pub(crate) fn continue_fixed(&mut self, id: QueryId, peers: I, info: QueryInfo) where I: IntoIterator, { assert!(!self.queries.contains_key(&id)); let parallelism = self.config.replication_factor; let peer_iter = QueryPeerIter::Fixed(FixedPeersIter::new(peers, parallelism)); - let query = Query::new(id, peer_iter, inner); + let query = Query::new(id, peer_iter, info); self.queries.insert(id, query); } /// Adds a query to the pool that iterates towards the closest peers to the target. - pub(crate) fn add_iter_closest(&mut self, target: T, peers: I, inner: TInner) -> QueryId + pub(crate) fn add_iter_closest(&mut self, target: T, peers: I, info: QueryInfo) -> QueryId where T: Into + Clone, I: IntoIterator>, { let id = self.next_query_id(); - self.continue_iter_closest(id, target, peers, inner); + self.continue_iter_closest(id, target, peers, info); id } @@ -129,13 +136,21 @@ impl QueryPool { id: QueryId, target: T, peers: I, - inner: TInner, + info: QueryInfo, ) where T: Into + Clone, I: IntoIterator>, { + let num_results = match info { + QueryInfo::GetClosestPeers { + num_results: Some(val), + .. + } => val, + _ => self.config.replication_factor, + }; + let cfg = ClosestPeersIterConfig { - num_results: self.config.replication_factor, + num_results, parallelism: self.config.parallelism, ..ClosestPeersIterConfig::default() }; @@ -148,7 +163,7 @@ impl QueryPool { QueryPeerIter::Closest(ClosestPeersIter::with_config(cfg, target, peers)) }; - let query = Query::new(id, peer_iter, inner); + let query = Query::new(id, peer_iter, info); self.queries.insert(id, query); } @@ -159,17 +174,17 @@ impl QueryPool { } /// Returns a reference to a query with the given ID, if it is in the pool. - pub(crate) fn get(&self, id: &QueryId) -> Option<&Query> { + pub(crate) fn get(&self, id: &QueryId) -> Option<&Query> { self.queries.get(id) } /// Returns a mutablereference to a query with the given ID, if it is in the pool. - pub(crate) fn get_mut(&mut self, id: &QueryId) -> Option<&mut Query> { + pub(crate) fn get_mut(&mut self, id: &QueryId) -> Option<&mut Query> { self.queries.get_mut(id) } /// Polls the pool to advance the queries. - pub(crate) fn poll(&mut self, now: Instant) -> QueryPoolState<'_, TInner> { + pub(crate) fn poll(&mut self, now: Instant) -> QueryPoolState<'_> { let mut finished = None; let mut timeout = None; let mut waiting = None; @@ -264,15 +279,53 @@ impl Default for QueryConfig { } /// A query in a `QueryPool`. -pub(crate) struct Query { +pub(crate) struct Query { /// The unique ID of the query. id: QueryId, /// The peer iterator that drives the query state. - peer_iter: QueryPeerIter, + pub(crate) peers: QueryPeers, /// Execution statistics of the query. - stats: QueryStats, - /// The opaque inner query state. - pub(crate) inner: TInner, + pub(crate) stats: QueryStats, + /// The query-specific state. + pub(crate) info: QueryInfo, + /// A map of pending requests to peers. + /// + /// A request is pending if the targeted peer is not currently connected + /// and these requests are sent as soon as a connection to the peer is established. + pub(crate) pending_rpcs: SmallVec<[(PeerId, HandlerIn); K_VALUE.get()]>, +} + +/// The peer iterator that drives the query state, +pub(crate) struct QueryPeers { + /// Addresses of peers discovered during a query. + pub(crate) addresses: FnvHashMap>, + /// The peer iterator that drives the query state. + peer_iter: QueryPeerIter, +} + +impl QueryPeers { + /// Consumes the peers iterator, producing a final `Iterator` over the discovered `PeerId`s. + pub(crate) fn into_peerids_iter(self) -> impl Iterator { + match self.peer_iter { + QueryPeerIter::Closest(iter) => Either::Left(Either::Left(iter.into_result())), + QueryPeerIter::ClosestDisjoint(iter) => Either::Left(Either::Right(iter.into_result())), + QueryPeerIter::Fixed(iter) => Either::Right(iter.into_result()), + } + } + + /// Consumes the peers iterator, producing a final `Iterator` over the discovered `PeerId`s + /// with their matching `Multiaddr`s. + pub(crate) fn into_peerinfos_iter(mut self) -> impl Iterator { + match self.peer_iter { + QueryPeerIter::Closest(iter) => Either::Left(Either::Left(iter.into_result())), + QueryPeerIter::ClosestDisjoint(iter) => Either::Left(Either::Right(iter.into_result())), + QueryPeerIter::Fixed(iter) => Either::Right(iter.into_result()), + } + .map(move |peer_id| { + let addrs = self.addresses.remove(&peer_id).unwrap_or_default().to_vec(); + PeerInfo { peer_id, addrs } + }) + } } /// The peer selection strategies that can be used by queries. @@ -282,13 +335,17 @@ enum QueryPeerIter { Fixed(FixedPeersIter), } -impl Query { +impl Query { /// Creates a new query without starting it. - fn new(id: QueryId, peer_iter: QueryPeerIter, inner: TInner) -> Self { + fn new(id: QueryId, peer_iter: QueryPeerIter, info: QueryInfo) -> Self { Query { id, - inner, - peer_iter, + info, + peers: QueryPeers { + addresses: Default::default(), + peer_iter, + }, + pending_rpcs: SmallVec::default(), stats: QueryStats::empty(), } } @@ -305,7 +362,7 @@ impl Query { /// Informs the query that the attempt to contact `peer` failed. pub(crate) fn on_failure(&mut self, peer: &PeerId) { - let updated = match &mut self.peer_iter { + let updated = match &mut self.peers.peer_iter { QueryPeerIter::Closest(iter) => iter.on_failure(peer), QueryPeerIter::ClosestDisjoint(iter) => iter.on_failure(peer), QueryPeerIter::Fixed(iter) => iter.on_failure(peer), @@ -322,7 +379,7 @@ impl Query { where I: IntoIterator, { - let updated = match &mut self.peer_iter { + let updated = match &mut self.peers.peer_iter { QueryPeerIter::Closest(iter) => iter.on_success(peer, new_peers), QueryPeerIter::ClosestDisjoint(iter) => iter.on_success(peer, new_peers), QueryPeerIter::Fixed(iter) => iter.on_success(peer), @@ -334,7 +391,7 @@ impl Query { /// Advances the state of the underlying peer iterator. fn next(&mut self, now: Instant) -> PeersIterState<'_> { - let state = match &mut self.peer_iter { + let state = match &mut self.peers.peer_iter { QueryPeerIter::Closest(iter) => iter.next(now), QueryPeerIter::ClosestDisjoint(iter) => iter.next(now), QueryPeerIter::Fixed(iter) => iter.next(), @@ -368,7 +425,7 @@ impl Query { where I: IntoIterator, { - match &mut self.peer_iter { + match &mut self.peers.peer_iter { QueryPeerIter::Closest(iter) => { iter.finish(); true @@ -386,7 +443,7 @@ impl Query { /// A finished query immediately stops yielding new peers to contact and will be /// reported by [`QueryPool::poll`] via [`QueryPoolState::Finished`]. pub(crate) fn finish(&mut self) { - match &mut self.peer_iter { + match &mut self.peers.peer_iter { QueryPeerIter::Closest(iter) => iter.finish(), QueryPeerIter::ClosestDisjoint(iter) => iter.finish(), QueryPeerIter::Fixed(iter) => iter.finish(), @@ -398,36 +455,12 @@ impl Query { /// A finished query is eventually reported by `QueryPool::next()` and /// removed from the pool. pub(crate) fn is_finished(&self) -> bool { - match &self.peer_iter { + match &self.peers.peer_iter { QueryPeerIter::Closest(iter) => iter.is_finished(), QueryPeerIter::ClosestDisjoint(iter) => iter.is_finished(), QueryPeerIter::Fixed(iter) => iter.is_finished(), } } - - /// Consumes the query, producing the final `QueryResult`. - pub(crate) fn into_result(self) -> QueryResult> { - let peers = match self.peer_iter { - QueryPeerIter::Closest(iter) => Either::Left(Either::Left(iter.into_result())), - QueryPeerIter::ClosestDisjoint(iter) => Either::Left(Either::Right(iter.into_result())), - QueryPeerIter::Fixed(iter) => Either::Right(iter.into_result()), - }; - QueryResult { - peers, - inner: self.inner, - stats: self.stats, - } - } -} - -/// The result of a `Query`. -pub(crate) struct QueryResult { - /// The opaque inner query state. - pub(crate) inner: TInner, - /// The successfully contacted peers. - pub(crate) peers: TPeers, - /// The collected query statistics. - pub(crate) stats: QueryStats, } /// Execution statistics of a query. diff --git a/protocols/kad/src/query/peers.rs b/protocols/kad/src/query/peers.rs index 11b8f974de9..fe8ada51e44 100644 --- a/protocols/kad/src/query/peers.rs +++ b/protocols/kad/src/query/peers.rs @@ -23,13 +23,11 @@ //! Using a peer iterator in a query involves performing the following steps //! repeatedly and in an alternating fashion: //! -//! 1. Calling `next` to observe the next state of the iterator and determine -//! what to do, which is to either issue new requests to peers or continue -//! waiting for responses. +//! 1. Calling `next` to observe the next state of the iterator and determine what to do, which is +//! to either issue new requests to peers or continue waiting for responses. //! -//! 2. When responses are received or requests fail, providing input to the -//! iterator via the `on_success` and `on_failure` callbacks, -//! respectively, followed by repeating step (1). +//! 2. When responses are received or requests fail, providing input to the iterator via the +//! `on_success` and `on_failure` callbacks, respectively, followed by repeating step (1). //! //! When a call to `next` returns [`Finished`], no more peers can be obtained //! from the iterator and the results can be obtained from `into_result`. @@ -40,9 +38,10 @@ pub(crate) mod closest; pub(crate) mod fixed; -use libp2p_identity::PeerId; use std::borrow::Cow; +use libp2p_identity::PeerId; + /// The state of a peer iterator. #[derive(Debug, Clone, PartialEq, Eq)] pub enum PeersIterState<'a> { diff --git a/protocols/kad/src/query/peers/closest.rs b/protocols/kad/src/query/peers/closest.rs index dc913f1bbca..2d1f91f050c 100644 --- a/protocols/kad/src/query/peers/closest.rs +++ b/protocols/kad/src/query/peers/closest.rs @@ -18,14 +18,19 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::*; +use std::{ + collections::btree_map::{BTreeMap, Entry}, + num::NonZeroUsize, + time::Duration, +}; + +use web_time::Instant; -use crate::kbucket::{Distance, Key, KeyBytes}; -use crate::{ALPHA_VALUE, K_VALUE}; -use instant::Instant; -use libp2p_identity::PeerId; -use std::collections::btree_map::{BTreeMap, Entry}; -use std::{iter::FromIterator, num::NonZeroUsize, time::Duration}; +use super::*; +use crate::{ + kbucket::{Distance, Key, KeyBytes}, + ALPHA_VALUE, K_VALUE, +}; pub(crate) mod disjoint; /// A peer iterator for a dynamically changing list of peers, sorted by increasing @@ -175,6 +180,20 @@ impl ClosestPeersIter { }, } + let mut cur_range = distance; + let num_results = self.config.num_results.get(); + // furthest_peer is the furthest peer in range among the closest_peers + let furthest_peer = self + .closest_peers + .iter() + .enumerate() + .nth(num_results - 1) + .map(|(_, peer)| peer) + .or_else(|| self.closest_peers.iter().last()); + if let Some((dist, _)) = furthest_peer { + cur_range = *dist; + } + // Incorporate the reported closer peers into the iterator. // // The iterator makes progress if: @@ -190,9 +209,16 @@ impl ClosestPeersIter { key, state: PeerState::NotContacted, }; - self.closest_peers.entry(distance).or_insert(peer); - progress = self.closest_peers.keys().next() == Some(&distance) || progress; + let is_first_insert = match self.closest_peers.entry(distance) { + Entry::Occupied(_) => false, + Entry::Vacant(entry) => { + entry.insert(peer); + true + } + }; + + progress = (is_first_insert && distance < cur_range) || progress; } // Update the iterator state. @@ -474,13 +500,14 @@ enum PeerState { #[cfg(test)] mod tests { - use super::*; - use crate::SHA_256_MH; + use std::iter; + use libp2p_core::multihash::Multihash; - use libp2p_identity::PeerId; use quickcheck::*; use rand::{rngs::StdRng, Rng, SeedableRng}; - use std::{iter, time::Duration}; + + use super::*; + use crate::SHA_256_MH; fn random_peers(n: usize, g: &mut R) -> Vec { (0..n) @@ -537,12 +564,12 @@ mod tests { #[test] fn new_iter() { fn prop(iter: ClosestPeersIter) { - let target = iter.target.clone(); + let target = iter.target; let (keys, states): (Vec<_>, Vec<_>) = iter .closest_peers .values() - .map(|e| (e.key.clone(), &e.state)) + .map(|e| (e.key, &e.state)) .unzip(); let none_contacted = states.iter().all(|s| matches!(s, PeerState::NotContacted)); @@ -576,12 +603,12 @@ mod tests { let mut expected = iter .closest_peers .values() - .map(|e| e.key.clone()) + .map(|e| e.key) .collect::>(); let num_known = expected.len(); let max_parallelism = usize::min(iter.config.parallelism.get(), num_known); - let target = iter.target.clone(); + let target = iter.target; let mut remaining; let mut num_failures = 0; @@ -648,7 +675,7 @@ mod tests { .values() .all(|e| !matches!(e.state, PeerState::NotContacted | PeerState::Waiting { .. })); - let target = iter.target.clone(); + let target = iter.target; let num_results = iter.config.num_results; let result = iter.into_result(); let closest = result.map(Key::from).collect::>(); @@ -725,7 +752,6 @@ mod tests { .next() .unwrap() .key - .clone() .into_preimage(); // Poll the iterator for the first peer to be in progress. diff --git a/protocols/kad/src/query/peers/closest/disjoint.rs b/protocols/kad/src/query/peers/closest/disjoint.rs index 68721f93d7c..70ded360c7e 100644 --- a/protocols/kad/src/query/peers/closest/disjoint.rs +++ b/protocols/kad/src/query/peers/closest/disjoint.rs @@ -18,16 +18,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::*; -use crate::kbucket::{Key, KeyBytes}; -use instant::Instant; -use libp2p_identity::PeerId; use std::{ collections::HashMap, iter::{Cycle, Map, Peekable}, ops::{Index, IndexMut, Range}, }; +use super::*; + /// Wraps around a set of [`ClosestPeersIter`], enforcing a disjoint discovery /// path per configured parallelism according to the S/Kademlia paper. pub(crate) struct ClosestDisjointPeersIter { @@ -376,7 +374,6 @@ enum ResponseState { /// Iterator combining the result of multiple [`ClosestPeersIter`] into a single /// deduplicated ordered iterator. -// // Note: This operates under the assumption that `I` is ordered. #[derive(Clone, Debug)] struct ResultIter @@ -436,13 +433,13 @@ impl>> Iterator for ResultIter { #[cfg(test)] mod tests { - use super::*; + use std::{collections::HashSet, iter}; - use crate::{K_VALUE, SHA_256_MH}; use libp2p_core::multihash::Multihash; use quickcheck::*; - use std::collections::HashSet; - use std::iter; + + use super::*; + use crate::SHA_256_MH; impl Arbitrary for ResultIter>> { fn arbitrary(g: &mut Gen) -> Self { @@ -463,7 +460,7 @@ mod tests { peers.into_iter() }); - ResultIter::new(target.clone(), iters) + ResultIter::new(target, iters) } fn shrink(&self) -> Box> { @@ -484,7 +481,7 @@ mod tests { .collect(); Box::new(ResultIterShrinker { - target: self.target.clone(), + target: self.target, peers, iters, }) @@ -514,7 +511,7 @@ mod tests { Some(iter.into_iter()) }); - Some(ResultIter::new(self.target.clone(), iters)) + Some(ResultIter::new(self.target, iters)) } } @@ -602,20 +599,6 @@ mod tests { } } - #[derive(Debug, Clone)] - struct PeerVec(Vec>); - - impl Arbitrary for PeerVec { - fn arbitrary(g: &mut Gen) -> Self { - PeerVec( - (0..g.gen_range(1..60u8)) - .map(|_| ArbitraryPeerId::arbitrary(g).0) - .map(Key::from) - .collect(), - ) - } - } - #[test] fn s_kademlia_disjoint_paths() { let now = Instant::now(); @@ -884,7 +867,7 @@ mod tests { let closest = drive_to_finish( PeerIterator::Closest(ClosestPeersIter::with_config( cfg.clone(), - target.clone(), + target, known_closest_peers.clone(), )), graph.clone(), @@ -894,7 +877,7 @@ mod tests { let disjoint = drive_to_finish( PeerIterator::Disjoint(ClosestDisjointPeersIter::with_config( cfg, - target.clone(), + target, known_closest_peers.clone(), )), graph, diff --git a/protocols/kad/src/query/peers/fixed.rs b/protocols/kad/src/query/peers/fixed.rs index 50a969380a3..41cb3559f1b 100644 --- a/protocols/kad/src/query/peers/fixed.rs +++ b/protocols/kad/src/query/peers/fixed.rs @@ -18,15 +18,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::*; +use std::{collections::hash_map::Entry, num::NonZeroUsize, vec}; use fnv::FnvHashMap; -use libp2p_identity::PeerId; -use std::{collections::hash_map::Entry, num::NonZeroUsize, vec}; + +use super::*; /// A peer iterator for a fixed set of peers. pub(crate) struct FixedPeersIter { - /// Ther permitted parallelism, i.e. number of pending results. + /// The permitted parallelism, i.e. number of pending results. parallelism: NonZeroUsize, /// The state of peers emitted by the iterator. diff --git a/protocols/kad/src/record.rs b/protocols/kad/src/record.rs index 4eb8e861c6f..fea17f826a4 100644 --- a/protocols/kad/src/record.rs +++ b/protocols/kad/src/record.rs @@ -22,14 +22,17 @@ pub mod store; +use std::{ + borrow::Borrow, + hash::{Hash, Hasher}, +}; + use bytes::Bytes; -use instant::Instant; use libp2p_core::{multihash::Multihash, Multiaddr}; use libp2p_identity::PeerId; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use std::borrow::Borrow; -use std::hash::{Hash, Hasher}; +use web_time::Instant; /// The (opaque) key of a record. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -101,7 +104,7 @@ impl Record { /// Checks whether the record is expired w.r.t. the given `Instant`. pub fn is_expired(&self, now: Instant) -> bool { - self.expires.map_or(false, |t| now >= t) + self.expires.is_some_and(|t| now >= t) } } @@ -154,16 +157,18 @@ impl ProviderRecord { /// Checks whether the provider record is expired w.r.t. the given `Instant`. pub fn is_expired(&self, now: Instant) -> bool { - self.expires.map_or(false, |t| now >= t) + self.expires.is_some_and(|t| now >= t) } } #[cfg(test)] mod tests { + use std::time::Duration; + + use quickcheck::*; + use super::*; use crate::SHA_256_MH; - use quickcheck::*; - use std::time::Duration; impl Arbitrary for Key { fn arbitrary(g: &mut Gen) -> Key { diff --git a/protocols/kad/src/record/store.rs b/protocols/kad/src/record/store.rs index 5c25bc8b2fa..ee40f568bb3 100644 --- a/protocols/kad/src/record/store.rs +++ b/protocols/kad/src/record/store.rs @@ -20,12 +20,13 @@ mod memory; +use std::borrow::Cow; + pub use memory::{MemoryStore, MemoryStoreConfig}; use thiserror::Error; use super::*; use crate::K_VALUE; -use std::borrow::Cow; /// The result of an operation on a `RecordStore`. pub type Result = std::result::Result; @@ -50,20 +51,16 @@ pub enum Error { /// /// There are two types of records managed by a `RecordStore`: /// -/// 1. Regular (value-)records. These records store an arbitrary value -/// associated with a key which is distributed to the closest nodes -/// to the key in the Kademlia DHT as per the standard Kademlia "push-model". -/// These records are subject to re-replication and re-publication as +/// 1. Regular (value-)records. These records store an arbitrary value associated with a key which +/// is distributed to the closest nodes to the key in the Kademlia DHT as per the standard +/// Kademlia "push-model". These records are subject to re-replication and re-publication as /// per the standard Kademlia protocol. /// -/// 2. Provider records. These records associate the ID of a peer with a key -/// who can supposedly provide the associated value. These records are -/// mere "pointers" to the data which may be followed by contacting these -/// providers to obtain the value. These records are specific to the -/// libp2p Kademlia specification and realise a "pull-model" for distributed -/// content. Just like a regular record, a provider record is distributed -/// to the closest nodes to the key. -/// +/// 2. Provider records. These records associate the ID of a peer with a key who can supposedly +/// provide the associated value. These records are mere "pointers" to the data which may be +/// followed by contacting these providers to obtain the value. These records are specific to +/// the libp2p Kademlia specification and realise a "pull-model" for distributed content. Just +/// like a regular record, a provider record is distributed to the closest nodes to the key. pub trait RecordStore { type RecordsIter<'a>: Iterator> where diff --git a/protocols/kad/src/record/store/memory.rs b/protocols/kad/src/record/store/memory.rs index edeae188ac6..28f6a55044f 100644 --- a/protocols/kad/src/record/store/memory.rs +++ b/protocols/kad/src/record/store/memory.rs @@ -18,14 +18,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::*; +use std::{ + collections::{hash_map, hash_set, HashMap, HashSet}, + iter, +}; -use crate::kbucket; -use libp2p_identity::PeerId; use smallvec::SmallVec; -use std::borrow::Cow; -use std::collections::{hash_map, hash_set, HashMap, HashSet}; -use std::iter; + +use super::*; +use crate::kbucket; /// In-memory implementation of a `RecordStore`. pub struct MemoryStore { @@ -154,38 +155,31 @@ impl RecordStore for MemoryStore { } .or_insert_with(Default::default); - if let Some(i) = providers.iter().position(|p| p.provider == record.provider) { - // In-place update of an existing provider record. - providers.as_mut()[i] = record; - } else { - // It is a new provider record for that key. - let local_key = self.local_key.clone(); - let key = kbucket::Key::new(record.key.clone()); - let provider = kbucket::Key::from(record.provider); - if let Some(i) = providers.iter().position(|p| { - let pk = kbucket::Key::from(p.provider); - provider.distance(&key) < pk.distance(&key) - }) { - // Insert the new provider. - if local_key.preimage() == &record.provider { - self.provided.insert(record.clone()); - } - providers.insert(i, record); - // Remove the excess provider, if any. - if providers.len() > self.config.max_providers_per_key { - if let Some(p) = providers.pop() { - self.provided.remove(&p); - } - } - } else if providers.len() < self.config.max_providers_per_key { - // The distance of the new provider to the key is larger than - // the distance of any existing provider, but there is still room. - if local_key.preimage() == &record.provider { + for p in providers.iter_mut() { + if p.provider == record.provider { + // In-place update of an existing provider record. + if self.local_key.preimage() == &record.provider { + self.provided.remove(p); self.provided.insert(record.clone()); } - providers.push(record); + *p = record; + return Ok(()); } } + + // If the providers list is full, we ignore the new provider. + // This strategy can mitigate Sybil attacks, in which an attacker + // floods the network with fake provider records. + if providers.len() == self.config.max_providers_per_key { + return Ok(()); + } + + // Otherwise, insert the new provider record. + if self.local_key.preimage() == &record.provider { + self.provided.insert(record.clone()); + } + providers.push(record); + Ok(()) } @@ -204,7 +198,9 @@ impl RecordStore for MemoryStore { let providers = e.get_mut(); if let Some(i) = providers.iter().position(|p| &p.provider == provider) { let p = providers.remove(i); - self.provided.remove(&p); + if &p.provider == self.local_key.preimage() { + self.provided.remove(&p); + } } if providers.is_empty() { e.remove(); @@ -215,20 +211,15 @@ impl RecordStore for MemoryStore { #[cfg(test)] mod tests { - use super::*; - use crate::SHA_256_MH; - use libp2p_core::multihash::Multihash; use quickcheck::*; use rand::Rng; + use super::*; + use crate::SHA_256_MH; + fn random_multihash() -> Multihash<64> { Multihash::wrap(SHA_256_MH, &rand::thread_rng().gen::<[u8; 32]>()).unwrap() } - - fn distance(r: &ProviderRecord) -> kbucket::Distance { - kbucket::Key::new(r.key.clone()).distance(&kbucket::Key::from(r.provider)) - } - #[test] fn put_get_remove_record() { fn prop(r: Record) { @@ -253,30 +244,6 @@ mod tests { quickcheck(prop as fn(_)) } - #[test] - fn providers_ordered_by_distance_to_key() { - fn prop(providers: Vec>) -> bool { - let mut store = MemoryStore::new(PeerId::random()); - let key = Key::from(random_multihash()); - - let mut records = providers - .into_iter() - .map(|p| ProviderRecord::new(key.clone(), p.into_preimage(), Vec::new())) - .collect::>(); - - for r in &records { - assert!(store.add_provider(r.clone()).is_ok()); - } - - records.sort_by_key(distance); - records.truncate(store.config.max_providers_per_key); - - records == store.providers(&key).to_vec() - } - - quickcheck(prop as fn(_) -> _) - } - #[test] fn provided() { let id = PeerId::random(); @@ -305,6 +272,46 @@ mod tests { assert_eq!(vec![rec.clone()], store.providers(&rec.key).to_vec()); } + #[test] + fn update_provided() { + let prv = PeerId::random(); + let mut store = MemoryStore::new(prv); + let key = random_multihash(); + let mut rec = ProviderRecord::new(key, prv, Vec::new()); + assert!(store.add_provider(rec.clone()).is_ok()); + assert_eq!( + vec![Cow::Borrowed(&rec)], + store.provided().collect::>() + ); + rec.expires = Some(Instant::now()); + assert!(store.add_provider(rec.clone()).is_ok()); + assert_eq!( + vec![Cow::Borrowed(&rec)], + store.provided().collect::>() + ); + } + + #[test] + fn max_providers_per_key() { + let config = MemoryStoreConfig::default(); + let key = kbucket::Key::new(Key::from(random_multihash())); + + let mut store = MemoryStore::with_config(PeerId::random(), config.clone()); + let peers = (0..config.max_providers_per_key) + .map(|_| PeerId::random()) + .collect::>(); + for peer in peers { + let rec = ProviderRecord::new(key.preimage().clone(), peer, Vec::new()); + assert!(store.add_provider(rec).is_ok()); + } + + // The new provider cannot be added because the key is already saturated. + let peer = PeerId::random(); + let rec = ProviderRecord::new(key.preimage().clone(), peer, Vec::new()); + assert!(store.add_provider(rec.clone()).is_ok()); + assert!(!store.providers(&rec.key).contains(&rec)); + } + #[test] fn max_provided_keys() { let mut store = MemoryStore::new(PeerId::random()); diff --git a/protocols/kad/tests/client_mode.rs b/protocols/kad/tests/client_mode.rs index f290a36b727..09e24c6f6ea 100644 --- a/protocols/kad/tests/client_mode.rs +++ b/protocols/kad/tests/client_mode.rs @@ -1,18 +1,14 @@ use libp2p_identify as identify; use libp2p_identity as identity; -use libp2p_kad::store::MemoryStore; -use libp2p_kad::{Behaviour, Config, Event, Mode}; +use libp2p_kad::{store::MemoryStore, Behaviour, Config, Event, Mode}; use libp2p_swarm::{Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt; -use tracing_subscriber::EnvFilter; use Event::*; use MyBehaviourEvent::*; #[async_std::test] async fn server_gets_added_to_routing_table_by_client() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut client = Swarm::new_ephemeral(MyBehaviour::new); let mut server = Swarm::new_ephemeral(MyBehaviour::new); @@ -23,21 +19,26 @@ async fn server_gets_added_to_routing_table_by_client() { let server_peer_id = *server.local_peer_id(); async_std::task::spawn(server.loop_on_next()); - let peer = client + let external_event_peer = client + .wait(|e| match e { + SwarmEvent::NewExternalAddrOfPeer { peer_id, .. } => Some(peer_id), + _ => None, + }) + .await; + let routing_updated_peer = client .wait(|e| match e { SwarmEvent::Behaviour(Kad(RoutingUpdated { peer, .. })) => Some(peer), _ => None, }) .await; - assert_eq!(peer, server_peer_id); + assert_eq!(external_event_peer, server_peer_id); + assert_eq!(routing_updated_peer, server_peer_id); } #[async_std::test] async fn two_servers_add_each_other_to_routing_table() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut server1 = Swarm::new_ephemeral(MyBehaviour::new); let mut server2 = Swarm::new_ephemeral(MyBehaviour::new); @@ -76,9 +77,7 @@ async fn two_servers_add_each_other_to_routing_table() { #[async_std::test] async fn adding_an_external_addresses_activates_server_mode_on_existing_connections() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut client = Swarm::new_ephemeral(MyBehaviour::new); let mut server = Swarm::new_ephemeral(MyBehaviour::new); @@ -97,7 +96,9 @@ async fn adding_an_external_addresses_activates_server_mode_on_existing_connecti // Server learns its external address (this could be through AutoNAT or some other mechanism). server.add_external_address(memory_addr); - // The server reconfigured its connection to the client to be in server mode, pushes that information to client which as a result updates its routing table and triggers a mode change to Mode::Server. + // The server reconfigured its connection to the client to be in server mode, + // pushes that information to client which as a result updates its routing + // table and triggers a mode change to Mode::Server. match libp2p_swarm_test::drive(&mut client, &mut server).await { ( [Identify(identify::Event::Received { .. }), Kad(RoutingUpdated { peer: peer1, .. })], @@ -112,9 +113,7 @@ async fn adding_an_external_addresses_activates_server_mode_on_existing_connecti #[async_std::test] async fn set_client_to_server_mode() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut client = Swarm::new_ephemeral(MyBehaviour::new); client.behaviour_mut().kad.set_mode(Some(Mode::Client)); @@ -126,6 +125,12 @@ async fn set_client_to_server_mode() { let server_peer_id = *server.local_peer_id(); + let peer_id = client + .wait(|e| match e { + SwarmEvent::NewExternalAddrOfPeer { peer_id, .. } => Some(peer_id), + _ => None, + }) + .await; let client_event = client.wait(|e| match e { SwarmEvent::Behaviour(Kad(RoutingUpdated { peer, .. })) => Some(peer), _ => None, @@ -138,6 +143,7 @@ async fn set_client_to_server_mode() { let (peer, info) = futures::future::join(client_event, server_event).await; assert_eq!(peer, server_peer_id); + assert_eq!(peer_id, server_peer_id); assert!(info .protocols .iter() @@ -179,7 +185,7 @@ impl MyBehaviour { kad: Behaviour::with_config( local_peer_id, MemoryStore::new(local_peer_id), - Config::default(), + Config::new(libp2p_kad::PROTOCOL_NAME), ), } } diff --git a/protocols/mdns/CHANGELOG.md b/protocols/mdns/CHANGELOG.md index cfd02232b07..45a479bf4af 100644 --- a/protocols/mdns/CHANGELOG.md +++ b/protocols/mdns/CHANGELOG.md @@ -1,3 +1,18 @@ +## 0.46.1 + +- Emit `ToSwarm::NewExternalAddrOfPeer` on discovery. + See [PR 5753](https://github.com/libp2p/rust-libp2p/pull/5753) +- Upgrade `hickory-proto`. + See [PR 5727](https://github.com/libp2p/rust-libp2p/pull/5727) + +## 0.46.0 + + +## 0.45.2 + +- Add `#[track_caller]` on all `spawn` wrappers. + See [PR 5465](https://github.com/libp2p/rust-libp2p/pull/5465). + ## 0.45.1 - Ensure `Multiaddr` handled and returned by `Behaviour` are `/p2p` terminated. diff --git a/protocols/mdns/Cargo.toml b/protocols/mdns/Cargo.toml index 97e7204cd63..ba86a82d5bb 100644 --- a/protocols/mdns/Cargo.toml +++ b/protocols/mdns/Cargo.toml @@ -2,7 +2,7 @@ name = "libp2p-mdns" edition = "2021" rust-version = { workspace = true } -version = "0.45.1" +version = "0.46.1" description = "Implementation of the libp2p mDNS discovery method" authors = ["Parity Technologies "] license = "MIT" @@ -12,20 +12,18 @@ categories = ["network-programming", "asynchronous"] [dependencies] async-std = { version = "1.12.0", optional = true } -async-io = { version = "2.2.1", optional = true } -data-encoding = "2.5.0" -futures = "0.3.29" +async-io = { version = "2.3.3", optional = true } +futures = { workspace = true } if-watch = "3.2.0" libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true } rand = "0.8.3" -smallvec = "1.11.2" -socket2 = { version = "0.5.5", features = ["all"] } -tokio = { version = "1.34", default-features = false, features = ["net", "time"], optional = true} -tracing = "0.1.37" -hickory-proto = { version = "0.24.0", default-features = false, features = ["mdns"] } -void = "1.0.2" +smallvec = "1.13.2" +socket2 = { version = "0.5.7", features = ["all"] } +tokio = { workspace = true, default-features = false, features = ["net", "time"], optional = true} +tracing = { workspace = true } +hickory-proto = { workspace = true, features = ["mdns"] } [features] tokio = ["dep:tokio", "if-watch/tokio"] @@ -33,13 +31,10 @@ async-io = ["dep:async-io", "dep:async-std", "if-watch/smol"] [dev-dependencies] async-std = { version = "1.9.0", features = ["attributes"] } -libp2p-noise = { workspace = true } libp2p-swarm = { workspace = true, features = ["tokio", "async-std"] } -libp2p-tcp = { workspace = true, features = ["tokio", "async-io"] } -libp2p-yamux = { workspace = true } -tokio = { version = "1.34", default-features = false, features = ["macros", "rt", "rt-multi-thread", "time"] } +tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread", "time"] } libp2p-swarm-test = { path = "../../swarm-test" } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +libp2p-test-utils = { workspace = true } [[test]] name = "use-async-std" @@ -54,8 +49,6 @@ required-features = ["tokio"] # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/mdns/src/behaviour.rs b/protocols/mdns/src/behaviour.rs index 4e3533f26ab..68e28cf3d63 100644 --- a/protocols/mdns/src/behaviour.rs +++ b/protocols/mdns/src/behaviour.rs @@ -22,24 +22,38 @@ mod iface; mod socket; mod timer; -use self::iface::InterfaceState; -use crate::behaviour::{socket::AsyncSocket, timer::Builder}; -use crate::Config; -use futures::channel::mpsc; -use futures::{Stream, StreamExt}; +use std::{ + cmp, + collections::{ + hash_map::{Entry, HashMap}, + VecDeque, + }, + convert::Infallible, + fmt, + future::Future, + io, + net::IpAddr, + pin::Pin, + sync::{Arc, RwLock}, + task::{Context, Poll}, + time::Instant, +}; + +use futures::{channel::mpsc, Stream, StreamExt}; use if_watch::IfEvent; -use libp2p_core::{Endpoint, Multiaddr}; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; use libp2p_identity::PeerId; -use libp2p_swarm::behaviour::FromSwarm; use libp2p_swarm::{ - dummy, ConnectionDenied, ConnectionId, ListenAddresses, NetworkBehaviour, THandler, - THandlerInEvent, THandlerOutEvent, ToSwarm, + behaviour::FromSwarm, dummy, ConnectionDenied, ConnectionId, ListenAddresses, NetworkBehaviour, + THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; use smallvec::SmallVec; -use std::collections::hash_map::{Entry, HashMap}; -use std::future::Future; -use std::sync::{Arc, RwLock}; -use std::{cmp, fmt, io, net::IpAddr, pin::Pin, task::Context, task::Poll, time::Instant}; + +use self::iface::InterfaceState; +use crate::{ + behaviour::{socket::AsyncSocket, timer::Builder}, + Config, +}; /// An abstraction to allow for compatibility with various async runtimes. pub trait Provider: 'static { @@ -55,6 +69,7 @@ pub trait Provider: 'static { /// Create a new instance of the `IfWatcher` type. fn new_watcher() -> Result; + #[track_caller] fn spawn(task: impl Future + Send + 'static) -> Self::TaskHandle; } @@ -66,11 +81,13 @@ pub trait Abort { /// The type of a [`Behaviour`] using the `async-io` implementation. #[cfg(feature = "async-io")] pub mod async_io { - use super::Provider; - use crate::behaviour::{socket::asio::AsyncUdpSocket, timer::asio::AsyncTimer, Abort}; + use std::future::Future; + use async_std::task::JoinHandle; use if_watch::smol::IfWatcher; - use std::future::Future; + + use super::Provider; + use crate::behaviour::{socket::asio::AsyncUdpSocket, timer::asio::AsyncTimer, Abort}; #[doc(hidden)] pub enum AsyncIo {} @@ -102,12 +119,14 @@ pub mod async_io { /// The type of a [`Behaviour`] using the `tokio` implementation. #[cfg(feature = "tokio")] pub mod tokio { - use super::Provider; - use crate::behaviour::{socket::tokio::TokioUdpSocket, timer::tokio::TokioTimer, Abort}; - use if_watch::tokio::IfWatcher; use std::future::Future; + + use if_watch::tokio::IfWatcher; use tokio::task::JoinHandle; + use super::Provider; + use crate::behaviour::{socket::tokio::TokioUdpSocket, timer::tokio::TokioTimer, Abort}; + #[doc(hidden)] pub enum Tokio {} @@ -168,10 +187,14 @@ where /// The current set of listen addresses. /// /// This is shared across all interface tasks using an [`RwLock`]. - /// The [`Behaviour`] updates this upon new [`FromSwarm`] events where as [`InterfaceState`]s read from it to answer inbound mDNS queries. + /// The [`Behaviour`] updates this upon new [`FromSwarm`] + /// events where as [`InterfaceState`]s read from it to answer inbound mDNS queries. listen_addresses: Arc>, local_peer_id: PeerId, + + /// Pending behaviour events to be emitted. + pending_events: VecDeque>, } impl

Behaviour

@@ -192,6 +215,7 @@ where closest_expiration: Default::default(), listen_addresses: Default::default(), local_peer_id, + pending_events: Default::default(), }) } @@ -262,6 +286,7 @@ where _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(dummy::ConnectionHandler) } @@ -272,7 +297,7 @@ where _: ConnectionId, ev: THandlerOutEvent, ) { - void::unreachable(ev) + libp2p_core::util::unreachable(ev) } fn on_swarm_event(&mut self, event: FromSwarm) { @@ -287,93 +312,113 @@ where &mut self, cx: &mut Context<'_>, ) -> Poll>> { - // Poll ifwatch. - while let Poll::Ready(Some(event)) = Pin::new(&mut self.if_watch).poll_next(cx) { - match event { - Ok(IfEvent::Up(inet)) => { - let addr = inet.addr(); - if addr.is_loopback() { - continue; - } - if addr.is_ipv4() && self.config.enable_ipv6 - || addr.is_ipv6() && !self.config.enable_ipv6 - { - continue; - } - if let Entry::Vacant(e) = self.if_tasks.entry(addr) { - match InterfaceState::::new( - addr, - self.config.clone(), - self.local_peer_id, - self.listen_addresses.clone(), - self.query_response_sender.clone(), - ) { - Ok(iface_state) => { - e.insert(P::spawn(iface_state)); - } - Err(err) => { - tracing::error!("failed to create `InterfaceState`: {}", err) + loop { + // Check for pending events and emit them. + if let Some(event) = self.pending_events.pop_front() { + return Poll::Ready(event); + } + + // Poll ifwatch. + while let Poll::Ready(Some(event)) = Pin::new(&mut self.if_watch).poll_next(cx) { + match event { + Ok(IfEvent::Up(inet)) => { + let addr = inet.addr(); + if addr.is_loopback() { + continue; + } + if addr.is_ipv4() && self.config.enable_ipv6 + || addr.is_ipv6() && !self.config.enable_ipv6 + { + continue; + } + if let Entry::Vacant(e) = self.if_tasks.entry(addr) { + match InterfaceState::::new( + addr, + self.config.clone(), + self.local_peer_id, + self.listen_addresses.clone(), + self.query_response_sender.clone(), + ) { + Ok(iface_state) => { + e.insert(P::spawn(iface_state)); + } + Err(err) => { + tracing::error!("failed to create `InterfaceState`: {}", err) + } } } } - } - Ok(IfEvent::Down(inet)) => { - if let Some(handle) = self.if_tasks.remove(&inet.addr()) { - tracing::info!(instance=%inet.addr(), "dropping instance"); + Ok(IfEvent::Down(inet)) => { + if let Some(handle) = self.if_tasks.remove(&inet.addr()) { + tracing::info!(instance=%inet.addr(), "dropping instance"); - handle.abort(); + handle.abort(); + } } + Err(err) => tracing::error!("if watch returned an error: {}", err), } - Err(err) => tracing::error!("if watch returned an error: {}", err), } - } - // Emit discovered event. - let mut discovered = Vec::new(); - - while let Poll::Ready(Some((peer, addr, expiration))) = - self.query_response_receiver.poll_next_unpin(cx) - { - if let Some((_, _, cur_expires)) = self - .discovered_nodes - .iter_mut() - .find(|(p, a, _)| *p == peer && *a == addr) + // Emit discovered event. + let mut discovered = Vec::new(); + + while let Poll::Ready(Some((peer, addr, expiration))) = + self.query_response_receiver.poll_next_unpin(cx) { - *cur_expires = cmp::max(*cur_expires, expiration); - } else { - tracing::info!(%peer, address=%addr, "discovered peer on address"); - self.discovered_nodes.push((peer, addr.clone(), expiration)); - discovered.push((peer, addr)); + if let Some((_, _, cur_expires)) = self + .discovered_nodes + .iter_mut() + .find(|(p, a, _)| *p == peer && *a == addr) + { + *cur_expires = cmp::max(*cur_expires, expiration); + } else { + tracing::info!(%peer, address=%addr, "discovered peer on address"); + self.discovered_nodes.push((peer, addr.clone(), expiration)); + discovered.push((peer, addr.clone())); + + self.pending_events + .push_back(ToSwarm::NewExternalAddrOfPeer { + peer_id: peer, + address: addr, + }); + } } - } - if !discovered.is_empty() { - let event = Event::Discovered(discovered); - return Poll::Ready(ToSwarm::GenerateEvent(event)); - } - // Emit expired event. - let now = Instant::now(); - let mut closest_expiration = None; - let mut expired = Vec::new(); - self.discovered_nodes.retain(|(peer, addr, expiration)| { - if *expiration <= now { - tracing::info!(%peer, address=%addr, "expired peer on address"); - expired.push((*peer, addr.clone())); - return false; + if !discovered.is_empty() { + let event = Event::Discovered(discovered); + // Push to the front of the queue so that the behavior event is reported before + // the individual discovered addresses. + self.pending_events + .push_front(ToSwarm::GenerateEvent(event)); + continue; + } + // Emit expired event. + let now = Instant::now(); + let mut closest_expiration = None; + let mut expired = Vec::new(); + self.discovered_nodes.retain(|(peer, addr, expiration)| { + if *expiration <= now { + tracing::info!(%peer, address=%addr, "expired peer on address"); + expired.push((*peer, addr.clone())); + return false; + } + closest_expiration = + Some(closest_expiration.unwrap_or(*expiration).min(*expiration)); + true + }); + if !expired.is_empty() { + let event = Event::Expired(expired); + self.pending_events.push_back(ToSwarm::GenerateEvent(event)); + continue; + } + if let Some(closest_expiration) = closest_expiration { + let mut timer = P::Timer::at(closest_expiration); + let _ = Pin::new(&mut timer).poll_next(cx); + + self.closest_expiration = Some(timer); } - closest_expiration = Some(closest_expiration.unwrap_or(*expiration).min(*expiration)); - true - }); - if !expired.is_empty() { - let event = Event::Expired(expired); - return Poll::Ready(ToSwarm::GenerateEvent(event)); - } - if let Some(closest_expiration) = closest_expiration { - let mut timer = P::Timer::at(closest_expiration); - let _ = Pin::new(&mut timer).poll_next(cx); - self.closest_expiration = Some(timer); + return Poll::Pending; } - Poll::Pending } } diff --git a/protocols/mdns/src/behaviour/iface.rs b/protocols/mdns/src/behaviour/iface.rs index 9302065cde2..873bb8a307b 100644 --- a/protocols/mdns/src/behaviour/iface.rs +++ b/protocols/mdns/src/behaviour/iface.rs @@ -21,27 +21,32 @@ mod dns; mod query; -use self::dns::{build_query, build_query_response, build_service_discovery_response}; -use self::query::MdnsPacket; -use crate::behaviour::{socket::AsyncSocket, timer::Builder}; -use crate::Config; -use futures::channel::mpsc; -use futures::{SinkExt, StreamExt}; -use libp2p_core::Multiaddr; -use libp2p_identity::PeerId; -use libp2p_swarm::ListenAddresses; -use socket2::{Domain, Socket, Type}; -use std::future::Future; -use std::sync::{Arc, RwLock}; use std::{ collections::VecDeque, + future::Future, io, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}, pin::Pin, + sync::{Arc, RwLock}, task::{Context, Poll}, time::{Duration, Instant}, }; +use futures::{channel::mpsc, SinkExt, StreamExt}; +use libp2p_core::Multiaddr; +use libp2p_identity::PeerId; +use libp2p_swarm::ListenAddresses; +use socket2::{Domain, Socket, Type}; + +use self::{ + dns::{build_query, build_query_response, build_service_discovery_response}, + query::MdnsPacket, +}; +use crate::{ + behaviour::{socket::AsyncSocket, timer::Builder}, + Config, +}; + /// Initial interval for starting probe const INITIAL_TIMEOUT_INTERVAL: Duration = Duration::from_millis(500); diff --git a/protocols/mdns/src/behaviour/iface/dns.rs b/protocols/mdns/src/behaviour/iface/dns.rs index 6cc5550dbe5..35cba44f4af 100644 --- a/protocols/mdns/src/behaviour/iface/dns.rs +++ b/protocols/mdns/src/behaviour/iface/dns.rs @@ -20,12 +20,13 @@ //! (M)DNS encoding and decoding on top of the `dns_parser` library. -use crate::{META_QUERY_SERVICE, SERVICE_NAME}; +use std::{borrow::Cow, cmp, error, fmt, str, time::Duration}; + use libp2p_core::Multiaddr; use libp2p_identity::PeerId; -use rand::distributions::Alphanumeric; -use rand::{thread_rng, Rng}; -use std::{borrow::Cow, cmp, error, fmt, str, time::Duration}; +use rand::{distributions::Alphanumeric, thread_rng, Rng}; + +use crate::{META_QUERY_SERVICE, SERVICE_NAME}; /// DNS TXT records can have up to 255 characters as a single string value. /// @@ -247,7 +248,7 @@ fn duration_to_secs(duration: Duration) -> u32 { let secs = duration .as_secs() .saturating_add(u64::from(duration.subsec_nanos() > 0)); - cmp::min(secs, From::from(u32::max_value())) as u32 + cmp::min(secs, From::from(u32::MAX)) as u32 } /// Appends a big-endian u32 to `out`. @@ -293,7 +294,6 @@ fn generate_peer_name() -> Vec { /// Panics if `name` has a zero-length component or a component that is too long. /// This is fine considering that this function is not public and is only called in a controlled /// environment. -/// fn append_qname(out: &mut Vec, name: &[u8]) { debug_assert!(name.is_ascii()); @@ -394,10 +394,10 @@ impl error::Error for MdnsResponseError {} #[cfg(test)] mod tests { - use super::*; use hickory_proto::op::Message; use libp2p_identity as identity; - use std::time::Duration; + + use super::*; #[test] fn build_query_correct() { diff --git a/protocols/mdns/src/behaviour/iface/query.rs b/protocols/mdns/src/behaviour/iface/query.rs index eeb699fca6b..a2a2c200b3b 100644 --- a/protocols/mdns/src/behaviour/iface/query.rs +++ b/protocols/mdns/src/behaviour/iface/query.rs @@ -18,19 +18,23 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use super::dns; -use crate::{META_QUERY_SERVICE_FQDN, SERVICE_NAME_FQDN}; +use std::{ + fmt, + net::SocketAddr, + str, + time::{Duration, Instant}, +}; + use hickory_proto::{ op::Message, rr::{Name, RData}, }; -use libp2p_core::{ - address_translation, - multiaddr::{Multiaddr, Protocol}, -}; +use libp2p_core::multiaddr::{Multiaddr, Protocol}; use libp2p_identity::PeerId; -use std::time::Instant; -use std::{fmt, net::SocketAddr, str, time::Duration}; +use libp2p_swarm::_address_translation; + +use super::dns; +use crate::{META_QUERY_SERVICE_FQDN, SERVICE_NAME_FQDN}; /// A valid mDNS packet received by the service. #[derive(Debug)] @@ -47,7 +51,7 @@ impl MdnsPacket { pub(crate) fn new_from_bytes( buf: &[u8], from: SocketAddr, - ) -> Result, hickory_proto::error::ProtoError> { + ) -> Result, hickory_proto::ProtoError> { let packet = Message::from_vec(buf)?; if packet.query().is_none() { @@ -70,7 +74,8 @@ impl MdnsPacket { .iter() .any(|q| q.name().to_utf8() == META_QUERY_SERVICE_FQDN) { - // TODO: what if multiple questions, one with SERVICE_NAME and one with META_QUERY_SERVICE? + // TODO: what if multiple questions, + // one with SERVICE_NAME and one with META_QUERY_SERVICE? return Ok(Some(MdnsPacket::ServiceDiscovery(MdnsServiceDiscovery { from, query_id: packet.header().id(), @@ -156,7 +161,7 @@ impl MdnsResponse { return None; } - let RData::PTR(record_value) = record.data()? else { + let RData::PTR(record_value) = record.data() else { return None; }; @@ -179,7 +184,7 @@ impl MdnsResponse { let new_expiration = now + peer.ttl(); peer.addresses().iter().filter_map(move |address| { - let new_addr = address_translation(address, &observed)?; + let new_addr = _address_translation(address, &observed)?; let new_addr = new_addr.with_p2p(*peer.id()).ok()?; Some((*peer.id(), new_addr, new_expiration)) @@ -238,7 +243,7 @@ impl MdnsPeer { return None; } - if let Some(RData::TXT(ref txt)) = add_record.data() { + if let RData::TXT(ref txt) = add_record.data() { Some(txt) } else { None @@ -308,8 +313,7 @@ impl fmt::Debug for MdnsPeer { #[cfg(test)] mod tests { - use super::super::dns::build_query_response; - use super::*; + use super::{super::dns::build_query_response, *}; #[test] fn test_create_mdns_peer() { @@ -337,7 +341,7 @@ mod tests { if record.name().to_utf8() != SERVICE_NAME_FQDN { return None; } - let Some(RData::PTR(record_value)) = record.data() else { + let RData::PTR(record_value) = record.data() else { return None; }; Some(record_value) diff --git a/protocols/mdns/src/behaviour/socket.rs b/protocols/mdns/src/behaviour/socket.rs index fa9e0fbaf1e..cf11450fb4b 100644 --- a/protocols/mdns/src/behaviour/socket.rs +++ b/protocols/mdns/src/behaviour/socket.rs @@ -20,12 +20,12 @@ use std::{ io::Error, - marker::Unpin, net::{SocketAddr, UdpSocket}, task::{Context, Poll}, }; -/// Interface that must be implemented by the different runtimes to use the [`UdpSocket`] in async mode +/// Interface that must be implemented by the different runtimes to use the [`UdpSocket`] in async +/// mode #[allow(unreachable_pub)] // Users should not depend on this. pub trait AsyncSocket: Unpin + Send + 'static { /// Create the async socket from the [`std::net::UdpSocket`] @@ -33,7 +33,8 @@ pub trait AsyncSocket: Unpin + Send + 'static { where Self: Sized; - /// Attempts to receive a single packet on the socket from the remote address to which it is connected. + /// Attempts to receive a single packet on the socket + /// from the remote address to which it is connected. fn poll_read( &mut self, _cx: &mut Context, @@ -51,10 +52,11 @@ pub trait AsyncSocket: Unpin + Send + 'static { #[cfg(feature = "async-io")] pub(crate) mod asio { - use super::*; use async_io::Async; use futures::FutureExt; + use super::*; + /// AsyncIo UdpSocket pub(crate) type AsyncUdpSocket = Async; impl AsyncSocket for AsyncUdpSocket { @@ -93,9 +95,10 @@ pub(crate) mod asio { #[cfg(feature = "tokio")] pub(crate) mod tokio { - use super::*; use ::tokio::{io::ReadBuf, net::UdpSocket as TkUdpSocket}; + use super::*; + /// Tokio ASync Socket` pub(crate) type TokioUdpSocket = TkUdpSocket; impl AsyncSocket for TokioUdpSocket { diff --git a/protocols/mdns/src/behaviour/timer.rs b/protocols/mdns/src/behaviour/timer.rs index 29622be9a38..5fdb1beffae 100644 --- a/protocols/mdns/src/behaviour/timer.rs +++ b/protocols/mdns/src/behaviour/timer.rs @@ -18,10 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::{ - marker::Unpin, - time::{Duration, Instant}, -}; +use std::time::{Duration, Instant}; /// Simple wrapper for the different type of timers #[derive(Debug)] @@ -45,14 +42,16 @@ pub trait Builder: Send + Unpin + 'static { #[cfg(feature = "async-io")] pub(crate) mod asio { - use super::*; - use async_io::Timer as AsioTimer; - use futures::Stream; use std::{ pin::Pin, task::{Context, Poll}, }; + use async_io::Timer as AsioTimer; + use futures::Stream; + + use super::*; + /// Async Timer pub(crate) type AsyncTimer = Timer; impl Builder for AsyncTimer { @@ -86,14 +85,16 @@ pub(crate) mod asio { #[cfg(feature = "tokio")] pub(crate) mod tokio { - use super::*; - use ::tokio::time::{self, Instant as TokioInstant, Interval, MissedTickBehavior}; - use futures::Stream; use std::{ pin::Pin, task::{Context, Poll}, }; + use ::tokio::time::{self, Instant as TokioInstant, Interval, MissedTickBehavior}; + use futures::Stream; + + use super::*; + /// Tokio wrapper pub(crate) type TokioTimer = Timer; impl Builder for TokioTimer { @@ -101,7 +102,7 @@ pub(crate) mod tokio { // Taken from: https://docs.rs/async-io/1.7.0/src/async_io/lib.rs.html#91 let mut inner = time::interval_at( TokioInstant::from_std(instant), - Duration::new(std::u64::MAX, 1_000_000_000 - 1), + Duration::new(u64::MAX, 1_000_000_000 - 1), ); inner.set_missed_tick_behavior(MissedTickBehavior::Skip); Self { inner } @@ -128,7 +129,7 @@ pub(crate) mod tokio { } fn size_hint(&self) -> (usize, Option) { - (std::usize::MAX, None) + (usize::MAX, None) } } } diff --git a/protocols/mdns/src/lib.rs b/protocols/mdns/src/lib.rs index 4823d740272..a0086a0e2d5 100644 --- a/protocols/mdns/src/lib.rs +++ b/protocols/mdns/src/lib.rs @@ -31,21 +31,20 @@ //! This crate provides a `Mdns` and `TokioMdns`, depending on the enabled features, which //! implements the `NetworkBehaviour` trait. This struct will automatically discover other //! libp2p nodes on the local network. -//! #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use std::net::{Ipv4Addr, Ipv6Addr}; -use std::time::Duration; +use std::{ + net::{Ipv4Addr, Ipv6Addr}, + time::Duration, +}; mod behaviour; -pub use crate::behaviour::{Behaviour, Event}; - #[cfg(feature = "async-io")] pub use crate::behaviour::async_io; - #[cfg(feature = "tokio")] pub use crate::behaviour::tokio; +pub use crate::behaviour::{Behaviour, Event}; /// The DNS service name for all libp2p peers used to query for addresses. const SERVICE_NAME: &[u8] = b"_p2p._udp.local"; diff --git a/protocols/mdns/tests/use-async-std.rs b/protocols/mdns/tests/use-async-std.rs index 549f70978af..9ee2b7659ea 100644 --- a/protocols/mdns/tests/use-async-std.rs +++ b/protocols/mdns/tests/use-async-std.rs @@ -18,28 +18,23 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE.use futures::StreamExt; +use std::time::Duration; + use futures::future::Either; -use libp2p_mdns::Event; -use libp2p_mdns::{async_io::Behaviour, Config}; +use libp2p_mdns::{async_io::Behaviour, Config, Event}; use libp2p_swarm::{Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt as _; -use std::time::Duration; -use tracing_subscriber::EnvFilter; #[async_std::test] async fn test_discovery_async_std_ipv4() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); run_discovery_test(Config::default()).await } #[async_std::test] async fn test_discovery_async_std_ipv6() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let config = Config { enable_ipv6: true, @@ -50,9 +45,7 @@ async fn test_discovery_async_std_ipv6() { #[async_std::test] async fn test_expired_async_std() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let config = Config { ttl: Duration::from_secs(1), @@ -85,9 +78,7 @@ async fn test_expired_async_std() { #[async_std::test] async fn test_no_expiration_on_close_async_std() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let config = Config { ttl: Duration::from_secs(120), query_interval: Duration::from_secs(10), diff --git a/protocols/mdns/tests/use-tokio.rs b/protocols/mdns/tests/use-tokio.rs index cf0d9f4bed4..a48f84217a3 100644 --- a/protocols/mdns/tests/use-tokio.rs +++ b/protocols/mdns/tests/use-tokio.rs @@ -17,27 +17,23 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE.use futures::StreamExt; +use std::time::Duration; + use futures::future::Either; use libp2p_mdns::{tokio::Behaviour, Config, Event}; use libp2p_swarm::{Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt as _; -use std::time::Duration; -use tracing_subscriber::EnvFilter; #[tokio::test] async fn test_discovery_tokio_ipv4() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); run_discovery_test(Config::default()).await } #[tokio::test] async fn test_discovery_tokio_ipv6() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let config = Config { enable_ipv6: true, @@ -48,9 +44,7 @@ async fn test_discovery_tokio_ipv6() { #[tokio::test] async fn test_expired_tokio() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let config = Config { ttl: Duration::from_secs(1), diff --git a/protocols/perf/CHANGELOG.md b/protocols/perf/CHANGELOG.md index 4e448d7f44a..c5eda88d97d 100644 --- a/protocols/perf/CHANGELOG.md +++ b/protocols/perf/CHANGELOG.md @@ -1,3 +1,16 @@ +## 0.4.0 + + +- Add ConnectionError to FromSwarm::ConnectionClosed. + See [PR 5485](https://github.com/libp2p/rust-libp2p/pull/5485). + +- Deprecate `void` crate. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). + +## 0.3.1 +- Use `web-time` instead of `instant`. + See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). + ## 0.3.0 - Continuously measure on single connection (iperf-style). diff --git a/protocols/perf/Cargo.toml b/protocols/perf/Cargo.toml index c2fc146f231..0b994447525 100644 --- a/protocols/perf/Cargo.toml +++ b/protocols/perf/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-perf" edition = "2021" rust-version = { workspace = true } description = "libp2p perf protocol implementation" -version = "0.3.0" +version = "0.4.0" authors = ["Max Inden "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -12,38 +12,33 @@ categories = ["network-programming", "asynchronous"] [dependencies] anyhow = "1" -clap = { version = "4.4.10", features = ["derive"] } -futures = "0.3.29" +clap = { version = "4.5.6", features = ["derive"] } +futures = { workspace = true } futures-bounded = { workspace = true } futures-timer = "3.0" -instant = "0.1.12" +web-time = { workspace = true } libp2p = { workspace = true, features = ["tokio", "tcp", "quic", "tls", "yamux", "dns"] } libp2p-core = { workspace = true } -libp2p-dns = { workspace = true, features = ["tokio"] } libp2p-identity = { workspace = true, features = ["rand"] } -libp2p-quic = { workspace = true, features = ["tokio"] } libp2p-swarm = { workspace = true, features = ["macros", "tokio"] } libp2p-tcp = { workspace = true, features = ["tokio"] } libp2p-tls = { workspace = true } libp2p-yamux = { workspace = true } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -thiserror = "1.0" -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -tokio = { version = "1.34", default-features = false, features = ["macros", "rt", "rt-multi-thread"] } -void = "1" +thiserror = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } +libp2p-test-utils = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] } [dev-dependencies] -rand = "0.8" libp2p-swarm-test = { path = "../../swarm-test" } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/perf/Dockerfile b/protocols/perf/Dockerfile index 6523e3bede1..f68ea6ef211 100644 --- a/protocols/perf/Dockerfile +++ b/protocols/perf/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1.5-labs -FROM rust:1.67.0 as builder +FROM rust:1.81.0 as builder # Run with access to the target cache to speed up builds WORKDIR /workspace diff --git a/protocols/perf/src/bin/perf.rs b/protocols/perf/src/bin/perf.rs index 9ac8f0a6cde..506455f081a 100644 --- a/protocols/perf/src/bin/perf.rs +++ b/protocols/perf/src/bin/perf.rs @@ -23,15 +23,16 @@ use std::{net::SocketAddr, str::FromStr}; use anyhow::{bail, Result}; use clap::Parser; use futures::StreamExt; -use instant::{Duration, Instant}; -use libp2p::core::{multiaddr::Protocol, upgrade, Multiaddr}; -use libp2p::identity::PeerId; -use libp2p::swarm::{NetworkBehaviour, Swarm, SwarmEvent}; -use libp2p::SwarmBuilder; -use libp2p_perf::{client, server}; -use libp2p_perf::{Final, Intermediate, Run, RunParams, RunUpdate}; +use libp2p::{ + core::{multiaddr::Protocol, upgrade, Multiaddr}, + identity::PeerId, + swarm::{NetworkBehaviour, Swarm, SwarmEvent}, + SwarmBuilder, +}; +use libp2p_perf::{client, server, Final, Intermediate, Run, RunParams, RunUpdate}; use serde::{Deserialize, Serialize}; use tracing_subscriber::EnvFilter; +use web_time::{Duration, Instant}; #[derive(Debug, Parser)] #[clap(name = "libp2p perf client")] diff --git a/protocols/perf/src/client.rs b/protocols/perf/src/client.rs index c4614e979db..7699bc85c17 100644 --- a/protocols/perf/src/client.rs +++ b/protocols/perf/src/client.rs @@ -21,11 +21,13 @@ mod behaviour; mod handler; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{ + convert::Infallible, + sync::atomic::{AtomicUsize, Ordering}, +}; pub use behaviour::{Behaviour, Event}; use libp2p_swarm::StreamUpgradeError; -use void::Void; static NEXT_RUN_ID: AtomicUsize = AtomicUsize::new(1); @@ -43,7 +45,7 @@ impl RunId { #[derive(thiserror::Error, Debug)] pub enum RunError { #[error(transparent)] - Upgrade(#[from] StreamUpgradeError), + Upgrade(#[from] StreamUpgradeError), #[error("Failed to execute perf run: {0}")] Io(#[from] std::io::Error), } diff --git a/protocols/perf/src/client/behaviour.rs b/protocols/perf/src/client/behaviour.rs index 880bcdd9c83..86c85d61da9 100644 --- a/protocols/perf/src/client/behaviour.rs +++ b/protocols/perf/src/client/behaviour.rs @@ -25,17 +25,15 @@ use std::{ task::{Context, Poll}, }; -use libp2p_core::Multiaddr; +use libp2p_core::{transport::PortUse, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ derive_prelude::ConnectionEstablished, ConnectionClosed, ConnectionId, FromSwarm, NetworkBehaviour, NotifyHandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; -use crate::RunParams; -use crate::{client::handler::Handler, RunUpdate}; - use super::{RunError, RunId}; +use crate::{client::handler::Handler, RunParams, RunUpdate}; #[derive(Debug)] pub struct Event { @@ -92,6 +90,7 @@ impl NetworkBehaviour for Behaviour { _peer: PeerId, _addr: &Multiaddr, _role_override: libp2p_core::Endpoint, + _port_use: PortUse, ) -> Result, libp2p_swarm::ConnectionDenied> { Ok(Handler::default()) } @@ -116,6 +115,7 @@ impl NetworkBehaviour for Behaviour { connection_id: _, endpoint: _, remaining_established, + .. }) => { if remaining_established == 0 { assert!(self.connected.remove(&peer_id)); diff --git a/protocols/perf/src/client/handler.rs b/protocols/perf/src/client/handler.rs index 2a2c5499fc2..fc427d8134c 100644 --- a/protocols/perf/src/client/handler.rs +++ b/protocols/perf/src/client/handler.rs @@ -36,8 +36,10 @@ use libp2p_swarm::{ ConnectionHandler, ConnectionHandlerEvent, StreamProtocol, SubstreamProtocol, }; -use crate::client::{RunError, RunId}; -use crate::{RunParams, RunUpdate}; +use crate::{ + client::{RunError, RunId}, + RunParams, RunUpdate, +}; #[derive(Debug)] pub struct Command { @@ -112,9 +114,11 @@ impl ConnectionHandler for Handler { >, ) { match event { + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { protocol, .. - }) => void::unreachable(protocol), + }) => libp2p_core::util::unreachable(protocol), ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound { protocol, info: (), @@ -144,8 +148,10 @@ impl ConnectionHandler for Handler { result: Err(error.into()), })); } + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::ListenUpgradeError(ListenUpgradeError { info: (), error }) => { - void::unreachable(error) + libp2p_core::util::unreachable(error) } _ => {} } diff --git a/protocols/perf/src/lib.rs b/protocols/perf/src/lib.rs index f9db96aa9d9..be950ac87a2 100644 --- a/protocols/perf/src/lib.rs +++ b/protocols/perf/src/lib.rs @@ -26,8 +26,8 @@ use std::fmt::Display; -use instant::Duration; use libp2p_swarm::StreamProtocol; +use web_time::Duration; pub mod client; mod protocol; diff --git a/protocols/perf/src/protocol.rs b/protocols/perf/src/protocol.rs index d2d65b42303..d07c90fa951 100644 --- a/protocols/perf/src/protocol.rs +++ b/protocols/perf/src/protocol.rs @@ -18,14 +18,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures_timer::Delay; -use instant::Instant; use std::time::Duration; use futures::{ future::{select, Either}, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, FutureExt, SinkExt, Stream, StreamExt, }; +use futures_timer::Delay; +use web_time::Instant; use crate::{Final, Intermediate, Run, RunDuration, RunParams, RunUpdate}; diff --git a/protocols/perf/src/server/behaviour.rs b/protocols/perf/src/server/behaviour.rs index da24d763606..22466bfe56a 100644 --- a/protocols/perf/src/server/behaviour.rs +++ b/protocols/perf/src/server/behaviour.rs @@ -25,13 +25,13 @@ use std::{ task::{Context, Poll}, }; +use libp2p_core::transport::PortUse; use libp2p_identity::PeerId; use libp2p_swarm::{ ConnectionId, FromSwarm, NetworkBehaviour, THandlerInEvent, THandlerOutEvent, ToSwarm, }; -use crate::server::handler::Handler; -use crate::Run; +use crate::{server::handler::Handler, Run}; #[derive(Debug)] pub struct Event { @@ -71,6 +71,7 @@ impl NetworkBehaviour for Behaviour { _peer: PeerId, _addr: &libp2p_core::Multiaddr, _role_override: libp2p_core::Endpoint, + _port_use: PortUse, ) -> Result, libp2p_swarm::ConnectionDenied> { Ok(Handler::default()) } diff --git a/protocols/perf/src/server/handler.rs b/protocols/perf/src/server/handler.rs index ddfe8f881e5..a78485cd9b5 100644 --- a/protocols/perf/src/server/handler.rs +++ b/protocols/perf/src/server/handler.rs @@ -18,7 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::task::{Context, Poll}; +use std::{ + convert::Infallible, + task::{Context, Poll}, +}; use futures::FutureExt; use libp2p_core::upgrade::{DeniedUpgrade, ReadyUpgrade}; @@ -30,7 +33,6 @@ use libp2p_swarm::{ ConnectionHandler, ConnectionHandlerEvent, StreamProtocol, SubstreamProtocol, }; use tracing::error; -use void::Void; use crate::Run; @@ -61,11 +63,11 @@ impl Default for Handler { } impl ConnectionHandler for Handler { - type FromBehaviour = Void; + type FromBehaviour = Infallible; type ToBehaviour = Event; type InboundProtocol = ReadyUpgrade; type OutboundProtocol = DeniedUpgrade; - type OutboundOpenInfo = Void; + type OutboundOpenInfo = Infallible; type InboundOpenInfo = (); fn listen_protocol(&self) -> SubstreamProtocol { @@ -73,7 +75,9 @@ impl ConnectionHandler for Handler { } fn on_behaviour_event(&mut self, v: Self::FromBehaviour) { - void::unreachable(v) + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(v) } fn on_connection_event( @@ -98,18 +102,24 @@ impl ConnectionHandler for Handler { tracing::warn!("Dropping inbound stream because we are at capacity"); } } + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound { info, .. }) => { - void::unreachable(info) + libp2p_core::util::unreachable(info) } + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::DialUpgradeError(DialUpgradeError { info, .. }) => { - void::unreachable(info) + libp2p_core::util::unreachable(info) } ConnectionEvent::AddressChange(_) | ConnectionEvent::LocalProtocolsChange(_) | ConnectionEvent::RemoteProtocolsChange(_) => {} + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::ListenUpgradeError(ListenUpgradeError { info: (), error }) => { - void::unreachable(error) + libp2p_core::util::unreachable(error) } _ => {} } diff --git a/protocols/perf/tests/lib.rs b/protocols/perf/tests/lib.rs index 017d475befd..c265b0e2e61 100644 --- a/protocols/perf/tests/lib.rs +++ b/protocols/perf/tests/lib.rs @@ -24,13 +24,10 @@ use libp2p_perf::{ }; use libp2p_swarm::{Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt; -use tracing_subscriber::EnvFilter; #[tokio::test] async fn perf() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut server = Swarm::new_ephemeral(|_| server::Behaviour::new()); let server_peer_id = *server.local_peer_id(); diff --git a/protocols/ping/CHANGELOG.md b/protocols/ping/CHANGELOG.md index 33e0139b996..d6e71b2c2d0 100644 --- a/protocols/ping/CHANGELOG.md +++ b/protocols/ping/CHANGELOG.md @@ -1,3 +1,28 @@ +## 0.45.1 + +- Deprecate `void` crate. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). + +## 0.45.0 + + + +## 0.44.2 + +- Use `web-time` instead of `instant`. + See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). + +- Fix panic in WASM caused by retrying on dial upgrade errors. + See [PR 5447](https://github.com/libp2p/rust-libp2p/pull/5447). + +## 0.44.1 + +- Impose `Sync` on `ping::Failure::Other`. + `ping::Event` can now be shared between threads. + See [PR 5250] + +[PR 5250]: https://github.com/libp2p/rust-libp2p/pull/5250 + ## 0.44.0 diff --git a/protocols/ping/Cargo.toml b/protocols/ping/Cargo.toml index 28ae5d39b62..83f3b6460c9 100644 --- a/protocols/ping/Cargo.toml +++ b/protocols/ping/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-ping" edition = "2021" rust-version = { workspace = true } description = "Ping protocol for libp2p" -version = "0.44.0" +version = "0.45.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,30 +11,25 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -either = "1.9.0" -futures = "0.3.29" -futures-timer = "3.0.2" -instant = "0.1.12" +futures = { workspace = true } +futures-timer = "3.0.3" +web-time = { workspace = true } libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true } rand = "0.8" -tracing = "0.1.37" -void = "1.0" +tracing = { workspace = true } [dev-dependencies] -async-std = "1.6.2" libp2p-swarm = { workspace = true, features = ["macros"] } libp2p-swarm-test = { path = "../../swarm-test" } quickcheck = { workspace = true } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tokio = {workspace = true, features = ["rt", "macros"]} # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/ping/src/handler.rs b/protocols/ping/src/handler.rs index 5e6fc2cd2cf..c7d65c64500 100644 --- a/protocols/ping/src/handler.rs +++ b/protocols/ping/src/handler.rs @@ -18,26 +18,28 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{protocol, PROTOCOL_NAME}; -use futures::future::{BoxFuture, Either}; -use futures::prelude::*; -use futures_timer::Delay; -use libp2p_core::upgrade::ReadyUpgrade; -use libp2p_swarm::handler::{ - ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, -}; -use libp2p_swarm::{ - ConnectionHandler, ConnectionHandlerEvent, Stream, StreamProtocol, StreamUpgradeError, - SubstreamProtocol, -}; -use std::collections::VecDeque; use std::{ + collections::VecDeque, + convert::Infallible, error::Error, fmt, io, task::{Context, Poll}, time::Duration, }; -use void::Void; + +use futures::{ + future::{BoxFuture, Either}, + prelude::*, +}; +use futures_timer::Delay; +use libp2p_core::upgrade::ReadyUpgrade; +use libp2p_swarm::{ + handler::{ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound}, + ConnectionHandler, ConnectionHandlerEvent, Stream, StreamProtocol, StreamUpgradeError, + SubstreamProtocol, +}; + +use crate::{protocol, PROTOCOL_NAME}; /// The configuration for outbound pings. #[derive(Debug, Clone)] @@ -57,8 +59,7 @@ impl Config { /// These settings have the following effect: /// /// * A ping is sent every 15 seconds on a healthy connection. - /// * Every ping sent must yield a response within 20 seconds in order to - /// be successful. + /// * Every ping sent must yield a response within 20 seconds in order to be successful. pub fn new() -> Self { Self { timeout: Duration::from_secs(20), @@ -95,12 +96,12 @@ pub enum Failure { Unsupported, /// The ping failed for reasons other than a timeout. Other { - error: Box, + error: Box, }, } impl Failure { - fn other(e: impl std::error::Error + Send + 'static) -> Self { + fn other(e: impl std::error::Error + Send + Sync + 'static) -> Self { Self::Other { error: Box::new(e) } } } @@ -184,6 +185,18 @@ impl Handler { ) { self.outbound = None; // Request a new substream on the next `poll`. + // Timer is already polled and expired before substream request is initiated + // and will be polled again later on in our `poll` because we reset `self.outbound`. + // + // `futures-timer` allows an expired timer to be polled again and returns + // immediately `Poll::Ready`. However in its WASM implementation there is + // a bug that causes the expired timer to panic. + // This is a workaround until a proper fix is merged and released. + // See libp2p/rust-libp2p#5447 for more info. + // + // TODO: remove when async-rs/futures-timer#74 gets merged. + self.interval.reset(Duration::new(0, 0)); + let error = match error { StreamUpgradeError::NegotiationFailed => { debug_assert_eq!(self.state, State::Active); @@ -198,7 +211,9 @@ impl Handler { "ping protocol negotiation timed out", )), }, - StreamUpgradeError::Apply(e) => void::unreachable(e), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + StreamUpgradeError::Apply(e) => libp2p_core::util::unreachable(e), StreamUpgradeError::Io(e) => Failure::Other { error: Box::new(e) }, }; @@ -207,7 +222,7 @@ impl Handler { } impl ConnectionHandler for Handler { - type FromBehaviour = Void; + type FromBehaviour = Infallible; type ToBehaviour = Result; type InboundProtocol = ReadyUpgrade; type OutboundProtocol = ReadyUpgrade; @@ -218,7 +233,7 @@ impl ConnectionHandler for Handler { SubstreamProtocol::new(ReadyUpgrade::new(PROTOCOL_NAME), ()) } - fn on_behaviour_event(&mut self, _: Void) {} + fn on_behaviour_event(&mut self, _: Infallible) {} #[tracing::instrument(level = "trace", name = "ConnectionHandler::poll", skip(self, cx))] fn poll( diff --git a/protocols/ping/src/lib.rs b/protocols/ping/src/lib.rs index 5eaa6d4952a..d48bcbc98ab 100644 --- a/protocols/ping/src/lib.rs +++ b/protocols/ping/src/lib.rs @@ -27,9 +27,11 @@ //! # Usage //! //! The [`Behaviour`] struct implements the [`NetworkBehaviour`] trait. -//! It will respond to inbound ping requests and periodically send outbound ping requests on every established connection. +//! It will respond to inbound ping requests and periodically send outbound ping requests on every +//! established connection. //! -//! It is up to the user to implement a health-check / connection management policy based on the ping protocol. +//! It is up to the user to implement a health-check / connection management policy based on the +//! ping protocol. //! //! For example: //! @@ -39,8 +41,10 @@ //! //! Users should inspect emitted [`Event`]s and call APIs on [`Swarm`]: //! -//! - [`Swarm::close_connection`](libp2p_swarm::Swarm::close_connection) to close a specific connection -//! - [`Swarm::disconnect_peer_id`](libp2p_swarm::Swarm::disconnect_peer_id) to close all connections to a peer +//! - [`Swarm::close_connection`](libp2p_swarm::Swarm::close_connection) to close a specific +//! connection +//! - [`Swarm::disconnect_peer_id`](libp2p_swarm::Swarm::disconnect_peer_id) to close all +//! connections to a peer //! //! [`Swarm`]: libp2p_swarm::Swarm //! [`Transport`]: libp2p_core::Transport @@ -50,21 +54,22 @@ mod handler; mod protocol; +use std::{ + collections::VecDeque, + task::{Context, Poll}, + time::Duration, +}; + use handler::Handler; -use libp2p_core::{Endpoint, Multiaddr}; +pub use handler::{Config, Failure}; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ behaviour::FromSwarm, ConnectionDenied, ConnectionId, NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; -use std::time::Duration; -use std::{ - collections::VecDeque, - task::{Context, Poll}, -}; pub use self::protocol::PROTOCOL_NAME; -pub use handler::{Config, Failure}; /// A [`NetworkBehaviour`] that responds to inbound pings and /// periodically sends outbound pings on every established connection. @@ -124,6 +129,7 @@ impl NetworkBehaviour for Behaviour { _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(Handler::new(self.config.clone())) } diff --git a/protocols/ping/src/protocol.rs b/protocols/ping/src/protocol.rs index 28549e1c198..5e84f55e090 100644 --- a/protocols/ping/src/protocol.rs +++ b/protocols/ping/src/protocol.rs @@ -18,11 +18,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::{io, time::Duration}; + use futures::prelude::*; -use instant::Instant; use libp2p_swarm::StreamProtocol; use rand::{distributions, prelude::*}; -use std::{io, time::Duration}; +use web_time::Instant; pub const PROTOCOL_NAME: StreamProtocol = StreamProtocol::new("/ipfs/ping/1.0.0"); @@ -40,12 +41,10 @@ pub const PROTOCOL_NAME: StreamProtocol = StreamProtocol::new("/ipfs/ping/1.0.0" /// Successful pings report the round-trip time. /// /// > **Note**: The round-trip time of a ping may be subject to delays induced -/// > by the underlying transport, e.g. in the case of TCP there is -/// > Nagle's algorithm, delayed acks and similar configuration options -/// > which can affect latencies especially on otherwise low-volume -/// > connections. -#[derive(Default, Debug, Copy, Clone)] -pub(crate) struct Ping; +/// > by the underlying transport, e.g. in the case of TCP there is +/// > Nagle's algorithm, delayed acks and similar configuration options +/// > which can affect latencies especially on otherwise low-volume +/// > connections. const PING_SIZE: usize = 32; /// Sends a ping and waits for the pong. @@ -83,17 +82,17 @@ where #[cfg(test)] mod tests { - use super::*; use futures::StreamExt; use libp2p_core::{ multiaddr::multiaddr, - transport::{memory::MemoryTransport, ListenerId, Transport}, + transport::{memory::MemoryTransport, DialOpts, ListenerId, PortUse, Transport}, + Endpoint, }; - use rand::{thread_rng, Rng}; - use std::time::Duration; - #[test] - fn ping_pong() { + use super::*; + + #[tokio::test] + async fn ping_pong() { let mem_addr = multiaddr![Memory(thread_rng().gen::())]; let mut transport = MemoryTransport::new().boxed(); transport.listen_on(ListenerId::next(), mem_addr).unwrap(); @@ -104,21 +103,25 @@ mod tests { .and_then(|ev| ev.into_new_address()) .expect("MemoryTransport not listening on an address!"); - async_std::task::spawn(async move { + tokio::spawn(async move { let transport_event = transport.next().await.unwrap(); let (listener_upgrade, _) = transport_event.into_incoming().unwrap(); let conn = listener_upgrade.await.unwrap(); recv_ping(conn).await.unwrap(); }); - async_std::task::block_on(async move { - let c = MemoryTransport::new() - .dial(listener_addr) - .unwrap() - .await - .unwrap(); - let (_, rtt) = send_ping(c).await.unwrap(); - assert!(rtt > Duration::from_secs(0)); - }); + let c = MemoryTransport::new() + .dial( + listener_addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap(); + let (_, rtt) = send_ping(c).await.unwrap(); + assert!(rtt > Duration::from_secs(0)); } } diff --git a/protocols/ping/tests/ping.rs b/protocols/ping/tests/ping.rs index 3ca469f16a8..210f9435e4a 100644 --- a/protocols/ping/tests/ping.rs +++ b/protocols/ping/tests/ping.rs @@ -20,22 +20,22 @@ //! Integration tests for the `Ping` network behaviour. +use std::{num::NonZeroU8, time::Duration}; + use libp2p_ping as ping; -use libp2p_swarm::dummy; -use libp2p_swarm::{Swarm, SwarmEvent}; +use libp2p_swarm::{dummy, Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt; use quickcheck::*; -use std::{num::NonZeroU8, time::Duration}; -#[test] -fn ping_pong() { +#[tokio::test] +async fn ping_pong() { fn prop(count: NonZeroU8) { let cfg = ping::Config::new().with_interval(Duration::from_millis(10)); let mut swarm1 = Swarm::new_ephemeral(|_| ping::Behaviour::new(cfg.clone())); let mut swarm2 = Swarm::new_ephemeral(|_| ping::Behaviour::new(cfg.clone())); - async_std::task::block_on(async { + tokio::spawn(async move { swarm1.listen().with_memory_addr_external().await; swarm2.connect(&mut swarm1).await; @@ -61,16 +61,16 @@ fn assert_ping_rtt_less_than_50ms(e: ping::Event) { assert!(rtt < Duration::from_millis(50)) } -#[test] -fn unsupported_doesnt_fail() { +#[tokio::test] +async fn unsupported_doesnt_fail() { let mut swarm1 = Swarm::new_ephemeral(|_| dummy::Behaviour); let mut swarm2 = Swarm::new_ephemeral(|_| ping::Behaviour::new(ping::Config::new())); - let result = async_std::task::block_on(async { + let result = { swarm1.listen().with_memory_addr_external().await; swarm2.connect(&mut swarm1).await; let swarm1_peer_id = *swarm1.local_peer_id(); - async_std::task::spawn(swarm1.loop_on_next()); + tokio::spawn(swarm1.loop_on_next()); loop { match swarm2.next_swarm_event().await { @@ -89,7 +89,7 @@ fn unsupported_doesnt_fail() { _ => {} } } - }); + }; result.expect("node with ping should not fail connection due to unsupported protocol"); } diff --git a/protocols/relay/CHANGELOG.md b/protocols/relay/CHANGELOG.md index aaade5e48f9..8119c24a491 100644 --- a/protocols/relay/CHANGELOG.md +++ b/protocols/relay/CHANGELOG.md @@ -1,3 +1,28 @@ +## 0.18.1 + +- Deprecate `void` crate. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). + +## 0.18.0 + + + +## 0.17.3 +- Use `web-time` instead of `instant`. + See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). +- Fix manual closure of relayed listener. + See [PR 5491](https://github.com/libp2p/rust-libp2p/pull/5491) +- Add resource limits to `CircuitReq` to be set + See [PR 5493](https://github.com/libp2p/rust-libp2p/pull/5493) + + +## 0.17.2 + +- Fix support for unlimited relay connection according to spec. + See [PR 5244](https://github.com/libp2p/rust-libp2p/pull/5244). +- use `web_time` `Instant` and `SystemTime` versions for wasm support. + See [PR 5328](https://github.com/libp2p/rust-libp2p/pull/5328). + ## 0.17.1 - Automatically register relayed addresses as external addresses. diff --git a/protocols/relay/Cargo.toml b/protocols/relay/Cargo.toml index 2ef9ed41019..e7e447e7d16 100644 --- a/protocols/relay/Cargo.toml +++ b/protocols/relay/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-relay" edition = "2021" rust-version = { workspace = true } description = "Communications relaying for libp2p" -version = "0.17.1" +version = "0.18.1" authors = ["Parity Technologies ", "Max Inden "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -13,11 +13,11 @@ categories = ["network-programming", "asynchronous"] [dependencies] asynchronous-codec = { workspace = true } bytes = "1" -either = "1.9.0" -futures = "0.3.29" +either = "1.12.0" +futures = { workspace = true } futures-timer = "3" futures-bounded = { workspace = true } -instant = "0.1.12" +web-time = { workspace = true } libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true } @@ -25,9 +25,8 @@ quick-protobuf = "0.8" quick-protobuf-codec = { workspace = true } rand = "0.8.4" static_assertions = "1" -thiserror = "1.0" -tracing = "0.1.37" -void = "1" +thiserror = { workspace = true } +tracing = { workspace = true } [dev-dependencies] libp2p-identity = { workspace = true, features = ["rand"] } @@ -37,15 +36,12 @@ libp2p-swarm = { workspace = true, features = ["macros", "async-std"] } libp2p-swarm-test = { workspace = true } libp2p-yamux = { workspace = true } quickcheck = { workspace = true } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } - +libp2p-test-utils = { workspace = true } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/relay/src/behaviour.rs b/protocols/relay/src/behaviour.rs index 59f679c9890..968642b3f1f 100644 --- a/protocols/relay/src/behaviour.rs +++ b/protocols/relay/src/behaviour.rs @@ -22,25 +22,30 @@ pub(crate) mod handler; pub(crate) mod rate_limiter; -use crate::behaviour::handler::Handler; -use crate::multiaddr_ext::MultiaddrExt; -use crate::proto; -use crate::protocol::{inbound_hop, outbound_stop}; +use std::{ + collections::{hash_map, HashMap, HashSet, VecDeque}, + num::NonZeroU32, + ops::Add, + task::{Context, Poll}, + time::Duration, +}; + use either::Either; -use instant::Instant; -use libp2p_core::multiaddr::Protocol; -use libp2p_core::{ConnectedPoint, Endpoint, Multiaddr}; +use libp2p_core::{multiaddr::Protocol, transport::PortUse, ConnectedPoint, Endpoint, Multiaddr}; use libp2p_identity::PeerId; -use libp2p_swarm::behaviour::{ConnectionClosed, FromSwarm}; use libp2p_swarm::{ + behaviour::{ConnectionClosed, FromSwarm}, dummy, ConnectionDenied, ConnectionId, ExternalAddresses, NetworkBehaviour, NotifyHandler, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; -use std::collections::{hash_map, HashMap, HashSet, VecDeque}; -use std::num::NonZeroU32; -use std::ops::Add; -use std::task::{Context, Poll}; -use std::time::Duration; +use web_time::Instant; + +use crate::{ + behaviour::handler::Handler, + multiaddr_ext::MultiaddrExt, + proto, + protocol::{inbound_hop, outbound_stop}, +}; /// Configuration for the relay [`Behaviour`]. /// @@ -119,12 +124,14 @@ impl std::fmt::Debug for Config { impl Default for Config { fn default() -> Self { let reservation_rate_limiters = vec![ - // For each peer ID one reservation every 2 minutes with up to 30 reservations per hour. + // For each peer ID one reservation every 2 minutes with up + // to 30 reservations per hour. rate_limiter::new_per_peer(rate_limiter::GenericRateLimiterConfig { limit: NonZeroU32::new(30).expect("30 > 0"), interval: Duration::from_secs(60 * 2), }), - // For each IP address one reservation every minute with up to 60 reservations per hour. + // For each IP address one reservation every minute with up + // to 60 reservations per hour. rate_limiter::new_per_ip(rate_limiter::GenericRateLimiterConfig { limit: NonZeroU32::new(60).expect("60 > 0"), interval: Duration::from_secs(60), @@ -202,12 +209,12 @@ pub enum Event { dst_peer_id: PeerId, error: inbound_hop::Error, }, - /// An inbound cirucit request has been accepted. + /// An inbound circuit request has been accepted. CircuitReqAccepted { src_peer_id: PeerId, dst_peer_id: PeerId, }, - /// An outbound connect for an inbound cirucit request failed. + /// An outbound connect for an inbound circuit request failed. #[deprecated( note = "Will be removed in favor of logging them internally, see for details." )] @@ -328,6 +335,7 @@ impl NetworkBehaviour for Behaviour { _: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { if addr.is_relayed() { // Deny all substreams on relayed connection. @@ -343,6 +351,7 @@ impl NetworkBehaviour for Behaviour { ConnectedPoint::Dialer { address: addr.clone(), role_override, + port_use, }, ))) } @@ -363,7 +372,9 @@ impl NetworkBehaviour for Behaviour { ) { let event = match event { Either::Left(e) => e, - Either::Right(v) => void::unreachable(v), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + Either::Right(v) => libp2p_core::util::unreachable(v), }; match event { @@ -381,7 +392,8 @@ impl NetworkBehaviour for Behaviour { ); let action = if - // Deny if it is a new reservation and exceeds `max_reservations_per_peer`. + // Deny if it is a new reservation and exceeds + // `max_reservations_per_peer`. (!renewed && self .reservations diff --git a/protocols/relay/src/behaviour/handler.rs b/protocols/relay/src/behaviour/handler.rs index 958c6a9b906..0a4fe11c00a 100644 --- a/protocols/relay/src/behaviour/handler.rs +++ b/protocols/relay/src/behaviour/handler.rs @@ -18,31 +18,37 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::behaviour::CircuitId; -use crate::copy_future::CopyFuture; -use crate::protocol::{inbound_hop, outbound_stop}; -use crate::{proto, HOP_PROTOCOL_NAME, STOP_PROTOCOL_NAME}; +use std::{ + collections::{HashMap, VecDeque}, + fmt, io, + task::{Context, Poll}, + time::Duration, +}; + use bytes::Bytes; use either::Either; -use futures::future::{BoxFuture, FutureExt, TryFutureExt}; -use futures::io::AsyncWriteExt; -use futures::stream::{FuturesUnordered, StreamExt}; +use futures::{ + future::{BoxFuture, FutureExt, TryFutureExt}, + io::AsyncWriteExt, + stream::{FuturesUnordered, StreamExt}, +}; use futures_timer::Delay; -use instant::Instant; -use libp2p_core::upgrade::ReadyUpgrade; -use libp2p_core::{ConnectedPoint, Multiaddr}; +use libp2p_core::{upgrade::ReadyUpgrade, ConnectedPoint, Multiaddr}; use libp2p_identity::PeerId; -use libp2p_swarm::handler::{ - ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, -}; use libp2p_swarm::{ + handler::{ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound}, ConnectionHandler, ConnectionHandlerEvent, ConnectionId, Stream, StreamProtocol, StreamUpgradeError, SubstreamProtocol, }; -use std::collections::{HashMap, VecDeque}; -use std::task::{Context, Poll}; -use std::time::Duration; -use std::{fmt, io}; +use web_time::Instant; + +use crate::{ + behaviour::CircuitId, + copy_future::CopyFuture, + proto, + protocol::{inbound_hop, outbound_stop}, + HOP_PROTOCOL_NAME, STOP_PROTOCOL_NAME, +}; const MAX_CONCURRENT_STREAMS_PER_CONNECTION: usize = 10; const STREAM_TIMEOUT: Duration = Duration::from_secs(60); @@ -449,7 +455,9 @@ impl Handler { StreamUpgradeError::Timeout => outbound_stop::Error::Io(io::ErrorKind::TimedOut.into()), StreamUpgradeError::NegotiationFailed => outbound_stop::Error::Unsupported, StreamUpgradeError::Io(e) => outbound_stop::Error::Io(e), - StreamUpgradeError::Apply(v) => void::unreachable(v), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + StreamUpgradeError::Apply(v) => libp2p_core::util::unreachable(v), }; let stop_command = self diff --git a/protocols/relay/src/behaviour/rate_limiter.rs b/protocols/relay/src/behaviour/rate_limiter.rs index a4a127e1253..4b97c3d5090 100644 --- a/protocols/relay/src/behaviour/rate_limiter.rs +++ b/protocols/relay/src/behaviour/rate_limiter.rs @@ -18,19 +18,20 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use instant::Instant; +use std::{ + collections::{HashMap, VecDeque}, + hash::Hash, + net::IpAddr, + num::NonZeroU32, + time::Duration, +}; + use libp2p_core::multiaddr::{Multiaddr, Protocol}; use libp2p_identity::PeerId; -use std::collections::{HashMap, VecDeque}; -use std::convert::TryInto; -use std::hash::Hash; -use std::net::IpAddr; -use std::num::NonZeroU32; -use std::time::Duration; +use web_time::Instant; /// Allows rate limiting access to some resource based on the [`PeerId`] and /// [`Multiaddr`] of a remote peer. -// // See [`new_per_peer`] and [`new_per_ip`] for precast implementations. Use // [`GenericRateLimiter`] to build your own, e.g. based on the autonomous system // number of a peers IP address. @@ -124,7 +125,7 @@ impl GenericRateLimiter { // Note when used with a high number of buckets: This loop refills all the to-be-refilled // buckets at once, thus potentially delaying the parent call to `try_next`. loop { - match self.refill_schedule.get(0) { + match self.refill_schedule.front() { // Only continue if (a) there is a bucket and (b) the bucket has not already been // refilled recently. Some((last_refill, _)) if now.duration_since(*last_refill) >= self.interval => {} @@ -171,9 +172,9 @@ impl GenericRateLimiter { #[cfg(test)] mod tests { - use super::*; use quickcheck::{QuickCheck, TestResult}; - use std::num::NonZeroU32; + + use super::*; #[test] fn first() { diff --git a/protocols/relay/src/copy_future.rs b/protocols/relay/src/copy_future.rs index 755344cd698..ae7ef22d648 100644 --- a/protocols/relay/src/copy_future.rs +++ b/protocols/relay/src/copy_future.rs @@ -24,17 +24,19 @@ //! //! Inspired by [`futures::io::Copy`]. -use futures::future::Future; -use futures::future::FutureExt; -use futures::io::{AsyncBufRead, BufReader}; -use futures::io::{AsyncRead, AsyncWrite}; -use futures::ready; +use std::{ + io, + pin::Pin, + task::{Context, Poll}, + time::Duration, +}; + +use futures::{ + future::{Future, FutureExt}, + io::{AsyncBufRead, AsyncRead, AsyncWrite, BufReader}, + ready, +}; use futures_timer::Delay; -use std::convert::TryInto; -use std::io; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::time::Duration; pub(crate) struct CopyFuture { src: BufReader, @@ -73,7 +75,7 @@ where let this = &mut *self; loop { - if this.bytes_sent > this.max_circuit_bytes { + if this.max_circuit_bytes > 0 && this.bytes_sent > this.max_circuit_bytes { return Poll::Ready(Err(io::Error::new( io::ErrorKind::Other, "Max circuit bytes reached.", @@ -162,14 +164,12 @@ fn forward_data( #[cfg(test)] mod tests { - use super::*; - use futures::executor::block_on; - use futures::io::{AsyncRead, AsyncWrite, BufReader, BufWriter}; - use quickcheck::QuickCheck; use std::io::ErrorKind; - use std::pin::Pin; - use std::task::{Context, Poll}; - use std::time::Duration; + + use futures::{executor::block_on, io::BufWriter}; + use quickcheck::QuickCheck; + + use super::*; #[test] fn quickcheck() { @@ -360,13 +360,14 @@ mod tests { } } - // The source has two reads available, handing them out on `AsyncRead::poll_read` one by one. + // The source has two reads available, handing them out + // on `AsyncRead::poll_read` one by one. let mut source = BufReader::new(NeverEndingSource { read: vec![1, 2] }); // The destination is wrapped by a `BufWriter` with a capacity of `3`, i.e. one larger than // the available reads of the source. Without an explicit `AsyncWrite::poll_flush` the two - // reads would thus never make it to the destination, but instead be stuck in the buffer of - // the `BufWrite`. + // reads would thus never make it to the destination, + // but instead be stuck in the buffer of the `BufWrite`. let mut destination = BufWriter::with_capacity( 3, RecordingDestination { diff --git a/protocols/relay/src/lib.rs b/protocols/relay/src/lib.rs index eca3578d599..dba07015765 100644 --- a/protocols/relay/src/lib.rs +++ b/protocols/relay/src/lib.rs @@ -32,10 +32,10 @@ mod protocol; mod proto { #![allow(unreachable_pub)] include!("generated/mod.rs"); - pub(crate) use self::message_v2::pb::mod_HopMessage::Type as HopMessageType; pub use self::message_v2::pb::mod_StopMessage::Type as StopMessageType; pub(crate) use self::message_v2::pb::{ - HopMessage, Limit, Peer, Reservation, Status, StopMessage, + mod_HopMessage::Type as HopMessageType, HopMessage, Limit, Peer, Reservation, Status, + StopMessage, }; } diff --git a/protocols/relay/src/multiaddr_ext.rs b/protocols/relay/src/multiaddr_ext.rs index 6991a8b9ded..7c06eb7eab0 100644 --- a/protocols/relay/src/multiaddr_ext.rs +++ b/protocols/relay/src/multiaddr_ext.rs @@ -1,5 +1,4 @@ -use libp2p_core::multiaddr::Protocol; -use libp2p_core::Multiaddr; +use libp2p_core::{multiaddr::Protocol, Multiaddr}; pub(crate) trait MultiaddrExt { fn is_relayed(&self) -> bool; diff --git a/protocols/relay/src/priv_client.rs b/protocols/relay/src/priv_client.rs index e414852ef81..7ac9b716700 100644 --- a/protocols/relay/src/priv_client.rs +++ b/protocols/relay/src/priv_client.rs @@ -23,31 +23,38 @@ pub(crate) mod handler; pub(crate) mod transport; -use crate::multiaddr_ext::MultiaddrExt; -use crate::priv_client::handler::Handler; -use crate::protocol::{self, inbound_stop}; +use std::{ + collections::{hash_map, HashMap, VecDeque}, + convert::Infallible, + io::{Error, ErrorKind, IoSlice}, + pin::Pin, + task::{Context, Poll}, +}; + use bytes::Bytes; use either::Either; -use futures::channel::mpsc::Receiver; -use futures::future::{BoxFuture, FutureExt}; -use futures::io::{AsyncRead, AsyncWrite}; -use futures::ready; -use futures::stream::StreamExt; -use libp2p_core::multiaddr::Protocol; -use libp2p_core::{Endpoint, Multiaddr}; +use futures::{ + channel::mpsc::Receiver, + future::{BoxFuture, FutureExt}, + io::{AsyncRead, AsyncWrite}, + ready, + stream::StreamExt, +}; +use libp2p_core::{multiaddr::Protocol, transport::PortUse, Endpoint, Multiaddr}; use libp2p_identity::PeerId; -use libp2p_swarm::behaviour::{ConnectionClosed, ConnectionEstablished, FromSwarm}; -use libp2p_swarm::dial_opts::DialOpts; use libp2p_swarm::{ + behaviour::{ConnectionClosed, ConnectionEstablished, FromSwarm}, + dial_opts::DialOpts, dummy, ConnectionDenied, ConnectionHandler, ConnectionId, DialFailure, NetworkBehaviour, NotifyHandler, Stream, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; -use std::collections::{hash_map, HashMap, VecDeque}; -use std::io::{Error, ErrorKind, IoSlice}; -use std::pin::Pin; -use std::task::{Context, Poll}; use transport::Transport; -use void::Void; + +use crate::{ + multiaddr_ext::MultiaddrExt, + priv_client::handler::Handler, + protocol::{self, inbound_stop}, +}; /// The events produced by the client `Behaviour`. #[derive(Debug)] @@ -88,11 +95,12 @@ pub struct Behaviour { /// Stores the address of a pending or confirmed reservation. /// - /// This is indexed by the [`ConnectionId`] to a relay server and the address is the `/p2p-circuit` address we reserved on it. + /// This is indexed by the [`ConnectionId`] to a relay server and the address is the + /// `/p2p-circuit` address we reserved on it. reservation_addresses: HashMap, /// Queue of actions to return when polled. - queued_actions: VecDeque>>, + queued_actions: VecDeque>>, pending_handler_commands: HashMap, } @@ -178,6 +186,7 @@ impl NetworkBehaviour for Behaviour { peer: PeerId, addr: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { if addr.is_relayed() { return Ok(Either::Right(dummy::ConnectionHandler)); @@ -234,7 +243,9 @@ impl NetworkBehaviour for Behaviour { ) { let handler_event = match handler_event { Either::Left(e) => e, - Either::Right(v) => void::unreachable(v), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + Either::Right(v) => libp2p_core::util::unreachable(v), }; let event = match handler_event { diff --git a/protocols/relay/src/priv_client/handler.rs b/protocols/relay/src/priv_client/handler.rs index 662d63cc742..8f60b689ec8 100644 --- a/protocols/relay/src/priv_client/handler.rs +++ b/protocols/relay/src/priv_client/handler.rs @@ -18,29 +18,35 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::client::Connection; -use crate::priv_client::transport; -use crate::priv_client::transport::ToListenerMsg; -use crate::protocol::{self, inbound_stop, outbound_hop}; -use crate::{priv_client, proto, HOP_PROTOCOL_NAME, STOP_PROTOCOL_NAME}; -use futures::channel::mpsc::Sender; -use futures::channel::{mpsc, oneshot}; -use futures::future::FutureExt; +use std::{ + collections::VecDeque, + convert::Infallible, + fmt, io, + task::{Context, Poll}, + time::Duration, +}; + +use futures::{ + channel::{mpsc, mpsc::Sender, oneshot}, + future::FutureExt, +}; use futures_timer::Delay; -use libp2p_core::multiaddr::Protocol; -use libp2p_core::upgrade::ReadyUpgrade; -use libp2p_core::Multiaddr; +use libp2p_core::{multiaddr::Protocol, upgrade::ReadyUpgrade, Multiaddr}; use libp2p_identity::PeerId; -use libp2p_swarm::handler::{ConnectionEvent, FullyNegotiatedInbound}; use libp2p_swarm::{ + handler::{ConnectionEvent, FullyNegotiatedInbound}, ConnectionHandler, ConnectionHandlerEvent, Stream, StreamProtocol, StreamUpgradeError, SubstreamProtocol, }; -use std::collections::VecDeque; -use std::task::{Context, Poll}; -use std::time::Duration; -use std::{fmt, io}; -use void::Void; + +use crate::{ + client::Connection, + priv_client, + priv_client::{transport, transport::ToListenerMsg}, + proto, + protocol::{self, inbound_stop, outbound_hop}, + HOP_PROTOCOL_NAME, STOP_PROTOCOL_NAME, +}; /// The maximum number of circuits being denied concurrently. /// @@ -106,7 +112,7 @@ pub struct Handler { >, >, - pending_streams: VecDeque>>>, + pending_streams: VecDeque>>>, inflight_reserve_requests: futures_bounded::FuturesTupleSet< Result, @@ -445,7 +451,9 @@ impl ConnectionHandler for Handler { let _ = next.send(Ok(ev.protocol)); } } - ConnectionEvent::ListenUpgradeError(ev) => void::unreachable(ev.error), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + ConnectionEvent::ListenUpgradeError(ev) => libp2p_core::util::unreachable(ev.error), ConnectionEvent::DialUpgradeError(ev) => { if let Some(next) = self.pending_streams.pop_front() { let _ = next.send(Err(ev.error)); @@ -578,23 +586,27 @@ impl Reservation { } } -fn into_reserve_error(e: StreamUpgradeError) -> outbound_hop::ReserveError { +fn into_reserve_error(e: StreamUpgradeError) -> outbound_hop::ReserveError { match e { StreamUpgradeError::Timeout => { outbound_hop::ReserveError::Io(io::ErrorKind::TimedOut.into()) } - StreamUpgradeError::Apply(never) => void::unreachable(never), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + StreamUpgradeError::Apply(never) => libp2p_core::util::unreachable(never), StreamUpgradeError::NegotiationFailed => outbound_hop::ReserveError::Unsupported, StreamUpgradeError::Io(e) => outbound_hop::ReserveError::Io(e), } } -fn into_connect_error(e: StreamUpgradeError) -> outbound_hop::ConnectError { +fn into_connect_error(e: StreamUpgradeError) -> outbound_hop::ConnectError { match e { StreamUpgradeError::Timeout => { outbound_hop::ConnectError::Io(io::ErrorKind::TimedOut.into()) } - StreamUpgradeError::Apply(never) => void::unreachable(never), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + StreamUpgradeError::Apply(never) => libp2p_core::util::unreachable(never), StreamUpgradeError::NegotiationFailed => outbound_hop::ConnectError::Unsupported, StreamUpgradeError::Io(e) => outbound_hop::ConnectError::Io(e), } diff --git a/protocols/relay/src/priv_client/transport.rs b/protocols/relay/src/priv_client/transport.rs index 7147f0b5e55..ed9faa946db 100644 --- a/protocols/relay/src/priv_client/transport.rs +++ b/protocols/relay/src/priv_client/transport.rs @@ -19,26 +19,35 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::multiaddr_ext::MultiaddrExt; -use crate::priv_client::Connection; -use crate::protocol::outbound_hop; -use crate::protocol::outbound_hop::{ConnectError, ReserveError}; -use crate::RequestId; -use futures::channel::mpsc; -use futures::channel::oneshot; -use futures::future::{ready, BoxFuture, FutureExt, Ready}; -use futures::ready; -use futures::sink::SinkExt; -use futures::stream::SelectAll; -use futures::stream::{Stream, StreamExt}; -use libp2p_core::multiaddr::{Multiaddr, Protocol}; -use libp2p_core::transport::{ListenerId, TransportError, TransportEvent}; +use std::{ + collections::VecDeque, + pin::Pin, + task::{Context, Poll, Waker}, +}; + +use futures::{ + channel::{mpsc, oneshot}, + future::{ready, BoxFuture, FutureExt, Ready}, + sink::SinkExt, + stream::{SelectAll, Stream, StreamExt}, +}; +use libp2p_core::{ + multiaddr::{Multiaddr, Protocol}, + transport::{DialOpts, ListenerId, TransportError, TransportEvent}, +}; use libp2p_identity::PeerId; -use std::collections::VecDeque; -use std::pin::Pin; -use std::task::{Context, Poll}; use thiserror::Error; +use crate::{ + multiaddr_ext::MultiaddrExt, + priv_client::Connection, + protocol::{ + outbound_hop, + outbound_hop::{ConnectError, ReserveError}, + }, + RequestId, +}; + /// A [`Transport`] enabling client relay capabilities. /// /// Note: The transport only handles listening and dialing on relayed [`Multiaddr`], and depends on @@ -50,7 +59,8 @@ use thiserror::Error; /// 1. Establish relayed connections by dialing `/p2p-circuit` addresses. /// /// ``` -/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport}; +/// # use libp2p_core::{Multiaddr, multiaddr::{Protocol}, Transport, +/// # transport::{DialOpts, PortUse}, connection::Endpoint}; /// # use libp2p_core::transport::memory::MemoryTransport; /// # use libp2p_core::transport::choice::OrTransport; /// # use libp2p_relay as relay; @@ -67,7 +77,10 @@ use thiserror::Error; /// .with(Protocol::P2p(relay_id.into())) // Relay peer id. /// .with(Protocol::P2pCircuit) // Signal to connect via relay and not directly. /// .with(Protocol::P2p(destination_id.into())); // Destination peer id. -/// transport.dial(dst_addr_via_relay).unwrap(); +/// transport.dial(dst_addr_via_relay, DialOpts { +/// port_use: PortUse::Reuse, +/// role: Endpoint::Dialer, +/// }).unwrap(); /// ``` /// /// 3. Listen for incoming relayed connections via specific relay. @@ -151,6 +164,7 @@ impl libp2p_core::Transport for Transport { queued_events: Default::default(), from_behaviour, is_closed: false, + waker: None, }; self.listeners.push(listener); Ok(()) @@ -165,7 +179,19 @@ impl libp2p_core::Transport for Transport { } } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + dial_opts: DialOpts, + ) -> Result> { + if dial_opts.role.is_listener() { + // [`Endpoint::Listener`] is used for NAT and firewall + // traversal. One would coordinate such traversal via a previously + // established relayed connection, but never using a relayed connection + // itself. + return Err(TransportError::MultiaddrNotSupported(addr)); + } + let RelayedMultiaddr { relay_peer_id, relay_addr, @@ -198,24 +224,6 @@ impl libp2p_core::Transport for Transport { .boxed()) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> - where - Self: Sized, - { - // [`Transport::dial_as_listener`] is used for NAT and firewall - // traversal. One would coordinate such traversal via a previously - // established relayed connection, but never using a relayed connection - // itself. - Err(TransportError::MultiaddrNotSupported(addr)) - } - - fn address_translation(&self, _server: &Multiaddr, _observed: &Multiaddr) -> Option { - None - } - fn poll( mut self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -310,9 +318,11 @@ pub(crate) struct Listener { queued_events: VecDeque<::Item>, /// Channel for messages from the behaviour [`Handler`][super::handler::Handler]. from_behaviour: mpsc::Receiver, - /// The listener can be closed either manually with [`Transport::remove_listener`](libp2p_core::Transport) or if - /// the sender side of the `from_behaviour` channel is dropped. + /// The listener can be closed either manually with + /// [`Transport::remove_listener`](libp2p_core::Transport) or if the sender side of the + /// `from_behaviour` channel is dropped. is_closed: bool, + waker: Option, } impl Listener { @@ -328,6 +338,10 @@ impl Listener { reason, }); self.is_closed = true; + + if let Some(waker) = self.waker.take() { + waker.wake(); + } } } @@ -337,18 +351,28 @@ impl Stream for Listener { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { if let Some(event) = self.queued_events.pop_front() { + self.waker = None; return Poll::Ready(Some(event)); } if self.is_closed { - // Terminate the stream if the listener closed and all remaining events have been reported. + // Terminate the stream if the listener closed and + // all remaining events have been reported. + self.waker = None; return Poll::Ready(None); } - let Some(msg) = ready!(self.from_behaviour.poll_next_unpin(cx)) else { - // Sender of `from_behaviour` has been dropped, signaling listener to close. - self.close(Ok(())); - continue; + let msg = match self.from_behaviour.poll_next_unpin(cx) { + Poll::Ready(Some(msg)) => msg, + Poll::Ready(None) => { + // Sender of `from_behaviour` has been dropped, signaling listener to close. + self.close(Ok(())); + continue; + } + Poll::Pending => { + self.waker = Some(cx.waker().clone()); + return Poll::Pending; + } }; match msg { diff --git a/protocols/relay/src/protocol.rs b/protocols/relay/src/protocol.rs index b94151259cd..b1adeedaaf5 100644 --- a/protocols/relay/src/protocol.rs +++ b/protocols/relay/src/protocol.rs @@ -18,10 +18,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::proto; -use libp2p_swarm::StreamProtocol; use std::time::Duration; +use libp2p_swarm::StreamProtocol; + +use crate::proto; + pub(crate) mod inbound_hop; pub(crate) mod inbound_stop; pub(crate) mod outbound_hop; diff --git a/protocols/relay/src/protocol/inbound_hop.rs b/protocols/relay/src/protocol/inbound_hop.rs index 41fe2675dce..01280d70897 100644 --- a/protocols/relay/src/protocol/inbound_hop.rs +++ b/protocols/relay/src/protocol/inbound_hop.rs @@ -18,21 +18,19 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::time::{Duration, SystemTime}; +use std::time::Duration; use asynchronous_codec::{Framed, FramedParts}; use bytes::Bytes; use either::Either; use futures::prelude::*; -use thiserror::Error; - use libp2p_core::Multiaddr; use libp2p_identity::PeerId; use libp2p_swarm::Stream; +use thiserror::Error; +use web_time::SystemTime; -use crate::proto; -use crate::proto::message_v2::pb::mod_HopMessage::Type; -use crate::protocol::MAX_MESSAGE_SIZE; +use crate::{proto, proto::message_v2::pb::mod_HopMessage::Type, protocol::MAX_MESSAGE_SIZE}; #[derive(Debug, Error)] pub enum Error { @@ -114,6 +112,8 @@ impl ReservationReq { pub struct CircuitReq { dst: PeerId, substream: Framed>, + max_circuit_duration: Duration, + max_circuit_bytes: u64, } impl CircuitReq { @@ -126,7 +126,15 @@ impl CircuitReq { type_pb: proto::HopMessageType::STATUS, peer: None, reservation: None, - limit: None, + limit: Some(proto::Limit { + duration: Some( + self.max_circuit_duration + .as_secs() + .try_into() + .expect("`max_circuit_duration` not to exceed `u32::MAX`."), + ), + data: Some(self.max_circuit_bytes), + }), status: Some(proto::Status::OK), }; @@ -203,7 +211,12 @@ pub(crate) async fn handle_inbound_request( let dst = peer_id_res.map_err(|_| Error::ParsePeerId)?; - Either::Right(CircuitReq { dst, substream }) + Either::Right(CircuitReq { + dst, + substream, + max_circuit_duration, + max_circuit_bytes, + }) } Type::STATUS => return Err(Error::UnexpectedTypeStatus), }; diff --git a/protocols/relay/src/protocol/inbound_stop.rs b/protocols/relay/src/protocol/inbound_stop.rs index b698a5ff769..8994c2cff73 100644 --- a/protocols/relay/src/protocol/inbound_stop.rs +++ b/protocols/relay/src/protocol/inbound_stop.rs @@ -18,16 +18,20 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::proto; -use crate::protocol::{self, MAX_MESSAGE_SIZE}; +use std::io; + use asynchronous_codec::{Framed, FramedParts}; use bytes::Bytes; use futures::prelude::*; use libp2p_identity::PeerId; use libp2p_swarm::Stream; -use std::io; use thiserror::Error; +use crate::{ + proto, + protocol::{self, MAX_MESSAGE_SIZE}, +}; + pub(crate) async fn handle_open_circuit(io: Stream) -> Result { let mut substream = Framed::new(io, quick_protobuf_codec::Codec::new(MAX_MESSAGE_SIZE)); diff --git a/protocols/relay/src/protocol/outbound_hop.rs b/protocols/relay/src/protocol/outbound_hop.rs index 3ae824be167..216c6d115bf 100644 --- a/protocols/relay/src/protocol/outbound_hop.rs +++ b/protocols/relay/src/protocol/outbound_hop.rs @@ -18,21 +18,23 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::io; -use std::time::{Duration, SystemTime}; +use std::{io, time::Duration}; use asynchronous_codec::{Framed, FramedParts}; use bytes::Bytes; use futures::prelude::*; use futures_timer::Delay; -use thiserror::Error; - use libp2p_core::Multiaddr; use libp2p_identity::PeerId; use libp2p_swarm::Stream; +use thiserror::Error; +use web_time::SystemTime; -use crate::protocol::{Limit, MAX_MESSAGE_SIZE}; -use crate::{proto, HOP_PROTOCOL_NAME}; +use crate::{ + proto, + protocol::{Limit, MAX_MESSAGE_SIZE}, + HOP_PROTOCOL_NAME, +}; #[derive(Debug, Error)] pub enum ConnectError { diff --git a/protocols/relay/src/protocol/outbound_stop.rs b/protocols/relay/src/protocol/outbound_stop.rs index 525ebc10821..272aa24eef6 100644 --- a/protocols/relay/src/protocol/outbound_stop.rs +++ b/protocols/relay/src/protocol/outbound_stop.rs @@ -18,19 +18,16 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::io; -use std::time::Duration; +use std::{io, time::Duration}; use asynchronous_codec::{Framed, FramedParts}; use bytes::Bytes; use futures::prelude::*; -use thiserror::Error; - use libp2p_identity::PeerId; use libp2p_swarm::Stream; +use thiserror::Error; -use crate::protocol::MAX_MESSAGE_SIZE; -use crate::{proto, STOP_PROTOCOL_NAME}; +use crate::{proto, protocol::MAX_MESSAGE_SIZE, STOP_PROTOCOL_NAME}; #[derive(Debug, Error)] pub enum Error { diff --git a/protocols/relay/tests/lib.rs b/protocols/relay/tests/lib.rs index 2b28d5a50cd..da6f549c091 100644 --- a/protocols/relay/tests/lib.rs +++ b/protocols/relay/tests/lib.rs @@ -18,33 +18,32 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::executor::LocalPool; -use futures::future::FutureExt; -use futures::io::{AsyncRead, AsyncWrite}; -use futures::stream::StreamExt; -use futures::task::Spawn; -use libp2p_core::multiaddr::{Multiaddr, Protocol}; -use libp2p_core::muxing::StreamMuxerBox; -use libp2p_core::transport::choice::OrTransport; -use libp2p_core::transport::{Boxed, MemoryTransport, Transport}; -use libp2p_core::upgrade; +use std::{error::Error, time::Duration}; + +use futures::{ + executor::LocalPool, + future::FutureExt, + io::{AsyncRead, AsyncWrite}, + stream::StreamExt, + task::Spawn, +}; +use libp2p_core::{ + multiaddr::{Multiaddr, Protocol}, + muxing::StreamMuxerBox, + transport::{choice::OrTransport, Boxed, MemoryTransport, Transport}, + upgrade, +}; use libp2p_identity as identity; use libp2p_identity::PeerId; use libp2p_ping as ping; use libp2p_plaintext as plaintext; use libp2p_relay as relay; -use libp2p_swarm::dial_opts::DialOpts; -use libp2p_swarm::{Config, DialError, NetworkBehaviour, Swarm, SwarmEvent}; +use libp2p_swarm::{dial_opts::DialOpts, Config, DialError, NetworkBehaviour, Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt; -use std::error::Error; -use std::time::Duration; -use tracing_subscriber::EnvFilter; #[test] fn reservation() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut pool = LocalPool::new(); let relay_addr = Multiaddr::empty().with(Protocol::Memory(rand::random::())); @@ -85,9 +84,7 @@ fn reservation() { #[test] fn new_reservation_to_same_relay_replaces_old() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut pool = LocalPool::new(); let relay_addr = Multiaddr::empty().with(Protocol::Memory(rand::random::())); @@ -184,9 +181,7 @@ fn new_reservation_to_same_relay_replaces_old() { #[test] fn connect() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut pool = LocalPool::new(); let relay_addr = Multiaddr::empty().with(Protocol::Memory(rand::random::())); @@ -270,9 +265,7 @@ async fn connection_established_to( #[test] fn handle_dial_failure() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut pool = LocalPool::new(); let relay_addr = Multiaddr::empty().with(Protocol::Memory(rand::random::())); @@ -291,9 +284,7 @@ fn handle_dial_failure() { #[test] fn propagate_reservation_error_to_listener() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut pool = LocalPool::new(); let relay_addr = Multiaddr::empty().with(Protocol::Memory(rand::random::())); @@ -340,9 +331,7 @@ fn propagate_reservation_error_to_listener() { #[test] fn propagate_connect_error_to_unknown_peer_to_dialer() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut pool = LocalPool::new(); let relay_addr = Multiaddr::empty().with(Protocol::Memory(rand::random::())); @@ -396,9 +385,7 @@ fn propagate_connect_error_to_unknown_peer_to_dialer() { #[test] fn reuse_connection() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut pool = LocalPool::new(); let relay_addr = Multiaddr::empty().with(Protocol::Memory(rand::random::())); @@ -414,10 +401,7 @@ fn reuse_connection() { .with(Protocol::P2p(relay_peer_id)) .with(Protocol::P2pCircuit); - // To reuse the connection, we need to ensure it is not shut down due to being idle. - let mut client = build_client_with_config( - Config::with_async_std_executor().with_idle_connection_timeout(Duration::from_secs(1)), - ); + let mut client = build_client(); let client_peer_id = *client.local_peer_id(); client.dial(relay_addr).unwrap(); diff --git a/protocols/rendezvous/CHANGELOG.md b/protocols/rendezvous/CHANGELOG.md index e60699da734..ca01538a76d 100644 --- a/protocols/rendezvous/CHANGELOG.md +++ b/protocols/rendezvous/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.15.1 + +- Update to `libp2p-request-response` `v0.28.0`. + +## 0.15.0 + + + +## 0.14.1 +- Use `web-time` instead of `instant`. + See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). + ## 0.14.0 diff --git a/protocols/rendezvous/Cargo.toml b/protocols/rendezvous/Cargo.toml index 4dfd77e1eba..104dc6ad1d4 100644 --- a/protocols/rendezvous/Cargo.toml +++ b/protocols/rendezvous/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-rendezvous" edition = "2021" rust-version = { workspace = true } description = "Rendezvous protocol for libp2p" -version = "0.14.0" +version = "0.15.1" authors = ["The COMIT guys "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -14,9 +14,9 @@ categories = ["network-programming", "asynchronous"] asynchronous-codec = { workspace = true } async-trait = "0.1" bimap = "0.6.3" -futures = { version = "0.3", default-features = false, features = ["std"] } -futures-timer = "3.0.2" -instant = "0.1.12" +futures = { workspace = true, features = ["std"] } +futures-timer = "3.0.3" +web-time = { workspace = true } libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true } @@ -24,28 +24,20 @@ libp2p-request-response = { workspace = true } quick-protobuf = "0.8" quick-protobuf-codec = { workspace = true } rand = "0.8" -thiserror = "1" -tracing = "0.1.37" -void = "1" +thiserror = { workspace = true } +tracing = { workspace = true } [dev-dependencies] libp2p-swarm = { workspace = true, features = ["macros", "tokio"] } -libp2p-noise = { workspace = true } -libp2p-ping = { workspace = true } -libp2p-identify = { workspace = true } libp2p-swarm-test = { path = "../../swarm-test" } -libp2p-tcp = { workspace = true, features = ["tokio"] } -libp2p-yamux = { workspace = true } rand = "0.8" -tokio = { version = "1.34", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs", "net" ] } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tokio = { workspace = true, features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs", "net" ] } +libp2p-test-utils = { workspace = true } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/rendezvous/src/client.rs b/protocols/rendezvous/src/client.rs index 92d7884758b..019b23c092b 100644 --- a/protocols/rendezvous/src/client.rs +++ b/protocols/rendezvous/src/client.rs @@ -18,23 +18,28 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::codec::Message::*; -use crate::codec::{Cookie, ErrorCode, Message, Namespace, NewRegistration, Registration, Ttl}; -use futures::future::BoxFuture; -use futures::future::FutureExt; -use futures::stream::FuturesUnordered; -use futures::stream::StreamExt; -use libp2p_core::{Endpoint, Multiaddr, PeerRecord}; +use std::{ + collections::HashMap, + iter, + task::{Context, Poll}, + time::Duration, +}; + +use futures::{ + future::{BoxFuture, FutureExt}, + stream::{FuturesUnordered, StreamExt}, +}; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr, PeerRecord}; use libp2p_identity::{Keypair, PeerId, SigningError}; use libp2p_request_response::{OutboundRequestId, ProtocolSupport}; use libp2p_swarm::{ ConnectionDenied, ConnectionId, ExternalAddresses, FromSwarm, NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; -use std::collections::HashMap; -use std::iter; -use std::task::{Context, Poll}; -use std::time::Duration; + +use crate::codec::{ + Cookie, ErrorCode, Message, Message::*, Namespace, NewRegistration, Registration, Ttl, +}; pub struct Behaviour { inner: libp2p_request_response::Behaviour, @@ -46,12 +51,14 @@ pub struct Behaviour { /// Hold addresses of all peers that we have discovered so far. /// - /// Storing these internally allows us to assist the [`libp2p_swarm::Swarm`] in dialing by returning addresses from [`NetworkBehaviour::handle_pending_outbound_connection`]. + /// Storing these internally allows us to assist the [`libp2p_swarm::Swarm`] in dialing by + /// returning addresses from [`NetworkBehaviour::handle_pending_outbound_connection`]. discovered_peers: HashMap<(PeerId, Namespace), Vec>, registered_namespaces: HashMap<(PeerId, Namespace), Ttl>, - /// Tracks the expiry of registrations that we have discovered and stored in `discovered_peers` otherwise we have a memory leak. + /// Tracks the expiry of registrations that we have discovered and stored in `discovered_peers` + /// otherwise we have a memory leak. expiring_registrations: FuturesUnordered>, external_addresses: ExternalAddresses, @@ -80,8 +87,9 @@ impl Behaviour { /// Register our external addresses in the given namespace with the given rendezvous peer. /// - /// External addresses are either manually added via [`libp2p_swarm::Swarm::add_external_address`] or reported - /// by other [`NetworkBehaviour`]s via [`ToSwarm::ExternalAddrConfirmed`]. + /// External addresses are either manually added via + /// [`libp2p_swarm::Swarm::add_external_address`] or reported by other [`NetworkBehaviour`]s + /// via [`ToSwarm::ExternalAddrConfirmed`]. pub fn register( &mut self, namespace: Namespace, @@ -208,9 +216,15 @@ impl NetworkBehaviour for Behaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { - self.inner - .handle_established_outbound_connection(connection_id, peer, addr, role_override) + self.inner.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) } fn on_connection_handler_event( diff --git a/protocols/rendezvous/src/codec.rs b/protocols/rendezvous/src/codec.rs index 41432a91d8c..60f9f14f332 100644 --- a/protocols/rendezvous/src/codec.rs +++ b/protocols/rendezvous/src/codec.rs @@ -18,17 +18,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::DEFAULT_TTL; +use std::{fmt, io}; + use async_trait::async_trait; -use asynchronous_codec::{BytesMut, Decoder, Encoder}; -use asynchronous_codec::{FramedRead, FramedWrite}; +use asynchronous_codec::{BytesMut, Decoder, Encoder, FramedRead, FramedWrite}; use futures::{AsyncRead, AsyncWrite, SinkExt, StreamExt}; use libp2p_core::{peer_record, signed_envelope, PeerRecord, SignedEnvelope}; use libp2p_swarm::StreamProtocol; use quick_protobuf_codec::Codec as ProtobufCodec; use rand::RngCore; -use std::convert::{TryFrom, TryInto}; -use std::{fmt, io}; + +use crate::DEFAULT_TTL; pub type Ttl = u64; pub(crate) type Limit = u64; @@ -55,7 +55,9 @@ pub struct Namespace(String); impl Namespace { /// Creates a new [`Namespace`] from a static string. /// - /// This will panic if the namespace is too long. We accepting panicking in this case because we are enforcing a `static lifetime which means this value can only be a constant in the program and hence we hope the developer checked that it is of an acceptable length. + /// This will panic if the namespace is too long. We accepting panicking in this case because we + /// are enforcing a `static lifetime which means this value can only be a constant in the + /// program and hence we hope the developer checked that it is of an acceptable length. pub fn from_static(value: &'static str) -> Self { if value.len() > crate::MAX_NAMESPACE { panic!("Namespace '{value}' is too long!") @@ -110,7 +112,8 @@ pub struct Cookie { impl Cookie { /// Construct a new [`Cookie`] for a given namespace. /// - /// This cookie will only be valid for subsequent DISCOVER requests targeting the same namespace. + /// This cookie will only be valid for subsequent DISCOVER requests targeting the same + /// namespace. pub fn for_namespace(namespace: Namespace) -> Self { Self { id: rand::thread_rng().next_u64(), @@ -642,7 +645,6 @@ mod proto { #[cfg(test)] mod tests { use super::*; - use crate::Namespace; #[test] fn cookie_wire_encoding_roundtrip() { diff --git a/protocols/rendezvous/src/lib.rs b/protocols/rendezvous/src/lib.rs index 7c607085f20..221178728af 100644 --- a/protocols/rendezvous/src/lib.rs +++ b/protocols/rendezvous/src/lib.rs @@ -22,9 +22,10 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -pub use self::codec::{Cookie, ErrorCode, Namespace, NamespaceTooLong, Registration, Ttl}; use libp2p_swarm::StreamProtocol; +pub use self::codec::{Cookie, ErrorCode, Namespace, NamespaceTooLong, Registration, Ttl}; + mod codec; /// If unspecified, rendezvous nodes should assume a TTL of 2h. diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index 78aa42043cd..1be7220cfcb 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -18,25 +18,27 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::codec::{Cookie, ErrorCode, Message, Namespace, NewRegistration, Registration, Ttl}; -use crate::{MAX_TTL, MIN_TTL}; +use std::{ + collections::{HashMap, HashSet}, + iter, + task::{ready, Context, Poll}, + time::Duration, +}; + use bimap::BiMap; -use futures::future::BoxFuture; -use futures::stream::FuturesUnordered; -use futures::{FutureExt, StreamExt}; -use libp2p_core::{Endpoint, Multiaddr}; +use futures::{future::BoxFuture, stream::FuturesUnordered, FutureExt, StreamExt}; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_request_response::ProtocolSupport; -use libp2p_swarm::behaviour::FromSwarm; use libp2p_swarm::{ - ConnectionDenied, ConnectionId, NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, - ToSwarm, + behaviour::FromSwarm, ConnectionDenied, ConnectionId, NetworkBehaviour, THandler, + THandlerInEvent, THandlerOutEvent, ToSwarm, +}; + +use crate::{ + codec::{Cookie, ErrorCode, Message, Namespace, NewRegistration, Registration, Ttl}, + MAX_TTL, MIN_TTL, }; -use std::collections::{HashMap, HashSet}; -use std::iter; -use std::iter::FromIterator; -use std::task::{ready, Context, Poll}; -use std::time::Duration; pub struct Behaviour { inner: libp2p_request_response::Behaviour, @@ -140,9 +142,15 @@ impl NetworkBehaviour for Behaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { - self.inner - .handle_established_outbound_connection(connection_id, peer, addr, role_override) + self.inner.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) } fn on_connection_handler_event( @@ -175,6 +183,7 @@ impl NetworkBehaviour for Behaviour { libp2p_request_response::Message::Request { request, channel, .. }, + .. }) => { if let Some((event, response)) = handle_request(peer_id, request, &mut self.registrations) @@ -194,6 +203,7 @@ impl NetworkBehaviour for Behaviour { peer, request_id, error, + .. }) => { tracing::warn!( %peer, @@ -209,26 +219,19 @@ impl NetworkBehaviour for Behaviour { | ToSwarm::GenerateEvent(libp2p_request_response::Event::Message { peer: _, message: libp2p_request_response::Message::Response { .. }, + .. }) | ToSwarm::GenerateEvent(libp2p_request_response::Event::OutboundFailure { .. }) => { continue; } - ToSwarm::Dial { .. } - | ToSwarm::ListenOn { .. } - | ToSwarm::RemoveListener { .. } - | ToSwarm::NotifyHandler { .. } - | ToSwarm::NewExternalAddrCandidate(_) - | ToSwarm::ExternalAddrConfirmed(_) - | ToSwarm::ExternalAddrExpired(_) - | ToSwarm::CloseConnection { .. } => { - let new_to_swarm = to_swarm + other => { + let new_to_swarm = other .map_out(|_| unreachable!("we manually map `GenerateEvent` variants")); return Poll::Ready(new_to_swarm); } - _ => {} }; } @@ -451,7 +454,7 @@ impl Registrations { match (discover_namespace.as_ref(), cookie_namespace) { // discover all namespace but cookie is specific to a namespace? => bad (None, Some(_)) => return Err(CookieNamespaceMismatch), - // discover for a namespace but cookie is for a different namesapce? => bad + // discover for a namespace but cookie is for a different namespace? => bad (Some(namespace), Some(cookie_namespace)) if namespace != cookie_namespace => { return Err(CookieNamespaceMismatch) } @@ -536,11 +539,9 @@ pub struct CookieNamespaceMismatch; #[cfg(test)] mod tests { - use instant::SystemTime; - use std::option::Option::None; - use libp2p_core::PeerRecord; use libp2p_identity as identity; + use web_time::SystemTime; use super::*; @@ -795,7 +796,8 @@ mod tests { .unwrap_err(); } - /// Polls [`Registrations`] for at most `seconds` and panics if doesn't return an event within that time. + /// Polls [`Registrations`] for at most `seconds` and panics if doesn't + /// return an event within that time. async fn next_event_in_at_most(&mut self, seconds: u64) -> ExpiredRegistration { tokio::time::timeout(Duration::from_secs(seconds), self.next_event()) .await diff --git a/protocols/rendezvous/tests/rendezvous.rs b/protocols/rendezvous/tests/rendezvous.rs index c2de88fd615..98aa9dab62d 100644 --- a/protocols/rendezvous/tests/rendezvous.rs +++ b/protocols/rendezvous/tests/rendezvous.rs @@ -18,24 +18,19 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::stream::FuturesUnordered; -use futures::StreamExt; -use libp2p_core::multiaddr::Protocol; -use libp2p_core::Multiaddr; +use std::time::Duration; + +use futures::{stream::FuturesUnordered, StreamExt}; +use libp2p_core::{multiaddr::Protocol, Multiaddr}; use libp2p_identity as identity; use libp2p_rendezvous as rendezvous; use libp2p_rendezvous::client::RegisterError; use libp2p_swarm::{DialError, Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt; -use std::convert::TryInto; -use std::time::Duration; -use tracing_subscriber::EnvFilter; #[tokio::test] async fn given_successful_registration_then_successful_discovery() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let namespace = rendezvous::Namespace::from_static("some-namespace"); let ([mut alice, mut bob], mut robert) = new_server_with_connected_clients(rendezvous::server::Config::default()).await; @@ -88,9 +83,7 @@ async fn given_successful_registration_then_successful_discovery() { #[tokio::test] async fn should_return_error_when_no_external_addresses() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let namespace = rendezvous::Namespace::from_static("some-namespace"); let server = new_server(rendezvous::server::Config::default()).await; let mut client = Swarm::new_ephemeral(rendezvous::client::Behaviour::new); @@ -105,9 +98,7 @@ async fn should_return_error_when_no_external_addresses() { #[tokio::test] async fn given_successful_registration_then_refresh_ttl() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let namespace = rendezvous::Namespace::from_static("some-namespace"); let ([mut alice, mut bob], mut robert) = new_server_with_connected_clients(rendezvous::server::Config::default()).await; @@ -173,9 +164,7 @@ async fn given_successful_registration_then_refresh_ttl() { #[tokio::test] async fn given_successful_registration_then_refresh_external_addrs() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let namespace = rendezvous::Namespace::from_static("some-namespace"); let ([mut alice], mut robert) = new_server_with_connected_clients(rendezvous::server::Config::default()).await; @@ -226,9 +215,7 @@ async fn given_successful_registration_then_refresh_external_addrs() { #[tokio::test] async fn given_invalid_ttl_then_unsuccessful_registration() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let namespace = rendezvous::Namespace::from_static("some-namespace"); let ([mut alice], mut robert) = new_server_with_connected_clients(rendezvous::server::Config::default()).await; @@ -255,9 +242,7 @@ async fn given_invalid_ttl_then_unsuccessful_registration() { #[tokio::test] async fn discover_allows_for_dial_by_peer_id() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let namespace = rendezvous::Namespace::from_static("some-namespace"); let ([mut alice, mut bob], robert) = new_server_with_connected_clients(rendezvous::server::Config::default()).await; @@ -312,9 +297,7 @@ async fn discover_allows_for_dial_by_peer_id() { #[tokio::test] async fn eve_cannot_register() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let namespace = rendezvous::Namespace::from_static("some-namespace"); let mut robert = new_server(rendezvous::server::Config::default()).await; let mut eve = new_impersonating_client().await; @@ -340,9 +323,7 @@ async fn eve_cannot_register() { // test if charlie can operate as client and server simultaneously #[tokio::test] async fn can_combine_client_and_server() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let namespace = rendezvous::Namespace::from_static("some-namespace"); let ([mut alice], mut robert) = new_server_with_connected_clients(rendezvous::server::Config::default()).await; @@ -378,9 +359,7 @@ async fn can_combine_client_and_server() { #[tokio::test] async fn registration_on_clients_expire() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let namespace = rendezvous::Namespace::from_static("some-namespace"); let ([mut alice, mut bob], robert) = new_server_with_connected_clients(rendezvous::server::Config::default().with_min_ttl(1)) @@ -472,9 +451,11 @@ async fn new_combined_node() -> Swarm { } async fn new_impersonating_client() -> Swarm { - // In reality, if Eve were to try and fake someones identity, she would obviously only know the public key. - // Due to the type-safe API of the `Rendezvous` behaviour and `PeerRecord`, we actually cannot construct a bad `PeerRecord` (i.e. one that is claims to be someone else). - // As such, the best we can do is hand eve a completely different keypair from what she is using to authenticate her connection. + // In reality, if Eve were to try and fake someones identity, she would obviously only know the + // public key. Due to the type-safe API of the `Rendezvous` behaviour and `PeerRecord`, we + // actually cannot construct a bad `PeerRecord` (i.e. one that is claims to be someone else). + // As such, the best we can do is hand eve a completely different keypair from what she is using + // to authenticate her connection. let someone_else = identity::Keypair::generate_ed25519(); let mut eve = Swarm::new_ephemeral(move |_| rendezvous::client::Behaviour::new(someone_else)); eve.listen().with_memory_addr_external().await; diff --git a/protocols/request-response/CHANGELOG.md b/protocols/request-response/CHANGELOG.md index c146a40c68f..514374c3a4b 100644 --- a/protocols/request-response/CHANGELOG.md +++ b/protocols/request-response/CHANGELOG.md @@ -1,3 +1,35 @@ +## 0.28.0 + +- Add connection id to the events emitted by a request-response `Behaviour`. + See [PR 5719](https://github.com/libp2p/rust-libp2p/pull/5719). + +## 0.27.1 + +- Deprecate `void` crate. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). + +## 0.27.0 + + + +## 0.26.4 + +- Use `web-time` instead of `instant`. + See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). + +## 0.26.3 + +- Report failure when streams are at capacity. + See [PR 5417](https://github.com/libp2p/rust-libp2p/pull/5417). + +- Report dial IO errors to the user. + See [PR 5429](https://github.com/libp2p/rust-libp2p/pull/5429). + +## 0.26.2 + +- Deprecate `Behaviour::add_address` in favor of `Swarm::add_peer_address`. + See [PR 4371](https://github.com/libp2p/rust-libp2p/pull/4371). + ## 0.26.1 - Allow configurable request and response sizes for `json` and `cbor` codec. diff --git a/protocols/request-response/Cargo.toml b/protocols/request-response/Cargo.toml index 6da25e24862..cb78f536ae4 100644 --- a/protocols/request-response/Cargo.toml +++ b/protocols/request-response/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-request-response" edition = "2021" rust-version = { workspace = true } description = "Generic Request/Response Protocols" -version = "0.26.1" +version = "0.28.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -13,18 +13,15 @@ categories = ["network-programming", "asynchronous"] [dependencies] async-trait = "0.1" cbor4ii = { version = "0.3.2", features = ["serde1", "use_std"], optional = true } -futures = "0.3.29" -instant = "0.1.12" +futures = { workspace = true } libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } libp2p-identity = { workspace = true } rand = "0.8" serde = { version = "1.0", optional = true} -serde_json = { version = "1.0.108", optional = true } -smallvec = "1.11.2" -tracing = "0.1.37" -void = "1.0.2" -futures-timer = "3.0.2" +serde_json = { version = "1.0.117", optional = true } +smallvec = "1.13.2" +tracing = { workspace = true } futures-bounded = { workspace = true } [features] @@ -32,23 +29,18 @@ json = ["dep:serde", "dep:serde_json", "libp2p-swarm/macros"] cbor = ["dep:serde", "dep:cbor4ii", "libp2p-swarm/macros"] [dev-dependencies] -anyhow = "1.0.75" +anyhow = "1.0.86" async-std = { version = "1.6.2", features = ["attributes"] } -libp2p-noise = { workspace = true } -libp2p-tcp = { workspace = true, features = ["async-io"] } -libp2p-yamux = { workspace = true } rand = "0.8" libp2p-swarm-test = { path = "../../swarm-test" } futures_ringbuf = "0.4.0" -serde = { version = "1.0", features = ["derive"]} -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +serde = { version = "1.0", features = ["derive"] } +libp2p-test-utils = { workspace = true } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/protocols/request-response/src/cbor.rs b/protocols/request-response/src/cbor.rs index ddf11feb29f..e0ac3c440f7 100644 --- a/protocols/request-response/src/cbor.rs +++ b/protocols/request-response/src/cbor.rs @@ -37,20 +37,23 @@ /// } /// /// let behaviour = cbor::Behaviour::::new( -/// [(StreamProtocol::new("/my-cbor-protocol"), ProtocolSupport::Full)], -/// request_response::Config::default() +/// [( +/// StreamProtocol::new("/my-cbor-protocol"), +/// ProtocolSupport::Full, +/// )], +/// request_response::Config::default(), /// ); /// ``` pub type Behaviour = crate::Behaviour>; mod codec { + use std::{collections::TryReserveError, convert::Infallible, io, marker::PhantomData}; + use async_trait::async_trait; use cbor4ii::core::error::DecodeError; use futures::prelude::*; - use futures::{AsyncRead, AsyncWrite}; use libp2p_swarm::StreamProtocol; use serde::{de::DeserializeOwned, Serialize}; - use std::{collections::TryReserveError, convert::Infallible, io, marker::PhantomData}; pub struct Codec { /// Max request size in bytes @@ -163,6 +166,8 @@ mod codec { fn decode_into_io_error(err: cbor4ii::serde::DecodeError) -> io::Error { match err { + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] cbor4ii::serde::DecodeError::Core(DecodeError::Read(e)) => { io::Error::new(io::ErrorKind::Other, e) } @@ -186,13 +191,13 @@ mod codec { #[cfg(test)] mod tests { - use crate::cbor::codec::Codec; - use crate::Codec as _; use futures::AsyncWriteExt; use futures_ringbuf::Endpoint; use libp2p_swarm::StreamProtocol; use serde::{Deserialize, Serialize}; + use crate::{cbor::codec::Codec, Codec as _}; + #[async_std::test] async fn test_codec() { let expected_request = TestRequest { diff --git a/protocols/request-response/src/codec.rs b/protocols/request-response/src/codec.rs index d26b729acae..d396a75ad7b 100644 --- a/protocols/request-response/src/codec.rs +++ b/protocols/request-response/src/codec.rs @@ -18,9 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::io; + use async_trait::async_trait; use futures::prelude::*; -use std::io; /// A `Codec` defines the request and response types /// for a request-response [`Behaviour`](crate::Behaviour) protocol or diff --git a/protocols/request-response/src/handler.rs b/protocols/request-response/src/handler.rs index 2d45e0d7dc3..133cff87f40 100644 --- a/protocols/request-response/src/handler.rs +++ b/protocols/request-response/src/handler.rs @@ -20,23 +20,6 @@ pub(crate) mod protocol; -pub use protocol::ProtocolSupport; - -use crate::codec::Codec; -use crate::handler::protocol::Protocol; -use crate::{InboundRequestId, OutboundRequestId, EMPTY_QUEUE_SHRINK_THRESHOLD}; - -use futures::channel::mpsc; -use futures::{channel::oneshot, prelude::*}; -use libp2p_swarm::handler::{ - ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, - ListenUpgradeError, -}; -use libp2p_swarm::{ - handler::{ConnectionHandler, ConnectionHandlerEvent, StreamUpgradeError}, - SubstreamProtocol, -}; -use smallvec::SmallVec; use std::{ collections::VecDeque, fmt, io, @@ -48,6 +31,25 @@ use std::{ time::Duration, }; +use futures::{ + channel::{mpsc, oneshot}, + prelude::*, +}; +use libp2p_swarm::{ + handler::{ + ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, DialUpgradeError, + FullyNegotiatedInbound, FullyNegotiatedOutbound, ListenUpgradeError, StreamUpgradeError, + }, + SubstreamProtocol, +}; +pub use protocol::ProtocolSupport; +use smallvec::SmallVec; + +use crate::{ + codec::Codec, handler::protocol::Protocol, InboundRequestId, OutboundRequestId, + EMPTY_QUEUE_SHRINK_THRESHOLD, +}; + /// A connection handler for a request response [`Behaviour`](super::Behaviour) protocol. pub struct Handler where @@ -159,6 +161,9 @@ where } }; + // Inbound connections are reported to the upper layer from within the above task, + // so by failing to schedule it, it means the upper layer will never know about the + // inbound request. Because of that we do not report any inbound failure. if self .worker_streams .try_push(RequestId::Inbound(request_id), recv.boxed()) @@ -204,7 +209,10 @@ where .try_push(RequestId::Outbound(request_id), send.boxed()) .is_err() { - tracing::warn!("Dropping outbound stream because we are at capacity") + self.pending_events.push_back(Event::OutboundStreamFailed { + request_id: message.request_id, + error: io::Error::new(io::ErrorKind::Other, "max sub-streams reached"), + }); } } @@ -234,13 +242,14 @@ where self.pending_events .push_back(Event::OutboundUnsupportedProtocols(message.request_id)); } - StreamUpgradeError::Apply(e) => void::unreachable(e), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + StreamUpgradeError::Apply(e) => libp2p_core::util::unreachable(e), StreamUpgradeError::Io(e) => { - tracing::debug!( - "outbound stream for request {} failed: {e}, retrying", - message.request_id - ); - self.requested_outbound.push_back(message); + self.pending_events.push_back(Event::OutboundStreamFailed { + request_id: message.request_id, + error: e, + }); } } } @@ -251,7 +260,9 @@ where ::InboundProtocol, >, ) { - void::unreachable(error) + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(error) } } @@ -479,6 +490,8 @@ where ConnectionEvent::DialUpgradeError(dial_upgrade_error) => { self.on_dial_upgrade_error(dial_upgrade_error) } + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::ListenUpgradeError(listen_upgrade_error) => { self.on_listen_upgrade_error(listen_upgrade_error) } diff --git a/protocols/request-response/src/handler/protocol.rs b/protocols/request-response/src/handler/protocol.rs index 833cacdd6ce..a55faec2f16 100644 --- a/protocols/request-response/src/handler/protocol.rs +++ b/protocols/request-response/src/handler/protocol.rs @@ -23,6 +23,8 @@ //! receives a request and sends a response, whereas the //! outbound upgrade send a request and receives a response. +use std::convert::Infallible; + use futures::future::{ready, Ready}; use libp2p_core::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use libp2p_swarm::Stream; @@ -82,7 +84,7 @@ where P: AsRef + Clone, { type Output = (Stream, P); - type Error = void::Void; + type Error = Infallible; type Future = Ready>; fn upgrade_inbound(self, io: Stream, protocol: Self::Info) -> Self::Future { @@ -95,7 +97,7 @@ where P: AsRef + Clone, { type Output = (Stream, P); - type Error = void::Void; + type Error = Infallible; type Future = Ready>; fn upgrade_outbound(self, io: Stream, protocol: Self::Info) -> Self::Future { diff --git a/protocols/request-response/src/json.rs b/protocols/request-response/src/json.rs index 6a9533633b7..ac6f29526a7 100644 --- a/protocols/request-response/src/json.rs +++ b/protocols/request-response/src/json.rs @@ -18,7 +18,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -/// A request-response behaviour using [`serde_json`] for serializing and deserializing the messages. +/// A request-response behaviour using [`serde_json`] for serializing and deserializing the +/// messages. /// /// # Example /// @@ -36,19 +37,22 @@ /// } /// /// let behaviour = json::Behaviour::::new( -/// [(StreamProtocol::new("/my-json-protocol"), ProtocolSupport::Full)], -/// request_response::Config::default() +/// [( +/// StreamProtocol::new("/my-json-protocol"), +/// ProtocolSupport::Full, +/// )], +/// request_response::Config::default(), /// ); /// ``` pub type Behaviour = crate::Behaviour>; mod codec { + use std::{io, marker::PhantomData}; + use async_trait::async_trait; use futures::prelude::*; - use futures::{AsyncRead, AsyncWrite}; use libp2p_swarm::StreamProtocol; use serde::{de::DeserializeOwned, Serialize}; - use std::{io, marker::PhantomData}; pub struct Codec { /// Max request size in bytes @@ -160,12 +164,13 @@ mod codec { #[cfg(test)] mod tests { - use crate::Codec; use futures::AsyncWriteExt; use futures_ringbuf::Endpoint; use libp2p_swarm::StreamProtocol; use serde::{Deserialize, Serialize}; + use crate::Codec; + #[async_std::test] async fn test_codec() { let expected_request = TestRequest { diff --git a/protocols/request-response/src/lib.rs b/protocols/request-response/src/lib.rs index fc68bd6cf1f..39a773d99b4 100644 --- a/protocols/request-response/src/lib.rs +++ b/protocols/request-response/src/lib.rs @@ -73,28 +73,29 @@ mod handler; #[cfg(feature = "json")] pub mod json; -pub use codec::Codec; -pub use handler::ProtocolSupport; +use std::{ + collections::{HashMap, HashSet, VecDeque}, + fmt, io, + sync::{atomic::AtomicU64, Arc}, + task::{Context, Poll}, + time::Duration, +}; -use crate::handler::OutboundMessage; +pub use codec::Codec; use futures::channel::oneshot; use handler::Handler; -use libp2p_core::{ConnectedPoint, Endpoint, Multiaddr}; +pub use handler::ProtocolSupport; +use libp2p_core::{transport::PortUse, ConnectedPoint, Endpoint, Multiaddr}; use libp2p_identity::PeerId; use libp2p_swarm::{ behaviour::{AddressChange, ConnectionClosed, DialFailure, FromSwarm}, dial_opts::DialOpts, - ConnectionDenied, ConnectionHandler, ConnectionId, NetworkBehaviour, NotifyHandler, THandler, - THandlerInEvent, THandlerOutEvent, ToSwarm, + ConnectionDenied, ConnectionHandler, ConnectionId, NetworkBehaviour, NotifyHandler, + PeerAddresses, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; use smallvec::SmallVec; -use std::{ - collections::{HashMap, HashSet, VecDeque}, - fmt, io, - sync::{atomic::AtomicU64, Arc}, - task::{Context, Poll}, - time::Duration, -}; + +use crate::handler::OutboundMessage; /// An inbound request or response. #[derive(Debug)] @@ -130,6 +131,8 @@ pub enum Event { Message { /// The peer who sent the message. peer: PeerId, + /// The connection used. + connection_id: ConnectionId, /// The incoming message. message: Message, }, @@ -137,6 +140,8 @@ pub enum Event { OutboundFailure { /// The peer to whom the request was sent. peer: PeerId, + /// The connection used. + connection_id: ConnectionId, /// The (local) ID of the failed request. request_id: OutboundRequestId, /// The error that occurred. @@ -146,6 +151,8 @@ pub enum Event { InboundFailure { /// The peer from whom the request was received. peer: PeerId, + /// The connection used. + connection_id: ConnectionId, /// The ID of the failed inbound request. request_id: InboundRequestId, /// The error that occurred. @@ -158,6 +165,8 @@ pub enum Event { ResponseSent { /// The peer to whom the response was sent. peer: PeerId, + /// The connection used. + connection_id: ConnectionId, /// The ID of the inbound request whose response was sent. request_id: InboundRequestId, }, @@ -353,11 +362,11 @@ where /// Pending events to return from `poll`. pending_events: VecDeque, OutboundMessage>>, - /// The currently connected peers, their pending outbound and inbound responses and their known, - /// reachable addresses, if any. + /// The currently connected peers, their pending outbound and inbound responses and their + /// known, reachable addresses, if any. connected: HashMap>, /// Externally managed addresses via `add_address` and `remove_address`. - addresses: HashMap>, + addresses: PeerAddresses, /// Requests that have not yet been sent and are waiting for a connection /// to be established. pending_outbound_requests: HashMap; 10]>>, @@ -367,7 +376,8 @@ impl Behaviour where TCodec: Codec + Default + Clone + Send + 'static, { - /// Creates a new `Behaviour` for the given protocols and configuration, using [`Default`] to construct the codec. + /// Creates a new `Behaviour` for the given protocols and configuration, using [`Default`] to + /// construct the codec. pub fn new(protocols: I, cfg: Config) -> Self where I: IntoIterator, @@ -406,7 +416,7 @@ where pending_events: VecDeque::new(), connected: HashMap::new(), pending_outbound_requests: HashMap::new(), - addresses: HashMap::new(), + addresses: PeerAddresses::default(), } } @@ -470,20 +480,15 @@ where /// /// Returns true if the address was added, false otherwise (i.e. if the /// address is already in the list). + #[deprecated(note = "Use `Swarm::add_peer_address` instead.")] pub fn add_address(&mut self, peer: &PeerId, address: Multiaddr) -> bool { - self.addresses.entry(*peer).or_default().insert(address) + self.addresses.add(*peer, address) } - /// Removes an address of a peer previously added via `add_address`. + /// Removes an address of a peer previously added via [`Behaviour::add_address`]. + #[deprecated(note = "Will be removed with the next breaking release and won't be replaced.")] pub fn remove_address(&mut self, peer: &PeerId, address: &Multiaddr) { - let mut last = false; - if let Some(addresses) = self.addresses.get_mut(peer) { - addresses.retain(|a| a != address); - last = addresses.is_empty(); - } - if last { - self.addresses.remove(peer); - } + self.addresses.remove(peer, address); } /// Checks whether a peer is currently connected. @@ -572,10 +577,10 @@ where fn remove_pending_outbound_response( &mut self, peer: &PeerId, - connection: ConnectionId, + connection_id: ConnectionId, request: OutboundRequestId, ) -> bool { - self.get_connection_mut(peer, connection) + self.get_connection_mut(peer, connection_id) .map(|c| c.pending_outbound_responses.remove(&request)) .unwrap_or(false) } @@ -588,10 +593,10 @@ where fn remove_pending_inbound_response( &mut self, peer: &PeerId, - connection: ConnectionId, + connection_id: ConnectionId, request: InboundRequestId, ) -> bool { - self.get_connection_mut(peer, connection) + self.get_connection_mut(peer, connection_id) .map(|c| c.pending_inbound_responses.remove(&request)) .unwrap_or(false) } @@ -601,11 +606,11 @@ where fn get_connection_mut( &mut self, peer: &PeerId, - connection: ConnectionId, + connection_id: ConnectionId, ) -> Option<&mut Connection> { self.connected .get_mut(peer) - .and_then(|connections| connections.iter_mut().find(|c| c.id == connection)) + .and_then(|connections| connections.iter_mut().find(|c| c.id == connection_id)) } fn on_address_change( @@ -662,6 +667,7 @@ where self.pending_events .push_back(ToSwarm::GenerateEvent(Event::InboundFailure { peer: peer_id, + connection_id, request_id, error: InboundFailure::ConnectionClosed, })); @@ -671,13 +677,21 @@ where self.pending_events .push_back(ToSwarm::GenerateEvent(Event::OutboundFailure { peer: peer_id, + connection_id, request_id, error: OutboundFailure::ConnectionClosed, })); } } - fn on_dial_failure(&mut self, DialFailure { peer_id, .. }: DialFailure) { + fn on_dial_failure( + &mut self, + DialFailure { + peer_id, + connection_id, + .. + }: DialFailure, + ) { if let Some(peer) = peer_id { // If there are pending outgoing requests when a dial failure occurs, // it is implied that we are not connected to the peer, since pending @@ -690,6 +704,7 @@ where self.pending_events .push_back(ToSwarm::GenerateEvent(Event::OutboundFailure { peer, + connection_id, request_id: request.request_id, error: OutboundFailure::DialFailure, })); @@ -698,7 +713,8 @@ where } } - /// Preloads a new [`Handler`] with requests that are waiting to be sent to the newly connected peer. + /// Preloads a new [`Handler`] with requests that are + /// waiting to be sent to the newly connected peer. fn preload_new_handler( &mut self, handler: &mut Handler, @@ -764,9 +780,9 @@ where if let Some(connections) = self.connected.get(&peer) { addresses.extend(connections.iter().filter_map(|c| c.remote_address.clone())) } - if let Some(more) = self.addresses.get(&peer) { - addresses.extend(more.iter().cloned()); - } + + let cached_addrs = self.addresses.get(&peer); + addresses.extend(cached_addrs); Ok(addresses) } @@ -777,6 +793,7 @@ where peer: PeerId, remote_address: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { let mut handler = Handler::new( self.inbound_protocols.clone(), @@ -797,6 +814,7 @@ where } fn on_swarm_event(&mut self, event: FromSwarm) { + self.addresses.on_swarm_event(&event); match event { FromSwarm::ConnectionEstablished(_) => {} FromSwarm::ConnectionClosed(connection_closed) => { @@ -811,7 +829,7 @@ where fn on_connection_handler_event( &mut self, peer: PeerId, - connection: ConnectionId, + connection_id: ConnectionId, event: THandlerOutEvent, ) { match event { @@ -819,7 +837,8 @@ where request_id, response, } => { - let removed = self.remove_pending_outbound_response(&peer, connection, request_id); + let removed = + self.remove_pending_outbound_response(&peer, connection_id, request_id); debug_assert!( removed, "Expect request_id to be pending before receiving response.", @@ -830,13 +849,17 @@ where response, }; self.pending_events - .push_back(ToSwarm::GenerateEvent(Event::Message { peer, message })); + .push_back(ToSwarm::GenerateEvent(Event::Message { + peer, + connection_id, + message, + })); } handler::Event::Request { request_id, request, sender, - } => match self.get_connection_mut(&peer, connection) { + } => match self.get_connection_mut(&peer, connection_id) { Some(connection) => { let inserted = connection.pending_inbound_responses.insert(request_id); debug_assert!(inserted, "Expect id of new request to be unknown."); @@ -848,14 +871,19 @@ where channel, }; self.pending_events - .push_back(ToSwarm::GenerateEvent(Event::Message { peer, message })); + .push_back(ToSwarm::GenerateEvent(Event::Message { + peer, + connection_id, + message, + })); } None => { - tracing::debug!("Connection ({connection}) closed after `Event::Request` ({request_id}) has been emitted."); + tracing::debug!("Connection ({connection_id}) closed after `Event::Request` ({request_id}) has been emitted."); } }, handler::Event::ResponseSent(request_id) => { - let removed = self.remove_pending_inbound_response(&peer, connection, request_id); + let removed = + self.remove_pending_inbound_response(&peer, connection_id, request_id); debug_assert!( removed, "Expect request_id to be pending before response is sent." @@ -864,11 +892,13 @@ where self.pending_events .push_back(ToSwarm::GenerateEvent(Event::ResponseSent { peer, + connection_id, request_id, })); } handler::Event::ResponseOmission(request_id) => { - let removed = self.remove_pending_inbound_response(&peer, connection, request_id); + let removed = + self.remove_pending_inbound_response(&peer, connection_id, request_id); debug_assert!( removed, "Expect request_id to be pending before response is omitted.", @@ -877,12 +907,14 @@ where self.pending_events .push_back(ToSwarm::GenerateEvent(Event::InboundFailure { peer, + connection_id, request_id, error: InboundFailure::ResponseOmission, })); } handler::Event::OutboundTimeout(request_id) => { - let removed = self.remove_pending_outbound_response(&peer, connection, request_id); + let removed = + self.remove_pending_outbound_response(&peer, connection_id, request_id); debug_assert!( removed, "Expect request_id to be pending before request times out." @@ -891,12 +923,14 @@ where self.pending_events .push_back(ToSwarm::GenerateEvent(Event::OutboundFailure { peer, + connection_id, request_id, error: OutboundFailure::Timeout, })); } handler::Event::OutboundUnsupportedProtocols(request_id) => { - let removed = self.remove_pending_outbound_response(&peer, connection, request_id); + let removed = + self.remove_pending_outbound_response(&peer, connection_id, request_id); debug_assert!( removed, "Expect request_id to be pending before failing to connect.", @@ -905,28 +939,33 @@ where self.pending_events .push_back(ToSwarm::GenerateEvent(Event::OutboundFailure { peer, + connection_id, request_id, error: OutboundFailure::UnsupportedProtocols, })); } handler::Event::OutboundStreamFailed { request_id, error } => { - let removed = self.remove_pending_outbound_response(&peer, connection, request_id); + let removed = + self.remove_pending_outbound_response(&peer, connection_id, request_id); debug_assert!(removed, "Expect request_id to be pending upon failure"); self.pending_events .push_back(ToSwarm::GenerateEvent(Event::OutboundFailure { peer, + connection_id, request_id, error: OutboundFailure::Io(error), })) } handler::Event::InboundTimeout(request_id) => { - let removed = self.remove_pending_inbound_response(&peer, connection, request_id); + let removed = + self.remove_pending_inbound_response(&peer, connection_id, request_id); if removed { self.pending_events .push_back(ToSwarm::GenerateEvent(Event::InboundFailure { peer, + connection_id, request_id, error: InboundFailure::Timeout, })); @@ -938,12 +977,14 @@ where } } handler::Event::InboundStreamFailed { request_id, error } => { - let removed = self.remove_pending_inbound_response(&peer, connection, request_id); + let removed = + self.remove_pending_inbound_response(&peer, connection_id, request_id); if removed { self.pending_events .push_back(ToSwarm::GenerateEvent(Event::InboundFailure { peer, + connection_id, request_id, error: InboundFailure::Io(error), })); diff --git a/protocols/request-response/tests/error_reporting.rs b/protocols/request-response/tests/error_reporting.rs index 2dc82b2e0c5..281701f5cc3 100644 --- a/protocols/request-response/tests/error_reporting.rs +++ b/protocols/request-response/tests/error_reporting.rs @@ -1,3 +1,5 @@ +use std::{io, iter, pin::pin, time::Duration}; + use anyhow::{bail, Result}; use async_std::task::sleep; use async_trait::async_trait; @@ -10,16 +12,10 @@ use libp2p_swarm_test::SwarmExt; use request_response::{ Codec, InboundFailure, InboundRequestId, OutboundFailure, OutboundRequestId, ResponseChannel, }; -use std::pin::pin; -use std::time::Duration; -use std::{io, iter}; -use tracing_subscriber::EnvFilter; #[async_std::test] async fn report_outbound_failure_on_read_response() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let (peer1_id, mut swarm1) = new_swarm(); let (peer2_id, mut swarm2) = new_swarm(); @@ -73,10 +69,7 @@ async fn report_outbound_failure_on_read_response() { #[async_std::test] async fn report_outbound_failure_on_write_request() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - + libp2p_test_utils::with_default_env_filter(); let (peer1_id, mut swarm1) = new_swarm(); let (_peer2_id, mut swarm2) = new_swarm(); @@ -116,9 +109,7 @@ async fn report_outbound_failure_on_write_request() { #[async_std::test] async fn report_outbound_timeout_on_read_response() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); // `swarm1` needs to have a bigger timeout to avoid racing let (peer1_id, mut swarm1) = new_swarm_with_timeout(Duration::from_millis(200)); @@ -161,11 +152,59 @@ async fn report_outbound_timeout_on_read_response() { futures::future::select(server_task, client_task).await; } +#[async_std::test] +async fn report_outbound_failure_on_max_streams() { + libp2p_test_utils::with_default_env_filter(); + + // `swarm2` will be able to handle only 1 stream per time. + let swarm2_config = request_response::Config::default() + .with_request_timeout(Duration::from_millis(100)) + .with_max_concurrent_streams(1); + + let (peer1_id, mut swarm1) = new_swarm(); + let (peer2_id, mut swarm2) = new_swarm_with_config(swarm2_config); + + swarm1.listen().with_memory_addr_external().await; + swarm2.connect(&mut swarm1).await; + + let swarm1_task = async move { + let _req_id = swarm1 + .behaviour_mut() + .send_request(&peer2_id, Action::FailOnMaxStreams); + + // Keep the connection alive, otherwise swarm2 may receive `ConnectionClosed` instead. + wait_no_events(&mut swarm1).await; + }; + + // Expects OutboundFailure::Io failure. + let swarm2_task = async move { + let (peer, _inbound_req_id, action, _resp_channel) = + wait_request(&mut swarm2).await.unwrap(); + assert_eq!(peer, peer1_id); + assert_eq!(action, Action::FailOnMaxStreams); + + // A task for sending back a response is already scheduled so max concurrent + // streams is reached and no new tasks can be scheduled. + // + // We produce the failure by creating new request before we response. + let outbound_req_id = swarm2 + .behaviour_mut() + .send_request(&peer1_id, Action::FailOnMaxStreams); + + let (peer, req_id_done, error) = wait_outbound_failure(&mut swarm2).await.unwrap(); + assert_eq!(peer, peer1_id); + assert_eq!(req_id_done, outbound_req_id); + assert!(matches!(error, OutboundFailure::Io(_))); + }; + + let swarm1_task = pin!(swarm1_task); + let swarm2_task = pin!(swarm2_task); + futures::future::select(swarm1_task, swarm2_task).await; +} + #[async_std::test] async fn report_inbound_failure_on_read_request() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let (peer1_id, mut swarm1) = new_swarm(); let (_peer2_id, mut swarm2) = new_swarm(); @@ -200,9 +239,7 @@ async fn report_inbound_failure_on_read_request() { #[async_std::test] async fn report_inbound_failure_on_write_response() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let (peer1_id, mut swarm1) = new_swarm(); let (peer2_id, mut swarm2) = new_swarm(); @@ -266,9 +303,7 @@ async fn report_inbound_failure_on_write_response() { #[async_std::test] async fn report_inbound_timeout_on_write_response() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); // `swarm2` needs to have a bigger timeout to avoid racing let (peer1_id, mut swarm1) = new_swarm_with_timeout(Duration::from_millis(100)); @@ -332,6 +367,7 @@ enum Action { FailOnWriteRequest, FailOnWriteResponse, TimeoutOnWriteResponse, + FailOnMaxStreams, } impl From for u8 { @@ -343,6 +379,7 @@ impl From for u8 { Action::FailOnWriteRequest => 3, Action::FailOnWriteResponse => 4, Action::TimeoutOnWriteResponse => 5, + Action::FailOnMaxStreams => 6, } } } @@ -358,6 +395,7 @@ impl TryFrom for Action { 3 => Ok(Action::FailOnWriteRequest), 4 => Ok(Action::FailOnWriteResponse), 5 => Ok(Action::TimeoutOnWriteResponse), + 6 => Ok(Action::FailOnMaxStreams), _ => Err(io::Error::new(io::ErrorKind::Other, "invalid action")), } } @@ -468,11 +506,10 @@ impl Codec for TestCodec { } } -fn new_swarm_with_timeout( - timeout: Duration, +fn new_swarm_with_config( + cfg: request_response::Config, ) -> (PeerId, Swarm>) { let protocols = iter::once((StreamProtocol::new("/test/1"), ProtocolSupport::Full)); - let cfg = request_response::Config::default().with_request_timeout(timeout); let swarm = Swarm::new_ephemeral(|_| request_response::Behaviour::::new(protocols, cfg)); @@ -481,6 +518,13 @@ fn new_swarm_with_timeout( (peed_id, swarm) } +fn new_swarm_with_timeout( + timeout: Duration, +) -> (PeerId, Swarm>) { + let cfg = request_response::Config::default().with_request_timeout(timeout); + new_swarm_with_config(cfg) +} + fn new_swarm() -> (PeerId, Swarm>) { new_swarm_with_timeout(Duration::from_millis(100)) } @@ -506,6 +550,7 @@ async fn wait_request( request, channel, }, + .. }) => { return Ok((peer, request_id, request, channel)); } @@ -540,6 +585,7 @@ async fn wait_inbound_failure( peer, request_id, error, + .. }) => { return Ok((peer, request_id, error)); } @@ -558,6 +604,7 @@ async fn wait_outbound_failure( peer, request_id, error, + .. }) => { return Ok((peer, request_id, error)); } diff --git a/protocols/request-response/tests/peer_address.rs b/protocols/request-response/tests/peer_address.rs new file mode 100644 index 00000000000..714091fc682 --- /dev/null +++ b/protocols/request-response/tests/peer_address.rs @@ -0,0 +1,59 @@ +use std::iter; + +use libp2p_core::ConnectedPoint; +use libp2p_request_response as request_response; +use libp2p_request_response::ProtocolSupport; +use libp2p_swarm::{StreamProtocol, Swarm, SwarmEvent}; +use libp2p_swarm_test::SwarmExt; +use serde::{Deserialize, Serialize}; + +#[async_std::test] +#[cfg(feature = "cbor")] +async fn dial_succeeds_after_adding_peers_address() { + libp2p_test_utils::with_default_env_filter(); + + let protocols = iter::once((StreamProtocol::new("/ping/1"), ProtocolSupport::Full)); + let config = request_response::Config::default(); + + let mut swarm = Swarm::new_ephemeral(|_| { + request_response::cbor::Behaviour::::new(protocols.clone(), config.clone()) + }); + + let mut swarm2 = Swarm::new_ephemeral(|_| { + request_response::cbor::Behaviour::::new(protocols.clone(), config.clone()) + }); + + let peer_id2 = *swarm2.local_peer_id(); + + let (listen_addr, _) = swarm2.listen().with_memory_addr_external().await; + + swarm.add_peer_address(peer_id2, listen_addr.clone()); + + swarm.dial(peer_id2).unwrap(); + + async_std::task::spawn(swarm2.loop_on_next()); + + let (connected_peer_id, connected_address) = swarm + .wait(|event| match event { + SwarmEvent::ConnectionEstablished { + peer_id, endpoint, .. + } => { + let address = match endpoint { + ConnectedPoint::Dialer { address, .. } => Some(address), + _ => None, + }; + Some((peer_id, address)) + } + _ => None, + }) + .await; + let expected_address = listen_addr.with_p2p(peer_id2).unwrap(); + + assert_eq!(connected_peer_id, peer_id2); + assert_eq!(expected_address, connected_address.unwrap()); +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +struct Ping(Vec); +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +struct Pong(Vec); diff --git a/protocols/request-response/tests/ping.rs b/protocols/request-response/tests/ping.rs index b9e7878a78b..12458a0e5e7 100644 --- a/protocols/request-response/tests/ping.rs +++ b/protocols/request-response/tests/ping.rs @@ -20,23 +20,21 @@ //! Integration tests for the `Behaviour`. +use std::{io, iter}; + use futures::prelude::*; use libp2p_identity::PeerId; use libp2p_request_response as request_response; use libp2p_request_response::ProtocolSupport; use libp2p_swarm::{StreamProtocol, Swarm, SwarmEvent}; use libp2p_swarm_test::SwarmExt; -use rand::{self, Rng}; +use rand::Rng; use serde::{Deserialize, Serialize}; -use std::{io, iter}; -use tracing_subscriber::EnvFilter; #[async_std::test] #[cfg(feature = "cbor")] async fn is_response_outbound() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let ping = Ping("ping".to_string().into_bytes()); let offline_peer = PeerId::random(); @@ -64,6 +62,7 @@ async fn is_response_outbound() { peer, request_id: req_id, error: _error, + .. } => { assert_eq!(&offline_peer, &peer); assert_eq!(req_id, request_id1); @@ -115,6 +114,7 @@ async fn ping_protocol() { request_response::Message::Request { request, channel, .. }, + .. }) => { assert_eq!(&request, &expected_ping); assert_eq!(&peer, &peer2_id); @@ -156,6 +156,7 @@ async fn ping_protocol() { request_id, response, }, + .. } => { count += 1; assert_eq!(&response, &expected_pong); @@ -204,7 +205,8 @@ async fn emits_inbound_connection_closed_failure() { event = swarm1.select_next_some() => match event { SwarmEvent::Behaviour(request_response::Event::Message { peer, - message: request_response::Message::Request { request, channel, .. } + message: request_response::Message::Request { request, channel, .. }, + .. }) => { assert_eq!(&request, &ping); assert_eq!(&peer, &peer2_id); @@ -269,7 +271,8 @@ async fn emits_inbound_connection_closed_if_channel_is_dropped() { event = swarm1.select_next_some() => { if let SwarmEvent::Behaviour(request_response::Event::Message { peer, - message: request_response::Message::Request { request, channel, .. } + message: request_response::Message::Request { request, channel, .. }, + .. }) = event { assert_eq!(&request, &ping); assert_eq!(&peer, &peer2_id); diff --git a/protocols/stream/CHANGELOG.md b/protocols/stream/CHANGELOG.md new file mode 100644 index 00000000000..6034104debd --- /dev/null +++ b/protocols/stream/CHANGELOG.md @@ -0,0 +1,16 @@ +## 0.2.0-alpha.1 + +- Deprecate `void` crate. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). + +## 0.2.0-alpha + + + +## 0.1.0-alpha.1 +- Implement Error for `OpenStreamError`. + See [PR 5169](https://github.com/libp2p/rust-libp2p/pull/5169). + +## 0.1.0-alpha + +Initial release. diff --git a/protocols/stream/Cargo.toml b/protocols/stream/Cargo.toml new file mode 100644 index 00000000000..adb7a797794 --- /dev/null +++ b/protocols/stream/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "libp2p-stream" +version = "0.2.0-alpha.1" +edition = "2021" +rust-version.workspace = true +description = "Generic stream protocols for libp2p" +license = "MIT" +repository = "https://github.com/libp2p/rust-libp2p" +keywords = ["peer-to-peer", "libp2p", "networking"] +categories = ["network-programming", "asynchronous"] + +[dependencies] +futures = { workspace = true } +libp2p-core = { workspace = true } +libp2p-identity = { workspace = true, features = ["peerid"] } +libp2p-swarm = { workspace = true } +tracing = { workspace = true } +rand = "0.8" + +[dev-dependencies] +libp2p-swarm-test = { workspace = true } +tokio = { workspace = true, features = ["full"] } +libp2p-test-utils = { workspace = true } + +[lints] +workspace = true diff --git a/protocols/stream/README.md b/protocols/stream/README.md new file mode 100644 index 00000000000..c8a56e119ca --- /dev/null +++ b/protocols/stream/README.md @@ -0,0 +1,69 @@ +# Generic (stream) protocols + +This module provides a generic [`NetworkBehaviour`](libp2p_swarm::NetworkBehaviour) for stream-oriented protocols. +Streams are the fundamental primitive of libp2p and all other protocols are implemented using streams. +In contrast to other [`NetworkBehaviour`](libp2p_swarm::NetworkBehaviour)s, this module takes a different design approach. +All interaction happens through a [`Control`] that can be obtained via [`Behaviour::new_control`]. +[`Control`]s can be cloned and thus shared across your application. + +## Inbound + +To accept streams for a particular [`StreamProtocol`](libp2p_swarm::StreamProtocol) using this module, use [`Control::accept`]: + +### Example + +```rust,no_run +# fn main() { +# use libp2p_swarm::{Swarm, StreamProtocol}; +# use libp2p_stream as stream; +# use futures::StreamExt as _; +let mut swarm: Swarm = todo!(); + +let mut control = swarm.behaviour().new_control(); +let mut incoming = control.accept(StreamProtocol::new("/my-protocol")).unwrap(); + +let handler_future = async move { + while let Some((peer, stream)) = incoming.next().await { + // Execute your protocol using `stream`. + } +}; +# } +``` + +### Resource management + +[`Control::accept`] returns you an instance of [`IncomingStreams`]. +This struct implements [`Stream`](futures::Stream) and like other streams, is lazy. +You must continuously poll it to make progress. +In the example above, this taken care of by using the [`StreamExt::next`](futures::StreamExt::next) helper. + +Internally, we will drop streams if your application falls behind in processing these incoming streams, i.e. if whatever loop calls `.next()` is not fast enough. + +### Drop + +As soon as you drop [`IncomingStreams`], the protocol will be de-registered. +Any further attempt by remote peers to open a stream using the provided protocol will result in a negotiation error. + +## Outbound + +To open a new outbound stream for a particular protocol, use [`Control::open_stream`]. + +### Example + +```rust,no_run +# fn main() { +# use libp2p_swarm::{Swarm, StreamProtocol}; +# use libp2p_stream as stream; +# use libp2p_identity::PeerId; +let mut swarm: Swarm = todo!(); +let peer_id: PeerId = todo!(); + +let mut control = swarm.behaviour().new_control(); + +let protocol_future = async move { + let stream = control.open_stream(peer_id, StreamProtocol::new("/my-protocol")).await.unwrap(); + + // Execute your protocol here using `stream`. +}; +# } +``` \ No newline at end of file diff --git a/protocols/stream/src/behaviour.rs b/protocols/stream/src/behaviour.rs new file mode 100644 index 00000000000..e72af8fbfce --- /dev/null +++ b/protocols/stream/src/behaviour.rs @@ -0,0 +1,144 @@ +use core::fmt; +use std::{ + sync::{Arc, Mutex}, + task::{Context, Poll}, +}; + +use futures::{channel::mpsc, StreamExt}; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; +use libp2p_identity::PeerId; +use libp2p_swarm::{ + self as swarm, dial_opts::DialOpts, ConnectionDenied, ConnectionId, FromSwarm, + NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, +}; +use swarm::{ + behaviour::ConnectionEstablished, dial_opts::PeerCondition, ConnectionClosed, DialError, + DialFailure, +}; + +use crate::{handler::Handler, shared::Shared, Control}; + +/// A generic behaviour for stream-oriented protocols. +pub struct Behaviour { + shared: Arc>, + dial_receiver: mpsc::Receiver, +} + +impl Default for Behaviour { + fn default() -> Self { + Self::new() + } +} + +impl Behaviour { + pub fn new() -> Self { + let (dial_sender, dial_receiver) = mpsc::channel(0); + + Self { + shared: Arc::new(Mutex::new(Shared::new(dial_sender))), + dial_receiver, + } + } + + /// Obtain a new [`Control`]. + pub fn new_control(&self) -> Control { + Control::new(self.shared.clone()) + } +} + +/// The protocol is already registered. +#[derive(Debug)] +pub struct AlreadyRegistered; + +impl fmt::Display for AlreadyRegistered { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "The protocol is already registered") + } +} + +impl std::error::Error for AlreadyRegistered {} + +impl NetworkBehaviour for Behaviour { + type ConnectionHandler = Handler; + type ToSwarm = (); + + fn handle_established_inbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + _: &Multiaddr, + _: &Multiaddr, + ) -> Result, ConnectionDenied> { + Ok(Handler::new( + peer, + self.shared.clone(), + Shared::lock(&self.shared).receiver(peer, connection_id), + )) + } + + fn handle_established_outbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + _: &Multiaddr, + _: Endpoint, + _: PortUse, + ) -> Result, ConnectionDenied> { + Ok(Handler::new( + peer, + self.shared.clone(), + Shared::lock(&self.shared).receiver(peer, connection_id), + )) + } + + fn on_swarm_event(&mut self, event: FromSwarm) { + match event { + FromSwarm::ConnectionEstablished(ConnectionEstablished { + peer_id, + connection_id, + .. + }) => Shared::lock(&self.shared).on_connection_established(connection_id, peer_id), + FromSwarm::ConnectionClosed(ConnectionClosed { connection_id, .. }) => { + Shared::lock(&self.shared).on_connection_closed(connection_id) + } + FromSwarm::DialFailure(DialFailure { + peer_id: Some(peer_id), + error: + error @ (DialError::Transport(_) + | DialError::Denied { .. } + | DialError::NoAddresses + | DialError::WrongPeerId { .. }), + .. + }) => { + let reason = error.to_string(); // We can only forward the string repr but it is better than nothing. + + Shared::lock(&self.shared).on_dial_failure(peer_id, reason) + } + _ => {} + } + } + + fn on_connection_handler_event( + &mut self, + _peer_id: PeerId, + _connection_id: ConnectionId, + event: THandlerOutEvent, + ) { + libp2p_core::util::unreachable(event); + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll>> { + if let Poll::Ready(Some(peer)) = self.dial_receiver.poll_next_unpin(cx) { + return Poll::Ready(ToSwarm::Dial { + opts: DialOpts::peer_id(peer) + .condition(PeerCondition::DisconnectedAndNotDialing) + .build(), + }); + } + + Poll::Pending + } +} diff --git a/protocols/stream/src/control.rs b/protocols/stream/src/control.rs new file mode 100644 index 00000000000..2149c6bca48 --- /dev/null +++ b/protocols/stream/src/control.rs @@ -0,0 +1,134 @@ +use core::fmt; +use std::{ + io, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll}, +}; + +use futures::{ + channel::{mpsc, oneshot}, + SinkExt as _, StreamExt as _, +}; +use libp2p_identity::PeerId; +use libp2p_swarm::{Stream, StreamProtocol}; + +use crate::{handler::NewStream, shared::Shared, AlreadyRegistered}; + +/// A (remote) control for opening new streams and registration of inbound protocols. +/// +/// A [`Control`] can be cloned and thus allows for concurrent access. +#[derive(Clone)] +pub struct Control { + shared: Arc>, +} + +impl Control { + pub(crate) fn new(shared: Arc>) -> Self { + Self { shared } + } + + /// Attempt to open a new stream for the given protocol and peer. + /// + /// In case we are currently not connected to the peer, + /// we will attempt to make a new connection. + /// + /// ## Backpressure + /// + /// [`Control`]s support backpressure similarly to bounded channels: + /// Each [`Control`] has a guaranteed slot for internal messages. + /// A single control will always open one stream at a + /// time which is enforced by requiring `&mut self`. + /// + /// This backpressure mechanism breaks if you clone [`Control`]s excessively. + pub async fn open_stream( + &mut self, + peer: PeerId, + protocol: StreamProtocol, + ) -> Result { + tracing::debug!(%peer, "Requesting new stream"); + + let mut new_stream_sender = Shared::lock(&self.shared).sender(peer); + + let (sender, receiver) = oneshot::channel(); + + new_stream_sender + .send(NewStream { protocol, sender }) + .await + .map_err(|e| io::Error::new(io::ErrorKind::ConnectionReset, e))?; + + let stream = receiver + .await + .map_err(|e| io::Error::new(io::ErrorKind::ConnectionReset, e))??; + + Ok(stream) + } + + /// Accept inbound streams for the provided protocol. + /// + /// To stop accepting streams, simply drop the returned [`IncomingStreams`] handle. + pub fn accept( + &mut self, + protocol: StreamProtocol, + ) -> Result { + Shared::lock(&self.shared).accept(protocol) + } +} + +/// Errors while opening a new stream. +#[derive(Debug)] +#[non_exhaustive] +pub enum OpenStreamError { + /// The remote does not support the requested protocol. + UnsupportedProtocol(StreamProtocol), + /// IO Error that occurred during the protocol handshake. + Io(std::io::Error), +} + +impl From for OpenStreamError { + fn from(v: std::io::Error) -> Self { + Self::Io(v) + } +} + +impl fmt::Display for OpenStreamError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + OpenStreamError::UnsupportedProtocol(p) => { + write!(f, "failed to open stream: remote peer does not support {p}") + } + OpenStreamError::Io(e) => { + write!(f, "failed to open stream: io error: {e}") + } + } + } +} + +impl std::error::Error for OpenStreamError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Io(error) => Some(error), + _ => None, + } + } +} + +/// A handle to inbound streams for a particular protocol. +#[must_use = "Streams do nothing unless polled."] +pub struct IncomingStreams { + receiver: mpsc::Receiver<(PeerId, Stream)>, +} + +impl IncomingStreams { + pub(crate) fn new(receiver: mpsc::Receiver<(PeerId, Stream)>) -> Self { + Self { receiver } + } +} + +impl futures::Stream for IncomingStreams { + type Item = (PeerId, Stream); + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.receiver.poll_next_unpin(cx) + } +} diff --git a/protocols/stream/src/handler.rs b/protocols/stream/src/handler.rs new file mode 100644 index 00000000000..d626f48fb09 --- /dev/null +++ b/protocols/stream/src/handler.rs @@ -0,0 +1,171 @@ +use std::{ + convert::Infallible, + io, + sync::{Arc, Mutex}, + task::{Context, Poll}, +}; + +use futures::{ + channel::{mpsc, oneshot}, + StreamExt as _, +}; +use libp2p_identity::PeerId; +use libp2p_swarm::{ + self as swarm, + handler::{ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound}, + ConnectionHandler, Stream, StreamProtocol, +}; + +use crate::{shared::Shared, upgrade::Upgrade, OpenStreamError}; + +pub struct Handler { + remote: PeerId, + shared: Arc>, + + receiver: mpsc::Receiver, + pending_upgrade: Option<( + StreamProtocol, + oneshot::Sender>, + )>, +} + +impl Handler { + pub(crate) fn new( + remote: PeerId, + shared: Arc>, + receiver: mpsc::Receiver, + ) -> Self { + Self { + shared, + receiver, + pending_upgrade: None, + remote, + } + } +} + +impl ConnectionHandler for Handler { + type FromBehaviour = Infallible; + type ToBehaviour = Infallible; + type InboundProtocol = Upgrade; + type OutboundProtocol = Upgrade; + type InboundOpenInfo = (); + type OutboundOpenInfo = (); + + fn listen_protocol( + &self, + ) -> swarm::SubstreamProtocol { + swarm::SubstreamProtocol::new( + Upgrade { + supported_protocols: Shared::lock(&self.shared).supported_inbound_protocols(), + }, + (), + ) + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll< + swarm::ConnectionHandlerEvent< + Self::OutboundProtocol, + Self::OutboundOpenInfo, + Self::ToBehaviour, + >, + > { + if self.pending_upgrade.is_some() { + return Poll::Pending; + } + + match self.receiver.poll_next_unpin(cx) { + Poll::Ready(Some(new_stream)) => { + self.pending_upgrade = Some((new_stream.protocol.clone(), new_stream.sender)); + return Poll::Ready(swarm::ConnectionHandlerEvent::OutboundSubstreamRequest { + protocol: swarm::SubstreamProtocol::new( + Upgrade { + supported_protocols: vec![new_stream.protocol], + }, + (), + ), + }); + } + Poll::Ready(None) => {} // Sender is gone, no more work to do. + Poll::Pending => {} + } + + Poll::Pending + } + + fn on_behaviour_event(&mut self, event: Self::FromBehaviour) { + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(event) + } + + fn on_connection_event( + &mut self, + event: ConnectionEvent< + Self::InboundProtocol, + Self::OutboundProtocol, + Self::InboundOpenInfo, + Self::OutboundOpenInfo, + >, + ) { + match event { + ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { + protocol: (stream, protocol), + info: (), + }) => { + Shared::lock(&self.shared).on_inbound_stream(self.remote, stream, protocol); + } + ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound { + protocol: (stream, actual_protocol), + info: (), + }) => { + let Some((expected_protocol, sender)) = self.pending_upgrade.take() else { + debug_assert!( + false, + "Negotiated an outbound stream without a back channel" + ); + return; + }; + debug_assert_eq!(expected_protocol, actual_protocol); + + let _ = sender.send(Ok(stream)); + } + ConnectionEvent::DialUpgradeError(DialUpgradeError { error, info: () }) => { + let Some((p, sender)) = self.pending_upgrade.take() else { + debug_assert!( + false, + "Received a `DialUpgradeError` without a back channel" + ); + return; + }; + + let error = match error { + swarm::StreamUpgradeError::Timeout => { + OpenStreamError::Io(io::Error::from(io::ErrorKind::TimedOut)) + } + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + swarm::StreamUpgradeError::Apply(v) => libp2p_core::util::unreachable(v), + swarm::StreamUpgradeError::NegotiationFailed => { + OpenStreamError::UnsupportedProtocol(p) + } + swarm::StreamUpgradeError::Io(io) => OpenStreamError::Io(io), + }; + + let _ = sender.send(Err(error)); + } + _ => {} + } + } +} + +/// Message from a [`Control`](crate::Control) to +/// a [`ConnectionHandler`] to negotiate a new outbound stream. +#[derive(Debug)] +pub(crate) struct NewStream { + pub(crate) protocol: StreamProtocol, + pub(crate) sender: oneshot::Sender>, +} diff --git a/protocols/stream/src/lib.rs b/protocols/stream/src/lib.rs new file mode 100644 index 00000000000..d498a1b71e5 --- /dev/null +++ b/protocols/stream/src/lib.rs @@ -0,0 +1,10 @@ +#![doc = include_str!("../README.md")] + +mod behaviour; +mod control; +mod handler; +mod shared; +mod upgrade; + +pub use behaviour::{AlreadyRegistered, Behaviour}; +pub use control::{Control, IncomingStreams, OpenStreamError}; diff --git a/protocols/stream/src/shared.rs b/protocols/stream/src/shared.rs new file mode 100644 index 00000000000..62d7b3cfe68 --- /dev/null +++ b/protocols/stream/src/shared.rs @@ -0,0 +1,170 @@ +use std::{ + collections::{hash_map::Entry, HashMap}, + io, + sync::{Arc, Mutex, MutexGuard}, +}; + +use futures::channel::mpsc; +use libp2p_identity::PeerId; +use libp2p_swarm::{ConnectionId, Stream, StreamProtocol}; +use rand::seq::IteratorRandom as _; + +use crate::{handler::NewStream, AlreadyRegistered, IncomingStreams}; + +pub(crate) struct Shared { + /// Tracks the supported inbound protocols created via + /// [`Control::accept`](crate::Control::accept). + /// + /// For each [`StreamProtocol`], we hold the [`mpsc::Sender`] corresponding to the + /// [`mpsc::Receiver`] in [`IncomingStreams`]. + supported_inbound_protocols: HashMap>, + + connections: HashMap, + senders: HashMap>, + + /// Tracks channel pairs for a peer whilst we are dialing them. + pending_channels: HashMap, mpsc::Receiver)>, + + /// Sender for peers we want to dial. + /// + /// We manage this through a channel to avoid locks as part of + /// [`NetworkBehaviour::poll`](libp2p_swarm::NetworkBehaviour::poll). + dial_sender: mpsc::Sender, +} + +impl Shared { + pub(crate) fn lock(shared: &Arc>) -> MutexGuard<'_, Shared> { + shared.lock().unwrap_or_else(|e| e.into_inner()) + } +} + +impl Shared { + pub(crate) fn new(dial_sender: mpsc::Sender) -> Self { + Self { + dial_sender, + connections: Default::default(), + senders: Default::default(), + pending_channels: Default::default(), + supported_inbound_protocols: Default::default(), + } + } + + pub(crate) fn accept( + &mut self, + protocol: StreamProtocol, + ) -> Result { + if self.supported_inbound_protocols.contains_key(&protocol) { + return Err(AlreadyRegistered); + } + + let (sender, receiver) = mpsc::channel(0); + self.supported_inbound_protocols + .insert(protocol.clone(), sender); + + Ok(IncomingStreams::new(receiver)) + } + + /// Lists the protocols for which we have an active [`IncomingStreams`] instance. + pub(crate) fn supported_inbound_protocols(&mut self) -> Vec { + self.supported_inbound_protocols + .retain(|_, sender| !sender.is_closed()); + + self.supported_inbound_protocols.keys().cloned().collect() + } + + pub(crate) fn on_inbound_stream( + &mut self, + remote: PeerId, + stream: Stream, + protocol: StreamProtocol, + ) { + match self.supported_inbound_protocols.entry(protocol.clone()) { + Entry::Occupied(mut entry) => match entry.get_mut().try_send((remote, stream)) { + Ok(()) => {} + Err(e) if e.is_full() => { + tracing::debug!(%protocol, "Channel is full, dropping inbound stream"); + } + Err(e) if e.is_disconnected() => { + tracing::debug!(%protocol, "Channel is gone, dropping inbound stream"); + entry.remove(); + } + _ => unreachable!(), + }, + Entry::Vacant(_) => { + tracing::debug!(%protocol, "channel is gone, dropping inbound stream"); + } + } + } + + pub(crate) fn on_connection_established(&mut self, conn: ConnectionId, peer: PeerId) { + self.connections.insert(conn, peer); + } + + pub(crate) fn on_connection_closed(&mut self, conn: ConnectionId) { + self.connections.remove(&conn); + } + + pub(crate) fn on_dial_failure(&mut self, peer: PeerId, reason: String) { + let Some((_, mut receiver)) = self.pending_channels.remove(&peer) else { + return; + }; + + while let Ok(Some(new_stream)) = receiver.try_next() { + let _ = new_stream + .sender + .send(Err(crate::OpenStreamError::Io(io::Error::new( + io::ErrorKind::NotConnected, + reason.clone(), + )))); + } + } + + pub(crate) fn sender(&mut self, peer: PeerId) -> mpsc::Sender { + let maybe_sender = self + .connections + .iter() + .filter_map(|(c, p)| (p == &peer).then_some(c)) + .choose(&mut rand::thread_rng()) + .and_then(|c| self.senders.get(c)); + + match maybe_sender { + Some(sender) => { + tracing::debug!("Returning sender to existing connection"); + + sender.clone() + } + None => { + tracing::debug!(%peer, "Not connected to peer, initiating dial"); + + let (sender, _) = self + .pending_channels + .entry(peer) + .or_insert_with(|| mpsc::channel(0)); + + let _ = self.dial_sender.try_send(peer); + + sender.clone() + } + } + } + + pub(crate) fn receiver( + &mut self, + peer: PeerId, + connection: ConnectionId, + ) -> mpsc::Receiver { + if let Some((sender, receiver)) = self.pending_channels.remove(&peer) { + tracing::debug!(%peer, %connection, "Returning existing pending receiver"); + + self.senders.insert(connection, sender); + return receiver; + } + + tracing::debug!(%peer, %connection, "Creating new channel pair"); + + let (sender, receiver) = mpsc::channel(0); + self.senders.insert(connection, sender); + + receiver + } +} diff --git a/protocols/stream/src/upgrade.rs b/protocols/stream/src/upgrade.rs new file mode 100644 index 00000000000..bbe679f4a2c --- /dev/null +++ b/protocols/stream/src/upgrade.rs @@ -0,0 +1,45 @@ +use std::{ + convert::Infallible, + future::{ready, Ready}, +}; + +use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; +use libp2p_swarm::{Stream, StreamProtocol}; + +pub struct Upgrade { + pub(crate) supported_protocols: Vec, +} + +impl UpgradeInfo for Upgrade { + type Info = StreamProtocol; + + type InfoIter = std::vec::IntoIter; + + fn protocol_info(&self) -> Self::InfoIter { + self.supported_protocols.clone().into_iter() + } +} + +impl InboundUpgrade for Upgrade { + type Output = (Stream, StreamProtocol); + + type Error = Infallible; + + type Future = Ready>; + + fn upgrade_inbound(self, socket: Stream, info: Self::Info) -> Self::Future { + ready(Ok((socket, info))) + } +} + +impl OutboundUpgrade for Upgrade { + type Output = (Stream, StreamProtocol); + + type Error = Infallible; + + type Future = Ready>; + + fn upgrade_outbound(self, socket: Stream, info: Self::Info) -> Self::Future { + ready(Ok((socket, info))) + } +} diff --git a/protocols/stream/tests/lib.rs b/protocols/stream/tests/lib.rs new file mode 100644 index 00000000000..425b49adfaf --- /dev/null +++ b/protocols/stream/tests/lib.rs @@ -0,0 +1,77 @@ +use std::io; + +use futures::{AsyncReadExt as _, AsyncWriteExt as _, StreamExt as _}; +use libp2p_identity::PeerId; +use libp2p_stream as stream; +use libp2p_swarm::{StreamProtocol, Swarm}; +use libp2p_swarm_test::SwarmExt as _; +use libp2p_test_utils::EnvFilter; +use stream::OpenStreamError; +use tracing::level_filters::LevelFilter; + +const PROTOCOL: StreamProtocol = StreamProtocol::new("/test"); + +#[tokio::test] +async fn dropping_incoming_streams_deregisters() { + libp2p_test_utils::with_env_filter( + EnvFilter::builder() + .with_default_directive(LevelFilter::DEBUG.into()) + .from_env() + .unwrap(), + ); + + let mut swarm1 = Swarm::new_ephemeral(|_| stream::Behaviour::new()); + let mut swarm2 = Swarm::new_ephemeral(|_| stream::Behaviour::new()); + + let mut control = swarm1.behaviour().new_control(); + let mut incoming = swarm2.behaviour().new_control().accept(PROTOCOL).unwrap(); + + swarm2.listen().with_memory_addr_external().await; + swarm1.connect(&mut swarm2).await; + + let swarm2_peer_id = *swarm2.local_peer_id(); + + let handle = tokio::spawn(async move { + while let Some((_, mut stream)) = incoming.next().await { + stream.write_all(&[42]).await.unwrap(); + stream.close().await.unwrap(); + } + }); + tokio::spawn(swarm1.loop_on_next()); + tokio::spawn(swarm2.loop_on_next()); + + let mut stream = control.open_stream(swarm2_peer_id, PROTOCOL).await.unwrap(); + + let mut buf = [0u8; 1]; + stream.read_exact(&mut buf).await.unwrap(); + assert_eq!([42], buf); + + handle.abort(); + let _ = handle.await; + + let error = control + .open_stream(swarm2_peer_id, PROTOCOL) + .await + .unwrap_err(); + assert!(matches!(error, OpenStreamError::UnsupportedProtocol(_))); +} + +#[tokio::test] +async fn dial_errors_are_propagated() { + let swarm1 = Swarm::new_ephemeral(|_| stream::Behaviour::new()); + + let mut control = swarm1.behaviour().new_control(); + tokio::spawn(swarm1.loop_on_next()); + + let error = control + .open_stream(PeerId::random(), PROTOCOL) + .await + .unwrap_err(); + + let OpenStreamError::Io(e) = error else { + panic!("Unexpected error: {error}") + }; + + assert_eq!(e.kind(), io::ErrorKind::NotConnected); + assert_eq!("Dial error: no addresses for peer.", e.to_string()); +} diff --git a/protocols/upnp/CHANGELOG.md b/protocols/upnp/CHANGELOG.md index 806ed2e11d5..d9c24f8efcc 100644 --- a/protocols/upnp/CHANGELOG.md +++ b/protocols/upnp/CHANGELOG.md @@ -1,3 +1,20 @@ +## 0.3.1 +- update igd-next to 0.15.1. + See [PR XXXX](https://github.com/libp2p/rust-libp2p/pull/XXXX). + +## 0.3.0 + + + +## 0.2.2 +- Fix a panic caused when `upnp::Gateway` is dropped and its events queue receiver is no longer +available. + See [PR 5273](https://github.com/libp2p/rust-libp2p/pull/5273). + +## 0.2.1 +- Fix a panic caused when dropping `upnp::Behaviour` such as when used together with `Toggle`. + See [PR 5096](https://github.com/libp2p/rust-libp2p/pull/5096). + ## 0.2.0 diff --git a/protocols/upnp/Cargo.toml b/protocols/upnp/Cargo.toml index f00c80aabb3..a069331b1ed 100644 --- a/protocols/upnp/Cargo.toml +++ b/protocols/upnp/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-upnp" edition = "2021" rust-version = "1.60.0" description = "UPnP support for libp2p transports" -version = "0.2.0" +version = "0.3.1" license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] @@ -11,14 +11,13 @@ categories = ["network-programming", "asynchronous"] publish = true [dependencies] -futures = "0.3.29" -futures-timer = "3.0.2" -igd-next = "0.14.2" +futures = { workspace = true } +futures-timer = "3.0.3" +igd-next = "0.15.1" libp2p-core = { workspace = true } libp2p-swarm = { workspace = true } -tokio = { version = "1.34", default-features = false, features = ["rt"], optional = true } -tracing = "0.1.37" -void = "1.0.2" +tokio = { workspace = true, default-features = false, features = ["rt"], optional = true } +tracing = { workspace = true } [features] tokio = ["igd-next/aio_tokio", "dep:tokio"] @@ -30,5 +29,3 @@ workspace = true # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] diff --git a/protocols/upnp/src/behaviour.rs b/protocols/upnp/src/behaviour.rs index a94ef9526dd..cea8efb1e3f 100644 --- a/protocols/upnp/src/behaviour.rs +++ b/protocols/upnp/src/behaviour.rs @@ -32,16 +32,21 @@ use std::{ time::Duration, }; -use crate::tokio::{is_addr_global, Gateway}; use futures::{channel::oneshot, Future, StreamExt}; use futures_timer::Delay; use igd_next::PortMappingProtocol; -use libp2p_core::{multiaddr, transport::ListenerId, Endpoint, Multiaddr}; +use libp2p_core::{ + multiaddr, + transport::{ListenerId, PortUse}, + Endpoint, Multiaddr, +}; use libp2p_swarm::{ derive_prelude::PeerId, dummy, ConnectionDenied, ConnectionId, ExpiredListenAddr, FromSwarm, NetworkBehaviour, NewListenAddr, ToSwarm, }; +use crate::tokio::{is_addr_global, Gateway}; + /// The duration in seconds of a port mapping on the gateway. const MAPPING_DURATION: u32 = 3600; @@ -248,6 +253,7 @@ impl NetworkBehaviour for Behaviour { _peer: PeerId, _addr: &Multiaddr, _role_override: Endpoint, + _port_use: PortUse, ) -> Result, libp2p_swarm::ConnectionDenied> { Ok(dummy::ConnectionHandler) } @@ -281,8 +287,9 @@ impl NetworkBehaviour for Behaviour { match &mut self.state { GatewayState::Searching(_) => { - // As the gateway is not yet available we add the mapping with `MappingState::Inactive` - // so that when and if it becomes available we map it. + // As the gateway is not yet available we add the mapping with + // `MappingState::Inactive` so that when and if it + // becomes available we map it. self.mappings.insert( Mapping { listener_id, @@ -361,7 +368,7 @@ impl NetworkBehaviour for Behaviour { _connection_id: ConnectionId, event: libp2p_swarm::THandlerOutEvent, ) { - void::unreachable(event) + libp2p_core::util::unreachable(event) } #[tracing::instrument(level = "trace", name = "NetworkBehaviour::poll", skip(self, cx))] diff --git a/protocols/upnp/src/lib.rs b/protocols/upnp/src/lib.rs index 8a74d7e8f63..d7a746f78df 100644 --- a/protocols/upnp/src/lib.rs +++ b/protocols/upnp/src/lib.rs @@ -24,7 +24,6 @@ //! implements the [`libp2p_swarm::NetworkBehaviour`] trait. //! This struct will automatically try to map the ports externally to internal //! addresses on the gateway. -//! #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] diff --git a/protocols/upnp/src/tokio.rs b/protocols/upnp/src/tokio.rs index c6a40182b33..67ef52f9608 100644 --- a/protocols/upnp/src/tokio.rs +++ b/protocols/upnp/src/tokio.rs @@ -20,7 +20,6 @@ use std::{error::Error, net::IpAddr}; -use crate::behaviour::{GatewayEvent, GatewayRequest}; use futures::{ channel::{mpsc, oneshot}, SinkExt, StreamExt, @@ -28,8 +27,9 @@ use futures::{ use igd_next::SearchOptions; pub use crate::behaviour::Behaviour; +use crate::behaviour::{GatewayEvent, GatewayRequest}; -//TODO: remove when `IpAddr::is_global` stabilizes. +// TODO: remove when `IpAddr::is_global` stabilizes. pub(crate) fn is_addr_global(addr: IpAddr) -> bool { match addr { IpAddr::V4(ip) => { @@ -100,9 +100,7 @@ pub(crate) fn search_gateway() -> oneshot::Receiver gateway, Err(err) => { - search_result_sender - .send(Err(err.into())) - .expect("receiver shouldn't have been dropped"); + let _ = search_result_sender.send(Err(err.into())); return; } }; @@ -110,20 +108,22 @@ pub(crate) fn search_gateway() -> oneshot::Receiver addr, Err(err) => { - search_result_sender - .send(Err(err.into())) - .expect("receiver shouldn't have been dropped"); + let _ = search_result_sender.send(Err(err.into())); return; } }; - search_result_sender + // Check if receiver dropped. + if search_result_sender .send(Ok(Gateway { sender: events_sender, receiver: events_queue, external_addr, })) - .expect("receiver shouldn't have been dropped"); + .is_err() + { + return; + } loop { // The task sender has dropped so we can return. @@ -158,10 +158,10 @@ pub(crate) fn search_gateway() -> oneshot::Receiver"] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -14,17 +14,14 @@ categories = ["network-programming", "asynchronous"] proc-macro = true [dependencies] -heck = "0.4" +heck = "0.5" quote = "1.0" -syn = { version = "2.0.39", default-features = false, features = ["clone-impls", "derive", "parsing", "printing", "proc-macro"] } -proc-macro2 = "1.0" +syn = { version = "2.0.66", default-features = false, features = ["clone-impls", "derive", "parsing", "printing", "proc-macro"] } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/swarm-derive/src/lib.rs b/swarm-derive/src/lib.rs index 514975390b0..41b909f329f 100644 --- a/swarm-derive/src/lib.rs +++ b/swarm-derive/src/lib.rs @@ -23,12 +23,12 @@ mod syn_ext; -use crate::syn_ext::RequireStrLit; use heck::ToUpperCamelCase; use proc_macro::TokenStream; use quote::quote; -use syn::punctuated::Punctuated; -use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Meta, Token}; +use syn::{parse_macro_input, punctuated::Punctuated, Data, DataStruct, DeriveInput, Meta, Token}; + +use crate::syn_ext::RequireStrLit; /// Generates a delegating `NetworkBehaviour` implementation for the struct this is used for. See /// the trait documentation for better description. @@ -71,24 +71,12 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> syn::Result syn::Result syn::Result quote! { - self.#i.on_swarm_event(#from_swarm::ConnectionEstablished(#connection_established { - peer_id, - connection_id, - endpoint, - failed_addresses, - other_established, - })); - }, - None => quote! { - self.#field_n.on_swarm_event(#from_swarm::ConnectionEstablished(#connection_established { - peer_id, - connection_id, - endpoint, - failed_addresses, - other_established, - })); - }, - }) - }; - - // Build the list of statements to put in the body of `on_swarm_event()` - // for the `FromSwarm::AddressChange variant`. - let on_address_change_stmts = { + // Build the list of statements to put in the body of `on_swarm_event()`. + let on_swarm_event_stmts = { data_struct .fields .iter() .enumerate() .map(|(field_n, field)| match field.ident { Some(ref i) => quote! { - self.#i.on_swarm_event(#from_swarm::AddressChange(#address_change { - peer_id, - connection_id, - old, - new, - })); + self.#i.on_swarm_event(event); }, None => quote! { - self.#field_n.on_swarm_event(#from_swarm::AddressChange(#address_change { - peer_id, - connection_id, - old, - new, - })); - }, - }) - }; - - // Build the list of statements to put in the body of `on_swarm_event()` - // for the `FromSwarm::ConnectionClosed` variant. - let on_connection_closed_stmts = { - data_struct - .fields - .iter() - .rev() - .enumerate() - .map(|(enum_n, field)| { - let inject = match field.ident { - Some(ref i) => quote! { - self.#i.on_swarm_event(#from_swarm::ConnectionClosed(#connection_closed { - peer_id, - connection_id, - endpoint, - remaining_established, - })); - }, - None => quote! { - self.#enum_n.on_swarm_event(#from_swarm::ConnectionClosed(#connection_closed { - peer_id, - connection_id, - endpoint, - remaining_established, - })); - }, - }; - - quote! { - #inject; - } - }) - }; - - // Build the list of statements to put in the body of `on_swarm_event()` - // for the `FromSwarm::DialFailure` variant. - let on_dial_failure_stmts = data_struct - .fields - .iter() - .enumerate() - .map(|(enum_n, field)| match field.ident { - Some(ref i) => quote! { - self.#i.on_swarm_event(#from_swarm::DialFailure(#dial_failure { - peer_id, - connection_id, - error, - })); - }, - None => quote! { - self.#enum_n.on_swarm_event(#from_swarm::DialFailure(#dial_failure { - peer_id, - connection_id, - error, - })); - }, - }); - - // Build the list of statements to put in the body of `on_swarm_event()` - // for the `FromSwarm::ListenFailure` variant. - let on_listen_failure_stmts = data_struct - .fields - .iter() - .enumerate() - .map(|(enum_n, field)| match field.ident { - Some(ref i) => quote! { - self.#i.on_swarm_event(#from_swarm::ListenFailure(#listen_failure { - local_addr, - send_back_addr, - connection_id, - error - })); - }, - None => quote! { - self.#enum_n.on_swarm_event(#from_swarm::ListenFailure(#listen_failure { - local_addr, - send_back_addr, - connection_id, - error - })); - }, - }); - - // Build the list of statements to put in the body of `on_swarm_event()` - // for the `FromSwarm::NewListener` variant. - let on_new_listener_stmts = { - data_struct - .fields - .iter() - .enumerate() - .map(|(field_n, field)| match field.ident { - Some(ref i) => quote! { - self.#i.on_swarm_event(#from_swarm::NewListener(#new_listener { - listener_id, - })); - }, - None => quote! { - self.#field_n.on_swarm_event(#from_swarm::NewListener(#new_listener { - listener_id, - })); - }, - }) - }; - - // Build the list of statements to put in the body of `on_swarm_event()` - // for the `FromSwarm::NewListenAddr` variant. - let on_new_listen_addr_stmts = { - data_struct - .fields - .iter() - .enumerate() - .map(|(field_n, field)| match field.ident { - Some(ref i) => quote! { - self.#i.on_swarm_event(#from_swarm::NewListenAddr(#new_listen_addr { - listener_id, - addr, - })); - }, - None => quote! { - self.#field_n.on_swarm_event(#from_swarm::NewListenAddr(#new_listen_addr { - listener_id, - addr, - })); - }, - }) - }; - - // Build the list of statements to put in the body of `on_swarm_event()` - // for the `FromSwarm::ExpiredListenAddr` variant. - let on_expired_listen_addr_stmts = { - data_struct - .fields - .iter() - .enumerate() - .map(|(field_n, field)| match field.ident { - Some(ref i) => quote! { - self.#i.on_swarm_event(#from_swarm::ExpiredListenAddr(#expired_listen_addr { - listener_id, - addr, - })); - }, - None => quote! { - self.#field_n.on_swarm_event(#from_swarm::ExpiredListenAddr(#expired_listen_addr { - listener_id, - addr, - })); - }, - }) - }; - - // Build the list of statements to put in the body of `on_swarm_event()` - // for the `FromSwarm::NewExternalAddr` variant. - let on_new_external_addr_candidate_stmts = { - data_struct - .fields - .iter() - .enumerate() - .map(|(field_n, field)| match field.ident { - Some(ref i) => quote! { - self.#i.on_swarm_event(#from_swarm::NewExternalAddrCandidate(#new_external_addr_candidate { - addr, - })); - }, - None => quote! { - self.#field_n.on_swarm_event(#from_swarm::NewExternalAddrCandidate(#new_external_addr_candidate { - addr, - })); - }, - }) - }; - - // Build the list of statements to put in the body of `on_swarm_event()` - // for the `FromSwarm::ExternalAddrExpired` variant. - let on_external_addr_expired_stmts = { - data_struct - .fields - .iter() - .enumerate() - .map(|(field_n, field)| match field.ident { - Some(ref i) => quote! { - self.#i.on_swarm_event(#from_swarm::ExternalAddrExpired(#external_addr_expired { - addr, - })); - }, - None => quote! { - self.#field_n.on_swarm_event(#from_swarm::ExternalAddrExpired(#external_addr_expired { - addr, - })); - }, - }) - }; - - // Build the list of statements to put in the body of `on_swarm_event()` - // for the `FromSwarm::ExternalAddrConfirmed` variant. - let on_external_addr_confirmed_stmts = { - data_struct - .fields - .iter() - .enumerate() - .map(|(field_n, field)| match field.ident { - Some(ref i) => quote! { - self.#i.on_swarm_event(#from_swarm::ExternalAddrConfirmed(#external_addr_confirmed { - addr, - })); - }, - None => quote! { - self.#field_n.on_swarm_event(#from_swarm::ExternalAddrConfirmed(#external_addr_confirmed { - addr, - })); - }, - }) - }; - - // Build the list of statements to put in the body of `on_swarm_event()` - // for the `FromSwarm::ListenerError` variant. - let on_listener_error_stmts = { - data_struct - .fields - .iter() - .enumerate() - .map(|(field_n, field)| match field.ident { - Some(ref i) => quote! { - self.#i.on_swarm_event(#from_swarm::ListenerError(#listener_error { - listener_id, - err, - })); - }, - None => quote! { - self.#field_n.on_swarm_event(#from_swarm::ListenerError(#listener_error { - listener_id, - err, - })); - }, - }) - }; - - // Build the list of statements to put in the body of `on_swarm_event()` - // for the `FromSwarm::ListenerClosed` variant. - let on_listener_closed_stmts = { - data_struct - .fields - .iter() - .enumerate() - .map(|(field_n, field)| match field.ident { - Some(ref i) => quote! { - self.#i.on_swarm_event(#from_swarm::ListenerClosed(#listener_closed { - listener_id, - reason, - })); - }, - None => quote! { - self.#field_n.on_swarm_event(#from_swarm::ListenerClosed(#listener_closed { - listener_id, - reason, - })); + self.#field_n.on_swarm_event(event); }, }) }; @@ -657,7 +347,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> syn::Result syn::Result Result<#t_handler, #connection_denied> { Ok(#handle_established_outbound_connection) } @@ -792,48 +483,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> syn::Result { #(#on_connection_established_stmts)* } - #from_swarm::AddressChange( - #address_change { peer_id, connection_id, old, new }) - => { #(#on_address_change_stmts)* } - #from_swarm::ConnectionClosed( - #connection_closed { peer_id, connection_id, endpoint, remaining_established }) - => { #(#on_connection_closed_stmts)* } - #from_swarm::DialFailure( - #dial_failure { peer_id, connection_id, error }) - => { #(#on_dial_failure_stmts)* } - #from_swarm::ListenFailure( - #listen_failure { local_addr, send_back_addr, connection_id, error }) - => { #(#on_listen_failure_stmts)* } - #from_swarm::NewListener( - #new_listener { listener_id }) - => { #(#on_new_listener_stmts)* } - #from_swarm::NewListenAddr( - #new_listen_addr { listener_id, addr }) - => { #(#on_new_listen_addr_stmts)* } - #from_swarm::ExpiredListenAddr( - #expired_listen_addr { listener_id, addr }) - => { #(#on_expired_listen_addr_stmts)* } - #from_swarm::NewExternalAddrCandidate( - #new_external_addr_candidate { addr }) - => { #(#on_new_external_addr_candidate_stmts)* } - #from_swarm::ExternalAddrExpired( - #external_addr_expired { addr }) - => { #(#on_external_addr_expired_stmts)* } - #from_swarm::ExternalAddrConfirmed( - #external_addr_confirmed { addr }) - => { #(#on_external_addr_confirmed_stmts)* } - #from_swarm::ListenerError( - #listener_error { listener_id, err }) - => { #(#on_listener_error_stmts)* } - #from_swarm::ListenerClosed( - #listener_closed { listener_id, reason }) - => { #(#on_listener_closed_stmts)* } - _ => {} - } + #(#on_swarm_event_stmts)* } } }; diff --git a/swarm-test/CHANGELOG.md b/swarm-test/CHANGELOG.md index 95223e60272..1fd213e12f6 100644 --- a/swarm-test/CHANGELOG.md +++ b/swarm-test/CHANGELOG.md @@ -1,3 +1,16 @@ +## 0.5.0 + +- Add `tokio` runtime support and make `tokio` and `async-std` runtimes optional behind features. + See [PR 5551]. + - Update default for idle-connection-timeout to 10s on `SwarmExt::new_ephemeral` methods. + See [PR 4967](https://github.com/libp2p/rust-libp2p/pull/4967). + +[PR 5551]: https://github.com/libp2p/rust-libp2p/pull/5551 + +## 0.4.0 + + + ## 0.3.0 diff --git a/swarm-test/Cargo.toml b/swarm-test/Cargo.toml index 12f8be2a1d8..4a0d5ee8c71 100644 --- a/swarm-test/Cargo.toml +++ b/swarm-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-swarm-test" -version = "0.3.0" +version = "0.5.0" edition = "2021" rust-version = { workspace = true } license = "MIT" @@ -12,17 +12,22 @@ categories = ["network-programming", "asynchronous"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-trait = "0.1.74" +async-trait = "0.1.80" libp2p-core = { workspace = true } libp2p-identity = { workspace = true, features = ["rand"] } libp2p-plaintext = { workspace = true } -libp2p-swarm = { workspace = true, features = ["async-std"] } -libp2p-tcp = { workspace = true, features = ["async-io"] } +libp2p-swarm = { workspace = true } +libp2p-tcp = { workspace = true } libp2p-yamux = { workspace = true } -futures = "0.3.29" -rand = "0.8.5" -tracing = "0.1.37" -futures-timer = "3.0.2" +futures = { workspace = true } +tracing = { workspace = true } +futures-timer = "3.0.3" + +[features] +default = ["async-std"] + +async-std = ["libp2p-swarm/async-std", "libp2p-tcp/async-io"] +tokio = ["libp2p-swarm/tokio", "libp2p-tcp/tokio"] [lints] workspace = true diff --git a/swarm-test/src/lib.rs b/swarm-test/src/lib.rs index 48f5bcbf4ef..0bc417dd8b1 100644 --- a/swarm-test/src/lib.rs +++ b/swarm-test/src/lib.rs @@ -18,40 +18,55 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::{fmt::Debug, future::IntoFuture, time::Duration}; + use async_trait::async_trait; -use futures::future::{BoxFuture, Either}; -use futures::{FutureExt, StreamExt}; -use libp2p_core::{ - multiaddr::Protocol, transport::MemoryTransport, upgrade::Version, Multiaddr, Transport, +use futures::{ + future::{BoxFuture, Either}, + FutureExt, StreamExt, +}; +use libp2p_core::{multiaddr::Protocol, Multiaddr}; +use libp2p_identity::PeerId; +use libp2p_swarm::{ + dial_opts::{DialOpts, PeerCondition}, + NetworkBehaviour, Swarm, SwarmEvent, }; -use libp2p_identity::{Keypair, PeerId}; -use libp2p_plaintext as plaintext; -use libp2p_swarm::dial_opts::PeerCondition; -use libp2p_swarm::{self as swarm, dial_opts::DialOpts, NetworkBehaviour, Swarm, SwarmEvent}; -use libp2p_yamux as yamux; -use std::fmt::Debug; -use std::future::IntoFuture; -use std::time::Duration; - -/// An extension trait for [`Swarm`] that makes it easier to set up a network of [`Swarm`]s for tests. + +/// An extension trait for [`Swarm`] that makes it +/// easier to set up a network of [`Swarm`]s for tests. #[async_trait] pub trait SwarmExt { type NB: NetworkBehaviour; - /// Create a new [`Swarm`] with an ephemeral identity. + /// Create a new [`Swarm`] with an ephemeral identity and the `async-std` runtime. + /// + /// The swarm will use a [`libp2p_core::transport::MemoryTransport`] together with a + /// [`libp2p_plaintext::Config`] authentication layer and [`libp2p_yamux::Config`] as the + /// multiplexer. However, these details should not be relied + /// upon by the test and may change at any time. + #[cfg(feature = "async-std")] + fn new_ephemeral(behaviour_fn: impl FnOnce(libp2p_identity::Keypair) -> Self::NB) -> Self + where + Self: Sized; + + /// Create a new [`Swarm`] with an ephemeral identity and the `tokio` runtime. /// - /// The swarm will use a [`MemoryTransport`] together with a [`plaintext::Config`] authentication layer and - /// [`yamux::Config`] as the multiplexer. However, these details should not be relied upon by the test - /// and may change at any time. - fn new_ephemeral(behaviour_fn: impl FnOnce(Keypair) -> Self::NB) -> Self + /// The swarm will use a [`libp2p_core::transport::MemoryTransport`] together with a + /// [`libp2p_plaintext::Config`] authentication layer and [`libp2p_yamux::Config`] as the + /// multiplexer. However, these details should not be relied + /// upon by the test and may change at any time. + #[cfg(feature = "tokio")] + fn new_ephemeral_tokio(behaviour_fn: impl FnOnce(libp2p_identity::Keypair) -> Self::NB) -> Self where Self: Sized; - /// Establishes a connection to the given [`Swarm`], polling both of them until the connection is established. + /// Establishes a connection to the given [`Swarm`], polling both of them until the connection + /// is established. /// /// This will take addresses from the `other` [`Swarm`] via [`Swarm::external_addresses`]. /// By default, this iterator will not yield any addresses. - /// To add listen addresses as external addresses, use [`ListenFuture::with_memory_addr_external`] or [`ListenFuture::with_tcp_addr_external`]. + /// To add listen addresses as external addresses, use + /// [`ListenFuture::with_memory_addr_external`] or [`ListenFuture::with_tcp_addr_external`]. async fn connect(&mut self, other: &mut Swarm) where T: NetworkBehaviour + Send, @@ -59,10 +74,12 @@ pub trait SwarmExt { /// Dial the provided address and wait until a connection has been established. /// - /// In a normal test scenario, you should prefer [`SwarmExt::connect`] but that is not always possible. - /// This function only abstracts away the "dial and wait for `ConnectionEstablished` event" part. + /// In a normal test scenario, you should prefer [`SwarmExt::connect`] but that is not always + /// possible. This function only abstracts away the "dial and wait for + /// `ConnectionEstablished` event" part. /// - /// Because we don't have access to the other [`Swarm`], we can't guarantee that it makes progress. + /// Because we don't have access to the other [`Swarm`], + /// we can't guarantee that it makes progress. async fn dial_and_wait(&mut self, addr: Multiaddr) -> PeerId; /// Wait for specified condition to return `Some`. @@ -71,7 +88,8 @@ pub trait SwarmExt { P: Fn(SwarmEvent<::ToSwarm>) -> Option, P: Send; - /// Listens for incoming connections, polling the [`Swarm`] until the transport is ready to accept connections. + /// Listens for incoming connections, polling the [`Swarm`] until the + /// transport is ready to accept connections. /// /// The first address is for the memory transport, the second one for the TCP transport. fn listen(&mut self) -> ListenFuture<&mut Self>; @@ -95,17 +113,19 @@ pub trait SwarmExt { /// /// ## Number of events /// -/// The number of events is configured via const generics based on the array size of the return type. -/// This allows the compiler to infer how many events you are expecting based on how you use this function. -/// For example, if you expect the first [`Swarm`] to emit 2 events, you should assign the first variable of the returned tuple value to an array of size 2. -/// This works especially well if you directly pattern-match on the return value. +/// The number of events is configured via const generics based on the array size of the return +/// type. This allows the compiler to infer how many events you are expecting based on how you use +/// this function. For example, if you expect the first [`Swarm`] to emit 2 events, you should +/// assign the first variable of the returned tuple value to an array of size 2. This works +/// especially well if you directly pattern-match on the return value. /// /// ## Type of event /// /// This function utilizes the [`TryIntoOutput`] trait. /// Similar as to the number of expected events, the type of event is inferred based on your usage. /// If you match against a [`SwarmEvent`], the first [`SwarmEvent`] will be returned. -/// If you match against your [`NetworkBehaviour::ToSwarm`] type, [`SwarmEvent`]s which are not [`SwarmEvent::Behaviour`] will be skipped until the [`Swarm`] returns a behaviour event. +/// If you match against your [`NetworkBehaviour::ToSwarm`] type, [`SwarmEvent`]s which are not +/// [`SwarmEvent::Behaviour`] will be skipped until the [`Swarm`] returns a behaviour event. /// /// You can implement the [`TryIntoOutput`] for any other type to further customize this behaviour. /// @@ -113,13 +133,16 @@ pub trait SwarmExt { /// /// This function is similar to joining two futures with two crucial differences: /// 1. As described above, it allows you to obtain more than a single event. -/// 2. More importantly, it will continue to poll the [`Swarm`]s **even if they already has emitted all expected events**. +/// 2. More importantly, it will continue to poll the [`Swarm`]s **even if they already has emitted +/// all expected events**. /// /// Especially (2) is crucial for our usage of this function. /// If a [`Swarm`] is not polled, nothing within it makes progress. -/// This can "starve" the other swarm which for example may wait for another message to be sent on a connection. +/// This can "starve" the other swarm which for example may wait for another message to be sent on a +/// connection. /// -/// Using [`drive`] instead of [`futures::future::join`] ensures that a [`Swarm`] continues to be polled, even after it emitted its events. +/// Using [`drive`] instead of [`futures::future::join`] ensures that a [`Swarm`] continues to be +/// polled, even after it emitted its events. pub async fn drive< TBehaviour1, const NUM_EVENTS_SWARM_1: usize, @@ -200,18 +223,49 @@ where { type NB = B; - fn new_ephemeral(behaviour_fn: impl FnOnce(Keypair) -> Self::NB) -> Self + #[cfg(feature = "async-std")] + fn new_ephemeral(behaviour_fn: impl FnOnce(libp2p_identity::Keypair) -> Self::NB) -> Self where Self: Sized, { + use libp2p_core::{transport::MemoryTransport, upgrade::Version, Transport as _}; + use libp2p_identity::Keypair; + let identity = Keypair::generate_ed25519(); let peer_id = PeerId::from(identity.public()); let transport = MemoryTransport::default() .or_transport(libp2p_tcp::async_io::Transport::default()) .upgrade(Version::V1) - .authenticate(plaintext::Config::new(&identity)) - .multiplex(yamux::Config::default()) + .authenticate(libp2p_plaintext::Config::new(&identity)) + .multiplex(libp2p_yamux::Config::default()) + .timeout(Duration::from_secs(20)) + .boxed(); + + Swarm::new( + transport, + behaviour_fn(identity), + peer_id, + libp2p_swarm::Config::with_async_std_executor(), + ) + } + + #[cfg(feature = "tokio")] + fn new_ephemeral_tokio(behaviour_fn: impl FnOnce(libp2p_identity::Keypair) -> Self::NB) -> Self + where + Self: Sized, + { + use libp2p_core::{transport::MemoryTransport, upgrade::Version, Transport as _}; + use libp2p_identity::Keypair; + + let identity = Keypair::generate_ed25519(); + let peer_id = PeerId::from(identity.public()); + + let transport = MemoryTransport::default() + .or_transport(libp2p_tcp::tokio::Transport::default()) + .upgrade(Version::V1) + .authenticate(libp2p_plaintext::Config::new(&identity)) + .multiplex(libp2p_yamux::Config::default()) .timeout(Duration::from_secs(20)) .boxed(); @@ -219,8 +273,7 @@ where transport, behaviour_fn(identity), peer_id, - swarm::Config::with_async_std_executor() - .with_idle_connection_timeout(Duration::from_secs(5)), // Some tests need connections to be kept alive beyond what the individual behaviour configures., + libp2p_swarm::Config::with_tokio_executor(), ) } @@ -346,20 +399,24 @@ pub struct ListenFuture { } impl ListenFuture { - /// Adds the memory address we are starting to listen on as an external address using [`Swarm::add_external_address`]. + /// Adds the memory address we are starting to listen on as an external address using + /// [`Swarm::add_external_address`]. /// - /// This is typically "safe" for tests because within a process, memory addresses are "globally" reachable. - /// However, some tests depend on which addresses are external and need this to be configurable so it is not a good default. + /// This is typically "safe" for tests because within a process, memory addresses are "globally" + /// reachable. However, some tests depend on which addresses are external and need this to + /// be configurable so it is not a good default. pub fn with_memory_addr_external(mut self) -> Self { self.add_memory_external = true; self } - /// Adds the TCP address we are starting to listen on as an external address using [`Swarm::add_external_address`]. + /// Adds the TCP address we are starting to listen on as an external address using + /// [`Swarm::add_external_address`]. /// - /// This is typically "safe" for tests because on the same machine, 127.0.0.1 is reachable for other [`Swarm`]s. - /// However, some tests depend on which addresses are external and need this to be configurable so it is not a good default. + /// This is typically "safe" for tests because on the same machine, 127.0.0.1 is reachable for + /// other [`Swarm`]s. However, some tests depend on which addresses are external and need + /// this to be configurable so it is not a good default. pub fn with_tcp_addr_external(mut self) -> Self { self.add_tcp_external = true; diff --git a/swarm/CHANGELOG.md b/swarm/CHANGELOG.md index 48cafee6ced..f1ad994bd1b 100644 --- a/swarm/CHANGELOG.md +++ b/swarm/CHANGELOG.md @@ -1,3 +1,59 @@ +## 0.45.2 + +- Don't report `NewExternalAddrCandidate` for confirmed external addresses. + See [PR 5582](https://github.com/libp2p/rust-libp2p/pull/5582). + +- Deprecate `void` crate. + See [PR 5676](https://github.com/libp2p/rust-libp2p/pull/5676). + +- Fix `cargo clippy` warnings in `rustc 1.84.0-beta.1`. + See [PR 5700](https://github.com/libp2p/rust-libp2p/pull/5700). + +- Update default for idle-connection-timeout to 10s. + See [PR 4967](https://github.com/libp2p/rust-libp2p/pull/4967). + +## 0.45.1 + +- Update `libp2p-swarm-derive` to version `0.35.0`, see [PR 5545] + +[PR 5545]: https://github.com/libp2p/rust-libp2p/pull/5545 + +## 0.45.0 + +- Implement refactored `Transport`. + See [PR 4568] +- Move `address_translation` into swarm and into `libp2p-identify`. + See [PR 4568] + +[PR 4568]: https://github.com/libp2p/rust-libp2p/pull/4568 + +## 0.44.3 +- Optimize internal connection `fn poll`. New implementation now scales much better with number of listen protocols active. + No changes to public API introduced. + See [PR 5026](https://github.com/libp2p/rust-libp2p/pull/5026) +- Add peer_id to `FromSwarm::ListenFailure`. + See [PR 4818](https://github.com/libp2p/rust-libp2p/pull/4818). +- Use `web-time` instead of `instant`. + See [PR 5347](https://github.com/libp2p/rust-libp2p/pull/5347). +- Add `#[track_caller]` on all `spawn` wrappers. + See [PR 5465](https://github.com/libp2p/rust-libp2p/pull/5465). +- Add ConnectionError to FromSwarm::ConnectionClosed. + See [PR 5485](https://github.com/libp2p/rust-libp2p/pull/5485). + +## 0.44.2 + +- Allow `NetworkBehaviour`s to share addresses of peers. + This is enabled via the new `ToSwarm::NewExternalAddrOfPeer` event. + The address is broadcast to all behaviours via `FromSwarm::NewExternalAddrOfPeer`. + Protocols that want to collect these addresses can use the new `PeerAddresses` utility. + See [PR 4371](https://github.com/libp2p/rust-libp2p/pull/4371). + +## 0.44.1 + +- Implement `Clone` & `Copy` for `FromSwarm. + This makes it easier to forward these events when wrapping other behaviours. + See [PR 4825](https://github.com/libp2p/rust-libp2p/pull/4825). + ## 0.44.0 - Add `#[non_exhaustive]` to `FromSwarm`, `ToSwarm`, `SwarmEvent`, `ConnectionHandlerEvent`, `ConnectionEvent`. @@ -591,7 +647,7 @@ ## 0.34.0 [2022-02-22] - Rename `ProtocolsHandler` to `ConnectionHandler`. Upgrade should be as simple as renaming all - occurences of `ProtocolsHandler` to `ConnectionHandler` with your favorite text manipulation tool + occurrences of `ProtocolsHandler` to `ConnectionHandler` with your favorite text manipulation tool across your codebase. See [PR 2527]. - Fold `libp2p-core`'s `Network` into `Swarm`. See [PR 2492]. diff --git a/swarm/Cargo.toml b/swarm/Cargo.toml index 3b706df6d2b..b7e0fd73b5e 100644 --- a/swarm/Cargo.toml +++ b/swarm/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-swarm" edition = "2021" rust-version = { workspace = true } description = "The libp2p swarm" -version = "0.44.0" +version = "0.45.2" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,26 +11,26 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -either = "1.9.0" +either = "1.11.0" fnv = "1.0" -futures = "0.3.29" -futures-timer = "3.0.2" -getrandom = { version = "0.2.11", features = ["js"], optional = true } # Explicit dependency to be used in `wasm-bindgen` feature -instant = "0.1.12" +futures = { workspace = true } +futures-timer = "3.0.3" +getrandom = { workspace = true, features = ["js"], optional = true } # Explicit dependency to be used in `wasm-bindgen` feature +web-time = { workspace = true } libp2p-core = { workspace = true } libp2p-identity = { workspace = true } libp2p-swarm-derive = { workspace = true, optional = true } +lru = "0.12.3" multistream-select = { workspace = true } -once_cell = "1.18.0" +once_cell = "1.19.0" rand = "0.8" -smallvec = "1.11.2" -tracing = "0.1.37" -void = "1" -wasm-bindgen-futures = { version = "0.4.38", optional = true } +smallvec = "1.13.2" +tracing = { workspace = true } +wasm-bindgen-futures = { version = "0.4.42", optional = true } [target.'cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")))'.dependencies] async-std = { version = "1.6.2", optional = true } -tokio = { version = "1.34", features = ["rt"], optional = true } +tokio = { workspace = true, features = ["rt"], optional = true } [features] macros = ["dep:libp2p-swarm-derive"] @@ -40,8 +40,8 @@ wasm-bindgen = ["dep:wasm-bindgen-futures", "dep:getrandom"] [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } -either = "1.9.0" -futures = "0.3.29" +either = "1.11.0" +futures = { workspace = true } libp2p-identify = { path = "../protocols/identify" } # Using `path` here because this is a cyclic dev-dependency which otherwise breaks releasing. libp2p-identity = { workspace = true, features = ["ed25519"] } libp2p-kad = { path = "../protocols/kad" } # Using `path` here because this is a cyclic dev-dependency which otherwise breaks releasing. @@ -51,11 +51,11 @@ libp2p-swarm-derive = { path = "../swarm-derive" } # Using `pat libp2p-swarm-test = { path = "../swarm-test" } # Using `path` here because this is a cyclic dev-dependency which otherwise breaks releasing. libp2p-yamux = { path = "../muxers/yamux" } # Using `path` here because this is a cyclic dev-dependency which otherwise breaks releasing. quickcheck = { workspace = true } -void = "1" -once_cell = "1.18.0" -trybuild = "1.0.85" -tokio = { version = "1.34.0", features = ["time", "rt", "macros", "rt-multi-thread"] } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +criterion = { version = "0.5", features = ["async_tokio"] } +once_cell = "1.19.0" +trybuild = "1.0.95" +tokio = { workspace = true, features = ["time", "rt", "macros", "rt-multi-thread"] } +libp2p-test-utils = { workspace = true } [[test]] name = "swarm_derive" @@ -65,8 +65,10 @@ required-features = ["macros"] # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] + +[[bench]] +name = "connection_handler" +harness = false [lints] workspace = true diff --git a/swarm/benches/connection_handler.rs b/swarm/benches/connection_handler.rs new file mode 100644 index 00000000000..a5e47528308 --- /dev/null +++ b/swarm/benches/connection_handler.rs @@ -0,0 +1,361 @@ +use std::{convert::Infallible, sync::atomic::AtomicUsize}; + +use async_std::stream::StreamExt; +use criterion::{criterion_group, criterion_main, Criterion}; +use libp2p_core::{ + transport::MemoryTransport, InboundUpgrade, Multiaddr, OutboundUpgrade, Transport, UpgradeInfo, +}; +use libp2p_identity::PeerId; +use libp2p_swarm::{ConnectionHandler, NetworkBehaviour, StreamProtocol}; +use web_time::Duration; + +macro_rules! gen_behaviour { + ($($name:ident {$($field:ident),*};)*) => {$( + #[derive(libp2p_swarm::NetworkBehaviour, Default)] + #[behaviour(prelude = "libp2p_swarm::derive_prelude")] + struct $name { + $($field: SpinningBehaviour,)* + } + + impl BigBehaviour for $name { + fn behaviours(&mut self) -> &mut [SpinningBehaviour] { + unsafe { + std::slice::from_raw_parts_mut( + self as *mut Self as *mut SpinningBehaviour, + std::mem::size_of::() / std::mem::size_of::(), + ) + } + } + } + )*}; +} + +macro_rules! benchmarks { + ($( + $group:ident::[$( + $beh:ident::bench() + .name($name:ident) + .poll_count($count:expr) + .protocols_per_behaviour($protocols:expr), + )+]; + )*) => { + + $( + $( + fn $name(c: &mut Criterion) { + <$beh>::run_bench(c, $protocols, $count, true); + } + )+ + + criterion_group!($group, $($name),*); + )* + + criterion_main!($($group),*); + }; +} + +// fans go brrr +gen_behaviour! { + SpinningBehaviour5 { a, b, c, d, e }; + SpinningBehaviour10 { a, b, c, d, e, f, g, h, i, j }; + SpinningBehaviour20 { a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u }; +} + +benchmarks! { + singles::[ + SpinningBehaviour::bench().name(b).poll_count(1000).protocols_per_behaviour(10), + SpinningBehaviour::bench().name(c).poll_count(1000).protocols_per_behaviour(100), + SpinningBehaviour::bench().name(d).poll_count(1000).protocols_per_behaviour(1000), + ]; + big_5::[ + SpinningBehaviour5::bench().name(e).poll_count(1000).protocols_per_behaviour(2), + SpinningBehaviour5::bench().name(f).poll_count(1000).protocols_per_behaviour(20), + SpinningBehaviour5::bench().name(g).poll_count(1000).protocols_per_behaviour(200), + ]; + top_10::[ + SpinningBehaviour10::bench().name(h).poll_count(1000).protocols_per_behaviour(1), + SpinningBehaviour10::bench().name(i).poll_count(1000).protocols_per_behaviour(10), + SpinningBehaviour10::bench().name(j).poll_count(1000).protocols_per_behaviour(100), + ]; + lucky_20::[ + SpinningBehaviour20::bench().name(k).poll_count(500).protocols_per_behaviour(1), + SpinningBehaviour20::bench().name(l).poll_count(500).protocols_per_behaviour(10), + SpinningBehaviour20::bench().name(m).poll_count(500).protocols_per_behaviour(100), + ]; +} +// fn main() {} + +trait BigBehaviour: Sized { + fn behaviours(&mut self) -> &mut [SpinningBehaviour]; + + fn for_each_beh(&mut self, f: impl FnMut(&mut SpinningBehaviour)) { + self.behaviours().iter_mut().for_each(f); + } + + fn any_beh(&mut self, f: impl FnMut(&mut SpinningBehaviour) -> bool) -> bool { + self.behaviours().iter_mut().any(f) + } + + fn run_bench( + c: &mut Criterion, + protocols_per_behaviour: usize, + spam_count: usize, + static_protocols: bool, + ) where + Self: Default + NetworkBehaviour, + { + let name = format!( + "{}::bench().poll_count({}).protocols_per_behaviour({})", + std::any::type_name::(), + spam_count, + protocols_per_behaviour + ); + + let init = || { + let mut swarm_a = new_swarm(Self::default()); + let mut swarm_b = new_swarm(Self::default()); + + let behaviour_count = swarm_a.behaviours().len(); + let protocol_count = behaviour_count * protocols_per_behaviour; + let protocols = (0..protocol_count) + .map(|i| { + if static_protocols { + StreamProtocol::new(format!("/protocol/{i}").leak()) + } else { + StreamProtocol::try_from_owned(format!("/protocol/{i}")).unwrap() + } + }) + .collect::>() + .leak(); + + let mut protocol_chunks = protocols.chunks(protocols_per_behaviour); + swarm_a.for_each_beh(|b| b.protocols = protocol_chunks.next().unwrap()); + let mut protocol_chunks = protocols.chunks(protocols_per_behaviour); + swarm_b.for_each_beh(|b| b.protocols = protocol_chunks.next().unwrap()); + + swarm_a.for_each_beh(|b| b.iter_count = spam_count); + swarm_b.for_each_beh(|b| b.iter_count = 0); + + swarm_a.for_each_beh(|b| b.other_peer = Some(*swarm_b.local_peer_id())); + swarm_b.for_each_beh(|b| b.other_peer = Some(*swarm_a.local_peer_id())); + + static OFFSET: AtomicUsize = AtomicUsize::new(8000); + let offset = OFFSET.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + + swarm_a + .listen_on(format!("/memory/{offset}").parse().unwrap()) + .unwrap(); + swarm_b + .dial(format!("/memory/{offset}").parse::().unwrap()) + .unwrap(); + + (swarm_a, swarm_b) + }; + + c.bench_function(&name, |b| { + b.to_async(tokio::runtime::Builder::new_multi_thread().build().unwrap()) + .iter_batched( + init, + |(mut swarm_a, mut swarm_b)| async move { + while swarm_a.any_beh(|b| !b.finished) || swarm_b.any_beh(|b| !b.finished) { + futures::future::select(swarm_b.next(), swarm_a.next()).await; + } + }, + criterion::BatchSize::LargeInput, + ); + }); + } +} + +impl BigBehaviour for libp2p_swarm::Swarm { + fn behaviours(&mut self) -> &mut [SpinningBehaviour] { + self.behaviour_mut().behaviours() + } +} + +fn new_swarm(beh: T) -> libp2p_swarm::Swarm { + let keypair = libp2p_identity::Keypair::generate_ed25519(); + libp2p_swarm::Swarm::new( + MemoryTransport::default() + .upgrade(multistream_select::Version::V1) + .authenticate(libp2p_plaintext::Config::new(&keypair)) + .multiplex(libp2p_yamux::Config::default()) + .boxed(), + beh, + keypair.public().to_peer_id(), + libp2p_swarm::Config::without_executor().with_idle_connection_timeout(Duration::MAX), + ) +} + +/// Whole purpose of the behaviour is to rapidly call `poll` on the handler +/// configured amount of times and then emit event when finished. +#[derive(Default)] +struct SpinningBehaviour { + iter_count: usize, + protocols: &'static [StreamProtocol], + finished: bool, + emitted: bool, + other_peer: Option, +} + +#[derive(Debug)] +struct FinishedSpinning; + +impl NetworkBehaviour for SpinningBehaviour { + type ConnectionHandler = SpinningHandler; + type ToSwarm = FinishedSpinning; + + fn handle_established_inbound_connection( + &mut self, + _connection_id: libp2p_swarm::ConnectionId, + _peer: libp2p_identity::PeerId, + _local_addr: &libp2p_core::Multiaddr, + _remote_addr: &libp2p_core::Multiaddr, + ) -> Result, libp2p_swarm::ConnectionDenied> { + Ok(SpinningHandler { + iter_count: 0, + protocols: self.protocols, + }) + } + + fn handle_established_outbound_connection( + &mut self, + _connection_id: libp2p_swarm::ConnectionId, + _peer: libp2p_identity::PeerId, + _addr: &libp2p_core::Multiaddr, + _role_override: libp2p_core::Endpoint, + _port_use: libp2p_core::transport::PortUse, + ) -> Result, libp2p_swarm::ConnectionDenied> { + Ok(SpinningHandler { + iter_count: self.iter_count, + protocols: self.protocols, + }) + } + + fn on_swarm_event(&mut self, _: libp2p_swarm::FromSwarm) {} + + fn on_connection_handler_event( + &mut self, + _peer_id: libp2p_identity::PeerId, + _connection_id: libp2p_swarm::ConnectionId, + _event: libp2p_swarm::THandlerOutEvent, + ) { + self.finished = true; + } + + fn poll( + &mut self, + _: &mut std::task::Context<'_>, + ) -> std::task::Poll>> + { + if self.finished && !self.emitted { + self.emitted = true; + std::task::Poll::Ready(libp2p_swarm::ToSwarm::GenerateEvent(FinishedSpinning)) + } else { + std::task::Poll::Pending + } + } +} + +impl BigBehaviour for SpinningBehaviour { + fn behaviours(&mut self) -> &mut [SpinningBehaviour] { + std::slice::from_mut(self) + } +} + +struct SpinningHandler { + iter_count: usize, + protocols: &'static [StreamProtocol], +} + +impl ConnectionHandler for SpinningHandler { + type FromBehaviour = Infallible; + + type ToBehaviour = FinishedSpinning; + + type InboundProtocol = Upgrade; + + type OutboundProtocol = Upgrade; + + type InboundOpenInfo = (); + + type OutboundOpenInfo = (); + + fn listen_protocol( + &self, + ) -> libp2p_swarm::SubstreamProtocol { + libp2p_swarm::SubstreamProtocol::new(Upgrade(self.protocols), ()) + } + + fn poll( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll< + libp2p_swarm::ConnectionHandlerEvent< + Self::OutboundProtocol, + Self::OutboundOpenInfo, + Self::ToBehaviour, + >, + > { + if self.iter_count == usize::MAX { + return std::task::Poll::Pending; + } + + if self.iter_count != 0 { + self.iter_count -= 1; + cx.waker().wake_by_ref(); + return std::task::Poll::Pending; + } + + self.iter_count = usize::MAX; + std::task::Poll::Ready(libp2p_swarm::ConnectionHandlerEvent::NotifyBehaviour( + FinishedSpinning, + )) + } + + fn on_behaviour_event(&mut self, event: Self::FromBehaviour) { + match event {} + } + + fn on_connection_event( + &mut self, + _event: libp2p_swarm::handler::ConnectionEvent< + Self::InboundProtocol, + Self::OutboundProtocol, + Self::InboundOpenInfo, + Self::OutboundOpenInfo, + >, + ) { + } +} + +pub struct Upgrade(&'static [StreamProtocol]); + +impl UpgradeInfo for Upgrade { + type Info = &'static StreamProtocol; + type InfoIter = std::slice::Iter<'static, StreamProtocol>; + + fn protocol_info(&self) -> Self::InfoIter { + self.0.iter() + } +} + +impl OutboundUpgrade for Upgrade { + type Output = libp2p_swarm::Stream; + type Error = Infallible; + type Future = futures::future::Ready>; + + fn upgrade_outbound(self, s: libp2p_swarm::Stream, _: Self::Info) -> Self::Future { + futures::future::ready(Ok(s)) + } +} + +impl InboundUpgrade for Upgrade { + type Output = libp2p_swarm::Stream; + type Error = Infallible; + type Future = futures::future::Ready>; + + fn upgrade_inbound(self, s: libp2p_swarm::Stream, _: Self::Info) -> Self::Future { + futures::future::ready(Ok(s)) + } +} diff --git a/swarm/src/behaviour.rs b/swarm/src/behaviour.rs index c25b14e75e3..8c8c5998f67 100644 --- a/swarm/src/behaviour.rs +++ b/swarm/src/behaviour.rs @@ -21,21 +21,25 @@ mod either; mod external_addresses; mod listen_addresses; +mod peer_addresses; pub mod toggle; +use std::task::{Context, Poll}; + pub use external_addresses::ExternalAddresses; +use libp2p_core::{ + transport::{ListenerId, PortUse}, + ConnectedPoint, Endpoint, Multiaddr, +}; +use libp2p_identity::PeerId; pub use listen_addresses::ListenAddresses; +pub use peer_addresses::PeerAddresses; -use crate::connection::ConnectionId; -use crate::dial_opts::DialOpts; -use crate::listen_opts::ListenOpts; use crate::{ - ConnectionDenied, ConnectionHandler, DialError, ListenError, THandler, THandlerInEvent, + connection::ConnectionId, dial_opts::DialOpts, listen_opts::ListenOpts, ConnectionDenied, + ConnectionError, ConnectionHandler, DialError, ListenError, THandler, THandlerInEvent, THandlerOutEvent, }; -use libp2p_core::{transport::ListenerId, ConnectedPoint, Endpoint, Multiaddr}; -use libp2p_identity::PeerId; -use std::{task::Context, task::Poll}; /// A [`NetworkBehaviour`] defines the behaviour of the local node on the network. /// @@ -96,25 +100,25 @@ use std::{task::Context, task::Poll}; /// #[behaviour(to_swarm = "Event")] /// # #[behaviour(prelude = "libp2p_swarm::derive_prelude")] /// struct MyBehaviour { -/// identify: identify::Behaviour, -/// ping: ping::Behaviour, +/// identify: identify::Behaviour, +/// ping: ping::Behaviour, /// } /// /// enum Event { -/// Identify(identify::Event), -/// Ping(ping::Event), +/// Identify(identify::Event), +/// Ping(ping::Event), /// } /// /// impl From for Event { -/// fn from(event: identify::Event) -> Self { -/// Self::Identify(event) -/// } +/// fn from(event: identify::Event) -> Self { +/// Self::Identify(event) +/// } /// } /// /// impl From for Event { -/// fn from(event: ping::Event) -> Self { -/// Self::Ping(event) -/// } +/// fn from(event: ping::Event) -> Self { +/// Self::Ping(event) +/// } /// } /// ``` pub trait NetworkBehaviour: 'static { @@ -126,8 +130,8 @@ pub trait NetworkBehaviour: 'static { /// Callback that is invoked for every new inbound connection. /// - /// At this point in the connection lifecycle, only the remote's and our local address are known. - /// We have also already allocated a [`ConnectionId`]. + /// At this point in the connection lifecycle, only the remote's and our local address are + /// known. We have also already allocated a [`ConnectionId`]. /// /// Any error returned from this function will immediately abort the dial attempt. fn handle_pending_inbound_connection( @@ -143,9 +147,13 @@ pub trait NetworkBehaviour: 'static { /// /// This is invoked once another peer has successfully dialed us. /// - /// At this point, we have verified their [`PeerId`] and we know, which particular [`Multiaddr`] succeeded in the dial. - /// In order to actually use this connection, this function must return a [`ConnectionHandler`]. - /// Returning an error will immediately close the connection. + /// At this point, we have verified their [`PeerId`] and we know, which particular [`Multiaddr`] + /// succeeded in the dial. In order to actually use this connection, this function must + /// return a [`ConnectionHandler`]. Returning an error will immediately close the + /// connection. + /// + /// Note when any composed behaviour returns an error the connection will be closed and a + /// [`FromSwarm::ListenFailure`] event will be emitted. fn handle_established_inbound_connection( &mut self, _connection_id: ConnectionId, @@ -160,10 +168,14 @@ pub trait NetworkBehaviour: 'static { /// /// - The [`PeerId`], if known. Remember that we can dial without a [`PeerId`]. /// - All addresses passed to [`DialOpts`] are passed in here too. - /// - The effective [`Role`](Endpoint) of this peer in the dial attempt. Typically, this is set to [`Endpoint::Dialer`] except if we are attempting a hole-punch. - /// - The [`ConnectionId`] identifying the future connection resulting from this dial, if successful. + /// - The effective [`Role`](Endpoint) of this peer in the dial attempt. Typically, this is set + /// to [`Endpoint::Dialer`] except if we are attempting a hole-punch. + /// - The [`ConnectionId`] identifying the future connection resulting from this dial, if + /// successful. /// - /// Note that the addresses returned from this function are only used for dialing if [`WithPeerIdWithAddresses::extend_addresses_through_behaviour`](crate::dial_opts::WithPeerIdWithAddresses::extend_addresses_through_behaviour) is set. + /// Note that the addresses returned from this function are only used for dialing if + /// [`WithPeerIdWithAddresses::extend_addresses_through_behaviour`](crate::dial_opts::WithPeerIdWithAddresses::extend_addresses_through_behaviour) + /// is set. /// /// Any error returned from this function will immediately abort the dial attempt. fn handle_pending_outbound_connection( @@ -179,15 +191,20 @@ pub trait NetworkBehaviour: 'static { /// Callback that is invoked for every established outbound connection. /// /// This is invoked once we have successfully dialed a peer. - /// At this point, we have verified their [`PeerId`] and we know, which particular [`Multiaddr`] succeeded in the dial. - /// In order to actually use this connection, this function must return a [`ConnectionHandler`]. - /// Returning an error will immediately close the connection. + /// At this point, we have verified their [`PeerId`] and we know, which particular [`Multiaddr`] + /// succeeded in the dial. In order to actually use this connection, this function must + /// return a [`ConnectionHandler`]. Returning an error will immediately close the + /// connection. + /// + /// Note when any composed behaviour returns an error the connection will be closed and a + /// [`FromSwarm::DialFailure`] event will be emitted. fn handle_established_outbound_connection( &mut self, _connection_id: ConnectionId, peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied>; /// Informs the behaviour about an event from the [`Swarm`](crate::Swarm). @@ -228,8 +245,9 @@ pub enum ToSwarm { /// On failure, [`NetworkBehaviour::on_swarm_event`] with `DialFailure` is invoked. /// /// [`DialOpts`] provides access to the [`ConnectionId`] via [`DialOpts::connection_id`]. - /// This [`ConnectionId`] will be used throughout the connection's lifecycle to associate events with it. - /// This allows a [`NetworkBehaviour`] to identify a connection that resulted out of its own dial request. + /// This [`ConnectionId`] will be used throughout the connection's lifecycle to associate + /// events with it. This allows a [`NetworkBehaviour`] to identify a connection that + /// resulted out of its own dial request. Dial { opts: DialOpts }, /// Instructs the [`Swarm`](crate::Swarm) to listen on the provided address. @@ -241,8 +259,8 @@ pub enum ToSwarm { /// Instructs the `Swarm` to send an event to the handler dedicated to a /// connection with a peer. /// - /// If the `Swarm` is connected to the peer, the message is delivered to the [`ConnectionHandler`] - /// instance identified by the peer ID and connection ID. + /// If the `Swarm` is connected to the peer, the message is delivered to the + /// [`ConnectionHandler`] instance identified by the peer ID and connection ID. /// /// If the specified connection no longer exists, the event is silently dropped. /// @@ -266,11 +284,12 @@ pub enum ToSwarm { /// /// The emphasis on a **new** candidate is important. /// Protocols MUST take care to only emit a candidate once per "source". - /// For example, the observed address of a TCP connection does not change throughout its lifetime. - /// Thus, only one candidate should be emitted per connection. + /// For example, the observed address of a TCP connection does not change throughout its + /// lifetime. Thus, only one candidate should be emitted per connection. /// - /// This makes the report frequency of an address a meaningful data-point for consumers of this event. - /// This address will be shared with all [`NetworkBehaviour`]s via [`FromSwarm::NewExternalAddrCandidate`]. + /// This makes the report frequency of an address a meaningful data-point for consumers of this + /// event. This address will be shared with all [`NetworkBehaviour`]s via + /// [`FromSwarm::NewExternalAddrCandidate`]. /// /// This address could come from a variety of sources: /// - A protocol such as identify obtained it from a remote. @@ -278,31 +297,42 @@ pub enum ToSwarm { /// - We made an educated guess based on one of our listen addresses. NewExternalAddrCandidate(Multiaddr), - /// Indicates to the [`Swarm`](crate::Swarm) that the provided address is confirmed to be externally reachable. + /// Indicates to the [`Swarm`](crate::Swarm) that the provided address is confirmed to be + /// externally reachable. /// - /// This is intended to be issued in response to a [`FromSwarm::NewExternalAddrCandidate`] if we are indeed externally reachable on this address. - /// This address will be shared with all [`NetworkBehaviour`]s via [`FromSwarm::ExternalAddrConfirmed`]. + /// This is intended to be issued in response to a [`FromSwarm::NewExternalAddrCandidate`] if + /// we are indeed externally reachable on this address. This address will be shared with + /// all [`NetworkBehaviour`]s via [`FromSwarm::ExternalAddrConfirmed`]. ExternalAddrConfirmed(Multiaddr), - /// Indicates to the [`Swarm`](crate::Swarm) that we are no longer externally reachable under the provided address. + /// Indicates to the [`Swarm`](crate::Swarm) that we are no longer externally reachable under + /// the provided address. /// /// This expires an address that was earlier confirmed via [`ToSwarm::ExternalAddrConfirmed`]. - /// This address will be shared with all [`NetworkBehaviour`]s via [`FromSwarm::ExternalAddrExpired`]. + /// This address will be shared with all [`NetworkBehaviour`]s via + /// [`FromSwarm::ExternalAddrExpired`]. ExternalAddrExpired(Multiaddr), - /// Instructs the `Swarm` to initiate a graceful close of one or all connections with the given peer. + /// Instructs the `Swarm` to initiate a graceful close of one or all connections with the given + /// peer. /// - /// Closing a connection via [`ToSwarm::CloseConnection`] will poll [`ConnectionHandler::poll_close`] to completion. - /// In most cases, stopping to "use" a connection is enough to have it closed. - /// The keep-alive algorithm will close a connection automatically once all [`ConnectionHandler`]s are idle. + /// Closing a connection via [`ToSwarm::CloseConnection`] will poll + /// [`ConnectionHandler::poll_close`] to completion. In most cases, stopping to "use" a + /// connection is enough to have it closed. The keep-alive algorithm will close a + /// connection automatically once all [`ConnectionHandler`]s are idle. /// - /// Use this command if you want to close a connection _despite_ it still being in use by one or more handlers. + /// Use this command if you want to close a connection _despite_ it still being in use by one + /// or more handlers. CloseConnection { /// The peer to disconnect. peer_id: PeerId, /// Whether to close a specific or all connections to the given peer. connection: CloseConnection, }, + + /// Reports external address of a remote peer to the [`Swarm`](crate::Swarm) and through that + /// to other [`NetworkBehaviour`]s. + NewExternalAddrOfPeer { peer_id: PeerId, address: Multiaddr }, } impl ToSwarm { @@ -335,6 +365,13 @@ impl ToSwarm { ToSwarm::NewExternalAddrCandidate(addr) => ToSwarm::NewExternalAddrCandidate(addr), ToSwarm::ExternalAddrConfirmed(addr) => ToSwarm::ExternalAddrConfirmed(addr), ToSwarm::ExternalAddrExpired(addr) => ToSwarm::ExternalAddrExpired(addr), + ToSwarm::NewExternalAddrOfPeer { + address: addr, + peer_id, + } => ToSwarm::NewExternalAddrOfPeer { + address: addr, + peer_id, + }, } } } @@ -366,6 +403,13 @@ impl ToSwarm { peer_id, connection, }, + ToSwarm::NewExternalAddrOfPeer { + address: addr, + peer_id, + } => ToSwarm::NewExternalAddrOfPeer { + address: addr, + peer_id, + }, } } } @@ -391,7 +435,7 @@ pub enum CloseConnection { /// Enumeration with the list of the possible events /// to pass to [`on_swarm_event`](NetworkBehaviour::on_swarm_event). -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] #[non_exhaustive] pub enum FromSwarm<'a> { /// Informs the behaviour about a newly established connection to a peer. @@ -411,8 +455,8 @@ pub enum FromSwarm<'a> { /// Informs the behaviour that an error /// happened on an incoming connection during its initial handshake. /// - /// This can include, for example, an error during the handshake of the encryption layer, or the - /// connection unexpectedly closed. + /// This can include, for example, an error during the handshake of the encryption layer, or + /// the connection unexpectedly closed. ListenFailure(ListenFailure<'a>), /// Informs the behaviour that a new listener was created. NewListener(NewListener), @@ -426,12 +470,16 @@ pub enum FromSwarm<'a> { ListenerError(ListenerError<'a>), /// Informs the behaviour that a listener closed. ListenerClosed(ListenerClosed<'a>), - /// Informs the behaviour that we have discovered a new candidate for an external address for us. + /// Informs the behaviour that we have discovered a new candidate for an external address for + /// us. NewExternalAddrCandidate(NewExternalAddrCandidate<'a>), /// Informs the behaviour that an external address of the local node was confirmed. ExternalAddrConfirmed(ExternalAddrConfirmed<'a>), - /// Informs the behaviour that an external address of the local node expired, i.e. is no-longer confirmed. + /// Informs the behaviour that an external address of the local node expired, i.e. is no-longer + /// confirmed. ExternalAddrExpired(ExternalAddrExpired<'a>), + /// Informs the behaviour that we have discovered a new external address for a remote peer. + NewExternalAddrOfPeer(NewExternalAddrOfPeer<'a>), } /// [`FromSwarm`] variant that informs the behaviour about a newly established connection to a peer. @@ -449,11 +497,12 @@ pub struct ConnectionEstablished<'a> { /// This event is always paired with an earlier /// [`FromSwarm::ConnectionEstablished`] with the same peer ID, connection ID /// and endpoint. -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct ConnectionClosed<'a> { pub peer_id: PeerId, pub connection_id: ConnectionId, pub endpoint: &'a ConnectedPoint, + pub cause: Option<&'a ConnectionError>, pub remaining_established: usize, } @@ -487,6 +536,7 @@ pub struct ListenFailure<'a> { pub send_back_addr: &'a Multiaddr, pub error: &'a ListenError, pub connection_id: ConnectionId, + pub peer_id: Option, } /// [`FromSwarm`] variant that informs the behaviour that a new listener was created. @@ -526,7 +576,8 @@ pub struct ListenerClosed<'a> { pub reason: Result<(), &'a std::io::Error>, } -/// [`FromSwarm`] variant that informs the behaviour about a new candidate for an external address for us. +/// [`FromSwarm`] variant that informs the behaviour about a new candidate for an external address +/// for us. #[derive(Debug, Clone, Copy)] pub struct NewExternalAddrCandidate<'a> { pub addr: &'a Multiaddr, @@ -543,3 +594,11 @@ pub struct ExternalAddrConfirmed<'a> { pub struct ExternalAddrExpired<'a> { pub addr: &'a Multiaddr, } + +/// [`FromSwarm`] variant that informs the behaviour that a new external address for a remote peer +/// was detected. +#[derive(Clone, Copy, Debug)] +pub struct NewExternalAddrOfPeer<'a> { + pub peer_id: PeerId, + pub addr: &'a Multiaddr, +} diff --git a/swarm/src/behaviour/either.rs b/swarm/src/behaviour/either.rs index 25da83fa11f..b9a86e1b9d8 100644 --- a/swarm/src/behaviour/either.rs +++ b/swarm/src/behaviour/either.rs @@ -18,13 +18,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::behaviour::{self, NetworkBehaviour, ToSwarm}; -use crate::connection::ConnectionId; -use crate::{ConnectionDenied, THandler, THandlerInEvent, THandlerOutEvent}; +use std::task::{Context, Poll}; + use either::Either; -use libp2p_core::{Endpoint, Multiaddr}; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; use libp2p_identity::PeerId; -use std::{task::Context, task::Poll}; + +use crate::{ + behaviour::{self, NetworkBehaviour, ToSwarm}, + connection::ConnectionId, + ConnectionDenied, THandler, THandlerInEvent, THandlerOutEvent, +}; /// Implementation of [`NetworkBehaviour`] that can be either of two implementations. impl NetworkBehaviour for Either @@ -103,6 +107,7 @@ where peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { let handler = match self { Either::Left(inner) => Either::Left(inner.handle_established_outbound_connection( @@ -110,12 +115,14 @@ where peer, addr, role_override, + port_use, )?), Either::Right(inner) => Either::Right(inner.handle_established_outbound_connection( connection_id, peer, addr, role_override, + port_use, )?), }; diff --git a/swarm/src/behaviour/external_addresses.rs b/swarm/src/behaviour/external_addresses.rs index 579f46fe486..ba2dd3eb890 100644 --- a/swarm/src/behaviour/external_addresses.rs +++ b/swarm/src/behaviour/external_addresses.rs @@ -1,6 +1,7 @@ -use crate::behaviour::{ExternalAddrConfirmed, ExternalAddrExpired, FromSwarm}; use libp2p_core::Multiaddr; +use crate::behaviour::{ExternalAddrConfirmed, ExternalAddrExpired, FromSwarm}; + /// The maximum number of local external addresses. When reached any /// further externally reported addresses are ignored. The behaviour always /// tracks all its listen addresses. @@ -78,17 +79,20 @@ impl ExternalAddresses { } fn push_front(&mut self, addr: &Multiaddr) { - self.addresses.insert(0, addr.clone()); // We have at most `MAX_LOCAL_EXTERNAL_ADDRS` so this isn't very expensive. + // We have at most `MAX_LOCAL_EXTERNAL_ADDRS` so + // this isn't very expensive. + self.addresses.insert(0, addr.clone()); } } #[cfg(test)] mod tests { - use super::*; use libp2p_core::multiaddr::Protocol; use once_cell::sync::Lazy; use rand::Rng; + use super::*; + #[test] fn new_external_addr_returns_correct_changed_value() { let mut addresses = ExternalAddresses::default(); diff --git a/swarm/src/behaviour/listen_addresses.rs b/swarm/src/behaviour/listen_addresses.rs index 6076f5e7923..0c685d798c7 100644 --- a/swarm/src/behaviour/listen_addresses.rs +++ b/swarm/src/behaviour/listen_addresses.rs @@ -1,7 +1,9 @@ -use crate::behaviour::{ExpiredListenAddr, FromSwarm, NewListenAddr}; -use libp2p_core::Multiaddr; use std::collections::HashSet; +use libp2p_core::Multiaddr; + +use crate::behaviour::{ExpiredListenAddr, FromSwarm, NewListenAddr}; + /// Utility struct for tracking the addresses a [`Swarm`](crate::Swarm) is listening on. #[derive(Debug, Default, Clone)] pub struct ListenAddresses { @@ -32,10 +34,11 @@ impl ListenAddresses { #[cfg(test)] mod tests { - use super::*; use libp2p_core::{multiaddr::Protocol, transport::ListenerId}; use once_cell::sync::Lazy; + use super::*; + #[test] fn new_listen_addr_returns_correct_changed_value() { let mut addresses = ListenAddresses::default(); diff --git a/swarm/src/behaviour/peer_addresses.rs b/swarm/src/behaviour/peer_addresses.rs new file mode 100644 index 00000000000..5aeae7741d5 --- /dev/null +++ b/swarm/src/behaviour/peer_addresses.rs @@ -0,0 +1,335 @@ +use std::num::NonZeroUsize; + +use libp2p_core::Multiaddr; +use libp2p_identity::PeerId; +use lru::LruCache; + +use crate::{behaviour::FromSwarm, DialError, DialFailure, NewExternalAddrOfPeer}; + +/// Struct for tracking peers' external addresses of the [`Swarm`](crate::Swarm). +#[derive(Debug)] +pub struct PeerAddresses(LruCache>); + +impl PeerAddresses { + /// Creates a [`PeerAddresses`] cache with capacity for the given number of peers. + /// + /// For each peer, we will at most store 10 addresses. + pub fn new(number_of_peers: NonZeroUsize) -> Self { + Self(LruCache::new(number_of_peers)) + } + + /// Feed a [`FromSwarm`] event to this struct. + /// + /// Returns whether the event changed peer's known external addresses. + pub fn on_swarm_event(&mut self, event: &FromSwarm) -> bool { + match event { + FromSwarm::NewExternalAddrOfPeer(NewExternalAddrOfPeer { peer_id, addr }) => { + self.add(*peer_id, (*addr).clone()) + } + FromSwarm::DialFailure(DialFailure { + peer_id: Some(peer_id), + error: DialError::Transport(errors), + .. + }) => { + for (addr, _error) in errors { + self.remove(peer_id, addr); + } + true + } + _ => false, + } + } + + /// Adds address to cache. + /// Appends address to the existing set if peer addresses already exist. + /// Creates a new cache entry for peer_id if no addresses are present. + /// Returns true if the newly added address was not previously in the cache. + pub fn add(&mut self, peer: PeerId, address: Multiaddr) -> bool { + match prepare_addr(&peer, &address) { + Ok(address) => { + if let Some(cached) = self.0.get_mut(&peer) { + cached.put(address, ()).is_none() + } else { + let mut set = LruCache::new(NonZeroUsize::new(10).expect("10 > 0")); + set.put(address, ()); + self.0.put(peer, set); + + true + } + } + Err(_) => false, + } + } + + /// Returns peer's external addresses. + pub fn get(&mut self, peer: &PeerId) -> impl Iterator + '_ { + self.0 + .get(peer) + .into_iter() + .flat_map(|c| c.iter().map(|(m, ())| m)) + .cloned() + } + + /// Removes address from peer addresses cache. + /// Returns true if the address was removed. + pub fn remove(&mut self, peer: &PeerId, address: &Multiaddr) -> bool { + match self.0.get_mut(peer) { + Some(addrs) => match prepare_addr(peer, address) { + Ok(address) => addrs.pop(&address).is_some(), + Err(_) => false, + }, + None => false, + } + } +} + +fn prepare_addr(peer: &PeerId, addr: &Multiaddr) -> Result { + addr.clone().with_p2p(*peer) +} + +impl Default for PeerAddresses { + fn default() -> Self { + Self(LruCache::new(NonZeroUsize::new(100).unwrap())) + } +} + +#[cfg(test)] +mod tests { + use std::io; + + use libp2p_core::{ + multiaddr::Protocol, + transport::{memory::MemoryTransportError, TransportError}, + }; + use once_cell::sync::Lazy; + + use super::*; + use crate::ConnectionId; + + #[test] + fn new_peer_addr_returns_correct_changed_value() { + let mut cache = PeerAddresses::default(); + let peer_id = PeerId::random(); + + let event = new_external_addr_of_peer1(peer_id); + + let changed = cache.on_swarm_event(&event); + assert!(changed); + + let changed = cache.on_swarm_event(&event); + assert!(!changed); + } + + #[test] + fn new_peer_addr_saves_peer_addrs() { + let mut cache = PeerAddresses::default(); + let peer_id = PeerId::random(); + let event = new_external_addr_of_peer1(peer_id); + + let changed = cache.on_swarm_event(&event); + assert!(changed); + + let addr1 = MEMORY_ADDR_1000.clone().with_p2p(peer_id).unwrap(); + let expected = cache.get(&peer_id).collect::>(); + assert_eq!(expected, vec![addr1]); + + let event = new_external_addr_of_peer2(peer_id); + let changed = cache.on_swarm_event(&event); + + let addr1 = MEMORY_ADDR_1000.clone().with_p2p(peer_id).unwrap(); + let addr2 = MEMORY_ADDR_2000.clone().with_p2p(peer_id).unwrap(); + + let expected_addrs = cache.get(&peer_id).collect::>(); + assert!(expected_addrs.contains(&addr1)); + assert!(expected_addrs.contains(&addr2)); + + let expected = cache.get(&peer_id).collect::>().len(); + assert_eq!(expected, 2); + + assert!(changed); + } + + #[test] + fn existing_addr_is_not_added_to_cache() { + let mut cache = PeerAddresses::default(); + let peer_id = PeerId::random(); + + let event = new_external_addr_of_peer1(peer_id); + + let addr1 = MEMORY_ADDR_1000.clone().with_p2p(peer_id).unwrap(); + let changed = cache.on_swarm_event(&event); + let expected = cache.get(&peer_id).collect::>(); + assert!(changed); + assert_eq!(expected, vec![addr1]); + + let addr1 = MEMORY_ADDR_1000.clone().with_p2p(peer_id).unwrap(); + let changed = cache.on_swarm_event(&event); + let expected = cache.get(&peer_id).collect::>(); + assert!(!changed); + assert_eq!(expected, [addr1]); + } + + #[test] + fn addresses_of_peer_are_removed_when_received_dial_failure() { + let mut cache = PeerAddresses::default(); + let peer_id = PeerId::random(); + + let addr: Multiaddr = "/ip4/127.0.0.1/tcp/8080".parse().unwrap(); + let addr2: Multiaddr = "/ip4/127.0.0.1/tcp/8081".parse().unwrap(); + let addr3: Multiaddr = "/ip4/127.0.0.1/tcp/8082".parse().unwrap(); + + cache.add(peer_id, addr.clone()); + cache.add(peer_id, addr2.clone()); + cache.add(peer_id, addr3.clone()); + + let error = DialError::Transport(prepare_errors(vec![addr, addr3])); + + let event = FromSwarm::DialFailure(DialFailure { + peer_id: Some(peer_id), + error: &error, + connection_id: ConnectionId::new_unchecked(8), + }); + + let changed = cache.on_swarm_event(&event); + + assert!(changed); + + let cached = cache.get(&peer_id).collect::>(); + let expected = prepare_expected_addrs(peer_id, [addr2].into_iter()); + + assert_eq!(cached, expected); + } + + #[test] + fn remove_removes_address_if_present() { + let mut cache = PeerAddresses::default(); + let peer_id = PeerId::random(); + let addr: Multiaddr = "/ip4/127.0.0.1/tcp/8080".parse().unwrap(); + + cache.add(peer_id, addr.clone()); + + assert!(cache.remove(&peer_id, &addr)); + } + + #[test] + fn remove_returns_false_if_address_not_present() { + let mut cache = PeerAddresses::default(); + let peer_id = PeerId::random(); + let addr: Multiaddr = "/ip4/127.0.0.1/tcp/8080".parse().unwrap(); + + assert!(!cache.remove(&peer_id, &addr)); + } + + #[test] + fn remove_returns_false_if_peer_not_present() { + let mut cache = PeerAddresses::default(); + let peer_id = PeerId::random(); + let addr: Multiaddr = "/ip4/127.0.0.1/tcp/8080".parse().unwrap(); + + assert!(!cache.remove(&peer_id, &addr)); + } + + #[test] + fn remove_removes_address_provided_in_param() { + let mut cache = PeerAddresses::default(); + let peer_id = PeerId::random(); + let addr: Multiaddr = "/ip4/127.0.0.1/tcp/8080".parse().unwrap(); + let addr2: Multiaddr = "/ip4/127.0.0.1/tcp/8081".parse().unwrap(); + let addr3: Multiaddr = "/ip4/127.0.0.1/tcp/8082".parse().unwrap(); + + cache.add(peer_id, addr.clone()); + cache.add(peer_id, addr2.clone()); + cache.add(peer_id, addr3.clone()); + + assert!(cache.remove(&peer_id, &addr2)); + + let mut cached = cache.get(&peer_id).collect::>(); + cached.sort(); + + let expected = prepare_expected_addrs(peer_id, [addr, addr3].into_iter()); + + assert_eq!(cached, expected); + } + + #[test] + fn add_adds_new_address_to_cache() { + let mut cache = PeerAddresses::default(); + let peer_id = PeerId::random(); + let addr: Multiaddr = "/ip4/127.0.0.1/tcp/8080".parse().unwrap(); + + assert!(cache.add(peer_id, addr.clone())); + + let mut cached = cache.get(&peer_id).collect::>(); + cached.sort(); + let expected = prepare_expected_addrs(peer_id, [addr].into_iter()); + + assert_eq!(cached, expected); + } + + #[test] + fn add_adds_address_to_cache_to_existing_key() { + let mut cache = PeerAddresses::default(); + let peer_id = PeerId::random(); + let addr: Multiaddr = "/ip4/127.0.0.1/tcp/8080".parse().unwrap(); + let addr2: Multiaddr = "/ip4/127.0.0.1/tcp/8081".parse().unwrap(); + let addr3: Multiaddr = "/ip4/127.0.0.1/tcp/8082".parse().unwrap(); + + assert!(cache.add(peer_id, addr.clone())); + + cache.add(peer_id, addr2.clone()); + cache.add(peer_id, addr3.clone()); + + let expected = prepare_expected_addrs(peer_id, [addr, addr2, addr3].into_iter()); + + let mut cached = cache.get(&peer_id).collect::>(); + cached.sort(); + + assert_eq!(cached, expected); + } + + fn prepare_expected_addrs( + peer_id: PeerId, + addrs: impl Iterator, + ) -> Vec { + let mut addrs = addrs + .filter_map(|a| a.with_p2p(peer_id).ok()) + .collect::>(); + addrs.sort(); + addrs + } + + fn new_external_addr_of_peer1(peer_id: PeerId) -> FromSwarm<'static> { + FromSwarm::NewExternalAddrOfPeer(NewExternalAddrOfPeer { + peer_id, + addr: &MEMORY_ADDR_1000, + }) + } + + fn new_external_addr_of_peer2(peer_id: PeerId) -> FromSwarm<'static> { + FromSwarm::NewExternalAddrOfPeer(NewExternalAddrOfPeer { + peer_id, + addr: &MEMORY_ADDR_2000, + }) + } + + fn prepare_errors(addrs: Vec) -> Vec<(Multiaddr, TransportError)> { + let errors: Vec<(Multiaddr, TransportError)> = addrs + .iter() + .map(|addr| { + ( + addr.clone(), + TransportError::Other(io::Error::new( + io::ErrorKind::Other, + MemoryTransportError::Unreachable, + )), + ) + }) + .collect(); + errors + } + + static MEMORY_ADDR_1000: Lazy = + Lazy::new(|| Multiaddr::empty().with(Protocol::Memory(1000))); + static MEMORY_ADDR_2000: Lazy = + Lazy::new(|| Multiaddr::empty().with(Protocol::Memory(2000))); +} diff --git a/swarm/src/behaviour/toggle.rs b/swarm/src/behaviour/toggle.rs index e81c5343701..e70e6cf9896 100644 --- a/swarm/src/behaviour/toggle.rs +++ b/swarm/src/behaviour/toggle.rs @@ -18,21 +18,24 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::behaviour::FromSwarm; -use crate::connection::ConnectionId; -use crate::handler::{ - AddressChange, ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, DialUpgradeError, - FullyNegotiatedInbound, FullyNegotiatedOutbound, ListenUpgradeError, SubstreamProtocol, -}; -use crate::upgrade::SendWrapper; -use crate::{ - ConnectionDenied, NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, -}; +use std::task::{Context, Poll}; + use either::Either; use futures::future; -use libp2p_core::{upgrade::DeniedUpgrade, Endpoint, Multiaddr}; +use libp2p_core::{transport::PortUse, upgrade::DeniedUpgrade, Endpoint, Multiaddr}; use libp2p_identity::PeerId; -use std::{task::Context, task::Poll}; + +use crate::{ + behaviour::FromSwarm, + connection::ConnectionId, + handler::{ + AddressChange, ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, + DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, ListenUpgradeError, + SubstreamProtocol, + }, + upgrade::SendWrapper, + ConnectionDenied, NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, +}; /// Implementation of `NetworkBehaviour` that can be either in the disabled or enabled state. /// @@ -139,6 +142,7 @@ where peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { let inner = match self.inner.as_mut() { None => return Ok(ToggleConnectionHandler { inner: None }), @@ -150,6 +154,7 @@ where peer, addr, role_override, + port_use, )?; Ok(ToggleConnectionHandler { @@ -207,7 +212,9 @@ where ) { let out = match out { future::Either::Left(out) => out, - future::Either::Right(v) => void::unreachable(v), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + future::Either::Right(v) => libp2p_core::util::unreachable(v), }; if let Either::Left(info) = info { @@ -248,7 +255,9 @@ where let err = match err { Either::Left(e) => e, - Either::Right(v) => void::unreachable(v), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + Either::Right(v) => libp2p_core::util::unreachable(v), }; inner.on_connection_event(ConnectionEvent::ListenUpgradeError(ListenUpgradeError { diff --git a/swarm/src/connection.rs b/swarm/src/connection.rs index 15c49bb7bd5..a1c7b2a2f20 100644 --- a/swarm/src/connection.rs +++ b/swarm/src/connection.rs @@ -23,42 +23,47 @@ mod error; pub(crate) mod pool; mod supported_protocols; +use std::{ + collections::{HashMap, HashSet}, + fmt, + fmt::{Display, Formatter}, + future::Future, + io, mem, + pin::Pin, + sync::atomic::{AtomicUsize, Ordering}, + task::{Context, Poll, Waker}, + time::Duration, +}; + pub use error::ConnectionError; pub(crate) use error::{ PendingConnectionError, PendingInboundConnectionError, PendingOutboundConnectionError, }; +use futures::{future::BoxFuture, stream, stream::FuturesUnordered, FutureExt, StreamExt}; +use futures_timer::Delay; +use libp2p_core::{ + connection::ConnectedPoint, + multiaddr::Multiaddr, + muxing::{StreamMuxerBox, StreamMuxerEvent, StreamMuxerExt, SubstreamBox}, + transport::PortUse, + upgrade, + upgrade::{NegotiationError, ProtocolError}, + Endpoint, +}; +use libp2p_identity::PeerId; pub use supported_protocols::SupportedProtocols; +use web_time::Instant; -use crate::handler::{ - AddressChange, ConnectionEvent, ConnectionHandler, DialUpgradeError, FullyNegotiatedInbound, - FullyNegotiatedOutbound, ListenUpgradeError, ProtocolSupport, ProtocolsAdded, ProtocolsChange, - UpgradeInfoSend, -}; -use crate::stream::ActiveStreamCounter; -use crate::upgrade::{InboundUpgradeSend, OutboundUpgradeSend}; use crate::{ + handler::{ + AddressChange, ConnectionEvent, ConnectionHandler, DialUpgradeError, + FullyNegotiatedInbound, FullyNegotiatedOutbound, ListenUpgradeError, ProtocolSupport, + ProtocolsChange, UpgradeInfoSend, + }, + stream::ActiveStreamCounter, + upgrade::{InboundUpgradeSend, OutboundUpgradeSend}, ConnectionHandlerEvent, Stream, StreamProtocol, StreamUpgradeError, SubstreamProtocol, }; -use futures::future::BoxFuture; -use futures::stream::FuturesUnordered; -use futures::StreamExt; -use futures::{stream, FutureExt}; -use futures_timer::Delay; -use instant::Instant; -use libp2p_core::connection::ConnectedPoint; -use libp2p_core::multiaddr::Multiaddr; -use libp2p_core::muxing::{StreamMuxerBox, StreamMuxerEvent, StreamMuxerExt, SubstreamBox}; -use libp2p_core::upgrade; -use libp2p_core::upgrade::{NegotiationError, ProtocolError}; -use libp2p_core::Endpoint; -use libp2p_identity::PeerId; -use std::collections::HashSet; -use std::fmt::{Display, Formatter}; -use std::future::Future; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::task::Waker; -use std::time::Duration; -use std::{fmt, io, mem, pin::Pin, task::Context, task::Poll}; static NEXT_CONNECTION_ID: AtomicUsize = AtomicUsize::new(1); @@ -72,7 +77,8 @@ impl ConnectionId { /// [`Swarm`](crate::Swarm) enforces that [`ConnectionId`]s are unique and not reused. /// This constructor does not, hence the _unchecked_. /// - /// It is primarily meant for allowing manual tests of [`NetworkBehaviour`](crate::NetworkBehaviour)s. + /// It is primarily meant for allowing manual tests of + /// [`NetworkBehaviour`](crate::NetworkBehaviour)s. pub fn new_unchecked(id: usize) -> Self { Self(id) } @@ -147,14 +153,17 @@ where max_negotiating_inbound_streams: usize, /// Contains all upgrades that are waiting for a new outbound substream. /// - /// The upgrade timeout is already ticking here so this may fail in case the remote is not quick - /// enough in providing us with a new stream. + /// The upgrade timeout is already ticking here so this may fail in case the remote is not + /// quick enough in providing us with a new stream. requested_substreams: FuturesUnordered< SubstreamRequested, >, - local_supported_protocols: HashSet, + local_supported_protocols: + HashMap::Info>, bool>, remote_supported_protocols: HashSet, + protocol_buffer: Vec, + idle_timeout: Duration, stream_counter: ActiveStreamCounter, } @@ -187,11 +196,17 @@ where idle_timeout: Duration, ) -> Self { let initial_protocols = gather_supported_protocols(&handler); + let mut buffer = Vec::new(); + if !initial_protocols.is_empty() { handler.on_connection_event(ConnectionEvent::LocalProtocolsChange( - ProtocolsChange::Added(ProtocolsAdded::from_set(&initial_protocols)), + ProtocolsChange::from_initial_protocols( + initial_protocols.keys().map(|e| &e.0), + &mut buffer, + ), )); } + Connection { muxing: muxer, handler, @@ -203,6 +218,7 @@ where requested_substreams: Default::default(), local_supported_protocols: initial_protocols, remote_supported_protocols: Default::default(), + protocol_buffer: buffer, idle_timeout, stream_counter: ActiveStreamCounter::default(), } @@ -213,7 +229,8 @@ where self.handler.on_behaviour_event(event); } - /// Begins an orderly shutdown of the connection, returning a stream of final events and a `Future` that resolves when connection shutdown is complete. + /// Begins an orderly shutdown of the connection, returning a stream of final events and a + /// `Future` that resolves when connection shutdown is complete. pub(crate) fn close( self, ) -> ( @@ -250,6 +267,7 @@ where substream_upgrade_protocol_override, local_supported_protocols: supported_protocols, remote_supported_protocols, + protocol_buffer, idle_timeout, stream_counter, .. @@ -287,30 +305,30 @@ where ProtocolSupport::Added(protocols), )) => { if let Some(added) = - ProtocolsChange::add(remote_supported_protocols, &protocols) + ProtocolsChange::add(remote_supported_protocols, protocols, protocol_buffer) { handler.on_connection_event(ConnectionEvent::RemoteProtocolsChange(added)); - remote_supported_protocols.extend(protocols); + remote_supported_protocols.extend(protocol_buffer.drain(..)); } - continue; } Poll::Ready(ConnectionHandlerEvent::ReportRemoteProtocols( ProtocolSupport::Removed(protocols), )) => { - if let Some(removed) = - ProtocolsChange::remove(remote_supported_protocols, &protocols) - { + if let Some(removed) = ProtocolsChange::remove( + remote_supported_protocols, + protocols, + protocol_buffer, + ) { handler .on_connection_event(ConnectionEvent::RemoteProtocolsChange(removed)); - remote_supported_protocols.retain(|p| !protocols.contains(p)); } - continue; } } - // In case the [`ConnectionHandler`] can not make any more progress, poll the negotiating outbound streams. + // In case the [`ConnectionHandler`] can not make any more progress, poll the + // negotiating outbound streams. match negotiating_out.poll_next_unpin(cx) { Poll::Pending | Poll::Ready(None) => {} Poll::Ready(Some((info, Ok(protocol)))) => { @@ -358,7 +376,8 @@ where } // Check if the connection (and handler) should be shut down. - // As long as we're still negotiating substreams or have any active streams shutdown is always postponed. + // As long as we're still negotiating substreams or have + // any active streams shutdown is always postponed. if negotiating_in.is_empty() && negotiating_out.is_empty() && requested_substreams.is_empty() @@ -373,7 +392,7 @@ where match shutdown { Shutdown::None => {} Shutdown::Asap => return Poll::Ready(Err(ConnectionError::KeepAliveTimeout)), - Shutdown::Later(delay, _) => match Future::poll(Pin::new(delay), cx) { + Shutdown::Later(delay) => match Future::poll(Pin::new(delay), cx) { Poll::Ready(_) => { return Poll::Ready(Err(ConnectionError::KeepAliveTimeout)) } @@ -409,7 +428,9 @@ where stream_counter.clone(), )); - continue; // Go back to the top, handler can potentially make progress again. + // Go back to the top, + // handler can potentially make progress again. + continue; } } } @@ -426,25 +447,29 @@ where stream_counter.clone(), )); - continue; // Go back to the top, handler can potentially make progress again. + // Go back to the top, + // handler can potentially make progress again. + continue; } } } - let new_protocols = gather_supported_protocols(handler); - let changes = ProtocolsChange::from_full_sets(supported_protocols, &new_protocols); + let changes = ProtocolsChange::from_full_sets( + supported_protocols, + handler.listen_protocol().upgrade().protocol_info(), + protocol_buffer, + ); if !changes.is_empty() { for change in changes { handler.on_connection_event(ConnectionEvent::LocalProtocolsChange(change)); } - - *supported_protocols = new_protocols; - - continue; // Go back to the top, handler can potentially make progress again. + // Go back to the top, handler can potentially make progress again. + continue; } - return Poll::Pending; // Nothing can make progress, return `Pending`. + // Nothing can make progress, return `Pending`. + return Poll::Pending; } } @@ -454,12 +479,14 @@ where } } -fn gather_supported_protocols(handler: &impl ConnectionHandler) -> HashSet { +fn gather_supported_protocols( + handler: &C, +) -> HashMap::Info>, bool> { handler .listen_protocol() .upgrade() .protocol_info() - .filter_map(|i| StreamProtocol::try_from_owned(i.as_ref().to_owned()).ok()) + .map(|info| (AsStrHashEq(info), true)) .collect() } @@ -470,24 +497,24 @@ fn compute_new_shutdown( ) -> Option { match (current_shutdown, handler_keep_alive) { (_, false) if idle_timeout == Duration::ZERO => Some(Shutdown::Asap), - (Shutdown::Later(_, _), false) => None, // Do nothing, i.e. let the shutdown timer continue to tick. + // Do nothing, i.e. let the shutdown timer continue to tick. + (Shutdown::Later(_), false) => None, (_, false) => { let now = Instant::now(); let safe_keep_alive = checked_add_fraction(now, idle_timeout); - Some(Shutdown::Later( - Delay::new(safe_keep_alive), - now + safe_keep_alive, - )) + Some(Shutdown::Later(Delay::new(safe_keep_alive))) } (_, true) => Some(Shutdown::None), } } -/// Repeatedly halves and adds the [`Duration`] to the [`Instant`] until [`Instant::checked_add`] succeeds. +/// Repeatedly halves and adds the [`Duration`] +/// to the [`Instant`] until [`Instant::checked_add`] succeeds. /// -/// [`Instant`] depends on the underlying platform and has a limit of which points in time it can represent. -/// The [`Duration`] computed by the this function may not be the longest possible that we can add to `now` but it will work. +/// [`Instant`] depends on the underlying platform and has a limit of which points in time it can +/// represent. The [`Duration`] computed by the this function may not be the longest possible that +/// we can add to `now` but it will work. fn checked_add_fraction(start: Instant, mut duration: Duration) -> Duration { while start.checked_add(duration).is_none() { tracing::debug!(start=?start, duration=?duration, "start + duration cannot be presented, halving duration"); @@ -507,7 +534,7 @@ pub(crate) struct IncomingInfo<'a> { pub(crate) send_back_addr: &'a Multiaddr, } -impl<'a> IncomingInfo<'a> { +impl IncomingInfo<'_> { /// Builds the [`ConnectedPoint`] corresponding to the incoming connection. pub(crate) fn create_connected_point(&self) -> ConnectedPoint { ConnectedPoint::Listener { @@ -720,7 +747,7 @@ impl Future for SubstreamRequested { /// The options for a planned connection & handler shutdown. /// -/// A shutdown is planned anew based on the the return value of +/// A shutdown is planned anew based on the return value of /// [`ConnectionHandler::connection_keep_alive`] of the underlying handler /// after every invocation of [`ConnectionHandler::poll`]. /// @@ -734,29 +761,49 @@ enum Shutdown { /// A shut down is planned as soon as possible. Asap, /// A shut down is planned for when a `Delay` has elapsed. - Later(Delay, Instant), + Later(Delay), +} + +// Structure used to avoid allocations when storing the protocols in the `HashMap. +// Instead of allocating a new `String` for the key, +// we use `T::as_ref()` in `Hash`, `Eq` and `PartialEq` requirements. +pub(crate) struct AsStrHashEq(pub(crate) T); + +impl> Eq for AsStrHashEq {} + +impl> PartialEq for AsStrHashEq { + fn eq(&self, other: &Self) -> bool { + self.0.as_ref() == other.0.as_ref() + } +} + +impl> std::hash::Hash for AsStrHashEq { + fn hash(&self, state: &mut H) { + self.0.as_ref().hash(state) + } } #[cfg(test)] mod tests { + use std::{ + convert::Infallible, + sync::{Arc, Weak}, + time::Instant, + }; + + use futures::{future, AsyncRead, AsyncWrite}; + use libp2p_core::{ + upgrade::{DeniedUpgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo}, + StreamMuxer, + }; + use quickcheck::*; + use super::*; use crate::dummy; - use futures::future; - use futures::AsyncRead; - use futures::AsyncWrite; - use libp2p_core::upgrade::{DeniedUpgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo}; - use libp2p_core::StreamMuxer; - use quickcheck::*; - use std::sync::{Arc, Weak}; - use std::time::Instant; - use tracing_subscriber::EnvFilter; - use void::Void; #[test] fn max_negotiating_inbound_streams() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); fn prop(max_negotiating_inbound_streams: u8) { let max_negotiating_inbound_streams: usize = max_negotiating_inbound_streams.into(); @@ -878,7 +925,8 @@ mod tests { ); assert!(connection.handler.remote_removed.is_empty()); - // Third, stop listening on a protocol it never advertised (we can't control what handlers do so this needs to be handled gracefully). + // Third, stop listening on a protocol it never advertised (we can't control what handlers + // do so this needs to be handled gracefully). connection.handler.remote_removes_support_for(&["/baz"]); let _ = connection.poll_noop_waker(); @@ -923,9 +971,7 @@ mod tests { #[test] fn checked_add_fraction_can_add_u64_max() { - let _ = tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let start = Instant::now(); let duration = checked_add_fraction(start, Duration::from_secs(u64::MAX)); @@ -935,9 +981,7 @@ mod tests { #[test] fn compute_new_shutdown_does_not_panic() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); #[derive(Debug)] struct ArbitraryShutdown(Shutdown); @@ -947,11 +991,10 @@ mod tests { let shutdown = match self.0 { Shutdown::None => Shutdown::None, Shutdown::Asap => Shutdown::Asap, - Shutdown::Later(_, instant) => Shutdown::Later( + Shutdown::Later(_) => Shutdown::Later( // compute_new_shutdown does not touch the delay. Delay does not // implement Clone. Thus use a placeholder delay. Delay::new(Duration::from_secs(1)), - instant, ), }; @@ -964,12 +1007,7 @@ mod tests { let shutdown = match g.gen_range(1u8..4) { 1 => Shutdown::None, 2 => Shutdown::Asap, - 3 => Shutdown::Later( - Delay::new(Duration::from_secs(u32::arbitrary(g) as u64)), - Instant::now() - .checked_add(Duration::arbitrary(g)) - .unwrap_or(Instant::now()), - ), + 3 => Shutdown::Later(Delay::new(Duration::from_secs(u32::arbitrary(g) as u64))), _ => unreachable!(), }; @@ -994,13 +1032,15 @@ mod tests { impl StreamMuxer for DummyStreamMuxer { type Substream = PendingSubstream; - type Error = Void; + type Error = Infallible; fn poll_inbound( self: Pin<&mut Self>, _: &mut Context<'_>, ) -> Poll> { - Poll::Ready(Ok(PendingSubstream(Arc::downgrade(&self.counter)))) + Poll::Ready(Ok(PendingSubstream { + _weak: Arc::downgrade(&self.counter), + })) } fn poll_outbound( @@ -1027,7 +1067,7 @@ mod tests { impl StreamMuxer for PendingStreamMuxer { type Substream = PendingSubstream; - type Error = Void; + type Error = Infallible; fn poll_inbound( self: Pin<&mut Self>, @@ -1055,7 +1095,9 @@ mod tests { } } - struct PendingSubstream(Weak<()>); + struct PendingSubstream { + _weak: Weak<()>, + } impl AsyncRead for PendingSubstream { fn poll_read( @@ -1087,7 +1129,7 @@ mod tests { struct MockConnectionHandler { outbound_requested: bool, - error: Option>, + error: Option>, upgrade_timeout: Duration, } @@ -1107,7 +1149,7 @@ mod tests { #[derive(Default)] struct ConfigurableProtocolConnectionHandler { - events: Vec>, + events: Vec>, active_protocols: HashSet, local_added: Vec>, local_removed: Vec>, @@ -1140,8 +1182,8 @@ mod tests { } impl ConnectionHandler for MockConnectionHandler { - type FromBehaviour = Void; - type ToBehaviour = Void; + type FromBehaviour = Infallible; + type ToBehaviour = Infallible; type InboundProtocol = DeniedUpgrade; type OutboundProtocol = DeniedUpgrade; type InboundOpenInfo = (); @@ -1163,17 +1205,23 @@ mod tests { >, ) { match event { + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { protocol, .. - }) => void::unreachable(protocol), + }) => libp2p_core::util::unreachable(protocol), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound { protocol, .. - }) => void::unreachable(protocol), + }) => libp2p_core::util::unreachable(protocol), ConnectionEvent::DialUpgradeError(DialUpgradeError { error, .. }) => { self.error = Some(error) } + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::AddressChange(_) | ConnectionEvent::ListenUpgradeError(_) | ConnectionEvent::LocalProtocolsChange(_) @@ -1182,7 +1230,9 @@ mod tests { } fn on_behaviour_event(&mut self, event: Self::FromBehaviour) { - void::unreachable(event) + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(event) } fn connection_keep_alive(&self) -> bool { @@ -1212,8 +1262,8 @@ mod tests { } impl ConnectionHandler for ConfigurableProtocolConnectionHandler { - type FromBehaviour = Void; - type ToBehaviour = Void; + type FromBehaviour = Infallible; + type ToBehaviour = Infallible; type InboundProtocol = ManyProtocolsUpgrade; type OutboundProtocol = DeniedUpgrade; type InboundOpenInfo = (); @@ -1257,7 +1307,9 @@ mod tests { } fn on_behaviour_event(&mut self, event: Self::FromBehaviour) { - void::unreachable(event) + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(event) } fn connection_keep_alive(&self) -> bool { @@ -1297,7 +1349,7 @@ mod tests { impl InboundUpgrade for ManyProtocolsUpgrade { type Output = C; - type Error = Void; + type Error = Infallible; type Future = future::Ready>; fn upgrade_inbound(self, stream: C, _: Self::Info) -> Self::Future { @@ -1307,7 +1359,7 @@ mod tests { impl OutboundUpgrade for ManyProtocolsUpgrade { type Output = C; - type Error = Void; + type Error = Infallible; type Future = future::Ready>; fn upgrade_outbound(self, stream: C, _: Self::Info) -> Self::Future { @@ -1327,6 +1379,7 @@ enum PendingPoint { Dialer { /// Same as [`ConnectedPoint::Dialer`] `role_override`. role_override: Endpoint, + port_use: PortUse, }, /// The socket comes from a listener. Listener { @@ -1340,7 +1393,14 @@ enum PendingPoint { impl From for PendingPoint { fn from(endpoint: ConnectedPoint) -> Self { match endpoint { - ConnectedPoint::Dialer { role_override, .. } => PendingPoint::Dialer { role_override }, + ConnectedPoint::Dialer { + role_override, + port_use, + .. + } => PendingPoint::Dialer { + role_override, + port_use, + }, ConnectedPoint::Listener { local_addr, send_back_addr, diff --git a/swarm/src/connection/error.rs b/swarm/src/connection/error.rs index 33aa81c19a9..39e5a88fca6 100644 --- a/swarm/src/connection/error.rs +++ b/swarm/src/connection/error.rs @@ -18,11 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::transport::TransportError; -use crate::Multiaddr; -use crate::{ConnectedPoint, PeerId}; use std::{fmt, io}; +use crate::{transport::TransportError, ConnectedPoint, Multiaddr, PeerId}; + /// Errors that can occur in the context of an established `Connection`. #[derive(Debug)] pub enum ConnectionError { diff --git a/swarm/src/connection/pool.rs b/swarm/src/connection/pool.rs index 9bcd1b446d3..49cc774b2b7 100644 --- a/swarm/src/connection/pool.rs +++ b/swarm/src/connection/pool.rs @@ -18,39 +18,40 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::connection::{Connection, ConnectionId, PendingPoint}; -use crate::{ - connection::{ - Connected, ConnectionError, IncomingInfo, PendingConnectionError, - PendingInboundConnectionError, PendingOutboundConnectionError, - }, - transport::TransportError, - ConnectedPoint, ConnectionHandler, Executor, Multiaddr, PeerId, +use std::{ + collections::HashMap, + convert::Infallible, + fmt, + num::{NonZeroU8, NonZeroUsize}, + pin::Pin, + task::{Context, Poll, Waker}, }; + use concurrent_dial::ConcurrentDial; use fnv::FnvHashMap; -use futures::prelude::*; -use futures::stream::SelectAll; use futures::{ channel::{mpsc, oneshot}, future::{poll_fn, BoxFuture, Either}, + prelude::*, ready, - stream::FuturesUnordered, + stream::{FuturesUnordered, SelectAll}, }; -use instant::{Duration, Instant}; -use libp2p_core::connection::Endpoint; -use libp2p_core::muxing::{StreamMuxerBox, StreamMuxerExt}; -use std::task::Waker; -use std::{ - collections::{hash_map, HashMap}, - fmt, - num::{NonZeroU8, NonZeroUsize}, - pin::Pin, - task::Context, - task::Poll, +use libp2p_core::{ + connection::Endpoint, + muxing::{StreamMuxerBox, StreamMuxerExt}, + transport::PortUse, }; use tracing::Instrument; -use void::Void; +use web_time::{Duration, Instant}; + +use crate::{ + connection::{ + Connected, Connection, ConnectionError, ConnectionId, IncomingInfo, PendingConnectionError, + PendingInboundConnectionError, PendingOutboundConnectionError, PendingPoint, + }, + transport::TransportError, + ConnectedPoint, ConnectionHandler, Executor, Multiaddr, PeerId, +}; mod concurrent_dial; mod task; @@ -70,6 +71,7 @@ impl ExecSwitch { } } + #[track_caller] fn spawn(&mut self, task: impl Future + Send + 'static) { let task = task.boxed(); @@ -113,7 +115,8 @@ where /// See [`Connection::max_negotiating_inbound_streams`]. max_negotiating_inbound_streams: usize, - /// How many [`task::EstablishedConnectionEvent`]s can be buffered before the connection is back-pressured. + /// How many [`task::EstablishedConnectionEvent`]s can be buffered before the connection is + /// back-pressured. per_connection_event_buffer_size: usize, /// The executor to use for running connection tasks. Can either be a global executor @@ -198,14 +201,14 @@ struct PendingConnection { peer_id: Option, endpoint: PendingPoint, /// When dropped, notifies the task which then knows to terminate. - abort_notifier: Option>, + abort_notifier: Option>, /// The moment we became aware of this possible connection, useful for timing metrics. accepted_at: Instant, } impl PendingConnection { fn is_for_same_remote_as(&self, other: PeerId) -> bool { - self.peer_id.map_or(false, |peer| peer == other) + self.peer_id == Some(other) } /// Aborts the connection attempt, closing the connection. @@ -245,13 +248,11 @@ pub(crate) enum PoolEvent { /// /// A connection may close if /// - /// * it encounters an error, which includes the connection being - /// closed by the remote. In this case `error` is `Some`. - /// * it was actively closed by [`EstablishedConnection::start_close`], - /// i.e. a successful, orderly close. - /// * it was actively closed by [`Pool::disconnect`], i.e. - /// dropped without an orderly close. - /// + /// * it encounters an error, which includes the connection being closed by the remote. In + /// this case `error` is `Some`. + /// * it was actively closed by [`EstablishedConnection::start_close`], i.e. a successful, + /// orderly close. + /// * it was actively closed by [`Pool::disconnect`], i.e. dropped without an orderly close. ConnectionClosed { id: ConnectionId, /// Information about the connection that errored. @@ -423,6 +424,7 @@ where >, peer: Option, role_override: Endpoint, + port_use: PortUse, dial_concurrency_factor_override: Option, connection_id: ConnectionId, ) { @@ -443,7 +445,10 @@ where .instrument(span), ); - let endpoint = PendingPoint::Dialer { role_override }; + let endpoint = PendingPoint::Dialer { + role_override, + port_use, + }; self.counters.inc_pending(&endpoint); self.pending.insert( @@ -649,10 +654,17 @@ where self.counters.dec_pending(&endpoint); let (endpoint, concurrent_dial_errors) = match (endpoint, outgoing) { - (PendingPoint::Dialer { role_override }, Some((address, errors))) => ( + ( + PendingPoint::Dialer { + role_override, + port_use, + }, + Some((address, errors)), + ) => ( ConnectedPoint::Dialer { address, role_override, + port_use, }, Some(errors), ), @@ -977,7 +989,7 @@ impl PoolConfig { task_command_buffer_size: 32, per_connection_event_buffer_size: 7, dial_concurrency_factor: NonZeroU8::new(8).expect("8 > 0"), - idle_connection_timeout: Duration::ZERO, + idle_connection_timeout: Duration::from_secs(10), substream_upgrade_protocol_override: None, max_negotiating_inbound_streams: 128, } @@ -1029,28 +1041,3 @@ impl PoolConfig { self } } - -trait EntryExt<'a, K, V> { - fn expect_occupied(self, msg: &'static str) -> hash_map::OccupiedEntry<'a, K, V>; -} - -impl<'a, K: 'a, V: 'a> EntryExt<'a, K, V> for hash_map::Entry<'a, K, V> { - fn expect_occupied(self, msg: &'static str) -> hash_map::OccupiedEntry<'a, K, V> { - match self { - hash_map::Entry::Occupied(entry) => entry, - hash_map::Entry::Vacant(_) => panic!("{}", msg), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use futures::future::Future; - - struct Dummy; - - impl Executor for Dummy { - fn exec(&self, _: Pin + Send>>) {} - } -} diff --git a/swarm/src/connection/pool/concurrent_dial.rs b/swarm/src/connection/pool/concurrent_dial.rs index 57e4b078098..99f0b385884 100644 --- a/swarm/src/connection/pool/concurrent_dial.rs +++ b/swarm/src/connection/pool/concurrent_dial.rs @@ -18,7 +18,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{transport::TransportError, Multiaddr}; +use std::{ + num::NonZeroU8, + pin::Pin, + task::{Context, Poll}, +}; + use futures::{ future::{BoxFuture, Future}, ready, @@ -26,11 +31,8 @@ use futures::{ }; use libp2p_core::muxing::StreamMuxerBox; use libp2p_identity::PeerId; -use std::{ - num::NonZeroU8, - pin::Pin, - task::{Context, Poll}, -}; + +use crate::{transport::TransportError, Multiaddr}; type Dial = BoxFuture< 'static, diff --git a/swarm/src/connection/pool/task.rs b/swarm/src/connection/pool/task.rs index 08674fd2ee5..3a82e5c11d1 100644 --- a/swarm/src/connection/pool/task.rs +++ b/swarm/src/connection/pool/task.rs @@ -21,6 +21,15 @@ //! Async functions driving pending and established connections in the form of a task. +use std::{convert::Infallible, pin::Pin}; + +use futures::{ + channel::{mpsc, oneshot}, + future::{poll_fn, Either, Future}, + SinkExt, StreamExt, +}; +use libp2p_core::muxing::StreamMuxerBox; + use super::concurrent_dial::ConcurrentDial; use crate::{ connection::{ @@ -30,14 +39,6 @@ use crate::{ transport::TransportError, ConnectionHandler, Multiaddr, PeerId, }; -use futures::{ - channel::{mpsc, oneshot}, - future::{poll_fn, Either, Future}, - SinkExt, StreamExt, -}; -use libp2p_core::muxing::StreamMuxerBox; -use std::pin::Pin; -use void::Void; /// Commands that can be sent to a task driving an established connection. #[derive(Debug)] @@ -93,7 +94,7 @@ pub(crate) enum EstablishedConnectionEvent { pub(crate) async fn new_for_pending_outgoing_connection( connection_id: ConnectionId, dial: ConcurrentDial, - abort_receiver: oneshot::Receiver, + abort_receiver: oneshot::Receiver, mut events: mpsc::Sender, ) { match futures::future::select(abort_receiver, Box::pin(dial)).await { @@ -105,7 +106,9 @@ pub(crate) async fn new_for_pending_outgoing_connection( }) .await; } - Either::Left((Ok(v), _)) => void::unreachable(v), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + Either::Left((Ok(v), _)) => libp2p_core::util::unreachable(v), Either::Right((Ok((address, output, errors)), _)) => { let _ = events .send(PendingConnectionEvent::ConnectionEstablished { @@ -129,7 +132,7 @@ pub(crate) async fn new_for_pending_outgoing_connection( pub(crate) async fn new_for_pending_incoming_connection( connection_id: ConnectionId, future: TFut, - abort_receiver: oneshot::Receiver, + abort_receiver: oneshot::Receiver, mut events: mpsc::Sender, ) where TFut: Future> + Send + 'static, @@ -143,7 +146,9 @@ pub(crate) async fn new_for_pending_incoming_connection( }) .await; } - Either::Left((Ok(v), _)) => void::unreachable(v), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + Either::Left((Ok(v), _)) => libp2p_core::util::unreachable(v), Either::Right((Ok(output), _)) => { let _ = events .send(PendingConnectionEvent::ConnectionEstablished { diff --git a/swarm/src/connection/supported_protocols.rs b/swarm/src/connection/supported_protocols.rs index 0575046bb44..c167bf88649 100644 --- a/swarm/src/connection/supported_protocols.rs +++ b/swarm/src/connection/supported_protocols.rs @@ -1,7 +1,7 @@ -use crate::handler::ProtocolsChange; -use crate::StreamProtocol; use std::collections::HashSet; +use crate::{handler::ProtocolsChange, StreamProtocol}; + #[derive(Default, Clone, Debug)] pub struct SupportedProtocols { protocols: HashSet, @@ -40,7 +40,6 @@ impl SupportedProtocols { mod tests { use super::*; use crate::handler::{ProtocolsAdded, ProtocolsRemoved}; - use once_cell::sync::Lazy; #[test] fn protocols_change_added_returns_correct_changed_value() { @@ -70,19 +69,24 @@ mod tests { } fn add_foo() -> ProtocolsChange<'static> { - ProtocolsChange::Added(ProtocolsAdded::from_set(&FOO_PROTOCOLS)) + ProtocolsChange::Added(ProtocolsAdded { + protocols: FOO_PROTOCOLS.iter(), + }) } fn add_foo_bar() -> ProtocolsChange<'static> { - ProtocolsChange::Added(ProtocolsAdded::from_set(&FOO_BAR_PROTOCOLS)) + ProtocolsChange::Added(ProtocolsAdded { + protocols: FOO_BAR_PROTOCOLS.iter(), + }) } fn remove_foo() -> ProtocolsChange<'static> { - ProtocolsChange::Removed(ProtocolsRemoved::from_set(&FOO_PROTOCOLS)) + ProtocolsChange::Removed(ProtocolsRemoved { + protocols: FOO_PROTOCOLS.iter(), + }) } - static FOO_PROTOCOLS: Lazy> = - Lazy::new(|| HashSet::from([StreamProtocol::new("/foo")])); - static FOO_BAR_PROTOCOLS: Lazy> = - Lazy::new(|| HashSet::from([StreamProtocol::new("/foo"), StreamProtocol::new("/bar")])); + static FOO_PROTOCOLS: &[StreamProtocol] = &[StreamProtocol::new("/foo")]; + static FOO_BAR_PROTOCOLS: &[StreamProtocol] = + &[StreamProtocol::new("/foo"), StreamProtocol::new("/bar")]; } diff --git a/swarm/src/dial_opts.rs b/swarm/src/dial_opts.rs index 4442d913847..f569a38df1c 100644 --- a/swarm/src/dial_opts.rs +++ b/swarm/src/dial_opts.rs @@ -19,13 +19,40 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::ConnectionId; -use libp2p_core::connection::Endpoint; -use libp2p_core::multiaddr::Protocol; -use libp2p_core::Multiaddr; -use libp2p_identity::PeerId; use std::num::NonZeroU8; +use libp2p_core::{connection::Endpoint, multiaddr::Protocol, transport::PortUse, Multiaddr}; +use libp2p_identity::PeerId; + +use crate::ConnectionId; + +macro_rules! fn_override_role { + () => { + /// Override role of local node on connection. I.e. execute the dial _as a + /// listener_. + /// + /// See + /// [`ConnectedPoint::Dialer`](libp2p_core::connection::ConnectedPoint::Dialer) + /// for details. + pub fn override_role(mut self) -> Self { + self.role_override = Endpoint::Listener; + self + } + }; +} + +macro_rules! fn_allocate_new_port { + () => { + /// Enforce the allocation of a new port. + /// Default behaviour is best effort reuse of existing ports. If there is no existing + /// fitting listener, a new port is allocated. + pub fn allocate_new_port(mut self) -> Self { + self.port_use = PortUse::New; + self + } + }; +} + /// Options to configure a dial to a known or unknown peer. /// /// Used in [`Swarm::dial`](crate::Swarm::dial) and @@ -45,6 +72,7 @@ pub struct DialOpts { role_override: Endpoint, dial_concurrency_factor_override: Option, connection_id: ConnectionId, + port_use: PortUse, } impl DialOpts { @@ -65,6 +93,7 @@ impl DialOpts { condition: Default::default(), role_override: Endpoint::Dialer, dial_concurrency_factor_override: Default::default(), + port_use: PortUse::Reuse, } } @@ -100,7 +129,8 @@ impl DialOpts { /// Get the [`ConnectionId`] of this dial attempt. /// /// All future events of this dial will be associated with this ID. - /// See [`DialFailure`](crate::DialFailure) and [`ConnectionEstablished`](crate::behaviour::ConnectionEstablished). + /// See [`DialFailure`](crate::DialFailure) and + /// [`ConnectionEstablished`](crate::behaviour::ConnectionEstablished). pub fn connection_id(&self) -> ConnectionId { self.connection_id } @@ -124,6 +154,10 @@ impl DialOpts { pub(crate) fn role_override(&self) -> Endpoint { self.role_override } + + pub(crate) fn port_use(&self) -> PortUse { + self.port_use + } } impl From for DialOpts { @@ -144,6 +178,7 @@ pub struct WithPeerId { condition: PeerCondition, role_override: Endpoint, dial_concurrency_factor_override: Option, + port_use: PortUse, } impl WithPeerId { @@ -169,19 +204,12 @@ impl WithPeerId { extend_addresses_through_behaviour: false, role_override: self.role_override, dial_concurrency_factor_override: self.dial_concurrency_factor_override, + port_use: self.port_use, } } - /// Override role of local node on connection. I.e. execute the dial _as a - /// listener_. - /// - /// See - /// [`ConnectedPoint::Dialer`](libp2p_core::connection::ConnectedPoint::Dialer) - /// for details. - pub fn override_role(mut self) -> Self { - self.role_override = Endpoint::Listener; - self - } + fn_override_role!(); + fn_allocate_new_port!(); /// Build the final [`DialOpts`]. pub fn build(self) -> DialOpts { @@ -193,6 +221,7 @@ impl WithPeerId { role_override: self.role_override, dial_concurrency_factor_override: self.dial_concurrency_factor_override, connection_id: ConnectionId::next(), + port_use: self.port_use, } } } @@ -205,6 +234,7 @@ pub struct WithPeerIdWithAddresses { extend_addresses_through_behaviour: bool, role_override: Endpoint, dial_concurrency_factor_override: Option, + port_use: PortUse, } impl WithPeerIdWithAddresses { @@ -221,16 +251,8 @@ impl WithPeerIdWithAddresses { self } - /// Override role of local node on connection. I.e. execute the dial _as a - /// listener_. - /// - /// See - /// [`ConnectedPoint::Dialer`](libp2p_core::connection::ConnectedPoint::Dialer) - /// for details. - pub fn override_role(mut self) -> Self { - self.role_override = Endpoint::Listener; - self - } + fn_override_role!(); + fn_allocate_new_port!(); /// Override /// Number of addresses concurrently dialed for a single outbound connection attempt. @@ -249,6 +271,7 @@ impl WithPeerIdWithAddresses { role_override: self.role_override, dial_concurrency_factor_override: self.dial_concurrency_factor_override, connection_id: ConnectionId::next(), + port_use: self.port_use, } } } @@ -262,6 +285,7 @@ impl WithoutPeerId { WithoutPeerIdWithAddress { address, role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, } } } @@ -270,19 +294,13 @@ impl WithoutPeerId { pub struct WithoutPeerIdWithAddress { address: Multiaddr, role_override: Endpoint, + port_use: PortUse, } impl WithoutPeerIdWithAddress { - /// Override role of local node on connection. I.e. execute the dial _as a - /// listener_. - /// - /// See - /// [`ConnectedPoint::Dialer`](libp2p_core::connection::ConnectedPoint::Dialer) - /// for details. - pub fn override_role(mut self) -> Self { - self.role_override = Endpoint::Listener; - self - } + fn_override_role!(); + fn_allocate_new_port!(); + /// Build the final [`DialOpts`]. pub fn build(self) -> DialOpts { DialOpts { @@ -293,6 +311,7 @@ impl WithoutPeerIdWithAddress { role_override: self.role_override, dial_concurrency_factor_override: None, connection_id: ConnectionId::next(), + port_use: self.port_use, } } } @@ -305,8 +324,8 @@ impl WithoutPeerIdWithAddress { /// # use libp2p_identity::PeerId; /// # /// DialOpts::peer_id(PeerId::random()) -/// .condition(PeerCondition::Disconnected) -/// .build(); +/// .condition(PeerCondition::Disconnected) +/// .build(); /// ``` #[derive(Debug, Copy, Clone, Default)] pub enum PeerCondition { @@ -319,7 +338,7 @@ pub enum PeerCondition { NotDialing, /// A combination of [`Disconnected`](PeerCondition::Disconnected) and /// [`NotDialing`](PeerCondition::NotDialing). A new dialing attempt is - /// iniated _only if_ the peer is both considered disconnected and there + /// initiated _only if_ the peer is both considered disconnected and there /// is currently no ongoing dialing attempt. #[default] DisconnectedAndNotDialing, diff --git a/swarm/src/dummy.rs b/swarm/src/dummy.rs index 86df676443b..5452c382cd4 100644 --- a/swarm/src/dummy.rs +++ b/swarm/src/dummy.rs @@ -1,25 +1,25 @@ -use crate::behaviour::{FromSwarm, NetworkBehaviour, ToSwarm}; -use crate::connection::ConnectionId; -use crate::handler::{ - ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, +use std::{ + convert::Infallible, + task::{Context, Poll}, }; + +use libp2p_core::{transport::PortUse, upgrade::DeniedUpgrade, Endpoint, Multiaddr}; +use libp2p_identity::PeerId; + use crate::{ + behaviour::{FromSwarm, NetworkBehaviour, ToSwarm}, + connection::ConnectionId, + handler::{ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound}, ConnectionDenied, ConnectionHandlerEvent, StreamUpgradeError, SubstreamProtocol, THandler, THandlerInEvent, THandlerOutEvent, }; -use libp2p_core::upgrade::DeniedUpgrade; -use libp2p_core::Endpoint; -use libp2p_core::Multiaddr; -use libp2p_identity::PeerId; -use std::task::{Context, Poll}; -use void::Void; /// Implementation of [`NetworkBehaviour`] that doesn't do anything. pub struct Behaviour; impl NetworkBehaviour for Behaviour { type ConnectionHandler = ConnectionHandler; - type ToSwarm = Void; + type ToSwarm = Infallible; fn handle_established_inbound_connection( &mut self, @@ -37,6 +37,7 @@ impl NetworkBehaviour for Behaviour { _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(ConnectionHandler) } @@ -47,7 +48,9 @@ impl NetworkBehaviour for Behaviour { _: ConnectionId, event: THandlerOutEvent, ) { - void::unreachable(event) + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(event) } fn poll(&mut self, _: &mut Context<'_>) -> Poll>> { @@ -57,24 +60,27 @@ impl NetworkBehaviour for Behaviour { fn on_swarm_event(&mut self, _event: FromSwarm) {} } -/// An implementation of [`ConnectionHandler`] that neither handles any protocols nor does it keep the connection alive. +/// An implementation of [`ConnectionHandler`] that neither handles any protocols nor does it keep +/// the connection alive. #[derive(Clone)] pub struct ConnectionHandler; impl crate::handler::ConnectionHandler for ConnectionHandler { - type FromBehaviour = Void; - type ToBehaviour = Void; + type FromBehaviour = Infallible; + type ToBehaviour = Infallible; type InboundProtocol = DeniedUpgrade; type OutboundProtocol = DeniedUpgrade; type InboundOpenInfo = (); - type OutboundOpenInfo = Void; + type OutboundOpenInfo = Infallible; fn listen_protocol(&self) -> SubstreamProtocol { SubstreamProtocol::new(DeniedUpgrade, ()) } fn on_behaviour_event(&mut self, event: Self::FromBehaviour) { - void::unreachable(event) + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(event) } fn poll( @@ -96,19 +102,29 @@ impl crate::handler::ConnectionHandler for ConnectionHandler { >, ) { match event { + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { protocol, .. - }) => void::unreachable(protocol), + }) => libp2p_core::util::unreachable(protocol), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound { protocol, .. - }) => void::unreachable(protocol), + }) => libp2p_core::util::unreachable(protocol), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::DialUpgradeError(DialUpgradeError { info: _, error }) => match error { + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] StreamUpgradeError::Timeout => unreachable!(), - StreamUpgradeError::Apply(e) => void::unreachable(e), + StreamUpgradeError::Apply(e) => libp2p_core::util::unreachable(e), StreamUpgradeError::NegotiationFailed | StreamUpgradeError::Io(_) => { unreachable!("Denied upgrade does not support any protocols") } }, + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::AddressChange(_) | ConnectionEvent::ListenUpgradeError(_) | ConnectionEvent::LocalProtocolsChange(_) diff --git a/swarm/src/executor.rs b/swarm/src/executor.rs index e949bf3cbde..db5ed6b2da4 100644 --- a/swarm/src/executor.rs +++ b/swarm/src/executor.rs @@ -1,16 +1,18 @@ //! Provides executors for spawning background tasks. -use futures::executor::ThreadPool; use std::{future::Future, pin::Pin}; +use futures::executor::ThreadPool; + /// Implemented on objects that can run a `Future` in the background. /// /// > **Note**: While it may be tempting to implement this trait on types such as -/// > [`futures::stream::FuturesUnordered`], please note that passing an `Executor` is -/// > optional, and that `FuturesUnordered` (or a similar struct) will automatically -/// > be used as fallback by libp2p. The `Executor` trait should therefore only be -/// > about running `Future`s on a separate task. +/// > [`futures::stream::FuturesUnordered`], please note that passing an `Executor` is +/// > optional, and that `FuturesUnordered` (or a similar struct) will automatically +/// > be used as fallback by libp2p. The `Executor` trait should therefore only be +/// > about running `Future`s on a separate task. pub trait Executor { /// Run the given future in the background until it ends. + #[track_caller] fn exec(&self, future: Pin + Send>>); } diff --git a/swarm/src/handler.rs b/swarm/src/handler.rs index 31d2c91e391..3d0407b4f70 100644 --- a/swarm/src/handler.rs +++ b/swarm/src/handler.rs @@ -34,9 +34,9 @@ //! used protocol(s) determined by the associated types of the handlers. //! //! > **Note**: A [`ConnectionHandler`] handles one or more protocols in the context of a single -//! > connection with a remote. In order to handle a protocol that requires knowledge of -//! > the network as a whole, see the -//! > [`NetworkBehaviour`](crate::behaviour::NetworkBehaviour) trait. +//! > connection with a remote. In order to handle a protocol that requires knowledge of +//! > the network as a whole, see the +//! > [`NetworkBehaviour`](crate::behaviour::NetworkBehaviour) trait. pub mod either; mod map_in; @@ -46,23 +46,24 @@ mod one_shot; mod pending; mod select; -pub use crate::upgrade::{InboundUpgradeSend, OutboundUpgradeSend, SendWrapper, UpgradeInfoSend}; +use core::slice; +use std::{ + collections::{HashMap, HashSet}, + error, fmt, io, + task::{Context, Poll}, + time::Duration, +}; + +use libp2p_core::Multiaddr; pub use map_in::MapInEvent; pub use map_out::MapOutEvent; pub use one_shot::{OneShotHandler, OneShotHandlerConfig}; pub use pending::PendingConnectionHandler; pub use select::ConnectionHandlerSelect; - -use crate::StreamProtocol; -use ::either::Either; -use libp2p_core::Multiaddr; -use once_cell::sync::Lazy; use smallvec::SmallVec; -use std::collections::hash_map::RandomState; -use std::collections::hash_set::{Difference, Intersection}; -use std::collections::HashSet; -use std::iter::Peekable; -use std::{error, fmt, io, task::Context, task::Poll, time::Duration}; + +pub use crate::upgrade::{InboundUpgradeSend, OutboundUpgradeSend, SendWrapper, UpgradeInfoSend}; +use crate::{connection::AsStrHashEq, StreamProtocol}; /// A handler for a set of protocols used on a connection with a remote. /// @@ -74,17 +75,17 @@ use std::{error, fmt, io, task::Context, task::Poll, time::Duration}; /// Communication with a remote over a set of protocols is initiated in one of two ways: /// /// 1. Dialing by initiating a new outbound substream. In order to do so, -/// [`ConnectionHandler::poll()`] must return an [`ConnectionHandlerEvent::OutboundSubstreamRequest`], -/// providing an instance of [`libp2p_core::upgrade::OutboundUpgrade`] that is used to negotiate the -/// protocol(s). Upon success, [`ConnectionHandler::on_connection_event`] is called with +/// [`ConnectionHandler::poll()`] must return an +/// [`ConnectionHandlerEvent::OutboundSubstreamRequest`], providing an instance of +/// [`libp2p_core::upgrade::OutboundUpgrade`] that is used to negotiate the protocol(s). Upon +/// success, [`ConnectionHandler::on_connection_event`] is called with /// [`ConnectionEvent::FullyNegotiatedOutbound`] translating the final output of the upgrade. /// -/// 2. Listening by accepting a new inbound substream. When a new inbound substream -/// is created on a connection, [`ConnectionHandler::listen_protocol`] is called -/// to obtain an instance of [`libp2p_core::upgrade::InboundUpgrade`] that is used to -/// negotiate the protocol(s). Upon success, -/// [`ConnectionHandler::on_connection_event`] is called with [`ConnectionEvent::FullyNegotiatedInbound`] -/// translating the final output of the upgrade. +/// 2. Listening by accepting a new inbound substream. When a new inbound substream is created on +/// a connection, [`ConnectionHandler::listen_protocol`] is called to obtain an instance of +/// [`libp2p_core::upgrade::InboundUpgrade`] that is used to negotiate the protocol(s). Upon +/// success, [`ConnectionHandler::on_connection_event`] is called with +/// [`ConnectionEvent::FullyNegotiatedInbound`] translating the final output of the upgrade. /// /// /// # Connection Keep-Alive @@ -98,9 +99,13 @@ use std::{error, fmt, io, task::Context, task::Poll, time::Duration}; /// When a connection is closed gracefully, the substreams used by the handler may still /// continue reading data until the remote closes its side of the connection. pub trait ConnectionHandler: Send + 'static { - /// A type representing the message(s) a [`NetworkBehaviour`](crate::behaviour::NetworkBehaviour) can send to a [`ConnectionHandler`] via [`ToSwarm::NotifyHandler`](crate::behaviour::ToSwarm::NotifyHandler) + /// A type representing the message(s) a + /// [`NetworkBehaviour`](crate::behaviour::NetworkBehaviour) can send to a [`ConnectionHandler`] + /// via [`ToSwarm::NotifyHandler`](crate::behaviour::ToSwarm::NotifyHandler) type FromBehaviour: fmt::Debug + Send + 'static; - /// A type representing message(s) a [`ConnectionHandler`] can send to a [`NetworkBehaviour`](crate::behaviour::NetworkBehaviour) via [`ConnectionHandlerEvent::NotifyBehaviour`]. + /// A type representing message(s) a [`ConnectionHandler`] can send to a + /// [`NetworkBehaviour`](crate::behaviour::NetworkBehaviour) via + /// [`ConnectionHandlerEvent::NotifyBehaviour`]. type ToBehaviour: fmt::Debug + Send + 'static; /// The inbound upgrade for the protocol(s) used by the handler. type InboundProtocol: InboundUpgradeSend; @@ -115,9 +120,9 @@ pub trait ConnectionHandler: Send + 'static { /// substreams to negotiate the desired protocols. /// /// > **Note**: The returned `InboundUpgrade` should always accept all the generally - /// > supported protocols, even if in a specific context a particular one is - /// > not supported, (eg. when only allowing one substream at a time for a protocol). - /// > This allows a remote to put the list of supported protocols in a cache. + /// > supported protocols, even if in a specific context a particular one is + /// > not supported, (eg. when only allowing one substream at a time for a protocol). + /// > This allows a remote to put the list of supported protocols in a cache. fn listen_protocol(&self) -> SubstreamProtocol; /// Returns whether the connection should be kept alive. @@ -130,14 +135,21 @@ pub trait ConnectionHandler: Send + 'static { /// - We are negotiating inbound or outbound streams. /// - There are active [`Stream`](crate::Stream)s on the connection. /// - /// The combination of the above means that _most_ protocols will not need to override this method. - /// This method is only invoked when all of the above are `false`, i.e. when the connection is entirely idle. + /// The combination of the above means that _most_ protocols will not need to override this + /// method. This method is only invoked when all of the above are `false`, i.e. when the + /// connection is entirely idle. /// /// ## Exceptions /// - /// - Protocols like [circuit-relay v2](https://github.com/libp2p/specs/blob/master/relay/circuit-v2.md) need to keep a connection alive beyond these circumstances and can thus override this method. - /// - Protocols like [ping](https://github.com/libp2p/specs/blob/master/ping/ping.md) **don't** want to keep a connection alive despite an active streams. - /// In that case, protocol authors can use [`Stream::ignore_for_keep_alive`](crate::Stream::ignore_for_keep_alive) to opt-out a particular stream from the keep-alive algorithm. + /// - Protocols like [circuit-relay v2](https://github.com/libp2p/specs/blob/master/relay/circuit-v2.md) + /// need to keep a connection alive beyond these circumstances and can thus override this + /// method. + /// - Protocols like [ping](https://github.com/libp2p/specs/blob/master/ping/ping.md) **don't** + /// want to keep a connection alive despite an active streams. + /// + /// In that case, protocol authors can use + /// [`Stream::ignore_for_keep_alive`](crate::Stream::ignore_for_keep_alive) to opt-out a + /// particular stream from the keep-alive algorithm. fn connection_keep_alive(&self) -> bool { false } @@ -162,7 +174,8 @@ pub trait ConnectionHandler: Send + 'static { /// To signal completion, [`Poll::Ready(None)`] should be returned. /// /// Implementations MUST have a [`fuse`](futures::StreamExt::fuse)-like behaviour. - /// That is, [`Poll::Ready(None)`] MUST be returned on repeated calls to [`ConnectionHandler::poll_close`]. + /// That is, [`Poll::Ready(None)`] MUST be returned on repeated calls to + /// [`ConnectionHandler::poll_close`]. fn poll_close(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(None) } @@ -228,7 +241,7 @@ pub enum ConnectionEvent<'a, IP: InboundUpgradeSend, OP: OutboundUpgradeSend, IO RemoteProtocolsChange(ProtocolsChange<'a>), } -impl<'a, IP, OP, IOI, OOI> fmt::Debug for ConnectionEvent<'a, IP, OP, IOI, OOI> +impl fmt::Debug for ConnectionEvent<'_, IP, OP, IOI, OOI> where IP: InboundUpgradeSend + fmt::Debug, IP::Output: fmt::Debug, @@ -264,8 +277,8 @@ where } } -impl<'a, IP: InboundUpgradeSend, OP: OutboundUpgradeSend, IOI, OOI> - ConnectionEvent<'a, IP, OP, IOI, OOI> +impl + ConnectionEvent<'_, IP, OP, IOI, OOI> { /// Whether the event concerns an outbound stream. pub fn is_outbound(&self) -> bool { @@ -310,7 +323,8 @@ pub struct FullyNegotiatedInbound { pub info: IOI, } -/// [`ConnectionEvent`] variant that informs the handler about successful upgrade on a new outbound stream. +/// [`ConnectionEvent`] variant that informs the handler about successful upgrade on a new outbound +/// stream. /// /// The `protocol` field is the information that was previously passed to /// [`ConnectionHandlerEvent::OutboundSubstreamRequest`]. @@ -320,13 +334,15 @@ pub struct FullyNegotiatedOutbound { pub info: OOI, } -/// [`ConnectionEvent`] variant that informs the handler about a change in the address of the remote. +/// [`ConnectionEvent`] variant that informs the handler about a change in the address of the +/// remote. #[derive(Debug)] pub struct AddressChange<'a> { pub new_address: &'a Multiaddr, } -/// [`ConnectionEvent`] variant that informs the handler about a change in the protocols supported on the connection. +/// [`ConnectionEvent`] variant that informs the handler about a change in the protocols supported +/// on the connection. #[derive(Debug, Clone)] pub enum ProtocolsChange<'a> { Added(ProtocolsAdded<'a>), @@ -334,64 +350,128 @@ pub enum ProtocolsChange<'a> { } impl<'a> ProtocolsChange<'a> { + /// Compute the protocol change for the initial set of protocols. + pub(crate) fn from_initial_protocols<'b, T: AsRef + 'b>( + new_protocols: impl IntoIterator, + buffer: &'a mut Vec, + ) -> Self { + buffer.clear(); + buffer.extend( + new_protocols + .into_iter() + .filter_map(|i| StreamProtocol::try_from_owned(i.as_ref().to_owned()).ok()), + ); + + ProtocolsChange::Added(ProtocolsAdded { + protocols: buffer.iter(), + }) + } + /// Compute the [`ProtocolsChange`] that results from adding `to_add` to `existing_protocols`. /// /// Returns `None` if the change is a no-op, i.e. `to_add` is a subset of `existing_protocols`. pub(crate) fn add( - existing_protocols: &'a HashSet, - to_add: &'a HashSet, + existing_protocols: &HashSet, + to_add: HashSet, + buffer: &'a mut Vec, ) -> Option { - let mut actually_added_protocols = to_add.difference(existing_protocols).peekable(); - - actually_added_protocols.peek()?; + buffer.clear(); + buffer.extend( + to_add + .into_iter() + .filter(|i| !existing_protocols.contains(i)), + ); + + if buffer.is_empty() { + return None; + } - Some(ProtocolsChange::Added(ProtocolsAdded { - protocols: actually_added_protocols, + Some(Self::Added(ProtocolsAdded { + protocols: buffer.iter(), })) } - /// Compute the [`ProtocolsChange`] that results from removing `to_remove` from `existing_protocols`. + /// Compute the [`ProtocolsChange`] that results from removing `to_remove` from + /// `existing_protocols`. Removes the protocols from `existing_protocols`. /// - /// Returns `None` if the change is a no-op, i.e. none of the protocols in `to_remove` are in `existing_protocols`. + /// Returns `None` if the change is a no-op, i.e. none of the protocols in `to_remove` are in + /// `existing_protocols`. pub(crate) fn remove( - existing_protocols: &'a HashSet, - to_remove: &'a HashSet, + existing_protocols: &mut HashSet, + to_remove: HashSet, + buffer: &'a mut Vec, ) -> Option { - let mut actually_removed_protocols = existing_protocols.intersection(to_remove).peekable(); - - actually_removed_protocols.peek()?; + buffer.clear(); + buffer.extend( + to_remove + .into_iter() + .filter_map(|i| existing_protocols.take(&i)), + ); + + if buffer.is_empty() { + return None; + } - Some(ProtocolsChange::Removed(ProtocolsRemoved { - protocols: Either::Right(actually_removed_protocols), + Some(Self::Removed(ProtocolsRemoved { + protocols: buffer.iter(), })) } - /// Compute the [`ProtocolsChange`]s required to go from `existing_protocols` to `new_protocols`. - pub(crate) fn from_full_sets( - existing_protocols: &'a HashSet, - new_protocols: &'a HashSet, + /// Compute the [`ProtocolsChange`]s required to go from `existing_protocols` to + /// `new_protocols`. + pub(crate) fn from_full_sets>( + existing_protocols: &mut HashMap, bool>, + new_protocols: impl IntoIterator, + buffer: &'a mut Vec, ) -> SmallVec<[Self; 2]> { - if existing_protocols == new_protocols { + buffer.clear(); + + // Initially, set the boolean for all protocols to `false`, meaning "not visited". + for v in existing_protocols.values_mut() { + *v = false; + } + + let mut new_protocol_count = 0; // We can only iterate `new_protocols` once, so keep track of its length separately. + for new_protocol in new_protocols { + existing_protocols + .entry(AsStrHashEq(new_protocol)) + .and_modify(|v| *v = true) // Mark protocol as visited (i.e. we still support it) + .or_insert_with_key(|k| { + // Encountered a previously unsupported protocol, remember it in `buffer`. + buffer.extend(StreamProtocol::try_from_owned(k.0.as_ref().to_owned()).ok()); + true + }); + new_protocol_count += 1; + } + + if new_protocol_count == existing_protocols.len() && buffer.is_empty() { return SmallVec::new(); } - let mut changes = SmallVec::new(); + let num_new_protocols = buffer.len(); + // Drain all protocols that we haven't visited. + // For existing protocols that are not in `new_protocols`, the boolean will be false, + // meaning we need to remove it. + existing_protocols.retain(|p, &mut is_supported| { + if !is_supported { + buffer.extend(StreamProtocol::try_from_owned(p.0.as_ref().to_owned()).ok()); + } - let mut added_protocols = new_protocols.difference(existing_protocols).peekable(); - let mut removed_protocols = existing_protocols.difference(new_protocols).peekable(); + is_supported + }); - if added_protocols.peek().is_some() { + let (added, removed) = buffer.split_at(num_new_protocols); + let mut changes = SmallVec::new(); + if !added.is_empty() { changes.push(ProtocolsChange::Added(ProtocolsAdded { - protocols: added_protocols, + protocols: added.iter(), })); } - - if removed_protocols.peek().is_some() { + if !removed.is_empty() { changes.push(ProtocolsChange::Removed(ProtocolsRemoved { - protocols: Either::Left(removed_protocols), + protocols: removed.iter(), })); } - changes } } @@ -399,33 +479,13 @@ impl<'a> ProtocolsChange<'a> { /// An [`Iterator`] over all protocols that have been added. #[derive(Debug, Clone)] pub struct ProtocolsAdded<'a> { - protocols: Peekable>, -} - -impl<'a> ProtocolsAdded<'a> { - pub(crate) fn from_set(protocols: &'a HashSet) -> Self { - ProtocolsAdded { - protocols: protocols.difference(&EMPTY_HASHSET).peekable(), - } - } + pub(crate) protocols: slice::Iter<'a, StreamProtocol>, } /// An [`Iterator`] over all protocols that have been removed. #[derive(Debug, Clone)] pub struct ProtocolsRemoved<'a> { - protocols: Either< - Peekable>, - Peekable>, - >, -} - -impl<'a> ProtocolsRemoved<'a> { - #[cfg(test)] - pub(crate) fn from_set(protocols: &'a HashSet) -> Self { - ProtocolsRemoved { - protocols: Either::Left(protocols.difference(&EMPTY_HASHSET).peekable()), - } - } + pub(crate) protocols: slice::Iter<'a, StreamProtocol>, } impl<'a> Iterator for ProtocolsAdded<'a> { @@ -690,6 +750,169 @@ where } } -/// A statically declared, empty [`HashSet`] allows us to work around borrow-checker rules for -/// [`ProtocolsAdded::from_set`]. The lifetimes don't work unless we have a [`HashSet`] with a `'static' lifetime. -static EMPTY_HASHSET: Lazy> = Lazy::new(HashSet::new); +#[cfg(test)] +mod test { + use super::*; + + fn protocol_set_of(s: &'static str) -> HashSet { + s.split_whitespace() + .map(|p| StreamProtocol::try_from_owned(format!("/{p}")).unwrap()) + .collect() + } + + fn test_remove( + existing: &mut HashSet, + to_remove: HashSet, + ) -> HashSet { + ProtocolsChange::remove(existing, to_remove, &mut Vec::new()) + .into_iter() + .flat_map(|c| match c { + ProtocolsChange::Added(_) => panic!("unexpected added"), + ProtocolsChange::Removed(r) => r.cloned(), + }) + .collect::>() + } + + #[test] + fn test_protocol_remove_subset() { + let mut existing = protocol_set_of("a b c"); + let to_remove = protocol_set_of("a b"); + + let change = test_remove(&mut existing, to_remove); + + assert_eq!(existing, protocol_set_of("c")); + assert_eq!(change, protocol_set_of("a b")); + } + + #[test] + fn test_protocol_remove_all() { + let mut existing = protocol_set_of("a b c"); + let to_remove = protocol_set_of("a b c"); + + let change = test_remove(&mut existing, to_remove); + + assert_eq!(existing, protocol_set_of("")); + assert_eq!(change, protocol_set_of("a b c")); + } + + #[test] + fn test_protocol_remove_superset() { + let mut existing = protocol_set_of("a b c"); + let to_remove = protocol_set_of("a b c d"); + + let change = test_remove(&mut existing, to_remove); + + assert_eq!(existing, protocol_set_of("")); + assert_eq!(change, protocol_set_of("a b c")); + } + + #[test] + fn test_protocol_remove_none() { + let mut existing = protocol_set_of("a b c"); + let to_remove = protocol_set_of("d"); + + let change = test_remove(&mut existing, to_remove); + + assert_eq!(existing, protocol_set_of("a b c")); + assert_eq!(change, protocol_set_of("")); + } + + #[test] + fn test_protocol_remove_none_from_empty() { + let mut existing = protocol_set_of(""); + let to_remove = protocol_set_of("d"); + + let change = test_remove(&mut existing, to_remove); + + assert_eq!(existing, protocol_set_of("")); + assert_eq!(change, protocol_set_of("")); + } + + fn test_from_full_sets( + existing: HashSet, + new: HashSet, + ) -> [HashSet; 2] { + let mut buffer = Vec::new(); + let mut existing = existing + .iter() + .map(|p| (AsStrHashEq(p.as_ref()), true)) + .collect::>(); + + let changes = ProtocolsChange::from_full_sets( + &mut existing, + new.iter().map(AsRef::as_ref), + &mut buffer, + ); + + let mut added_changes = HashSet::new(); + let mut removed_changes = HashSet::new(); + + for change in changes { + match change { + ProtocolsChange::Added(a) => { + added_changes.extend(a.cloned()); + } + ProtocolsChange::Removed(r) => { + removed_changes.extend(r.cloned()); + } + } + } + + [removed_changes, added_changes] + } + + #[test] + fn test_from_full_stes_subset() { + let existing = protocol_set_of("a b c"); + let new = protocol_set_of("a b"); + + let [removed_changes, added_changes] = test_from_full_sets(existing, new); + + assert_eq!(added_changes, protocol_set_of("")); + assert_eq!(removed_changes, protocol_set_of("c")); + } + + #[test] + fn test_from_full_sets_superset() { + let existing = protocol_set_of("a b"); + let new = protocol_set_of("a b c"); + + let [removed_changes, added_changes] = test_from_full_sets(existing, new); + + assert_eq!(added_changes, protocol_set_of("c")); + assert_eq!(removed_changes, protocol_set_of("")); + } + + #[test] + fn test_from_full_sets_intersection() { + let existing = protocol_set_of("a b c"); + let new = protocol_set_of("b c d"); + + let [removed_changes, added_changes] = test_from_full_sets(existing, new); + + assert_eq!(added_changes, protocol_set_of("d")); + assert_eq!(removed_changes, protocol_set_of("a")); + } + + #[test] + fn test_from_full_sets_disjoint() { + let existing = protocol_set_of("a b c"); + let new = protocol_set_of("d e f"); + + let [removed_changes, added_changes] = test_from_full_sets(existing, new); + + assert_eq!(added_changes, protocol_set_of("d e f")); + assert_eq!(removed_changes, protocol_set_of("a b c")); + } + + #[test] + fn test_from_full_sets_empty() { + let existing = protocol_set_of(""); + let new = protocol_set_of(""); + + let [removed_changes, added_changes] = test_from_full_sets(existing, new); + + assert_eq!(added_changes, protocol_set_of("")); + assert_eq!(removed_changes, protocol_set_of("")); + } +} diff --git a/swarm/src/handler/either.rs b/swarm/src/handler/either.rs index a5aab9b5fee..1dc62e0eb2a 100644 --- a/swarm/src/handler/either.rs +++ b/swarm/src/handler/either.rs @@ -18,14 +18,18 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::handler::{ - ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, FullyNegotiatedInbound, - InboundUpgradeSend, ListenUpgradeError, SubstreamProtocol, -}; -use crate::upgrade::SendWrapper; +use std::task::{Context, Poll}; + use either::Either; use futures::future; -use std::task::{Context, Poll}; + +use crate::{ + handler::{ + ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, FullyNegotiatedInbound, + InboundUpgradeSend, ListenUpgradeError, SubstreamProtocol, + }, + upgrade::SendWrapper, +}; impl FullyNegotiatedInbound, SendWrapper>, Either> diff --git a/swarm/src/handler/map_in.rs b/swarm/src/handler/map_in.rs index 9316ef4d2ce..55885b351bb 100644 --- a/swarm/src/handler/map_in.rs +++ b/swarm/src/handler/map_in.rs @@ -18,10 +18,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::{ + fmt::Debug, + marker::PhantomData, + task::{Context, Poll}, +}; + use crate::handler::{ ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, SubstreamProtocol, }; -use std::{fmt::Debug, marker::PhantomData, task::Context, task::Poll}; /// Wrapper around a protocol handler that turns the input event into something else. #[derive(Debug)] diff --git a/swarm/src/handler/map_out.rs b/swarm/src/handler/map_out.rs index f877bfa6f64..6d05551aeec 100644 --- a/swarm/src/handler/map_out.rs +++ b/swarm/src/handler/map_out.rs @@ -18,12 +18,16 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::{ + fmt::Debug, + task::{Context, Poll}, +}; + +use futures::ready; + use crate::handler::{ ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, SubstreamProtocol, }; -use futures::ready; -use std::fmt::Debug; -use std::task::{Context, Poll}; /// Wrapper around a protocol handler that turns the output event into something else. #[derive(Debug)] diff --git a/swarm/src/handler/multi.rs b/swarm/src/handler/multi.rs index 0b4549ed733..73af1b1109e 100644 --- a/swarm/src/handler/multi.rs +++ b/swarm/src/handler/multi.rs @@ -21,25 +21,30 @@ //! A [`ConnectionHandler`] implementation that combines multiple other [`ConnectionHandler`]s //! indexed by some key. -use crate::handler::{ - AddressChange, ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, DialUpgradeError, - FullyNegotiatedInbound, FullyNegotiatedOutbound, ListenUpgradeError, SubstreamProtocol, -}; -use crate::upgrade::{InboundUpgradeSend, OutboundUpgradeSend, UpgradeInfoSend}; -use crate::Stream; -use futures::{future::BoxFuture, prelude::*, ready}; -use rand::Rng; use std::{ cmp, collections::{HashMap, HashSet}, error, fmt::{self, Debug}, hash::Hash, - iter::{self, FromIterator}, + iter, task::{Context, Poll}, time::Duration, }; +use futures::{future::BoxFuture, prelude::*, ready}; +use rand::Rng; + +use crate::{ + handler::{ + AddressChange, ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, + DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, ListenUpgradeError, + SubstreamProtocol, + }, + upgrade::{InboundUpgradeSend, OutboundUpgradeSend, UpgradeInfoSend}, + Stream, +}; + /// A [`ConnectionHandler`] for multiple [`ConnectionHandler`]s of the same type. #[derive(Clone)] pub struct MultiHandler { @@ -248,7 +253,8 @@ where return Poll::Pending; } - // Not always polling handlers in the same order should give anyone the chance to make progress. + // Not always polling handlers in the same order + // should give anyone the chance to make progress. let pos = rand::thread_rng().gen_range(0..self.handlers.len()); for (k, h) in self.handlers.iter_mut().skip(pos) { @@ -437,7 +443,7 @@ where pub struct DuplicateProtonameError(Vec); impl DuplicateProtonameError { - /// The protocol name bytes that occured in more than one handler. + /// The protocol name bytes that occurred in more than one handler. pub fn protocol_name(&self) -> &[u8] { &self.0 } diff --git a/swarm/src/handler/one_shot.rs b/swarm/src/handler/one_shot.rs index b1fc41e9098..29f2811522b 100644 --- a/swarm/src/handler/one_shot.rs +++ b/swarm/src/handler/one_shot.rs @@ -18,14 +18,23 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::handler::{ - ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, DialUpgradeError, - FullyNegotiatedInbound, FullyNegotiatedOutbound, SubstreamProtocol, +use std::{ + error, + fmt::Debug, + task::{Context, Poll}, + time::Duration, }; -use crate::upgrade::{InboundUpgradeSend, OutboundUpgradeSend}; -use crate::StreamUpgradeError; + use smallvec::SmallVec; -use std::{error, fmt::Debug, task::Context, task::Poll, time::Duration}; + +use crate::{ + handler::{ + ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, DialUpgradeError, + FullyNegotiatedInbound, FullyNegotiatedOutbound, SubstreamProtocol, + }, + upgrade::{InboundUpgradeSend, OutboundUpgradeSend}, + StreamUpgradeError, +}; /// A [`ConnectionHandler`] that opens a new substream for each request. // TODO: Debug @@ -71,7 +80,7 @@ where /// Returns a reference to the listen protocol configuration. /// /// > **Note**: If you modify the protocol, modifications will only applies to future inbound - /// > substreams, not the ones already being negotiated. + /// > substreams, not the ones already being negotiated. pub fn listen_protocol_ref(&self) -> &SubstreamProtocol { &self.listen_protocol } @@ -79,7 +88,7 @@ where /// Returns a mutable reference to the listen protocol configuration. /// /// > **Note**: If you modify the protocol, modifications will only applies to future inbound - /// > substreams, not the ones already being negotiated. + /// > substreams, not the ones already being negotiated. pub fn listen_protocol_mut(&mut self) -> &mut SubstreamProtocol { &mut self.listen_protocol } @@ -212,16 +221,16 @@ impl Default for OneShotHandlerConfig { #[cfg(test)] mod tests { - use super::*; + use std::convert::Infallible; - use futures::executor::block_on; - use futures::future::poll_fn; + use futures::{executor::block_on, future::poll_fn}; use libp2p_core::upgrade::DeniedUpgrade; - use void::Void; + + use super::*; #[test] fn do_not_keep_idle_connection_alive() { - let mut handler: OneShotHandler<_, DeniedUpgrade, Void> = OneShotHandler::new( + let mut handler: OneShotHandler<_, DeniedUpgrade, Infallible> = OneShotHandler::new( SubstreamProtocol::new(DeniedUpgrade {}, ()), Default::default(), ); @@ -232,6 +241,6 @@ mod tests { } })); - assert!(matches!(handler.connection_keep_alive(), false)); + assert!(!handler.connection_keep_alive()); } } diff --git a/swarm/src/handler/pending.rs b/swarm/src/handler/pending.rs index 23b9adcfd90..483c4b3d6e8 100644 --- a/swarm/src/handler/pending.rs +++ b/swarm/src/handler/pending.rs @@ -19,13 +19,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::{ + convert::Infallible, + task::{Context, Poll}, +}; + +use libp2p_core::upgrade::PendingUpgrade; + use crate::handler::{ ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, FullyNegotiatedInbound, FullyNegotiatedOutbound, SubstreamProtocol, }; -use libp2p_core::upgrade::PendingUpgrade; -use std::task::{Context, Poll}; -use void::Void; /// Implementation of [`ConnectionHandler`] that returns a pending upgrade. #[derive(Clone, Debug)] @@ -40,11 +44,11 @@ impl PendingConnectionHandler { } impl ConnectionHandler for PendingConnectionHandler { - type FromBehaviour = Void; - type ToBehaviour = Void; + type FromBehaviour = Infallible; + type ToBehaviour = Infallible; type InboundProtocol = PendingUpgrade; type OutboundProtocol = PendingUpgrade; - type OutboundOpenInfo = Void; + type OutboundOpenInfo = Infallible; type InboundOpenInfo = (); fn listen_protocol(&self) -> SubstreamProtocol { @@ -52,7 +56,9 @@ impl ConnectionHandler for PendingConnectionHandler { } fn on_behaviour_event(&mut self, v: Self::FromBehaviour) { - void::unreachable(v) + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(v) } fn poll( @@ -74,19 +80,25 @@ impl ConnectionHandler for PendingConnectionHandler { >, ) { match event { + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { protocol, .. - }) => void::unreachable(protocol), + }) => libp2p_core::util::unreachable(protocol), + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound { protocol, info: _info, }) => { - void::unreachable(protocol); + libp2p_core::util::unreachable(protocol); #[allow(unreachable_code, clippy::used_underscore_binding)] { - void::unreachable(_info); + libp2p_core::util::unreachable(_info); } } + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] ConnectionEvent::AddressChange(_) | ConnectionEvent::DialUpgradeError(_) | ConnectionEvent::ListenUpgradeError(_) diff --git a/swarm/src/handler/select.rs b/swarm/src/handler/select.rs index e049252d448..0f6dbe988ff 100644 --- a/swarm/src/handler/select.rs +++ b/swarm/src/handler/select.rs @@ -18,16 +18,23 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::handler::{ - AddressChange, ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, DialUpgradeError, - FullyNegotiatedInbound, FullyNegotiatedOutbound, InboundUpgradeSend, ListenUpgradeError, - OutboundUpgradeSend, StreamUpgradeError, SubstreamProtocol, +use std::{ + cmp, + task::{Context, Poll}, }; -use crate::upgrade::SendWrapper; + use either::Either; use futures::{future, ready}; use libp2p_core::upgrade::SelectUpgrade; -use std::{cmp, task::Context, task::Poll}; + +use crate::{ + handler::{ + AddressChange, ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, + DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, InboundUpgradeSend, + ListenUpgradeError, OutboundUpgradeSend, StreamUpgradeError, SubstreamProtocol, + }, + upgrade::SendWrapper, +}; /// Implementation of [`ConnectionHandler`] that combines two protocols into one. #[derive(Debug, Clone)] diff --git a/swarm/src/lib.rs b/swarm/src/lib.rs index 0354f39cfdc..f9c4c71c76f 100644 --- a/swarm/src/lib.rs +++ b/swarm/src/lib.rs @@ -31,12 +31,11 @@ //! Creating a `Swarm` requires three things: //! //! 1. A network identity of the local node in form of a [`PeerId`]. -//! 2. An implementation of the [`Transport`] trait. This is the type that -//! will be used in order to reach nodes on the network based on their -//! address. See the `transport` module for more information. -//! 3. An implementation of the [`NetworkBehaviour`] trait. This is a state -//! machine that defines how the swarm should behave once it is connected -//! to a node. +//! 2. An implementation of the [`Transport`] trait. This is the type that will be used in order to +//! reach nodes on the network based on their address. See the `transport` module for more +//! information. +//! 3. An implementation of the [`NetworkBehaviour`] trait. This is a state machine that defines +//! how the swarm should behave once it is connected to a node. //! //! # Network Behaviour //! @@ -51,7 +50,6 @@ //! The [`ConnectionHandler`] trait defines how each active connection to a //! remote should behave: how to handle incoming substreams, which protocols //! are supported, when to open a new outbound substream, etc. -//! #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] @@ -68,89 +66,78 @@ pub mod dial_opts; pub mod dummy; pub mod handler; mod listen_opts; +mod translation; /// Bundles all symbols required for the [`libp2p_swarm_derive::NetworkBehaviour`] macro. #[doc(hidden)] pub mod derive_prelude { - pub use crate::behaviour::AddressChange; - pub use crate::behaviour::ConnectionClosed; - pub use crate::behaviour::ConnectionEstablished; - pub use crate::behaviour::DialFailure; - pub use crate::behaviour::ExpiredListenAddr; - pub use crate::behaviour::ExternalAddrConfirmed; - pub use crate::behaviour::ExternalAddrExpired; - pub use crate::behaviour::FromSwarm; - pub use crate::behaviour::ListenFailure; - pub use crate::behaviour::ListenerClosed; - pub use crate::behaviour::ListenerError; - pub use crate::behaviour::NewExternalAddrCandidate; - pub use crate::behaviour::NewListenAddr; - pub use crate::behaviour::NewListener; - pub use crate::connection::ConnectionId; - pub use crate::ConnectionDenied; - pub use crate::ConnectionHandler; - pub use crate::ConnectionHandlerSelect; - pub use crate::DialError; - pub use crate::NetworkBehaviour; - pub use crate::THandler; - pub use crate::THandlerInEvent; - pub use crate::THandlerOutEvent; - pub use crate::ToSwarm; pub use either::Either; pub use futures::prelude as futures; - pub use libp2p_core::transport::ListenerId; - pub use libp2p_core::ConnectedPoint; - pub use libp2p_core::Endpoint; - pub use libp2p_core::Multiaddr; + pub use libp2p_core::{ + transport::{ListenerId, PortUse}, + ConnectedPoint, Endpoint, Multiaddr, + }; pub use libp2p_identity::PeerId; + + pub use crate::{ + behaviour::{ + AddressChange, ConnectionClosed, ConnectionEstablished, DialFailure, ExpiredListenAddr, + ExternalAddrConfirmed, ExternalAddrExpired, FromSwarm, ListenFailure, ListenerClosed, + ListenerError, NewExternalAddrCandidate, NewExternalAddrOfPeer, NewListenAddr, + NewListener, + }, + connection::ConnectionId, + ConnectionDenied, ConnectionHandler, ConnectionHandlerSelect, DialError, NetworkBehaviour, + THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, + }; } +use std::{ + collections::{HashMap, HashSet, VecDeque}, + error, fmt, io, + num::{NonZeroU32, NonZeroU8, NonZeroUsize}, + pin::Pin, + task::{Context, Poll}, + time::Duration, +}; + pub use behaviour::{ AddressChange, CloseConnection, ConnectionClosed, DialFailure, ExpiredListenAddr, ExternalAddrExpired, ExternalAddresses, FromSwarm, ListenAddresses, ListenFailure, - ListenerClosed, ListenerError, NetworkBehaviour, NewExternalAddrCandidate, NewListenAddr, - NotifyHandler, ToSwarm, + ListenerClosed, ListenerError, NetworkBehaviour, NewExternalAddrCandidate, + NewExternalAddrOfPeer, NewListenAddr, NotifyHandler, PeerAddresses, ToSwarm, +}; +pub use connection::{pool::ConnectionCounters, ConnectionError, ConnectionId, SupportedProtocols}; +use connection::{ + pool::{EstablishedConnection, Pool, PoolConfig, PoolEvent}, + IncomingInfo, PendingConnectionError, PendingInboundConnectionError, + PendingOutboundConnectionError, }; -pub use connection::pool::ConnectionCounters; -pub use connection::{ConnectionError, ConnectionId, SupportedProtocols}; +use dial_opts::{DialOpts, PeerCondition}; pub use executor::Executor; +use futures::{prelude::*, stream::FusedStream}; pub use handler::{ ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerSelect, OneShotHandler, OneShotHandlerConfig, StreamUpgradeError, SubstreamProtocol, }; -#[cfg(feature = "macros")] -pub use libp2p_swarm_derive::NetworkBehaviour; -pub use listen_opts::ListenOpts; -pub use stream::Stream; -pub use stream_protocol::{InvalidProtocol, StreamProtocol}; - -use crate::behaviour::ExternalAddrConfirmed; -use crate::handler::UpgradeInfoSend; -use connection::pool::{EstablishedConnection, Pool, PoolConfig, PoolEvent}; -use connection::IncomingInfo; -use connection::{ - PendingConnectionError, PendingInboundConnectionError, PendingOutboundConnectionError, -}; -use dial_opts::{DialOpts, PeerCondition}; -use futures::{prelude::*, stream::FusedStream}; use libp2p_core::{ connection::ConnectedPoint, muxing::StreamMuxerBox, transport::{self, ListenerId, TransportError, TransportEvent}, - Endpoint, Multiaddr, Transport, + Multiaddr, Transport, }; use libp2p_identity::PeerId; +#[cfg(feature = "macros")] +pub use libp2p_swarm_derive::NetworkBehaviour; +pub use listen_opts::ListenOpts; use smallvec::SmallVec; -use std::collections::{HashMap, HashSet, VecDeque}; -use std::num::{NonZeroU32, NonZeroU8, NonZeroUsize}; -use std::time::Duration; -use std::{ - convert::TryFrom, - error, fmt, io, - pin::Pin, - task::{Context, Poll}, -}; +pub use stream::Stream; +pub use stream_protocol::{InvalidProtocol, StreamProtocol}; use tracing::Instrument; +#[doc(hidden)] +pub use translation::_address_translation; + +use crate::{behaviour::ExternalAddrConfirmed, handler::UpgradeInfoSend}; /// Event generated by the [`NetworkBehaviour`] that the swarm will report back. type TBehaviourOutEvent = ::ToSwarm; @@ -214,8 +201,8 @@ pub enum SwarmEvent { /// Identifier of the connection. connection_id: ConnectionId, /// Local connection address. - /// This address has been earlier reported with a [`NewListenAddr`](SwarmEvent::NewListenAddr) - /// event. + /// This address has been earlier reported with a + /// [`NewListenAddr`](SwarmEvent::NewListenAddr) event. local_addr: Multiaddr, /// Address used to send back data to the remote. send_back_addr: Multiaddr, @@ -228,8 +215,8 @@ pub enum SwarmEvent { /// Identifier of the connection. connection_id: ConnectionId, /// Local connection address. - /// This address has been earlier reported with a [`NewListenAddr`](SwarmEvent::NewListenAddr) - /// event. + /// This address has been earlier reported with a + /// [`NewListenAddr`](SwarmEvent::NewListenAddr) event. local_addr: Multiaddr, /// Address used to send back data to the remote. send_back_addr: Multiaddr, @@ -298,10 +285,13 @@ pub enum SwarmEvent { ExternalAddrConfirmed { address: Multiaddr }, /// An external address of the local node expired, i.e. is no-longer confirmed. ExternalAddrExpired { address: Multiaddr }, + /// We have discovered a new address of a peer. + NewExternalAddrOfPeer { peer_id: PeerId, address: Multiaddr }, } impl SwarmEvent { - /// Extract the `TBehaviourOutEvent` from this [`SwarmEvent`] in case it is the `Behaviour` variant, otherwise fail. + /// Extract the `TBehaviourOutEvent` from this [`SwarmEvent`] in case it is the `Behaviour` + /// variant, otherwise fail. #[allow(clippy::result_large_err)] pub fn try_into_behaviour_event(self) -> Result { match self { @@ -526,18 +516,15 @@ where .into_iter() .map(|a| match peer_id.map_or(Ok(a.clone()), |p| a.with_p2p(p)) { Ok(address) => { - let (dial, span) = match dial_opts.role_override() { - Endpoint::Dialer => ( - self.transport.dial(address.clone()), - tracing::debug_span!(parent: tracing::Span::none(), "Transport::dial", %address), - ), - Endpoint::Listener => ( - self.transport.dial_as_listener(address.clone()), - tracing::debug_span!(parent: tracing::Span::none(), "Transport::dial_as_listener", %address), - ), - }; + let dial = self.transport.dial( + address.clone(), + transport::DialOpts { + role: dial_opts.role_override(), + port_use: dial_opts.port_use(), + }, + ); + let span = tracing::debug_span!(parent: tracing::Span::none(), "Transport::dial", %address); span.follows_from(tracing::Span::current()); - match dial { Ok(fut) => fut .map(|r| (address, r.map_err(TransportError::Other))) @@ -558,6 +545,7 @@ where dials, peer_id, dial_opts.role_override(), + dial_opts.port_use(), dial_opts.dial_concurrency_override(), connection_id, ); @@ -605,7 +593,8 @@ where /// Add a **confirmed** external address for the local node. /// /// This function should only be called with addresses that are guaranteed to be reachable. - /// The address is broadcast to all [`NetworkBehaviour`]s via [`FromSwarm::ExternalAddrConfirmed`]. + /// The address is broadcast to all [`NetworkBehaviour`]s via + /// [`FromSwarm::ExternalAddrConfirmed`]. pub fn add_external_address(&mut self, a: Multiaddr) { self.behaviour .on_swarm_event(FromSwarm::ExternalAddrConfirmed(ExternalAddrConfirmed { @@ -616,19 +605,33 @@ where /// Remove an external address for the local node. /// - /// The address is broadcast to all [`NetworkBehaviour`]s via [`FromSwarm::ExternalAddrExpired`]. + /// The address is broadcast to all [`NetworkBehaviour`]s via + /// [`FromSwarm::ExternalAddrExpired`]. pub fn remove_external_address(&mut self, addr: &Multiaddr) { self.behaviour .on_swarm_event(FromSwarm::ExternalAddrExpired(ExternalAddrExpired { addr })); self.confirmed_external_addr.remove(addr); } + /// Add a new external address of a remote peer. + /// + /// The address is broadcast to all [`NetworkBehaviour`]s via + /// [`FromSwarm::NewExternalAddrOfPeer`]. + pub fn add_peer_address(&mut self, peer_id: PeerId, addr: Multiaddr) { + self.behaviour + .on_swarm_event(FromSwarm::NewExternalAddrOfPeer(NewExternalAddrOfPeer { + peer_id, + addr: &addr, + })) + } + /// Disconnects a peer by its peer ID, closing all connections to said peer. /// /// Returns `Ok(())` if there was one or more established connections to the peer. /// - /// Closing a connection via [`Swarm::disconnect_peer_id`] will poll [`ConnectionHandler::poll_close`] to completion. - /// Use this function if you want to close a connection _despite_ it still being in use by one or more handlers. + /// Closing a connection via [`Swarm::disconnect_peer_id`] will poll + /// [`ConnectionHandler::poll_close`] to completion. Use this function if you want to close + /// a connection _despite_ it still being in use by one or more handlers. #[allow(clippy::result_unit_err)] pub fn disconnect_peer_id(&mut self, peer_id: PeerId) -> Result<(), ()> { let was_connected = self.pool.is_connected(peer_id); @@ -644,7 +647,8 @@ where /// Attempt to gracefully close a connection. /// /// Closing a connection is asynchronous but this function will return immediately. - /// A [`SwarmEvent::ConnectionClosed`] event will be emitted once the connection is actually closed. + /// A [`SwarmEvent::ConnectionClosed`] event will be emitted + /// once the connection is actually closed. /// /// # Returns /// @@ -693,12 +697,14 @@ where ConnectedPoint::Dialer { address, role_override, + port_use, } => { match self.behaviour.handle_established_outbound_connection( id, peer_id, &address, role_override, + port_use, ) { Ok(handler) => handler, Err(cause) => { @@ -741,6 +747,7 @@ where send_back_addr: &send_back_addr, error: &listen_error, connection_id: id, + peer_id: Some(peer_id), }, )); @@ -854,6 +861,7 @@ where send_back_addr: &send_back_addr, error: &error, connection_id: id, + peer_id: None, })); self.pending_swarm_events .push_back(SwarmEvent::IncomingConnectionError { @@ -894,6 +902,7 @@ where peer_id, connection_id: id, endpoint: &endpoint, + cause: error.as_ref(), remaining_established: num_established as usize, })); self.pending_swarm_events @@ -957,6 +966,7 @@ where send_back_addr: &send_back_addr, error: &listen_error, connection_id, + peer_id: None, })); self.pending_swarm_events @@ -1118,39 +1128,13 @@ where self.pending_handler_event = Some((peer_id, handler, event)); } ToSwarm::NewExternalAddrCandidate(addr) => { - // Apply address translation to the candidate address. - // For TCP without port-reuse, the observed address contains an ephemeral port which needs to be replaced by the port of a listen address. - let translated_addresses = { - let mut addrs: Vec<_> = self - .listened_addrs - .values() - .flatten() - .filter_map(|server| self.transport.address_translation(server, &addr)) - .collect(); - - // remove duplicates - addrs.sort_unstable(); - addrs.dedup(); - addrs - }; - - // If address translation yielded nothing, broacast the original candidate address. - if translated_addresses.is_empty() { + if !self.confirmed_external_addr.contains(&addr) { self.behaviour .on_swarm_event(FromSwarm::NewExternalAddrCandidate( NewExternalAddrCandidate { addr: &addr }, )); self.pending_swarm_events .push_back(SwarmEvent::NewExternalAddrCandidate { address: addr }); - } else { - for addr in translated_addresses { - self.behaviour - .on_swarm_event(FromSwarm::NewExternalAddrCandidate( - NewExternalAddrCandidate { addr: &addr }, - )); - self.pending_swarm_events - .push_back(SwarmEvent::NewExternalAddrCandidate { address: addr }); - } } } ToSwarm::ExternalAddrConfirmed(addr) => { @@ -1176,6 +1160,15 @@ where self.pool.disconnect(peer_id); } }, + ToSwarm::NewExternalAddrOfPeer { peer_id, address } => { + self.behaviour + .on_swarm_event(FromSwarm::NewExternalAddrOfPeer(NewExternalAddrOfPeer { + peer_id, + addr: &address, + })); + self.pending_swarm_events + .push_back(SwarmEvent::NewExternalAddrOfPeer { peer_id, address }); + } } } @@ -1199,15 +1192,16 @@ where // // (1) is polled before (2) to prioritize local work over work coming from a remote. // - // (2) is polled before (3) to prioritize existing connections over upgrading new incoming connections. + // (2) is polled before (3) to prioritize existing connections + // over upgrading new incoming connections. loop { if let Some(swarm_event) = this.pending_swarm_events.pop_front() { return Poll::Ready(swarm_event); } match this.pending_handler_event.take() { - // Try to deliver the pending event emitted by the [`NetworkBehaviour`] in the previous - // iteration to the connection handler(s). + // Try to deliver the pending event emitted by the [`NetworkBehaviour`] in the + // previous iteration to the connection handler(s). Some((peer_id, handler, event)) => match handler { PendingNotifyHandler::One(conn_id) => { match this.pool.get_established(conn_id) { @@ -1391,6 +1385,14 @@ impl Config { } } + #[doc(hidden)] + /// Used on connection benchmarks. + pub fn without_executor() -> Self { + Self { + pool_config: PoolConfig::new(None), + } + } + /// Sets executor to the `wasm` executor. /// Background tasks will be executed by the browser on the next micro-tick. /// @@ -1493,7 +1495,21 @@ impl Config { /// How long to keep a connection alive once it is idling. /// - /// Defaults to 0. + /// Defaults to 10s. + /// + /// Typically, you shouldn't _need_ to modify this default as connections will be kept alive + /// whilst they are "in use" (see below). Depending on the application's usecase, it may be + /// desirable to keep connections alive despite them not being in use. + /// + /// A connection is considered idle if: + /// - There are no active inbound streams. + /// - There are no active outbounds streams. + /// - There are no pending outbound streams (i.e. all streams requested via + /// [`ConnectionHandlerEvent::OutboundSubstreamRequest`] have completed). + /// - Every [`ConnectionHandler`] returns `false` from + /// [`ConnectionHandler::connection_keep_alive`]. + /// + /// Once all these conditions are true, the idle connection timeout starts ticking. pub fn with_idle_connection_timeout(mut self, timeout: Duration) -> Self { self.pool_config.idle_connection_timeout = timeout; self @@ -1504,10 +1520,9 @@ impl Config { #[derive(Debug)] pub enum DialError { /// The peer identity obtained on the connection matches the local peer. - LocalPeerId { - endpoint: ConnectedPoint, - }, - /// No addresses have been provided by [`NetworkBehaviour::handle_pending_outbound_connection`] and [`DialOpts`]. + LocalPeerId { endpoint: ConnectedPoint }, + /// No addresses have been provided by [`NetworkBehaviour::handle_pending_outbound_connection`] + /// and [`DialOpts`]. NoAddresses, /// The provided [`dial_opts::PeerCondition`] evaluated to false and thus /// the dial was aborted. @@ -1519,9 +1534,10 @@ pub enum DialError { obtained: PeerId, endpoint: ConnectedPoint, }, - Denied { - cause: ConnectionDenied, - }, + /// One of the [`NetworkBehaviour`]s rejected the outbound connection + /// via [`NetworkBehaviour::handle_pending_outbound_connection`] or + /// [`NetworkBehaviour::handle_established_outbound_connection`]. + Denied { cause: ConnectionDenied }, /// An error occurred while negotiating the transport protocol(s) on a connection. Transport(Vec<(Multiaddr, TransportError)>), } @@ -1676,7 +1692,8 @@ impl error::Error for ListenError { /// A connection was denied. /// -/// To figure out which [`NetworkBehaviour`] denied the connection, use [`ConnectionDenied::downcast`]. +/// To figure out which [`NetworkBehaviour`] denied the connection, use +/// [`ConnectionDenied::downcast`]. #[derive(Debug)] pub struct ConnectionDenied { inner: Box, @@ -1747,20 +1764,21 @@ impl NetworkInfo { #[cfg(test)] mod tests { - use super::*; - use crate::dummy; - use crate::test::{CallTraceBehaviour, MockBehaviour}; - use futures::future; - use libp2p_core::multiaddr::multiaddr; - use libp2p_core::transport::memory::MemoryTransportError; - use libp2p_core::transport::TransportEvent; - use libp2p_core::Endpoint; - use libp2p_core::{multiaddr, transport, upgrade}; + use libp2p_core::{ + multiaddr, + multiaddr::multiaddr, + transport, + transport::{memory::MemoryTransportError, PortUse, TransportEvent}, + upgrade, Endpoint, + }; use libp2p_identity as identity; use libp2p_plaintext as plaintext; use libp2p_yamux as yamux; use quickcheck::*; + use super::*; + use crate::test::{CallTraceBehaviour, MockBehaviour}; + // Test execution state. // Connection => Disconnecting => Connecting. enum State { @@ -1780,12 +1798,7 @@ mod tests { .boxed(); let behaviour = CallTraceBehaviour::new(MockBehaviour::new(dummy::ConnectionHandler)); - Swarm::new( - transport, - behaviour, - local_public_key.into(), - config.with_idle_connection_timeout(Duration::from_secs(5)), - ) + Swarm::new(transport, behaviour, local_public_key.into(), config) } fn swarms_connected( @@ -1809,7 +1822,7 @@ mod tests { && swarm2.is_connected(swarm1.local_peer_id()) } - fn swarms_disconnected( + fn swarms_disconnected( swarm1: &Swarm>, swarm2: &Swarm>, ) -> bool @@ -1832,8 +1845,9 @@ mod tests { /// Establishes multiple connections between two peers, /// after which one peer disconnects the other using [`Swarm::disconnect_peer_id`]. /// - /// The test expects both behaviours to be notified via calls to [`NetworkBehaviour::on_swarm_event`] - /// with pairs of [`FromSwarm::ConnectionEstablished`] / [`FromSwarm::ConnectionClosed`] + /// The test expects both behaviours to be notified via calls to + /// [`NetworkBehaviour::on_swarm_event`] with pairs of [`FromSwarm::ConnectionEstablished`] + /// / [`FromSwarm::ConnectionClosed`] #[tokio::test] async fn test_swarm_disconnect() { let mut swarm1 = new_test_swarm(Config::with_tokio_executor()); @@ -1895,8 +1909,9 @@ mod tests { /// after which one peer disconnects the other /// using [`ToSwarm::CloseConnection`] returned by a [`NetworkBehaviour`]. /// - /// The test expects both behaviours to be notified via calls to [`NetworkBehaviour::on_swarm_event`] - /// with pairs of [`FromSwarm::ConnectionEstablished`] / [`FromSwarm::ConnectionClosed`] + /// The test expects both behaviours to be notified via calls to + /// [`NetworkBehaviour::on_swarm_event`] with pairs of [`FromSwarm::ConnectionEstablished`] + /// / [`FromSwarm::ConnectionClosed`] #[tokio::test] async fn test_behaviour_disconnect_all() { let mut swarm1 = new_test_swarm(Config::with_tokio_executor()); @@ -1962,8 +1977,9 @@ mod tests { /// after which one peer closes a single connection /// using [`ToSwarm::CloseConnection`] returned by a [`NetworkBehaviour`]. /// - /// The test expects both behaviours to be notified via calls to [`NetworkBehaviour::on_swarm_event`] - /// with pairs of [`FromSwarm::ConnectionEstablished`] / [`FromSwarm::ConnectionClosed`] + /// The test expects both behaviours to be notified via calls to + /// [`NetworkBehaviour::on_swarm_event`] with pairs of [`FromSwarm::ConnectionEstablished`] + /// / [`FromSwarm::ConnectionClosed`] #[tokio::test] async fn test_behaviour_disconnect_one() { let mut swarm1 = new_test_swarm(Config::with_tokio_executor()); @@ -2150,6 +2166,7 @@ mod tests { ConnectedPoint::Dialer { address: other_addr, role_override: Endpoint::Dialer, + port_use: PortUse::Reuse, } ); } @@ -2164,8 +2181,10 @@ mod tests { // Dialing the same address we're listening should result in three events: // // - The incoming connection notification (before we know the incoming peer ID). - // - The connection error for the dialing endpoint (once we've determined that it's our own ID). - // - The connection error for the listening endpoint (once we've determined that it's our own ID). + // - The connection error for the dialing endpoint (once we've determined that it's our own + // ID). + // - The connection error for the listening endpoint (once we've determined that it's our + // own ID). // // The last two can happen in any order. @@ -2179,8 +2198,9 @@ mod tests { }) .await; - swarm.listened_addrs.clear(); // This is a hack to actually execute the dial to ourselves which would otherwise be filtered. - + // This is a hack to actually execute the dial + // to ourselves which would otherwise be filtered. + swarm.listened_addrs.clear(); swarm.dial(local_address.clone()).unwrap(); let mut got_dial_err = false; @@ -2283,9 +2303,7 @@ mod tests { #[tokio::test] async fn aborting_pending_connection_surfaces_error() { - let _ = tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let mut dialer = new_test_swarm(Config::with_tokio_executor()); let mut listener = new_test_swarm(Config::with_tokio_executor()); @@ -2331,7 +2349,8 @@ mod tests { let string = format!("{error}"); - // Unfortunately, we have some "empty" errors that lead to multiple colons without text but that is the best we can do. + // Unfortunately, we have some "empty" errors + // that lead to multiple colons without text but that is the best we can do. assert_eq!("Failed to negotiate transport protocol(s): [(/ip4/127.0.0.1/tcp/80: : No listener on the given port.)]", string) } } diff --git a/swarm/src/listen_opts.rs b/swarm/src/listen_opts.rs index 9c4d69a6fa0..1fcb33cd348 100644 --- a/swarm/src/listen_opts.rs +++ b/swarm/src/listen_opts.rs @@ -1,6 +1,7 @@ -use crate::ListenerId; use libp2p_core::Multiaddr; +use crate::ListenerId; + #[derive(Debug)] pub struct ListenOpts { id: ListenerId, diff --git a/swarm/src/stream.rs b/swarm/src/stream.rs index 871352f3c6a..d3936cb557a 100644 --- a/swarm/src/stream.rs +++ b/swarm/src/stream.rs @@ -1,6 +1,3 @@ -use futures::{AsyncRead, AsyncWrite}; -use libp2p_core::muxing::SubstreamBox; -use libp2p_core::Negotiated; use std::{ io::{IoSlice, IoSliceMut}, pin::Pin, @@ -8,6 +5,9 @@ use std::{ task::{Context, Poll}, }; +use futures::{AsyncRead, AsyncWrite}; +use libp2p_core::{muxing::SubstreamBox, Negotiated}; + /// Counter for the number of active streams on a connection. #[derive(Debug, Clone)] pub(crate) struct ActiveStreamCounter(Arc<()>); diff --git a/swarm/src/stream_protocol.rs b/swarm/src/stream_protocol.rs index f746429a3d7..abf8068238e 100644 --- a/swarm/src/stream_protocol.rs +++ b/swarm/src/stream_protocol.rs @@ -1,7 +1,10 @@ +use std::{ + fmt, + hash::{Hash, Hasher}, + sync::Arc, +}; + use either::Either; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::sync::Arc; /// Identifies a protocol for a stream. /// @@ -39,7 +42,9 @@ impl StreamProtocol { } Ok(StreamProtocol { - inner: Either::Right(Arc::from(protocol)), // FIXME: Can we somehow reuse the allocation from the owned string? + // FIXME: Can we somehow reuse the + // allocation from the owned string? + inner: Either::Right(Arc::from(protocol)), }) } } diff --git a/swarm/src/test.rs b/swarm/src/test.rs index 4f6adfc37b0..59aadf7e3c7 100644 --- a/swarm/src/test.rs +++ b/swarm/src/test.rs @@ -18,18 +18,27 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::behaviour::{ - ConnectionClosed, ConnectionEstablished, DialFailure, ExpiredListenAddr, ExternalAddrExpired, - FromSwarm, ListenerClosed, ListenerError, NewExternalAddrCandidate, NewListenAddr, NewListener, +use std::{ + collections::HashMap, + task::{Context, Poll}, }; + +use libp2p_core::{ + multiaddr::Multiaddr, + transport::{ListenerId, PortUse}, + ConnectedPoint, Endpoint, +}; +use libp2p_identity::PeerId; + use crate::{ + behaviour::{ + ConnectionClosed, ConnectionEstablished, DialFailure, ExpiredListenAddr, + ExternalAddrExpired, FromSwarm, ListenerClosed, ListenerError, NewExternalAddrCandidate, + NewListenAddr, NewListener, + }, ConnectionDenied, ConnectionHandler, ConnectionId, NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; -use libp2p_core::{multiaddr::Multiaddr, transport::ListenerId, ConnectedPoint, Endpoint}; -use libp2p_identity::PeerId; -use std::collections::HashMap; -use std::task::{Context, Poll}; /// A `MockBehaviour` is a `NetworkBehaviour` that allows for /// the instrumentation of return values, without keeping @@ -41,7 +50,8 @@ where TOutEvent: Send + 'static, { /// The prototype protocols handler that is cloned for every - /// invocation of [`NetworkBehaviour::handle_established_inbound_connection`] and [`NetworkBehaviour::handle_established_outbound_connection`] + /// invocation of [`NetworkBehaviour::handle_established_inbound_connection`] and + /// [`NetworkBehaviour::handle_established_outbound_connection`] pub(crate) handler_proto: THandler, /// The addresses to return from [`NetworkBehaviour::handle_established_outbound_connection`]. pub(crate) addresses: HashMap>, @@ -91,6 +101,7 @@ where _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result { Ok(self.handler_proto.clone()) } @@ -264,8 +275,8 @@ where }) .take(other_established); - // We are informed that there are `other_established` additional connections. Ensure that the - // number of previous connections is consistent with this + // We are informed that there are `other_established` additional connections. Ensure that + // the number of previous connections is consistent with this if let Some(&prev) = other_peer_connections.next() { if prev < other_established { assert_eq!( @@ -301,6 +312,7 @@ where connection_id, endpoint, remaining_established, + cause, }: ConnectionClosed, ) { let mut other_closed_connections = self @@ -316,8 +328,8 @@ where }) .take(remaining_established); - // We are informed that there are `other_established` additional connections. Ensure that the - // number of previous connections is consistent with this + // We are informed that there are `other_established` additional connections. Ensure that + // the number of previous connections is consistent with this if let Some(&prev) = other_closed_connections.next() { if prev < remaining_established { assert_eq!( @@ -350,6 +362,7 @@ where connection_id, endpoint, remaining_established, + cause, })); } } @@ -425,6 +438,7 @@ where peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { self.handle_established_outbound_connection.push(( peer, @@ -432,11 +446,18 @@ where role_override, connection_id, )); - self.inner - .handle_established_outbound_connection(connection_id, peer, addr, role_override) + self.inner.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) } fn on_swarm_event(&mut self, event: FromSwarm) { + self.inner.on_swarm_event(event); + match event { FromSwarm::ConnectionEstablished(connection_established) => { self.on_connection_established(connection_established) @@ -444,68 +465,33 @@ where FromSwarm::ConnectionClosed(connection_closed) => { self.on_connection_closed(connection_closed) } - FromSwarm::DialFailure(DialFailure { - peer_id, - connection_id, - error, - }) => { + FromSwarm::DialFailure(DialFailure { peer_id, .. }) => { self.on_dial_failure.push(peer_id); - self.inner - .on_swarm_event(FromSwarm::DialFailure(DialFailure { - peer_id, - connection_id, - error, - })); } FromSwarm::NewListener(NewListener { listener_id }) => { self.on_new_listener.push(listener_id); - self.inner - .on_swarm_event(FromSwarm::NewListener(NewListener { listener_id })); } FromSwarm::NewListenAddr(NewListenAddr { listener_id, addr }) => { self.on_new_listen_addr.push((listener_id, addr.clone())); - self.inner - .on_swarm_event(FromSwarm::NewListenAddr(NewListenAddr { - listener_id, - addr, - })); } FromSwarm::ExpiredListenAddr(ExpiredListenAddr { listener_id, addr }) => { self.on_expired_listen_addr .push((listener_id, addr.clone())); - self.inner - .on_swarm_event(FromSwarm::ExpiredListenAddr(ExpiredListenAddr { - listener_id, - addr, - })); } FromSwarm::NewExternalAddrCandidate(NewExternalAddrCandidate { addr }) => { self.on_new_external_addr.push(addr.clone()); - self.inner - .on_swarm_event(FromSwarm::NewExternalAddrCandidate( - NewExternalAddrCandidate { addr }, - )); } FromSwarm::ExternalAddrExpired(ExternalAddrExpired { addr }) => { self.on_expired_external_addr.push(addr.clone()); - self.inner - .on_swarm_event(FromSwarm::ExternalAddrExpired(ExternalAddrExpired { addr })); } - FromSwarm::ListenerError(ListenerError { listener_id, err }) => { + FromSwarm::ListenerError(ListenerError { listener_id, .. }) => { self.on_listener_error.push(listener_id); - self.inner - .on_swarm_event(FromSwarm::ListenerError(ListenerError { listener_id, err })); } FromSwarm::ListenerClosed(ListenerClosed { listener_id, reason, }) => { self.on_listener_closed.push((listener_id, reason.is_ok())); - self.inner - .on_swarm_event(FromSwarm::ListenerClosed(ListenerClosed { - listener_id, - reason, - })); } _ => {} } diff --git a/core/src/translation.rs b/swarm/src/translation.rs similarity index 94% rename from core/src/translation.rs rename to swarm/src/translation.rs index 70700ca6ae8..baa80c907b5 100644 --- a/core/src/translation.rs +++ b/swarm/src/translation.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use multiaddr::{Multiaddr, Protocol}; +use libp2p_core::{multiaddr::Protocol, Multiaddr}; /// Perform IP address translation. /// @@ -35,7 +35,8 @@ use multiaddr::{Multiaddr, Protocol}; /// address and vice versa. /// /// If the first [`Protocol`]s are not IP addresses, `None` is returned instead. -pub fn address_translation(original: &Multiaddr, observed: &Multiaddr) -> Option { +#[doc(hidden)] +pub fn _address_translation(original: &Multiaddr, observed: &Multiaddr) -> Option { original.replace(0, move |proto| match proto { Protocol::Ip4(_) | Protocol::Ip6(_) @@ -65,7 +66,7 @@ mod tests { expected: Multiaddr, } - let tests = vec![ + let tests = [ // Basic ipv4. Test { original: "/ip4/192.0.2.1/tcp/1".parse().unwrap(), @@ -106,7 +107,7 @@ mod tests { for test in tests.iter() { assert_eq!( - address_translation(&test.original, &test.observed), + _address_translation(&test.original, &test.observed), Some(test.expected.clone()) ); } diff --git a/swarm/src/upgrade.rs b/swarm/src/upgrade.rs index 53b627458c9..ba40e5606bb 100644 --- a/swarm/src/upgrade.rs +++ b/swarm/src/upgrade.rs @@ -18,11 +18,11 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::Stream; - use futures::prelude::*; use libp2p_core::upgrade; +use crate::Stream; + /// Implemented automatically on all types that implement [`UpgradeInfo`](upgrade::UpgradeInfo) /// and `Send + 'static`. /// @@ -65,7 +65,8 @@ pub trait OutboundUpgradeSend: UpgradeInfoSend { /// Equivalent to [`OutboundUpgrade::Future`](upgrade::OutboundUpgrade::Future). type Future: Future> + Send + 'static; - /// Equivalent to [`OutboundUpgrade::upgrade_outbound`](upgrade::OutboundUpgrade::upgrade_outbound). + /// Equivalent to + /// [`OutboundUpgrade::upgrade_outbound`](upgrade::OutboundUpgrade::upgrade_outbound). fn upgrade_outbound(self, socket: Stream, info: Self::Info) -> Self::Future; } @@ -121,11 +122,12 @@ where } /// Wraps around a type that implements [`OutboundUpgradeSend`], [`InboundUpgradeSend`], or +/// /// both, and implements [`OutboundUpgrade`](upgrade::OutboundUpgrade) and/or /// [`InboundUpgrade`](upgrade::InboundUpgrade). /// /// > **Note**: This struct is mostly an implementation detail of the library and normally -/// > doesn't need to be used directly. +/// > doesn't need to be used directly. pub struct SendWrapper(pub T); impl upgrade::UpgradeInfo for SendWrapper { diff --git a/swarm/tests/connection_close.rs b/swarm/tests/connection_close.rs index 4efe8d17e49..bc71216870a 100644 --- a/swarm/tests/connection_close.rs +++ b/swarm/tests/connection_close.rs @@ -1,15 +1,16 @@ -use libp2p_core::upgrade::DeniedUpgrade; -use libp2p_core::{Endpoint, Multiaddr}; +use std::{ + convert::Infallible, + task::{Context, Poll}, +}; + +use libp2p_core::{transport::PortUse, upgrade::DeniedUpgrade, Endpoint, Multiaddr}; use libp2p_identity::PeerId; -use libp2p_swarm::handler::ConnectionEvent; use libp2p_swarm::{ - ConnectionDenied, ConnectionHandler, ConnectionHandlerEvent, ConnectionId, FromSwarm, - NetworkBehaviour, SubstreamProtocol, Swarm, SwarmEvent, THandler, THandlerInEvent, - THandlerOutEvent, ToSwarm, + handler::ConnectionEvent, ConnectionDenied, ConnectionHandler, ConnectionHandlerEvent, + ConnectionId, FromSwarm, NetworkBehaviour, SubstreamProtocol, Swarm, SwarmEvent, THandler, + THandlerInEvent, THandlerOutEvent, ToSwarm, }; use libp2p_swarm_test::SwarmExt; -use std::task::{Context, Poll}; -use void::Void; #[async_std::test] async fn sends_remaining_events_to_behaviour_on_connection_close() { @@ -66,6 +67,7 @@ impl NetworkBehaviour for Behaviour { _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(HandlerWithState { precious_state: self.state, @@ -94,7 +96,7 @@ impl NetworkBehaviour for Behaviour { } impl ConnectionHandler for HandlerWithState { - type FromBehaviour = Void; + type FromBehaviour = Infallible; type ToBehaviour = u64; type InboundProtocol = DeniedUpgrade; type OutboundProtocol = DeniedUpgrade; @@ -130,7 +132,7 @@ impl ConnectionHandler for HandlerWithState { } fn on_behaviour_event(&mut self, event: Self::FromBehaviour) { - void::unreachable(event) + libp2p_core::util::unreachable(event) } fn on_connection_event( diff --git a/swarm/tests/listener.rs b/swarm/tests/listener.rs index 8d22acc90e2..01d5784cfa5 100644 --- a/swarm/tests/listener.rs +++ b/swarm/tests/listener.rs @@ -1,16 +1,20 @@ use std::{ collections::{HashSet, VecDeque}, + convert::Infallible, task::{Context, Poll}, }; -use libp2p_core::{multiaddr::Protocol, transport::ListenerId, Endpoint, Multiaddr}; +use libp2p_core::{ + multiaddr::Protocol, + transport::{ListenerId, PortUse}, + Endpoint, Multiaddr, +}; use libp2p_identity::PeerId; use libp2p_swarm::{ derive_prelude::NewListener, dummy, ConnectionDenied, ConnectionId, FromSwarm, ListenOpts, ListenerClosed, ListenerError, NetworkBehaviour, NewListenAddr, Swarm, SwarmEvent, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }; - use libp2p_swarm_test::SwarmExt; #[async_std::test] @@ -75,7 +79,7 @@ impl Behaviour { impl NetworkBehaviour for Behaviour { type ConnectionHandler = dummy::ConnectionHandler; - type ToSwarm = void::Void; + type ToSwarm = Infallible; fn handle_established_inbound_connection( &mut self, @@ -93,6 +97,7 @@ impl NetworkBehaviour for Behaviour { _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(dummy::ConnectionHandler) } diff --git a/swarm/tests/swarm_derive.rs b/swarm/tests/swarm_derive.rs index 3337cb7b137..a1c8bc5ff73 100644 --- a/swarm/tests/swarm_derive.rs +++ b/swarm/tests/swarm_derive.rs @@ -18,27 +18,28 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::fmt::Debug; + use futures::StreamExt; -use libp2p_core::{Endpoint, Multiaddr}; +use libp2p_core::{transport::PortUse, Endpoint, Multiaddr}; use libp2p_identify as identify; use libp2p_ping as ping; use libp2p_swarm::{ behaviour::FromSwarm, dummy, ConnectionDenied, NetworkBehaviour, SwarmEvent, THandler, THandlerInEvent, THandlerOutEvent, }; -use std::fmt::Debug; /// Small utility to check that a type implements `NetworkBehaviour`. #[allow(dead_code)] fn require_net_behaviour() {} // TODO: doesn't compile -/*#[test] -fn empty() { - #[allow(dead_code)] - #[derive(NetworkBehaviour)] - struct Foo {} -}*/ +// #[test] +// fn empty() { +// #[allow(dead_code)] +// #[derive(NetworkBehaviour)] +// struct Foo {} +// } #[test] fn one_field() { @@ -133,19 +134,19 @@ fn custom_event() { #[allow(clippy::large_enum_variant)] enum MyEvent { - Ping(ping::Event), - Identify(identify::Event), + Ping, + Identify, } impl From for MyEvent { - fn from(event: ping::Event) -> Self { - MyEvent::Ping(event) + fn from(_event: ping::Event) -> Self { + MyEvent::Ping } } impl From for MyEvent { - fn from(event: identify::Event) -> Self { - MyEvent::Identify(event) + fn from(_event: identify::Event) -> Self { + MyEvent::Identify } } @@ -167,19 +168,19 @@ fn custom_event_mismatching_field_names() { #[allow(clippy::large_enum_variant)] enum MyEvent { - Ping(ping::Event), - Identify(identify::Event), + Ping, + Identify, } impl From for MyEvent { - fn from(event: ping::Event) -> Self { - MyEvent::Ping(event) + fn from(_event: ping::Event) -> Self { + MyEvent::Ping } } impl From for MyEvent { - fn from(event: identify::Event) -> Self { - MyEvent::Identify(event) + fn from(_event: identify::Event) -> Self { + MyEvent::Identify } } @@ -252,19 +253,19 @@ fn nested_derives_with_import() { fn custom_event_emit_event_through_poll() { #[allow(clippy::large_enum_variant)] enum BehaviourOutEvent { - Ping(ping::Event), - Identify(identify::Event), + Ping, + Identify, } impl From for BehaviourOutEvent { - fn from(event: ping::Event) -> Self { - BehaviourOutEvent::Ping(event) + fn from(_event: ping::Event) -> Self { + BehaviourOutEvent::Ping } } impl From for BehaviourOutEvent { - fn from(event: identify::Event) -> Self { - BehaviourOutEvent::Identify(event) + fn from(_event: identify::Event) -> Self { + BehaviourOutEvent::Identify } } @@ -293,8 +294,8 @@ fn custom_event_emit_event_through_poll() { // check that the event is bubbled up all the way to swarm loop { match _swarm.select_next_some().await { - SwarmEvent::Behaviour(BehaviourOutEvent::Ping(_)) => break, - SwarmEvent::Behaviour(BehaviourOutEvent::Identify(_)) => break, + SwarmEvent::Behaviour(BehaviourOutEvent::Ping) => break, + SwarmEvent::Behaviour(BehaviourOutEvent::Identify) => break, _ => {} } } @@ -371,25 +372,110 @@ fn with_generics_mixed() { } } +#[test] +fn with_generics_constrained() { + use std::task::{Context, Poll}; + trait Mark {} + struct Marked; + impl Mark for Marked {} + + /// A struct with a generic constraint, for which we manually implement `NetworkBehaviour`. + #[allow(dead_code)] + struct Bar { + a: A, + } + + impl NetworkBehaviour for Bar { + type ConnectionHandler = dummy::ConnectionHandler; + type ToSwarm = std::convert::Infallible; + + fn handle_established_inbound_connection( + &mut self, + _: libp2p_swarm::ConnectionId, + _: libp2p_identity::PeerId, + _: &Multiaddr, + _: &Multiaddr, + ) -> Result, ConnectionDenied> { + Ok(dummy::ConnectionHandler) + } + + fn handle_established_outbound_connection( + &mut self, + _: libp2p_swarm::ConnectionId, + _: libp2p_identity::PeerId, + _: &Multiaddr, + _: Endpoint, + _: PortUse, + ) -> Result, ConnectionDenied> { + Ok(dummy::ConnectionHandler) + } + + fn on_swarm_event(&mut self, _event: FromSwarm) {} + + fn on_connection_handler_event( + &mut self, + _: libp2p_identity::PeerId, + _: libp2p_swarm::ConnectionId, + _: THandlerOutEvent, + ) { + } + + fn poll( + &mut self, + _: &mut Context<'_>, + ) -> Poll>> { + Poll::Pending + } + } + + /// A struct which uses the above, inheriting the generic constraint, + /// for which we want to derive the `NetworkBehaviour`. + #[allow(dead_code)] + #[derive(NetworkBehaviour)] + #[behaviour(prelude = "libp2p_swarm::derive_prelude")] + struct Foo1 { + bar: Bar, + } + + /// A struct which uses the above, inheriting the generic constraint, + /// for which we want to derive the `NetworkBehaviour`. + /// + /// Using a where clause instead of inline constraint. + #[allow(dead_code)] + #[derive(NetworkBehaviour)] + #[behaviour(prelude = "libp2p_swarm::derive_prelude")] + struct Foo2 + where + A: Mark, + { + bar: Bar, + } + + #[allow(dead_code)] + fn foo() { + require_net_behaviour::>(); + require_net_behaviour::>(); + } +} + #[test] fn custom_event_with_either() { use either::Either; - #[allow(clippy::large_enum_variant)] enum BehaviourOutEvent { - Kad(libp2p_kad::Event), - PingOrIdentify(Either), + Kad, + PingOrIdentify, } impl From for BehaviourOutEvent { - fn from(event: libp2p_kad::Event) -> Self { - BehaviourOutEvent::Kad(event) + fn from(_event: libp2p_kad::Event) -> Self { + BehaviourOutEvent::Kad } } impl From> for BehaviourOutEvent { - fn from(event: Either) -> Self { - BehaviourOutEvent::PingOrIdentify(event) + fn from(_event: Either) -> Self { + BehaviourOutEvent::PingOrIdentify } } @@ -452,10 +538,10 @@ fn multiple_behaviour_attributes() { #[test] fn custom_out_event_no_type_parameters() { + use std::task::{Context, Poll}; + use libp2p_identity::PeerId; use libp2p_swarm::{ConnectionId, ToSwarm}; - use std::task::Context; - use std::task::Poll; pub(crate) struct TemplatedBehaviour { _data: T, @@ -463,7 +549,7 @@ fn custom_out_event_no_type_parameters() { impl NetworkBehaviour for TemplatedBehaviour { type ConnectionHandler = dummy::ConnectionHandler; - type ToSwarm = void::Void; + type ToSwarm = std::convert::Infallible; fn handle_established_inbound_connection( &mut self, @@ -481,6 +567,7 @@ fn custom_out_event_no_type_parameters() { _: PeerId, _: &Multiaddr, _: Endpoint, + _: PortUse, ) -> Result, ConnectionDenied> { Ok(dummy::ConnectionHandler) } @@ -491,7 +578,9 @@ fn custom_out_event_no_type_parameters() { _connection: ConnectionId, message: THandlerOutEvent, ) { - void::unreachable(message); + // TODO: remove when Rust 1.82 is MSRV + #[allow(unreachable_patterns)] + libp2p_core::util::unreachable(message); } fn poll( @@ -515,8 +604,8 @@ fn custom_out_event_no_type_parameters() { None, } - impl From for OutEvent { - fn from(_e: void::Void) -> Self { + impl From for OutEvent { + fn from(_e: std::convert::Infallible) -> Self { Self::None } } diff --git a/transports/dns/CHANGELOG.md b/transports/dns/CHANGELOG.md index 91cfbc00883..b46b0413403 100644 --- a/transports/dns/CHANGELOG.md +++ b/transports/dns/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.42.1 + +- Upgrade `async-std-resolver` and `hickory-resolver`. + See [PR 5727](https://github.com/libp2p/rust-libp2p/pull/5727) + +## 0.42.0 + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) + ## 0.41.1 - Add hidden API that removes unnecessary async for `async-std`. diff --git a/transports/dns/Cargo.toml b/transports/dns/Cargo.toml index f1946af7c0e..a07e795397b 100644 --- a/transports/dns/Cargo.toml +++ b/transports/dns/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-dns" edition = "2021" rust-version = { workspace = true } description = "DNS transport implementation for libp2p" -version = "0.41.1" +version = "0.42.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,21 +11,21 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -async-std-resolver = { version = "0.24", optional = true } -async-trait = "0.1.74" -futures = "0.3.28" +async-std-resolver = { workspace = true, features = ["system-config"], optional = true } +async-trait = "0.1.80" +futures = { workspace = true } libp2p-core = { workspace = true } libp2p-identity = { workspace = true } -parking_lot = "0.12.0" -hickory-resolver = { version = "0.24.0", default-features = false, features = ["system-config"] } -smallvec = "1.11.2" -tracing = "0.1.37" +parking_lot = "0.12.3" +hickory-resolver = { workspace = true, features = ["system-config"] } +smallvec = "1.13.2" +tracing = { workspace = true } [dev-dependencies] libp2p-identity = { workspace = true, features = ["rand"] } -tokio-crate = { package = "tokio", version = "1.0", default-features = false, features = ["rt", "time"] } +tokio = { workspace = true, features = ["rt", "time"] } async-std-crate = { package = "async-std", version = "1.6" } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +libp2p-test-utils = { workspace = true } [features] async-std = ["async-std-resolver"] @@ -35,8 +35,6 @@ tokio = ["hickory-resolver/tokio-runtime"] # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/dns/src/lib.rs b/transports/dns/src/lib.rs index 0c41990fab1..581942b8b7e 100644 --- a/transports/dns/src/lib.rs +++ b/transports/dns/src/lib.rs @@ -40,25 +40,26 @@ //! On Unix systems, if no custom configuration is given, [trust-dns-resolver] //! will try to parse the `/etc/resolv.conf` file. This approach comes with a //! few caveats to be aware of: -//! 1) This fails (panics even!) if `/etc/resolv.conf` does not exist. This is -//! the case on all versions of Android. -//! 2) DNS configuration is only evaluated during startup. Runtime changes are -//! thus ignored. -//! 3) DNS resolution is obviously done in process and consequently not using -//! any system APIs (like libc's `gethostbyname`). Again this is -//! problematic on platforms like Android, where there's a lot of -//! complexity hidden behind the system APIs. +//! 1) This fails (panics even!) if `/etc/resolv.conf` does not exist. This is the case on all +//! versions of Android. +//! 2) DNS configuration is only evaluated during startup. Runtime changes are thus ignored. +//! 3) DNS resolution is obviously done in process and consequently not using any system APIs +//! (like libc's `gethostbyname`). Again this is problematic on platforms like Android, where +//! there's a lot of complexity hidden behind the system APIs. +//! //! If the implementation requires different characteristics, one should //! consider providing their own implementation of [`Transport`] or use //! platform specific APIs to extract the host's DNS configuration (if possible) //! and provide a custom [`ResolverConfig`]. //! -//![trust-dns-resolver]: https://docs.rs/trust-dns-resolver/latest/trust_dns_resolver/#dns-over-tls-and-dns-over-https +//! [trust-dns-resolver]: https://docs.rs/trust-dns-resolver/latest/trust_dns_resolver/#dns-over-tls-and-dns-over-https #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[cfg(feature = "async-std")] pub mod async_std { + use std::{io, sync::Arc}; + use async_std_resolver::AsyncStdResolver; use futures::FutureExt; use hickory_resolver::{ @@ -66,7 +67,6 @@ pub mod async_std { system_conf, }; use parking_lot::Mutex; - use std::{io, sync::Arc}; /// A `Transport` wrapper for performing DNS lookups when dialing `Multiaddr`esses /// using `async-std` for all async I/O. @@ -115,13 +115,14 @@ pub mod async_std { #[cfg(feature = "tokio")] pub mod tokio { - use hickory_resolver::{system_conf, TokioAsyncResolver}; - use parking_lot::Mutex; use std::sync::Arc; + use hickory_resolver::{system_conf, TokioResolver}; + use parking_lot::Mutex; + /// A `Transport` wrapper for performing DNS lookups when dialing `Multiaddr`esses /// using `tokio` for all async I/O. - pub type Transport = crate::Transport; + pub type Transport = crate::Transport; impl Transport { /// Creates a new [`Transport`] from the OS's DNS configuration and defaults. @@ -139,26 +140,15 @@ pub mod tokio { ) -> Transport { Transport { inner: Arc::new(Mutex::new(inner)), - resolver: TokioAsyncResolver::tokio(cfg, opts), + resolver: TokioResolver::tokio(cfg, opts), } } } } -use async_trait::async_trait; -use futures::{future::BoxFuture, prelude::*}; -use libp2p_core::{ - connection::Endpoint, - multiaddr::{Multiaddr, Protocol}, - transport::{ListenerId, TransportError, TransportEvent}, -}; -use parking_lot::Mutex; -use smallvec::SmallVec; -use std::io; -use std::net::{Ipv4Addr, Ipv6Addr}; use std::{ - convert::TryFrom, - error, fmt, iter, + error, fmt, io, iter, + net::{Ipv4Addr, Ipv6Addr}, ops::DerefMut, pin::Pin, str, @@ -166,12 +156,23 @@ use std::{ task::{Context, Poll}, }; -pub use hickory_resolver::config::{ResolverConfig, ResolverOpts}; -pub use hickory_resolver::error::{ResolveError, ResolveErrorKind}; -use hickory_resolver::lookup::{Ipv4Lookup, Ipv6Lookup, TxtLookup}; -use hickory_resolver::lookup_ip::LookupIp; -use hickory_resolver::name_server::ConnectionProvider; -use hickory_resolver::AsyncResolver; +use async_trait::async_trait; +use futures::{future::BoxFuture, prelude::*}; +pub use hickory_resolver::{ + config::{ResolverConfig, ResolverOpts}, + ResolveError, ResolveErrorKind, +}; +use hickory_resolver::{ + lookup::{Ipv4Lookup, Ipv6Lookup, TxtLookup}, + lookup_ip::LookupIp, + name_server::ConnectionProvider, +}; +use libp2p_core::{ + multiaddr::{Multiaddr, Protocol}, + transport::{DialOpts, ListenerId, TransportError, TransportEvent}, +}; +use parking_lot::Mutex; +use smallvec::SmallVec; /// The prefix for `dnsaddr` protocol TXT record lookups. const DNSADDR_PREFIX: &str = "_dnsaddr."; @@ -192,7 +193,8 @@ const MAX_DNS_LOOKUPS: usize = 32; const MAX_TXT_RECORDS: usize = 16; /// A [`Transport`] for performing DNS lookups when dialing `Multiaddr`esses. -/// You shouldn't need to use this type directly. Use [`tokio::Transport`] or [`async_std::Transport`] instead. +/// You shouldn't need to use this type directly. Use [`tokio::Transport`] or +/// [`async_std::Transport`] instead. #[derive(Debug)] pub struct Transport { /// The underlying transport. @@ -231,19 +233,12 @@ where self.inner.lock().remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - self.do_dial(addr, Endpoint::Dialer) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + dial_opts: DialOpts, ) -> Result> { - self.do_dial(addr, Endpoint::Listener) - } - - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.inner.lock().address_translation(server, observed) + Ok(self.do_dial(addr, dial_opts)) } fn poll( @@ -269,17 +264,14 @@ where fn do_dial( &mut self, addr: Multiaddr, - role_override: Endpoint, - ) -> Result< - ::Dial, - TransportError<::Error>, - > { + dial_opts: DialOpts, + ) -> ::Dial { let resolver = self.resolver.clone(); let inner = self.inner.clone(); - // Asynchronlously resolve all DNS names in the address before proceeding + // Asynchronously resolve all DNS names in the address before proceeding // with dialing on the underlying transport. - Ok(async move { + async move { let mut last_err = None; let mut dns_lookups = 0; let mut dial_attempts = 0; @@ -358,10 +350,7 @@ where tracing::debug!(address=%addr, "Dialing address"); let transport = inner.clone(); - let dial = match role_override { - Endpoint::Dialer => transport.lock().dial(addr), - Endpoint::Listener => transport.lock().dial_as_listener(addr), - }; + let dial = transport.lock().dial(addr, dial_opts); let result = match dial { Ok(out) => { // We only count attempts that the inner transport @@ -405,7 +394,7 @@ where })) } .boxed() - .right_future()) + .right_future() } } @@ -416,6 +405,7 @@ pub enum Error { /// The underlying transport encountered an error. Transport(TErr), /// DNS resolution failed. + #[allow(clippy::enum_variant_names)] ResolveError(ResolveError), /// DNS resolution was successful, but the underlying transport refused the resolved address. MultiaddrNotSupported(Multiaddr), @@ -603,7 +593,7 @@ pub trait Resolver { } #[async_trait] -impl Resolver for AsyncResolver +impl Resolver for hickory_resolver::Resolver where C: ConnectionProvider, { @@ -626,20 +616,20 @@ where #[cfg(all(test, any(feature = "tokio", feature = "async-std")))] mod tests { - use super::*; use futures::future::BoxFuture; + use hickory_resolver::proto::{ProtoError, ProtoErrorKind}; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, - transport::{TransportError, TransportEvent}, - Transport, + transport::{PortUse, TransportError, TransportEvent}, + Endpoint, Transport, }; use libp2p_identity::PeerId; + use super::*; + #[test] fn basic_resolve() { - let _ = tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); #[derive(Clone)] struct CustomTransport; @@ -662,7 +652,11 @@ mod tests { false } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + _: DialOpts, + ) -> Result> { // Check that all DNS components have been resolved, i.e. replaced. assert!(!addr.iter().any(|p| matches!( p, @@ -671,17 +665,6 @@ mod tests { Ok(Box::pin(future::ready(Ok(())))) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - self.dial(addr) - } - - fn address_translation(&self, _: &Multiaddr, _: &Multiaddr) -> Option { - None - } - fn poll( self: Pin<&mut Self>, _: &mut Context<'_>, @@ -697,30 +680,34 @@ mod tests { T::Dial: Send, R: Clone + Send + Sync + Resolver + 'static, { + let dial_opts = DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }; // Success due to existing A record for example.com. let _ = transport - .dial("/dns4/example.com/tcp/20000".parse().unwrap()) + .dial("/dns4/example.com/tcp/20000".parse().unwrap(), dial_opts) .unwrap() .await .unwrap(); // Success due to existing AAAA record for example.com. let _ = transport - .dial("/dns6/example.com/tcp/20000".parse().unwrap()) + .dial("/dns6/example.com/tcp/20000".parse().unwrap(), dial_opts) .unwrap() .await .unwrap(); // Success due to pass-through, i.e. nothing to resolve. let _ = transport - .dial("/ip4/1.2.3.4/tcp/20000".parse().unwrap()) + .dial("/ip4/1.2.3.4/tcp/20000".parse().unwrap(), dial_opts) .unwrap() .await .unwrap(); // Success due to the DNS TXT records at _dnsaddr.bootstrap.libp2p.io. let _ = transport - .dial("/dnsaddr/bootstrap.libp2p.io".parse().unwrap()) + .dial("/dnsaddr/bootstrap.libp2p.io".parse().unwrap(), dial_opts) .unwrap() .await .unwrap(); @@ -729,7 +716,7 @@ mod tests { // an entry with suffix `/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`, // i.e. a bootnode with such a peer ID. let _ = transport - .dial("/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN".parse().unwrap()) + .dial("/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN".parse().unwrap(), dial_opts) .unwrap() .await .unwrap(); @@ -741,6 +728,7 @@ mod tests { format!("/dnsaddr/bootstrap.libp2p.io/p2p/{}", PeerId::random()) .parse() .unwrap(), + dial_opts, ) .unwrap() .await @@ -752,12 +740,16 @@ mod tests { // Failure due to no records. match transport - .dial("/dns4/example.invalid/tcp/20000".parse().unwrap()) + .dial( + "/dns4/example.invalid/tcp/20000".parse().unwrap(), + dial_opts, + ) .unwrap() .await { Err(Error::ResolveError(e)) => match e.kind() { - ResolveErrorKind::NoRecordsFound { .. } => {} + ResolveErrorKind::Proto(ProtoError { kind, .. }) + if matches!(kind.as_ref(), ProtoErrorKind::NoRecordsFound { .. }) => {} _ => panic!("Unexpected DNS error: {e:?}"), }, Err(e) => panic!("Unexpected error: {e:?}"), @@ -782,7 +774,7 @@ mod tests { // type record lookups may not work with the system DNS resolver. let config = ResolverConfig::quad9(); let opts = ResolverOpts::default(); - let rt = tokio_crate::runtime::Builder::new_current_thread() + let rt = ::tokio::runtime::Builder::new_current_thread() .enable_io() .enable_time() .build() diff --git a/transports/noise/CHANGELOG.md b/transports/noise/CHANGELOG.md index 78effb673d2..cda7132cb28 100644 --- a/transports/noise/CHANGELOG.md +++ b/transports/noise/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.45.1 + +- Fix `cargo clippy` warnings in `rustc 1.84.0-beta.1`. + See [PR 5700](https://github.com/libp2p/rust-libp2p/pull/5700). + +## 0.45.0 + + + ## 0.44.0 - Migrate to `{In,Out}boundConnectionUpgrade` traits. diff --git a/transports/noise/Cargo.toml b/transports/noise/Cargo.toml index 772b74c7acb..a1b712dbdaf 100644 --- a/transports/noise/Cargo.toml +++ b/transports/noise/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-noise" edition = "2021" rust-version = { workspace = true } description = "Cryptographic handshake protocol using the noise framework." -version = "0.44.0" +version = "0.45.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,40 +11,36 @@ repository = "https://github.com/libp2p/rust-libp2p" [dependencies] asynchronous-codec = { workspace = true } bytes = "1" -curve25519-dalek = "4.1.1" -futures = "0.3.29" +futures = { workspace = true } libp2p-core = { workspace = true } libp2p-identity = { workspace = true, features = ["ed25519"] } multiaddr = { workspace = true } multihash = { workspace = true } -once_cell = "1.18.0" +once_cell = "1.19.0" quick-protobuf = "0.8" rand = "0.8.3" -sha2 = "0.10.8" static_assertions = "1" -thiserror = "1.0.50" -tracing = "0.1.37" +thiserror = { workspace = true } +tracing = { workspace = true } x25519-dalek = "2" zeroize = "1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -snow = { version = "0.9.4", features = ["ring-resolver"], default-features = false } +snow = { version = "0.9.6", features = ["ring-resolver"], default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] -snow = { version = "0.9.2", features = ["default-resolver"], default-features = false } +snow = { version = "0.9.5", features = ["default-resolver"], default-features = false } [dev-dependencies] futures_ringbuf = "0.4.0" quickcheck = { workspace = true } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +libp2p-test-utils = { workspace = true } libp2p-identity = { workspace = true, features = ["rand"] } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/noise/src/io.rs b/transports/noise/src/io.rs index 9cd4cfed52a..84aad79d76b 100644 --- a/transports/noise/src/io.rs +++ b/transports/noise/src/io.rs @@ -22,11 +22,6 @@ mod framed; pub(crate) mod handshake; -use asynchronous_codec::Framed; -use bytes::Bytes; -use framed::{Codec, MAX_FRAME_LEN}; -use futures::prelude::*; -use futures::ready; use std::{ cmp::min, fmt, io, @@ -34,6 +29,11 @@ use std::{ task::{Context, Poll}, }; +use asynchronous_codec::Framed; +use bytes::Bytes; +use framed::{Codec, MAX_FRAME_LEN}; +use futures::{prelude::*, ready}; + /// A noise session to a remote. /// /// `T` is the type of the underlying I/O resource. diff --git a/transports/noise/src/io/framed.rs b/transports/noise/src/io/framed.rs index 9ed6045cf38..5aaad6f55e7 100644 --- a/transports/noise/src/io/framed.rs +++ b/transports/noise/src/io/framed.rs @@ -23,13 +23,14 @@ //! Alongside a [`asynchronous_codec::Framed`] this provides a [Sink](futures::Sink) //! and [Stream](futures::Stream) for length-delimited Noise protocol messages. -use super::handshake::proto; -use crate::{protocol::PublicKey, Error}; +use std::{io, mem::size_of}; + use asynchronous_codec::{Decoder, Encoder}; use bytes::{Buf, Bytes, BytesMut}; use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; -use std::io; -use std::mem::size_of; + +use super::handshake::proto; +use crate::{protocol::PublicKey, Error}; /// Max. size of a noise message. const MAX_NOISE_MSG_LEN: usize = 65535; @@ -170,7 +171,8 @@ impl Decoder for Codec { /// Encrypts the given cleartext to `dst`. /// -/// This is a standalone function to allow us reusing the `encrypt_buffer` and to use to across different session states of the noise protocol. +/// This is a standalone function to allow us reusing the `encrypt_buffer` and to use to across +/// different session states of the noise protocol. fn encrypt( cleartext: &[u8], dst: &mut BytesMut, @@ -191,13 +193,14 @@ fn encrypt( /// Encrypts the given ciphertext. /// -/// This is a standalone function so we can use it across different session states of the noise protocol. -/// In case `ciphertext` does not contain enough bytes to decrypt the entire frame, `Ok(None)` is returned. +/// This is a standalone function so we can use it across different session states of the noise +/// protocol. In case `ciphertext` does not contain enough bytes to decrypt the entire frame, +/// `Ok(None)` is returned. fn decrypt( ciphertext: &mut BytesMut, decrypt_fn: impl FnOnce(&[u8], &mut [u8]) -> Result, ) -> io::Result> { - let Some(ciphertext) = decode_length_prefixed(ciphertext)? else { + let Some(ciphertext) = decode_length_prefixed(ciphertext) else { return Ok(None); }; @@ -223,9 +226,9 @@ fn encode_length_prefixed(src: &[u8], dst: &mut BytesMut) { dst.extend_from_slice(src); } -fn decode_length_prefixed(src: &mut BytesMut) -> Result, io::Error> { +fn decode_length_prefixed(src: &mut BytesMut) -> Option { if src.len() < size_of::() { - return Ok(None); + return None; } let mut len_bytes = [0u8; U16_LENGTH]; @@ -235,8 +238,8 @@ fn decode_length_prefixed(src: &mut BytesMut) -> Result, io::Error if src.len() - U16_LENGTH >= len { // Skip the length header we already read. src.advance(U16_LENGTH); - Ok(Some(src.split_to(len).freeze())) + Some(src.split_to(len).freeze()) } else { - Ok(None) + None } } diff --git a/transports/noise/src/io/handshake.rs b/transports/noise/src/io/handshake.rs index 7cc0f859e6e..d4727b91420 100644 --- a/transports/noise/src/io/handshake.rs +++ b/transports/noise/src/io/handshake.rs @@ -23,21 +23,23 @@ pub(super) mod proto { #![allow(unreachable_pub)] include!("../generated/mod.rs"); - pub use self::payload::proto::NoiseExtensions; - pub use self::payload::proto::NoiseHandshakePayload; + pub use self::payload::proto::{NoiseExtensions, NoiseHandshakePayload}; } -use super::framed::Codec; -use crate::io::Output; -use crate::protocol::{KeypairIdentity, PublicKey, STATIC_KEY_DOMAIN}; -use crate::Error; +use std::{collections::HashSet, io, mem}; + use asynchronous_codec::Framed; use futures::prelude::*; use libp2p_identity as identity; use multihash::Multihash; use quick_protobuf::MessageWrite; -use std::collections::HashSet; -use std::{io, mem}; + +use super::framed::Codec; +use crate::{ + io::Output, + protocol::{KeypairIdentity, PublicKey, STATIC_KEY_DOMAIN}, + Error, +}; ////////////////////////////////////////////////////////////////////////////// // Internal @@ -73,7 +75,6 @@ where /// will be sent and received on the given I/O resource and using the /// provided session for cryptographic operations according to the chosen /// Noise handshake pattern. - pub(crate) fn new( io: T, session: snow::HandshakeState, @@ -107,7 +108,7 @@ where .id_remote_pubkey .ok_or_else(|| Error::AuthenticationFailed)?; - let is_valid_signature = self.dh_remote_pubkey_sig.as_ref().map_or(false, |s| { + let is_valid_signature = self.dh_remote_pubkey_sig.as_ref().is_some_and(|s| { id_pk.verify(&[STATIC_KEY_DOMAIN.as_bytes(), pubkey.as_ref()].concat(), s) }); @@ -143,12 +144,16 @@ where } } -/// Maps the provided [`Framed`] from the [`snow::HandshakeState`] into the [`snow::TransportState`]. +/// Maps the provided [`Framed`] from the [`snow::HandshakeState`] into the +/// [`snow::TransportState`]. /// -/// This is a bit tricky because [`Framed`] cannot just be de-composed but only into its [`FramedParts`](asynchronous_codec::FramedParts). -/// However, we need to retain the original [`FramedParts`](asynchronous_codec::FramedParts) because they contain the active read & write buffers. +/// This is a bit tricky because [`Framed`] cannot just be de-composed but only into its +/// [`FramedParts`](asynchronous_codec::FramedParts). However, we need to retain the original +/// [`FramedParts`](asynchronous_codec::FramedParts) because they contain the active read & write +/// buffers. /// -/// Those are likely **not** empty because the remote may directly write to the stream again after the noise handshake finishes. +/// Those are likely **not** empty because the remote may directly write to the stream again after +/// the noise handshake finishes. fn map_into_transport( framed: Framed>, ) -> Result<(PublicKey, Framed>), Error> @@ -248,7 +253,7 @@ where ..Default::default() }; - pb.identity_sig = state.identity.signature.clone(); + pb.identity_sig.clone_from(&state.identity.signature); // If this is the responder then send WebTransport certhashes to initiator, if any. if state.io.codec().is_responder() { diff --git a/transports/noise/src/lib.rs b/transports/noise/src/lib.rs index 70fae9d7ee6..e05556744fe 100644 --- a/transports/noise/src/lib.rs +++ b/transports/noise/src/lib.rs @@ -21,14 +21,14 @@ //! [Noise protocol framework][noise] support for libp2p. //! //! > **Note**: This crate is still experimental and subject to major breaking changes -//! > both on the API and the wire protocol. +//! > both on the API and the wire protocol. //! //! This crate provides `libp2p_core::InboundUpgrade` and `libp2p_core::OutboundUpgrade` //! implementations for various noise handshake patterns (currently `IK`, `IX`, and `XX`) //! over a particular choice of Diffie–Hellman key agreement (currently only X25519). //! //! > **Note**: Only the `XX` handshake pattern is currently guaranteed to provide -//! > interoperability with other libp2p implementations. +//! > interoperability with other libp2p implementations. //! //! All upgrades produce as output a pair, consisting of the remote's static public key //! and a `NoiseOutput` which represents the established cryptographic session with the @@ -39,14 +39,16 @@ //! Example: //! //! ``` -//! use libp2p_core::{Transport, upgrade, transport::MemoryTransport}; -//! use libp2p_noise as noise; +//! use libp2p_core::{transport::MemoryTransport, upgrade, Transport}; //! use libp2p_identity as identity; +//! use libp2p_noise as noise; //! //! # fn main() { //! let id_keys = identity::Keypair::generate_ed25519(); //! let noise = noise::Config::new(&id_keys).unwrap(); -//! let builder = MemoryTransport::default().upgrade(upgrade::Version::V1).authenticate(noise); +//! let builder = MemoryTransport::default() +//! .upgrade(upgrade::Version::V1) +//! .authenticate(noise); //! // let transport = builder.multiplex(...); //! # } //! ``` @@ -58,22 +60,25 @@ mod io; mod protocol; -pub use io::Output; +use std::{collections::HashSet, fmt::Write, pin::Pin}; -use crate::handshake::State; -use crate::io::handshake; -use crate::protocol::{noise_params_into_builder, AuthenticKeypair, Keypair, PARAMS_XX}; use futures::prelude::*; -use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}; -use libp2p_core::UpgradeInfo; +pub use io::Output; +use libp2p_core::{ + upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}, + UpgradeInfo, +}; use libp2p_identity as identity; use libp2p_identity::PeerId; use multiaddr::Protocol; use multihash::Multihash; use snow::params::NoiseParams; -use std::collections::HashSet; -use std::fmt::Write; -use std::pin::Pin; + +use crate::{ + handshake::State, + io::handshake, + protocol::{noise_params_into_builder, AuthenticKeypair, Keypair, PARAMS_XX}, +}; /// The configuration for the noise handshake. #[derive(Clone)] @@ -240,6 +245,7 @@ pub enum Error { #[error("failed to decode protobuf ")] InvalidPayload(#[from] DecodeError), #[error(transparent)] + #[allow(clippy::enum_variant_names)] SigningError(#[from] libp2p_identity::SigningError), #[error("Expected WebTransport certhashes ({}) are not a subset of received ones ({})", certhashes_to_string(.0), certhashes_to_string(.1))] UnknownWebTransportCerthashes(HashSet>, HashSet>), diff --git a/transports/noise/src/protocol.rs b/transports/noise/src/protocol.rs index e37c55c7f10..ca47ea0dfcd 100644 --- a/transports/noise/src/protocol.rs +++ b/transports/noise/src/protocol.rs @@ -20,7 +20,6 @@ //! Components of a Noise protocol. -use crate::Error; use libp2p_identity as identity; use once_cell::sync::Lazy; use rand::{Rng as _, SeedableRng}; @@ -28,6 +27,8 @@ use snow::params::NoiseParams; use x25519_dalek::{x25519, X25519_BASEPOINT_BYTES}; use zeroize::Zeroize; +use crate::Error; + /// Prefix of static key signatures for domain separation. pub(crate) const STATIC_KEY_DOMAIN: &str = "noise-libp2p-static-key:"; @@ -292,8 +293,6 @@ impl snow::types::Dh for Keypair { #[cfg(test)] mod tests { use super::*; - use crate::protocol::PARAMS_XX; - use once_cell::sync::Lazy; #[test] fn handshake_hashes_disagree_if_prologue_differs() { diff --git a/transports/noise/tests/smoke.rs b/transports/noise/tests/smoke.rs index 0afebc0cbea..cd9702b1c2f 100644 --- a/transports/noise/tests/smoke.rs +++ b/transports/noise/tests/smoke.rs @@ -18,19 +18,21 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::io; + use futures::prelude::*; -use libp2p_core::transport::{MemoryTransport, Transport}; -use libp2p_core::upgrade; -use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}; +use libp2p_core::{ + transport::{MemoryTransport, Transport}, + upgrade, + upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}, +}; use libp2p_identity as identity; use libp2p_noise as noise; use quickcheck::*; -use std::{convert::TryInto, io}; -use tracing_subscriber::EnvFilter; #[allow(dead_code)] fn core_upgrade_compat() { - // Tests API compaibility with the libp2p-core upgrade API, + // Tests API compatibility with the libp2p-core upgrade API, // i.e. if it compiles, the "test" is considered a success. let id_keys = identity::Keypair::generate_ed25519(); let noise = noise::Config::new(&id_keys).unwrap(); @@ -41,9 +43,7 @@ fn core_upgrade_compat() { #[test] fn xx() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); fn prop(mut messages: Vec) -> bool { messages.truncate(5); let server_id = identity::Keypair::generate_ed25519(); diff --git a/transports/noise/tests/webtransport_certhashes.rs b/transports/noise/tests/webtransport_certhashes.rs index b3c924f8188..7fa28da0ebe 100644 --- a/transports/noise/tests/webtransport_certhashes.rs +++ b/transports/noise/tests/webtransport_certhashes.rs @@ -1,8 +1,9 @@ +use std::collections::HashSet; + use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}; use libp2p_identity as identity; use libp2p_noise as noise; use multihash::Multihash; -use std::collections::HashSet; const SHA_256_MH: u64 = 0x12; diff --git a/transports/plaintext/CHANGELOG.md b/transports/plaintext/CHANGELOG.md index 42b53d12a88..91860c76590 100644 --- a/transports/plaintext/CHANGELOG.md +++ b/transports/plaintext/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.42.0 + + + ## 0.41.0 - Migrate to `{In,Out}boundConnectionUpgrade` traits. diff --git a/transports/plaintext/Cargo.toml b/transports/plaintext/Cargo.toml index 96cb83c7d69..95f8f5af065 100644 --- a/transports/plaintext/Cargo.toml +++ b/transports/plaintext/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-plaintext" edition = "2021" rust-version = { workspace = true } description = "Plaintext encryption dummy protocol for libp2p" -version = "0.41.0" +version = "0.42.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -13,26 +13,23 @@ categories = ["network-programming", "asynchronous"] [dependencies] asynchronous-codec = { workspace = true } bytes = "1" -futures = "0.3.29" +futures = { workspace = true } libp2p-core = { workspace = true } libp2p-identity = { workspace = true } quick-protobuf = "0.8" -tracing = "0.1.37" +tracing = { workspace = true } quick-protobuf-codec = { workspace = true } [dev-dependencies] libp2p-identity = { workspace = true, features = ["ed25519", "rand"] } quickcheck = { workspace = true } -rand = "0.8" futures_ringbuf = "0.4.0" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +libp2p-test-utils = { workspace = true } -# Passing arguments to the docsrs builder in order to properly document cfg's. +# Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/plaintext/src/error.rs b/transports/plaintext/src/error.rs index 7480874a85e..2d352562528 100644 --- a/transports/plaintext/src/error.rs +++ b/transports/plaintext/src/error.rs @@ -18,9 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::error; -use std::fmt; -use std::io::Error as IoError; +use std::{error, fmt, io::Error as IoError}; #[derive(Debug)] pub enum Error { diff --git a/transports/plaintext/src/handshake.rs b/transports/plaintext/src/handshake.rs index ddd5f7f8a9b..38a56b84862 100644 --- a/transports/plaintext/src/handshake.rs +++ b/transports/plaintext/src/handshake.rs @@ -18,14 +18,18 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::error::{DecodeError, Error}; -use crate::proto::Exchange; -use crate::Config; +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; + use asynchronous_codec::{Framed, FramedParts}; use bytes::Bytes; use futures::prelude::*; use libp2p_identity::{PeerId, PublicKey}; -use std::io::{Error as IoError, ErrorKind as IoErrorKind}; + +use crate::{ + error::{DecodeError, Error}, + proto::Exchange, + Config, +}; pub(crate) async fn handshake(socket: S, config: Config) -> Result<(S, PublicKey, Bytes), Error> where diff --git a/transports/plaintext/src/lib.rs b/transports/plaintext/src/lib.rs index 4a322d63fab..f841a859a62 100644 --- a/transports/plaintext/src/lib.rs +++ b/transports/plaintext/src/lib.rs @@ -22,22 +22,23 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use crate::error::Error; - -use bytes::Bytes; -use futures::future::BoxFuture; -use futures::prelude::*; -use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}; -use libp2p_core::UpgradeInfo; -use libp2p_identity as identity; -use libp2p_identity::PeerId; -use libp2p_identity::PublicKey; use std::{ io, iter, pin::Pin, task::{Context, Poll}, }; +use bytes::Bytes; +use futures::{future::BoxFuture, prelude::*}; +use libp2p_core::{ + upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}, + UpgradeInfo, +}; +use libp2p_identity as identity; +use libp2p_identity::{PeerId, PublicKey}; + +use crate::error::Error; + mod error; mod handshake; mod proto { diff --git a/transports/plaintext/tests/smoke.rs b/transports/plaintext/tests/smoke.rs index f77f23d3ad3..ee8cec46c0b 100644 --- a/transports/plaintext/tests/smoke.rs +++ b/transports/plaintext/tests/smoke.rs @@ -23,13 +23,10 @@ use libp2p_core::upgrade::InboundConnectionUpgrade; use libp2p_identity as identity; use libp2p_plaintext as plaintext; use quickcheck::QuickCheck; -use tracing_subscriber::EnvFilter; #[test] fn variable_msg_length() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); fn prop(msg: Vec) { let msg_to_send = msg.clone(); diff --git a/transports/pnet/CHANGELOG.md b/transports/pnet/CHANGELOG.md index e6c3d1974dc..86d519e8640 100644 --- a/transports/pnet/CHANGELOG.md +++ b/transports/pnet/CHANGELOG.md @@ -1,9 +1,13 @@ +## 0.25.0 + + + ## 0.24.0 ## 0.23.1 - + +## 0.4.1 + +- Fix a panic caused by `rustls` parsing the libp2p TLS extension. + See [PR 5498](https://github.com/libp2p/rust-libp2p/pull/5498). + +## 0.4.0 + +- Upgrade `rustls` to `0.23`. See [PR 5385](https://github.com/libp2p/rust-libp2p/pull/5385) + ## 0.3.0 - Migrate to `{In,Out}boundConnectionUpgrade` traits. diff --git a/transports/tls/Cargo.toml b/transports/tls/Cargo.toml index 5a1e4788245..7702a4361b1 100644 --- a/transports/tls/Cargo.toml +++ b/transports/tls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-tls" -version = "0.3.0" +version = "0.5.0" edition = "2021" rust-version = { workspace = true } description = "TLS configuration based on libp2p TLS specs." @@ -9,38 +9,36 @@ license = "MIT" exclude = ["src/test_assets"] [dependencies] -futures = { version = "0.3.29", default-features = false } -futures-rustls = "0.24.0" +futures = { workspace = true } +futures-rustls = { workspace = true } libp2p-core = { workspace = true } libp2p-identity = { workspace = true } -rcgen = "0.11.3" -ring = "0.16.20" -thiserror = "1.0.50" +rcgen = { workspace = true } +ring = { workspace = true } +thiserror = { workspace = true } webpki = { version = "0.101.4", package = "rustls-webpki", features = ["std"] } -x509-parser = "0.15.1" +x509-parser = "0.16.0" yasna = "0.5.2" # Exposed dependencies. Breaking changes to these are breaking changes to us. [dependencies.rustls] -version = "0.21.9" +version = "0.23.9" default-features = false -features = ["dangerous_configuration"] # Must enable this to allow for custom verification code. +features = ["ring", "std"] # Must enable this to allow for custom verification code. + [dev-dependencies] -hex = "0.4.3" hex-literal = "0.4.1" libp2p-core = { workspace = true } libp2p-identity = { workspace = true, features = ["ed25519", "rsa", "secp256k1", "ecdsa", "rand"] } libp2p-swarm = { workspace = true, features = ["tokio"] } libp2p-yamux = { workspace = true } -tokio = { version = "1.34.0", features = ["full"] } +tokio = { workspace = true, features = ["full"] } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/tls/src/certificate.rs b/transports/tls/src/certificate.rs index ff9d296bb16..3e7eeb22bf3 100644 --- a/transports/tls/src/certificate.rs +++ b/transports/tls/src/certificate.rs @@ -22,12 +22,14 @@ //! //! This module handles generation, signing, and verification of certificates. +use std::sync::Arc; + use libp2p_identity as identity; use libp2p_identity::PeerId; use x509_parser::{prelude::*, signature_algorithm::SignatureAlgorithm}; /// The libp2p Public Key Extension is a X.509 extension -/// with the Object Identier 1.3.6.1.4.1.53594.1.1, +/// with the Object Identifier 1.3.6.1.4.1.53594.1.1, /// allocated by IANA to the libp2p project at Protocol Labs. const P2P_EXT_OID: [u64; 9] = [1, 3, 6, 1, 4, 1, 53594, 1, 1]; @@ -42,18 +44,65 @@ const P2P_SIGNING_PREFIX: [u8; 21] = *b"libp2p-tls-handshake:"; // Similarly, hash functions with an output length less than 256 bits MUST NOT be used. static P2P_SIGNATURE_ALGORITHM: &rcgen::SignatureAlgorithm = &rcgen::PKCS_ECDSA_P256_SHA256; +#[derive(Debug)] +pub(crate) struct AlwaysResolvesCert(Arc); + +impl AlwaysResolvesCert { + pub(crate) fn new( + cert: rustls::pki_types::CertificateDer<'static>, + key: &rustls::pki_types::PrivateKeyDer<'_>, + ) -> Result { + let certified_key = rustls::sign::CertifiedKey::new( + vec![cert], + rustls::crypto::ring::sign::any_ecdsa_type(key)?, + ); + Ok(Self(Arc::new(certified_key))) + } +} + +impl rustls::client::ResolvesClientCert for AlwaysResolvesCert { + fn resolve( + &self, + _root_hint_subjects: &[&[u8]], + _sigschemes: &[rustls::SignatureScheme], + ) -> Option> { + Some(Arc::clone(&self.0)) + } + + fn has_certs(&self) -> bool { + true + } +} + +impl rustls::server::ResolvesServerCert for AlwaysResolvesCert { + fn resolve( + &self, + _client_hello: rustls::server::ClientHello<'_>, + ) -> Option> { + Some(Arc::clone(&self.0)) + } +} + /// Generates a self-signed TLS certificate that includes a libp2p-specific /// certificate extension containing the public key of the given keypair. pub fn generate( identity_keypair: &identity::Keypair, -) -> Result<(rustls::Certificate, rustls::PrivateKey), GenError> { +) -> Result< + ( + rustls::pki_types::CertificateDer<'static>, + rustls::pki_types::PrivateKeyDer<'static>, + ), + GenError, +> { // Keypair used to sign the certificate. // SHOULD NOT be related to the host's key. // Endpoints MAY generate a new key and certificate // for every connection attempt, or they MAY reuse the same key // and certificate for multiple connections. let certificate_keypair = rcgen::KeyPair::generate(P2P_SIGNATURE_ALGORITHM)?; - let rustls_key = rustls::PrivateKey(certificate_keypair.serialize_der()); + let rustls_key = rustls::pki_types::PrivateKeyDer::from( + rustls::pki_types::PrivatePkcs8KeyDer::from(certificate_keypair.serialize_der()), + ); let certificate = { let mut params = rcgen::CertificateParams::new(vec![]); @@ -67,7 +116,7 @@ pub fn generate( rcgen::Certificate::from_params(params)? }; - let rustls_certificate = rustls::Certificate(certificate.serialize_der()?); + let rustls_certificate = rustls::pki_types::CertificateDer::from(certificate.serialize_der()?); Ok((rustls_certificate, rustls_key)) } @@ -76,7 +125,9 @@ pub fn generate( /// /// For this to succeed, the certificate must contain the specified extension and the signature must /// match the embedded public key. -pub fn parse(certificate: &rustls::Certificate) -> Result, ParseError> { +pub fn parse<'a>( + certificate: &'a rustls::pki_types::CertificateDer<'a>, +) -> Result, ParseError> { let certificate = parse_unverified(certificate.as_ref())?; certificate.verify()?; @@ -232,8 +283,8 @@ impl P2pCertificate<'_> { self.extension.public_key.to_peer_id() } - /// Verify the `signature` of the `message` signed by the private key corresponding to the public key stored - /// in the certificate. + /// Verify the `signature` of the `message` signed by the private key corresponding to the + /// public key stored in the certificate. pub fn verify_signature( &self, signature_scheme: rustls::SignatureScheme, @@ -374,7 +425,7 @@ impl P2pCertificate<'_> { } if signature_algorithm.algorithm == OID_PKCS1_RSASSAPSS { // According to https://datatracker.ietf.org/doc/html/rfc4055#section-3.1: - // Inside of params there shuld be a sequence of: + // Inside of params there should be a sequence of: // - Hash Algorithm // - Mask Algorithm // - Salt Length @@ -441,9 +492,10 @@ impl P2pCertificate<'_> { #[cfg(test)] mod tests { - use super::*; use hex_literal::hex; + use super::*; + #[test] fn sanity_check() { let keypair = identity::Keypair::generate_ed25519(); @@ -481,7 +533,9 @@ mod tests { #[test] fn rsa_pss_sha384() { - let cert = rustls::Certificate(include_bytes!("./test_assets/rsa_pss_sha384.der").to_vec()); + let cert = rustls::pki_types::CertificateDer::from( + include_bytes!("./test_assets/rsa_pss_sha384.der").to_vec(), + ); let cert = parse(&cert).unwrap(); @@ -502,7 +556,7 @@ mod tests { #[test] fn can_parse_certificate_with_ed25519_keypair() { - let certificate = rustls::Certificate(hex!("308201773082011ea003020102020900f5bd0debaa597f52300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d030107034200046bf9871220d71dcb3483ecdfcbfcc7c103f8509d0974b3c18ab1f1be1302d643103a08f7a7722c1b247ba3876fe2c59e26526f479d7718a85202ddbe47562358a37f307d307b060a2b0601040183a25a01010101ff046a30680424080112207fda21856709c5ae12fd6e8450623f15f11955d384212b89f56e7e136d2e17280440aaa6bffabe91b6f30c35e3aa4f94b1188fed96b0ffdd393f4c58c1c047854120e674ce64c788406d1c2c4b116581fd7411b309881c3c7f20b46e54c7e6fe7f0f300a06082a8648ce3d040302034700304402207d1a1dbd2bda235ff2ec87daf006f9b04ba076a5a5530180cd9c2e8f6399e09d0220458527178c7e77024601dbb1b256593e9b96d961b96349d1f560114f61a87595").to_vec()); + let certificate = rustls::pki_types::CertificateDer::from(hex!("308201773082011ea003020102020900f5bd0debaa597f52300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d030107034200046bf9871220d71dcb3483ecdfcbfcc7c103f8509d0974b3c18ab1f1be1302d643103a08f7a7722c1b247ba3876fe2c59e26526f479d7718a85202ddbe47562358a37f307d307b060a2b0601040183a25a01010101ff046a30680424080112207fda21856709c5ae12fd6e8450623f15f11955d384212b89f56e7e136d2e17280440aaa6bffabe91b6f30c35e3aa4f94b1188fed96b0ffdd393f4c58c1c047854120e674ce64c788406d1c2c4b116581fd7411b309881c3c7f20b46e54c7e6fe7f0f300a06082a8648ce3d040302034700304402207d1a1dbd2bda235ff2ec87daf006f9b04ba076a5a5530180cd9c2e8f6399e09d0220458527178c7e77024601dbb1b256593e9b96d961b96349d1f560114f61a87595").to_vec()); let peer_id = parse(&certificate).unwrap().peer_id(); @@ -516,7 +570,7 @@ mod tests { #[test] fn fails_to_parse_bad_certificate_with_ed25519_keypair() { - let certificate = rustls::Certificate(hex!("308201773082011da003020102020830a73c5d896a1109300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d03010703420004bbe62df9a7c1c46b7f1f21d556deec5382a36df146fb29c7f1240e60d7d5328570e3b71d99602b77a65c9b3655f62837f8d66b59f1763b8c9beba3be07778043a37f307d307b060a2b0601040183a25a01010101ff046a3068042408011220ec8094573afb9728088860864f7bcea2d4fd412fef09a8e2d24d482377c20db60440ecabae8354afa2f0af4b8d2ad871e865cb5a7c0c8d3dbdbf42de577f92461a0ebb0a28703e33581af7d2a4f2270fc37aec6261fcc95f8af08f3f4806581c730a300a06082a8648ce3d040302034800304502202dfb17a6fa0f94ee0e2e6a3b9fb6e986f311dee27392058016464bd130930a61022100ba4b937a11c8d3172b81e7cd04aedb79b978c4379c2b5b24d565dd5d67d3cb3c").to_vec()); + let certificate = rustls::pki_types::CertificateDer::from(hex!("308201773082011da003020102020830a73c5d896a1109300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d03010703420004bbe62df9a7c1c46b7f1f21d556deec5382a36df146fb29c7f1240e60d7d5328570e3b71d99602b77a65c9b3655f62837f8d66b59f1763b8c9beba3be07778043a37f307d307b060a2b0601040183a25a01010101ff046a3068042408011220ec8094573afb9728088860864f7bcea2d4fd412fef09a8e2d24d482377c20db60440ecabae8354afa2f0af4b8d2ad871e865cb5a7c0c8d3dbdbf42de577f92461a0ebb0a28703e33581af7d2a4f2270fc37aec6261fcc95f8af08f3f4806581c730a300a06082a8648ce3d040302034800304502202dfb17a6fa0f94ee0e2e6a3b9fb6e986f311dee27392058016464bd130930a61022100ba4b937a11c8d3172b81e7cd04aedb79b978c4379c2b5b24d565dd5d67d3cb3c").to_vec()); let error = parse(&certificate).unwrap_err(); @@ -525,7 +579,7 @@ mod tests { #[test] fn can_parse_certificate_with_ecdsa_keypair() { - let certificate = rustls::Certificate(hex!("308201c030820166a003020102020900eaf419a6e3edb4a6300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d030107034200048dbf1116c7c608d6d5292bd826c3feb53483a89fce434bf64538a359c8e07538ff71f6766239be6a146dcc1a5f3bb934bcd4ae2ae1d4da28ac68b4a20593f06ba381c63081c33081c0060a2b0601040183a25a01010101ff0481ae3081ab045f0803125b3059301306072a8648ce3d020106082a8648ce3d0301070342000484b93fa456a74bd0153919f036db7bc63c802f055bc7023395d0203de718ee0fc7b570b767cdd858aca6c7c4113ff002e78bd2138ac1a3b26dde3519e06979ad04483046022100bc84014cea5a41feabdf4c161096564b9ccf4b62fbef4fe1cd382c84e11101780221009204f086a84cb8ed8a9ddd7868dc90c792ee434adf62c66f99a08a5eba11615b300a06082a8648ce3d0403020348003045022054b437be9a2edf591312d68ff24bf91367ad4143f76cf80b5658f232ade820da022100e23b48de9df9c25d4c83ddddf75d2676f0b9318ee2a6c88a736d85eab94a912f").to_vec()); + let certificate = rustls::pki_types::CertificateDer::from(hex!("308201c030820166a003020102020900eaf419a6e3edb4a6300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d030107034200048dbf1116c7c608d6d5292bd826c3feb53483a89fce434bf64538a359c8e07538ff71f6766239be6a146dcc1a5f3bb934bcd4ae2ae1d4da28ac68b4a20593f06ba381c63081c33081c0060a2b0601040183a25a01010101ff0481ae3081ab045f0803125b3059301306072a8648ce3d020106082a8648ce3d0301070342000484b93fa456a74bd0153919f036db7bc63c802f055bc7023395d0203de718ee0fc7b570b767cdd858aca6c7c4113ff002e78bd2138ac1a3b26dde3519e06979ad04483046022100bc84014cea5a41feabdf4c161096564b9ccf4b62fbef4fe1cd382c84e11101780221009204f086a84cb8ed8a9ddd7868dc90c792ee434adf62c66f99a08a5eba11615b300a06082a8648ce3d0403020348003045022054b437be9a2edf591312d68ff24bf91367ad4143f76cf80b5658f232ade820da022100e23b48de9df9c25d4c83ddddf75d2676f0b9318ee2a6c88a736d85eab94a912f").to_vec()); let peer_id = parse(&certificate).unwrap().peer_id(); @@ -539,7 +593,7 @@ mod tests { #[test] fn can_parse_certificate_with_secp256k1_keypair() { - let certificate = rustls::Certificate(hex!("3082018230820128a003020102020900f3b305f55622cfdf300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d0301070342000458f7e9581748ff9bdd933b655cc0e5552a1248f840658cc221dec2186b5a2fe4641b86ab7590a3422cdbb1000cf97662f27e5910d7569f22feed8829c8b52e0fa38188308185308182060a2b0601040183a25a01010101ff0471306f042508021221026b053094d1112bce799dc8026040ae6d4eb574157929f1598172061f753d9b1b04463044022040712707e97794c478d93989aaa28ae1f71c03af524a8a4bd2d98424948a782302207b61b7f074b696a25fb9e0059141a811cccc4cc28042d9301b9b2a4015e87470300a06082a8648ce3d04030203480030450220143ae4d86fdc8675d2480bb6912eca5e39165df7f572d836aa2f2d6acfab13f8022100831d1979a98f0c4a6fb5069ca374de92f1a1205c962a6d90ad3d7554cb7d9df4").to_vec()); + let certificate = rustls::pki_types::CertificateDer::from(hex!("3082018230820128a003020102020900f3b305f55622cfdf300a06082a8648ce3d04030230003020170d3735303130313030303030305a180f34303936303130313030303030305a30003059301306072a8648ce3d020106082a8648ce3d0301070342000458f7e9581748ff9bdd933b655cc0e5552a1248f840658cc221dec2186b5a2fe4641b86ab7590a3422cdbb1000cf97662f27e5910d7569f22feed8829c8b52e0fa38188308185308182060a2b0601040183a25a01010101ff0471306f042508021221026b053094d1112bce799dc8026040ae6d4eb574157929f1598172061f753d9b1b04463044022040712707e97794c478d93989aaa28ae1f71c03af524a8a4bd2d98424948a782302207b61b7f074b696a25fb9e0059141a811cccc4cc28042d9301b9b2a4015e87470300a06082a8648ce3d04030203480030450220143ae4d86fdc8675d2480bb6912eca5e39165df7f572d836aa2f2d6acfab13f8022100831d1979a98f0c4a6fb5069ca374de92f1a1205c962a6d90ad3d7554cb7d9df4").to_vec()); let peer_id = parse(&certificate).unwrap().peer_id(); diff --git a/transports/tls/src/lib.rs b/transports/tls/src/lib.rs index 1edd83e9807..57d7d69d4bd 100644 --- a/transports/tls/src/lib.rs +++ b/transports/tls/src/lib.rs @@ -29,13 +29,12 @@ pub mod certificate; mod upgrade; mod verifier; -use libp2p_identity::Keypair; -use libp2p_identity::PeerId; use std::sync::Arc; +use certificate::AlwaysResolvesCert; pub use futures_rustls::TlsStream; -pub use upgrade::Config; -pub use upgrade::UpgradeError; +use libp2p_identity::{Keypair, PeerId}; +pub use upgrade::{Config, UpgradeError}; const P2P_ALPN: [u8; 6] = *b"libp2p"; @@ -46,16 +45,22 @@ pub fn make_client_config( ) -> Result { let (certificate, private_key) = certificate::generate(keypair)?; - let mut crypto = rustls::ClientConfig::builder() - .with_cipher_suites(verifier::CIPHERSUITES) - .with_safe_default_kx_groups() + let mut provider = rustls::crypto::ring::default_provider(); + provider.cipher_suites = verifier::CIPHERSUITES.to_vec(); + + let cert_resolver = Arc::new( + AlwaysResolvesCert::new(certificate, &private_key) + .expect("Client cert key DER is valid; qed"), + ); + + let mut crypto = rustls::ClientConfig::builder_with_provider(provider.into()) .with_protocol_versions(verifier::PROTOCOL_VERSIONS) .expect("Cipher suites and kx groups are configured; qed") + .dangerous() .with_custom_certificate_verifier(Arc::new( verifier::Libp2pCertificateVerifier::with_remote_peer_id(remote_peer_id), )) - .with_client_auth_cert(vec![certificate], private_key) - .expect("Client cert key DER is valid; qed"); + .with_client_cert_resolver(cert_resolver); crypto.alpn_protocols = vec![P2P_ALPN.to_vec()]; Ok(crypto) @@ -67,14 +72,19 @@ pub fn make_server_config( ) -> Result { let (certificate, private_key) = certificate::generate(keypair)?; - let mut crypto = rustls::ServerConfig::builder() - .with_cipher_suites(verifier::CIPHERSUITES) - .with_safe_default_kx_groups() + let mut provider = rustls::crypto::ring::default_provider(); + provider.cipher_suites = verifier::CIPHERSUITES.to_vec(); + + let cert_resolver = Arc::new( + AlwaysResolvesCert::new(certificate, &private_key) + .expect("Server cert key DER is valid; qed"), + ); + + let mut crypto = rustls::ServerConfig::builder_with_provider(provider.into()) .with_protocol_versions(verifier::PROTOCOL_VERSIONS) .expect("Cipher suites and kx groups are configured; qed") .with_client_cert_verifier(Arc::new(verifier::Libp2pCertificateVerifier::new())) - .with_single_cert(vec![certificate], private_key) - .expect("Server cert key DER is valid; qed"); + .with_cert_resolver(cert_resolver); crypto.alpn_protocols = vec![P2P_ALPN.to_vec()]; Ok(crypto) diff --git a/transports/tls/src/upgrade.rs b/transports/tls/src/upgrade.rs index 84510b6bab0..a6d81ab36c9 100644 --- a/transports/tls/src/upgrade.rs +++ b/transports/tls/src/upgrade.rs @@ -18,19 +18,22 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::certificate; -use crate::certificate::P2pCertificate; -use futures::future::BoxFuture; -use futures::AsyncWrite; -use futures::{AsyncRead, FutureExt}; +use std::{ + net::{IpAddr, Ipv4Addr}, + sync::Arc, +}; + +use futures::{future::BoxFuture, AsyncRead, AsyncWrite, FutureExt}; use futures_rustls::TlsStream; -use libp2p_core::upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}; -use libp2p_core::UpgradeInfo; +use libp2p_core::{ + upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade}, + UpgradeInfo, +}; use libp2p_identity as identity; use libp2p_identity::PeerId; -use rustls::{CommonState, ServerName}; -use std::net::{IpAddr, Ipv4Addr}; -use std::sync::Arc; +use rustls::{pki_types::ServerName, CommonState}; + +use crate::{certificate, certificate::P2pCertificate}; #[derive(thiserror::Error, Debug)] pub enum UpgradeError { @@ -101,9 +104,13 @@ where fn upgrade_outbound(self, socket: C, _: Self::Info) -> Self::Future { async move { - // Spec: In order to keep this flexibility for future versions, clients that only support the version of the handshake defined in this document MUST NOT send any value in the Server Name Indication. - // Setting `ServerName` to unspecified will disable the use of the SNI extension. - let name = ServerName::IpAddress(IpAddr::V4(Ipv4Addr::UNSPECIFIED)); + // Spec: In order to keep this flexibility for future versions, clients that only + // support the version of the handshake defined in this document MUST NOT send any value + // in the Server Name Indication. Setting `ServerName` to unspecified will + // disable the use of the SNI extension. + let name = ServerName::IpAddress(rustls::pki_types::IpAddr::from(IpAddr::V4( + Ipv4Addr::UNSPECIFIED, + ))); let stream = futures_rustls::TlsConnector::from(Arc::new(self.client)) .connect(name, socket) diff --git a/transports/tls/src/verifier.rs b/transports/tls/src/verifier.rs index 01fdb8fdf11..82b275bc7be 100644 --- a/transports/tls/src/verifier.rs +++ b/transports/tls/src/verifier.rs @@ -23,18 +23,21 @@ //! This module handles a verification of a client/server certificate chain //! and signatures allegedly by the given certificates. -use crate::certificate; +use std::sync::Arc; + use libp2p_identity::PeerId; use rustls::{ - cipher_suite::{ + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + crypto::ring::cipher_suite::{ TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS13_CHACHA20_POLY1305_SHA256, }, - client::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, - server::{ClientCertVerified, ClientCertVerifier}, - Certificate, CertificateError, DigitallySignedStruct, DistinguishedName, SignatureScheme, + pki_types::CertificateDer, + server::danger::{ClientCertVerified, ClientCertVerifier}, + CertificateError, DigitallySignedStruct, DistinguishedName, OtherError, SignatureScheme, SupportedCipherSuite, SupportedProtocolVersion, }; -use std::sync::Arc; + +use crate::certificate; /// The protocol versions supported by this verifier. /// @@ -56,6 +59,7 @@ pub(crate) static CIPHERSUITES: &[SupportedCipherSuite] = &[ /// Implementation of the `rustls` certificate verification traits for libp2p. /// /// Only TLS 1.3 is supported. TLS 1.2 should be disabled in the configuration of `rustls`. +#[derive(Debug)] pub(crate) struct Libp2pCertificateVerifier { /// The peer ID we intend to connect to remote_peer_id: Option, @@ -65,8 +69,8 @@ pub(crate) struct Libp2pCertificateVerifier { /// /// - Exactly one certificate must be presented. /// - The certificate must be self-signed. -/// - The certificate must have a valid libp2p extension that includes a -/// signature of its public key. +/// - The certificate must have a valid libp2p extension that includes a signature of its public +/// key. impl Libp2pCertificateVerifier { pub(crate) fn new() -> Self { Self { @@ -103,12 +107,11 @@ impl Libp2pCertificateVerifier { impl ServerCertVerifier for Libp2pCertificateVerifier { fn verify_server_cert( &self, - end_entity: &Certificate, - intermediates: &[Certificate], - _server_name: &rustls::ServerName, - _scts: &mut dyn Iterator, + end_entity: &CertificateDer, + intermediates: &[CertificateDer], + _server_name: &rustls::pki_types::ServerName, _ocsp_response: &[u8], - _now: std::time::SystemTime, + _now: rustls::pki_types::UnixTime, ) -> Result { let peer_id = verify_presented_certs(end_entity, intermediates)?; @@ -130,7 +133,7 @@ impl ServerCertVerifier for Libp2pCertificateVerifier { fn verify_tls12_signature( &self, _message: &[u8], - _cert: &Certificate, + _cert: &CertificateDer, _dss: &DigitallySignedStruct, ) -> Result { unreachable!("`PROTOCOL_VERSIONS` only allows TLS 1.3") @@ -139,7 +142,7 @@ impl ServerCertVerifier for Libp2pCertificateVerifier { fn verify_tls13_signature( &self, message: &[u8], - cert: &Certificate, + cert: &CertificateDer, dss: &DigitallySignedStruct, ) -> Result { verify_tls13_signature(cert, dss.scheme, message, dss.signature()) @@ -152,25 +155,25 @@ impl ServerCertVerifier for Libp2pCertificateVerifier { /// libp2p requires the following of X.509 client certificate chains: /// -/// - Exactly one certificate must be presented. In particular, client -/// authentication is mandatory in libp2p. +/// - Exactly one certificate must be presented. In particular, client authentication is mandatory +/// in libp2p. /// - The certificate must be self-signed. -/// - The certificate must have a valid libp2p extension that includes a -/// signature of its public key. +/// - The certificate must have a valid libp2p extension that includes a signature of its public +/// key. impl ClientCertVerifier for Libp2pCertificateVerifier { fn offer_client_auth(&self) -> bool { true } - fn client_auth_root_subjects(&self) -> &[DistinguishedName] { + fn root_hint_subjects(&self) -> &[DistinguishedName] { &[] } fn verify_client_cert( &self, - end_entity: &Certificate, - intermediates: &[Certificate], - _now: std::time::SystemTime, + end_entity: &CertificateDer, + intermediates: &[CertificateDer], + _now: rustls::pki_types::UnixTime, ) -> Result { verify_presented_certs(end_entity, intermediates)?; @@ -180,7 +183,7 @@ impl ClientCertVerifier for Libp2pCertificateVerifier { fn verify_tls12_signature( &self, _message: &[u8], - _cert: &Certificate, + _cert: &CertificateDer, _dss: &DigitallySignedStruct, ) -> Result { unreachable!("`PROTOCOL_VERSIONS` only allows TLS 1.3") @@ -189,7 +192,7 @@ impl ClientCertVerifier for Libp2pCertificateVerifier { fn verify_tls13_signature( &self, message: &[u8], - cert: &Certificate, + cert: &CertificateDer, dss: &DigitallySignedStruct, ) -> Result { verify_tls13_signature(cert, dss.scheme, message, dss.signature()) @@ -207,8 +210,8 @@ impl ClientCertVerifier for Libp2pCertificateVerifier { /// Endpoints MUST abort the connection attempt if more than one certificate is received, /// or if the certificate’s self-signature is not valid. fn verify_presented_certs( - end_entity: &Certificate, - intermediates: &[Certificate], + end_entity: &CertificateDer, + intermediates: &[CertificateDer], ) -> Result { if !intermediates.is_empty() { return Err(rustls::Error::General( @@ -222,7 +225,7 @@ fn verify_presented_certs( } fn verify_tls13_signature( - cert: &Certificate, + cert: &CertificateDer, signature_scheme: SignatureScheme, message: &[u8], signature: &[u8], @@ -237,7 +240,9 @@ impl From for rustls::Error { use webpki::Error::*; match e { BadDer => rustls::Error::InvalidCertificate(CertificateError::BadEncoding), - e => rustls::Error::InvalidCertificate(CertificateError::Other(Arc::new(e))), + e => { + rustls::Error::InvalidCertificate(CertificateError::Other(OtherError(Arc::new(e)))) + } } } } @@ -248,7 +253,9 @@ impl From for rustls::Error { InvalidSignatureForPublicKey => { rustls::Error::InvalidCertificate(CertificateError::BadSignature) } - other => rustls::Error::InvalidCertificate(CertificateError::Other(Arc::new(other))), + other => rustls::Error::InvalidCertificate(CertificateError::Other(OtherError( + Arc::new(other), + ))), } } } diff --git a/transports/tls/tests/smoke.rs b/transports/tls/tests/smoke.rs index d488ae7846a..e335f68a7e4 100644 --- a/transports/tls/tests/smoke.rs +++ b/transports/tls/tests/smoke.rs @@ -1,10 +1,6 @@ use futures::{future, StreamExt}; -use libp2p_core::multiaddr::Protocol; -use libp2p_core::transport::MemoryTransport; -use libp2p_core::upgrade::Version; -use libp2p_core::Transport; +use libp2p_core::{multiaddr::Protocol, transport::MemoryTransport, upgrade::Version, Transport}; use libp2p_swarm::{dummy, Config, Swarm, SwarmEvent}; -use std::time::Duration; #[tokio::test] async fn can_establish_connection() { @@ -69,6 +65,6 @@ fn make_swarm() -> Swarm { transport, dummy::Behaviour, identity.public().to_peer_id(), - Config::with_tokio_executor().with_idle_connection_timeout(Duration::from_secs(60)), + Config::with_tokio_executor(), ) } diff --git a/transports/uds/CHANGELOG.md b/transports/uds/CHANGELOG.md index aad61d21547..aa068fe3877 100644 --- a/transports/uds/CHANGELOG.md +++ b/transports/uds/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.41.0 + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) + ## 0.40.0 diff --git a/transports/uds/Cargo.toml b/transports/uds/Cargo.toml index bcf7570c44f..f70ac388fa8 100644 --- a/transports/uds/Cargo.toml +++ b/transports/uds/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-uds" edition = "2021" rust-version = { workspace = true } description = "Unix domain sockets transport for libp2p" -version = "0.40.0" +version = "0.41.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -13,19 +13,17 @@ categories = ["network-programming", "asynchronous"] [dependencies] async-std = { version = "1.6.2", optional = true } libp2p-core = { workspace = true } -futures = "0.3.29" -tokio = { version = "1.34", default-features = false, features = ["net"], optional = true } -tracing = "0.1.37" +futures = { workspace = true } +tokio = { workspace = true, default-features = false, features = ["net"], optional = true } +tracing = { workspace = true } [dev-dependencies] -tempfile = "3.8" +tempfile = "3.10" # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/uds/src/lib.rs b/transports/uds/src/lib.rs index 075cbadb80a..74e19476595 100644 --- a/transports/uds/src/lib.rs +++ b/transports/uds/src/lib.rs @@ -38,21 +38,24 @@ ))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -use futures::stream::BoxStream; +use std::{ + collections::VecDeque, + io, + path::PathBuf, + pin::Pin, + task::{Context, Poll}, +}; + use futures::{ future::{BoxFuture, Ready}, prelude::*, + stream::BoxStream, }; -use libp2p_core::transport::ListenerId; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, - transport::{TransportError, TransportEvent}, + transport::{DialOpts, ListenerId, TransportError, TransportEvent}, Transport, }; -use std::collections::VecDeque; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{io, path::PathBuf}; pub type Listener = BoxStream< 'static, @@ -159,7 +162,7 @@ macro_rules! codegen { } } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial(&mut self, addr: Multiaddr, _dial_opts: DialOpts) -> Result> { // TODO: Should we dial at all? if let Ok(path) = multiaddr_to_path(&addr) { tracing::debug!(address=%addr, "Dialing address"); @@ -169,21 +172,6 @@ macro_rules! codegen { } } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - self.dial(addr) - } - - fn address_translation( - &self, - _server: &Multiaddr, - _observed: &Multiaddr, - ) -> Option { - None - } - fn poll( mut self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -256,14 +244,16 @@ fn multiaddr_to_path(addr: &Multiaddr) -> Result { #[cfg(all(test, feature = "async-std"))] mod tests { - use super::{multiaddr_to_path, UdsConfig}; + use std::{borrow::Cow, path::Path}; + use futures::{channel::oneshot, prelude::*}; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, - transport::ListenerId, - Transport, + transport::{DialOpts, ListenerId, PortUse}, + Endpoint, Transport, }; - use std::{self, borrow::Cow, path::Path}; + + use super::{multiaddr_to_path, UdsConfig}; #[test] fn multiaddr_to_path_conversion() { @@ -318,7 +308,17 @@ mod tests { async_std::task::block_on(async move { let mut uds = UdsConfig::new(); let addr = rx.await.unwrap(); - let mut socket = uds.dial(addr).unwrap().await.unwrap(); + let mut socket = uds + .dial( + addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap(); let _ = socket.write(&[1, 2, 3]).await.unwrap(); }); } diff --git a/transports/webrtc-websys/CHANGELOG.md b/transports/webrtc-websys/CHANGELOG.md index 634120c53c3..5b8f2efb3b0 100644 --- a/transports/webrtc-websys/CHANGELOG.md +++ b/transports/webrtc-websys/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.4.0-alpha.2 + +- Bump version of web-sys and update `__Nonexhaustive` to `__Invalid`. + See [PR 5569](https://github.com/libp2p/rust-libp2p/pull/5569) + +## 0.4.0-alpha + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) + ## 0.3.0-alpha - Bump version in order to publish a new version dependent on latest `libp2p-core`. diff --git a/transports/webrtc-websys/Cargo.toml b/transports/webrtc-websys/Cargo.toml index a5eb8f0b14d..6d42d74f610 100644 --- a/transports/webrtc-websys/Cargo.toml +++ b/transports/webrtc-websys/Cargo.toml @@ -8,31 +8,24 @@ license = "MIT" name = "libp2p-webrtc-websys" repository = "https://github.com/libp2p/rust-libp2p" rust-version = { workspace = true } -version = "0.3.0-alpha" +version = "0.4.0-alpha.2" publish = true [dependencies] bytes = "1" -futures = "0.3" -futures-timer = "3" -getrandom = { version = "0.2.11", features = ["js"] } +futures = { workspace = true } +getrandom = { workspace = true, features = ["js"] } hex = "0.4.3" js-sys = { version = "0.3" } libp2p-core = { workspace = true } libp2p-identity = { workspace = true } libp2p-webrtc-utils = { workspace = true } send_wrapper = { version = "0.6.0", features = ["futures"] } -serde = { version = "1.0", features = ["derive"] } -thiserror = "1" -tracing = "0.1.37" -wasm-bindgen = { version = "0.2.89" } -wasm-bindgen-futures = { version = "0.4.38" } -web-sys = { version = "0.3.65", features = ["Document", "Location", "MessageEvent", "Navigator", "RtcCertificate", "RtcConfiguration", "RtcDataChannel", "RtcDataChannelEvent", "RtcDataChannelInit", "RtcDataChannelState", "RtcDataChannelType", "RtcPeerConnection", "RtcSdpType", "RtcSessionDescription", "RtcSessionDescriptionInit", "Window"] } - -[dev-dependencies] -hex-literal = "0.4" -libp2p-ping = { workspace = true } -libp2p-swarm = { workspace = true, features = ["wasm-bindgen"] } +thiserror = { workspace = true } +tracing = { workspace = true } +wasm-bindgen = { version = "0.2.90" } +wasm-bindgen-futures = { version = "0.4.42" } +web-sys = { version = "0.3.70", features = ["Document", "Location", "MessageEvent", "Navigator", "RtcCertificate", "RtcConfiguration", "RtcDataChannel", "RtcDataChannelEvent", "RtcDataChannelInit", "RtcDataChannelState", "RtcDataChannelType", "RtcPeerConnection", "RtcSdpType", "RtcSessionDescription", "RtcSessionDescriptionInit", "Window"] } [lints] workspace = true diff --git a/transports/webrtc-websys/src/connection.rs b/transports/webrtc-websys/src/connection.rs index b858237da63..01c1a8b3b60 100644 --- a/transports/webrtc-websys/src/connection.rs +++ b/transports/webrtc-websys/src/connection.rs @@ -1,17 +1,15 @@ //! A libp2p connection backed by an [RtcPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection). -use super::{Error, Stream}; -use crate::stream::DropListener; -use futures::channel::mpsc; -use futures::stream::FuturesUnordered; -use futures::StreamExt; +use std::{ + pin::Pin, + task::{ready, Context, Poll, Waker}, +}; + +use futures::{channel::mpsc, stream::FuturesUnordered, StreamExt}; use js_sys::{Object, Reflect}; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; use libp2p_webrtc_utils::Fingerprint; use send_wrapper::SendWrapper; -use std::pin::Pin; -use std::task::Waker; -use std::task::{ready, Context, Poll}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; use web_sys::{ @@ -19,6 +17,9 @@ use web_sys::{ RtcSessionDescriptionInit, }; +use super::{Error, Stream}; +use crate::stream::DropListener; + /// A WebRTC Connection. /// /// All connections need to be [`Send`] which is why some fields are wrapped in [`SendWrapper`]. @@ -31,7 +32,8 @@ pub struct Connection { closed: bool, /// An [`mpsc::channel`] for all inbound data channels. /// - /// Because the browser's WebRTC API is event-based, we need to use a channel to obtain all inbound data channels. + /// Because the browser's WebRTC API is event-based, we need to use a channel to obtain all + /// inbound data channels. inbound_data_channels: SendWrapper>, /// A list of futures, which, once completed, signal that a [`Stream`] has been dropped. drop_listeners: FuturesUnordered, @@ -43,7 +45,8 @@ pub struct Connection { impl Connection { /// Create a new inner WebRTC Connection pub(crate) fn new(peer_connection: RtcPeerConnection) -> Self { - // An ondatachannel Future enables us to poll for incoming data channel events in poll_incoming + // An ondatachannel Future enables us to poll for incoming data channel events in + // poll_incoming let (mut tx_ondatachannel, rx_ondatachannel) = mpsc::channel(4); // we may get more than one data channel opened on a single peer connection let ondatachannel_closure = Closure::new(move |ev: RtcDataChannelEvent| { @@ -120,7 +123,8 @@ impl StreamMuxer for Connection { Poll::Ready(Ok(stream)) } None => { - // This only happens if the [`RtcPeerConnection::ondatachannel`] closure gets freed which means we are most likely shutting down the connection. + // This only happens if the [`RtcPeerConnection::ondatachannel`] closure gets freed + // which means we are most likely shutting down the connection. tracing::debug!("`Sender` for inbound data channels has been dropped"); Poll::Ready(Err(Error::Connection("connection closed".to_owned()))) } @@ -186,11 +190,11 @@ impl RtcPeerConnection { let certificate = JsFuture::from(certificate_promise).await?; - let mut config = RtcConfiguration::default(); + let config = RtcConfiguration::default(); // wrap certificate in a js Array first before adding it to the config object let certificate_arr = js_sys::Array::new(); certificate_arr.push(&certificate); - config.certificates(&certificate_arr); + config.set_certificates(&certificate_arr); let inner = web_sys::RtcPeerConnection::new_with_configuration(&config)?; @@ -214,8 +218,9 @@ impl RtcPeerConnection { let dc = match negotiated { true => { - let mut options = RtcDataChannelInit::new(); - options.negotiated(true).id(0); // id is only ever set to zero when negotiated is true + let options = RtcDataChannelInit::new(); + options.set_negotiated(true); + options.set_id(0); // id is only ever set to zero when negotiated is true self.inner .create_data_channel_with_data_channel_dict(LABEL, &options) diff --git a/transports/webrtc-websys/src/lib.rs b/transports/webrtc-websys/src/lib.rs index 04fced4111b..07207eb0ae8 100644 --- a/transports/webrtc-websys/src/lib.rs +++ b/transports/webrtc-websys/src/lib.rs @@ -7,7 +7,9 @@ mod stream; mod transport; mod upgrade; -pub use self::connection::Connection; -pub use self::error::Error; -pub use self::stream::Stream; -pub use self::transport::{Config, Transport}; +pub use self::{ + connection::Connection, + error::Error, + stream::Stream, + transport::{Config, Transport}, +}; diff --git a/transports/webrtc-websys/src/sdp.rs b/transports/webrtc-websys/src/sdp.rs index 439182ea4db..628043111ee 100644 --- a/transports/webrtc-websys/src/sdp.rs +++ b/transports/webrtc-websys/src/sdp.rs @@ -1,5 +1,6 @@ -use libp2p_webrtc_utils::Fingerprint; use std::net::SocketAddr; + +use libp2p_webrtc_utils::Fingerprint; use web_sys::{RtcSdpType, RtcSessionDescriptionInit}; /// Creates the SDP answer used by the client. @@ -8,8 +9,8 @@ pub(crate) fn answer( server_fingerprint: Fingerprint, client_ufrag: &str, ) -> RtcSessionDescriptionInit { - let mut answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer); - answer_obj.sdp(&libp2p_webrtc_utils::sdp::answer( + let answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer); + answer_obj.set_sdp(&libp2p_webrtc_utils::sdp::answer( addr, server_fingerprint, client_ufrag, @@ -48,8 +49,8 @@ pub(crate) fn offer(offer: String, client_ufrag: &str) -> RtcSessionDescriptionI tracing::trace!(offer=%munged_sdp_offer, "Created SDP offer"); - let mut offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer); - offer_obj.sdp(&munged_sdp_offer); + let offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer); + offer_obj.set_sdp(&munged_sdp_offer); offer_obj } diff --git a/transports/webrtc-websys/src/stream.rs b/transports/webrtc-websys/src/stream.rs index 812aa5afbbf..ee0183b07f0 100644 --- a/transports/webrtc-websys/src/stream.rs +++ b/transports/webrtc-websys/src/stream.rs @@ -1,11 +1,15 @@ //! The WebRTC [Stream] over the Connection -use self::poll_data_channel::PollDataChannel; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + use futures::{AsyncRead, AsyncWrite}; use send_wrapper::SendWrapper; -use std::pin::Pin; -use std::task::{Context, Poll}; use web_sys::RtcDataChannel; +use self::poll_data_channel::PollDataChannel; + mod poll_data_channel; /// A stream over a WebRTC connection. diff --git a/transports/webrtc-websys/src/stream/poll_data_channel.rs b/transports/webrtc-websys/src/stream/poll_data_channel.rs index 0ee4f7920c9..2abe499afce 100644 --- a/transports/webrtc-websys/src/stream/poll_data_channel.rs +++ b/transports/webrtc-websys/src/stream/poll_data_channel.rs @@ -1,19 +1,23 @@ -use std::cmp::min; -use std::io; -use std::pin::Pin; -use std::rc::Rc; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Mutex; -use std::task::{Context, Poll}; +use std::{ + cmp::min, + io, + pin::Pin, + rc::Rc, + sync::{ + atomic::{AtomicBool, Ordering}, + Mutex, + }, + task::{Context, Poll}, +}; use bytes::BytesMut; -use futures::task::AtomicWaker; -use futures::{AsyncRead, AsyncWrite}; +use futures::{task::AtomicWaker, AsyncRead, AsyncWrite}; use libp2p_webrtc_utils::MAX_MSG_LEN; -use wasm_bindgen::{prelude::*, JsCast}; +use wasm_bindgen::prelude::*; use web_sys::{Event, MessageEvent, RtcDataChannel, RtcDataChannelEvent, RtcDataChannelState}; -/// [`PollDataChannel`] is a wrapper around around [`RtcDataChannel`] which implements [`AsyncRead`] and [`AsyncWrite`]. +/// [`PollDataChannel`] is a wrapper around [`RtcDataChannel`] which implements [`AsyncRead`] and +/// [`AsyncWrite`]. #[derive(Debug, Clone)] pub(crate) struct PollDataChannel { /// The [`RtcDataChannel`] being wrapped. @@ -25,7 +29,8 @@ pub(crate) struct PollDataChannel { /// Waker for when we are waiting for the DC to be opened. open_waker: Rc, - /// Waker for when we are waiting to write (again) to the DC because we previously exceeded the [`MAX_MSG_LEN`] threshold. + /// Waker for when we are waiting to write (again) to the DC because we previously exceeded the + /// [`MAX_MSG_LEN`] threshold. write_waker: Rc, /// Waker for when we are waiting for the DC to be closed. @@ -33,9 +38,11 @@ pub(crate) struct PollDataChannel { /// Whether we've been overloaded with data by the remote. /// - /// This is set to `true` in case `read_buffer` overflows, i.e. the remote is sending us messages faster than we can read them. - /// In that case, we return an [`std::io::Error`] from [`AsyncRead`] or [`AsyncWrite`], depending which one gets called earlier. - /// Failing these will (very likely), cause the application developer to drop the stream which resets it. + /// This is set to `true` in case `read_buffer` overflows, i.e. the remote is sending us + /// messages faster than we can read them. In that case, we return an [`std::io::Error`] + /// from [`AsyncRead`] or [`AsyncWrite`], depending which one gets called earlier. + /// Failing these will (very likely), + /// cause the application developer to drop the stream which resets it. overloaded: Rc, // Store the closures for proper garbage collection. @@ -83,7 +90,9 @@ impl PollDataChannel { inner.set_onclose(Some(on_close_closure.as_ref().unchecked_ref())); let new_data_waker = Rc::new(AtomicWaker::new()); - let read_buffer = Rc::new(Mutex::new(BytesMut::new())); // We purposely don't use `with_capacity` so we don't eagerly allocate `MAX_READ_BUFFER` per stream. + // We purposely don't use `with_capacity` + // so we don't eagerly allocate `MAX_READ_BUFFER` per stream. + let read_buffer = Rc::new(Mutex::new(BytesMut::new())); let overloaded = Rc::new(AtomicBool::new(false)); let on_message_closure = Closure::::new({ @@ -143,7 +152,8 @@ impl PollDataChannel { RtcDataChannelState::Closing | RtcDataChannelState::Closed => { return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())) } - RtcDataChannelState::Open | RtcDataChannelState::__Nonexhaustive => {} + RtcDataChannelState::Open | RtcDataChannelState::__Invalid => {} + _ => {} } if self.overloaded.load(Ordering::SeqCst) { diff --git a/transports/webrtc-websys/src/transport.rs b/transports/webrtc-websys/src/transport.rs index ecf137eab8a..abf02520244 100644 --- a/transports/webrtc-websys/src/transport.rs +++ b/transports/webrtc-websys/src/transport.rs @@ -1,14 +1,18 @@ -use super::upgrade; -use super::Connection; -use super::Error; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + use futures::future::FutureExt; -use libp2p_core::multiaddr::Multiaddr; -use libp2p_core::muxing::StreamMuxerBox; -use libp2p_core::transport::{Boxed, ListenerId, Transport as _, TransportError, TransportEvent}; +use libp2p_core::{ + multiaddr::Multiaddr, + muxing::StreamMuxerBox, + transport::{Boxed, DialOpts, ListenerId, Transport as _, TransportError, TransportEvent}, +}; use libp2p_identity::{Keypair, PeerId}; -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; + +use super::{upgrade, Connection, Error}; /// Config for the [`Transport`]. #[derive(Clone)] @@ -62,7 +66,15 @@ impl libp2p_core::Transport for Transport { false } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + dial_opts: DialOpts, + ) -> Result> { + if dial_opts.role.is_listener() { + return Err(TransportError::MultiaddrNotSupported(addr)); + } + if maybe_local_firefox() { return Err(TransportError::Other( "Firefox does not support WebRTC over localhost or 127.0.0.1" @@ -89,23 +101,12 @@ impl libp2p_core::Transport for Transport { .boxed()) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - Err(TransportError::MultiaddrNotSupported(addr)) - } - fn poll( self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll> { Poll::Pending } - - fn address_translation(&self, _listen: &Multiaddr, _observed: &Multiaddr) -> Option { - None - } } /// Checks if local Firefox. diff --git a/transports/webrtc-websys/src/upgrade.rs b/transports/webrtc-websys/src/upgrade.rs index d42f2e3ae18..b1de908ae82 100644 --- a/transports/webrtc-websys/src/upgrade.rs +++ b/transports/webrtc-websys/src/upgrade.rs @@ -1,13 +1,11 @@ -use super::Error; -use crate::connection::RtcPeerConnection; -use crate::error::AuthenticationError; -use crate::sdp; -use crate::Connection; +use std::net::SocketAddr; + use libp2p_identity::{Keypair, PeerId}; -use libp2p_webrtc_utils::noise; -use libp2p_webrtc_utils::Fingerprint; +use libp2p_webrtc_utils::{noise, Fingerprint}; use send_wrapper::SendWrapper; -use std::net::SocketAddr; + +use super::Error; +use crate::{connection::RtcPeerConnection, error::AuthenticationError, sdp, Connection}; /// Upgrades an outbound WebRTC connection by creating the data channel /// and conducting a Noise handshake diff --git a/transports/webrtc/CHANGELOG.md b/transports/webrtc/CHANGELOG.md index 7cc2b2b63bc..90d4ce83df3 100644 --- a/transports/webrtc/CHANGELOG.md +++ b/transports/webrtc/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.8.0-alpha + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) + +## 0.7.1-alpha + +- Bump `libp2p-webrtc-utils` dependency to `0.2.0`. + See [PR 5118](https://github.com/libp2p/rust-libp2p/pull/5118). + ## 0.7.0-alpha - Bump version in order to publish a new version dependent on latest `libp2p-core`. diff --git a/transports/webrtc/Cargo.toml b/transports/webrtc/Cargo.toml index 214d6778bf0..d43be5720d4 100644 --- a/transports/webrtc/Cargo.toml +++ b/transports/webrtc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libp2p-webrtc" -version = "0.7.0-alpha" +version = "0.8.0-alpha" authors = ["Parity Technologies "] description = "WebRTC transport for libp2p" repository = "https://github.com/libp2p/rust-libp2p" @@ -12,8 +12,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] async-trait = "0.1" -bytes = "1" -futures = "0.3" +futures = { workspace = true } futures-timer = "3" hex = "0.4" if-watch = "3.2" @@ -23,15 +22,14 @@ libp2p-identity = { workspace = true } libp2p-webrtc-utils = { workspace = true } multihash = { workspace = true } rand = "0.8" -rcgen = "0.11.3" -serde = { version = "1.0", features = ["derive"] } -stun = "0.5" -thiserror = "1" -tinytemplate = "1.2" -tokio = { version = "1.34", features = ["net"], optional = true } +rcgen = { workspace = true } +stun = "0.7" +thiserror = { workspace = true } +tokio = { workspace = true, features = ["net"], optional = true } tokio-util = { version = "0.7", features = ["compat"], optional = true } -tracing = "0.1.37" +tracing = { workspace = true } webrtc = { version = "0.9.0", optional = true } +webrtc-ice = "=0.10.0" # smoke tests only work with this version [features] tokio = ["dep:tokio", "dep:tokio-util", "dep:webrtc", "if-watch/tokio"] @@ -39,10 +37,9 @@ pem = ["webrtc?/pem"] [dev-dependencies] libp2p-identity = { workspace = true, features = ["rand"] } -tokio = { version = "1.34", features = ["full"] } +tokio = { workspace = true, features = ["full"] } quickcheck = "1.0.3" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } - +libp2p-test-utils = { workspace = true } [[test]] name = "smoke" @@ -55,5 +52,3 @@ workspace = true # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] diff --git a/transports/webrtc/src/lib.rs b/transports/webrtc/src/lib.rs index ea1e6a4d646..99f0c7da658 100644 --- a/transports/webrtc/src/lib.rs +++ b/transports/webrtc/src/lib.rs @@ -23,7 +23,7 @@ //! //! # Overview //! -//! ## ICE +//!  ## ICE //! //! RFCs: 8839, 8445 See also: //! @@ -39,10 +39,9 @@ //! //! The ICE workflow works as follows: //! -//! - An "offerer" determines ways in which it could be accessible (either an -//! IP address or through a relay using a TURN server), which are called "candidates". It then -//! generates a small text payload in a format called SDP, that describes the request for a -//! connection. +//! - An "offerer" determines ways in which it could be accessible (either an IP address or through +//! a relay using a TURN server), which are called "candidates". It then generates a small text +//! payload in a format called SDP, that describes the request for a connection. //! - The offerer sends this SDP-encoded message to the answerer. The medium through which this //! exchange is done is out of scope of the ICE protocol. //! - The answerer then finds its own candidates, and generates an answer, again in the SDP format. diff --git a/transports/webrtc/src/tokio/certificate.rs b/transports/webrtc/src/tokio/certificate.rs index 7c7c65f0447..7ff35d46bdd 100644 --- a/transports/webrtc/src/tokio/certificate.rs +++ b/transports/webrtc/src/tokio/certificate.rs @@ -32,6 +32,7 @@ impl Certificate { /// Generate new certificate. /// /// `_rng` argument is ignored for now. See . + #[allow(clippy::unnecessary_wraps)] pub fn generate(_rng: &mut R) -> Result where R: CryptoRng + Rng, @@ -99,9 +100,10 @@ enum Kind { #[cfg(all(test, feature = "pem"))] mod test { - use super::*; use rand::thread_rng; + use super::*; + #[test] fn test_certificate_serialize_pem_and_from_pem() { let cert = Certificate::generate(&mut thread_rng()).unwrap(); diff --git a/transports/webrtc/src/tokio/connection.rs b/transports/webrtc/src/tokio/connection.rs index 3bcc4c3193e..19232707e7f 100644 --- a/transports/webrtc/src/tokio/connection.rs +++ b/transports/webrtc/src/tokio/connection.rs @@ -18,26 +18,27 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::stream::FuturesUnordered; +use std::{ + pin::Pin, + sync::Arc, + task::{Context, Poll, Waker}, +}; + use futures::{ channel::{ mpsc, oneshot::{self, Sender}, }, + future::BoxFuture, lock::Mutex as FutMutex, + ready, + stream::FuturesUnordered, StreamExt, - {future::BoxFuture, ready}, }; use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; -use webrtc::data::data_channel::DataChannel as DetachedDataChannel; -use webrtc::data_channel::RTCDataChannel; -use webrtc::peer_connection::RTCPeerConnection; - -use std::task::Waker; -use std::{ - pin::Pin, - sync::Arc, - task::{Context, Poll}, +use webrtc::{ + data::data_channel::DataChannel as DetachedDataChannel, data_channel::RTCDataChannel, + peer_connection::RTCPeerConnection, }; use crate::tokio::{error::Error, stream, stream::Stream}; @@ -172,7 +173,9 @@ impl StreamMuxer for Connection { "Sender-end of channel should be owned by `RTCPeerConnection`" ); - Poll::Pending // Return `Pending` without registering a waker: If the channel is closed, we don't need to be called anymore. + // Return `Pending` without registering a waker: If the channel is + // closed, we don't need to be called anymore. + Poll::Pending } } } diff --git a/transports/webrtc/src/tokio/req_res_chan.rs b/transports/webrtc/src/tokio/req_res_chan.rs index fb29e16db27..a733c86d5cc 100644 --- a/transports/webrtc/src/tokio/req_res_chan.rs +++ b/transports/webrtc/src/tokio/req_res_chan.rs @@ -18,16 +18,16 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::{ - channel::{mpsc, oneshot}, - SinkExt, StreamExt, -}; - use std::{ io, task::{Context, Poll}, }; +use futures::{ + channel::{mpsc, oneshot}, + SinkExt, StreamExt, +}; + pub(crate) fn new(capacity: usize) -> (Sender, Receiver) { let (sender, receiver) = mpsc::channel(capacity); diff --git a/transports/webrtc/src/tokio/sdp.rs b/transports/webrtc/src/tokio/sdp.rs index 8549a864dcc..d9f869d4433 100644 --- a/transports/webrtc/src/tokio/sdp.rs +++ b/transports/webrtc/src/tokio/sdp.rs @@ -18,10 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -pub(crate) use libp2p_webrtc_utils::sdp::random_ufrag; -use libp2p_webrtc_utils::sdp::render_description; -use libp2p_webrtc_utils::Fingerprint; use std::net::SocketAddr; + +pub(crate) use libp2p_webrtc_utils::sdp::random_ufrag; +use libp2p_webrtc_utils::{sdp::render_description, Fingerprint}; use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; /// Creates the SDP answer used by the client. @@ -98,7 +98,7 @@ pub(crate) fn offer(addr: SocketAddr, client_ufrag: &str) -> RTCSessionDescripti // // a=ice-options:ice2 // -// Indicates that we are complying with RFC8839 (as oppposed to the legacy RFC5245). +// Indicates that we are complying with RFC8839 (as opposed to the legacy RFC5245). // // a=ice-ufrag: // a=ice-pwd: diff --git a/transports/webrtc/src/tokio/stream.rs b/transports/webrtc/src/tokio/stream.rs index 4278a751e27..9d5a9faf440 100644 --- a/transports/webrtc/src/tokio/stream.rs +++ b/transports/webrtc/src/tokio/stream.rs @@ -40,8 +40,8 @@ pub struct Stream { pub(crate) type DropListener = libp2p_webrtc_utils::DropListener>; impl Stream { - /// Returns a new `Substream` and a listener, which will notify the receiver when/if the substream - /// is dropped. + /// Returns a new `Substream` and a listener, which will notify the receiver when/if the + /// substream is dropped. pub(crate) fn new(data_channel: Arc) -> (Self, DropListener) { let mut data_channel = PollDataChannel::new(data_channel).compat(); data_channel.get_mut().set_read_buf_capacity(MAX_MSG_LEN); diff --git a/transports/webrtc/src/tokio/transport.rs b/transports/webrtc/src/tokio/transport.rs index 02cfa6f7296..29fad180d93 100644 --- a/transports/webrtc/src/tokio/transport.rs +++ b/transports/webrtc/src/tokio/transport.rs @@ -18,24 +18,23 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::{future::BoxFuture, prelude::*, stream::SelectAll, stream::Stream}; +use std::{ + io, + net::{IpAddr, SocketAddr}, + pin::Pin, + task::{Context, Poll, Waker}, +}; + +use futures::{future::BoxFuture, prelude::*, stream::SelectAll}; use if_watch::{tokio::IfWatcher, IfEvent}; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, - transport::{ListenerId, TransportError, TransportEvent}, + transport::{DialOpts, ListenerId, TransportError, TransportEvent}, }; use libp2p_identity as identity; use libp2p_identity::PeerId; use webrtc::peer_connection::configuration::RTCConfiguration; -use std::net::IpAddr; -use std::{ - io, - net::SocketAddr, - pin::Pin, - task::{Context, Poll, Waker}, -}; - use crate::tokio::{ certificate::Certificate, connection::Connection, @@ -60,8 +59,8 @@ impl Transport { /// /// ``` /// use libp2p_identity as identity; + /// use libp2p_webrtc::tokio::{Certificate, Transport}; /// use rand::thread_rng; - /// use libp2p_webrtc::tokio::{Transport, Certificate}; /// /// let id_keys = identity::Keypair::generate_ed25519(); /// let transport = Transport::new(id_keys, Certificate::generate(&mut thread_rng()).unwrap()); @@ -118,7 +117,19 @@ impl libp2p_core::Transport for Transport { } } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + dial_opts: DialOpts, + ) -> Result> { + if dial_opts.role.is_listener() { + // TODO: As the listener of a WebRTC hole punch, we need to send a random UDP packet to + // the `addr`. See DCUtR specification below. + // + // https://github.com/libp2p/specs/blob/master/relay/DCUtR.md#the-protocol + tracing::warn!("WebRTC hole punch is not yet supported"); + } + let (sock_addr, server_fingerprint) = libp2p_webrtc_utils::parse_webrtc_dial_addr(&addr) .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; if sock_addr.port() == 0 || sock_addr.ip().is_unspecified() { @@ -150,21 +161,6 @@ impl libp2p_core::Transport for Transport { } .boxed()) } - - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - // TODO: As the listener of a WebRTC hole punch, we need to send a random UDP packet to the - // `addr`. See DCUtR specification below. - // - // https://github.com/libp2p/specs/blob/master/relay/DCUtR.md#the-protocol - self.dial(addr) - } - - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - libp2p_core::address_translation(server, observed) - } } /// A stream of incoming connections on one or more interfaces. @@ -429,11 +425,13 @@ fn parse_webrtc_listen_addr(addr: &Multiaddr) -> Option { #[cfg(test)] mod tests { - use super::*; + use std::net::Ipv6Addr; + use futures::future::poll_fn; - use libp2p_core::{multiaddr::Protocol, Transport as _}; + use libp2p_core::Transport as _; use rand::thread_rng; - use std::net::{IpAddr, Ipv6Addr}; + + use super::*; #[test] fn missing_webrtc_protocol() { diff --git a/transports/webrtc/src/tokio/udp_mux.rs b/transports/webrtc/src/tokio/udp_mux.rs index 20e04edaf72..dcb88592c9b 100644 --- a/transports/webrtc/src/tokio/udp_mux.rs +++ b/transports/webrtc/src/tokio/udp_mux.rs @@ -18,6 +18,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::{ + collections::{HashMap, HashSet}, + io, + io::ErrorKind, + net::SocketAddr, + sync::Arc, + task::{Context, Poll}, +}; + use async_trait::async_trait; use futures::{ channel::oneshot, @@ -31,16 +40,9 @@ use stun::{ }; use thiserror::Error; use tokio::{io::ReadBuf, net::UdpSocket}; -use webrtc::ice::udp_mux::{UDPMux, UDPMuxConn, UDPMuxConnParams, UDPMuxWriter}; -use webrtc::util::{Conn, Error}; - -use std::{ - collections::{HashMap, HashSet}, - io, - io::ErrorKind, - net::SocketAddr, - sync::Arc, - task::{Context, Poll}, +use webrtc::{ + ice::udp_mux::{UDPMux, UDPMuxConn, UDPMuxConnParams, UDPMuxWriter}, + util::{Conn, Error}, }; use crate::tokio::req_res_chan; @@ -303,8 +305,8 @@ impl UDPMuxNewAddr { if let Poll::Ready(Some((ufrag, response))) = self.remove_conn_command.poll_next_unpin(cx) { - // Pion's ice implementation has both `RemoveConnByFrag` and `RemoveConn`, but since `conns` - // is keyed on `ufrag` their implementation is equivalent. + // Pion's ice implementation has both `RemoveConnByFrag` and `RemoveConn`, but since + // `conns` is keyed on `ufrag` their implementation is equivalent. if let Some(removed_conn) = self.conns.remove(&ufrag) { for address in removed_conn.get_addresses() { @@ -336,8 +338,9 @@ impl UDPMuxNewAddr { let conn = self.address_map.get(&addr); let conn = match conn { - // If we couldn't find the connection based on source address, see if - // this is a STUN mesage and if so if we can find the connection based on ufrag. + // If we couldn't find the connection based on source address, see + // if this is a STUN message and if + // so if we can find the connection based on ufrag. None if is_stun_message(read.filled()) => { match self.conn_from_stun_message(read.filled(), &addr) { Some(Ok(s)) => Some(s), diff --git a/transports/webrtc/src/tokio/upgrade.rs b/transports/webrtc/src/tokio/upgrade.rs index 4145a5e7510..9293a474084 100644 --- a/transports/webrtc/src/tokio/upgrade.rs +++ b/transports/webrtc/src/tokio/upgrade.rs @@ -18,27 +18,23 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use libp2p_webrtc_utils::{noise, Fingerprint}; +use std::{net::SocketAddr, sync::Arc, time::Duration}; -use futures::channel::oneshot; -use futures::future::Either; +use futures::{channel::oneshot, future::Either}; use futures_timer::Delay; use libp2p_identity as identity; use libp2p_identity::PeerId; -use std::{net::SocketAddr, sync::Arc, time::Duration}; -use webrtc::api::setting_engine::SettingEngine; -use webrtc::api::APIBuilder; -use webrtc::data::data_channel::DataChannel; -use webrtc::data_channel::data_channel_init::RTCDataChannelInit; -use webrtc::dtls_transport::dtls_role::DTLSRole; -use webrtc::ice::network_type::NetworkType; -use webrtc::ice::udp_mux::UDPMux; -use webrtc::ice::udp_network::UDPNetwork; -use webrtc::peer_connection::configuration::RTCConfiguration; -use webrtc::peer_connection::RTCPeerConnection; - -use crate::tokio::sdp::random_ufrag; -use crate::tokio::{error::Error, sdp, stream::Stream, Connection}; +use libp2p_webrtc_utils::{noise, Fingerprint}; +use webrtc::{ + api::{setting_engine::SettingEngine, APIBuilder}, + data::data_channel::DataChannel, + data_channel::data_channel_init::RTCDataChannelInit, + dtls_transport::dtls_role::DTLSRole, + ice::{network_type::NetworkType, udp_mux::UDPMux, udp_network::UDPNetwork}, + peer_connection::{configuration::RTCConfiguration, RTCPeerConnection}, +}; + +use crate::tokio::{error::Error, sdp, sdp::random_ufrag, stream::Stream, Connection}; /// Creates a new outbound WebRTC connection. pub(crate) async fn outbound( diff --git a/transports/webrtc/tests/smoke.rs b/transports/webrtc/tests/smoke.rs index 76e168edfd6..e27e3cee672 100644 --- a/transports/webrtc/tests/smoke.rs +++ b/transports/webrtc/tests/smoke.rs @@ -18,28 +18,34 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::channel::mpsc; -use futures::future::{BoxFuture, Either}; -use futures::stream::StreamExt; -use futures::{future, ready, AsyncReadExt, AsyncWriteExt, FutureExt, SinkExt}; -use libp2p_core::muxing::{StreamMuxerBox, StreamMuxerExt}; -use libp2p_core::transport::{Boxed, ListenerId, TransportEvent}; -use libp2p_core::{Multiaddr, Transport}; +use std::{ + future::Future, + num::NonZeroU8, + pin::Pin, + task::{Context, Poll}, + time::Duration, +}; + +use futures::{ + channel::mpsc, + future, + future::{BoxFuture, Either}, + ready, + stream::StreamExt, + AsyncReadExt, AsyncWriteExt, FutureExt, SinkExt, +}; +use libp2p_core::{ + muxing::{StreamMuxerBox, StreamMuxerExt}, + transport::{Boxed, DialOpts, ListenerId, PortUse, TransportEvent}, + Endpoint, Multiaddr, Transport, +}; use libp2p_identity::PeerId; use libp2p_webrtc as webrtc; use rand::{thread_rng, RngCore}; -use std::future::Future; -use std::num::NonZeroU8; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::time::Duration; -use tracing_subscriber::EnvFilter; #[tokio::test] async fn smoke() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let (a_peer_id, mut a_transport) = create_transport(); let (b_peer_id, mut b_transport) = create_transport(); @@ -56,9 +62,7 @@ async fn smoke() { // Note: This test should likely be ported to the muxer compliance test suite. #[test] fn concurrent_connections_and_streams_tokio() { - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); + libp2p_test_utils::with_default_env_filter(); let rt = tokio::runtime::Runtime::new().unwrap(); let _guard = rt.enter(); @@ -322,7 +326,17 @@ struct Dial<'a> { impl<'a> Dial<'a> { fn new(dialer: &'a mut Boxed<(PeerId, StreamMuxerBox)>, addr: Multiaddr) -> Self { Self { - dial_task: dialer.dial(addr).unwrap().map(|r| r.unwrap()).boxed(), + dial_task: dialer + .dial( + addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .map(|r| r.unwrap()) + .boxed(), dialer, } } diff --git a/transports/websocket-websys/CHANGELOG.md b/transports/websocket-websys/CHANGELOG.md index 3cfb1b2fbf9..affe9ff2551 100644 --- a/transports/websocket-websys/CHANGELOG.md +++ b/transports/websocket-websys/CHANGELOG.md @@ -1,3 +1,30 @@ +## 0.4.1 + +- fix: Return `None` when extracting a `/dnsaddr` address + See [PR 5613](https://github.com/libp2p/rust-libp2p/pull/5613) + +- Fix `cargo clippy` warnings in `rustc 1.84.0-beta.1`. + See [PR 5700](https://github.com/libp2p/rust-libp2p/pull/5700). + +## 0.4.0 + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) +- Add support for `/tls/ws` and keep `/wss` backward compatible. + See [PR 5523](https://github.com/libp2p/rust-libp2p/pull/5523). + +## 0.3.3 + +- Fix use-after-free handler invocation from JS side. + See [PR 5521](https://github.com/libp2p/rust-libp2p/pull/5521). + +## 0.3.2 + +- Change close code in drop implementation to `1000` given that in browsers only + the code `1000` and codes between `3000` and `4999` are allowed to be set by + userland code. + See [PR 5229](https://github.com/libp2p/rust-libp2p/pull/5229). + ## 0.3.1 - Add support for different WASM environments by introducing a `WebContext` that diff --git a/transports/websocket-websys/Cargo.toml b/transports/websocket-websys/Cargo.toml index b0493533e26..f33703c1884 100644 --- a/transports/websocket-websys/Cargo.toml +++ b/transports/websocket-websys/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-websocket-websys" edition = "2021" rust-version = "1.60.0" description = "WebSocket for libp2p under WASM environment" -version = "0.3.1" +version = "0.4.1" authors = ["Vince Vasta "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,23 +11,20 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -bytes = "1.4.0" -futures = "0.3.29" -js-sys = "0.3.66" +bytes = "1.6.0" +futures = { workspace = true } +js-sys = "0.3.69" libp2p-core = { workspace = true } -tracing = "0.1.37" -parking_lot = "0.12.1" +tracing = { workspace = true } send_wrapper = "0.6.0" -thiserror = "1.0.50" -wasm-bindgen = "0.2.89" -web-sys = { version = "0.3.65", features = ["BinaryType", "CloseEvent", "MessageEvent", "WebSocket", "Window", "WorkerGlobalScope"] } +thiserror = { workspace = true } +wasm-bindgen = "0.2.90" +web-sys = { version = "0.3.69", features = ["BinaryType", "CloseEvent", "MessageEvent", "WebSocket", "Window", "WorkerGlobalScope"] } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [dev-dependencies] libp2p-yamux = { workspace = true } diff --git a/transports/websocket-websys/src/lib.rs b/transports/websocket-websys/src/lib.rs index 5c1a6ebf1c4..72f4068610d 100644 --- a/transports/websocket-websys/src/lib.rs +++ b/transports/websocket-websys/src/lib.rs @@ -20,23 +20,30 @@ //! Libp2p websocket transports built on [web-sys](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html). +#![allow(unexpected_cfgs)] + mod web_context; +use std::{ + cmp::min, + pin::Pin, + rc::Rc, + sync::{ + atomic::{AtomicBool, Ordering}, + Mutex, + }, + task::{Context, Poll}, +}; + use bytes::BytesMut; -use futures::task::AtomicWaker; -use futures::{future::Ready, io, prelude::*}; +use futures::{future::Ready, io, prelude::*, task::AtomicWaker}; use js_sys::Array; use libp2p_core::{ multiaddr::{Multiaddr, Protocol}, - transport::{ListenerId, TransportError, TransportEvent}, + transport::{DialOpts, ListenerId, TransportError, TransportEvent}, }; use send_wrapper::SendWrapper; -use std::cmp::min; -use std::rc::Rc; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Mutex; -use std::{pin::Pin, task::Context, task::Poll}; -use wasm_bindgen::{prelude::*, JsCast}; +use wasm_bindgen::prelude::*; use web_sys::{CloseEvent, Event, MessageEvent, WebSocket}; use crate::web_context::WebContext; @@ -59,7 +66,6 @@ use crate::web_context::WebContext; /// .multiplex(yamux::Config::default()) /// .boxed(); /// ``` -/// #[derive(Default)] pub struct Transport { _private: (), @@ -86,9 +92,17 @@ impl libp2p_core::Transport for Transport { false } - fn dial(&mut self, addr: Multiaddr) -> Result> { - let url = extract_websocket_url(&addr) - .ok_or_else(|| TransportError::MultiaddrNotSupported(addr))?; + fn dial( + &mut self, + addr: Multiaddr, + dial_opts: DialOpts, + ) -> Result> { + if dial_opts.role.is_listener() { + return Err(TransportError::MultiaddrNotSupported(addr)); + } + + let url = + extract_websocket_url(&addr).ok_or(TransportError::MultiaddrNotSupported(addr))?; Ok(async move { let socket = match WebSocket::new(&url) { @@ -101,23 +115,12 @@ impl libp2p_core::Transport for Transport { .boxed()) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - Err(TransportError::MultiaddrNotSupported(addr)) - } - fn poll( self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> std::task::Poll> { Poll::Pending } - - fn address_translation(&self, _listen: &Multiaddr, _observed: &Multiaddr) -> Option { - None - } } // Try to convert Multiaddr to a Websocket url. @@ -132,16 +135,16 @@ fn extract_websocket_url(addr: &Multiaddr) -> Option { } (Some(Protocol::Dns(h)), Some(Protocol::Tcp(port))) | (Some(Protocol::Dns4(h)), Some(Protocol::Tcp(port))) - | (Some(Protocol::Dns6(h)), Some(Protocol::Tcp(port))) - | (Some(Protocol::Dnsaddr(h)), Some(Protocol::Tcp(port))) => { + | (Some(Protocol::Dns6(h)), Some(Protocol::Tcp(port))) => { format!("{}:{}", &h, port) } _ => return None, }; - let (scheme, wspath) = match protocols.next() { - Some(Protocol::Ws(path)) => ("ws", path.into_owned()), - Some(Protocol::Wss(path)) => ("wss", path.into_owned()), + let (scheme, wspath) = match (protocols.next(), protocols.next()) { + (Some(Protocol::Tls), Some(Protocol::Ws(path))) => ("wss", path.into_owned()), + (Some(Protocol::Ws(path)), _) => ("ws", path.into_owned()), + (Some(Protocol::Wss(path)), _) => ("wss", path.into_owned()), _ => return None, }; @@ -176,7 +179,8 @@ struct Inner { /// Waker for when we are waiting for the WebSocket to be opened. open_waker: Rc, - /// Waker for when we are waiting to write (again) to the WebSocket because we previously exceeded the [`MAX_BUFFER`] threshold. + /// Waker for when we are waiting to write (again) to the WebSocket because we previously + /// exceeded the [`MAX_BUFFER`] threshold. write_waker: Rc, /// Waker for when we are waiting for the WebSocket to be closed. @@ -308,7 +312,9 @@ impl Connection { .expect("to have a window or worker context") .set_interval_with_callback_and_timeout_and_arguments( on_buffered_amount_low_closure.as_ref().unchecked_ref(), - 100, // Chosen arbitrarily and likely worth tuning. Due to low impact of the /ws transport, no further effort was invested at the time. + // Chosen arbitrarily and likely worth tuning. Due to low impact of the /ws + // transport, no further effort was invested at the time. + 100, &Array::new(), ) .expect("to be able to set an interval"); @@ -434,13 +440,21 @@ impl AsyncWrite for Connection { impl Drop for Connection { fn drop(&mut self) { - const GO_AWAY_STATUS_CODE: u16 = 1001; // See https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1. + // Unset event listeners, as otherwise they will be called by JS after the handlers have + // already been dropped. + self.inner.socket.set_onclose(None); + self.inner.socket.set_onerror(None); + self.inner.socket.set_onopen(None); + self.inner.socket.set_onmessage(None); + + // In browsers, userland code is not allowed to use any other status code than 1000: https://websockets.spec.whatwg.org/#dom-websocket-close + const REGULAR_CLOSE: u16 = 1000; // See https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1. if let ReadyState::Connecting | ReadyState::Open = self.inner.ready_state() { let _ = self .inner .socket - .close_with_code_and_reason(GO_AWAY_STATUS_CODE, "connection dropped"); + .close_with_code_and_reason(REGULAR_CLOSE, "connection dropped"); } WebContext::new() @@ -448,3 +462,110 @@ impl Drop for Connection { .clear_interval_with_handle(self.inner.buffered_amount_low_interval); } } + +#[cfg(test)] +mod tests { + use libp2p_identity::PeerId; + + use super::*; + + #[test] + fn extract_url() { + let peer_id = PeerId::random(); + + // Check `/tls/ws` + let addr = "/dns4/example.com/tcp/2222/tls/ws" + .parse::() + .unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://example.com:2222/"); + + // Check `/tls/ws` with `/p2p` + let addr = format!("/dns4/example.com/tcp/2222/tls/ws/p2p/{peer_id}") + .parse() + .unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://example.com:2222/"); + + // Check `/tls/ws` with `/ip4` + let addr = "/ip4/127.0.0.1/tcp/2222/tls/ws" + .parse::() + .unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://127.0.0.1:2222/"); + + // Check `/tls/ws` with `/ip6` + let addr = "/ip6/::1/tcp/2222/tls/ws".parse::().unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://[::1]:2222/"); + + // Check `/wss` + let addr = "/dns4/example.com/tcp/2222/wss" + .parse::() + .unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://example.com:2222/"); + + // Check `/wss` with `/p2p` + let addr = format!("/dns4/example.com/tcp/2222/wss/p2p/{peer_id}") + .parse() + .unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://example.com:2222/"); + + // Check `/wss` with `/ip4` + let addr = "/ip4/127.0.0.1/tcp/2222/wss".parse::().unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://127.0.0.1:2222/"); + + // Check `/wss` with `/ip6` + let addr = "/ip6/::1/tcp/2222/wss".parse::().unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "wss://[::1]:2222/"); + + // Check `/ws` + let addr = "/dns4/example.com/tcp/2222/ws" + .parse::() + .unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "ws://example.com:2222/"); + + // Check `/ws` with `/p2p` + let addr = format!("/dns4/example.com/tcp/2222/ws/p2p/{peer_id}") + .parse() + .unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "ws://example.com:2222/"); + + // Check `/ws` with `/ip4` + let addr = "/ip4/127.0.0.1/tcp/2222/ws".parse::().unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "ws://127.0.0.1:2222/"); + + // Check `/ws` with `/ip6` + let addr = "/ip6/::1/tcp/2222/ws".parse::().unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "ws://[::1]:2222/"); + + // Check `/ws` with `/ip4` + let addr = "/ip4/127.0.0.1/tcp/2222/ws".parse::().unwrap(); + let url = extract_websocket_url(&addr).unwrap(); + assert_eq!(url, "ws://127.0.0.1:2222/"); + + // Check that `/tls/wss` is invalid + let addr = "/ip4/127.0.0.1/tcp/2222/tls/wss" + .parse::() + .unwrap(); + assert!(extract_websocket_url(&addr).is_none()); + + // Check `/dnsaddr` + let addr = "/dnsaddr/example.com/tcp/2222/ws" + .parse::() + .unwrap(); + assert!(extract_websocket_url(&addr).is_none()); + + // Check non-ws address + let addr = "/ip4/127.0.0.1/tcp/2222".parse::().unwrap(); + assert!(extract_websocket_url(&addr).is_none()); + } +} diff --git a/transports/websocket-websys/src/web_context.rs b/transports/websocket-websys/src/web_context.rs index c514435d2bb..40efbb1d9b0 100644 --- a/transports/websocket-websys/src/web_context.rs +++ b/transports/websocket-websys/src/web_context.rs @@ -1,4 +1,7 @@ -use wasm_bindgen::{prelude::*, JsCast}; +// TODO: remove when https://github.com/rust-lang/rust-clippy/issues/12377 fix lands in stable clippy. +#![allow(clippy::empty_docs)] + +use wasm_bindgen::prelude::*; use web_sys::window; /// Web context that abstract the window vs web worker API diff --git a/transports/websocket/CHANGELOG.md b/transports/websocket/CHANGELOG.md index 192b1fa094e..1a8e9569c06 100644 --- a/transports/websocket/CHANGELOG.md +++ b/transports/websocket/CHANGELOG.md @@ -1,3 +1,25 @@ +## 0.44.1 + +- fix: Return `Error::InvalidMultiaddr` when dialed to a `/dnsaddr` address + See [PR 5613](https://github.com/libp2p/rust-libp2p/pull/5613) + +## 0.44.0 + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) +- Allow wss connections on IP addresses. + See [PR 5525](https://github.com/libp2p/rust-libp2p/pull/5525). +- Add support for `/tls/ws` and keep `/wss` backward compatible. + See [PR 5523](https://github.com/libp2p/rust-libp2p/pull/5523). + +## 0.43.2 + +- fix: Avoid websocket panic on polling after errors. See [PR 5482]. + +[PR 5482]: https://github.com/libp2p/rust-libp2p/pull/5482 + +## 0.43.1 + ## 0.43.0 diff --git a/transports/websocket/Cargo.toml b/transports/websocket/Cargo.toml index 31f91874f68..5c9734e420a 100644 --- a/transports/websocket/Cargo.toml +++ b/transports/websocket/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-websocket" edition = "2021" rust-version = { workspace = true } description = "WebSocket transport for libp2p" -version = "0.43.0" +version = "0.44.1" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" @@ -11,16 +11,17 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -futures-rustls = "0.24.0" -either = "1.9.0" -futures = "0.3.29" +futures-rustls = { workspace = true, features = ["ring"] } +either = "1.12.0" +futures = { workspace = true } libp2p-core = { workspace = true } libp2p-identity = { workspace = true } -parking_lot = "0.12.0" -pin-project-lite = "0.2.13" +parking_lot = "0.12.3" +pin-project-lite = "0.2.14" rw-stream-sink = { workspace = true } -soketto = "0.7.0" -tracing = "0.1.37" +soketto = "0.8.0" +tracing = { workspace = true } +thiserror = { workspace = true } url = "2.5" webpki-roots = "0.25" @@ -29,14 +30,12 @@ libp2p-tcp = { workspace = true, features = ["async-io"] } libp2p-dns = { workspace = true, features = ["async-std"] } libp2p-identity = { workspace = true, features = ["rand"] } async-std = { version = "1.6.5", features = ["attributes"] } -rcgen = "0.11.3" +rcgen = { workspace = true } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/websocket/src/error.rs b/transports/websocket/src/error.rs index 7dc22331bcd..efab95a7621 100644 --- a/transports/websocket/src/error.rs +++ b/transports/websocket/src/error.rs @@ -18,10 +18,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::tls; -use libp2p_core::Multiaddr; use std::{error, fmt}; +use libp2p_core::Multiaddr; + +use crate::tls; + /// Error in WebSockets. #[derive(Debug)] pub enum Error { diff --git a/transports/websocket/src/framed.rs b/transports/websocket/src/framed.rs index 3593e1eaff2..3d2738cbc3c 100644 --- a/transports/websocket/src/framed.rs +++ b/transports/websocket/src/framed.rs @@ -18,14 +18,23 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{error::Error, quicksink, tls}; +use std::{ + borrow::Cow, + collections::HashMap, + fmt, io, mem, + net::IpAddr, + ops::DerefMut, + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; + use either::Either; use futures::{future::BoxFuture, prelude::*, ready, stream::BoxStream}; -use futures_rustls::{client, rustls, server}; +use futures_rustls::{client, rustls::pki_types::ServerName, server}; use libp2p_core::{ - connection::Endpoint, multiaddr::{Multiaddr, Protocol}, - transport::{ListenerId, TransportError, TransportEvent}, + transport::{DialOpts, ListenerId, TransportError, TransportEvent}, Transport, }; use parking_lot::Mutex; @@ -33,10 +42,10 @@ use soketto::{ connection::{self, CloseReason}, handshake, }; -use std::{collections::HashMap, ops::DerefMut, sync::Arc}; -use std::{convert::TryInto, fmt, io, mem, pin::Pin, task::Context, task::Poll}; use url::Url; +use crate::{error::Error, quicksink, tls}; + /// Max. number of payload bytes of a single frame. const MAX_DATA_SIZE: usize = 256 * 1024 * 1024; @@ -50,10 +59,7 @@ pub struct WsConfig { tls_config: tls::Config, max_redirects: u8, /// Websocket protocol of the inner listener. - /// - /// This is the suffix of the address provided in `listen_on`. - /// Can only be [`Protocol::Ws`] or [`Protocol::Wss`]. - listener_protos: HashMap>, + listener_protos: HashMap>, } impl WsConfig @@ -120,22 +126,19 @@ where id: ListenerId, addr: Multiaddr, ) -> Result<(), TransportError> { - let mut inner_addr = addr.clone(); - let proto = match inner_addr.pop() { - Some(p @ Protocol::Wss(_)) => { - if self.tls_config.server.is_some() { - p - } else { - tracing::debug!("/wss address but TLS server support is not configured"); - return Err(TransportError::MultiaddrNotSupported(addr)); - } - } - Some(p @ Protocol::Ws(_)) => p, - _ => { - tracing::debug!(address=%addr, "Address is not a websocket multiaddr"); - return Err(TransportError::MultiaddrNotSupported(addr)); - } - }; + let (inner_addr, proto) = parse_ws_listen_addr(&addr).ok_or_else(|| { + tracing::debug!(address=%addr, "Address is not a websocket multiaddr"); + TransportError::MultiaddrNotSupported(addr.clone()) + })?; + + if proto.use_tls() && self.tls_config.server.is_none() { + tracing::debug!( + "{} address but TLS server support is not configured", + proto.prefix() + ); + return Err(TransportError::MultiaddrNotSupported(addr)); + } + match self.transport.lock().listen_on(id, inner_addr) { Ok(()) => { self.listener_protos.insert(id, proto); @@ -149,19 +152,12 @@ where self.transport.lock().remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - self.do_dial(addr, Endpoint::Dialer) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + dial_opts: DialOpts, ) -> Result> { - self.do_dial(addr, Endpoint::Listener) - } - - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.transport.lock().address_translation(server, observed) + self.do_dial(addr, dial_opts) } fn poll( @@ -181,11 +177,10 @@ where mut listen_addr, } => { // Append the ws / wss protocol back to the inner address. - let proto = self - .listener_protos + self.listener_protos .get(&listener_id) - .expect("Protocol was inserted in Transport::listen_on."); - listen_addr.push(proto.clone()); + .expect("Protocol was inserted in Transport::listen_on.") + .append_on_addr(&mut listen_addr); tracing::debug!(address=%listen_addr, "Listening on address"); TransportEvent::NewAddress { listener_id, @@ -196,11 +191,10 @@ where listener_id, mut listen_addr, } => { - let proto = self - .listener_protos + self.listener_protos .get(&listener_id) - .expect("Protocol was inserted in Transport::listen_on."); - listen_addr.push(proto.clone()); + .expect("Protocol was inserted in Transport::listen_on.") + .append_on_addr(&mut listen_addr); TransportEvent::AddressExpired { listener_id, listen_addr, @@ -232,13 +226,9 @@ where .listener_protos .get(&listener_id) .expect("Protocol was inserted in Transport::listen_on."); - let use_tls = match proto { - Protocol::Wss(_) => true, - Protocol::Ws(_) => false, - _ => unreachable!("Map contains only ws and wss protocols."), - }; - local_addr.push(proto.clone()); - send_back_addr.push(proto.clone()); + let use_tls = proto.use_tls(); + proto.append_on_addr(&mut local_addr); + proto.append_on_addr(&mut send_back_addr); let upgrade = self.map_upgrade(upgrade, send_back_addr.clone(), use_tls); TransportEvent::Incoming { listener_id, @@ -263,7 +253,7 @@ where fn do_dial( &mut self, addr: Multiaddr, - role_override: Endpoint, + dial_opts: DialOpts, ) -> Result<::Dial, TransportError<::Error>> { let mut addr = match parse_ws_dial_addr(addr) { Ok(addr) => addr, @@ -282,8 +272,7 @@ where let future = async move { loop { - match Self::dial_once(transport.clone(), addr, tls_config.clone(), role_override) - .await + match Self::dial_once(transport.clone(), addr, tls_config.clone(), dial_opts).await { Ok(Either::Left(redirect)) => { if remaining_redirects == 0 { @@ -307,33 +296,29 @@ where transport: Arc>, addr: WsAddress, tls_config: tls::Config, - role_override: Endpoint, + dial_opts: DialOpts, ) -> Result>, Error> { tracing::trace!(address=?addr, "Dialing websocket address"); - let dial = match role_override { - Endpoint::Dialer => transport.lock().dial(addr.tcp_addr), - Endpoint::Listener => transport.lock().dial_as_listener(addr.tcp_addr), - } - .map_err(|e| match e { - TransportError::MultiaddrNotSupported(a) => Error::InvalidMultiaddr(a), - TransportError::Other(e) => Error::Transport(e), - })?; + let dial = transport + .lock() + .dial(addr.tcp_addr, dial_opts) + .map_err(|e| match e { + TransportError::MultiaddrNotSupported(a) => Error::InvalidMultiaddr(a), + TransportError::Other(e) => Error::Transport(e), + })?; let stream = dial.map_err(Error::Transport).await?; tracing::trace!(port=%addr.host_port, "TCP connection established"); let stream = if addr.use_tls { // begin TLS session - let dns_name = addr - .dns_name - .expect("for use_tls we have checked that dns_name is some"); - tracing::trace!(?dns_name, "Starting TLS handshake"); + tracing::trace!(?addr.server_name, "Starting TLS handshake"); let stream = tls_config .client - .connect(dns_name.clone(), stream) + .connect(addr.server_name.clone(), stream) .map_err(|e| { - tracing::debug!(?dns_name, "TLS handshake failed: {}", e); + tracing::debug!(?addr.server_name, "TLS handshake failed: {}", e); Error::Tls(tls::Error::from(e)) }) .await?; @@ -457,11 +442,53 @@ where } } +#[derive(Debug, PartialEq)] +pub(crate) enum WsListenProto<'a> { + Ws(Cow<'a, str>), + Wss(Cow<'a, str>), + TlsWs(Cow<'a, str>), +} + +impl WsListenProto<'_> { + pub(crate) fn append_on_addr(&self, addr: &mut Multiaddr) { + match self { + WsListenProto::Ws(path) => { + addr.push(Protocol::Ws(path.clone())); + } + // `/tls/ws` and `/wss` are equivalend, however we regenerate + // the one that user passed at `listen_on` for backward compatibility. + WsListenProto::Wss(path) => { + addr.push(Protocol::Wss(path.clone())); + } + WsListenProto::TlsWs(path) => { + addr.push(Protocol::Tls); + addr.push(Protocol::Ws(path.clone())); + } + } + } + + pub(crate) fn use_tls(&self) -> bool { + match self { + WsListenProto::Ws(_) => false, + WsListenProto::Wss(_) => true, + WsListenProto::TlsWs(_) => true, + } + } + + pub(crate) fn prefix(&self) -> &'static str { + match self { + WsListenProto::Ws(_) => "/ws", + WsListenProto::Wss(_) => "/wss", + WsListenProto::TlsWs(_) => "/tls/ws", + } + } +} + #[derive(Debug)] struct WsAddress { host_port: String, path: String, - dns_name: Option, + server_name: ServerName<'static>, use_tls: bool, tcp_addr: Multiaddr, } @@ -478,19 +505,20 @@ fn parse_ws_dial_addr(addr: Multiaddr) -> Result> { let mut protocols = addr.iter(); let mut ip = protocols.next(); let mut tcp = protocols.next(); - let (host_port, dns_name) = loop { + let (host_port, server_name) = loop { match (ip, tcp) { (Some(Protocol::Ip4(ip)), Some(Protocol::Tcp(port))) => { - break (format!("{ip}:{port}"), None) + let server_name = ServerName::IpAddress(IpAddr::V4(ip).into()); + break (format!("{ip}:{port}"), server_name); } (Some(Protocol::Ip6(ip)), Some(Protocol::Tcp(port))) => { - break (format!("{ip}:{port}"), None) + let server_name = ServerName::IpAddress(IpAddr::V6(ip).into()); + break (format!("[{ip}]:{port}"), server_name); } (Some(Protocol::Dns(h)), Some(Protocol::Tcp(port))) | (Some(Protocol::Dns4(h)), Some(Protocol::Tcp(port))) - | (Some(Protocol::Dns6(h)), Some(Protocol::Tcp(port))) - | (Some(Protocol::Dnsaddr(h)), Some(Protocol::Tcp(port))) => { - break (format!("{}:{}", &h, port), Some(tls::dns_name_ref(&h)?)) + | (Some(Protocol::Dns6(h)), Some(Protocol::Tcp(port))) => { + break (format!("{h}:{port}"), tls::dns_name_ref(&h)?) } (Some(_), Some(p)) => { ip = Some(p); @@ -508,20 +536,21 @@ fn parse_ws_dial_addr(addr: Multiaddr) -> Result> { let (use_tls, path) = loop { match protocols.pop() { p @ Some(Protocol::P2p(_)) => p2p = p, - Some(Protocol::Ws(path)) => break (false, path.into_owned()), - Some(Protocol::Wss(path)) => { - if dns_name.is_none() { - tracing::debug!(addrress=%addr, "Missing DNS name in WSS address"); - return Err(Error::InvalidMultiaddr(addr)); + Some(Protocol::Ws(path)) => match protocols.pop() { + Some(Protocol::Tls) => break (true, path.into_owned()), + Some(p) => { + protocols.push(p); + break (false, path.into_owned()); } - break (true, path.into_owned()); - } + None => return Err(Error::InvalidMultiaddr(addr)), + }, + Some(Protocol::Wss(path)) => break (true, path.into_owned()), _ => return Err(Error::InvalidMultiaddr(addr)), } }; // The original address, stripped of the `/ws` and `/wss` protocols, - // makes up the the address for the inner TCP-based transport. + // makes up the address for the inner TCP-based transport. let tcp_addr = match p2p { Some(p) => protocols.with(p), None => protocols, @@ -529,13 +558,29 @@ fn parse_ws_dial_addr(addr: Multiaddr) -> Result> { Ok(WsAddress { host_port, - dns_name, + server_name, path, use_tls, tcp_addr, }) } +fn parse_ws_listen_addr(addr: &Multiaddr) -> Option<(Multiaddr, WsListenProto<'static>)> { + let mut inner_addr = addr.clone(); + + match inner_addr.pop()? { + Protocol::Wss(path) => Some((inner_addr, WsListenProto::Wss(path))), + Protocol::Ws(path) => match inner_addr.pop()? { + Protocol::Tls => Some((inner_addr, WsListenProto::TlsWs(path))), + p => { + inner_addr.push(p); + Some((inner_addr, WsListenProto::Ws(path))) + } + }, + _ => None, + } +} + // Given a location URL, build a new websocket [`Multiaddr`]. fn location_to_multiaddr(location: &str) -> Result> { match Url::parse(location) { @@ -552,7 +597,8 @@ fn location_to_multiaddr(location: &str) -> Result> { } let s = url.scheme(); if s.eq_ignore_ascii_case("https") | s.eq_ignore_ascii_case("wss") { - a.push(Protocol::Wss(url.path().into())) + a.push(Protocol::Tls); + a.push(Protocol::Ws(url.path().into())); } else if s.eq_ignore_ascii_case("http") | s.eq_ignore_ascii_case("ws") { a.push(Protocol::Ws(url.path().into())) } else { @@ -571,7 +617,7 @@ fn location_to_multiaddr(location: &str) -> Result> { /// The websocket connection. pub struct Connection { receiver: BoxStream<'static, Result>, - sender: Pin + Send>>, + sender: Pin> + Send>>, _marker: std::marker::PhantomData, } @@ -767,3 +813,202 @@ where .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) } } + +#[cfg(test)] +mod tests { + use std::io; + + use libp2p_identity::PeerId; + + use super::*; + + #[test] + fn listen_addr() { + let tcp_addr = "/ip4/0.0.0.0/tcp/2222".parse::().unwrap(); + + // Check `/tls/ws` + let addr = tcp_addr + .clone() + .with(Protocol::Tls) + .with(Protocol::Ws("/".into())); + let (inner_addr, proto) = parse_ws_listen_addr(&addr).unwrap(); + assert_eq!(&inner_addr, &tcp_addr); + assert_eq!(proto, WsListenProto::TlsWs("/".into())); + + let mut listen_addr = tcp_addr.clone(); + proto.append_on_addr(&mut listen_addr); + assert_eq!(listen_addr, addr); + + // Check `/wss` + let addr = tcp_addr.clone().with(Protocol::Wss("/".into())); + let (inner_addr, proto) = parse_ws_listen_addr(&addr).unwrap(); + assert_eq!(&inner_addr, &tcp_addr); + assert_eq!(proto, WsListenProto::Wss("/".into())); + + let mut listen_addr = tcp_addr.clone(); + proto.append_on_addr(&mut listen_addr); + assert_eq!(listen_addr, addr); + + // Check `/ws` + let addr = tcp_addr.clone().with(Protocol::Ws("/".into())); + let (inner_addr, proto) = parse_ws_listen_addr(&addr).unwrap(); + assert_eq!(&inner_addr, &tcp_addr); + assert_eq!(proto, WsListenProto::Ws("/".into())); + + let mut listen_addr = tcp_addr.clone(); + proto.append_on_addr(&mut listen_addr); + assert_eq!(listen_addr, addr); + } + + #[test] + fn dial_addr() { + let peer_id = PeerId::random(); + + // Check `/tls/ws` + let addr = "/dns4/example.com/tcp/2222/tls/ws" + .parse::() + .unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "example.com:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "example.com".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/dns4/example.com/tcp/2222".parse().unwrap()); + + // Check `/tls/ws` with `/p2p` + let addr = format!("/dns4/example.com/tcp/2222/tls/ws/p2p/{peer_id}") + .parse() + .unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "example.com:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "example.com".try_into().unwrap()); + assert_eq!( + info.tcp_addr, + format!("/dns4/example.com/tcp/2222/p2p/{peer_id}") + .parse() + .unwrap() + ); + + // Check `/tls/ws` with `/ip4` + let addr = "/ip4/127.0.0.1/tcp/2222/tls/ws" + .parse::() + .unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "127.0.0.1:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "127.0.0.1".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/ip4/127.0.0.1/tcp/2222".parse().unwrap()); + + // Check `/tls/ws` with `/ip6` + let addr = "/ip6/::1/tcp/2222/tls/ws".parse::().unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "[::1]:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "::1".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/ip6/::1/tcp/2222".parse().unwrap()); + + // Check `/wss` + let addr = "/dns4/example.com/tcp/2222/wss" + .parse::() + .unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "example.com:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "example.com".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/dns4/example.com/tcp/2222".parse().unwrap()); + + // Check `/wss` with `/p2p` + let addr = format!("/dns4/example.com/tcp/2222/wss/p2p/{peer_id}") + .parse() + .unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "example.com:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "example.com".try_into().unwrap()); + assert_eq!( + info.tcp_addr, + format!("/dns4/example.com/tcp/2222/p2p/{peer_id}") + .parse() + .unwrap() + ); + + // Check `/wss` with `/ip4` + let addr = "/ip4/127.0.0.1/tcp/2222/wss".parse::().unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "127.0.0.1:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "127.0.0.1".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/ip4/127.0.0.1/tcp/2222".parse().unwrap()); + + // Check `/wss` with `/ip6` + let addr = "/ip6/::1/tcp/2222/wss".parse::().unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "[::1]:2222"); + assert_eq!(info.path, "/"); + assert!(info.use_tls); + assert_eq!(info.server_name, "::1".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/ip6/::1/tcp/2222".parse().unwrap()); + + // Check `/ws` + let addr = "/dns4/example.com/tcp/2222/ws" + .parse::() + .unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "example.com:2222"); + assert_eq!(info.path, "/"); + assert!(!info.use_tls); + assert_eq!(info.server_name, "example.com".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/dns4/example.com/tcp/2222".parse().unwrap()); + + // Check `/ws` with `/p2p` + let addr = format!("/dns4/example.com/tcp/2222/ws/p2p/{peer_id}") + .parse() + .unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "example.com:2222"); + assert_eq!(info.path, "/"); + assert!(!info.use_tls); + assert_eq!(info.server_name, "example.com".try_into().unwrap()); + assert_eq!( + info.tcp_addr, + format!("/dns4/example.com/tcp/2222/p2p/{peer_id}") + .parse() + .unwrap() + ); + + // Check `/ws` with `/ip4` + let addr = "/ip4/127.0.0.1/tcp/2222/ws".parse::().unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "127.0.0.1:2222"); + assert_eq!(info.path, "/"); + assert!(!info.use_tls); + assert_eq!(info.server_name, "127.0.0.1".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/ip4/127.0.0.1/tcp/2222".parse().unwrap()); + + // Check `/ws` with `/ip6` + let addr = "/ip6/::1/tcp/2222/ws".parse::().unwrap(); + let info = parse_ws_dial_addr::(addr).unwrap(); + assert_eq!(info.host_port, "[::1]:2222"); + assert_eq!(info.path, "/"); + assert!(!info.use_tls); + assert_eq!(info.server_name, "::1".try_into().unwrap()); + assert_eq!(info.tcp_addr, "/ip6/::1/tcp/2222".parse().unwrap()); + + // Check `/dnsaddr` + let addr = "/dnsaddr/example.com/tcp/2222/ws" + .parse::() + .unwrap(); + parse_ws_dial_addr::(addr).unwrap_err(); + + // Check non-ws address + let addr = "/ip4/127.0.0.1/tcp/2222".parse::().unwrap(); + parse_ws_dial_addr::(addr).unwrap_err(); + } +} diff --git a/transports/websocket/src/lib.rs b/transports/websocket/src/lib.rs index e0b3d09ca25..fbed8fddc66 100644 --- a/transports/websocket/src/lib.rs +++ b/transports/websocket/src/lib.rs @@ -27,21 +27,22 @@ pub mod framed; mod quicksink; pub mod tls; +use std::{ + io, + pin::Pin, + task::{Context, Poll}, +}; + use error::Error; use framed::{Connection, Incoming}; use futures::{future::BoxFuture, prelude::*, ready}; use libp2p_core::{ connection::ConnectedPoint, multiaddr::Multiaddr, - transport::{map::MapFuture, ListenerId, TransportError, TransportEvent}, + transport::{map::MapFuture, DialOpts, ListenerId, TransportError, TransportEvent}, Transport, }; use rw_stream_sink::RwStreamSink; -use std::{ - io, - pin::Pin, - task::{Context, Poll}, -}; /// A Websocket transport. /// @@ -75,18 +76,28 @@ use std::{ /// # #[async_std::main] /// # async fn main() { /// -/// let mut transport = websocket::WsConfig::new(dns::async_std::Transport::system( -/// tcp::async_io::Transport::new(tcp::Config::default()), -/// ).await.unwrap()); +/// let mut transport = websocket::WsConfig::new( +/// dns::async_std::Transport::system(tcp::async_io::Transport::new(tcp::Config::default())) +/// .await +/// .unwrap(), +/// ); /// /// let rcgen_cert = generate_simple_self_signed(vec!["localhost".to_string()]).unwrap(); /// let priv_key = websocket::tls::PrivateKey::new(rcgen_cert.serialize_private_key_der()); /// let cert = websocket::tls::Certificate::new(rcgen_cert.serialize_der().unwrap()); /// transport.set_tls_config(websocket::tls::Config::new(priv_key, vec![cert]).unwrap()); /// -/// let id = transport.listen_on(ListenerId::next(), "/ip4/127.0.0.1/tcp/0/wss".parse().unwrap()).unwrap(); +/// let id = transport +/// .listen_on( +/// ListenerId::next(), +/// "/ip4/127.0.0.1/tcp/0/tls/ws".parse().unwrap(), +/// ) +/// .unwrap(); /// -/// let addr = future::poll_fn(|cx| Pin::new(&mut transport).poll(cx)).await.into_new_address().unwrap(); +/// let addr = future::poll_fn(|cx| Pin::new(&mut transport).poll(cx)) +/// .await +/// .into_new_address() +/// .unwrap(); /// println!("Listening on {addr}"); /// /// # } @@ -105,13 +116,20 @@ use std::{ /// # #[async_std::main] /// # async fn main() { /// -/// let mut transport = websocket::WsConfig::new( -/// tcp::async_io::Transport::new(tcp::Config::default()), -/// ); +/// let mut transport = +/// websocket::WsConfig::new(tcp::async_io::Transport::new(tcp::Config::default())); /// -/// let id = transport.listen_on(ListenerId::next(), "/ip4/127.0.0.1/tcp/0/ws".parse().unwrap()).unwrap(); +/// let id = transport +/// .listen_on( +/// ListenerId::next(), +/// "/ip4/127.0.0.1/tcp/0/ws".parse().unwrap(), +/// ) +/// .unwrap(); /// -/// let addr = future::poll_fn(|cx| Pin::new(&mut transport).poll(cx)).await.into_new_address().unwrap(); +/// let addr = future::poll_fn(|cx| Pin::new(&mut transport).poll(cx)) +/// .await +/// .into_new_address() +/// .unwrap(); /// println!("Listening on {addr}"); /// /// # } @@ -202,19 +220,12 @@ where self.transport.remove_listener(id) } - fn dial(&mut self, addr: Multiaddr) -> Result> { - self.transport.dial(addr) - } - - fn dial_as_listener( + fn dial( &mut self, addr: Multiaddr, + opts: DialOpts, ) -> Result> { - self.transport.dial_as_listener(addr) - } - - fn address_translation(&self, server: &Multiaddr, observed: &Multiaddr) -> Option { - self.transport.address_translation(server, observed) + self.transport.dial(addr, opts) } fn poll( @@ -290,12 +301,17 @@ where #[cfg(test)] mod tests { - use super::WsConfig; use futures::prelude::*; - use libp2p_core::{multiaddr::Protocol, transport::ListenerId, Multiaddr, Transport}; + use libp2p_core::{ + multiaddr::Protocol, + transport::{DialOpts, ListenerId, PortUse}, + Endpoint, Multiaddr, Transport, + }; use libp2p_identity::PeerId; use libp2p_tcp as tcp; + use super::WsConfig; + #[test] fn dialer_connects_to_listener_ipv4() { let a = "/ip4/127.0.0.1/tcp/0/ws".parse().unwrap(); @@ -339,7 +355,13 @@ mod tests { let outbound = new_ws_config() .boxed() - .dial(addr.with(Protocol::P2p(PeerId::random()))) + .dial( + addr.with(Protocol::P2p(PeerId::random())), + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::New, + }, + ) .unwrap(); let (a, b) = futures::join!(inbound, outbound); diff --git a/transports/websocket/src/quicksink.rs b/transports/websocket/src/quicksink.rs index d9edb4dfe0d..a0e2fb8b0f6 100644 --- a/transports/websocket/src/quicksink.rs +++ b/transports/websocket/src/quicksink.rs @@ -19,34 +19,28 @@ // ```no_run // use async_std::io; // use futures::prelude::*; +// // use crate::quicksink::Action; // // crate::quicksink::make_sink(io::stdout(), |mut stdout, action| async move { // match action { // Action::Send(x) => stdout.write_all(x).await?, // Action::Flush => stdout.flush().await?, -// Action::Close => stdout.close().await? +// Action::Close => stdout.close().await?, // } // Ok::<_, io::Error>(stdout) // }); // ``` -// -// # Panics -// -// - If any of the [`Sink`] methods produce an error, the sink transitions -// to a failure state and none of its methods must be called afterwards or -// else a panic will occur. -// - If [`Sink::poll_close`] has been called, no other sink method must be -// called afterwards or else a panic will be caused. -use futures::{ready, sink::Sink}; -use pin_project_lite::pin_project; use std::{ future::Future, pin::Pin, task::{Context, Poll}, }; +use futures::{ready, sink::Sink}; +use pin_project_lite::pin_project; + /// Returns a `Sink` impl based on the initial value and the given closure. /// /// The closure will be applied to the initial value and an [`Action`] that @@ -102,6 +96,15 @@ enum State { Failed, } +/// Errors the `Sink` may return. +#[derive(Debug, thiserror::Error)] +pub(crate) enum Error { + #[error("Error while sending over the sink, {0}")] + Send(E), + #[error("The Sink has closed")] + Closed, +} + pin_project! { /// `SinkImpl` implements the `Sink` trait. #[derive(Debug)] @@ -119,7 +122,7 @@ where F: FnMut(S, Action) -> T, T: Future>, { - type Error = E; + type Error = Error; fn poll_ready(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { let mut this = self.project(); @@ -135,7 +138,7 @@ where Err(e) => { this.future.set(None); *this.state = State::Failed; - Poll::Ready(Err(e)) + Poll::Ready(Err(Error::Send(e))) } } } @@ -143,20 +146,19 @@ where Ok(_) => { this.future.set(None); *this.state = State::Closed; - panic!("SinkImpl::poll_ready called on a closing sink.") + Poll::Ready(Err(Error::Closed)) } Err(e) => { this.future.set(None); *this.state = State::Failed; - Poll::Ready(Err(e)) + Poll::Ready(Err(Error::Send(e))) } }, State::Empty => { assert!(this.param.is_some()); Poll::Ready(Ok(())) } - State::Closed => panic!("SinkImpl::poll_ready called on a closed sink."), - State::Failed => panic!("SinkImpl::poll_ready called after error."), + State::Closed | State::Failed => Poll::Ready(Err(Error::Closed)), } } @@ -193,7 +195,7 @@ where Err(e) => { this.future.set(None); *this.state = State::Failed; - return Poll::Ready(Err(e)); + return Poll::Ready(Err(Error::Send(e))); } }, State::Flushing => { @@ -207,7 +209,7 @@ where Err(e) => { this.future.set(None); *this.state = State::Failed; - return Poll::Ready(Err(e)); + return Poll::Ready(Err(Error::Send(e))); } } } @@ -221,11 +223,10 @@ where Err(e) => { this.future.set(None); *this.state = State::Failed; - return Poll::Ready(Err(e)); + return Poll::Ready(Err(Error::Send(e))); } }, - State::Closed => return Poll::Ready(Ok(())), - State::Failed => panic!("SinkImpl::poll_flush called after error."), + State::Closed | State::Failed => return Poll::Ready(Err(Error::Closed)), } } } @@ -253,7 +254,7 @@ where Err(e) => { this.future.set(None); *this.state = State::Failed; - return Poll::Ready(Err(e)); + return Poll::Ready(Err(Error::Send(e))); } }, State::Flushing => { @@ -266,7 +267,7 @@ where Err(e) => { this.future.set(None); *this.state = State::Failed; - return Poll::Ready(Err(e)); + return Poll::Ready(Err(Error::Send(e))); } } } @@ -280,11 +281,11 @@ where Err(e) => { this.future.set(None); *this.state = State::Failed; - return Poll::Ready(Err(e)); + return Poll::Ready(Err(Error::Send(e))); } }, State::Closed => return Poll::Ready(Ok(())), - State::Failed => panic!("SinkImpl::poll_closed called after error."), + State::Failed => return Poll::Ready(Err(Error::Closed)), } } } @@ -292,9 +293,10 @@ where #[cfg(test)] mod tests { - use crate::quicksink::{make_sink, Action}; use async_std::{io, task}; - use futures::{channel::mpsc, prelude::*, stream}; + use futures::{channel::mpsc, prelude::*}; + + use crate::quicksink::{make_sink, Action}; #[test] fn smoke_test() { @@ -347,4 +349,31 @@ mod tests { assert_eq!(&expected[..], &actual[..]) }); } + + #[test] + fn error_does_not_panic() { + task::block_on(async { + let sink = make_sink(io::stdout(), |mut _stdout, _action| async move { + Err(io::Error::new(io::ErrorKind::Other, "oh no")) + }); + + futures::pin_mut!(sink); + + let result = sink.send("hello").await; + match result { + Err(crate::quicksink::Error::Send(e)) => { + assert_eq!(e.kind(), io::ErrorKind::Other); + assert_eq!(e.to_string(), "oh no") + } + _ => panic!("unexpected result: {:?}", result), + }; + + // Call send again, expect not to panic. + let result = sink.send("hello").await; + match result { + Err(crate::quicksink::Error::Closed) => {} + _ => panic!("unexpected result: {:?}", result), + }; + }) + } } diff --git a/transports/websocket/src/tls.rs b/transports/websocket/src/tls.rs index 5bff818f34c..598dcc22765 100644 --- a/transports/websocket/src/tls.rs +++ b/transports/websocket/src/tls.rs @@ -18,10 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures_rustls::{rustls, TlsAcceptor, TlsConnector}; -use std::convert::TryFrom; use std::{fmt, io, sync::Arc}; +use futures_rustls::{rustls, TlsAcceptor, TlsConnector}; + /// TLS configuration. #[derive(Clone)] pub struct Config { @@ -36,24 +36,32 @@ impl fmt::Debug for Config { } /// Private key, DER-encoded ASN.1 in either PKCS#8 or PKCS#1 format. -#[derive(Clone)] -pub struct PrivateKey(rustls::PrivateKey); +pub struct PrivateKey(rustls::pki_types::PrivateKeyDer<'static>); impl PrivateKey { /// Assert the given bytes are DER-encoded ASN.1 in either PKCS#8 or PKCS#1 format. pub fn new(bytes: Vec) -> Self { - PrivateKey(rustls::PrivateKey(bytes)) + PrivateKey( + rustls::pki_types::PrivateKeyDer::try_from(bytes) + .expect("unknown or invalid key format"), + ) + } +} + +impl Clone for PrivateKey { + fn clone(&self) -> Self { + Self(self.0.clone_key()) } } /// Certificate, DER-encoded X.509 format. #[derive(Debug, Clone)] -pub struct Certificate(rustls::Certificate); +pub struct Certificate(rustls::pki_types::CertificateDer<'static>); impl Certificate { /// Assert the given bytes are in DER-encoded X.509 format. pub fn new(bytes: Vec) -> Self { - Certificate(rustls::Certificate(bytes)) + Certificate(rustls::pki_types::CertificateDer::from(bytes)) } } @@ -70,8 +78,10 @@ impl Config { /// Create a client-only configuration. pub fn client() -> Self { - let client = rustls::ClientConfig::builder() - .with_safe_defaults() + let provider = rustls::crypto::ring::default_provider(); + let client = rustls::ClientConfig::builder_with_provider(provider.into()) + .with_safe_default_protocol_versions() + .unwrap() .with_root_certificates(client_root_store()) .with_no_client_auth(); Config { @@ -92,12 +102,12 @@ impl Config { /// Setup the rustls client configuration. fn client_root_store() -> rustls::RootCertStore { let mut client_root_store = rustls::RootCertStore::empty(); - client_root_store.add_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| { - rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( - ta.subject, - ta.spki, - ta.name_constraints, - ) + client_root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| { + rustls::pki_types::TrustAnchor { + subject: ta.subject.into(), + subject_public_key_info: ta.spki.into(), + name_constraints: ta.name_constraints.map(|v| v.into()), + } })); client_root_store } @@ -115,8 +125,10 @@ impl Builder { I: IntoIterator, { let certs = certs.into_iter().map(|c| c.0).collect(); - let server = rustls::ServerConfig::builder() - .with_safe_defaults() + let provider = rustls::crypto::ring::default_provider(); + let server = rustls::ServerConfig::builder_with_provider(provider.into()) + .with_safe_default_protocol_versions() + .unwrap() .with_no_client_auth() .with_single_cert(certs, key.0) .map_err(|e| Error::Tls(Box::new(e)))?; @@ -127,15 +139,17 @@ impl Builder { /// Add an additional trust anchor. pub fn add_trust(&mut self, cert: &Certificate) -> Result<&mut Self, Error> { self.client_root_store - .add(&cert.0) + .add(cert.0.to_owned()) .map_err(|e| Error::Tls(Box::new(e)))?; Ok(self) } /// Finish configuration. pub fn finish(self) -> Config { - let client = rustls::ClientConfig::builder() - .with_safe_defaults() + let provider = rustls::crypto::ring::default_provider(); + let client = rustls::ClientConfig::builder_with_provider(provider.into()) + .with_safe_default_protocol_versions() + .unwrap() .with_root_certificates(self.client_root_store) .with_no_client_auth(); @@ -146,8 +160,9 @@ impl Builder { } } -pub(crate) fn dns_name_ref(name: &str) -> Result { - rustls::ServerName::try_from(name).map_err(|_| Error::InvalidDnsName(name.into())) +pub(crate) fn dns_name_ref(name: &str) -> Result, Error> { + rustls::pki_types::ServerName::try_from(String::from(name)) + .map_err(|_| Error::InvalidDnsName(name.into())) } // Error ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/transports/webtransport-websys/CHANGELOG.md b/transports/webtransport-websys/CHANGELOG.md index b368a943395..45a94495e4e 100644 --- a/transports/webtransport-websys/CHANGELOG.md +++ b/transports/webtransport-websys/CHANGELOG.md @@ -1,3 +1,23 @@ +## 0.4.1 + +- Fix `cargo clippy` warnings in `rustc 1.84.0-beta.1`. + See [PR 5700](https://github.com/libp2p/rust-libp2p/pull/5700). + +## 0.4.0 + +- Implement refactored `Transport`. + See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568) +- Bump version of web-sys and wasm-bindgen. + See [PR 5569](https://github.com/libp2p/rust-libp2p/pull/5569) + +## 0.3.0 + +* Fix unhandled exceptions thrown when calling `Webtransport::close`. + See [PR 5390](https://github.com/libp2p/rust-libp2p/pull/5390). +* Change logs to debug level. + See [PR 5396](https://github.com/libp2p/rust-libp2p/pull/5396). + + ## 0.2.0 diff --git a/transports/webtransport-websys/Cargo.toml b/transports/webtransport-websys/Cargo.toml index d21730e42c4..ef2865535bf 100644 --- a/transports/webtransport-websys/Cargo.toml +++ b/transports/webtransport-websys/Cargo.toml @@ -3,7 +3,7 @@ name = "libp2p-webtransport-websys" edition = "2021" rust-version = { workspace = true } description = "WebTransport for libp2p under WASM environment" -version = "0.2.0" +version = "0.4.1" authors = [ "Yiannis Marangos ", "oblique ", @@ -14,19 +14,20 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] -futures = "0.3.29" -js-sys = "0.3.66" +futures = { workspace = true } +js-sys = "0.3.70" libp2p-core = { workspace = true } libp2p-identity = { workspace = true } libp2p-noise = { workspace = true } multiaddr = { workspace = true } multihash = { workspace = true } +once_cell = "1.19.0" send_wrapper = { version = "0.6.0", features = ["futures"] } -thiserror = "1.0.50" -tracing = "0.1.37" -wasm-bindgen = "0.2.89" -wasm-bindgen-futures = "0.4.38" -web-sys = { version = "0.3.65", features = [ +thiserror = { workspace = true } +tracing = { workspace = true } +wasm-bindgen = "0.2.93" +wasm-bindgen-futures = "0.4.43" +web-sys = { version = "0.3.70", features = [ "ReadableStreamDefaultReader", "WebTransport", "WebTransportBidirectionalStream", @@ -44,8 +45,6 @@ multibase = "0.9.1" # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] [lints] workspace = true diff --git a/transports/webtransport-websys/src/connection.rs b/transports/webtransport-websys/src/connection.rs index 982f9e5a32c..75c8603864a 100644 --- a/transports/webtransport-websys/src/connection.rs +++ b/transports/webtransport-websys/src/connection.rs @@ -1,22 +1,29 @@ +use std::{ + collections::HashSet, + future::poll_fn, + pin::Pin, + task::{ready, Context, Poll}, +}; + use futures::FutureExt; -use libp2p_core::muxing::{StreamMuxer, StreamMuxerEvent}; -use libp2p_core::upgrade::OutboundConnectionUpgrade; -use libp2p_core::UpgradeInfo; +use libp2p_core::{ + muxing::{StreamMuxer, StreamMuxerEvent}, + upgrade::OutboundConnectionUpgrade, + UpgradeInfo, +}; use libp2p_identity::{Keypair, PeerId}; use multihash::Multihash; use send_wrapper::SendWrapper; -use std::collections::HashSet; -use std::future::poll_fn; -use std::pin::Pin; -use std::task::{ready, Context, Poll}; use wasm_bindgen_futures::JsFuture; use web_sys::ReadableStreamDefaultReader; -use crate::bindings::{WebTransport, WebTransportBidirectionalStream}; -use crate::endpoint::Endpoint; -use crate::fused_js_promise::FusedJsPromise; -use crate::utils::{detach_promise, parse_reader_response, to_js_type}; -use crate::{Error, Stream}; +use crate::{ + bindings::{WebTransport, WebTransportBidirectionalStream}, + endpoint::Endpoint, + fused_js_promise::FusedJsPromise, + utils::{detach_promise, parse_reader_response, to_js_type}, + Error, Stream, +}; /// An opened WebTransport connection. #[derive(Debug)] @@ -47,6 +54,12 @@ impl Connection { let opts = endpoint.webtransport_opts(); WebTransport::new_with_options(&url, &opts).map_err(Error::from_js_value)? }; + // Create a promise that will resolve once session is closed. + // It will catch the errors that can eventually happen when + // `.close()` is called. Without it, there is no way of catching + // those from the `.close()` itself, resulting in `Uncaught in promise...` + // logs popping up. + detach_promise(session.closed()); let incoming_streams = session.incoming_bidirectional_streams(); let incoming_streams_reader = diff --git a/transports/webtransport-websys/src/endpoint.rs b/transports/webtransport-websys/src/endpoint.rs index 0bff1ed6186..fd209c51664 100644 --- a/transports/webtransport-websys/src/endpoint.rs +++ b/transports/webtransport-websys/src/endpoint.rs @@ -1,11 +1,14 @@ +use std::collections::HashSet; + use js_sys::{Array, Uint8Array}; use libp2p_identity::PeerId; use multiaddr::{Multiaddr, Protocol}; use multihash::Multihash; -use std::collections::HashSet; -use crate::bindings::{WebTransportHash, WebTransportOptions}; -use crate::Error; +use crate::{ + bindings::{WebTransportHash, WebTransportOptions}, + Error, +}; pub(crate) struct Endpoint { pub(crate) host: String, @@ -149,9 +152,10 @@ impl Endpoint { #[cfg(test)] mod tests { - use super::*; use std::str::FromStr; + use super::*; + fn multihash_from_str(s: &str) -> Multihash<64> { let (_base, bytes) = multibase::decode(s).unwrap(); Multihash::from_bytes(&bytes).unwrap() diff --git a/transports/webtransport-websys/src/error.rs b/transports/webtransport-websys/src/error.rs index ad85cab7537..704cfffb1fd 100644 --- a/transports/webtransport-websys/src/error.rs +++ b/transports/webtransport-websys/src/error.rs @@ -11,6 +11,7 @@ pub enum Error { Noise(#[from] libp2p_noise::Error), #[error("JavaScript error: {0}")] + #[allow(clippy::enum_variant_names)] JsError(String), #[error("JavaScript typecasting failed")] diff --git a/transports/webtransport-websys/src/fused_js_promise.rs b/transports/webtransport-websys/src/fused_js_promise.rs index 0ba846501c2..d3d3858a553 100644 --- a/transports/webtransport-websys/src/fused_js_promise.rs +++ b/transports/webtransport-websys/src/fused_js_promise.rs @@ -1,8 +1,11 @@ +use std::{ + future::Future, + pin::Pin, + task::{ready, Context, Poll}, +}; + use futures::FutureExt; use js_sys::Promise; -use std::future::Future; -use std::pin::Pin; -use std::task::{ready, Context, Poll}; use wasm_bindgen::JsValue; use wasm_bindgen_futures::JsFuture; diff --git a/transports/webtransport-websys/src/lib.rs b/transports/webtransport-websys/src/lib.rs index f9c59694fa3..126adc054a9 100644 --- a/transports/webtransport-websys/src/lib.rs +++ b/transports/webtransport-websys/src/lib.rs @@ -1,5 +1,7 @@ //! Libp2p WebTransport built on [web-sys](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html) +#![allow(unexpected_cfgs)] + mod bindings; mod connection; mod endpoint; @@ -9,7 +11,9 @@ mod stream; mod transport; mod utils; -pub use self::connection::Connection; -pub use self::error::Error; -pub use self::stream::Stream; -pub use self::transport::{Config, Transport}; +pub use self::{ + connection::Connection, + error::Error, + stream::Stream, + transport::{Config, Transport}, +}; diff --git a/transports/webtransport-websys/src/stream.rs b/transports/webtransport-websys/src/stream.rs index ba4238ac814..b9d1669b6dc 100644 --- a/transports/webtransport-websys/src/stream.rs +++ b/transports/webtransport-websys/src/stream.rs @@ -1,16 +1,20 @@ +use std::{ + io, + pin::Pin, + task::{ready, Context, Poll}, +}; + use futures::{AsyncRead, AsyncWrite, FutureExt}; use js_sys::Uint8Array; use send_wrapper::SendWrapper; -use std::io; -use std::pin::Pin; -use std::task::ready; -use std::task::{Context, Poll}; use web_sys::{ReadableStreamDefaultReader, WritableStreamDefaultWriter}; -use crate::bindings::WebTransportBidirectionalStream; -use crate::fused_js_promise::FusedJsPromise; -use crate::utils::{detach_promise, parse_reader_response, to_io_error, to_js_type}; -use crate::Error; +use crate::{ + bindings::WebTransportBidirectionalStream, + fused_js_promise::FusedJsPromise, + utils::{detach_promise, parse_reader_response, to_io_error, to_js_type}, + Error, +}; /// A stream on a connection. #[derive(Debug)] diff --git a/transports/webtransport-websys/src/transport.rs b/transports/webtransport-websys/src/transport.rs index cb556ffef99..bad9509864e 100644 --- a/transports/webtransport-websys/src/transport.rs +++ b/transports/webtransport-websys/src/transport.rs @@ -1,15 +1,18 @@ +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + use futures::future::FutureExt; -use libp2p_core::muxing::StreamMuxerBox; -use libp2p_core::transport::{Boxed, ListenerId, Transport as _, TransportError, TransportEvent}; +use libp2p_core::{ + muxing::StreamMuxerBox, + transport::{Boxed, DialOpts, ListenerId, Transport as _, TransportError, TransportEvent}, +}; use libp2p_identity::{Keypair, PeerId}; use multiaddr::Multiaddr; -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; -use crate::endpoint::Endpoint; -use crate::Connection; -use crate::Error; +use crate::{endpoint::Endpoint, Connection, Error}; /// Config for the [`Transport`]. pub struct Config { @@ -62,10 +65,18 @@ impl libp2p_core::Transport for Transport { false } - fn dial(&mut self, addr: Multiaddr) -> Result> { + fn dial( + &mut self, + addr: Multiaddr, + dial_opts: DialOpts, + ) -> Result> { + if dial_opts.role.is_listener() { + return Err(TransportError::MultiaddrNotSupported(addr)); + } + let endpoint = Endpoint::from_multiaddr(&addr).map_err(|e| match e { e @ Error::InvalidMultiaddr(_) => { - tracing::warn!("{}", e); + tracing::debug!("{}", e); TransportError::MultiaddrNotSupported(addr) } e => TransportError::Other(e), @@ -83,21 +94,10 @@ impl libp2p_core::Transport for Transport { .boxed()) } - fn dial_as_listener( - &mut self, - addr: Multiaddr, - ) -> Result> { - Err(TransportError::MultiaddrNotSupported(addr)) - } - fn poll( self: Pin<&mut Self>, _cx: &mut Context<'_>, ) -> Poll> { Poll::Pending } - - fn address_translation(&self, _listen: &Multiaddr, _observed: &Multiaddr) -> Option { - None - } } diff --git a/transports/webtransport-websys/src/utils.rs b/transports/webtransport-websys/src/utils.rs index fcde226be87..df59ee15161 100644 --- a/transports/webtransport-websys/src/utils.rs +++ b/transports/webtransport-websys/src/utils.rs @@ -1,34 +1,28 @@ +use std::io; + use js_sys::{Promise, Reflect}; +use once_cell::sync::Lazy; use send_wrapper::SendWrapper; -use std::io; use wasm_bindgen::{JsCast, JsValue}; use crate::Error; +type Closure = wasm_bindgen::closure::Closure; +static DO_NOTHING: Lazy> = Lazy::new(|| { + let cb = Closure::new(|_| {}); + SendWrapper::new(cb) +}); + /// Properly detach a promise. /// /// A promise always runs in the background, however if you don't await it, /// or specify a `catch` handler before you drop it, it might cause some side /// effects. This function avoids any side effects. -// // Ref: https://github.com/typescript-eslint/typescript-eslint/blob/391a6702c0a9b5b3874a7a27047f2a721f090fb6/packages/eslint-plugin/docs/rules/no-floating-promises.md pub(crate) fn detach_promise(promise: Promise) { - type Closure = wasm_bindgen::closure::Closure; - static mut DO_NOTHING: Option> = None; - - // Allocate Closure only once and reuse it - let do_nothing = unsafe { - if DO_NOTHING.is_none() { - let cb = Closure::new(|_| {}); - DO_NOTHING = Some(SendWrapper::new(cb)); - } - - DO_NOTHING.as_deref().unwrap() - }; - // Avoid having "floating" promise and ignore any errors. // After `catch` promise is allowed to be dropped. - let _ = promise.catch(do_nothing); + let _ = promise.catch(&DO_NOTHING); } /// Typecasts a JavaScript type. @@ -55,8 +49,7 @@ where } } -/// Parse reponse from `ReadableStreamDefaultReader::read`. -// +/// Parse response from `ReadableStreamDefaultReader::read`. // Ref: https://streams.spec.whatwg.org/#default-reader-prototype pub(crate) fn parse_reader_response(resp: &JsValue) -> Result, JsValue> { let value = Reflect::get(resp, &JsValue::from_str("value"))?; diff --git a/wasm-tests/README.md b/wasm-tests/README.md index 1d0902b106c..2538e48a145 100644 --- a/wasm-tests/README.md +++ b/wasm-tests/README.md @@ -8,4 +8,4 @@ Before you run the tests you need to install the following: # Run tests -Just call `run-all.sh` or `run.sh` in the test directory you are interested. +Just call `run-all.sh` or `run.sh` in the test directory if you are interested. diff --git a/wasm-tests/webtransport-tests/Cargo.toml b/wasm-tests/webtransport-tests/Cargo.toml index 8d775b6c2fb..593743d1617 100644 --- a/wasm-tests/webtransport-tests/Cargo.toml +++ b/wasm-tests/webtransport-tests/Cargo.toml @@ -9,18 +9,18 @@ publish = false release = false [dependencies] -futures = "0.3.29" -getrandom = { version = "0.2.11", features = ["js"] } +futures = { workspace = true } +getrandom = { workspace = true, features = ["js"] } libp2p-core = { workspace = true } libp2p-identity = { workspace = true, features = ["rand"] } libp2p-noise = { workspace = true } libp2p-webtransport-websys = { workspace = true } multiaddr = { workspace = true } multihash = { workspace = true } -wasm-bindgen = "0.2.89" -wasm-bindgen-futures = "0.4.38" -wasm-bindgen-test = "0.3.38" -web-sys = { version = "0.3.65", features = ["Response", "Window"] } +wasm-bindgen = "0.2.93" +wasm-bindgen-futures = "0.4.43" +wasm-bindgen-test = "0.3.43" +web-sys = { version = "0.3.70", features = ["Response", "Window"] } [lints] workspace = true diff --git a/wasm-tests/webtransport-tests/echo-server/Dockerfile b/wasm-tests/webtransport-tests/echo-server/Dockerfile index f498e2baa1b..2d973c49041 100644 --- a/wasm-tests/webtransport-tests/echo-server/Dockerfile +++ b/wasm-tests/webtransport-tests/echo-server/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1.5-labs -FROM docker.io/library/golang:1.20 AS builder +FROM docker.io/library/golang:1.22 AS builder WORKDIR /workspace ADD . . RUN CGO_ENABLED=0 go build . diff --git a/wasm-tests/webtransport-tests/echo-server/go.mod b/wasm-tests/webtransport-tests/echo-server/go.mod index 669124e1f69..6ac311fcb68 100644 --- a/wasm-tests/webtransport-tests/echo-server/go.mod +++ b/wasm-tests/webtransport-tests/echo-server/go.mod @@ -1,64 +1,55 @@ module echo-server -go 1.20 +go 1.22 require ( - github.com/libp2p/go-libp2p v0.27.8 - github.com/multiformats/go-multiaddr v0.9.0 + github.com/libp2p/go-libp2p v0.33.1 + github.com/multiformats/go-multiaddr v0.12.2 + github.com/quic-go/quic-go v0.42.0 ) require ( - github.com/benbjohnson/clock v1.3.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/benbjohnson/clock v1.3.5 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect - github.com/flynn/noise v1.0.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/flynn/noise v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect - github.com/golang/mock v1.6.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/google/gopacket v1.1.19 // indirect - github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b // indirect + github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect github.com/ipfs/go-cid v0.4.1 // indirect github.com/ipfs/go-log/v2 v2.5.1 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect - github.com/klauspost/compress v1.16.4 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/klauspost/compress v1.17.6 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-netroute v0.2.1 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/minio/sha256-simd v1.0.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-multibase v0.2.0 // indirect - github.com/multiformats/go-multicodec v0.8.1 // indirect - github.com/multiformats/go-multihash v0.2.1 // indirect - github.com/multiformats/go-multistream v0.4.1 // indirect + github.com/multiformats/go-multicodec v0.9.0 // indirect + github.com/multiformats/go-multihash v0.2.3 // indirect + github.com/multiformats/go-multistream v0.5.0 // indirect github.com/multiformats/go-varint v0.0.7 // indirect - github.com/onsi/ginkgo/v2 v2.9.2 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect + github.com/onsi/ginkgo/v2 v2.15.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-19 v0.3.3 // indirect - github.com/quic-go/qtls-go1-20 v0.2.3 // indirect - github.com/quic-go/quic-go v0.33.0 // indirect - github.com/quic-go/webtransport-go v0.5.2 // indirect + github.com/quic-go/webtransport-go v0.6.0 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - go.uber.org/atomic v1.10.0 // indirect + go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.7.0 // indirect - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect - golang.org/x/mod v0.10.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.7.0 // indirect - golang.org/x/text v0.8.0 // indirect - golang.org/x/tools v0.7.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect - lukechampine.com/blake3 v1.1.7 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + google.golang.org/protobuf v1.33.0 // indirect + lukechampine.com/blake3 v1.2.1 // indirect ) diff --git a/wasm-tests/webtransport-tests/echo-server/go.sum b/wasm-tests/webtransport-tests/echo-server/go.sum index 2e4b66d2ec0..b6fac448bd7 100644 --- a/wasm-tests/webtransport-tests/echo-server/go.sum +++ b/wasm-tests/webtransport-tests/echo-server/go.sum @@ -10,36 +10,35 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= -github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= +github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= +github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -47,26 +46,22 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b h1:Qcx5LM0fSiks9uCyFZwDBUasd3lxd1RM0GYpL+Li5o4= -github.com/google/pprof v0.0.0-20230405160723-4a4c7d95572b/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= +github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo= +github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -82,38 +77,38 @@ github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU= -github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= +github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/libp2p/go-libp2p v0.27.8 h1:IX5x/4yKwyPQeVS2AXHZ3J4YATM9oHBGH1gBc23jBAI= -github.com/libp2p/go-libp2p v0.27.8/go.mod h1:eCFFtd0s5i/EVKR7+5Ki8bM7qwkNW3TPTTSSW9sz8NE= +github.com/libp2p/go-libp2p v0.33.1 h1:tvJl9b9M6nSLBtZSXSguq+/lRhRj2oLRkyhBmQNMFLA= +github.com/libp2p/go-libp2p v0.33.1/go.mod h1:zOUTMjG4I7TXwMndNyOBn/CNtVBLlvBlnxfi+8xzx+E= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= +github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= -github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ= +github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ= +github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= -github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= @@ -122,50 +117,42 @@ github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aG github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= -github.com/multiformats/go-multiaddr v0.9.0 h1:3h4V1LHIk5w4hJHekMKWALPXErDfz/sggzwC/NcqbDQ= -github.com/multiformats/go-multiaddr v0.9.0/go.mod h1:mI67Lb1EeTOYb8GQfL/7wpIZwc46ElrvzhYnoJOmTT0= +github.com/multiformats/go-multiaddr v0.12.2 h1:9G9sTY/wCYajKa9lyfWPmpZAwe6oV+Wb1zcmMS1HG24= +github.com/multiformats/go-multiaddr v0.12.2/go.mod h1:GKyaTYjZRdcUhyOetrxTk9z0cW+jA/YrnqTOvKgi44M= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= -github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8= -github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= -github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108= -github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= -github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= -github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= +github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= +github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= +github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= +github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= +github.com/multiformats/go-multistream v0.5.0 h1:5htLSLl7lvJk3xx3qT/8Zm9J4K8vEOf/QGkvOGQAyiE= +github.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dydlEqV3l6N3/GBsX6ILA= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= -github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= -github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-19 v0.3.3 h1:wznEHvJwd+2X3PqftRha0SUKmGsnb6dfArMhy9PeJVE= -github.com/quic-go/qtls-go1-19 v0.3.3/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= -github.com/quic-go/qtls-go1-20 v0.2.3 h1:m575dovXn1y2ATOb1XrRFcrv0F+EQmlowTkoraNkDPI= -github.com/quic-go/qtls-go1-20 v0.2.3/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= -github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= -github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA= -github.com/quic-go/webtransport-go v0.5.2 h1:GA6Bl6oZY+g/flt00Pnu0XtivSD8vukOu3lYhJjnGEk= -github.com/quic-go/webtransport-go v0.5.2/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= +github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM= +github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= +github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY= +github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= @@ -199,23 +186,25 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -224,11 +213,11 @@ golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= +golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -236,8 +225,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -251,8 +240,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -264,6 +253,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -275,18 +266,20 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -295,13 +288,11 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= @@ -319,10 +310,8 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -334,11 +323,12 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= -lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/wasm-tests/webtransport-tests/echo-server/main.go b/wasm-tests/webtransport-tests/echo-server/main.go index def4151bd1b..f5e36692bd0 100644 --- a/wasm-tests/webtransport-tests/echo-server/main.go +++ b/wasm-tests/webtransport-tests/echo-server/main.go @@ -12,6 +12,7 @@ import ( "github.com/libp2p/go-libp2p/core/transport" "github.com/libp2p/go-libp2p/p2p/transport/quicreuse" webtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport" + "github.com/quic-go/quic-go" "github.com/multiformats/go-multiaddr" ) @@ -69,7 +70,7 @@ func main() { panic(err) } - connManager, err := quicreuse.NewConnManager([32]byte{}) + connManager, err := quicreuse.NewConnManager(quic.StatelessResetKey{}, quic.TokenGeneratorKey{}) if err != nil { panic(err) } diff --git a/wasm-tests/webtransport-tests/src/lib.rs b/wasm-tests/webtransport-tests/src/lib.rs index 1f420cd6671..0ff838b49e5 100644 --- a/wasm-tests/webtransport-tests/src/lib.rs +++ b/wasm-tests/webtransport-tests/src/lib.rs @@ -1,14 +1,17 @@ -use futures::channel::oneshot; -use futures::{AsyncReadExt, AsyncWriteExt}; +#![allow(unexpected_cfgs)] +use std::{future::poll_fn, pin::Pin}; + +use futures::{channel::oneshot, AsyncReadExt, AsyncWriteExt}; use getrandom::getrandom; -use libp2p_core::{StreamMuxer, Transport as _}; +use libp2p_core::{ + transport::{DialOpts, PortUse}, + Endpoint, StreamMuxer, Transport as _, +}; use libp2p_identity::{Keypair, PeerId}; use libp2p_noise as noise; use libp2p_webtransport_websys::{Config, Connection, Error, Stream, Transport}; use multiaddr::{Multiaddr, Protocol}; use multihash::Multihash; -use std::future::poll_fn; -use std::pin::Pin; use wasm_bindgen::JsCast; use wasm_bindgen_futures::{spawn_local, JsFuture}; use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; @@ -17,7 +20,7 @@ use web_sys::{window, Response}; wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test] -async fn single_conn_single_stream() { +pub async fn single_conn_single_stream() { let mut conn = new_connection_to_echo_server().await; let mut stream = create_stream(&mut conn).await; @@ -25,7 +28,7 @@ async fn single_conn_single_stream() { } #[wasm_bindgen_test] -async fn single_conn_single_stream_incoming() { +pub async fn single_conn_single_stream_incoming() { let mut conn = new_connection_to_echo_server().await; let mut stream = incoming_stream(&mut conn).await; @@ -33,7 +36,7 @@ async fn single_conn_single_stream_incoming() { } #[wasm_bindgen_test] -async fn single_conn_multiple_streams() { +pub async fn single_conn_multiple_streams() { let mut conn = new_connection_to_echo_server().await; let mut tasks = Vec::new(); let mut streams = Vec::new(); @@ -56,7 +59,7 @@ async fn single_conn_multiple_streams() { } #[wasm_bindgen_test] -async fn multiple_conn_multiple_streams() { +pub async fn multiple_conn_multiple_streams() { let mut tasks = Vec::new(); let mut conns = Vec::new(); @@ -87,7 +90,7 @@ async fn multiple_conn_multiple_streams() { } #[wasm_bindgen_test] -async fn multiple_conn_multiple_streams_sequential() { +pub async fn multiple_conn_multiple_streams_sequential() { for _ in 0..10 { let mut conn = new_connection_to_echo_server().await; @@ -104,7 +107,7 @@ async fn multiple_conn_multiple_streams_sequential() { } #[wasm_bindgen_test] -async fn read_leftovers() { +pub async fn read_leftovers() { let mut conn = new_connection_to_echo_server().await; let mut stream = create_stream(&mut conn).await; @@ -127,7 +130,7 @@ async fn read_leftovers() { } #[wasm_bindgen_test] -async fn allow_read_after_closing_writer() { +pub async fn allow_read_after_closing_writer() { let mut conn = new_connection_to_echo_server().await; let mut stream = create_stream(&mut conn).await; @@ -153,7 +156,7 @@ async fn allow_read_after_closing_writer() { } #[wasm_bindgen_test] -async fn poll_outbound_error_after_connection_close() { +pub async fn poll_outbound_error_after_connection_close() { let mut conn = new_connection_to_echo_server().await; // Make sure that poll_outbound works well before closing the connection @@ -171,7 +174,7 @@ async fn poll_outbound_error_after_connection_close() { } #[wasm_bindgen_test] -async fn poll_inbound_error_after_connection_close() { +pub async fn poll_inbound_error_after_connection_close() { let mut conn = new_connection_to_echo_server().await; // Make sure that poll_inbound works well before closing the connection @@ -189,7 +192,7 @@ async fn poll_inbound_error_after_connection_close() { } #[wasm_bindgen_test] -async fn read_error_after_connection_drop() { +pub async fn read_error_after_connection_drop() { let mut conn = new_connection_to_echo_server().await; let mut stream = create_stream(&mut conn).await; @@ -204,7 +207,7 @@ async fn read_error_after_connection_drop() { } #[wasm_bindgen_test] -async fn read_error_after_connection_close() { +pub async fn read_error_after_connection_close() { let mut conn = new_connection_to_echo_server().await; let mut stream = create_stream(&mut conn).await; @@ -222,7 +225,7 @@ async fn read_error_after_connection_close() { } #[wasm_bindgen_test] -async fn write_error_after_connection_drop() { +pub async fn write_error_after_connection_drop() { let mut conn = new_connection_to_echo_server().await; let mut stream = create_stream(&mut conn).await; @@ -237,7 +240,7 @@ async fn write_error_after_connection_drop() { } #[wasm_bindgen_test] -async fn write_error_after_connection_close() { +pub async fn write_error_after_connection_close() { let mut conn = new_connection_to_echo_server().await; let mut stream = create_stream(&mut conn).await; @@ -255,7 +258,7 @@ async fn write_error_after_connection_close() { } #[wasm_bindgen_test] -async fn connect_without_peer_id() { +pub async fn connect_without_peer_id() { let mut addr = fetch_server_addr().await; let keypair = Keypair::generate_ed25519(); @@ -263,11 +266,21 @@ async fn connect_without_peer_id() { addr.pop(); let mut transport = Transport::new(Config::new(&keypair)); - transport.dial(addr).unwrap().await.unwrap(); + transport + .dial( + addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap(); } #[wasm_bindgen_test] -async fn error_on_unknown_peer_id() { +pub async fn error_on_unknown_peer_id() { let mut addr = fetch_server_addr().await; let keypair = Keypair::generate_ed25519(); @@ -278,12 +291,22 @@ async fn error_on_unknown_peer_id() { addr.push(Protocol::P2p(PeerId::random())); let mut transport = Transport::new(Config::new(&keypair)); - let e = transport.dial(addr.clone()).unwrap().await.unwrap_err(); + let e = transport + .dial( + addr.clone(), + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap_err(); assert!(matches!(e, Error::UnknownRemotePeerId)); } #[wasm_bindgen_test] -async fn error_on_unknown_certhash() { +pub async fn error_on_unknown_certhash() { let mut addr = fetch_server_addr().await; let keypair = Keypair::generate_ed25519(); @@ -297,7 +320,17 @@ async fn error_on_unknown_certhash() { addr.push(peer_id); let mut transport = Transport::new(Config::new(&keypair)); - let e = transport.dial(addr.clone()).unwrap().await.unwrap_err(); + let e = transport + .dial( + addr.clone(), + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap_err(); assert!(matches!( e, Error::Noise(noise::Error::UnknownWebTransportCerthashes(..)) @@ -310,7 +343,17 @@ async fn new_connection_to_echo_server() -> Connection { let mut transport = Transport::new(Config::new(&keypair)); - let (_peer_id, conn) = transport.dial(addr).unwrap().await.unwrap(); + let (_peer_id, conn) = transport + .dial( + addr, + DialOpts { + role: Endpoint::Dialer, + port_use: PortUse::Reuse, + }, + ) + .unwrap() + .await + .unwrap(); conn }