diff --git a/.github/workflows/clippy-lint.yaml b/.github/workflows/clippy-lint.yaml
index e73915f245..d4ef46216a 100644
--- a/.github/workflows/clippy-lint.yaml
+++ b/.github/workflows/clippy-lint.yaml
@@ -27,5 +27,6 @@ jobs:
toolchain: 1.89
override: true
components: clippy
- - name: Run Clippy on different workspaces and crates
- run: ./scripts/rust/clippy.sh
+ - name: Run Clippy
+ run: |
+ cargo clippy --all-features -- -D warnings
diff --git a/.github/workflows/coverage-protocols.yaml b/.github/workflows/coverage-protocols.yaml
index 4b0239e12d..5b33615af8 100644
--- a/.github/workflows/coverage-protocols.yaml
+++ b/.github/workflows/coverage-protocols.yaml
@@ -24,95 +24,119 @@ jobs:
- name: Upload protocols coverage to codecov.io
uses: codecov/codecov-action@v4
with:
- directory: ./protocols/target/tarpaulin-reports
- file: ./protocols/target/tarpaulin-reports/cobertura.xml
+ directory: ./target/tarpaulin-reports
+ file: ./target/tarpaulin-reports/cobertura.xml
flags: protocols
token: ${{ secrets.CODECOV_TOKEN }}
-
+
+ - name: Upload binary_codec_sv2-coverage to codecov.io
+ uses: codecov/codecov-action@v4
+ with:
+ directory: ./target/tarpaulin-reports/codec-coverage
+ file: ./target/tarpaulin-reports/codec-coverage/cobertura.xml
+ flags: binary_codec_sv2-coverage
+ token: ${{ secrets.CODECOV_TOKEN }}
+
- name: Upload binary_sv2-coverage to codecov.io
uses: codecov/codecov-action@v4
with:
- directory: ./protocols/target/tarpaulin-reports/binary-sv2-coverage
- file: ./protocols/target/tarpaulin-reports/binary-sv2-coverage/cobertura.xml
+ directory: ./target/tarpaulin-reports/binary-sv2-coverage
+ file: ./target/tarpaulin-reports/binary-sv2-coverage/cobertura.xml
flags: binary_sv2-coverage
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload codec_sv2-coverage to codecov.io
uses: codecov/codecov-action@v4
with:
- directory: ./protocols/target/tarpaulin-reports/codec-sv2-coverage
- file: ./protocols/target/tarpaulin-reports/codec-sv2-coverage/cobertura.xml
+ directory: ./target/tarpaulin-reports/codec-sv2-coverage
+ file: ./target/tarpaulin-reports/codec-sv2-coverage/cobertura.xml
flags: codec_sv2-coverage
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload channels_sv2-coverage to codecov.io
uses: codecov/codecov-action@v4
with:
- directory: ./protocols/target/tarpaulin-reports/channels-sv2-coverage
- file: ./protocols/target/tarpaulin-reports/channels-sv2-coverage/cobertura.xml
+ directory: ./target/tarpaulin-reports/channels-sv2-coverage
+ file: ./target/tarpaulin-reports/channels-sv2-coverage/cobertura.xml
flags: channels_sv2-coverage
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload common_messages_sv2-coverage to codecov.io
uses: codecov/codecov-action@v4
with:
- directory: ./protocols/target/tarpaulin-reports/common-messages-coverage
- file: ./protocols/target/tarpaulin-reports/common-messages-coverage/cobertura.xml
+ directory: ./target/tarpaulin-reports/common-messages-coverage
+ file: ./target/tarpaulin-reports/common-messages-coverage/cobertura.xml
flags: common_messages_sv2-coverage
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload framing_sv2-coverage to codecov.io
uses: codecov/codecov-action@v4
with:
- directory: ./protocols/target/tarpaulin-reports/framing-sv2-coverage
- file: ./protocols/target/tarpaulin-reports/framing-sv2-coverage/cobertura.xml
+ directory: ./target/tarpaulin-reports/framing-sv2-coverage
+ file: ./target/tarpaulin-reports/framing-sv2-coverage/cobertura.xml
flags: framing_sv2-coverage
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload job_declaration_sv2-coverage to codecov.io
uses: codecov/codecov-action@v4
with:
- directory: ./protocols/target/tarpaulin-reports/job-declaration-coverage
- file: ./protocols/target/tarpaulin-reports/job-declaration-coverage/cobertura.xml
+ directory: ./target/tarpaulin-reports/job-declaration-coverage
+ file: ./target/tarpaulin-reports/job-declaration-coverage/cobertura.xml
flags: job_declaration_sv2-coverage
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload noise_sv2-coverage to codecov.io
uses: codecov/codecov-action@v4
with:
- directory: ./protocols/target/tarpaulin-reports/noise-sv2-coverage
- file: ./protocols/target/tarpaulin-reports/noise-sv2-coverage/cobertura.xml
+ directory: ./target/tarpaulin-reports/noise-sv2-coverage
+ file: ./target/tarpaulin-reports/noise-sv2-coverage/cobertura.xml
flags: noise_sv2-coverage
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload parsers_sv2-coverage to codecov.io
uses: codecov/codecov-action@v4
with:
- directory: ./protocols/target/tarpaulin-reports/parsers-sv2-coverage
- file: ./protocols/target/tarpaulin-reports/parsers-sv2-coverage/cobertura.xml
+ directory: ./target/tarpaulin-reports/parsers-sv2-coverage
+ file: ./target/tarpaulin-reports/parsers-sv2-coverage/cobertura.xml
flags: parsers_sv2-coverage
token: ${{ secrets.CODECOV_TOKEN }}
+ - name: Upload roles_logic_sv2-coverage to codecov.io
+ uses: codecov/codecov-action@v4
+ with:
+ directory: ./target/tarpaulin-reports/roles-logic-sv2-coverage
+ file: ./target/tarpaulin-reports/roles-logic-sv2-coverage/cobertura.xml
+ flags: roles_logic_sv2-coverage
+ token: ${{ secrets.CODECOV_TOKEN }}
+
- name: Upload v1-coverage to codecov.io
uses: codecov/codecov-action@v4
with:
- directory: ./protocols/target/tarpaulin-reports/v1-coverage
- file: ./protocols/target/tarpaulin-reports/v1-coverage/cobertura.xml
+ directory: ./target/tarpaulin-reports/v1-coverage
+ file: ./target/tarpaulin-reports/v1-coverage/cobertura.xml
flags: v1-coverage
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload template_distribution_sv2-coverage to codecov.io
uses: codecov/codecov-action@v4
with:
- directory: ./protocols/target/tarpaulin-reports/template-distribution-coverage
- file: ./protocols/target/tarpaulin-reports/template-distribution-coverage/cobertura.xml
+ directory: ./target/tarpaulin-reports/template-distribution-coverage
+ file: ./target/tarpaulin-reports/template-distribution-coverage/cobertura.xml
flags: template_distribution_sv2-coverage
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload mining-coverage to codecov.io
uses: codecov/codecov-action@v4
with:
- directory: ./protocols/target/tarpaulin-reports/mining-coverage
- file: ./protocols/target/tarpaulin-reports/mining-coverage/cobertura.xml
+ directory: ./target/tarpaulin-reports/mining-coverage
+ file: ./target/tarpaulin-reports/mining-coverage/cobertura.xml
flags: mining-coverage
token: ${{ secrets.CODECOV_TOKEN }}
+
+ - name: Upload handlers_sv2-coverage to codecov.io
+ uses: codecov/codecov-action@v4
+ with:
+ directory: ./target/tarpaulin-reports/handlers-sv2-coverage
+ file: ./target/tarpaulin-reports/handlers-sv2-coverage/cobertura.xml
+ flags: handler_sv2-coverage
+ token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/coverage-roles.yaml b/.github/workflows/coverage-roles.yaml
deleted file mode 100644
index ce2c51e376..0000000000
--- a/.github/workflows/coverage-roles.yaml
+++ /dev/null
@@ -1,86 +0,0 @@
-name: Roles test Coverage
-
-on:
- push:
- branches:
- - main
-
-jobs:
- roles-coverage:
-
- name: tarpaulin Test
- runs-on: ubuntu-latest
- container:
- image: xd009642/tarpaulin:0.27.1-nightly
- options: --security-opt seccomp=unconfined
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Generate code coverage
- run: |
- ./scripts/coverage-roles.sh
-
- - name: Upload roles coverage to codecov.io
- uses: codecov/codecov-action@v4
- with:
- directory: ./roles/target/tarpaulin-reports
- file: ./roles/target/tarpaulin-reports/cobertura.xml
- flags: roles
- token: ${{ secrets.CODECOV_TOKEN }}
-
- - name: Upload jd_client-coverage to codecov.io
- uses: codecov/codecov-action@v4
- with:
- directory: ./roles/target/tarpaulin-reports/jd-client-coverage
- file: ./roles/target/tarpaulin-reports/jd-client-coverage/cobertura.xml
- flags: jd_client-coverage
- token: ${{ secrets.CODECOV_TOKEN }}
-
- - name: Upload jd_server-coverage to codecov.io
- uses: codecov/codecov-action@v4
- with:
- directory: ./roles/target/tarpaulin-reports/jd-server-coverage
- file: ./roles/target/tarpaulin-reports/jd-server-coverage/cobertura.xml
- flags: jd_server-coverage
- token: ${{ secrets.CODECOV_TOKEN }}
-
- - name: Upload mining_device-coverage to codecov.io
- uses: codecov/codecov-action@v4
- with:
- directory: ./roles/target/tarpaulin-reports/mining-device-coverage
- file: ./roles/target/tarpaulin-reports/mining-device-coverage/cobertura.xml
- flags: mining_device-coverage
- token: ${{ secrets.CODECOV_TOKEN }}
-
- - name: Upload pool_sv2-coverage to codecov.io
- uses: codecov/codecov-action@v4
- with:
- directory: ./roles/target/tarpaulin-reports/pool-coverage
- file: ./roles/target/tarpaulin-reports/pool-coverage/cobertura.xml
- flags: pool_sv2-coverage
- token: ${{ secrets.CODECOV_TOKEN }}
-
- - name: Upload sv1-mining-device-coverage to codecov.io
- uses: codecov/codecov-action@v4
- with:
- directory: ./roles/target/tarpaulin-reports/sv1-mining-device-coverage
- file: ./roles/target/tarpaulin-reports/sv1-mining-device-coverage/cobertura.xml
- flags: sv1-mining-device-coverage
- token: ${{ secrets.CODECOV_TOKEN }}
-
- - name: Upload translator_sv2-coverage to codecov.io
- uses: codecov/codecov-action@v4
- with:
- directory: ./roles/target/tarpaulin-reports/translator-coverage
- file: ./roles/target/tarpaulin-reports/translator-coverage/cobertura.xml
- flags: translator_sv2-coverage
- token: ${{ secrets.CODECOV_TOKEN }}
-
- - name: Upload stratum-apps-coverage to codecov.io
- uses: codecov/codecov-action@v4
- with:
- directory: ./roles/target/tarpaulin-reports/stratum-apps-coverage
- file: ./roles/target/tarpaulin-reports/stratum-apps-coverage/cobertura.xml
- flags: stratum-apps-coverage
- token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/coverage-utils.yaml b/.github/workflows/coverage-utils.yaml
deleted file mode 100644
index a3c6611263..0000000000
--- a/.github/workflows/coverage-utils.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-name: Util Test Coverage
-
-on:
- push:
- branches:
- - main
-
-jobs:
- utils-coverage:
-
- name: tarpaulin Test
- runs-on: ubuntu-latest
- container:
- image: xd009642/tarpaulin:0.27.1-nightly
- options: --security-opt seccomp=unconfined
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Generate code coverage
- run: |
- ./scripts/coverage-utils.sh
-
- - name: Upload utils coverage to codecov.io
- uses: codecov/codecov-action@v4
- with:
- directory: ./utils/target/tarpaulin-reports
- file: ./utils/target/tarpaulin-reports/cobertura.xml
- flags: utils
- token: ${{ secrets.CODECOV_TOKEN }}
-
- - name: Upload buffer_sv2-coverage to codecov.io
- uses: codecov/codecov-action@v4
- with:
- directory: ./utils/target/tarpaulin-reports/buffer-coverage
- file: ./utils/target/tarpaulin-reports/buffer-coverage/cobertura.xml
- flags: buffer_sv2-coverage
-
- token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
index 347b4a8293..10bbec46f7 100644
--- a/.github/workflows/docs.yaml
+++ b/.github/workflows/docs.yaml
@@ -31,65 +31,70 @@ jobs:
- name: Rust Docs crate buffer_sv2
run: |
- cd utils/buffer
+ cd sv2/buffer-sv2
cargo doc
- name: Rust Docs crate binary_sv2 derive_codec
run: |
- cd protocols/v2/binary-sv2/derive_codec
+ cd sv2/binary-sv2/derive_codec
cargo doc
- name: Rust Docs crate binary_sv2
run: |
- cd protocols/v2/binary-sv2
+ cd sv2/binary-sv2
cargo doc --features with_buffer_pool
- name: Rust Docs crate channels_sv2
run: |
- cd protocols/v2/channels-sv2
+ cd sv2/channels-sv2
cargo doc
- name: Rust Docs crate parsers_sv2
run: |
- cd protocols/v2/parsers-sv2
+ cd sv2/parsers-sv2
cargo doc
- name: Rust Docs crate framing_sv2
run: |
- cd protocols/v2/framing-sv2
+ cd sv2/framing-sv2
cargo doc --features with_buffer_pool
- name: Rust Docs crate noise_sv2
run: |
- cd protocols/v2/noise-sv2
+ cd sv2/noise-sv2
cargo doc --features std
- name: Rust Docs crate codec_sv2
run: |
- cd protocols/v2/codec-sv2
+ cd sv2/codec-sv2
cargo doc --features with_buffer_pool,noise_sv2
+ - name: Rust Docs crate handlers_sv2
+ run: |
+ cd sv2/handlers-sv2
+ cargo doc
+
- name: Rust Docs crate common_messages
run: |
- cd protocols/v2/subprotocols/common-messages
+ cd sv2/subprotocols/common-messages
cargo doc
- name: Rust Docs crate job_declaration
run: |
- cd protocols/v2/subprotocols/job-declaration
+ cd sv2/subprotocols/job-declaration
cargo doc --all-features
- name: Rust Docs crate mining
run: |
- cd protocols/v2/subprotocols/mining
+ cd sv2/subprotocols/mining
cargo doc --all-features
- name: Rust Docs crate template_distribution
run: |
- cd protocols/v2/subprotocols/template-distribution
+ cd sv2/subprotocols/template-distribution
cargo doc
- name: Rust Docs crate sv1_api
run: |
- cd protocols/v1
+ cd sv1
cargo doc
diff --git a/.github/workflows/fmt.yaml b/.github/workflows/fmt.yaml
index 0006a96765..91bf9254df 100644
--- a/.github/workflows/fmt.yaml
+++ b/.github/workflows/fmt.yaml
@@ -29,8 +29,4 @@ jobs:
components: rustfmt
- name: Run fmt in different workspaces and crates
run: |
- cargo fmt --all --manifest-path=stratum-core/Cargo.toml -- --check
- cargo fmt --all --manifest-path=protocols/Cargo.toml -- --check
- cargo fmt --all --manifest-path=roles/Cargo.toml -- --check
- cargo fmt --all --manifest-path=utils/Cargo.toml -- --check
- cargo fmt --all --manifest-path=test/integration-tests/Cargo.toml -- --check
+ cargo fmt --all -- --check --verbose
diff --git a/.github/workflows/integration-tests.yaml b/.github/workflows/integration-tests.yaml
index 23e3017950..9b3a5d5ed1 100644
--- a/.github/workflows/integration-tests.yaml
+++ b/.github/workflows/integration-tests.yaml
@@ -24,9 +24,6 @@ jobs:
toolchain: stable
override: true
- - name: Install cargo-nextest
- run: cargo install cargo-nextest --locked
-
- - name: Integration Tests
+ - name: Run Integration Tests Script
run: |
- RUST_BACKTRACE=1 RUST_LOG=debug cargo nextest run --manifest-path=test/integration-tests/Cargo.toml --nocapture
+ ./scripts/run-integration-tests.sh
diff --git a/.github/workflows/lockfiles.yaml b/.github/workflows/lockfiles.yaml
deleted file mode 100644
index cfe52da48d..0000000000
--- a/.github/workflows/lockfiles.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Lockfiles
-
-# Trigger the workflow on pull request events for the main branch
-on:
- pull_request:
- branches:
- - main
-
-jobs:
- build:
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v4
-
- - name: Install Rust
- uses: actions-rs/toolchain@v1
- with:
- toolchain: stable
- override: true
-
- - name: Build with locked dependencies
- run: |
- cargo build --manifest-path=roles/Cargo.toml --locked
- cargo build --manifest-path=utils/Cargo.toml --locked
- cargo build --manifest-path=test/integration-tests/Cargo.toml --locked
diff --git a/.github/workflows/release-libs.yaml b/.github/workflows/release-libs.yaml
index 7fbc12fa1a..6d8d0782f6 100644
--- a/.github/workflows/release-libs.yaml
+++ b/.github/workflows/release-libs.yaml
@@ -32,77 +32,73 @@ jobs:
# Base dependencies with no local dependencies
- name: Publish crate buffer_sv2
run: |
- ./scripts/release-libs.sh utils/buffer
+ ./scripts/release-libs.sh sv2/buffer-sv2
- name: Publish crate noise_sv2
run: |
- ./scripts/release-libs.sh protocols/v2/noise-sv2
+ ./scripts/release-libs.sh sv2/noise-sv2
- name: Publish crate binary_sv2 derive_codec
run: |
- ./scripts/release-libs.sh protocols/v2/binary-sv2/derive_codec
+ ./scripts/release-libs.sh sv2/binary-sv2/derive_codec
- name: Publish crate binary_sv2
run: |
- ./scripts/release-libs.sh protocols/v2/binary-sv2
+ ./scripts/release-libs.sh sv2/binary-sv2
# framing_sv2(depends on binary_sv2, buffer_sv2, noise_sv2)
- name: Publish crate framing_sv2
run: |
- ./scripts/release-libs.sh protocols/v2/framing-sv2
+ ./scripts/release-libs.sh sv2/framing-sv2
# codec_sv2 (depends on framing_sv2, noise_sv2, binary_sv2, buffer_sv2)
- name: Publish crate codec_sv2
run: |
- ./scripts/release-libs.sh protocols/v2/codec-sv2
+ ./scripts/release-libs.sh sv2/codec-sv2
# Subprotocols (depend on binary_sv2)
- name: Publish crate common_messages
run: |
- ./scripts/release-libs.sh protocols/v2/subprotocols/common-messages
+ ./scripts/release-libs.sh sv2/subprotocols/common-messages
- name: Publish crate job_declaration
run: |
- ./scripts/release-libs.sh protocols/v2/subprotocols/job-declaration
+ ./scripts/release-libs.sh sv2/subprotocols/job-declaration
- name: Publish crate mining
run: |
- ./scripts/release-libs.sh protocols/v2/subprotocols/mining
+ ./scripts/release-libs.sh sv2/subprotocols/mining
- name: Publish crate template_distribution
run: |
- ./scripts/release-libs.sh protocols/v2/subprotocols/template-distribution
+ ./scripts/release-libs.sh sv2/subprotocols/template-distribution
# channels_sv2 (depends on binary_sv2, common_messages_sv2, mining_sv2, template_distribution_sv2, job_declaration_sv2)
- name: Publish crate channels_sv2
run: |
- ./scripts/release-libs.sh protocols/v2/channels-sv2
+ ./scripts/release-libs.sh sv2/channels-sv2
# parsers_sv2 (depends on binary_sv2, framing_sv2, common_messages, mining, template_distribution, job_declaration)
- name: Publish crate parsers_sv2
run: |
- ./scripts/release-libs.sh protocols/v2/parsers-sv2
+ ./scripts/release-libs.sh sv2/parsers-sv2
# sv1_api (depends on binary_sv2)
- name: Publish crate v1
run: |
- ./scripts/release-libs.sh protocols/v1
+ ./scripts/release-libs.sh sv1
# stratum_translation (depends on binary_sv2, mining_sv2, channels_sv2, v1)
- name: Publish crate stratum_translation
run: |
- ./scripts/release-libs.sh protocols/stratum-translation
+ ./scripts/release-libs.sh stratum-core/stratum-translation
# handlers_sv2 (depends on parsers_sv2, binary_sv2, common_messages_sv2, mining_sv2, template_distribution_sv2, job_declaration_sv2)
- name: Publish crate handlers_sv2
run: |
- ./scripts/release-libs.sh protocols/v2/handlers-sv2
+ ./scripts/release-libs.sh sv2/handlers-sv2
# Stratum Core (re-exports all the protocol crates)
- name: Publish crate stratum-core
run: |
./scripts/release-libs.sh stratum-core/stratum-core
- # stratum-apps (depends on stratum-core and external crates)
- - name: Publish crate stratum-apps
- run: |
- ./scripts/release-libs.sh roles/stratum-apps
diff --git a/.github/workflows/rust-msrv.yaml b/.github/workflows/rust-msrv.yaml
index ae1a84cf7c..17c98b53fb 100644
--- a/.github/workflows/rust-msrv.yaml
+++ b/.github/workflows/rust-msrv.yaml
@@ -22,25 +22,9 @@ jobs:
with:
toolchain: ${{ matrix.rust }}
override: true
- - name: Build stratum-core
- run: cargo build --manifest-path=stratum-core/Cargo.toml
- - name: Build Protocols
- run: cargo build --manifest-path=protocols/Cargo.toml
- - name: Build Roles
- run: cargo build --locked --manifest-path=roles/Cargo.toml
- - name: Build Utils
- run: cargo build --locked --manifest-path=utils/Cargo.toml
- - name: Build Integration Tests
- run: cargo build --locked --manifest-path=test/integration-tests/Cargo.toml
+ - name: Build Workspace
+ run: cargo build
# also check test compilation without running tests
- - name: Check Test Compilation for Protocols
- run: cargo test --manifest-path=protocols/Cargo.toml --no-run
- - name: Check Test Compilation for Stratum Core
- run: cargo test --manifest-path=stratum-core/Cargo.toml --no-run
- - name: Check Test Compilation for Roles
- run: cargo test --locked --manifest-path=roles/Cargo.toml --no-run
- - name: Check Test Compilation for Utils
- run: cargo test --locked --manifest-path=utils/Cargo.toml --no-run
- - name: Check Test Compilation for Integration Tests
- run: cargo test --locked --manifest-path=test/integration-tests/Cargo.toml --no-run
+ - name: Check Test Compilation for Workspace
+ run: cargo test --no-run
diff --git a/.github/workflows/semver-check.yaml b/.github/workflows/semver-check.yaml
index d7865da3d5..5b845d2606 100644
--- a/.github/workflows/semver-check.yaml
+++ b/.github/workflows/semver-check.yaml
@@ -41,60 +41,60 @@ jobs:
- name: Install cargo-semver-checks
run: cargo install cargo-semver-checks --version 0.37.0 --locked
- - name: Run semver checks for utils/buffer
- working-directory: utils/buffer
+ - name: Run semver checks for sv2/buffer-sv2
+ working-directory: sv2/buffer-sv2
run: cargo semver-checks
- - name: Run semver checks for protocols/v2/binary-sv2
- working-directory: protocols/v2/binary-sv2
+ - name: Run semver checks for sv2/binary-sv2
+ working-directory: sv2/binary-sv2
run: cargo semver-checks
- - name: Run semver checks for protocols/v2/framing-sv2
- working-directory: protocols/v2/framing-sv2
+ - name: Run semver checks for sv2/framing-sv2
+ working-directory: sv2/framing-sv2
run: cargo semver-checks
- - name: Run semver checks for protocols/v2/noise-sv2
- working-directory: protocols/v2/noise-sv2
+ - name: Run semver checks for sv2/noise-sv2
+ working-directory: sv2/noise-sv2
run: cargo semver-checks
- - name: Run semver checks for protocols/v2/codec-sv2
- working-directory: protocols/v2/codec-sv2
+ - name: Run semver checks for sv2/codec-sv2
+ working-directory: sv2/codec-sv2
run: cargo semver-checks
- - name: Run semver checks for protocols/v2/subprotocols/common-messages
- working-directory: protocols/v2/subprotocols/common-messages
+ - name: Run semver checks for sv2/subprotocols/common-messages
+ working-directory: sv2/subprotocols/common-messages
run: cargo semver-checks
- - name: Run semver checks for protocols/v2/subprotocols/job-declaration
- working-directory: protocols/v2/subprotocols/job-declaration
+ - name: Run semver checks for sv2/subprotocols/job-declaration
+ working-directory: sv2/subprotocols/job-declaration
run: cargo semver-checks
- - name: Run semver checks for protocols/v2/subprotocols/mining
- working-directory: protocols/v2/subprotocols/mining
+ - name: Run semver checks for sv2/subprotocols/mining
+ working-directory: sv2/subprotocols/mining
run: cargo semver-checks
- - name: Run semver checks for protocols/v2/subprotocols/template-distribution
- working-directory: protocols/v2/subprotocols/template-distribution
+ - name: Run semver checks for sv2/subprotocols/template-distribution
+ working-directory: sv2/subprotocols/template-distribution
run: cargo semver-checks
- - name: Run semver checks for protocols/v2/channels-sv2
- working-directory: protocols/v2/channels-sv2
+ - name: Run semver checks for sv2/channels-sv2
+ working-directory: sv2/channels-sv2
run: cargo semver-checks
- - name: Run semver checks for protocols/v2/parsers-sv2
- working-directory: protocols/v2/parsers-sv2
+ - name: Run semver checks for sv2/parsers-sv2
+ working-directory: sv2/parsers-sv2
run: cargo semver-checks
- - name: Run semver checks for protocols/v2/handlers-sv2
- working-directory: protocols/v2/handlers-sv2
+ - name: Run semver checks for sv2/handlers-sv2
+ working-directory: sv2/handlers-sv2
run: cargo semver-checks
- name: Run semver checks for protocols/v1
- working-directory: protocols/v1
+ working-directory: sv1
run: cargo semver-checks
- - name: Run semver checks for protocols/stratum-translation
- working-directory: protocols/stratum-translation
+ - name: Run semver checks for stratum-core/stratum-translation
+ working-directory: stratum-core/stratum-translation
run: cargo semver-checks
# TODO: Uncomment this when the stratum-core crate is published to crates.io
@@ -102,7 +102,3 @@ jobs:
# working-directory: stratum-core
# run: cargo semver-checks
- # TODO: Uncomment this when the stratum-apps crate is published to crates.io
- # - name: Run semver checks for roles/stratum-apps
- # working-directory: roles/stratum-apps
- # run: cargo semver-checks
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 68bac3d762..c0e8c03576 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -1,18 +1,49 @@
+name: Rust CI
+
on:
pull_request:
branches:
- main
-name: Test, Prop Tests, Example Tests
-
jobs:
- ci:
+ test-core:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
+ - ubuntu-latest
- macos-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ profile: minimal
+ override: true
+
+ - name: Cache cargo registry
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: Run tests
+ run: |
+ cargo test --verbose
+
+ run_examples:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os:
- ubuntu-latest
+ - macos-latest
include:
- os: macos-latest
target: aarch64-apple-darwin
@@ -20,69 +51,36 @@ jobs:
target: x86_64-unknown-linux-musl
steps:
- - name: Install stable toolchain & components
- uses: actions/checkout@v4
+ - uses: actions/checkout@v4
+
+ - name: Setup Rust
+ uses: actions-rs/toolchain@v1
with:
- profile: minimal
toolchain: nightly
+ profile: minimal
override: true
- name: Build
run: |
- cargo build --manifest-path=stratum-core/Cargo.toml
- cargo build --manifest-path=protocols/Cargo.toml
- cargo build --manifest-path=roles/Cargo.toml
- cargo build --manifest-path=utils/Cargo.toml
- cargo build --manifest-path=roles/stratum-apps/Cargo.toml
-
- - name: Run sv1-client-and-server example
- run: |
- cargo run --manifest-path=protocols/v1/Cargo.toml --example client_and_server 30
-
- - name: Run framing-sv2 example
- run: |
- cargo run --manifest-path=protocols/v2/framing-sv2/Cargo.toml --example sv2_frame
-
- - name: Run codec-sv2 examples
- run: |
- cargo run --manifest-path=protocols/v2/codec-sv2/Cargo.toml --example unencrypted
- cargo run --manifest-path=protocols/v2/codec-sv2/Cargo.toml --example encrypted --features=noise_sv2
+ cargo build --workspace
- - name: Run binary-sv2 examples
+ - name: Run examples
run: |
- cargo run --manifest-path=protocols/v2/binary-sv2/Cargo.toml --example encode_decode
+ cargo run --manifest-path=sv1/Cargo.toml --example client_and_server 30
+ cargo run --manifest-path=sv2/framing-sv2/Cargo.toml --example sv2_frame
+ cargo run --manifest-path=sv2/codec-sv2/Cargo.toml --example unencrypted
+ cargo run --manifest-path=sv2/codec-sv2/Cargo.toml --example encrypted --features=noise_sv2
+ cargo run --manifest-path=sv2/binary-sv2/Cargo.toml --example encode_decode
+ cargo run --manifest-path=sv2/noise-sv2/Cargo.toml --example handshake
- - name: Run noise-sv2 examples
- run: |
- cargo run --manifest-path=protocols/v2/noise-sv2/Cargo.toml --example handshake
-
- - name: fuzz tests
+ - name: Fuzz tests
run: |
if [ ${{ matrix.os }} == "ubuntu-latest" ]; then
./run.sh 30
else
echo "Skipping fuzz test on ${{ matrix.os }} - not supported"
fi
- working-directory: utils/buffer/fuzz
+ working-directory: sv2/buffer-sv2/fuzz
- - name: Test
- run: |
- cargo test --manifest-path=stratum-core/Cargo.toml
- cargo test --manifest-path=protocols/Cargo.toml
- cargo test --manifest-path=roles/Cargo.toml
- cargo test --manifest-path=utils/Cargo.toml
- cargo test --manifest-path=roles/stratum-apps/Cargo.toml --features config
- cargo test --manifest-path=roles/stratum-apps/Cargo.toml sv1_connection::tests::test_sv1_connection --features sv1
- cargo test --manifest-path=protocols/stratum-translation/Cargo.toml
-
- - name: Property based testing
- run: |
- cargo test --manifest-path=protocols/Cargo.toml --features prop_test
-
- - name: Run ping-pong-encrypted example
- run: |
- cargo run --manifest-path=examples/ping-pong-encrypted/Cargo.toml
-
- - name: Run ping-pong example
- run: |
- cargo run --manifest-path=examples/ping-pong/Cargo.toml
+ - name: Property-based testing
+ run: cargo test --features prop_test
diff --git a/.gitignore b/.gitignore
index 70c24c10d0..44edb9a6d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,28 +1,5 @@
.idea
-*/**/target
-/protocols/guix-example/guix-example.h
-/protocols/Cargo.lock
-/stratum-core/Cargo.lock
-/roles/stratum-apps/Cargo.lock
-/protocols/v2/binary-sv2/derive_codec/Cargo.lock
-/benches/Cargo.lock
-/ignore
-/vendor/ed25519-dalek/target
-/utils/buffer/target
-/sv2.h
-/test/bitcoin_data/regtest
-lcov.info
-/target
+/integration-test-framework
+Cargo.lock
+target/
.vscode
-*.py
-**/conf/**
-cobertura.xml
-/roles/*/*-config.toml
-/examples/*/Cargo.lock
-/scripts/sv2.h
-/test/integration-tests/template-provider
-/test/integration-tests/minerd
-**/template-provider
-stratum-message-generator
-*.log
-**/minerd
\ No newline at end of file
diff --git a/roles/Cargo.toml b/Cargo.toml
similarity index 53%
rename from roles/Cargo.toml
rename to Cargo.toml
index d71deeb381..7274f43df2 100644
--- a/roles/Cargo.toml
+++ b/Cargo.toml
@@ -1,13 +1,12 @@
[workspace]
-resolver="2"
+resolver = "2"
members = [
- "pool",
- "test-utils/mining-device",
- "translator",
- "jd-client",
- "jd-server",
- "stratum-apps"
+ "stratum-core",
+]
+
+exclude = [
+ "integration-test-framework"
]
[profile.dev]
diff --git a/README.md b/README.md
index 0dc2c554f6..463ddfd118 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,11 @@
-
-Stratum V2 Reference Implementation (SRI)
+SV2 Libraries
-
-SRI is a reference implementation of the Stratum V2 protocol written in Rust π¦.
-
+Stratum V2 protocol libraries from the SRI project π¦
@@ -18,89 +15,65 @@ Stratum V2 Reference Implementation (SRI)
-## πΌ Table of Contents
-
-
- Introduction β’
- Getting Started β’
- Use Cases β’
- Roadmap β’
- Contribute β’
- Support β’
- Donate β’
- Supporters β’
- License
- MSRV
-
-
-## π Introduction
-
-Welcome to the official GitHub repository for the **SRI - Stratum V2 Reference Implementation**.
-
-[Stratum V2](https://stratumprotocol.org) is a next-generation bitcoin mining protocol designed to enhance the efficiency, security, flexibility and decentralization.
-SRI is fully open-source, community-developed, independent of any single entity, aiming to be fully compatible with [Stratum V2 Specification](https://github.com/stratum-mining/sv2-spec).
-
-## βοΈ Getting Started
-
-To get started with the Stratum V2 Reference Implementation (SRI), please follow the detailed setup instructions available on the official website:
+# Stratum Repository
-[Getting Started with Stratum V2](https://stratumprotocol.org/blog/getting-started/)
+This repository contains the low-level crates.
+If youβre looking to run Sv2 applications at the most recent changes, check out the [`sv2-apps` repository](https://github.com/stratum-mining/sv2-apps). Those crates are application-level, currently in **alpha** stage.
-This guide provides all the necessary information on prerequisites, installation, and configuration to help you begin using, testing or contributing to SRI.
+## Contents
-## π Use Cases
+- `sv1/` - Stratum V1 protocol implementation and utilities
+- `sv2/` - Stratum V2 protocol implementations
+ - `binary-sv2/` - Binary encoding/decoding for SV2 messages
+ - `buffer/` - Buffer management and pooling
+ - `channels-sv2/` - Channel management for SV2
+ - `codec-sv2/` - SV2 message codec with encryption support
+ - `framing-sv2/` - SV2 message framing utilities
+ - `handlers_sv2/` - SV2 message handler traits
+ - `noise-sv2/` - Noise protocol implementation for SV2
+ - `parsers-sv2/` - Message parsing utilities
+ - `subprotocols/` - SV2 subprotocol implementations
+- `stratum-core/` - Entrypoint for all the low-level crates in `sv2/` and `sv1/`implementations
+ - `stratum-translation` - Stratum V1 β Stratum V2 translation utilities
-The library is modular to address different use-cases and desired functionality. Examples include:
+## Local Integration Testing
-### π· Miners
+To run integration tests locally:
-- SV1 Miners can use the translator proxy (`roles/translator`) to connect with a SV2-compatible pool.
-- SV1 mining farms mining to a SV2-compatible pool gain some of the security and efficiency improvements SV2 offers over Stratum V1 (SV1). The SV1<->SV2 translator proxy does not support _all_ the features of SV2, but works as a temporary measure before upgrading completely to SV2-compatible firmware. (The SV1<->SV2 translation proxy implementation is a work in progress.)
+```bash
+./scripts/run-integration-tests.sh
+```
-### π οΈ Pools
-
-- Pools supporting SV2 can deploy the open source binary crate (`roles/pool`) to offer their clients (miners participating in said pool) an SV2-compatible pool.
-- The Rust helper library provides a suite of tools for mining pools to build custom SV2 compatible pool implementations.
+This will:
+1. Clone/update the integration test framework
+2. Update dependencies to use your local changes
+3. Run the full integration test suite
+4. Restore the original configuration
## π£ Roadmap
-Our roadmap is publicly available, outlining current and future plans. Decisions on the roadmap are made through a consensus-driven approach, through participation on dev meetings, Discord or GitHub.
-
-[View the SRI Roadmap](https://github.com/orgs/stratum-mining/projects/5)
-
-### π
Project Maturity
-
-Low-level crates (`protocols` directory) are considered **beta** software. Rust API Docs is a [work-in-progress](https://github.com/stratum-mining/stratum/issues/845), and the community should still expect small breaking API changes and patches.
-
-Application-level crates (`roles` directory) are considered **alpha** software, and bugs are expected. They should be used as a guide on how to consume the low-level crates as dependencies.
+Our roadmap is publicly available as part of the broader SRI project, outlining current and future plans. Decisions are made through a consensus-driven approach via dev meetings, Discord, and GitHub.
-### π― Goals
-
-The goals of this project are to provide:
-
-1. A robust set of Stratum V2 (SV2) primitives as Rust library crates which anyone can use
- to expand the protocol or implement a role. For example:
- - Pools supporting SV2
- - Mining-device/hashrate producers integrating SV2 into their firmware
- - Bitcoin nodes implementing Template Provider to build the `blocktemplate`
-2. A set of helpers built on top of the above primitives and the external Bitcoin-related Rust crates for anyone to implement the SV2 roles.
-3. An open-source implementation of a SV2 proxy for miners.
-4. An open-source implementation of a SV2 pool for mining pool operators.
+[View the SRI Roadmap](https://github.com/orgs/stratum-mining/projects/15)
## π» Contribute
-If you are a developer looking to help, but you're not sure where to begin, check the [good first issue label](https://github.com/stratum-mining/stratum/labels/good%20first%20issue), which contains small pieces of work that have been specifically flagged as being friendly to new contributors.
+We welcome contributions to improve these SV2 libraries! Here's how you can help:
-Contributors looking to do something a bit more challenging, before opening a pull request, please join [our community chat](https://discord.gg/fsEW23wFYs) or [start a GitHub issue](https://github.com/stratum-mining/stratum/issues) to get early feedback, discuss the best ways to tackle the problem, and ensure there is no work duplication and consensus.
+1. **Start small**: Check the [good first issue label](https://github.com/stratum-mining/stratum/labels/good%20first%20issue) in the main SRI repository
+2. **Join the community**: Connect with us on [Discord](https://discord.gg/fsEW23wFYs) before starting larger contributions
+3. **Open issues**: [Create GitHub issues](https://github.com/stratum-mining/stratum/issues) for bugs, feature requests, or questions
+4. **Follow standards**: Ensure code follows Rust best practices and includes appropriate tests
## π€ Support
-Join our Discord community to get help, share your ideas, or discuss anything related to Stratum V2 and its reference implementation.
-
-Whether you're looking for technical support, want to contribute, or are just interested in learning more about the project, our community is the place to be.
+Join our Discord community for technical support, discussions, and collaboration:
[Join the Stratum V2 Discord Community](https://discord.gg/fsEW23wFYs)
+For detailed documentation and guides, visit:
+[Stratum V2 Documentation](https://stratumprotocol.org)
+
## π Donate
### π€ Individual Donations
@@ -111,8 +84,6 @@ If you wish to support the development and maintenance of the Stratum V2 Referen
### π’ Corporate Donations
For corporate entities interested in providing more substantial support, such as grants to SRI contributors, please get in touch with us directly. Your support can make a significant difference in accelerating development, research, and innovation.
-Email us at: stratumv2@gmail.com
-
## π Supporters
SRI contributors are independently, financially supported by following entities:
@@ -134,4 +105,4 @@ Minimum Supported Rust Version: 1.75.0
> Website [stratumprotocol.org](https://www.stratumprotocol.org) ·
> Discord [SV2 Discord](https://discord.gg/fsEW23wFYs) ·
-> Twitter [@Stratumv2](https://twitter.com/StratumV2)
+> Twitter [@Stratumv2](https://twitter.com/StratumV2) ·
\ No newline at end of file
diff --git a/examples/ping-pong-encrypted/Cargo.toml b/examples/ping-pong-encrypted/Cargo.toml
deleted file mode 100644
index a59daf9441..0000000000
--- a/examples/ping-pong-encrypted/Cargo.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[package]
-name = "ping-pong-encrypted"
-version = "0.1.0"
-edition = "2021"
-authors = [ "SRI Community" ]
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-stratum-apps = { path = "../../roles/stratum-apps" }
-rand = "0.8"
-tokio = { version = "1.44.1", features = [ "full" ] }
-async-channel = "1.5.1"
diff --git a/examples/ping-pong-encrypted/README.md b/examples/ping-pong-encrypted/README.md
deleted file mode 100644
index ab4da5813e..0000000000
--- a/examples/ping-pong-encrypted/README.md
+++ /dev/null
@@ -1,20 +0,0 @@
-`ping-pong-encrypted` is an example of how to encode and decode SV2 binary frames (without any encryption layer) while leveraging the following crates:
-- [`binary_sv2`](http://docs.rs/binary_sv2)
-- [`codec_sv2`](http://docs.rs/codec_sv2)
-- [`framing_sv2`](http://docs.rs/framing_sv2) (which is actually just re-exported by `codec_sv2`)
-- [`noise_sv2`](http://docs.rs/noise_sv2)
-
-We establish a simple `Ping`-`Pong` protocol with a server and a client communicating over a TCP socket.
-
-The server expects to receive a `Ping` message encoded as a SV2 binary frame.
-The `Ping` message contains a `nonce`, which is a `u8` generated randomly by the client.
-
-The client expects to get a `Pong` message in response, also encoded as a SV2 binary frame, with the same `nonce`.
-
-The messages are assigned arbitrary values for binary encoding:
-```rust
-pub const PING_MSG_TYPE: u8 = 0xfe;
-pub const PONG_MSG_TYPE: u8 = 0xff;
-```
-
-All communication is encrypted with [SV2 Noise Protocol](https://stratumprotocol.org/specification/04-Protocol-Security/).
\ No newline at end of file
diff --git a/examples/ping-pong-encrypted/src/client.rs b/examples/ping-pong-encrypted/src/client.rs
deleted file mode 100644
index 1fd3985513..0000000000
--- a/examples/ping-pong-encrypted/src/client.rs
+++ /dev/null
@@ -1,82 +0,0 @@
-use crate::messages::{Message, Ping, Pong, PING_MSG_TYPE, PONG_MSG_TYPE};
-use stratum_apps::{
- key_utils::Secp256k1PublicKey,
- network_helpers::noise_connection::Connection,
- stratum_core::{
- binary_sv2,
- codec_sv2::{HandshakeRole, StandardSv2Frame},
- noise_sv2::Initiator,
- },
-};
-use tokio::net::TcpStream;
-
-use crate::error::Error;
-
-pub async fn start_client(address: &str, k_pub: String) -> Result<(), Error> {
- let stream = TcpStream::connect(address).await?;
-
- println!("CLIENT: Connected to server on {}", address);
-
- // parse server pubkey
- let k_pub: Secp256k1PublicKey = k_pub.try_into()?;
-
- // noise handshake initiator
- let initiator = Initiator::from_raw_k(k_pub.into_bytes())?;
-
- // channels for encrypted connection
- let (receiver, sender) = Connection::new(stream, HandshakeRole::Initiator(initiator)).await?;
-
- // create Ping message
- let ping = Ping::new()?;
- let ping_nonce = ping.get_nonce();
- let message = Message::Ping(ping);
-
- // create Ping frame
- let ping_frame =
- StandardSv2Frame::::from_message(message.clone(), PING_MSG_TYPE, 0, false)
- .ok_or(Error::FrameFromMessage)?;
-
- // send Ping frame (sender takes care of encryption)
- println!(
- "CLIENT: Sending encrypted Ping to server with nonce: {}",
- ping_nonce
- );
- sender
- .send(ping_frame.into())
- .await
- .map_err(|_| Error::Sender)?;
-
- // ok, we have successfully sent the ping message
- // now it's time to receive and verify the pong response
- // receiver already took care of decryption
- let mut frame: StandardSv2Frame = match receiver.recv().await {
- Ok(f) => f.try_into()?,
- Err(_) => return Err(Error::Receiver),
- };
-
- let frame_header = frame.get_header().ok_or(Error::FrameHeader)?;
-
- // check message type on header
- if frame_header.msg_type() != PONG_MSG_TYPE {
- return Err(Error::FrameHeader);
- }
-
- // decode frame payload
- let decoded_payload: Pong = match binary_sv2::from_bytes(frame.payload()) {
- Ok(pong) => pong,
- Err(e) => return Err(Error::BinarySv2(e)),
- };
-
- // check if nonce is the same as ping
- let pong_nonce = decoded_payload.get_nonce();
- if ping_nonce == pong_nonce {
- println!(
- "CLIENT: Received encrypted Pong with identical nonce as Ping: {}",
- pong_nonce
- );
- } else {
- return Err(Error::Nonce);
- }
-
- Ok(())
-}
diff --git a/examples/ping-pong-encrypted/src/error.rs b/examples/ping-pong-encrypted/src/error.rs
deleted file mode 100644
index a9b5eb6982..0000000000
--- a/examples/ping-pong-encrypted/src/error.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-use stratum_apps::{
- key_utils, network_helpers,
- stratum_core::{binary_sv2, codec_sv2, framing_sv2, noise_sv2},
-};
-
-#[derive(std::fmt::Debug)]
-pub enum Error {
- Io(std::io::Error),
- CodecSv2(codec_sv2::Error),
- FramingSv2(framing_sv2::Error),
- BinarySv2(binary_sv2::Error),
- NoiseSv2(noise_sv2::Error),
- NetworkHelpersSv2(network_helpers::Error),
- KeyUtils(key_utils::Error),
- Receiver,
- Sender,
- FrameHeader,
- FrameFromMessage,
- Nonce,
- WrongMessage,
- Tcp(std::io::Error),
-}
-
-impl From for Error {
- fn from(e: std::io::Error) -> Error {
- Error::Io(e)
- }
-}
-
-impl From for Error {
- fn from(e: codec_sv2::Error) -> Error {
- Error::CodecSv2(e)
- }
-}
-
-impl From for Error {
- fn from(e: network_helpers::Error) -> Error {
- Error::NetworkHelpersSv2(e)
- }
-}
-
-impl From for Error {
- fn from(e: binary_sv2::Error) -> Error {
- Error::BinarySv2(e)
- }
-}
-
-impl From for Error {
- fn from(e: noise_sv2::Error) -> Error {
- Error::NoiseSv2(e)
- }
-}
-
-impl From for Error {
- fn from(e: key_utils::Error) -> Error {
- Error::KeyUtils(e)
- }
-}
-
-impl From for Error {
- fn from(e: framing_sv2::Error) -> Error {
- Error::FramingSv2(e)
- }
-}
diff --git a/examples/ping-pong-encrypted/src/main.rs b/examples/ping-pong-encrypted/src/main.rs
deleted file mode 100644
index 1afa72a316..0000000000
--- a/examples/ping-pong-encrypted/src/main.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-mod client;
-mod error;
-mod messages;
-mod server;
-
-const ADDR: &str = "127.0.0.1:3333";
-const SERVER_PUBLIC_K: &str = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72";
-const SERVER_PRIVATE_K: &str = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n";
-const SERVER_CERT_VALIDITY: std::time::Duration = std::time::Duration::from_secs(3600);
-
-#[tokio::main]
-async fn main() {
- // start the server in a separate thread
- tokio::spawn(async {
- server::start_server(
- ADDR,
- SERVER_PUBLIC_K.to_string(),
- SERVER_PRIVATE_K.to_string(),
- SERVER_CERT_VALIDITY,
- )
- .await
- .expect("Server failed");
- });
-
- // give the server a moment to start up
- std::thread::sleep(std::time::Duration::from_secs(1));
-
- // start the client
- // note: it only knows the server's pubkey!
- client::start_client(ADDR, SERVER_PUBLIC_K.to_string())
- .await
- .expect("Client failed");
-}
diff --git a/examples/ping-pong-encrypted/src/messages.rs b/examples/ping-pong-encrypted/src/messages.rs
deleted file mode 100644
index 53dd5c1d3f..0000000000
--- a/examples/ping-pong-encrypted/src/messages.rs
+++ /dev/null
@@ -1,83 +0,0 @@
-use crate::error::Error;
-use stratum_apps::stratum_core::binary_sv2::{
- self as binary_sv2,
- decodable::{DecodableField, FieldMarker},
- Deserialize, Serialize,
-};
-
-use rand::Rng;
-
-pub const PING_MSG_TYPE: u8 = 0xfe;
-pub const PONG_MSG_TYPE: u8 = 0xff;
-
-// we derive binary_sv2::{Serialize, Deserialize}
-// to allow for binary encoding
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct Ping {
- nonce: u8,
-}
-
-impl Ping {
- pub fn new() -> Result {
- let mut rng = rand::thread_rng();
- let random: u8 = rng.gen();
- Ok(Self { nonce: random })
- }
-
- pub fn get_nonce(&self) -> u8 {
- self.nonce
- }
-}
-
-// we derive binary_sv2::{Serialize, Deserialize}
-// to allow for binary encoding
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct Pong {
- nonce: u8,
-}
-
-impl Pong {
- pub fn new(nonce: u8) -> Result {
- Ok(Self { nonce })
- }
-
- pub fn get_nonce(&self) -> u8 {
- self.nonce
- }
-}
-
-// unifies message types for noise_connection_tokio::Connection
-#[derive(Clone)]
-pub enum Message {
- Ping(Ping),
- Pong(Pong),
-}
-
-impl binary_sv2::GetSize for Message {
- fn get_size(&self) -> usize {
- match self {
- Self::Ping(ping) => ping.get_size(),
- Self::Pong(pong) => pong.get_size(),
- }
- }
-}
-
-impl From for binary_sv2::encodable::EncodableField<'_> {
- fn from(m: Message) -> Self {
- match m {
- Message::Ping(p) => p.into(),
- Message::Pong(p) => p.into(),
- }
- }
-}
-
-impl Deserialize<'_> for Message {
- fn get_structure(_v: &[u8]) -> std::result::Result, binary_sv2::Error> {
- unimplemented!()
- }
- fn from_decoded_fields(
- _v: Vec,
- ) -> std::result::Result {
- unimplemented!()
- }
-}
diff --git a/examples/ping-pong-encrypted/src/server.rs b/examples/ping-pong-encrypted/src/server.rs
deleted file mode 100644
index 3dc8a1913e..0000000000
--- a/examples/ping-pong-encrypted/src/server.rs
+++ /dev/null
@@ -1,105 +0,0 @@
-use crate::{
- error::Error,
- messages::{Message, Ping, Pong, PING_MSG_TYPE, PONG_MSG_TYPE},
-};
-use stratum_apps::{
- key_utils::{Secp256k1PublicKey, Secp256k1SecretKey},
- network_helpers::noise_connection::Connection,
- stratum_core::{
- binary_sv2,
- codec_sv2::{HandshakeRole, StandardEitherFrame, StandardSv2Frame},
- noise_sv2::Responder,
- },
-};
-
-use async_channel::{Receiver, Sender};
-use tokio::net::TcpListener;
-
-pub async fn start_server(
- address: &str,
- k_pub: String,
- k_priv: String,
- cert_validity: std::time::Duration,
-) -> Result<(), Error> {
- let listener = TcpListener::bind(address).await?;
-
- // parse keys
- let k_pub: Secp256k1PublicKey = k_pub.to_string().try_into()?;
- let k_priv: Secp256k1SecretKey = k_priv.to_string().try_into()?;
-
- println!("SERVER: Listening on {}", address);
-
- loop {
- let (stream, _) = listener.accept().await?;
- tokio::spawn(async move {
- // noise handshake responder
- let responder = Responder::from_authority_kp(
- &k_pub.into_bytes(),
- &k_priv.into_bytes(),
- cert_validity,
- )?;
-
- // channels for encrypted connection
- let (receiver, sender) =
- Connection::new(stream, HandshakeRole::Responder(responder)).await?;
-
- // handle encrypted connection
- handle_connection(receiver, sender).await?;
- Ok::<(), Error>(())
- });
- }
-}
-
-async fn handle_connection(
- receiver: Receiver>,
- sender: Sender>,
-) -> Result<(), Error> {
- // first, we need to read the ping frame
- // receiver already took care of decryption
- let mut frame: StandardSv2Frame = match receiver.recv().await {
- Ok(f) => f.try_into()?,
- Err(_) => return Err(Error::Receiver),
- };
-
- let frame_header = frame.get_header().ok_or(Error::FrameHeader)?;
-
- // check message type on header
- if frame_header.msg_type() != PING_MSG_TYPE {
- return Err(Error::WrongMessage);
- }
-
- // decode frame payload
- let decoded_payload: Ping = match binary_sv2::from_bytes(frame.payload()) {
- Ok(ping) => ping,
- Err(e) => return Err(Error::BinarySv2(e)),
- };
-
- // ok, we have successfully received the ping message
- // now it's time to send the pong response
-
- // we need the ping nonce to create our pong response
- let ping_nonce = decoded_payload.get_nonce();
-
- println!("SERVER: Received encrypted Ping with nonce: {}", ping_nonce);
-
- // create Pong message
- let pong = Pong::new(ping_nonce)?;
- let message = Message::Pong(pong.clone());
-
- // create Pong frame
- let pong_frame =
- StandardSv2Frame::::from_message(message.clone(), PONG_MSG_TYPE, 0, false)
- .ok_or(Error::FrameFromMessage)?;
-
- // respond Pong (sender takes care of encryption)
- println!(
- "SERVER: Sending encrypted Pong to client with nonce: {}",
- pong.get_nonce()
- );
- sender
- .send(pong_frame.into())
- .await
- .map_err(|_| Error::Sender)?;
-
- Ok(())
-}
diff --git a/examples/ping-pong/Cargo.toml b/examples/ping-pong/Cargo.toml
deleted file mode 100644
index fef89b57c7..0000000000
--- a/examples/ping-pong/Cargo.toml
+++ /dev/null
@@ -1,11 +0,0 @@
-[package]
-name = "ping-pong"
-version = "0.1.0"
-edition = "2021"
-authors = [ "SRI Community" ]
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-stratum-apps = { path = "../../roles/stratum-apps" }
-rand = "0.8"
diff --git a/examples/ping-pong/README.md b/examples/ping-pong/README.md
deleted file mode 100644
index e10b42e069..0000000000
--- a/examples/ping-pong/README.md
+++ /dev/null
@@ -1,17 +0,0 @@
-`ping-pong` is an example of how to encode and decode SV2 binary frames (without any encryption layer) while leveraging the following crates:
-- [`binary_sv2`](http://docs.rs/binary_sv2)
-- [`codec_sv2`](http://docs.rs/codec_sv2)
-- [`framing_sv2`](http://docs.rs/framing_sv2) (which is actually just re-exported by `codec_sv2`)
-
-We establish a simple `Ping`-`Pong` protocol with a server and a client communicating over a TCP socket.
-
-The server expects to receive a `Ping` message encoded as a SV2 binary frame.
-The `Ping` message contains a `nonce`, which is a `u8` generated randomly by the client.
-
-The client expects to get a `Pong` message in response, also encoded as a SV2 binary frame, with the same `nonce`.
-
-The messages are assigned arbitrary values for binary encoding:
-```rust
-pub const PING_MSG_TYPE: u8 = 0xfe;
-pub const PONG_MSG_TYPE: u8 = 0xff;
-```
\ No newline at end of file
diff --git a/examples/ping-pong/src/client.rs b/examples/ping-pong/src/client.rs
deleted file mode 100644
index 557091cfb4..0000000000
--- a/examples/ping-pong/src/client.rs
+++ /dev/null
@@ -1,86 +0,0 @@
-use crate::messages::{Ping, Pong, PING_MSG_TYPE, PONG_MSG_TYPE};
-use std::{
- io::{Read, Write},
- net::TcpStream,
-};
-use stratum_apps::stratum_core::{
- binary_sv2,
- codec_sv2::{self, StandardDecoder, StandardSv2Frame},
-};
-
-use crate::error::Error;
-
-pub fn start_client(address: &str) -> Result<(), Error> {
- let mut stream = TcpStream::connect(address)?;
-
- println!("CLIENT: Connected to server on {}", address);
-
- // create Ping message
- let ping_message = Ping::new()?;
- let ping_nonce = ping_message.get_nonce();
-
- // create Ping frame
- let ping_frame =
- StandardSv2Frame::::from_message(ping_message.clone(), PING_MSG_TYPE, 0, false)
- .ok_or(Error::FrameFromMessage)?;
-
- // encode Ping frame
- let mut encoder = codec_sv2::Encoder::::new();
- let ping_encoded = encoder.encode(ping_frame)?;
-
- println!("CLIENT: Sending Ping to server with nonce: {}", ping_nonce);
- stream.write_all(ping_encoded)?;
-
- // ok, we have successfully sent the ping message
- // now it's time to receive and verify the pong response
-
- // initialize decoder
- let mut decoder = StandardDecoder::::new();
-
- // right now, the decoder buffer can only read a frame header
- // because decoder.missing_b is initialized with a header size
- let decoder_buf = decoder.writable();
-
- // read frame header into decoder_buf
- stream.read_exact(decoder_buf)?;
-
- // this returns an error (MissingBytes), because it only read the header, and there's no payload
- // in memory yet therefore, we safely ignore the error
- // the important thing here is that we loaded decoder.missing_b with the expected frame payload
- // size
- let _ = decoder.next_frame();
-
- // now, the decoder buffer has the expected size of the frame payload
- let decoder_buf = decoder.writable();
-
- // read the payload into the decoder_buf
- stream.read_exact(decoder_buf)?;
-
- // finally read the frame
- let mut frame = decoder.next_frame()?;
- let frame_header = frame.get_header().ok_or(Error::FrameHeader)?;
-
- // check message type on header
- if frame_header.msg_type() != PONG_MSG_TYPE {
- return Err(Error::FrameHeader);
- }
-
- // decode frame payload
- let decoded_payload: Pong = match binary_sv2::from_bytes(frame.payload()) {
- Ok(pong) => pong,
- Err(e) => return Err(Error::BinarySv2(e)),
- };
-
- // check if nonce is the same as ping
- let pong_nonce = decoded_payload.get_nonce();
- if ping_nonce == pong_nonce {
- println!(
- "CLIENT: Received Pong with identical nonce as Ping: {}",
- pong_nonce
- );
- } else {
- return Err(Error::Nonce);
- }
-
- Ok(())
-}
diff --git a/examples/ping-pong/src/error.rs b/examples/ping-pong/src/error.rs
deleted file mode 100644
index 4794c903db..0000000000
--- a/examples/ping-pong/src/error.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use stratum_apps::stratum_core::{binary_sv2, codec_sv2};
-
-#[derive(std::fmt::Debug)]
-pub enum Error {
- Io(std::io::Error),
- Codec(codec_sv2::Error),
- BinarySv2(binary_sv2::Error),
- FrameHeader,
- FrameFromMessage,
- Nonce,
- WrongMessage,
- Tcp(std::io::Error),
-}
-
-impl From for Error {
- fn from(e: std::io::Error) -> Error {
- Error::Io(e)
- }
-}
-
-impl From for Error {
- fn from(e: codec_sv2::Error) -> Error {
- Error::Codec(e)
- }
-}
-
-impl From for Error {
- fn from(e: binary_sv2::Error) -> Error {
- Error::BinarySv2(e)
- }
-}
diff --git a/examples/ping-pong/src/main.rs b/examples/ping-pong/src/main.rs
deleted file mode 100644
index d1edd9c776..0000000000
--- a/examples/ping-pong/src/main.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-mod client;
-mod error;
-mod messages;
-mod server;
-
-const ADDR: &str = "127.0.0.1:3333";
-
-fn main() {
- // Start the server in a separate thread
- std::thread::spawn(|| {
- server::start_server(ADDR).expect("Server failed");
- });
-
- // Give the server a moment to start up
- std::thread::sleep(std::time::Duration::from_secs(1));
-
- // Start the client
- client::start_client(ADDR).expect("Client failed");
-}
diff --git a/examples/ping-pong/src/messages.rs b/examples/ping-pong/src/messages.rs
deleted file mode 100644
index f71c8e3664..0000000000
--- a/examples/ping-pong/src/messages.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-use crate::error::Error;
-use stratum_apps::stratum_core::binary_sv2::{self, Deserialize, Serialize};
-
-use rand::Rng;
-
-pub const PING_MSG_TYPE: u8 = 0xfe;
-pub const PONG_MSG_TYPE: u8 = 0xff;
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct Ping {
- nonce: u8,
-}
-
-impl Ping {
- pub fn new() -> Result {
- let mut rng = rand::thread_rng();
- let random: u8 = rng.gen();
- Ok(Self { nonce: random })
- }
-
- pub fn get_nonce(&self) -> u8 {
- self.nonce
- }
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct Pong {
- nonce: u8,
-}
-
-impl Pong {
- pub fn new(nonce: u8) -> Result {
- Ok(Self { nonce })
- }
-
- pub fn get_nonce(&self) -> u8 {
- self.nonce
- }
-}
diff --git a/examples/ping-pong/src/server.rs b/examples/ping-pong/src/server.rs
deleted file mode 100644
index e4b0c11472..0000000000
--- a/examples/ping-pong/src/server.rs
+++ /dev/null
@@ -1,104 +0,0 @@
-use crate::{
- error::Error,
- messages::{Ping, Pong, PING_MSG_TYPE, PONG_MSG_TYPE},
-};
-use std::{
- io::{Read, Write},
- net::{TcpListener, TcpStream},
- thread,
-};
-use stratum_apps::stratum_core::{
- binary_sv2,
- codec_sv2::{self, StandardDecoder, StandardSv2Frame},
-};
-
-use stratum_apps::stratum_core::framing_sv2::header::Header as StandardSv2Header;
-
-pub fn start_server(address: &str) -> Result<(), Error> {
- let listener = TcpListener::bind(address)?;
-
- println!("SERVER: Listening on {}", address);
-
- for stream in listener.incoming() {
- match stream {
- Ok(stream) => {
- thread::spawn(|| {
- handle_connection(stream)?;
- Ok::<(), Error>(())
- });
- }
- Err(e) => return Err(Error::Tcp(e)),
- }
- }
-
- Ok(())
-}
-
-fn handle_connection(mut stream: TcpStream) -> Result<(), Error> {
- // first, we need to read the ping message
-
- // initialize decoder
- let mut decoder = StandardDecoder::::new();
-
- // right now, the decoder buffer can only read a frame header
- // because decoder.missing_b is initialized with a header size
- let decoder_buf = decoder.writable();
-
- // read frame header into decoder_buf
- stream.read_exact(decoder_buf)?;
-
- // this returns an error (MissingBytes), because it only read the header, and there's no payload
- // in memory yet therefore, we safely ignore the error
- // the important thing here is that we loaded decoder.missing_b with the expected frame payload
- // size
- let _ = decoder.next_frame();
-
- // now, the decoder buffer has the expected size of the frame payload
- let decoder_buf = decoder.writable();
-
- // read from stream into decoder_buf again, loading the payload into memory
- stream.read_exact(decoder_buf)?;
-
- // parse into a Sv2Frame
- let mut frame: StandardSv2Frame = decoder.next_frame()?;
- let frame_header: StandardSv2Header = frame.get_header().ok_or(Error::FrameHeader)?;
-
- // check message type on header
- if frame_header.msg_type() != PING_MSG_TYPE {
- return Err(Error::WrongMessage);
- }
-
- // decode frame payload
- let decoded_payload: Ping = match binary_sv2::from_bytes(frame.payload()) {
- Ok(ping) => ping,
- Err(e) => return Err(Error::BinarySv2(e)),
- };
-
- // ok, we have successfully received the ping message
- // now it's time to send the pong response
-
- // we need the ping nonce to create our pong response
- let ping_nonce = decoded_payload.get_nonce();
-
- println!("SERVER: Received Ping message with nonce: {}", ping_nonce);
-
- // create Pong message
- let pong_message = Pong::new(ping_nonce)?;
-
- // create Pong frame
- let pong_frame =
- StandardSv2Frame::::from_message(pong_message.clone(), PONG_MSG_TYPE, 0, false)
- .ok_or(Error::FrameFromMessage)?;
-
- // encode Pong frame
- let mut encoder = codec_sv2::Encoder::::new();
- let pong_encoded = encoder.encode(pong_frame)?;
-
- println!(
- "SERVER: Sending Pong to client with nonce: {}",
- pong_message.get_nonce()
- );
- stream.write_all(pong_encoded)?;
-
- Ok(())
-}
diff --git a/protocols/fuzz-tests/Cargo.lock b/fuzz-tests/Cargo.lock
similarity index 56%
rename from protocols/fuzz-tests/Cargo.lock
rename to fuzz-tests/Cargo.lock
index 7c93c90277..2ed7a968a1 100644
--- a/protocols/fuzz-tests/Cargo.lock
+++ b/fuzz-tests/Cargo.lock
@@ -58,110 +58,27 @@ dependencies = [
"derive_arbitrary",
]
-[[package]]
-name = "arrayvec"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
-
-[[package]]
-name = "base58ck"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f"
-dependencies = [
- "bitcoin-internals 0.3.0",
- "bitcoin_hashes 0.14.0",
-]
-
-[[package]]
-name = "bech32"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d"
-
[[package]]
name = "binary_sv2"
-version = "4.0.0"
+version = "5.0.0"
dependencies = [
- "buffer_sv2",
"derive_codec_sv2",
]
-[[package]]
-name = "bitcoin"
-version = "0.32.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda569d741b895131a88ee5589a467e73e9c4718e958ac9308e4f7dc44b6945"
-dependencies = [
- "base58ck",
- "bech32",
- "bitcoin-internals 0.3.0",
- "bitcoin-io",
- "bitcoin-units",
- "bitcoin_hashes 0.14.0",
- "hex-conservative 0.2.1",
- "hex_lit",
- "secp256k1 0.29.1",
-]
-
[[package]]
name = "bitcoin-internals"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb"
-[[package]]
-name = "bitcoin-internals"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2"
-
-[[package]]
-name = "bitcoin-io"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf"
-
-[[package]]
-name = "bitcoin-units"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2"
-dependencies = [
- "bitcoin-internals 0.3.0",
-]
-
[[package]]
name = "bitcoin_hashes"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b"
dependencies = [
- "bitcoin-internals 0.2.0",
- "hex-conservative 0.1.2",
-]
-
-[[package]]
-name = "bitcoin_hashes"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16"
-dependencies = [
- "bitcoin-io",
- "hex-conservative 0.2.1",
-]
-
-[[package]]
-name = "bitvec"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
-dependencies = [
- "funty",
- "radium",
- "tap",
- "wyz",
+ "bitcoin-internals",
+ "hex-conservative",
]
[[package]]
@@ -172,18 +89,6 @@ dependencies = [
"generic-array",
]
-[[package]]
-name = "byte-slice-cast"
-version = "1.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d"
-
-[[package]]
-name = "byteorder"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
-
[[package]]
name = "cc"
version = "1.2.41"
@@ -226,20 +131,6 @@ dependencies = [
"zeroize",
]
-[[package]]
-name = "channels_sv2"
-version = "2.0.0"
-dependencies = [
- "binary_sv2",
- "bitcoin",
- "common_messages_sv2",
- "job_declaration_sv2",
- "mining_sv2",
- "primitive-types",
- "template_distribution_sv2",
- "tracing",
-]
-
[[package]]
name = "cipher"
version = "0.4.4"
@@ -265,31 +156,11 @@ dependencies = [
[[package]]
name = "common_messages_sv2"
-version = "6.0.1"
+version = "6.0.2"
dependencies = [
"binary_sv2",
]
-[[package]]
-name = "const_format"
-version = "0.2.35"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad"
-dependencies = [
- "const_format_proc_macros",
-]
-
-[[package]]
-name = "const_format_proc_macros"
-version = "0.2.34"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-xid",
-]
-
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -299,12 +170,6 @@ dependencies = [
"libc",
]
-[[package]]
-name = "crunchy"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
-
[[package]]
name = "crypto-common"
version = "0.1.6"
@@ -312,7 +177,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
- "rand_core",
"typenum",
]
@@ -340,12 +204,6 @@ dependencies = [
name = "derive_codec_sv2"
version = "1.1.1"
-[[package]]
-name = "equivalent"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
-
[[package]]
name = "errno"
version = "0.3.14"
@@ -362,33 +220,14 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
-[[package]]
-name = "fixed-hash"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534"
-dependencies = [
- "byteorder",
- "rand",
- "rustc-hex",
- "static_assertions",
-]
-
[[package]]
name = "framing_sv2"
-version = "5.0.1"
+version = "5.0.2"
dependencies = [
"binary_sv2",
- "buffer_sv2",
"noise_sv2",
]
-[[package]]
-name = "funty"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
-
[[package]]
name = "fuzz-tests"
version = "1.0.1"
@@ -402,7 +241,6 @@ dependencies = [
"libfuzzer-sys",
"parsers_sv2",
"rand",
- "roles_logic_sv2",
"threadpool",
]
@@ -449,97 +287,18 @@ dependencies = [
"polyval",
]
-[[package]]
-name = "handlers_sv2"
-version = "0.2.0"
-dependencies = [
- "binary_sv2",
- "common_messages_sv2",
- "job_declaration_sv2",
- "mining_sv2",
- "parsers_sv2",
- "template_distribution_sv2",
- "trait-variant",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
-
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
-[[package]]
-name = "hex"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
-
[[package]]
name = "hex-conservative"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20"
-[[package]]
-name = "hex-conservative"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd"
-dependencies = [
- "arrayvec",
-]
-
-[[package]]
-name = "hex-conservative"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4afe881d0527571892c4034822e59bb10c6c991cce6abe8199b6f5cf10766f55"
-dependencies = [
- "arrayvec",
-]
-
-[[package]]
-name = "hex_lit"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
-
-[[package]]
-name = "impl-codec"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14"
-dependencies = [
- "parity-scale-codec",
-]
-
-[[package]]
-name = "impl-trait-for-tuples"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "indexmap"
-version = "2.11.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
-dependencies = [
- "equivalent",
- "hashbrown",
-]
-
[[package]]
name = "inout"
version = "0.1.4"
@@ -551,7 +310,7 @@ dependencies = [
[[package]]
name = "job_declaration_sv2"
-version = "5.0.1"
+version = "5.0.2"
dependencies = [
"binary_sv2",
]
@@ -588,25 +347,13 @@ dependencies = [
"cc",
]
-[[package]]
-name = "memchr"
-version = "2.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
-
[[package]]
name = "mining_sv2"
-version = "5.0.1"
+version = "6.0.0"
dependencies = [
"binary_sv2",
]
-[[package]]
-name = "nohash-hasher"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
-
[[package]]
name = "noise_sv2"
version = "1.4.0"
@@ -616,7 +363,7 @@ dependencies = [
"generic-array",
"rand",
"rand_chacha",
- "secp256k1 0.28.2",
+ "secp256k1",
]
[[package]]
@@ -641,37 +388,9 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
-[[package]]
-name = "parity-scale-codec"
-version = "3.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa"
-dependencies = [
- "arrayvec",
- "bitvec",
- "byte-slice-cast",
- "const_format",
- "impl-trait-for-tuples",
- "parity-scale-codec-derive",
- "rustversion",
- "serde",
-]
-
-[[package]]
-name = "parity-scale-codec-derive"
-version = "3.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a"
-dependencies = [
- "proc-macro-crate",
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "parsers_sv2"
-version = "0.1.1"
+version = "0.1.2"
dependencies = [
"binary_sv2",
"common_messages_sv2",
@@ -719,26 +438,6 @@ dependencies = [
"zerocopy",
]
-[[package]]
-name = "primitive-types"
-version = "0.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5"
-dependencies = [
- "fixed-hash",
- "impl-codec",
- "uint",
-]
-
-[[package]]
-name = "proc-macro-crate"
-version = "3.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
-dependencies = [
- "toml_edit",
-]
-
[[package]]
name = "proc-macro2"
version = "1.0.101"
@@ -763,12 +462,6 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
-[[package]]
-name = "radium"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
-
[[package]]
name = "rand"
version = "0.8.5"
@@ -799,58 +492,15 @@ dependencies = [
"getrandom 0.2.16",
]
-[[package]]
-name = "roles_logic_sv2"
-version = "5.0.0"
-dependencies = [
- "binary_sv2",
- "bitcoin",
- "chacha20poly1305",
- "channels_sv2",
- "codec_sv2",
- "common_messages_sv2",
- "handlers_sv2",
- "hex-conservative 0.3.0",
- "job_declaration_sv2",
- "mining_sv2",
- "nohash-hasher",
- "parsers_sv2",
- "primitive-types",
- "template_distribution_sv2",
- "tracing",
-]
-
-[[package]]
-name = "rustc-hex"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
-
-[[package]]
-name = "rustversion"
-version = "1.0.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
-
[[package]]
name = "secp256k1"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10"
dependencies = [
- "bitcoin_hashes 0.13.0",
+ "bitcoin_hashes",
"rand",
- "secp256k1-sys 0.9.2",
-]
-
-[[package]]
-name = "secp256k1"
-version = "0.29.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
-dependencies = [
- "bitcoin_hashes 0.14.0",
- "secp256k1-sys 0.10.1",
+ "secp256k1-sys",
]
[[package]]
@@ -862,56 +512,12 @@ dependencies = [
"cc",
]
-[[package]]
-name = "secp256k1-sys"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9"
-dependencies = [
- "cc",
-]
-
-[[package]]
-name = "serde"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
-dependencies = [
- "serde_core",
-]
-
-[[package]]
-name = "serde_core"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
[[package]]
name = "subtle"
version = "2.6.1"
@@ -929,15 +535,9 @@ dependencies = [
"unicode-ident",
]
-[[package]]
-name = "tap"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
-
[[package]]
name = "template_distribution_sv2"
-version = "4.0.1"
+version = "4.0.2"
dependencies = [
"binary_sv2",
]
@@ -951,36 +551,6 @@ dependencies = [
"num_cpus",
]
-[[package]]
-name = "toml_datetime"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
-dependencies = [
- "serde_core",
-]
-
-[[package]]
-name = "toml_edit"
-version = "0.23.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d"
-dependencies = [
- "indexmap",
- "toml_datetime",
- "toml_parser",
- "winnow",
-]
-
-[[package]]
-name = "toml_parser"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
-dependencies = [
- "winnow",
-]
-
[[package]]
name = "tracing"
version = "0.1.41"
@@ -1012,47 +582,18 @@ dependencies = [
"once_cell",
]
-[[package]]
-name = "trait-variant"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
-[[package]]
-name = "uint"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e"
-dependencies = [
- "byteorder",
- "crunchy",
- "hex",
- "static_assertions",
-]
-
[[package]]
name = "unicode-ident"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
-[[package]]
-name = "unicode-xid"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
-
[[package]]
name = "universal-hash"
version = "0.5.1"
@@ -1099,30 +640,12 @@ dependencies = [
"windows-link",
]
-[[package]]
-name = "winnow"
-version = "0.7.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
-dependencies = [
- "memchr",
-]
-
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
-[[package]]
-name = "wyz"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
-dependencies = [
- "tap",
-]
-
[[package]]
name = "zerocopy"
version = "0.8.27"
diff --git a/protocols/fuzz-tests/Cargo.toml b/fuzz-tests/Cargo.toml
similarity index 77%
rename from protocols/fuzz-tests/Cargo.toml
rename to fuzz-tests/Cargo.toml
index 5d28609b50..8fc366159d 100644
--- a/protocols/fuzz-tests/Cargo.toml
+++ b/fuzz-tests/Cargo.toml
@@ -17,10 +17,10 @@ cargo-fuzz = true
libfuzzer-sys = { version = "0.4.0", features = ["arbitrary-derive"] }
arbitrary = { version = "1", features = ["derive"] }
rand = "0.8.3"
-binary_sv2 = { path = "../v2/binary-sv2"}
-parsers_sv2 = { path = "../v2/parsers-sv2" }
-framing_sv2 = { path = "../v2/framing-sv2" }
-codec_sv2 = { path = "../v2/codec-sv2", features = ["noise_sv2"]}
+binary_sv2 = { path = "../sv2/binary-sv2"}
+parsers_sv2 = { path = "../sv2/parsers-sv2" }
+framing_sv2 = { path = "../sv2/framing-sv2" }
+codec_sv2 = { path = "../sv2/codec-sv2", features = ["noise_sv2"]}
affinity = "0.1.1"
threadpool = "1.8.1"
lazy_static = "1.4.0"
diff --git a/protocols/fuzz-tests/src/main.rs b/fuzz-tests/src/main.rs
similarity index 100%
rename from protocols/fuzz-tests/src/main.rs
rename to fuzz-tests/src/main.rs
diff --git a/protocols/Cargo.toml b/protocols/Cargo.toml
deleted file mode 100644
index 8095ce1205..0000000000
--- a/protocols/Cargo.toml
+++ /dev/null
@@ -1,28 +0,0 @@
-[workspace]
-
-resolver="2"
-
-members = [
- "v1",
- "v2/binary-sv2/derive_codec",
- "v2/binary-sv2",
- "v2/noise-sv2",
- "v2/framing-sv2",
- "v2/codec-sv2",
- "v2/subprotocols/common-messages",
- "v2/subprotocols/template-distribution",
- "v2/subprotocols/mining",
- "v2/subprotocols/job-declaration",
- "v2/channels-sv2",
- "v2/parsers-sv2",
- "v2/handlers-sv2",
- "stratum-translation",
-]
-
-[profile.dev]
-# Required by super_safe_lock
-opt-level = 1
-
-[profile.test]
-# Required by super_safe_lock
-opt-level = 1
diff --git a/protocols/v2/binary-sv2/derive_codec/.gitignore b/protocols/v2/binary-sv2/derive_codec/.gitignore
deleted file mode 100644
index ea8c4bf7f3..0000000000
--- a/protocols/v2/binary-sv2/derive_codec/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/target
diff --git a/roles/Cargo.lock b/roles/Cargo.lock
deleted file mode 100644
index acdbd055fd..0000000000
--- a/roles/Cargo.lock
+++ /dev/null
@@ -1,3790 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "addr2line"
-version = "0.25.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
-dependencies = [
- "gimli",
-]
-
-[[package]]
-name = "adler2"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
-
-[[package]]
-name = "aead"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
-dependencies = [
- "crypto-common",
- "generic-array",
-]
-
-[[package]]
-name = "aes"
-version = "0.8.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
-dependencies = [
- "cfg-if",
- "cipher",
- "cpufeatures",
-]
-
-[[package]]
-name = "aes-gcm"
-version = "0.10.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
-dependencies = [
- "aead",
- "aes",
- "cipher",
- "ctr",
- "ghash",
- "subtle",
-]
-
-[[package]]
-name = "ahash"
-version = "0.7.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
-dependencies = [
- "getrandom",
- "once_cell",
- "version_check",
-]
-
-[[package]]
-name = "ahash"
-version = "0.8.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
-dependencies = [
- "cfg-if",
- "once_cell",
- "version_check",
- "zerocopy",
-]
-
-[[package]]
-name = "aho-corasick"
-version = "1.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "allocator-api2"
-version = "0.2.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
-
-[[package]]
-name = "anes"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
-
-[[package]]
-name = "anstream"
-version = "0.6.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
-dependencies = [
- "anstyle",
- "anstyle-parse",
- "anstyle-query",
- "anstyle-wincon",
- "colorchoice",
- "is_terminal_polyfill",
- "utf8parse",
-]
-
-[[package]]
-name = "anstyle"
-version = "1.0.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
-
-[[package]]
-name = "anstyle-parse"
-version = "0.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
-dependencies = [
- "utf8parse",
-]
-
-[[package]]
-name = "anstyle-query"
-version = "1.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
-dependencies = [
- "windows-sys 0.60.2",
-]
-
-[[package]]
-name = "anstyle-wincon"
-version = "3.0.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
-dependencies = [
- "anstyle",
- "once_cell_polyfill",
- "windows-sys 0.60.2",
-]
-
-[[package]]
-name = "arraydeque"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
-
-[[package]]
-name = "arrayvec"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
-
-[[package]]
-name = "async-channel"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
-dependencies = [
- "concurrent-queue",
- "event-listener 2.5.3",
- "futures-core",
-]
-
-[[package]]
-name = "async-channel"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
-dependencies = [
- "concurrent-queue",
- "event-listener-strategy",
- "futures-core",
- "pin-project-lite",
-]
-
-[[package]]
-name = "async-executor"
-version = "1.13.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8"
-dependencies = [
- "async-task",
- "concurrent-queue",
- "fastrand 2.3.0",
- "futures-lite 2.6.1",
- "pin-project-lite",
- "slab",
-]
-
-[[package]]
-name = "async-fs"
-version = "1.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
-dependencies = [
- "async-lock 2.8.0",
- "autocfg",
- "blocking",
- "futures-lite 1.13.0",
-]
-
-[[package]]
-name = "async-global-executor"
-version = "2.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
-dependencies = [
- "async-channel 2.5.0",
- "async-executor",
- "async-io 2.6.0",
- "async-lock 3.4.1",
- "blocking",
- "futures-lite 2.6.1",
- "once_cell",
-]
-
-[[package]]
-name = "async-io"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
-dependencies = [
- "async-lock 2.8.0",
- "autocfg",
- "cfg-if",
- "concurrent-queue",
- "futures-lite 1.13.0",
- "log",
- "parking",
- "polling 2.8.0",
- "rustix 0.37.28",
- "slab",
- "socket2 0.4.10",
- "waker-fn",
-]
-
-[[package]]
-name = "async-io"
-version = "2.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
-dependencies = [
- "autocfg",
- "cfg-if",
- "concurrent-queue",
- "futures-io",
- "futures-lite 2.6.1",
- "parking",
- "polling 3.11.0",
- "rustix 1.1.2",
- "slab",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "async-lock"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
-dependencies = [
- "event-listener 2.5.3",
-]
-
-[[package]]
-name = "async-lock"
-version = "3.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc"
-dependencies = [
- "event-listener 5.4.1",
- "event-listener-strategy",
- "pin-project-lite",
-]
-
-[[package]]
-name = "async-net"
-version = "1.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0434b1ed18ce1cf5769b8ac540e33f01fa9471058b5e89da9e06f3c882a8c12f"
-dependencies = [
- "async-io 1.13.0",
- "blocking",
- "futures-lite 1.13.0",
-]
-
-[[package]]
-name = "async-process"
-version = "1.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88"
-dependencies = [
- "async-io 1.13.0",
- "async-lock 2.8.0",
- "async-signal",
- "blocking",
- "cfg-if",
- "event-listener 3.1.0",
- "futures-lite 1.13.0",
- "rustix 0.38.44",
- "windows-sys 0.48.0",
-]
-
-[[package]]
-name = "async-recursion"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "async-signal"
-version = "0.2.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c"
-dependencies = [
- "async-io 2.6.0",
- "async-lock 3.4.1",
- "atomic-waker",
- "cfg-if",
- "futures-core",
- "futures-io",
- "rustix 1.1.2",
- "signal-hook-registry",
- "slab",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "async-std"
-version = "1.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b"
-dependencies = [
- "async-channel 1.9.0",
- "async-global-executor",
- "async-io 2.6.0",
- "async-lock 3.4.1",
- "crossbeam-utils",
- "futures-channel",
- "futures-core",
- "futures-io",
- "futures-lite 2.6.1",
- "gloo-timers",
- "kv-log-macro",
- "log",
- "memchr",
- "once_cell",
- "pin-project-lite",
- "pin-utils",
- "slab",
- "wasm-bindgen-futures",
-]
-
-[[package]]
-name = "async-task"
-version = "4.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
-
-[[package]]
-name = "async-trait"
-version = "0.1.89"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.106",
-]
-
-[[package]]
-name = "atomic-waker"
-version = "1.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
-
-[[package]]
-name = "autocfg"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
-
-[[package]]
-name = "backtrace"
-version = "0.3.76"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
-dependencies = [
- "addr2line",
- "cfg-if",
- "libc",
- "miniz_oxide",
- "object",
- "rustc-demangle",
- "windows-link 0.2.1",
-]
-
-[[package]]
-name = "base58ck"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f"
-dependencies = [
- "bitcoin-internals 0.3.0",
- "bitcoin_hashes 0.14.0",
-]
-
-[[package]]
-name = "base64"
-version = "0.21.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
-
-[[package]]
-name = "base64"
-version = "0.22.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
-
-[[package]]
-name = "bech32"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d"
-
-[[package]]
-name = "binary_codec_sv2"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ad24342e0abdcc463ad6ad4ac7b0ec606122c11eddf92de186a657df0114eb7"
-
-[[package]]
-name = "binary_codec_sv2"
-version = "3.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d16415a0a9ccee2f71820da352c1f2a7f16d9f8e3ae6fb5e97834c6d732e98cd"
-dependencies = [
- "buffer_sv2 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "binary_sv2"
-version = "4.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba8295945d51b707f3a49e17810dddef858549e2b52383c7f2c4dd036f6bc1e6"
-dependencies = [
- "binary_codec_sv2 3.0.0",
- "derive_codec_sv2 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "binary_sv2"
-version = "5.0.0"
-dependencies = [
- "buffer_sv2 2.0.0",
- "derive_codec_sv2 1.1.1",
-]
-
-[[package]]
-name = "bitcoin"
-version = "0.32.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda569d741b895131a88ee5589a467e73e9c4718e958ac9308e4f7dc44b6945"
-dependencies = [
- "base58ck",
- "bech32",
- "bitcoin-internals 0.3.0",
- "bitcoin-io",
- "bitcoin-units",
- "bitcoin_hashes 0.14.0",
- "hex-conservative 0.2.1",
- "hex_lit",
- "secp256k1 0.29.1",
-]
-
-[[package]]
-name = "bitcoin-internals"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb"
-
-[[package]]
-name = "bitcoin-internals"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2"
-
-[[package]]
-name = "bitcoin-io"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf"
-
-[[package]]
-name = "bitcoin-units"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2"
-dependencies = [
- "bitcoin-internals 0.3.0",
-]
-
-[[package]]
-name = "bitcoin_hashes"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b7a2e9773ee7ae7f2560f0426c938f57902dcb9e39321b0cbd608f47ed579a4"
-dependencies = [
- "byteorder",
-]
-
-[[package]]
-name = "bitcoin_hashes"
-version = "0.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b"
-dependencies = [
- "bitcoin-internals 0.2.0",
- "hex-conservative 0.1.2",
-]
-
-[[package]]
-name = "bitcoin_hashes"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16"
-dependencies = [
- "bitcoin-io",
- "hex-conservative 0.2.1",
-]
-
-[[package]]
-name = "bitflags"
-version = "1.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-
-[[package]]
-name = "bitflags"
-version = "2.9.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "bitvec"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
-dependencies = [
- "funty",
- "radium",
- "tap",
- "wyz",
-]
-
-[[package]]
-name = "block-buffer"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
-dependencies = [
- "generic-array",
-]
-
-[[package]]
-name = "block-buffer"
-version = "0.10.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
-dependencies = [
- "generic-array",
-]
-
-[[package]]
-name = "blocking"
-version = "1.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
-dependencies = [
- "async-channel 2.5.0",
- "async-task",
- "futures-io",
- "futures-lite 2.6.1",
- "piper",
-]
-
-[[package]]
-name = "bs58"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
-dependencies = [
- "sha2 0.9.9",
-]
-
-[[package]]
-name = "buffer_sv2"
-version = "2.0.0"
-dependencies = [
- "aes-gcm",
- "generic-array",
-]
-
-[[package]]
-name = "buffer_sv2"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19781425841d2e217eb7ded68089b693b47c8f756eb02231c92122dbf505bcf0"
-dependencies = [
- "aes-gcm",
-]
-
-[[package]]
-name = "bumpalo"
-version = "3.19.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
-
-[[package]]
-name = "byte-slice-cast"
-version = "1.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d"
-
-[[package]]
-name = "byteorder"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
-
-[[package]]
-name = "bytes"
-version = "1.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
-
-[[package]]
-name = "cast"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
-
-[[package]]
-name = "cc"
-version = "1.2.41"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
-dependencies = [
- "find-msvc-tools",
- "shlex",
-]
-
-[[package]]
-name = "cfg-if"
-version = "1.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
-
-[[package]]
-name = "chacha20"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
-dependencies = [
- "cfg-if",
- "cipher",
- "cpufeatures",
-]
-
-[[package]]
-name = "chacha20poly1305"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
-dependencies = [
- "aead",
- "chacha20",
- "cipher",
- "poly1305",
- "zeroize",
-]
-
-[[package]]
-name = "channels_sv2"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11ac02b93b5bd92a7dda2bc4b8c9d1f087e1fffc8b1018b532109135629051fc"
-dependencies = [
- "binary_sv2 4.0.0",
- "bitcoin",
- "common_messages_sv2 6.0.1",
- "job_declaration_sv2 5.0.1",
- "mining_sv2 5.0.1",
- "primitive-types",
- "template_distribution_sv2 4.0.1",
- "tracing",
-]
-
-[[package]]
-name = "channels_sv2"
-version = "2.0.0"
-dependencies = [
- "binary_sv2 5.0.0",
- "bitcoin",
- "common_messages_sv2 6.0.2",
- "job_declaration_sv2 5.0.2",
- "mining_sv2 6.0.0",
- "primitive-types",
- "template_distribution_sv2 4.0.2",
- "tracing",
-]
-
-[[package]]
-name = "ciborium"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
-dependencies = [
- "ciborium-io",
- "ciborium-ll",
- "serde",
-]
-
-[[package]]
-name = "ciborium-io"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
-
-[[package]]
-name = "ciborium-ll"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
-dependencies = [
- "ciborium-io",
- "half",
-]
-
-[[package]]
-name = "cipher"
-version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
-dependencies = [
- "crypto-common",
- "inout",
- "zeroize",
-]
-
-[[package]]
-name = "clap"
-version = "4.5.49"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f"
-dependencies = [
- "clap_builder",
- "clap_derive",
-]
-
-[[package]]
-name = "clap_builder"
-version = "4.5.49"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730"
-dependencies = [
- "anstream",
- "anstyle",
- "clap_lex",
- "strsim",
-]
-
-[[package]]
-name = "clap_derive"
-version = "4.5.49"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
-dependencies = [
- "heck",
- "proc-macro2",
- "quote",
- "syn 2.0.106",
-]
-
-[[package]]
-name = "clap_lex"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
-
-[[package]]
-name = "codec_sv2"
-version = "3.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01e6d43e79e66d0f98038922157db8b6101594921be87ac2cca3754d669f2a05"
-dependencies = [
- "binary_sv2 4.0.0",
- "buffer_sv2 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "framing_sv2 5.0.1",
- "noise_sv2 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand",
- "tracing",
-]
-
-[[package]]
-name = "codec_sv2"
-version = "4.0.0"
-dependencies = [
- "binary_sv2 5.0.0",
- "buffer_sv2 2.0.0",
- "framing_sv2 5.0.2",
- "noise_sv2 1.4.0",
- "rand",
- "tracing",
-]
-
-[[package]]
-name = "colorchoice"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
-
-[[package]]
-name = "common_messages_sv2"
-version = "6.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e6ec6ab527aeebf8ead273d6ab712ff181c050ee5e1082f3f6a2c65c0a10bf6"
-dependencies = [
- "binary_sv2 4.0.0",
-]
-
-[[package]]
-name = "common_messages_sv2"
-version = "6.0.2"
-dependencies = [
- "binary_sv2 5.0.0",
-]
-
-[[package]]
-name = "concurrent-queue"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
-dependencies = [
- "crossbeam-utils",
-]
-
-[[package]]
-name = "config"
-version = "0.14.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf"
-dependencies = [
- "async-trait",
- "convert_case",
- "json5",
- "nom",
- "pathdiff",
- "ron",
- "rust-ini",
- "serde",
- "serde_json",
- "toml",
- "yaml-rust2",
-]
-
-[[package]]
-name = "const-random"
-version = "0.1.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
-dependencies = [
- "const-random-macro",
-]
-
-[[package]]
-name = "const-random-macro"
-version = "0.1.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
-dependencies = [
- "getrandom",
- "once_cell",
- "tiny-keccak",
-]
-
-[[package]]
-name = "const_format"
-version = "0.2.35"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad"
-dependencies = [
- "const_format_proc_macros",
-]
-
-[[package]]
-name = "const_format_proc_macros"
-version = "0.2.34"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-xid",
-]
-
-[[package]]
-name = "convert_case"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
-dependencies = [
- "unicode-segmentation",
-]
-
-[[package]]
-name = "core-foundation"
-version = "0.9.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
-dependencies = [
- "core-foundation-sys",
- "libc",
-]
-
-[[package]]
-name = "core-foundation-sys"
-version = "0.8.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
-
-[[package]]
-name = "cpufeatures"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "criterion"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
-dependencies = [
- "anes",
- "async-std",
- "cast",
- "ciborium",
- "clap",
- "criterion-plot",
- "csv",
- "futures",
- "is-terminal",
- "itertools",
- "num-traits",
- "once_cell",
- "oorandom",
- "regex",
- "serde",
- "serde_derive",
- "serde_json",
- "smol",
- "tinytemplate",
- "tokio",
- "walkdir",
-]
-
-[[package]]
-name = "criterion-plot"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
-dependencies = [
- "cast",
- "itertools",
-]
-
-[[package]]
-name = "crossbeam-utils"
-version = "0.8.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
-
-[[package]]
-name = "crunchy"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
-
-[[package]]
-name = "crypto-common"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
-dependencies = [
- "generic-array",
- "rand_core",
- "typenum",
-]
-
-[[package]]
-name = "csv"
-version = "1.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
-dependencies = [
- "csv-core",
- "itoa",
- "ryu",
- "serde",
-]
-
-[[package]]
-name = "csv-core"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "ctr"
-version = "0.9.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
-dependencies = [
- "cipher",
-]
-
-[[package]]
-name = "derive_codec_sv2"
-version = "1.1.1"
-
-[[package]]
-name = "derive_codec_sv2"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "924f288d967a5cd37956b195269ee7f710999169895cf670a736e1b2267d6137"
-dependencies = [
- "binary_codec_sv2 1.2.0",
-]
-
-[[package]]
-name = "digest"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
-dependencies = [
- "generic-array",
-]
-
-[[package]]
-name = "digest"
-version = "0.10.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
-dependencies = [
- "block-buffer 0.10.4",
- "crypto-common",
-]
-
-[[package]]
-name = "dlv-list"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
-dependencies = [
- "const-random",
-]
-
-[[package]]
-name = "either"
-version = "1.15.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
-
-[[package]]
-name = "encoding_rs"
-version = "0.8.35"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "equivalent"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
-
-[[package]]
-name = "errno"
-version = "0.3.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
-dependencies = [
- "libc",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "error_handling"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfdf3be9049288001eb8a37f21b0f4e922598a6fa0098630fd3a6a14459ef217"
-
-[[package]]
-name = "event-listener"
-version = "2.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
-
-[[package]]
-name = "event-listener"
-version = "3.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2"
-dependencies = [
- "concurrent-queue",
- "parking",
- "pin-project-lite",
-]
-
-[[package]]
-name = "event-listener"
-version = "5.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
-dependencies = [
- "concurrent-queue",
- "parking",
- "pin-project-lite",
-]
-
-[[package]]
-name = "event-listener-strategy"
-version = "0.5.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
-dependencies = [
- "event-listener 5.4.1",
- "pin-project-lite",
-]
-
-[[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.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
-
-[[package]]
-name = "find-msvc-tools"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
-
-[[package]]
-name = "fixed-hash"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534"
-dependencies = [
- "byteorder",
- "rand",
- "rustc-hex",
- "static_assertions",
-]
-
-[[package]]
-name = "fnv"
-version = "1.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
-
-[[package]]
-name = "framing_sv2"
-version = "5.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc6543955264144174b93780e0e76623ee4293037c9e180cfde3e2c155b59fa9"
-dependencies = [
- "binary_sv2 4.0.0",
- "buffer_sv2 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "noise_sv2 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "framing_sv2"
-version = "5.0.2"
-dependencies = [
- "binary_sv2 5.0.0",
- "buffer_sv2 2.0.0",
- "noise_sv2 1.4.0",
-]
-
-[[package]]
-name = "funty"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
-
-[[package]]
-name = "futures"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
-dependencies = [
- "futures-channel",
- "futures-core",
- "futures-executor",
- "futures-io",
- "futures-sink",
- "futures-task",
- "futures-util",
-]
-
-[[package]]
-name = "futures-channel"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
-dependencies = [
- "futures-core",
- "futures-sink",
-]
-
-[[package]]
-name = "futures-core"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
-
-[[package]]
-name = "futures-executor"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
-dependencies = [
- "futures-core",
- "futures-task",
- "futures-util",
-]
-
-[[package]]
-name = "futures-io"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
-
-[[package]]
-name = "futures-lite"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
-dependencies = [
- "fastrand 1.9.0",
- "futures-core",
- "futures-io",
- "memchr",
- "parking",
- "pin-project-lite",
- "waker-fn",
-]
-
-[[package]]
-name = "futures-lite"
-version = "2.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
-dependencies = [
- "fastrand 2.3.0",
- "futures-core",
- "futures-io",
- "parking",
- "pin-project-lite",
-]
-
-[[package]]
-name = "futures-macro"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.106",
-]
-
-[[package]]
-name = "futures-sink"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
-
-[[package]]
-name = "futures-task"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
-
-[[package]]
-name = "futures-util"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
-dependencies = [
- "futures-channel",
- "futures-core",
- "futures-io",
- "futures-macro",
- "futures-sink",
- "futures-task",
- "memchr",
- "pin-project-lite",
- "pin-utils",
- "slab",
-]
-
-[[package]]
-name = "generic-array"
-version = "0.14.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
-dependencies = [
- "typenum",
- "version_check",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.2.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi",
-]
-
-[[package]]
-name = "ghash"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
-dependencies = [
- "opaque-debug",
- "polyval",
-]
-
-[[package]]
-name = "gimli"
-version = "0.32.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
-
-[[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 = "h2"
-version = "0.4.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
-dependencies = [
- "atomic-waker",
- "bytes",
- "fnv",
- "futures-core",
- "futures-sink",
- "http",
- "indexmap",
- "slab",
- "tokio",
- "tokio-util",
- "tracing",
-]
-
-[[package]]
-name = "half"
-version = "2.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872"
-dependencies = [
- "cfg-if",
- "crunchy",
-]
-
-[[package]]
-name = "handlers_sv2"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "472824f98b68a963dbf4c77625a8b5525c322abe49afa9403dfb816e35dd4d93"
-dependencies = [
- "binary_sv2 4.0.0",
- "common_messages_sv2 6.0.1",
- "job_declaration_sv2 5.0.1",
- "mining_sv2 5.0.1",
- "parsers_sv2 0.1.1",
- "template_distribution_sv2 4.0.1",
- "trait-variant",
-]
-
-[[package]]
-name = "handlers_sv2"
-version = "0.2.0"
-dependencies = [
- "binary_sv2 5.0.0",
- "common_messages_sv2 6.0.2",
- "job_declaration_sv2 5.0.2",
- "mining_sv2 6.0.0",
- "parsers_sv2 0.1.2",
- "template_distribution_sv2 4.0.2",
- "trait-variant",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
-dependencies = [
- "ahash 0.7.8",
- "serde",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.14.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
-dependencies = [
- "ahash 0.8.12",
- "allocator-api2",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
-
-[[package]]
-name = "hashlink"
-version = "0.8.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
-dependencies = [
- "hashbrown 0.14.5",
-]
-
-[[package]]
-name = "heck"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
-
-[[package]]
-name = "hermit-abi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
-
-[[package]]
-name = "hermit-abi"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
-
-[[package]]
-name = "hex"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
-
-[[package]]
-name = "hex-conservative"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20"
-
-[[package]]
-name = "hex-conservative"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd"
-dependencies = [
- "arrayvec",
-]
-
-[[package]]
-name = "hex-conservative"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4afe881d0527571892c4034822e59bb10c6c991cce6abe8199b6f5cf10766f55"
-dependencies = [
- "arrayvec",
-]
-
-[[package]]
-name = "hex_lit"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
-
-[[package]]
-name = "http"
-version = "1.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
-dependencies = [
- "bytes",
- "fnv",
- "itoa",
-]
-
-[[package]]
-name = "http-body"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
-dependencies = [
- "bytes",
- "http",
-]
-
-[[package]]
-name = "http-body-util"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
-dependencies = [
- "bytes",
- "futures-core",
- "http",
- "http-body",
- "pin-project-lite",
-]
-
-[[package]]
-name = "httparse"
-version = "1.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
-
-[[package]]
-name = "httpdate"
-version = "1.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
-
-[[package]]
-name = "hyper"
-version = "1.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e"
-dependencies = [
- "atomic-waker",
- "bytes",
- "futures-channel",
- "futures-core",
- "h2",
- "http",
- "http-body",
- "httparse",
- "httpdate",
- "itoa",
- "pin-project-lite",
- "pin-utils",
- "smallvec",
- "tokio",
- "want",
-]
-
-[[package]]
-name = "hyper-util"
-version = "0.1.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
-dependencies = [
- "base64 0.22.1",
- "bytes",
- "futures-channel",
- "futures-core",
- "futures-util",
- "http",
- "http-body",
- "hyper",
- "ipnet",
- "libc",
- "percent-encoding",
- "pin-project-lite",
- "socket2 0.6.1",
- "system-configuration",
- "tokio",
- "tower-service",
- "tracing",
- "windows-registry",
-]
-
-[[package]]
-name = "impl-codec"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14"
-dependencies = [
- "parity-scale-codec",
-]
-
-[[package]]
-name = "impl-trait-for-tuples"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.106",
-]
-
-[[package]]
-name = "indexmap"
-version = "2.11.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
-dependencies = [
- "equivalent",
- "hashbrown 0.16.0",
-]
-
-[[package]]
-name = "inout"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
-dependencies = [
- "generic-array",
-]
-
-[[package]]
-name = "instant"
-version = "0.1.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "io-lifetimes"
-version = "1.0.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
-dependencies = [
- "hermit-abi 0.3.9",
- "libc",
- "windows-sys 0.48.0",
-]
-
-[[package]]
-name = "io-uring"
-version = "0.7.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
-dependencies = [
- "bitflags 2.9.4",
- "cfg-if",
- "libc",
-]
-
-[[package]]
-name = "ipnet"
-version = "2.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
-
-[[package]]
-name = "is-terminal"
-version = "0.4.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
-dependencies = [
- "hermit-abi 0.5.2",
- "libc",
- "windows-sys 0.59.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"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
-dependencies = [
- "either",
-]
-
-[[package]]
-name = "itoa"
-version = "1.0.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
-
-[[package]]
-name = "jd_client_sv2"
-version = "0.1.0"
-dependencies = [
- "async-channel 1.9.0",
- "clap",
- "config",
- "serde",
- "stratum-apps",
- "tokio",
- "tracing",
-]
-
-[[package]]
-name = "jd_server"
-version = "0.1.3"
-dependencies = [
- "async-channel 1.9.0",
- "binary_sv2 4.0.0",
- "bitcoin",
- "clap",
- "codec_sv2 3.0.1",
- "common_messages_sv2 6.0.1",
- "config",
- "error_handling",
- "framing_sv2 5.0.1",
- "hashbrown 0.11.2",
- "hex",
- "job_declaration_sv2 5.0.1",
- "mining_sv2 5.0.1",
- "network_helpers_sv2",
- "nohash-hasher",
- "noise_sv2 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "parsers_sv2 0.1.1",
- "rand",
- "roles_logic_sv2 5.0.0",
- "rpc_sv2",
- "serde",
- "serde_json",
- "stratum-apps",
- "tokio",
- "tracing",
-]
-
-[[package]]
-name = "job_declaration_sv2"
-version = "5.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8d4edc436d29e8dcac178539222de2b3681d629f9884191bd7db8831e49dd24"
-dependencies = [
- "binary_sv2 4.0.0",
-]
-
-[[package]]
-name = "job_declaration_sv2"
-version = "5.0.2"
-dependencies = [
- "binary_sv2 5.0.0",
-]
-
-[[package]]
-name = "js-sys"
-version = "0.3.81"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
-dependencies = [
- "once_cell",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "json5"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
-dependencies = [
- "pest",
- "pest_derive",
- "serde",
-]
-
-[[package]]
-name = "kv-log-macro"
-version = "1.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
-dependencies = [
- "log",
-]
-
-[[package]]
-name = "lazy_static"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
-
-[[package]]
-name = "libc"
-version = "0.2.177"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
-
-[[package]]
-name = "linux-raw-sys"
-version = "0.3.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
-
-[[package]]
-name = "linux-raw-sys"
-version = "0.4.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
-
-[[package]]
-name = "linux-raw-sys"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
-
-[[package]]
-name = "lock_api"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
-dependencies = [
- "scopeguard",
-]
-
-[[package]]
-name = "log"
-version = "0.4.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
-dependencies = [
- "value-bag",
-]
-
-[[package]]
-name = "matchers"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
-dependencies = [
- "regex-automata",
-]
-
-[[package]]
-name = "memchr"
-version = "2.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
-
-[[package]]
-name = "minimal-lexical"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
-
-[[package]]
-name = "mining_device"
-version = "0.1.3"
-dependencies = [
- "async-channel 1.9.0",
- "async-recursion",
- "binary_sv2 4.0.0",
- "clap",
- "codec_sv2 3.0.1",
- "common_messages_sv2 6.0.1",
- "criterion",
- "framing_sv2 5.0.1",
- "futures",
- "half",
- "mining_sv2 5.0.1",
- "network_helpers_sv2",
- "noise_sv2 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "num-format",
- "num_cpus",
- "parsers_sv2 0.1.1",
- "primitive-types",
- "rand",
- "roles_logic_sv2 5.0.0",
- "sha2 0.10.9",
- "stratum-apps",
- "tokio",
- "tracing",
- "tracing-subscriber",
-]
-
-[[package]]
-name = "mining_sv2"
-version = "5.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1eb3c055232f64d36e3eee4296adcaa584fb3185a57e0de11ad5807766c45edc"
-dependencies = [
- "binary_sv2 4.0.0",
-]
-
-[[package]]
-name = "mining_sv2"
-version = "6.0.0"
-dependencies = [
- "binary_sv2 5.0.0",
-]
-
-[[package]]
-name = "miniscript"
-version = "12.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "487906208f38448e186e3deb02f2b8ef046a9078b0de00bdb28bf4fb9b76951c"
-dependencies = [
- "bech32",
- "bitcoin",
-]
-
-[[package]]
-name = "miniz_oxide"
-version = "0.8.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
-dependencies = [
- "adler2",
-]
-
-[[package]]
-name = "mio"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
-dependencies = [
- "libc",
- "wasi",
- "windows-sys 0.59.0",
-]
-
-[[package]]
-name = "network_helpers_sv2"
-version = "4.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05d720d6a31532fb4f08e59b71669084d06462f42e9d2c2aede7368d221d36db"
-dependencies = [
- "async-channel 1.9.0",
- "codec_sv2 3.0.1",
- "futures",
- "tokio",
- "tracing",
-]
-
-[[package]]
-name = "nohash-hasher"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
-
-[[package]]
-name = "noise_sv2"
-version = "1.4.0"
-dependencies = [
- "aes-gcm",
- "chacha20poly1305",
- "generic-array",
- "rand",
- "rand_chacha",
- "secp256k1 0.28.2",
-]
-
-[[package]]
-name = "noise_sv2"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30964f9fbc4572bb5a1b0046176331d20e9ce6de0ca18afc3cfd42c6e91a94aa"
-dependencies = [
- "aes-gcm",
- "chacha20poly1305",
- "rand",
- "rand_chacha",
- "secp256k1 0.28.2",
-]
-
-[[package]]
-name = "nom"
-version = "7.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
-dependencies = [
- "memchr",
- "minimal-lexical",
-]
-
-[[package]]
-name = "nu-ansi-term"
-version = "0.50.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
-dependencies = [
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "num-format"
-version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
-dependencies = [
- "arrayvec",
- "itoa",
-]
-
-[[package]]
-name = "num-traits"
-version = "0.2.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "num_cpus"
-version = "1.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
-dependencies = [
- "hermit-abi 0.5.2",
- "libc",
-]
-
-[[package]]
-name = "object"
-version = "0.37.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "once_cell"
-version = "1.21.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
-
-[[package]]
-name = "once_cell_polyfill"
-version = "1.70.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
-
-[[package]]
-name = "oorandom"
-version = "11.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
-
-[[package]]
-name = "opaque-debug"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
-
-[[package]]
-name = "ordered-multimap"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
-dependencies = [
- "dlv-list",
- "hashbrown 0.14.5",
-]
-
-[[package]]
-name = "parity-scale-codec"
-version = "3.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa"
-dependencies = [
- "arrayvec",
- "bitvec",
- "byte-slice-cast",
- "const_format",
- "impl-trait-for-tuples",
- "parity-scale-codec-derive",
- "rustversion",
- "serde",
-]
-
-[[package]]
-name = "parity-scale-codec-derive"
-version = "3.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a"
-dependencies = [
- "proc-macro-crate",
- "proc-macro2",
- "quote",
- "syn 2.0.106",
-]
-
-[[package]]
-name = "parking"
-version = "2.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
-
-[[package]]
-name = "parking_lot"
-version = "0.12.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
-dependencies = [
- "lock_api",
- "parking_lot_core",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.9.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
-dependencies = [
- "cfg-if",
- "libc",
- "redox_syscall",
- "smallvec",
- "windows-link 0.2.1",
-]
-
-[[package]]
-name = "parsers_sv2"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "109e80bc77241a729f61cad15f3f246f3de12e1b741b31e419fc7e02f20c2ccb"
-dependencies = [
- "binary_sv2 4.0.0",
- "common_messages_sv2 6.0.1",
- "framing_sv2 5.0.1",
- "job_declaration_sv2 5.0.1",
- "mining_sv2 5.0.1",
- "template_distribution_sv2 4.0.1",
-]
-
-[[package]]
-name = "parsers_sv2"
-version = "0.1.2"
-dependencies = [
- "binary_sv2 5.0.0",
- "common_messages_sv2 6.0.2",
- "framing_sv2 5.0.2",
- "job_declaration_sv2 5.0.2",
- "mining_sv2 6.0.0",
- "template_distribution_sv2 4.0.2",
-]
-
-[[package]]
-name = "pathdiff"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
-
-[[package]]
-name = "percent-encoding"
-version = "2.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
-
-[[package]]
-name = "pest"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6"
-dependencies = [
- "memchr",
- "thiserror",
- "ucd-trie",
-]
-
-[[package]]
-name = "pest_derive"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5"
-dependencies = [
- "pest",
- "pest_generator",
-]
-
-[[package]]
-name = "pest_generator"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841"
-dependencies = [
- "pest",
- "pest_meta",
- "proc-macro2",
- "quote",
- "syn 2.0.106",
-]
-
-[[package]]
-name = "pest_meta"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0"
-dependencies = [
- "once_cell",
- "pest",
- "sha2 0.10.9",
-]
-
-[[package]]
-name = "pin-project-lite"
-version = "0.2.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
-
-[[package]]
-name = "pin-utils"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
-
-[[package]]
-name = "piper"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
-dependencies = [
- "atomic-waker",
- "fastrand 2.3.0",
- "futures-io",
-]
-
-[[package]]
-name = "polling"
-version = "2.8.0"
-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.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
-dependencies = [
- "cfg-if",
- "concurrent-queue",
- "hermit-abi 0.5.2",
- "pin-project-lite",
- "rustix 1.1.2",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "poly1305"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
-dependencies = [
- "cpufeatures",
- "opaque-debug",
- "universal-hash",
-]
-
-[[package]]
-name = "polyval"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "opaque-debug",
- "universal-hash",
-]
-
-[[package]]
-name = "pool_sv2"
-version = "0.2.0"
-dependencies = [
- "async-channel 1.9.0",
- "clap",
- "config",
- "rand",
- "secp256k1 0.28.2",
- "serde",
- "stratum-apps",
- "tokio",
- "tracing",
-]
-
-[[package]]
-name = "ppv-lite86"
-version = "0.2.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
-dependencies = [
- "zerocopy",
-]
-
-[[package]]
-name = "primitive-types"
-version = "0.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5"
-dependencies = [
- "fixed-hash",
- "impl-codec",
- "uint",
-]
-
-[[package]]
-name = "proc-macro-crate"
-version = "3.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
-dependencies = [
- "toml_edit 0.23.7",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.101"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.41"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "radium"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
-
-[[package]]
-name = "rand"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
-dependencies = [
- "libc",
- "rand_chacha",
- "rand_core",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
-dependencies = [
- "ppv-lite86",
- "rand_core",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
-dependencies = [
- "getrandom",
-]
-
-[[package]]
-name = "redox_syscall"
-version = "0.5.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
-dependencies = [
- "bitflags 2.9.4",
-]
-
-[[package]]
-name = "regex"
-version = "1.12.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-automata",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-automata"
-version = "0.4.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-syntax"
-version = "0.8.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
-
-[[package]]
-name = "roles_logic_sv2"
-version = "4.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7241840512841396df29ede2094619ad06cbbd1a0dc342553c7a5901506d096b"
-dependencies = [
- "bitcoin",
- "chacha20poly1305",
- "channels_sv2 1.0.2",
- "codec_sv2 3.0.1",
- "common_messages_sv2 6.0.1",
- "handlers_sv2 0.1.0",
- "hex-conservative 0.3.0",
- "job_declaration_sv2 5.0.1",
- "mining_sv2 5.0.1",
- "nohash-hasher",
- "parsers_sv2 0.1.1",
- "primitive-types",
- "template_distribution_sv2 4.0.1",
- "tracing",
-]
-
-[[package]]
-name = "roles_logic_sv2"
-version = "5.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88850ead16993f86cb4616d154ddd37b9c0d739ea23711b1cc51f40484e0e39a"
-dependencies = [
- "binary_sv2 4.0.0",
- "bitcoin",
- "chacha20poly1305",
- "channels_sv2 1.0.2",
- "codec_sv2 3.0.1",
- "common_messages_sv2 6.0.1",
- "handlers_sv2 0.1.0",
- "hex-conservative 0.3.0",
- "job_declaration_sv2 5.0.1",
- "mining_sv2 5.0.1",
- "nohash-hasher",
- "parsers_sv2 0.1.1",
- "primitive-types",
- "template_distribution_sv2 4.0.1",
- "tracing",
-]
-
-[[package]]
-name = "ron"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
-dependencies = [
- "base64 0.21.7",
- "bitflags 2.9.4",
- "serde",
- "serde_derive",
-]
-
-[[package]]
-name = "rpc_sv2"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af9b3a6c43d03c5cc6ca9f40797cbf17a9a30b8db236be6c87f5243bd404d6af"
-dependencies = [
- "base64 0.21.7",
- "hex",
- "http-body-util",
- "hyper",
- "hyper-util",
- "serde",
- "serde_json",
- "stratum-common",
-]
-
-[[package]]
-name = "rust-ini"
-version = "0.20.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a"
-dependencies = [
- "cfg-if",
- "ordered-multimap",
-]
-
-[[package]]
-name = "rustc-demangle"
-version = "0.1.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
-
-[[package]]
-name = "rustc-hex"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
-
-[[package]]
-name = "rustix"
-version = "0.37.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6"
-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.44"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
-dependencies = [
- "bitflags 2.9.4",
- "errno",
- "libc",
- "linux-raw-sys 0.4.15",
- "windows-sys 0.59.0",
-]
-
-[[package]]
-name = "rustix"
-version = "1.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
-dependencies = [
- "bitflags 2.9.4",
- "errno",
- "libc",
- "linux-raw-sys 0.11.0",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "rustversion"
-version = "1.0.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
-
-[[package]]
-name = "ryu"
-version = "1.0.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
-
-[[package]]
-name = "same-file"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
-dependencies = [
- "winapi-util",
-]
-
-[[package]]
-name = "scopeguard"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-
-[[package]]
-name = "secp256k1"
-version = "0.28.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10"
-dependencies = [
- "bitcoin_hashes 0.13.0",
- "rand",
- "secp256k1-sys 0.9.2",
-]
-
-[[package]]
-name = "secp256k1"
-version = "0.29.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
-dependencies = [
- "bitcoin_hashes 0.14.0",
- "secp256k1-sys 0.10.1",
-]
-
-[[package]]
-name = "secp256k1-sys"
-version = "0.9.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb"
-dependencies = [
- "cc",
-]
-
-[[package]]
-name = "secp256k1-sys"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9"
-dependencies = [
- "cc",
-]
-
-[[package]]
-name = "serde"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
-dependencies = [
- "serde_core",
- "serde_derive",
-]
-
-[[package]]
-name = "serde_core"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.106",
-]
-
-[[package]]
-name = "serde_json"
-version = "1.0.145"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
-dependencies = [
- "itoa",
- "memchr",
- "ryu",
- "serde",
- "serde_core",
-]
-
-[[package]]
-name = "serde_spanned"
-version = "0.6.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "sha2"
-version = "0.9.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
-dependencies = [
- "block-buffer 0.9.0",
- "cfg-if",
- "cpufeatures",
- "digest 0.9.0",
- "opaque-debug",
-]
-
-[[package]]
-name = "sha2"
-version = "0.10.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest 0.10.7",
- "sha2-asm",
-]
-
-[[package]]
-name = "sha2-asm"
-version = "0.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b845214d6175804686b2bd482bcffe96651bb2d1200742b712003504a2dac1ab"
-dependencies = [
- "cc",
-]
-
-[[package]]
-name = "sharded-slab"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
-dependencies = [
- "lazy_static",
-]
-
-[[package]]
-name = "shlex"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
-
-[[package]]
-name = "signal-hook-registry"
-version = "1.4.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "slab"
-version = "0.4.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
-
-[[package]]
-name = "smallvec"
-version = "1.15.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
-
-[[package]]
-name = "smol"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1"
-dependencies = [
- "async-channel 1.9.0",
- "async-executor",
- "async-fs",
- "async-io 1.13.0",
- "async-lock 2.8.0",
- "async-net",
- "async-process",
- "blocking",
- "futures-lite 1.13.0",
-]
-
-[[package]]
-name = "socket2"
-version = "0.4.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
-dependencies = [
- "libc",
- "winapi",
-]
-
-[[package]]
-name = "socket2"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
-dependencies = [
- "libc",
- "windows-sys 0.60.2",
-]
-
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
-[[package]]
-name = "stratum-apps"
-version = "0.1.0"
-dependencies = [
- "async-channel 1.9.0",
- "base64 0.21.7",
- "bs58",
- "clap",
- "config",
- "futures",
- "generic-array",
- "hex",
- "http-body-util",
- "hyper",
- "hyper-util",
- "miniscript",
- "rand",
- "rustversion",
- "secp256k1 0.28.2",
- "serde",
- "serde_json",
- "stratum-core",
- "tokio",
- "tokio-util",
- "tracing",
- "tracing-subscriber",
-]
-
-[[package]]
-name = "stratum-common"
-version = "4.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77b7dc7a762d19aba6f355599a61440b29603ceece5a158914888691b9867ebe"
-dependencies = [
- "roles_logic_sv2 4.0.0",
-]
-
-[[package]]
-name = "stratum-core"
-version = "0.1.0"
-dependencies = [
- "binary_sv2 5.0.0",
- "bitcoin",
- "buffer_sv2 2.0.0",
- "channels_sv2 2.0.0",
- "codec_sv2 4.0.0",
- "common_messages_sv2 6.0.2",
- "framing_sv2 5.0.2",
- "handlers_sv2 0.2.0",
- "job_declaration_sv2 5.0.2",
- "mining_sv2 6.0.0",
- "noise_sv2 1.4.0",
- "parsers_sv2 0.1.2",
- "stratum_translation",
- "sv1_api",
- "template_distribution_sv2 4.0.2",
-]
-
-[[package]]
-name = "stratum_translation"
-version = "0.1.1"
-dependencies = [
- "binary_sv2 5.0.0",
- "bitcoin",
- "channels_sv2 2.0.0",
- "mining_sv2 6.0.0",
- "sv1_api",
- "tracing",
-]
-
-[[package]]
-name = "strsim"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
-
-[[package]]
-name = "subtle"
-version = "2.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
-
-[[package]]
-name = "sv1_api"
-version = "2.1.2"
-dependencies = [
- "binary_sv2 5.0.0",
- "bitcoin_hashes 0.3.2",
- "byteorder",
- "hex",
- "serde",
- "serde_json",
- "tracing",
-]
-
-[[package]]
-name = "syn"
-version = "1.0.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "syn"
-version = "2.0.106"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "system-configuration"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
-dependencies = [
- "bitflags 2.9.4",
- "core-foundation",
- "system-configuration-sys",
-]
-
-[[package]]
-name = "system-configuration-sys"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
-dependencies = [
- "core-foundation-sys",
- "libc",
-]
-
-[[package]]
-name = "tap"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
-
-[[package]]
-name = "template_distribution_sv2"
-version = "4.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6298fc9f339b1c3b654ef3590857d5d3eff6d709891f003b7f7a701b8a64a3a4"
-dependencies = [
- "binary_sv2 4.0.0",
-]
-
-[[package]]
-name = "template_distribution_sv2"
-version = "4.0.2"
-dependencies = [
- "binary_sv2 5.0.0",
-]
-
-[[package]]
-name = "thiserror"
-version = "2.0.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
-dependencies = [
- "thiserror-impl",
-]
-
-[[package]]
-name = "thiserror-impl"
-version = "2.0.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.106",
-]
-
-[[package]]
-name = "thread_local"
-version = "1.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "tiny-keccak"
-version = "2.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
-dependencies = [
- "crunchy",
-]
-
-[[package]]
-name = "tinytemplate"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
-dependencies = [
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "tokio"
-version = "1.47.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
-dependencies = [
- "backtrace",
- "bytes",
- "io-uring",
- "libc",
- "mio",
- "parking_lot",
- "pin-project-lite",
- "signal-hook-registry",
- "slab",
- "socket2 0.6.1",
- "tokio-macros",
- "windows-sys 0.59.0",
-]
-
-[[package]]
-name = "tokio-macros"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.106",
-]
-
-[[package]]
-name = "tokio-util"
-version = "0.7.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
-dependencies = [
- "bytes",
- "futures-core",
- "futures-sink",
- "pin-project-lite",
- "tokio",
-]
-
-[[package]]
-name = "toml"
-version = "0.8.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
-dependencies = [
- "serde",
- "serde_spanned",
- "toml_datetime 0.6.11",
- "toml_edit 0.22.27",
-]
-
-[[package]]
-name = "toml_datetime"
-version = "0.6.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "toml_datetime"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
-dependencies = [
- "serde_core",
-]
-
-[[package]]
-name = "toml_edit"
-version = "0.22.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
-dependencies = [
- "indexmap",
- "serde",
- "serde_spanned",
- "toml_datetime 0.6.11",
- "toml_write",
- "winnow",
-]
-
-[[package]]
-name = "toml_edit"
-version = "0.23.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d"
-dependencies = [
- "indexmap",
- "toml_datetime 0.7.3",
- "toml_parser",
- "winnow",
-]
-
-[[package]]
-name = "toml_parser"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
-dependencies = [
- "winnow",
-]
-
-[[package]]
-name = "toml_write"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
-
-[[package]]
-name = "tower-service"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
-
-[[package]]
-name = "tracing"
-version = "0.1.41"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
-dependencies = [
- "pin-project-lite",
- "tracing-attributes",
- "tracing-core",
-]
-
-[[package]]
-name = "tracing-attributes"
-version = "0.1.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.106",
-]
-
-[[package]]
-name = "tracing-core"
-version = "0.1.34"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
-dependencies = [
- "once_cell",
- "valuable",
-]
-
-[[package]]
-name = "tracing-log"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
-dependencies = [
- "log",
- "once_cell",
- "tracing-core",
-]
-
-[[package]]
-name = "tracing-subscriber"
-version = "0.3.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
-dependencies = [
- "matchers",
- "nu-ansi-term",
- "once_cell",
- "regex-automata",
- "sharded-slab",
- "smallvec",
- "thread_local",
- "tracing",
- "tracing-core",
- "tracing-log",
-]
-
-[[package]]
-name = "trait-variant"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.106",
-]
-
-[[package]]
-name = "translator_sv2"
-version = "2.0.0"
-dependencies = [
- "async-channel 1.9.0",
- "clap",
- "config",
- "serde",
- "serde_json",
- "sha2 0.10.9",
- "stratum-apps",
- "tokio",
- "tracing",
-]
-
-[[package]]
-name = "try-lock"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
-
-[[package]]
-name = "typenum"
-version = "1.19.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
-
-[[package]]
-name = "ucd-trie"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
-
-[[package]]
-name = "uint"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e"
-dependencies = [
- "byteorder",
- "crunchy",
- "hex",
- "static_assertions",
-]
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
-
-[[package]]
-name = "unicode-segmentation"
-version = "1.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
-
-[[package]]
-name = "unicode-xid"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
-
-[[package]]
-name = "universal-hash"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
-dependencies = [
- "crypto-common",
- "subtle",
-]
-
-[[package]]
-name = "utf8parse"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
-
-[[package]]
-name = "valuable"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
-
-[[package]]
-name = "value-bag"
-version = "1.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5"
-
-[[package]]
-name = "version_check"
-version = "0.9.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
-
-[[package]]
-name = "waker-fn"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
-
-[[package]]
-name = "walkdir"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
-dependencies = [
- "same-file",
- "winapi-util",
-]
-
-[[package]]
-name = "want"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
-dependencies = [
- "try-lock",
-]
-
-[[package]]
-name = "wasi"
-version = "0.11.1+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
-
-[[package]]
-name = "wasm-bindgen"
-version = "0.2.104"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
-dependencies = [
- "cfg-if",
- "once_cell",
- "rustversion",
- "wasm-bindgen-macro",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.104"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
-dependencies = [
- "bumpalo",
- "log",
- "proc-macro2",
- "quote",
- "syn 2.0.106",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-futures"
-version = "0.4.54"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c"
-dependencies = [
- "cfg-if",
- "js-sys",
- "once_cell",
- "wasm-bindgen",
- "web-sys",
-]
-
-[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.104"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
-dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
-]
-
-[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.104"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.106",
- "wasm-bindgen-backend",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.104"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "web-sys"
-version = "0.3.81"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-util"
-version = "0.1.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
-dependencies = [
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-
-[[package]]
-name = "windows-link"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
-
-[[package]]
-name = "windows-link"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
-
-[[package]]
-name = "windows-registry"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
-dependencies = [
- "windows-link 0.1.3",
- "windows-result",
- "windows-strings",
-]
-
-[[package]]
-name = "windows-result"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
-dependencies = [
- "windows-link 0.1.3",
-]
-
-[[package]]
-name = "windows-strings"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
-dependencies = [
- "windows-link 0.1.3",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
-dependencies = [
- "windows-targets 0.48.5",
-]
-
-[[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]]
-name = "windows-sys"
-version = "0.60.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
-dependencies = [
- "windows-targets 0.53.5",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.61.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
-dependencies = [
- "windows-link 0.2.1",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
-dependencies = [
- "windows_aarch64_gnullvm 0.48.5",
- "windows_aarch64_msvc 0.48.5",
- "windows_i686_gnu 0.48.5",
- "windows_i686_msvc 0.48.5",
- "windows_x86_64_gnu 0.48.5",
- "windows_x86_64_gnullvm 0.48.5",
- "windows_x86_64_msvc 0.48.5",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
-dependencies = [
- "windows_aarch64_gnullvm 0.52.6",
- "windows_aarch64_msvc 0.52.6",
- "windows_i686_gnu 0.52.6",
- "windows_i686_gnullvm 0.52.6",
- "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]]
-name = "windows-targets"
-version = "0.53.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
-dependencies = [
- "windows-link 0.2.1",
- "windows_aarch64_gnullvm 0.53.1",
- "windows_aarch64_msvc 0.53.1",
- "windows_i686_gnu 0.53.1",
- "windows_i686_gnullvm 0.53.1",
- "windows_i686_msvc 0.53.1",
- "windows_x86_64_gnu 0.53.1",
- "windows_x86_64_gnullvm 0.53.1",
- "windows_x86_64_msvc 0.53.1",
-]
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
-
-[[package]]
-name = "windows_i686_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
-
-[[package]]
-name = "windows_i686_gnullvm"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
-
-[[package]]
-name = "winnow"
-version = "0.7.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "wyz"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
-dependencies = [
- "tap",
-]
-
-[[package]]
-name = "yaml-rust2"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8"
-dependencies = [
- "arraydeque",
- "encoding_rs",
- "hashlink",
-]
-
-[[package]]
-name = "zerocopy"
-version = "0.8.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
-dependencies = [
- "zerocopy-derive",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.8.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.106",
-]
-
-[[package]]
-name = "zeroize"
-version = "1.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
diff --git a/roles/jd-client/Cargo.toml b/roles/jd-client/Cargo.toml
deleted file mode 100644
index a161bdce71..0000000000
--- a/roles/jd-client/Cargo.toml
+++ /dev/null
@@ -1,25 +0,0 @@
-[package]
-name = "jd_client_sv2"
-version = "0.1.0"
-authors = ["The Stratum V2 Developers"]
-edition = "2021"
-description = "Job Declarator Client (JDC) role"
-documentation = "https://docs.rs/jd_client"
-readme = "README.md"
-homepage = "https://stratumprotocol.org"
-repository = "https://github.com/stratum-mining/stratum"
-license = "MIT OR Apache-2.0"
-keywords = ["stratum", "mining", "bitcoin", "protocol"]
-
-[lib]
-name = "jd_client_sv2"
-path = "src/lib/mod.rs"
-
-[dependencies]
-stratum-apps = { path = "../stratum-apps", features = ["jd_client"] }
-async-channel = "1.5.1"
-serde = { version = "1.0.89", default-features = false, features = ["derive", "alloc"] }
-tokio = { version = "1.44.1", features = ["full"] }
-ext-config = { version = "0.14.0", features = ["toml"], package = "config" }
-tracing = { version = "0.1" }
-clap = { version = "4.5.39", features = ["derive"] }
diff --git a/roles/jd-client/README.md b/roles/jd-client/README.md
deleted file mode 100644
index e08a767fae..0000000000
--- a/roles/jd-client/README.md
+++ /dev/null
@@ -1,188 +0,0 @@
-
-# Job Declarator Client
-
-The **Job Declarator Client (JDC)** is responsible for:
-
-* Connecting to the **Pool** and **JD Server**.
-* Connecting to the **Template Provider**.
-* Receiving custom block templates from the Template Provider and declaring them to the pool via the **Job Declaration Protocol**.
-* Sending jobs to downstream clients.
-* Forwarding shares to the pool.
-
-## Architecture Overview
-
-The JDC sits between **SV2 downstream clients** (e.g., SV2 mining devices or Translator Proxies) and **SV2 upstream servers** (the Pool and JD Server).
-
-* It obtains templates from the Bitcoin node.
-* It creates and broadcasts jobs to downstream clients.
-* It declares and sets custom jobs to the pool side.
-* It also supports solo mining mode in case no upstream is available or the upstream is fraudulent
-
-Note: while JDC can cater for multiple downstream clients, with either one or multiple channels per client, it only opens one single extended channel with the upstream Pool server.
-
-```
-<--- Most Downstream ------------------------------------------------------------------------------------------------ Most Upstream --->
-
-+----------------------------------------------------------------------------------------------------+ +------------------------------+
-| Mining Farm | | Remote Pool |
-| | | |
-| +-------------------+ +------------------+ | | +-----------------+ |
-| | SV1 Mining Device | <-> | Translator Proxy |-------| |-------------------------------> | SV2 Pool Server | |
-| +-------------------+ +------------------+ | | | | +-----------------+ |
-| | | | | |
-| | | | | |
-| +-----------------------+| | | |
-| | Job Declarator Client | | | |
-| +-----------------------+| | | +-----------------------+ |
-| | |--------------------------------> | Job Declarator Server | |
-| +-------------------+ | | | +-----------------------+ |
-| | SV2 Mining Device |-----------------------------| | | |
-| +-------------------+ | | |
-| | | |
-| | | |
-| | | |
-+----------------------------------------------------------------------------------------------------+ +------------------------------+
-
-
-```
-## Setup
-
-### Configuration File
-
-The configuration file contains the following information:
-
-1. The downstream socket information, which includes the listening IP address (`downstream_address`) and port (`downstream_port`).
-2. The maximum and minimum protocol versions (`max_supported_version` and `min_supported_version`) with size as (`min_extranonce2_size`)
-3. The authentication keys used for the downstream connections (`authority_public_key`, `authority_secret_key`)
-4. The Template Provider address (`tp_address`).
-
-## Configuration
-
-The JDC is configured via a `.toml` file.
-See [`config-examples/jdc-config-local-example.toml`](./config-examples/jdc-config-local-example.toml) for a full example.
-
-### Example Configuration
-
-```toml
-# Listening address for downstream clients
-listening_address = "127.0.0.1:34265"
-
-# Version support
-max_supported_version = 2
-min_supported_version = 2
-
-# Authentication keys for encrypted downstream connections
-authority_public_key = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72"
-authority_secret_key = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n"
-cert_validity_sec = 3600
-
-user_identity = "your_username_here"
-
-# Target shares per minute & batching
-shares_per_minute = 1.0
-share_batch_size = 1
-min_extranonce_size = 4
-
-# Template Provider
-tp_address = "127.0.0.1:8442"
-jdc_signature = "Sv2MinerSignature"
-
-# Coinbase output for solo mining fallback
-coinbase_reward_script = "addr(tb1qa0sm0hxzj0x25rh8gw5xlzwlsfvvyz8u96w3p8)"
-
-[[upstreams]]
-authority_pubkey = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72"
-pool_address = "127.0.0.1"
-pool_port = 34254
-jd_address = "127.0.0.1"
-jd_port = 34264
-```
-
-For a complete, annotated config, see the [full example](./config-examples/jdc-config-hosted-example.toml).
-
-
-## Usage
-
-### Installation & Build
-
-```bash
-# Clone the repository
-git clone https://github.com/stratum-mining/stratum.git
-cd stratum
-
-# Build JDC
-cargo build --release -p jd_client
-```
-
-### Running JDC
-
-#### With Local Pool and Job Declarator Server
-
-```bash
-cd roles/jd_client
-cargo run -- -c config-examples/jdc-config-local-example.toml
-```
-
-#### With Hosted Pool and Job Declarator Server
-
-```bash
-cd roles/jd_client
-cargo run -- -c config-examples/jdc-config-hosted-example.toml
-```
-
-### Command Line Options
-
-```bash
-# Use specific config file
-jd_client -c /path/to/config.toml
-jd_client --config /path/to/config.toml
-
-# Show help
-jd_client -h
-jd_client --help
-```
-
-## Architecture Details
-
-### **Component Overview**
-
-1. **Channel Manager**: Orchestrates message routing among sub-systems in JDC
-2. **Task Manager**: Manages async task lifecycle and coordination
-3. **Status System**: Provides real-time monitoring and health reporting
-
-## Internal Architecture
-
-JDC is built from several modules that divide responsibility for handling different roles and protocols:
-
-### **Modules**
-
-1. **Upstream**
-
- * Connects to the **pool**.
- * Handles messages coming from the Pool (the ones defined in the Common Protocol are directly handled, others are forwarded to the Channel Manager).
-
-2. **Downstream**
-
- * Accepts connections from Sv2 Mining Devices or Translator Proxies.
- * Includes a **ChannelState**, which provisions new channels when `OpenStandard/ExtendedChannel` messages arrive from the downstreams.
-
-3. **Template Receiver**
-
- * Connects to the **Template Provider**.
- * Handles messages received by the TP (the ones defined in the Common Protocol are directly handled, while the others are forwarded to the Channel Manager).
-
-4. **Job Declarator**
-
- * Connects to the **Job Declarator Server (JDS)**.
- * Handles messages received by the JDS (the ones defined in the Common Protocol are directly handled, while the others are forwarded to the Channel Manager).
-
-5. **Channel Manager (Orchestrator)**
-
- * Central coordination point.
- * Responsibilities:
-
- * Handles **non-common messages** forwarded from all modules.
- * Maintains **upstream channel state**.
- * Maintains most of the **Job Declarator state**.
- * Orchestrates job lifecycle and state synchronization across upstream and downstream roles.
-
diff --git a/roles/jd-client/config-examples/jdc-config-hosted-example.toml b/roles/jd-client/config-examples/jdc-config-hosted-example.toml
deleted file mode 100644
index 3b9d9887f8..0000000000
--- a/roles/jd-client/config-examples/jdc-config-hosted-example.toml
+++ /dev/null
@@ -1,66 +0,0 @@
-# SRI JDC config
-listening_address = "127.0.0.1:34265"
-
-# Version support
-max_supported_version = 2
-min_supported_version = 2
-
-# Auth keys for open encrypted connection downstream
-authority_public_key = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72"
-authority_secret_key = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n"
-cert_validity_sec = 3600
-
-
-# User identity/username for pool connection
-user_identity = "your_username_here"
-
-# target number of shares per minute applied to every downstream channel
-shares_per_minute = 6.0
-
-# Share batch size
-share_batch_size = 10
-
-# JDC supports two modes:
-# "FULLTEMPLATE" - full template mining
-# "COINBASEONLY" - coinbase-only mining
-mode = "FULLTEMPLATE"
-
-# Template Provider config
-# Local TP (this is pointing to localhost so you must run a TP locally for this configuration to work)
-# tp_address = "127.0.0.1:8442"
-# Hosted testnet TP
-tp_address = "75.119.150.111:8442"
-tp_authority_public_key = "9bwHCYnjhbHm4AS3pWg9MtAH83mzWohoJJJDELYBqZhDNqszDLc"
-
-# string to be added into the Coinbase scriptSig
-jdc_signature = "Sv2MinerSignature"
-
-# Solo Mining config
-# Coinbase output used to build the coinbase tx in case of Solo Mining (as last-resort solution of the pools fallback system)
-#
-# Coinbase outputs are specified as descriptors. A full list of descriptors is available at
-# https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki#appendix-b-index-of-script-expressions
-# Although the `musig` descriptor is not yet supported and the legacy `combo` descriptor never
-# will be. If you have an address, embed it in a descriptor like `addr()`.
-coinbase_reward_script = "addr(tb1qa0sm0hxzj0x25rh8gw5xlzwlsfvvyz8u96w3p8)"
-
-# Enable this option to set a predefined log file path.
-# When enabled, logs will always be written to this file.
-# The CLI option --log-file (or -f) will override this setting if provided.
-# log_file = "./jd-client.log"
-
-# List of upstreams (JDS) used as backup endpoints
-# In case of shares refused by the JDS, the fallback system will propose the same job to the next upstream in this list
-[[upstreams]]
-authority_pubkey = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72"
-pool_address = "75.119.150.111"
-pool_port = "34254"
-jds_address = "75.119.150.111"
-jds_port = "34264"
-
-# [[upstreams]]
-# authority_pubkey = "2di19GHYQnAZJmEpoUeP7C3Eg9TCcksHr23rZCC83dvUiZgiDL"
-# pool_address = "127.0.0.1:34254"
-# pool_port = "34254"
-# jds_address = "127.0.0.1:34264"
-# jds_port = "34264"
diff --git a/roles/jd-client/config-examples/jdc-config-local-example.toml b/roles/jd-client/config-examples/jdc-config-local-example.toml
deleted file mode 100644
index f550a5fd6d..0000000000
--- a/roles/jd-client/config-examples/jdc-config-local-example.toml
+++ /dev/null
@@ -1,66 +0,0 @@
-# SRI JDC config
-listening_address = "127.0.0.1:34265"
-
-# Version support
-max_supported_version = 2
-min_supported_version = 2
-
-# Auth keys for open encrypted connection downstream
-authority_public_key = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72"
-authority_secret_key = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n"
-cert_validity_sec = 3600
-
-
-# User identity/username for pool connection
-user_identity = "your_username_here"
-
-# target number of shares per minute applied to every downstream channel
-shares_per_minute = 6.0
-
-# Share batch size
-share_batch_size = 10
-
-# JDC supports two modes:
-# "FULLTEMPLATE" - full template mining
-# "COINBASEONLY" - coinbase-only mining
-mode = "FULLTEMPLATE"
-
-# Template Provider config
-# Local TP (this is pointing to localhost so you must run a TP locally for this configuration to work)
-tp_address = "127.0.0.1:8442"
-# Hosted testnet TP
-# tp_address = "75.119.150.111:8442"
-
-# string to be added into the Coinbase scriptSig
-jdc_signature = "Sv2MinerSignature"
-
-# Solo Mining config
-# Coinbase output used to build the coinbase tx in case of Solo Mining (as last-resort solution of the pools fallback system)
-#
-# Coinbase outputs are specified as descriptors. A full list of descriptors is available at
-# https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki#appendix-b-index-of-script-expressions
-# Although the `musig` descriptor is not yet supported and the legacy `combo` descriptor never
-# will be. If you have an address, embed it in a descriptor like `addr()`.
-coinbase_reward_script = "addr(tb1qa0sm0hxzj0x25rh8gw5xlzwlsfvvyz8u96w3p8)"
-
-# Enable this option to set a predefined log file path.
-# When enabled, logs will always be written to this file.
-# The CLI option --log-file (or -f) will override this setting if provided.
-# log_file = "./jd-client.log"
-
-
-# List of upstreams (JDS) used as backup endpoints
-# In case of shares refused by the JDS, the fallback system will propose the same job to the next upstream in this list
-[[upstreams]]
-authority_pubkey = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72"
-pool_address = "127.0.0.1"
-pool_port = 34254
-jds_address = "127.0.0.1"
-jds_port = 34264
-
-[[upstreams]]
-authority_pubkey = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72"
-pool_address = "75.119.150.111"
-pool_port = "34254"
-jds_address = "75.119.150.111"
-jds_port = "34264"
diff --git a/roles/jd-client/src/args.rs b/roles/jd-client/src/args.rs
deleted file mode 100644
index 1836b2d1c1..0000000000
--- a/roles/jd-client/src/args.rs
+++ /dev/null
@@ -1,43 +0,0 @@
-use clap::Parser;
-use ext_config::{Config, File, FileFormat};
-use jd_client_sv2::{config::JobDeclaratorClientConfig, error::JDCError};
-
-use std::path::PathBuf;
-use tracing::error;
-#[derive(Debug, Parser)]
-#[command(author, version, about = "JD Client", long_about = None)]
-pub struct Args {
- #[arg(
- short = 'c',
- long = "config",
- help = "Path to the TOML configuration file",
- default_value = "jdc-config.toml"
- )]
- pub config_path: PathBuf,
- #[arg(
- short = 'f',
- long = "log-file",
- help = "Path to the log file. If not set, logs will only be written to stdout."
- )]
- pub log_file: Option,
-}
-
-#[allow(clippy::result_large_err)]
-pub fn process_cli_args() -> Result {
- let args = Args::parse();
-
- let config_path = args.config_path.to_str().ok_or_else(|| {
- error!("Invalid configuration path.");
- JDCError::BadCliArgs
- })?;
-
- let settings = Config::builder()
- .add_source(File::new(config_path, FileFormat::Toml))
- .build()?;
-
- let mut config = settings.try_deserialize::()?;
-
- config.set_log_file(args.log_file);
-
- Ok(config)
-}
diff --git a/roles/jd-client/src/lib/channel_manager/downstream_message_handler.rs b/roles/jd-client/src/lib/channel_manager/downstream_message_handler.rs
deleted file mode 100644
index 1075485d3d..0000000000
--- a/roles/jd-client/src/lib/channel_manager/downstream_message_handler.rs
+++ /dev/null
@@ -1,1317 +0,0 @@
-use std::sync::atomic::Ordering;
-
-use stratum_apps::stratum_core::{
- binary_sv2::Str0255,
- bitcoin::{Amount, Target},
- channels_sv2::{
- client,
- outputs::deserialize_outputs,
- server::{
- error::{ExtendedChannelError, StandardChannelError},
- extended::ExtendedChannel,
- group::GroupChannel,
- jobs::job_store::DefaultJobStore,
- share_accounting::{ShareValidationError, ShareValidationResult},
- standard::StandardChannel,
- },
- Vardiff, VardiffState,
- },
- handlers_sv2::{HandleMiningMessagesFromClientAsync, SupportedChannelTypes},
- job_declaration_sv2::PushSolution,
- mining_sv2::*,
- parsers_sv2::{AnyMessage, JobDeclaration, Mining, TemplateDistribution},
- template_distribution_sv2::SubmitSolution,
-};
-use tracing::{debug, error, info, warn};
-
-use crate::{
- channel_manager::{ChannelManager, ChannelManagerChannel},
- error::{ChannelSv2Error, JDCError},
- jd_mode::{get_jd_mode, JdMode},
- utils::StdFrame,
-};
-
-/// `RouteMessageTo` is an abstraction used to route protocol messages
-/// to the appropriate subsystem connected to the JDC.
-///
-/// Instead of manually handling routing logic for each message type,
-/// this enum provides a unified interface. Each variant represents
-/// a possible destination:
-///
-/// - [`RouteMessageTo::Upstream`] β For messages intended for the upstream.
-/// - [`RouteMessageTo::JobDeclarator`] β For job declaration messages sent to the JDS.
-/// - [`RouteMessageTo::TemplateProvider`] β For template distribution messages sent to the template
-/// provider.
-/// - [`RouteMessageTo::Downstream`] β For messages destined to a specific downstream client,
-/// identified by its `u32` downstream ID.
-#[derive(Clone)]
-pub enum RouteMessageTo<'a> {
- /// Route to the upstream (mining) channel.
- Upstream(Mining<'a>),
- /// Route to the job declarator subsystem.
- JobDeclarator(JobDeclaration<'a>),
- /// Route to the template provider subsystem.
- TemplateProvider(TemplateDistribution<'a>),
- /// Route to a specific downstream client by ID, along with its mining message.
- Downstream((u32, Mining<'a>)),
-}
-
-impl<'a> From> for RouteMessageTo<'a> {
- fn from(value: Mining<'a>) -> Self {
- Self::Upstream(value)
- }
-}
-
-impl<'a> From> for RouteMessageTo<'a> {
- fn from(value: JobDeclaration<'a>) -> Self {
- Self::JobDeclarator(value)
- }
-}
-
-impl<'a> From> for RouteMessageTo<'a> {
- fn from(value: TemplateDistribution<'a>) -> Self {
- Self::TemplateProvider(value)
- }
-}
-
-impl<'a> From<(u32, Mining<'a>)> for RouteMessageTo<'a> {
- fn from(value: (u32, Mining<'a>)) -> Self {
- Self::Downstream(value)
- }
-}
-
-impl RouteMessageTo<'_> {
- /// Forwards the message to its corresponding destination channel.
- ///
- /// The routing is handled as follows:
- /// - [`RouteMessageTo::Downstream`] β Sends the mining message to the specified downstream
- /// client.
- /// - [`RouteMessageTo::Upstream`] β Sends the mining message upstream, unless in
- /// [`JdMode::SoloMining`].
- /// - [`RouteMessageTo::JobDeclarator`] β Sends the job declaration message to the JDS.
- /// - [`RouteMessageTo::TemplateProvider`] β Sends the template distribution message to the
- /// template provider.
- ///
- /// Messages are automatically converted into the appropriate
- /// [`AnyMessage`] variant and wrapped into a [`StdFrame`].
- pub async fn forward(self, channel_manager_channel: &ChannelManagerChannel) {
- match self {
- RouteMessageTo::Downstream((downstream_id, message)) => {
- _ = channel_manager_channel
- .downstream_sender
- .send((downstream_id, AnyMessage::Mining(message).into_static()));
- }
- RouteMessageTo::Upstream(message) => {
- if get_jd_mode() != JdMode::SoloMining {
- let message = AnyMessage::Mining(message).into_static();
- let frame: StdFrame = message.try_into().unwrap();
- _ = channel_manager_channel.upstream_sender.send(frame).await;
- }
- }
- RouteMessageTo::JobDeclarator(message) => {
- let message = AnyMessage::JobDeclaration(message).into_static();
- let frame: StdFrame = message.try_into().unwrap();
- _ = channel_manager_channel.jd_sender.send(frame).await;
- }
- RouteMessageTo::TemplateProvider(message) => {
- let message = AnyMessage::TemplateDistribution(message).into_static();
- let frame: StdFrame = message.try_into().unwrap();
- _ = channel_manager_channel.tp_sender.send(frame).await;
- }
- }
- }
-}
-
-impl HandleMiningMessagesFromClientAsync for ChannelManager {
- type Error = JDCError;
-
- fn get_channel_type_for_client(&self, _client_id: Option) -> SupportedChannelTypes {
- SupportedChannelTypes::GroupAndExtended
- }
- fn is_work_selection_enabled_for_client(&self, _client_id: Option) -> bool {
- false
- }
- fn is_client_authorized(
- &self,
- _client_id: Option,
- _user_identity: &Str0255,
- ) -> Result {
- Ok(true)
- }
-
- // Handles a `CloseChannel` message:
- // - Look up the downstream associated with the given `channel_id`.
- // - If found, remove the channel from its `extended_channels` and `standard_channels`.
- // - If not found, return an appropriate error.
- async fn handle_close_channel(
- &mut self,
- _client_id: Option,
- msg: CloseChannel<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
- self.channel_manager_data
- .super_safe_lock(|channel_manager_data| {
- let Some(downstream_id) = channel_manager_data
- .channel_id_to_downstream_id
- .get(&msg.channel_id)
- else {
- error!(
- "No downstream_id related to channel_id: {:?}, found",
- msg.channel_id
- );
- return Err(JDCError::DownstreamNotFoundWithChannelId(msg.channel_id));
- };
- let Some(downstream) = channel_manager_data.downstream.get(downstream_id) else {
- error!(
- "No downstream with channel_id: {:?} and downstream_id: {:?}, found",
- msg.channel_id, downstream_id
- );
- return Err(JDCError::DownstreamNotFound(*downstream_id));
- };
- downstream.downstream_data.super_safe_lock(|data| {
- data.extended_channels.remove(&msg.channel_id);
- data.standard_channels.remove(&msg.channel_id);
- });
- Ok(())
- })
- }
-
- // Handles an `OpenStandardMiningChannel` message from a downstream.
- //
- // Steps:
- // 1. Parse the `downstream_id` from the `user_identity`.
- // 2. Create a new `StandardChannel` for the downstream.
- // 3. Ensure a valid `GroupChannel` exists (create one if needed).
- // 4. Apply the latest future template and prevhash to both group and standard channels.
- // 5. Send the following messages back to the downstream:
- // - `OpenStandardMiningChannelSuccess`
- // - `NewMiningJob`
- // - `SetNewPrevHash`
- // 6. Update the downstream state, including:
- // - Channel manager mappings
- // - Standard and group channel registrations
- // - Vardiff state
- //
- // Returns an error if any step fails, such as missing templates, invalid identity,
- // or failure to apply updates to channels.
- async fn handle_open_standard_mining_channel(
- &mut self,
- _client_id: Option,
- msg: OpenStandardMiningChannel<'_>,
- ) -> Result<(), Self::Error> {
- let request_id = msg.get_request_id_as_u32();
- let user_string = msg.user_identity.as_utf8_or_hex();
-
- let coinbase_outputs = self
- .channel_manager_data
- .super_safe_lock(|data| data.coinbase_outputs.clone());
-
- let mut coinbase_outputs = deserialize_outputs(coinbase_outputs)
- .map_err(|_| JDCError::ChannelManagerHasBadCoinbaseOutputs)?;
-
- let (user_identity, downstream_id) = match user_string.rsplit_once('#') {
- Some((user_identity, id)) => match id.parse::() {
- Ok(id) => (user_identity, id),
- Err(e) => {
- warn!(
- ?e,
- user_string, "Failed to parse downstream_id from user_identity"
- );
- return Err(JDCError::ParseInt(e));
- }
- },
- None => {
- warn!(user_string, "User identity missing downstream_id");
- return Err(JDCError::DownstreamIdNotFound);
- }
- };
-
- info!(downstream_id, "Received: {}", msg);
-
- let build_error = |code: &str| {
- Mining::OpenMiningChannelError(OpenMiningChannelError {
- request_id,
- error_code: code.to_string().try_into().expect("valid error code"),
- })
- };
-
- let messages: Vec =
- self.channel_manager_data
- .super_safe_lock(|channel_manager_data| {
- let Some(last_future_template) =
- channel_manager_data.last_future_template.clone()
- else {
- error!("Missing last_future_template, cannot open channel");
- return Err(JDCError::FutureTemplateNotPresent);
- };
-
- let Some(last_new_prev_hash) = channel_manager_data.last_new_prev_hash.clone()
- else {
- error!("Missing last_new_prev_hash, cannot open channel");
- return Err(JDCError::LastNewPrevhashNotFound);
- };
-
- let Some(downstream) = channel_manager_data.downstream.get(&downstream_id)
- else {
- error!(downstream_id, "Downstream not registered");
- return Err(JDCError::DownstreamNotFound(downstream_id));
- };
-
- coinbase_outputs[0].value =
- Amount::from_sat(last_future_template.coinbase_tx_value_remaining);
-
- downstream.downstream_data.super_safe_lock(|data| {
- let mut messages: Vec = vec![];
-
- if !data.require_std_job && data.group_channels.is_none() {
- let group_channel_id = channel_manager_data
- .channel_id_factory
- .fetch_add(1, Ordering::Relaxed);
- let job_store = DefaultJobStore::new();
- let full_extranonce_size = channel_manager_data
- .upstream_channel
- .as_ref()
- .map(|channel| channel.get_full_extranonce_size())
- .unwrap_or(32);
- let mut group_channel =
- match GroupChannel::new_for_job_declaration_client(
- group_channel_id,
- job_store,
- full_extranonce_size,
- channel_manager_data.pool_tag_string.clone(),
- self.miner_tag_string.clone(),
- ) {
- Ok(channel) => channel,
- Err(e) => {
- error!(?e, "Failed to create group channel");
- return Err(JDCError::FailedToCreateGroupChannel(e));
- }
- };
-
- if let Err(e) = group_channel.on_new_template(
- last_future_template.clone(),
- coinbase_outputs.clone(),
- ) {
- error!(?e, "Failed to apply template to group channel");
- return Err(JDCError::ChannelSv2(
- ChannelSv2Error::GroupChannelServerSide(e),
- ));
- }
-
- if let Err(e) =
- group_channel.on_set_new_prev_hash(last_new_prev_hash.clone())
- {
- error!(?e, "Failed to apply prevhash to group channel");
- return Err(JDCError::ChannelSv2(
- ChannelSv2Error::GroupChannelServerSide(e),
- ));
- };
-
- data.group_channels = Some(group_channel);
- }
-
- let nominal_hash_rate = msg.nominal_hash_rate;
- let requested_max_target = Target::from_le_bytes(
- msg.max_target.inner_as_ref().try_into().unwrap(),
- );
-
- let group_channel_id = data
- .group_channels
- .as_ref()
- .map(|gc| gc.get_group_channel_id())
- .unwrap_or(0);
- let standard_channel_id = channel_manager_data
- .channel_id_factory
- .fetch_add(1, Ordering::Relaxed);
-
- let extranonce_prefix = match channel_manager_data
- .extranonce_prefix_factory_standard
- .next_prefix_standard()
- {
- Ok(p) => p,
- Err(e) => {
- error!(?e, "Failed to get extranonce prefix");
- return Err(JDCError::ExtranoncePrefixFactoryError(e));
- }
- };
-
- let job_store = DefaultJobStore::new();
- let mut standard_channel =
- match StandardChannel::new_for_job_declaration_client(
- standard_channel_id,
- user_identity.to_string(),
- extranonce_prefix.to_vec(),
- requested_max_target,
- nominal_hash_rate,
- self.share_batch_size,
- self.shares_per_minute,
- job_store,
- channel_manager_data.pool_tag_string.clone(),
- self.miner_tag_string.clone(),
- ) {
- Ok(channel) => channel,
- Err(e) => {
- error!(?e, "Failed to create standard channel");
- return match e {
- StandardChannelError::InvalidNominalHashrate => Ok(vec![(
- downstream_id,
- build_error("invalid-nominal-hashrate"),
- )
- .into()]),
- StandardChannelError::RequestedMaxTargetOutOfRange => {
- Ok(vec![(
- downstream_id,
- build_error("max-target-out-of-range"),
- )
- .into()])
- }
- other => Err(JDCError::ChannelSv2(
- ChannelSv2Error::StandardChannelServerSide(other),
- )),
- };
- }
- };
-
- let open_standard_mining_channel_success =
- OpenStandardMiningChannelSuccess {
- request_id: msg.request_id.clone(),
- channel_id: standard_channel_id,
- target: standard_channel.get_target().to_le_bytes().into(),
- extranonce_prefix: standard_channel
- .get_extranonce_prefix()
- .clone()
- .try_into()
- .expect("extranonce_prefix must be valid"),
- group_channel_id,
- }
- .into_static();
-
- messages.push(
- (
- downstream_id,
- Mining::OpenStandardMiningChannelSuccess(
- open_standard_mining_channel_success,
- ),
- )
- .into(),
- );
-
- if let Err(e) = standard_channel
- .on_new_template(last_future_template.clone(), coinbase_outputs.clone())
- {
- error!(?e, "Failed to apply template to standard channel");
- return Err(JDCError::ChannelSv2(
- ChannelSv2Error::StandardChannelServerSide(e),
- ));
- }
-
- let future_standard_job_id = standard_channel
- .get_future_template_to_job_id()
- .get(&last_future_template.template_id)
- .cloned()
- .expect("future job id must exist");
-
- let future_standard_job = standard_channel
- .get_future_jobs()
- .get(&future_standard_job_id)
- .expect("future job must exist");
-
- let future_standard_job_message =
- future_standard_job.get_job_message().clone().into_static();
-
- messages.push(
- (
- downstream_id,
- Mining::NewMiningJob(future_standard_job_message),
- )
- .into(),
- );
-
- let prev_hash = last_new_prev_hash.prev_hash.clone();
- let header_timestamp = last_new_prev_hash.header_timestamp;
- let n_bits = last_new_prev_hash.n_bits;
- let set_new_prev_hash_mining = SetNewPrevHash {
- channel_id: standard_channel_id,
- job_id: future_standard_job_id,
- prev_hash,
- min_ntime: header_timestamp,
- nbits: n_bits,
- };
-
- if let Err(e) =
- standard_channel.on_set_new_prev_hash(last_new_prev_hash.clone())
- {
- error!(?e, "Failed to apply prevhash to standard channel");
- return Err(JDCError::ChannelSv2(
- ChannelSv2Error::StandardChannelServerSide(e),
- ));
- }
- messages.push(
- (
- downstream_id,
- Mining::SetNewPrevHash(set_new_prev_hash_mining),
- )
- .into(),
- );
-
- let vardiff =
- VardiffState::new().expect("Vardiff state should instantiate.");
-
- channel_manager_data
- .vardiff
- .insert((standard_channel_id, downstream_id), vardiff);
- data.standard_channels
- .insert(standard_channel_id, standard_channel);
- channel_manager_data
- .channel_id_to_downstream_id
- .insert(standard_channel_id, downstream_id);
-
- channel_manager_data
- .downstream_channel_id_and_job_id_to_template_id
- .insert(
- (standard_channel_id, future_standard_job_id),
- last_future_template.template_id,
- );
- if let Some(group_channel) = data.group_channels.as_mut() {
- group_channel.add_standard_channel_id(standard_channel_id);
- }
-
- Ok(messages)
- })
- })?;
-
- for messages in messages {
- messages.forward(&self.channel_manager_channel).await;
- }
- Ok(())
- }
-
- // Handles an `OpenExtendedMiningChannel` request from a downstream.
- //
- // Workflow:
- // 1. Extract the `downstream_id` from `user_identity`.
- // 2. Create a new `ExtendedChannel` with the requested parameters.
- // 3. Send back to the downstream:
- // - `OpenExtendedMiningChannelSuccess`
- // - `NewExtendedMiningJob` (based on the latest future template)
- // - `SetNewPrevHash` (to immediately activate the job)
- // 4. Update internal state, including:
- // - Extended channel registry
- // - Downstream/channel mappings
- // - Vardiff state
- //
- // Returns an error if the downstream is missing, template/prevhash are unavailable,
- // or if extended channel creation fails.
- async fn handle_open_extended_mining_channel(
- &mut self,
- _client_id: Option,
- msg: OpenExtendedMiningChannel<'_>,
- ) -> Result<(), Self::Error> {
- let user_string = msg.user_identity.as_utf8_or_hex();
- let (user_identity, downstream_id) = match user_string.rsplit_once('#') {
- Some((user_identity, id)) => match id.parse::() {
- Ok(v) => (user_identity, v),
- Err(e) => {
- warn!(?e, user_string, "Invalid downstream_id in user_identity");
- return Err(JDCError::ParseInt(e));
- }
- },
- None => {
- warn!(user_string, "Missing downstream_id in user_identity");
- return Err(JDCError::DownstreamIdNotFound);
- }
- };
-
- info!(downstream_id, "Received: {}", msg);
- let request_id = msg.get_request_id_as_u32();
-
- let nominal_hash_rate = msg.nominal_hash_rate;
- let requested_max_target =
- Target::from_le_bytes(msg.max_target.inner_as_ref().try_into().unwrap());
- let requested_min_rollable_extranonce_size = msg.min_extranonce_size;
-
- let build_error = |code: &str| {
- Mining::OpenMiningChannelError(OpenMiningChannelError {
- request_id,
- error_code: code.to_string().try_into().expect("valid error code"),
- })
- };
-
- let messages =
- self.channel_manager_data
- .super_safe_lock(|channel_manager_data| {
-
- let Some(downstream) = channel_manager_data.downstream.get_mut(&downstream_id) else {
- error!(downstream_id, "Downstream not found");
- return Err(JDCError::DownstreamNotFound(downstream_id));
- };
-
- downstream.downstream_data.super_safe_lock(|data| {
-
- let mut messages: Vec = vec![];
- let extended_channel_id = channel_manager_data.channel_id_factory.fetch_add(1, Ordering::Relaxed);
-
- let extranonce_prefix = match channel_manager_data.extranonce_prefix_factory_extended
- .next_prefix_extended(requested_min_rollable_extranonce_size.into())
- {
- Ok(p) => p,
- Err(e) => {
- error!(?e, "Extranonce prefix error");
- return Err(JDCError::ExtranoncePrefixFactoryError(e));
- }
- };
-
- let Some(last_future_template) = channel_manager_data.last_future_template.clone() else {
- error!("No template to share");
- return Err(JDCError::FutureTemplateNotPresent);
- };
-
- let Some(last_new_prev_hash) = channel_manager_data.last_new_prev_hash.clone() else {
- error!("No prevhash in system");
- return Err(JDCError::LastNewPrevhashNotFound);
- };
-
- let job_store = DefaultJobStore::new();
-
- let mut extended_channel = match ExtendedChannel::new_for_job_declaration_client(
- extended_channel_id,
- user_identity.to_string(),
- extranonce_prefix.into(),
- requested_max_target,
- nominal_hash_rate,
- true,
- requested_min_rollable_extranonce_size,
- self.share_batch_size,
- self.shares_per_minute,
- job_store,
- channel_manager_data.pool_tag_string.clone(),
- self.miner_tag_string.clone(),
- ) {
- Ok(c) => c,
- Err(e) => {
- error!(?e, "Failed to create ExtendedChannel");
- return match e {
- ExtendedChannelError::InvalidNominalHashrate => {
- Ok(vec![(downstream_id, build_error("invalid-nominal-hashrate")).into()])
- }
- ExtendedChannelError::RequestedMaxTargetOutOfRange => {
- Ok(vec![(downstream_id, build_error("max-target-out-of-range")).into()])
- }
- ExtendedChannelError::RequestedMinExtranonceSizeTooLarge => {
- Ok(vec![(downstream_id, build_error("min-extranonce-size-too-large")).into()])
- }
- other => Err(
- JDCError::ChannelSv2(
- ChannelSv2Error::ExtendedChannelServerSide(other)
- )
- ),
- }
- }
- };
-
- let open_extended_mining_channel_success =
- OpenExtendedMiningChannelSuccess {
- request_id,
- channel_id: extended_channel_id,
- target: extended_channel.get_target().to_le_bytes().into(),
- extranonce_prefix: extended_channel
- .get_extranonce_prefix()
- .clone()
- .try_into()
- .expect("valid extranonce prefix"),
- extranonce_size: extended_channel.get_rollable_extranonce_size(),
- }
- .into_static();
-
- messages.push((
- downstream_id,
- Mining::OpenExtendedMiningChannelSuccess(
- open_extended_mining_channel_success,
- ),
- ).into());
-
- let mut coinbase_outputs = match deserialize_outputs(channel_manager_data.coinbase_outputs.clone()) {
- Ok(outputs) => outputs,
- Err(_) => return Err(JDCError::ChannelManagerHasBadCoinbaseOutputs),
- };
- coinbase_outputs[0].value =
- Amount::from_sat(last_future_template.coinbase_tx_value_remaining);
-
-
- // create a future extended job based on the last future template
- if let Err(e) =
- extended_channel.on_new_template(last_future_template.clone(), coinbase_outputs)
- {
- error!(?e, "Failed to apply template to extended channel");
- return Err(JDCError::ChannelSv2(ChannelSv2Error::ExtendedChannelServerSide(e)));
- }
-
- let future_extended_job_id = extended_channel
- .get_future_template_to_job_id()
- .get(&last_future_template.template_id)
- .cloned()
- .expect("future job id must exist");
- let future_extended_job = extended_channel
- .get_future_jobs()
- .get(&future_extended_job_id)
- .expect("future job must exist");
-
- let future_extended_job_message =
- future_extended_job.get_job_message().clone().into_static();
-
- // send this future job as new job message
- // to be immediately activated with the subsequent SetNewPrevHash message
- messages.push((
- downstream_id,
- Mining::NewExtendedMiningJob(
- future_extended_job_message,
- ),
- ).into());
-
- // SetNewPrevHash message activates the future job
- let prev_hash = last_new_prev_hash.prev_hash.clone();
- let header_timestamp = last_new_prev_hash.header_timestamp;
- let n_bits = last_new_prev_hash.n_bits;
- let set_new_prev_hash_mining = SetNewPrevHash {
- channel_id: extended_channel_id,
- job_id: future_extended_job_id,
- prev_hash,
- min_ntime: header_timestamp,
- nbits: n_bits,
- };
- if let Err(e) = extended_channel.on_set_new_prev_hash(last_new_prev_hash) {
- error!(?e, "Failed to set prevhash on extended channel");
- return Err(JDCError::ChannelSv2(ChannelSv2Error::ExtendedChannelServerSide(e)));
- }
- messages.push((
- downstream_id,
- Mining::SetNewPrevHash(set_new_prev_hash_mining),
- ).into());
-
- let vardiff = VardiffState::new().expect("Vardiff should instantiate.");
- data.extended_channels.insert(extended_channel_id, extended_channel);
-
- channel_manager_data.downstream_channel_id_and_job_id_to_template_id.insert((extended_channel_id, future_extended_job_id), last_future_template.template_id);
- channel_manager_data
- .channel_id_to_downstream_id
- .insert(extended_channel_id, downstream_id);
- channel_manager_data.vardiff.insert((extended_channel_id, downstream_id), vardiff);
-
- Ok(messages)
- })
- })?;
-
- for messages in messages {
- messages.forward(&self.channel_manager_channel).await;
- }
-
- Ok(())
- }
-
- // Handles an `UpdateChannel` message from a downstream.
- //
- // Workflow:
- // 1. Update the target for the corresponding downstream channel (standard or extended).
- // - On success, reply with a `SetTarget`.
- // - On failure, return an `UpdateChannelError`.
- // 2. Recompute aggregate downstream state:
- // - Sum all downstream nominal hashrates.
- // - Determine the minimum target across all downstream channels.
- // 3. Propagate the update upstream by sending an `UpdateChannel` with the aggregated hashrate
- // and minimum target.
- //
- // Returns an error if the downstream channel is missing or update
- // validation fails.
- async fn handle_update_channel(
- &mut self,
- _client_id: Option,
- msg: UpdateChannel<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
- let channel_id = msg.channel_id;
- let new_nominal_hash_rate = msg.nominal_hash_rate;
- let requested_maximum_target =
- Target::from_le_bytes(msg.maximum_target.inner_as_ref().try_into().unwrap());
-
- let messages = self
- .channel_manager_data
- .super_safe_lock(|channel_manager_data| {
- let mut messages: Vec = vec![];
-
- let downstream_id = match channel_manager_data
- .channel_id_to_downstream_id
- .get(&channel_id)
- {
- Some(id) => *id,
- None => {
- error!(
- channel_id,
- "UpdateChannelError: invalid-channel-id (no downstream_id mapping)"
- );
- return Err(JDCError::DownstreamNotFoundWithChannelId(channel_id));
- }
- };
-
- if let Some(downstream) = channel_manager_data.downstream.get_mut(&downstream_id) {
- messages.extend_from_slice(&downstream.downstream_data.super_safe_lock(
- |data| {
- let mut messages: Vec = vec![];
-
- let build_error = |code: &str| {
- error!(channel_id, error_code = code, "UpdateChannelError");
- Mining::UpdateChannelError(UpdateChannelError {
- channel_id,
- error_code: code
- .to_string()
- .try_into()
- .expect("valid error code"),
- })
- };
-
- if let Some(standard_channel) =
- data.standard_channels.get_mut(&channel_id)
- {
- let update_channel = standard_channel.update_channel(
- new_nominal_hash_rate,
- Some(requested_maximum_target),
- );
- let new_target = standard_channel.get_target();
-
- if let Err(e) = update_channel {
- error!(channel_id, ?e, "StandardChannel update failed");
-
- let err_code = match e {
- StandardChannelError::InvalidNominalHashrate => {
- "invalid-nominal-hashrate"
- }
- StandardChannelError::RequestedMaxTargetOutOfRange => {
- "requested-max-target-out-of-range"
- }
- _ => "internal-error",
- };
- if err_code == "internal-error" {
- warn!("Failed to update extended channel {channel_id}");
- } else {
- return vec![(downstream_id, build_error(err_code)).into()];
- }
- }
-
- messages.push(
- (
- downstream_id,
- Mining::SetTarget(SetTarget {
- channel_id,
- maximum_target: new_target.to_le_bytes().into(),
- }),
- )
- .into(),
- );
- } else if let Some(extended_channel) =
- data.extended_channels.get_mut(&channel_id)
- {
- let update_channel = extended_channel.update_channel(
- new_nominal_hash_rate,
- Some(requested_maximum_target),
- );
- let new_target = extended_channel.get_target();
-
- if let Err(e) = update_channel {
- error!(channel_id, ?e, "StandardChannel update failed");
- let err_code = match e {
- ExtendedChannelError::InvalidNominalHashrate => {
- "invalid-nominal-hashrate"
- }
- ExtendedChannelError::RequestedMaxTargetOutOfRange => {
- "requested-max-target-out-of-range"
- }
- _ => "internal-error",
- };
- if err_code == "internal-error" {
- warn!("Failed to update extended channel {channel_id}");
- } else {
- return vec![(downstream_id, build_error(err_code)).into()];
- }
- }
-
- messages.push(
- (
- downstream_id,
- Mining::SetTarget(SetTarget {
- channel_id,
- maximum_target: new_target.to_le_bytes().into(),
- }),
- )
- .into(),
- );
- } else {
- error!("UpdateChannelError: invalid-channel-id");
- return vec![
- (downstream_id, build_error("invalid-channel-id")).into()
- ];
- }
-
- messages
- },
- ));
- }
-
- let mut downstream_hashrate = 0.0;
- let mut min_target = Target::from_le_bytes([0xff; 32]);
-
- for (_, downstream) in channel_manager_data.downstream.iter() {
- downstream.downstream_data.super_safe_lock(|data| {
- let mut update_from_channel = |hashrate: f32, target: &Target| {
- downstream_hashrate += hashrate;
- min_target = std::cmp::min(*target, min_target);
- };
-
- for (_, channel) in data.standard_channels.iter() {
- update_from_channel(
- channel.get_nominal_hashrate(),
- channel.get_target(),
- );
- }
-
- for (_, channel) in data.extended_channels.iter() {
- update_from_channel(
- channel.get_nominal_hashrate(),
- channel.get_target(),
- );
- }
- });
- }
-
- if let Some(ref upstream_channel) = channel_manager_data.upstream_channel {
- debug!(
- "Checking upstream channel {} with hashrate {} and target {:?}",
- upstream_channel.get_channel_id(),
- upstream_channel.get_nominal_hashrate(),
- upstream_channel.get_target()
- );
-
- info!("Sending update channel message upstream");
- messages.push(
- Mining::UpdateChannel(UpdateChannel {
- channel_id: upstream_channel.get_channel_id(),
- nominal_hash_rate: downstream_hashrate,
- maximum_target: min_target.to_le_bytes().into(),
- })
- .into(),
- )
- }
-
- Ok(messages)
- })?;
-
- for messages in messages {
- messages.forward(&self.channel_manager_channel).await;
- }
-
- Ok(())
- }
-
- // Handles a `SubmitSharesStandard` message from a downstream.
- //
- // Steps:
- // 1. Validate the share against the downstream channel.
- // - On error, respond with `SubmitSharesError`.
- // - On success, acknowledge with `SubmitSharesSuccess` (and optionally a block found).
- //
- // 2. If the share is valid, attempt to forward it upstream:
- // - Translate the share into an upstream `SubmitSharesExtended`.
- // - Validate with the upstream channel.
- // - Forward valid shares (or block solutions) upstream.
- async fn handle_submit_shares_standard(
- &mut self,
- _client_id: Option,
- msg: SubmitSharesStandard,
- ) -> Result<(), Self::Error> {
- info!("Received SubmitSharesStandard");
- let channel_id = msg.channel_id;
- let job_id = msg.job_id;
-
- let build_error = |code: &str| {
- Mining::SubmitSharesError(SubmitSharesError {
- channel_id,
- sequence_number: msg.sequence_number,
- error_code: code.to_string().try_into().expect("valid error code"),
- })
- };
-
- let messages = self.channel_manager_data.super_safe_lock(|channel_manager_data| {
- let Some(downstream_id) = channel_manager_data.channel_id_to_downstream_id.get(&channel_id) else {
- warn!("No downstream_id found for channel_id={channel_id}");
- return Err(JDCError::DownstreamNotFoundWithChannelId(channel_id))
- };
- let Some(downstream) = channel_manager_data.downstream.get_mut(downstream_id) else {
- warn!("No downstream found for downstream_id={downstream_id}");
- return Err(JDCError::DownstreamNotFound(*downstream_id));
- };
- let Some(prev_hash) = channel_manager_data.last_new_prev_hash.as_ref() else {
- warn!("No prev_hash available yet, ignoring share");
- return Err(JDCError::LastNewPrevhashNotFound);
- };
-
- downstream.downstream_data.super_safe_lock(|data| {
- let mut messages: Vec = vec![];
-
- let Some(standard_channel) = data.standard_channels.get_mut(&channel_id) else {
- error!("SubmitSharesError: channel_id: {channel_id}, sequence_number: {}, error_code: invalid-channel-id", msg.sequence_number);
- return Ok(vec![(*downstream_id, build_error("invalid-channel-id")).into()]);
- };
-
- let Some(vardiff) = channel_manager_data.vardiff.get_mut(&(channel_id, *downstream_id)) else {
- return Err(JDCError::VardiffNotFound(channel_id));
- };
- vardiff.increment_shares_since_last_update();
- let res = standard_channel.validate_share(msg.clone());
- let mut is_downstream_share_valid = false;
- match res {
- Ok(ShareValidationResult::Valid(share_hash)) => {
- let share_accounting = standard_channel.get_share_accounting();
- if share_accounting.should_acknowledge() {
- let success = SubmitSharesSuccess {
- channel_id,
- last_sequence_number: share_accounting.get_last_share_sequence_number(),
- new_submits_accepted_count: share_accounting.get_last_batch_accepted(),
- new_shares_sum: share_accounting.get_last_batch_work_sum() as u64,
- };
- info!("SubmitSharesStandard on downstream channel: {} β
", success);
- messages.push((downstream.downstream_id, Mining::SubmitSharesSuccess(success)).into());
- } else {
- info!(
- "SubmitSharesStandard on downstream channel: valid share | channel_id: {}, sequence_number: {}, share_hash: {} βοΈ",
- channel_id, msg.sequence_number, share_hash
- );
- }
- is_downstream_share_valid = true;
- }
- Ok(ShareValidationResult::BlockFound(share_hash, template_id, coinbase)) => {
- info!("SubmitSharesStandard on downstream channel: π° Block Found!!! π°{share_hash}");
- is_downstream_share_valid = true;
- if let Some(template_id) = template_id {
- info!("SubmitSharesStandard: Propagating solution to the Template Provider.");
- let solution = SubmitSolution {
- template_id,
- version: msg.version,
- header_timestamp: msg.ntime,
- header_nonce: msg.nonce,
- coinbase_tx: coinbase.try_into()?,
- };
-
- messages.push(TemplateDistribution::SubmitSolution(solution.clone()).into());
- }
- let share_accounting = standard_channel.get_share_accounting().clone();
- let success = SubmitSharesSuccess {
- channel_id,
- last_sequence_number: share_accounting.get_last_share_sequence_number(),
- new_submits_accepted_count: share_accounting.get_last_batch_accepted(),
- new_shares_sum: share_accounting.get_last_batch_work_sum() as u64,
- };
- messages.push((
- downstream.downstream_id,
- Mining::SubmitSharesSuccess(success),
- ).into());
- }
- Err(err) => {
- let code = match err {
- ShareValidationError::Invalid => "invalid-share",
- ShareValidationError::Stale => "stale-share",
- ShareValidationError::InvalidJobId => "invalid-job-id",
- ShareValidationError::DoesNotMeetTarget => "difficulty-too-low",
- ShareValidationError::DuplicateShare => "duplicate-share",
- _ => unreachable!(),
- };
- error!("β SubmitSharesError: ch={}, seq={}, error={code}", channel_id, msg.sequence_number);
- messages.push((*downstream_id, build_error(code)).into());
- }
- }
-
- if !is_downstream_share_valid {
- return Ok(messages);
- }
-
- if let Some(upstream_channel) = channel_manager_data.upstream_channel.as_mut() {
- let prefix = standard_channel.get_extranonce_prefix().clone();
- let mut extranonce_parts = Vec::new();
- let up_prefix = upstream_channel.get_extranonce_prefix();
- extranonce_parts.extend_from_slice(&prefix[up_prefix.len()..]);
-
- let upstream_message = channel_manager_data
- .downstream_channel_id_and_job_id_to_template_id
- .get(&(channel_id, job_id))
- .and_then(|tid| channel_manager_data.template_id_to_upstream_job_id.get(tid))
- .map(|&upstream_job_id| {
- SubmitSharesExtended {
- channel_id: upstream_channel.get_channel_id(),
- job_id: upstream_job_id as u32,
- extranonce: extranonce_parts.try_into().unwrap(),
- nonce: msg.nonce,
- ntime: msg.ntime,
- // We assign sequence number later, when we validate the share
- // and send it to upstream.
- sequence_number: 0,
- version: msg.version,
- }
- });
-
- if let Some(mut upstream_message) = upstream_message {
- let res = upstream_channel.validate_share(upstream_message.clone());
- match res {
- Ok(client::share_accounting::ShareValidationResult::Valid(share_hash)) => {
- upstream_message.sequence_number = channel_manager_data.sequence_number_factory.fetch_add(1, Ordering::Relaxed);
- info!(
- "SubmitSharesStandard, forwarding it to upstream: valid share | channel_id: {}, sequence_number: {}, share_hash: {} β
",
- channel_id, upstream_message.sequence_number, share_hash
- );
- messages.push(Mining::SubmitSharesExtended(upstream_message).into());
- }
- Ok(client::share_accounting::ShareValidationResult::BlockFound(share_hash)) => {
- upstream_message.sequence_number = channel_manager_data.sequence_number_factory.fetch_add(1, Ordering::Relaxed);
- info!("SubmitSharesStandard forwarding it to upstream: π° Block Found!!! π°{share_hash}");
- let push_solution = PushSolution {
- extranonce: standard_channel.get_extranonce_prefix().to_vec().try_into()?,
- ntime: upstream_message.ntime,
- nonce: upstream_message.nonce,
- version: upstream_message.version,
- nbits: prev_hash.n_bits,
- prev_hash: prev_hash.prev_hash.clone(),
- };
- messages.push(JobDeclaration::PushSolution(push_solution).into());
- messages.push(Mining::SubmitSharesExtended(upstream_message).into());
- }
- Err(err) => {
- let code = match err {
- client::share_accounting::ShareValidationError::Invalid => "invalid-share",
- client::share_accounting::ShareValidationError::Stale => "stale-share",
- client::share_accounting::ShareValidationError::InvalidJobId => "invalid-job-id",
- client::share_accounting::ShareValidationError::DoesNotMeetTarget => "difficulty-too-low",
- client::share_accounting::ShareValidationError::DuplicateShare => "duplicate-share",
- _ => unreachable!(),
- };
- debug!("β SubmitSharesError not forwarding it to upstream: ch={}, seq={}, error={code}", channel_id, upstream_message.sequence_number);
- }
- }
- }
- }
-
- Ok(messages)
- })
- })?;
-
- for messages in messages {
- messages.forward(&self.channel_manager_channel).await;
- }
-
- Ok(())
- }
-
- // Handles a `SubmitSharesExtended` message from a downstream.
- //
- // Steps:
- // 1. Validate the share against the downstream channel.
- // - On error, respond with `SubmitSharesError`.
- // - On success, acknowledge with `SubmitSharesSuccess` (and optionally a block found).
- //
- // 2. If the share is valid, attempt to forward it upstream:
- // - Translate the share into an upstream `SubmitSharesExtended`.
- // - Validate with the upstream channel.
- // - Forward valid shares (or block solutions) upstream.
- async fn handle_submit_shares_extended(
- &mut self,
- _client_id: Option,
- msg: SubmitSharesExtended<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received SubmitSharesExtended");
- let channel_id = msg.channel_id;
- let job_id = msg.job_id;
-
- let build_error = |code: &str| {
- Mining::SubmitSharesError(SubmitSharesError {
- channel_id,
- sequence_number: msg.sequence_number,
- error_code: code.to_string().try_into().expect("valid error code"),
- })
- };
-
- let messages = self.channel_manager_data.super_safe_lock(|channel_manager_data| {
- let Some(downstream_id) = channel_manager_data.channel_id_to_downstream_id.get(&channel_id) else {
- warn!("No downstream_id found for channel_id={channel_id}");
- return Err(JDCError::DownstreamNotFoundWithChannelId(channel_id));
- };
- let Some(downstream) = channel_manager_data.downstream.get_mut(downstream_id) else {
- warn!("No downstream found for downstream_id={downstream_id}");
- return Err(JDCError::DownstreamNotFound(*downstream_id));
- };
- let Some(prev_hash) = channel_manager_data.last_new_prev_hash.as_ref() else {
- warn!("No prev_hash available yet, ignoring share");
- return Err(JDCError::LastNewPrevhashNotFound);
- };
- downstream.downstream_data.super_safe_lock(|data| {
- let mut messages: Vec = vec![];
-
- let Some(extended_channel) = data.extended_channels.get_mut(&channel_id) else {
- error!("SubmitSharesError: channel_id: {channel_id}, sequence_number: {}, error_code: invalid-channel-id", msg.sequence_number);
- return Ok(vec![(*downstream_id, build_error("invalid-channel-id")).into()]);
- };
-
- let Some(vardiff) = channel_manager_data.vardiff.get_mut(&(channel_id, *downstream_id)) else {
- return Err(JDCError::VardiffNotFound(channel_id));
- };
- vardiff.increment_shares_since_last_update();
- let res = extended_channel.validate_share(msg.clone());
- let mut is_downstream_share_valid = false;
- match res {
- Ok(ShareValidationResult::Valid(share_hash)) => {
- let share_accounting = extended_channel.get_share_accounting();
- if share_accounting.should_acknowledge() {
- let success = SubmitSharesSuccess {
- channel_id,
- last_sequence_number: share_accounting.get_last_share_sequence_number(),
- new_submits_accepted_count: share_accounting.get_last_batch_accepted(),
- new_shares_sum: share_accounting.get_last_batch_work_sum() as u64,
- };
- info!("SubmitSharesExtended on downstream channel: {} β
", success);
- messages.push((downstream.downstream_id, Mining::SubmitSharesSuccess(success)).into());
- } else {
- info!(
- "SubmitSharesExtended on downstream channel: valid share | channel_id: {}, sequence_number: {}, share_hash: {} βοΈ",
- channel_id, msg.sequence_number, share_hash
- );
- }
- is_downstream_share_valid = true;
- }
- Ok(ShareValidationResult::BlockFound(share_hash, template_id, coinbase)) => {
- info!("SubmitSharesExtended on downstream channel: π° Block Found!!! π°{share_hash}");
- if let Some(template_id) = template_id {
- info!("SubmitSharesExtended: Propagating solution to the Template Provider.");
- let solution = SubmitSolution {
- template_id,
- version: msg.version,
- header_timestamp: msg.ntime,
- header_nonce: msg.nonce,
- coinbase_tx: coinbase.try_into()?,
- };
- messages.push(TemplateDistribution::SubmitSolution(solution.clone()).into());
- }
- let share_accounting = extended_channel.get_share_accounting().clone();
- let success = SubmitSharesSuccess {
- channel_id,
- last_sequence_number: share_accounting.get_last_share_sequence_number(),
- new_submits_accepted_count: share_accounting.get_last_batch_accepted(),
- new_shares_sum: share_accounting.get_last_batch_work_sum() as u64,
- };
- is_downstream_share_valid = true;
- messages.push((
- downstream.downstream_id,
- Mining::SubmitSharesSuccess(success),
- ).into());
- }
- Err(err) => {
- let code = match err {
- ShareValidationError::Invalid => "invalid-share",
- ShareValidationError::Stale => "stale-share",
- ShareValidationError::InvalidJobId => "invalid-job-id",
- ShareValidationError::DoesNotMeetTarget => "difficulty-too-low",
- ShareValidationError::DuplicateShare => "duplicate-share",
- _ => unreachable!(),
- };
- error!("β SubmitSharesError on downstream channel: ch={}, seq={}, error={code}", channel_id, msg.sequence_number);
- messages.push((*downstream_id, build_error(code)).into());
- }
- }
-
- if !is_downstream_share_valid{
- return Ok(messages);
- }
-
- if let Some(upstream_channel) = channel_manager_data.upstream_channel.as_mut() {
- let prefix = extended_channel.get_extranonce_prefix().clone();
- let mut extranonce_parts = Vec::new();
- let up_prefix = upstream_channel.get_extranonce_prefix();
- extranonce_parts.extend_from_slice(&prefix[up_prefix.len()..]);
-
- let upstream_message = channel_manager_data
- .downstream_channel_id_and_job_id_to_template_id
- .get(&(channel_id, job_id))
- .and_then(|tid| channel_manager_data.template_id_to_upstream_job_id.get(tid))
- .map(|&upstream_job_id| {
- let mut new_msg = msg.clone();
- new_msg.channel_id = upstream_channel.get_channel_id();
- new_msg.job_id = upstream_job_id as u32;
- // We assign sequence number later, when we validate the share
- // and send it to upstream.
- new_msg.sequence_number = 0;
-
- extranonce_parts.extend_from_slice(&msg.extranonce.to_vec());
- new_msg.extranonce = extranonce_parts.try_into().unwrap();
-
- new_msg
- });
- if let Some(mut upstream_message) = upstream_message{
- let res = upstream_channel.validate_share(upstream_message.clone());
- match res {
- Ok(client::share_accounting::ShareValidationResult::Valid(share_hash)) => {
- upstream_message.sequence_number = channel_manager_data.sequence_number_factory.fetch_add(1, Ordering::Relaxed);
- info!(
- "SubmitSharesExtended forwarding it to upstream: valid share | channel_id: {}, sequence_number: {}, share_hash: {} β
",
- channel_id, upstream_message.sequence_number, share_hash
- );
- messages.push(
- Mining::SubmitSharesExtended(upstream_message.into_static()).into(),
- );
- }
- Ok(client::share_accounting::ShareValidationResult::BlockFound(share_hash)) => {
- upstream_message.sequence_number = channel_manager_data.sequence_number_factory.fetch_add(1, Ordering::Relaxed);
- info!("SubmitSharesExtended forwarding it to upstream: π° Block Found!!! π°{share_hash}");
- let mut channel_extranonce = upstream_channel.get_extranonce_prefix().to_vec();
- channel_extranonce.extend_from_slice(&upstream_message.extranonce.to_vec());
- let push_solution = PushSolution {
- extranonce: channel_extranonce.try_into()?,
- ntime: upstream_message.ntime,
- nonce: upstream_message.nonce,
- version: upstream_message.version,
- nbits: prev_hash.n_bits,
- prev_hash: prev_hash.prev_hash.clone(),
- };
- messages.push(JobDeclaration::PushSolution(push_solution.clone()).into());
- messages.push(Mining::SubmitSharesExtended(upstream_message.into_static()).into());
- }
- Err(err) => {
- let code = match err {
- client::share_accounting::ShareValidationError::Invalid=>"invalid-share",
- client::share_accounting::ShareValidationError::Stale=>"stale-share",
- client::share_accounting::ShareValidationError::InvalidJobId=>"invalid-job-id",
- client::share_accounting::ShareValidationError::DoesNotMeetTarget=>"difficulty-too-low",
- client::share_accounting::ShareValidationError::DuplicateShare=>"duplicate-share",
- _ => unreachable!(),
- };
- debug!("β SubmitSharesError not forwarding it to upstream: ch={}, seq={}, error={code}", channel_id, upstream_message.sequence_number);
- }
- }
- }
- }
-
- Ok(messages)
- })
- })?;
-
- for messages in messages {
- messages.forward(&self.channel_manager_channel).await;
- }
-
- Ok(())
- }
-
- // Handles an incoming `SetCustomMiningJob` message from a downstream.
- async fn handle_set_custom_mining_job(
- &mut self,
- _client_id: Option,
- msg: SetCustomMiningJob<'_>,
- ) -> Result<(), Self::Error> {
- warn!("Received: {}", msg);
- Err(Self::Error::UnexpectedMessage(
- MESSAGE_TYPE_SET_CUSTOM_MINING_JOB,
- ))
- }
-}
diff --git a/roles/jd-client/src/lib/channel_manager/jd_message_handler.rs b/roles/jd-client/src/lib/channel_manager/jd_message_handler.rs
deleted file mode 100644
index 36460f10f8..0000000000
--- a/roles/jd-client/src/lib/channel_manager/jd_message_handler.rs
+++ /dev/null
@@ -1,294 +0,0 @@
-use stratum_apps::stratum_core::{
- binary_sv2::{self, Sv2DataType, B016M},
- bitcoin::{
- self, absolute::LockTime, transaction::Version, OutPoint, ScriptBuf, Sequence, Transaction,
- TxIn, TxOut, Witness,
- },
- channels_sv2::outputs::deserialize_outputs,
- handlers_sv2::HandleJobDeclarationMessagesFromServerAsync,
- job_declaration_sv2::{
- AllocateMiningJobTokenSuccess, DeclareMiningJobError, DeclareMiningJobSuccess,
- ProvideMissingTransactions, ProvideMissingTransactionsSuccess,
- },
- parsers_sv2::{AnyMessage, JobDeclaration, Mining, TemplateDistribution},
- template_distribution_sv2::CoinbaseOutputConstraints,
-};
-use tracing::{debug, error, info, warn};
-
-use crate::{
- channel_manager::ChannelManager,
- error::JDCError,
- status::{State, Status},
- utils::StdFrame,
-};
-
-impl HandleJobDeclarationMessagesFromServerAsync for ChannelManager {
- type Error = JDCError;
-
- // Handles a successful `AllocateMiningJobToken` response from the JDS.
- //
- // When the JDS confirms job token allocation:
- // - Updates the channel manager state with the newly issued token.
- // - Checks whether the JDS has provided updated coinbase outputs.
- // - If outputs have changed, recalculates the corresponding size and sigops constraints.
- // - Sends an updated `CoinbaseOutputConstraints` message to the Template Provider to ensure
- // the new coinbase rules are enforced.
- // - If outputs are unchanged, skips recomputation and continues as normal.
- async fn handle_allocate_mining_job_token_success(
- &mut self,
- _server_id: Option,
- msg: AllocateMiningJobTokenSuccess<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
-
- let coinbase_changed = self.channel_manager_data.super_safe_lock(|data| {
- let changed = data.coinbase_outputs != msg.coinbase_outputs.to_vec();
- data.coinbase_outputs = msg.coinbase_outputs.to_vec();
- data.allocate_tokens = Some(msg.clone().into_static());
- changed
- });
-
- if coinbase_changed {
- info!("Coinbase outputs from JDS changed, recalculating constraints");
- let deserialized_jds_coinbase_outputs: Vec =
- bitcoin::consensus::deserialize(&msg.coinbase_outputs.to_vec())
- .map_err(JDCError::BitcoinEncodeError)?;
-
- let max_additional_size: usize = deserialized_jds_coinbase_outputs
- .iter()
- .map(|o| o.size())
- .sum();
-
- // create a dummy coinbase transaction with the empty output
- // this is used to calculate the sigops of the coinbase output
- let dummy_coinbase = Transaction {
- version: Version::TWO,
- lock_time: LockTime::ZERO,
- input: vec![TxIn {
- previous_output: OutPoint::null(),
- script_sig: ScriptBuf::new(),
- sequence: Sequence::MAX,
- witness: Witness::from(vec![vec![0; 32]]),
- }],
- output: deserialized_jds_coinbase_outputs,
- };
-
- let max_additional_sigops = dummy_coinbase.total_sigop_cost(|_| None) as u16;
-
- debug!(
- max_additional_size,
- max_additional_sigops, "Computed coinbase output constraints"
- );
-
- let coinbase_output_contraints_message = AnyMessage::TemplateDistribution(
- TemplateDistribution::CoinbaseOutputConstraints(CoinbaseOutputConstraints {
- coinbase_output_max_additional_size: max_additional_size as u32,
- coinbase_output_max_additional_sigops: max_additional_sigops,
- }),
- );
-
- let frame: StdFrame = coinbase_output_contraints_message.try_into()?;
-
- self.channel_manager_channel
- .tp_sender
- .send(frame)
- .await
- .map_err(|_e| JDCError::ChannelErrorSender)?;
-
- info!("Sent updated CoinbaseOutputConstraints to TP channel");
- } else {
- debug!("Coinbase outputs unchanged, skipping constraints update");
- }
-
- Ok(())
- }
-
- // Handles a `DeclareMiningJobError` response from the JDS.
- //
- // Receiving this error is treated as a malicious or invalid upstream behavior,
- // since it indicates the JDS has rejected a declared mining job request.
- //
- // Upon receiving it:
- // - Triggers the fallback mechanism by signaling a shutdown through the status channel, causing
- // the Job Declarator Client to enter `JobDeclaratorShutdownFallback`.
- //
- // This ensures that the system does not continue relying on a potentially
- // untrustworthy or misbehaving JDS, and instead fails over to a safer state.
- async fn handle_declare_mining_job_error(
- &mut self,
- _server_id: Option,
- msg: DeclareMiningJobError<'_>,
- ) -> Result<(), Self::Error> {
- warn!("Received: {}", msg);
- warn!("β οΈ JDS refused the declared job with a DeclareMiningJobError β. Starting fallback mechanism.");
- self.channel_manager_channel
- .status_sender
- .send(Status {
- state: State::JobDeclaratorShutdownFallback(JDCError::Shutdown),
- })
- .await
- .map_err(|_e| JDCError::ChannelErrorSender)?;
-
- Ok(())
- }
-
- // Handles a `DeclareMiningJobSuccess` message from the JDS.
- //
- // Receiving this message means the JDS has accepted the declared mining job,
- // giving us the green light to propagate it upstream.
- //
- // The steps are:
- // 1. Look up the last declared job using the `request_id`.
- // 2. Validate that a `prevhash` exists and retrieve job details.
- // 3. Use the job factory to create a new `SetCustomMiningJob` request, embedding the token
- // provided by the JDS.
- // 4. Update the channel manager state with the newly created custom job.
- // 5. Send the `SetCustomMiningJob` message to the upstream, ensuring the job is now distributed
- // across the mining network.
- //
- // If any required data (like `prevhash` or the last declared job) is missing,
- // this handler returns an error to prevent propagation of an incomplete job.
- async fn handle_declare_mining_job_success(
- &mut self,
- _server_id: Option,
- msg: DeclareMiningJobSuccess<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
-
- let Some(last_declare_job) = self
- .channel_manager_data
- .super_safe_lock(|data| data.last_declare_job_store.get(&msg.request_id).cloned())
- else {
- error!(
- "No last_declare_job found for request_id={}",
- msg.request_id
- );
- return Err(JDCError::LastDeclareJobNotFound(msg.request_id));
- };
-
- let Some(prevhash) = last_declare_job.prev_hash else {
- error!("Prevhash not found for request_id = {}", msg.request_id);
- return Err(JDCError::LastNewPrevhashNotFound);
- };
-
- let outputs = match deserialize_outputs(last_declare_job.coinbase_output.clone()) {
- Ok(outputs) => outputs,
- Err(_) => return Err(JDCError::ChannelManagerHasBadCoinbaseOutputs),
- };
-
- let Some(custom_job) = self
- .channel_manager_data
- .super_safe_lock(|channel_manager_data| {
- let job_factory = channel_manager_data.job_factory.as_mut()?;
- let upstream_channel = channel_manager_data.upstream_channel.as_ref()?;
- let full_extranonce_size = upstream_channel.get_full_extranonce_size();
- let custom_job = job_factory.new_custom_job(
- upstream_channel.get_channel_id(),
- msg.request_id,
- msg.new_mining_job_token,
- prevhash.into(),
- last_declare_job.template,
- outputs,
- full_extranonce_size,
- );
- Some(custom_job)
- })
- else {
- return Err(JDCError::FailedToCreateCustomJob);
- };
-
- let custom_job = custom_job.map_err(|_e| JDCError::FailedToCreateCustomJob)?;
-
- self.channel_manager_data.super_safe_lock(|data| {
- if let Some(value) = data.last_declare_job_store.get_mut(&msg.request_id) {
- value.set_custom_mining_job = Some(custom_job.clone().into_static());
- }
- });
-
- let channel_id = custom_job.channel_id;
-
- debug!("Sending SetCustomMiningJob to the upstream with channel_id: {channel_id}");
- let message = AnyMessage::Mining(Mining::SetCustomMiningJob(custom_job)).into_static();
- let frame: StdFrame = message.try_into()?;
-
- self.channel_manager_channel
- .upstream_sender
- .send(frame)
- .await
- .map_err(|_e| JDCError::ChannelErrorSender)?;
-
- info!("Successfully sent SetCustomMiningJob to the upstream with channel_id: {channel_id}");
- Ok(())
- }
-
- // Handles a `ProvideMissingTransactions` request from the JDS.
- //
- // The JDS provides a list of transaction positions it could not resolve.
- // We then:
- // - Retrieve the full transaction list for the given `request_id`.
- // - Identify which transactions are missing based on the provided positions.
- // - Collect and package those transactions into a `ProvideMissingTransactionsSuccess`.
- // - Send the response back to the JDS.
- async fn handle_provide_missing_transactions(
- &mut self,
- _server_id: Option,
- msg: ProvideMissingTransactions<'_>,
- ) -> Result<(), Self::Error> {
- let request_id = msg.request_id;
-
- info!("Received: {}", msg);
-
- let tx_store_entry = self
- .channel_manager_data
- .super_safe_lock(|data| data.last_declare_job_store.get(&request_id).cloned());
-
- let Some(entry) = tx_store_entry else {
- warn!(
- "No transaction list found for request_id={}",
- msg.request_id
- );
- return Err(JDCError::LastDeclareJobNotFound(msg.request_id));
- };
-
- let full_tx_list: Vec = entry
- .tx_list
- .iter()
- .map(|raw| B016M::from_vec_unchecked(raw.clone()))
- .collect();
-
- let unknown_positions: Vec = msg.unknown_tx_position_list.into_inner();
- debug!(
- total_known = full_tx_list.len(),
- unknown_positions = unknown_positions.len(),
- "Resolving missing transactions"
- );
-
- let missing_txns: Vec = unknown_positions
- .iter()
- .filter_map(|&pos| full_tx_list.get(pos as usize).cloned())
- .collect();
-
- if missing_txns.is_empty() {
- warn!("No matching transactions found for request_id={request_id}");
- }
-
- let response = ProvideMissingTransactionsSuccess {
- request_id: msg.request_id,
- transaction_list: binary_sv2::Seq064K::new(missing_txns)
- .map_err(JDCError::BinarySv2)?,
- };
- let frame: StdFrame =
- AnyMessage::JobDeclaration(JobDeclaration::ProvideMissingTransactionsSuccess(response))
- .try_into()?;
-
- self.channel_manager_channel
- .jd_sender
- .send(frame)
- .await
- .map_err(|_e| JDCError::ChannelErrorSender)?;
-
- info!("Successfully sent ProvideMissingTransactionsSuccess to the JDS with request_id: {request_id}");
-
- Ok(())
- }
-}
diff --git a/roles/jd-client/src/lib/channel_manager/mod.rs b/roles/jd-client/src/lib/channel_manager/mod.rs
deleted file mode 100644
index d35c01f38c..0000000000
--- a/roles/jd-client/src/lib/channel_manager/mod.rs
+++ /dev/null
@@ -1,1119 +0,0 @@
-use std::{
- collections::{HashMap, VecDeque},
- net::SocketAddr,
- sync::{
- atomic::{AtomicU32, Ordering},
- Arc,
- },
-};
-
-use async_channel::{Receiver, Sender};
-use stratum_apps::{
- custom_mutex::Mutex,
- key_utils::{Secp256k1PublicKey, Secp256k1SecretKey},
- network_helpers::noise_stream::NoiseTcpStream,
- stratum_core::{
- bitcoin::Target,
- channels_sv2::{
- client::extended::ExtendedChannel,
- server::{
- jobs::{
- extended::ExtendedJob, factory::JobFactory, job_store::DefaultJobStore,
- standard::StandardJob,
- },
- standard::StandardChannel,
- },
- Vardiff, VardiffState,
- },
- framing_sv2::framing::Sv2Frame,
- handlers_sv2::{
- HandleJobDeclarationMessagesFromServerAsync, HandleMiningMessagesFromClientAsync,
- HandleMiningMessagesFromServerAsync, HandleTemplateDistributionMessagesFromServerAsync,
- },
- job_declaration_sv2::{
- AllocateMiningJobToken, AllocateMiningJobTokenSuccess, DeclareMiningJob,
- },
- mining_sv2::{
- ExtendedExtranonce, OpenExtendedMiningChannel, SetCustomMiningJob, SetTarget,
- UpdateChannel, MAX_EXTRANONCE_LEN, MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL,
- MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL,
- },
- noise_sv2::Responder,
- parsers_sv2::{AnyMessage, JobDeclaration, Mining},
- template_distribution_sv2::{NewTemplate, SetNewPrevHash as SetNewPrevHashTdp},
- },
-};
-use tokio::{net::TcpListener, select, sync::broadcast};
-use tracing::{debug, error, info, warn};
-
-use crate::{
- channel_manager::downstream_message_handler::RouteMessageTo,
- config::JobDeclaratorClientConfig,
- downstream::Downstream,
- error::JDCError,
- status::{handle_error, Status, StatusSender},
- task_manager::TaskManager,
- utils::{
- AtomicUpstreamState, Message, PendingChannelRequest, SV2Frame, ShutdownMessage, StdFrame,
- UpstreamState,
- },
-};
-mod downstream_message_handler;
-mod jd_message_handler;
-mod template_message_handler;
-mod upstream_message_handler;
-
-pub const JDC_SEARCH_SPACE_BYTES: usize = 4;
-
-/// A `DeclaredJob` encapsulates all the relevant data associated with a single
-/// job declaration, including its template, optional messages, coinbase output,
-/// and transaction list.
-#[derive(Clone, Debug)]
-pub struct DeclaredJob {
- // The original `DeclareMiningJob` message associated with this job,
- // if one was sent.
- declare_mining_job: Option>,
- // The template associated with the declared job.
- template: NewTemplate<'static>,
- // The `SetNewPrevHashTdp` message associated with this job, if available.
- prev_hash: Option>,
- // The `SetCustomMiningJob` message associated with this job,
- // if a custom job was created.
- set_custom_mining_job: Option>,
- // The coinbase output for this job.
- coinbase_output: Vec,
- // The list of transactions included in the jobβs template.
- tx_list: Vec>,
-}
-
-/// Central state container for the **Channel Manager**.
-///
-/// `ChannelManagerData` holds all runtime state that the JDC
-/// needs to manage downstream clients, upstream connections, extranonce allocation,
-/// job tracking, and various ID factories.
-pub struct ChannelManagerData {
- // Mapping of `downstream_id` β `Downstream` object,
- // used by the channel manager to locate and interact with downstream clients.
- downstream: HashMap,
- // Extranonce prefix factory for **extended downstream channels**.
- // Each new extended downstream receives a unique extranonce prefix.
- extranonce_prefix_factory_extended: ExtendedExtranonce,
- // Extranonce prefix factory for **standard downstream channels**.
- // Each new standard downstream receives a unique extranonce prefix.
- extranonce_prefix_factory_standard: ExtendedExtranonce,
- // Factory that generates **monotonically increasing request IDs**
- // for messages sent from the JDC.
- request_id_factory: AtomicU32,
- // Factory that assigns a unique ID to each new **downstream connection**.
- downstream_id_factory: AtomicU32,
- // Factory that assigns a unique **channel ID** to each channel.
- //
- // β οΈ Note: In this version of the JDC, channel IDs are unique
- // across *all downstreams*, not scoped per downstream.
- channel_id_factory: AtomicU32,
- // Factory that assigns a unique **sequence number** to each share
- // submitted from the JDC to the upstream.
- sequence_number_factory: AtomicU32,
- // The last **future template** received from the upstream.
- last_future_template: Option>,
- // The last **new prevhash** received from the upstream.
- last_new_prev_hash: Option>,
- // The most recent set of **allocation tokens** received from the JDS.
- allocate_tokens: Option>,
- // Stores new templates as they arrive, mapped by their **template ID**.
- template_store: HashMap>,
- // Stores the last declared job, keyed by the `request_id` used when
- // declaring the job to the JDS.
- // This is later used to send a `SetCustomMiningJob`.
- last_declare_job_store: HashMap,
- // Maps a template ID β corresponding upstream job ID.
- template_id_to_upstream_job_id: HashMap,
- // Maps a downstream channel ID + job ID β corresponding template ID.
- downstream_channel_id_and_job_id_to_template_id: HashMap<(u32, u32), u64>,
- // The coinbase outputs currently in use.
- coinbase_outputs: Vec,
- // Maps channel ID β downstream ID.
- channel_id_to_downstream_id: HashMap,
- // The active upstream extended channel (client-side instance), if any.
- upstream_channel: Option>,
- // Optional "pool tag" string, identifying the pool.
- pool_tag_string: Option,
- // List of pending downstream connection requests,
- // persisted while the JDC is opening a channel with the upstream.
- pending_downstream_requests: VecDeque,
- // Factory for creating **custom mining jobs**, if available.
- job_factory: Option,
- // Mapping of `(downstream_id, channel_id)` β vardiff controller.
- // Each entry manages variable difficulty for a specific downstream channel.
- vardiff: HashMap<(u32, u32), VardiffState>,
-}
-
-impl ChannelManagerData {
- /// Resets the internal state of the Channel Manager.
- ///
- /// This method is primarily used during **fallback scenarios** to clear and
- /// reinitialize all internal data structures. It ensures that the Channel Manager
- /// returns to a clean state, ready to handle fresh upstream or downstream connections.
- pub fn reset(&mut self, coinbase_outputs: Vec) {
- self.downstream.clear();
- self.template_store.clear();
- self.last_declare_job_store.clear();
- self.template_id_to_upstream_job_id.clear();
- self.downstream_channel_id_and_job_id_to_template_id.clear();
- self.channel_id_to_downstream_id.clear();
- self.pending_downstream_requests.clear();
-
- self.downstream_id_factory = AtomicU32::new(0);
- self.request_id_factory = AtomicU32::new(0);
- self.channel_id_factory = AtomicU32::new(0);
-
- let (range_0, range_1, range_2) = {
- let range_1 = 0..JDC_SEARCH_SPACE_BYTES;
- (
- 0..range_1.start,
- range_1.clone(),
- range_1.end..MAX_EXTRANONCE_LEN,
- )
- };
- self.extranonce_prefix_factory_extended =
- ExtendedExtranonce::new(range_0.clone(), range_1.clone(), range_2.clone(), None)
- .expect("valid ranges");
- self.extranonce_prefix_factory_standard =
- ExtendedExtranonce::new(range_0, range_1, range_2, None).expect("valid ranges");
-
- self.allocate_tokens = None;
- self.upstream_channel = None;
- self.pool_tag_string = None;
-
- self.coinbase_outputs = coinbase_outputs;
- }
-}
-
-/// Represents all communication channels managed by the Channel Manager.
-///
-/// The `ChannelManagerChannel` holds all the asynchronous communication primitives
-/// required for message exchange between the **Channel Manager** and other subsystems.
-/// It ensures decoupled, structured communication between upstreams, downstreams,
-/// the Job Dispatcher Service (JDS), and the Template Provider (TP).
-///
-/// # Channels
-/// 1. **Upstream**:
-/// - `(upstream_sender, upstream_receiver)` Used to send and receive messages from the upstream
-/// subsystem.
-///
-/// 2. **JDS**:
-/// - `(jd_sender, jd_receiver)` Handles communication with JDS.
-///
-/// 3. **Template Provider**:
-/// - `(tp_sender, tp_receiver)` Manages communication with the Template Provider.
-///
-/// 4. **Downstream**:
-/// - `(downstream_sender, downstream_receiver)` Broadcasts messages to all downstream clients
-/// and receives messages from them.
-///
-/// 5. **Status**:
-/// - `status_sender` Allows the Channel Manager to notify the main status loop of critical state
-/// changes.
-
-#[derive(Clone)]
-pub struct ChannelManagerChannel {
- upstream_sender: Sender,
- upstream_receiver: Receiver,
- jd_sender: Sender,
- jd_receiver: Receiver,
- tp_sender: Sender,
- tp_receiver: Receiver,
- downstream_sender: broadcast::Sender<(u32, Message)>,
- downstream_receiver: Receiver<(u32, SV2Frame)>,
- status_sender: Sender,
-}
-
-/// Contains all the state of mutable and immutable data required
-/// by channel manager to process its task along with channels
-/// to perform message traversal.
-#[derive(Clone)]
-pub struct ChannelManager {
- channel_manager_data: Arc>,
- channel_manager_channel: ChannelManagerChannel,
- miner_tag_string: String,
- share_batch_size: usize,
- shares_per_minute: f32,
- user_identity: String,
- /// This represent the current state of Upstream channel
- /// 1. NoChannel: No active upstream connection.
- /// 2. Pending: A channel request has been sent, awaiting response.
- /// 3. Connected: An upstream channel is successfully established.
- /// 4. SoloMining: No upstream is available; the JDC operates in solo mining mode. case.
- pub upstream_state: AtomicUpstreamState,
-}
-
-impl ChannelManager {
- /// Constructor method used to instantiate the Channel Manager
- #[allow(clippy::too_many_arguments)]
- pub async fn new(
- config: JobDeclaratorClientConfig,
- upstream_sender: Sender,
- upstream_receiver: Receiver,
- jd_sender: Sender,
- jd_receiver: Receiver,
- tp_sender: Sender,
- tp_receiver: Receiver,
- downstream_sender: broadcast::Sender<(u32, Message)>,
- downstream_receiver: Receiver<(u32, SV2Frame)>,
- status_sender: Sender,
- coinbase_outputs: Vec,
- ) -> Result {
- let (range_0, range_1, range_2) = {
- let range_1 = 0..JDC_SEARCH_SPACE_BYTES;
- (
- 0..range_1.start,
- range_1.clone(),
- range_1.end..MAX_EXTRANONCE_LEN,
- )
- };
-
- let make_extranonce_factory = || {
- ExtendedExtranonce::new(range_0.clone(), range_1.clone(), range_2.clone(), None)
- .expect("Failed to create ExtendedExtranonce with valid ranges")
- };
-
- let extranonce_prefix_factory_extended = make_extranonce_factory();
- let extranonce_prefix_factory_standard = make_extranonce_factory();
-
- let channel_manager_data = Arc::new(Mutex::new(ChannelManagerData {
- downstream: HashMap::new(),
- extranonce_prefix_factory_extended,
- extranonce_prefix_factory_standard,
- downstream_id_factory: AtomicU32::new(0),
- request_id_factory: AtomicU32::new(0),
- channel_id_factory: AtomicU32::new(0),
- sequence_number_factory: AtomicU32::new(0),
- last_future_template: None,
- last_new_prev_hash: None,
- allocate_tokens: None,
- template_store: HashMap::new(),
- last_declare_job_store: HashMap::new(),
- template_id_to_upstream_job_id: HashMap::new(),
- downstream_channel_id_and_job_id_to_template_id: HashMap::new(),
- coinbase_outputs,
- channel_id_to_downstream_id: HashMap::new(),
- upstream_channel: None,
- pool_tag_string: None,
- pending_downstream_requests: VecDeque::new(),
- job_factory: None,
- vardiff: HashMap::new(),
- }));
-
- let channel_manager_channel = ChannelManagerChannel {
- upstream_sender,
- upstream_receiver,
- jd_sender,
- jd_receiver,
- tp_sender,
- tp_receiver,
- downstream_sender,
- downstream_receiver,
- status_sender,
- };
-
- let channel_manager = ChannelManager {
- channel_manager_data,
- channel_manager_channel,
- share_batch_size: config.share_batch_size() as usize,
- shares_per_minute: config.shares_per_minute() as f32,
- miner_tag_string: config.jdc_signature().to_string(),
- user_identity: config.user_identity().to_string(),
- upstream_state: AtomicUpstreamState::new(UpstreamState::SoloMining),
- };
-
- Ok(channel_manager)
- }
-
- /// Starts the downstream server, and accepts new connection request.
- #[allow(clippy::too_many_arguments)]
- pub async fn start_downstream_server(
- self,
- authority_public_key: Secp256k1PublicKey,
- authority_secret_key: Secp256k1SecretKey,
- cert_validity_sec: u64,
- listening_address: SocketAddr,
- task_manager: Arc,
- notify_shutdown: broadcast::Sender,
- status_sender: Sender,
- channel_manager_sender: Sender<(u32, SV2Frame)>,
- channel_manager_receiver: broadcast::Sender<(u32, Message)>,
- ) -> Result<(), JDCError> {
- info!("Starting downstream server at {listening_address}");
- let server = TcpListener::bind(listening_address).await.map_err(|e| {
- error!(error = ?e, "Failed to bind downstream server at {listening_address}");
- e
- })?;
-
- let mut shutdown_rx = notify_shutdown.subscribe();
-
- let task_manager_clone = task_manager.clone();
- task_manager.spawn(async move {
-
- loop {
- select! {
- message = shutdown_rx.recv() => {
- match message {
- Ok(ShutdownMessage::ShutdownAll) => {
- info!("Channel Manager: received shutdown signal");
- break;
- }
- Ok(ShutdownMessage::JobDeclaratorShutdownFallback(_)) => {
- info!("Downstream Server: received job declarator shutdown signal");
- break;
- }
- Ok(ShutdownMessage::UpstreamShutdownFallback(_)) => {
- info!("Downstream Server: received upstream shutdown signal");
- break;
- }
- Err(e) => {
- warn!(error = ?e, "shutdown channel closed unexpectedly");
- break;
- }
- _ => {}
- }
- }
- res = server.accept() => {
- match res {
- Ok((stream, socket_address)) => {
- info!(%socket_address, "New downstream connection");
- let responder = match Responder::from_authority_kp(
- &authority_public_key.into_bytes(),
- &authority_secret_key.into_bytes(),
- std::time::Duration::from_secs(cert_validity_sec),
- ) {
- Ok(r) => r,
- Err(e) => {
- error!(error = ?e, "Failed to create responder");
- continue;
- }
- };
- let noise_stream = match NoiseTcpStream::::new(
- stream,
- stratum_apps::stratum_core::codec_sv2::HandshakeRole::Responder(responder),
- )
- .await
- {
- Ok(ns) => ns,
- Err(e) => {
- error!(error = ?e, "Noise handshake failed");
- continue;
- }
- };
-
- let downstream_id = self
- .channel_manager_data
- .super_safe_lock(|data| data.downstream_id_factory.fetch_add(1, Ordering::Relaxed));
-
- let downstream = Downstream::new(
- downstream_id,
- channel_manager_sender.clone(),
- channel_manager_receiver.clone(),
- noise_stream,
- notify_shutdown.clone(),
- task_manager_clone.clone(),
- status_sender.clone(),
- );
-
- self.channel_manager_data.super_safe_lock(|data| {
- data.downstream.insert(downstream_id, downstream.clone());
- });
-
- downstream
- .start(
- notify_shutdown.clone(),
- status_sender.clone(),
- task_manager_clone.clone(),
- )
- .await;
- }
-
- Err(e) => {
- error!(error = ?e, "Failed to accept new downstream connection");
- }
- }
- }
- }
- }
- info!("Downstream server: Unified loop break");
- });
- Ok(())
- }
-
- /// The central orchestrator of the Channel Manager.
- ///
- /// Responsible for receiving messages from all subsystems, processing them,
- /// and either forwarding them to the appropriate subsystem or updating
- /// the internal state of the Channel Manager as needed.
- pub async fn start(
- mut self,
- notify_shutdown: broadcast::Sender,
- status_sender: Sender,
- task_manager: Arc,
- ) {
- let status_sender = StatusSender::ChannelManager(status_sender);
- let mut shutdown_rx = notify_shutdown.subscribe();
-
- task_manager.spawn(async move {
- let cm = self.clone();
- let vd = self.clone();
- let vardiff_future = vd.run_vardiff_loop();
- tokio::pin!(vardiff_future);
- loop {
- let mut cm_jds = cm.clone();
- let mut cm_pool = cm.clone();
- let mut cm_template = cm.clone();
- let mut cm_downstreams = cm.clone();
- tokio::select! {
- message = shutdown_rx.recv() => {
- match message {
- Ok(ShutdownMessage::ShutdownAll) => {
- info!("Channel Manager: received shutdown signal");
- break;
- }
- Ok(ShutdownMessage::DownstreamShutdown(downstream_id)) => {
- info!(%downstream_id, "Channel Manager: removing downstream after shutdown");
- if let Err(e) = self.remove_downstream(downstream_id) {
- tracing::error!(%downstream_id, error = ?e, "Failed to remove downstream");
- }
- }
- Ok(ShutdownMessage::JobDeclaratorShutdownFallback((coinbase_outputs,tx))) => {
- info!("Channel Manager: Job declarator shutdown signal");
- self.upstream_state.set(UpstreamState::SoloMining);
- self.channel_manager_data.super_safe_lock(|data| data.reset(coinbase_outputs));
- drop(tx);
- }
- Ok(ShutdownMessage::UpstreamShutdownFallback((coinbase_outputs,tx))) => {
- info!("Channel Manager: Upstream shutdown signal");
- self.upstream_state.set(UpstreamState::SoloMining);
- self.channel_manager_data.super_safe_lock(|data| data.reset(coinbase_outputs));
- drop(tx);
- }
- Err(e) => {
- warn!(error = ?e, "shutdown channel closed unexpectedly");
- break;
- }
- _ => {}
- }
- }
- res = &mut vardiff_future => {
- info!("Vardiff loop completed with: {res:?}");
- }
- res = cm_jds.handle_jds_message() => {
- if let Err(e) = res {
- if !e.is_critical() {
- continue;
- }
- error!(error = ?e, "Error handling JDS message");
- handle_error(&status_sender, e).await;
- break;
- }
- }
- res = cm_pool.handle_pool_message() => {
- if let Err(e) = res {
- if !e.is_critical() {
- continue;
- }
- error!(error = ?e, "Error handling Pool message");
- handle_error(&status_sender, e).await;
- break;
- }
- }
- res = cm_template.handle_template_provider_message() => {
- if let Err(e) = res {
- if !e.is_critical() {
- continue;
- }
- error!(error = ?e, "Error handling Template Receiver message");
- handle_error(&status_sender, e).await;
- break;
- }
- }
- res = cm_downstreams.handle_downstream_message() => {
- if let Err(e) = res {
- if !e.is_critical() {
- continue;
- }
- error!(error = ?e, "Error handling Downstreams message");
- handle_error(&status_sender, e).await;
- break;
- }
- }
- }
- }
- });
- }
-
- // Removes a downstream entry from the Channel Managerβs state.
- //
- // Given a `downstream_id`, this method:
- // 1. Removes the corresponding downstream from the `downstream` map.
- // 2. Cleans up all associated channel mappings (both standard and extended) by removing their
- // entries from `channel_id_to_downstream_id`.
- fn remove_downstream(&mut self, downstream_id: u32) -> Result<(), JDCError> {
- self.channel_manager_data.super_safe_lock(|cm_data| {
- if let Some(downstream) = cm_data.downstream.remove(&downstream_id) {
- downstream.downstream_data.super_safe_lock(|ds_data| {
- for k in ds_data
- .standard_channels
- .keys()
- .chain(ds_data.extended_channels.keys())
- {
- cm_data.channel_id_to_downstream_id.remove(k);
- }
- });
- }
- });
- Ok(())
- }
-
- /// Handles messages received from the JDS subsystem.
- ///
- /// This method listens for incoming frames on the `jd_receiver` channel.
- /// - If the frame contains a JobDeclaration message, it forwards it to the job declaration
- /// message handler.
- /// - If the frame contains any unsupported message type, an error is returned.
- async fn handle_jds_message(&mut self) -> Result<(), JDCError> {
- if let Ok(mut sv2_frame) = self.channel_manager_channel.jd_receiver.recv().await {
- let Some(message_type) = sv2_frame.get_header().map(|m| m.msg_type()) else {
- return Ok(());
- };
-
- self.handle_job_declaration_message_frame_from_server(
- None,
- message_type,
- sv2_frame.payload(),
- )
- .await?;
- }
- Ok(())
- }
-
- /// Handles messages received from the Upstream subsystem.
- ///
- /// This method listens for incoming frames on the `upstream_receiver` channel.
- /// - If the frame contains a **Mining** message, it forwards it to the mining message
- /// handler.
- /// - If the frame contains any unsupported message type, an error is returned.
- async fn handle_pool_message(&mut self) -> Result<(), JDCError> {
- if let Ok(mut sv2_frame) = self.channel_manager_channel.upstream_receiver.recv().await {
- let Some(message_type) = sv2_frame.get_header().map(|m| m.msg_type()) else {
- return Ok(());
- };
-
- self.handle_mining_message_frame_from_server(None, message_type, sv2_frame.payload())
- .await?;
- }
- Ok(())
- }
-
- // Handles messages received from the TP subsystem.
- //
- // This method listens for incoming frames on the `tp_receiver` channel.
- // - If the frame contains a TemplateDistribution message, it forwards it to the template
- // distribution message handler.
- // - If the frame contains any unsupported message type, an error is returned.
- async fn handle_template_provider_message(&mut self) -> Result<(), JDCError> {
- if let Ok(mut sv2_frame) = self.channel_manager_channel.tp_receiver.recv().await {
- let Some(message_type) = sv2_frame.get_header().map(|m| m.msg_type()) else {
- return Ok(());
- };
- self.handle_template_distribution_message_frame_from_server(
- None,
- message_type,
- sv2_frame.payload(),
- )
- .await?;
- }
- Ok(())
- }
-
- // Handles messages received from downstream clients and routes them appropriately.
- //
- // # Overview
- // This method is similar to the upstream JDS message handler, but introduces additional
- // logic for handling OpenChannel requests (both standard and extended).
- //
- // # Message Flow
- // - For most mining messages: The message is forwarded directly to
- // `handle_mining_message_from_client`, and the `channel_id_to_downstream_id` map is used to
- // determine the origin downstream.
- //
- // - For OpenChannel messages: At the time of request, the `channel_id` is not yet assigned, so
- // we cannot map the message back to the downstream. To solve this:
- // 1. The `downstream_id` is appended to the `user_identity` (e.g.,
- // `"identity#downstream_id"`).
- // 2. Later, the appended downstream ID is stripped and used by the message handler to
- // correctly attribute the request.
- //
- // # Channel Establishment Logic
- // - NoChannel β Pending:
- // - The first downstream OpenChannel request is stored in `pending_downstream_requests`.
- // - The upstream state transitions from `NoChannel` to `Pending`.
- // - A single channel request is then sent to the upstream (JDC β upstream).
- //
- // - Pending:
- // - Additional downstream OpenChannel requests are stored in `pending_downstream_requests`
- // until the upstream connection is established.
- //
- // - Connected / SoloMining:
- // - Downstream OpenChannel requests are immediately forwarded to the mining handler.
- //
- // # Notes
- // - Only one upstream channel is created per JDC instance.
- // - After the upstream channel is established, all new downstream requests bypass the pending
- // mechanism and are sent directly to the mining handler.
- async fn handle_downstream_message(&mut self) -> Result<(), JDCError> {
- if let Ok((downstream_id, mut sv2_frame)) = self
- .channel_manager_channel
- .downstream_receiver
- .recv()
- .await
- {
- let Some(message_type) = sv2_frame.get_header().map(|m| m.msg_type()) else {
- return Err(JDCError::UnexpectedMessage(0));
- };
-
- match message_type {
- MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL => {
- let message: Mining = (message_type, sv2_frame.payload()).try_into()?;
- let Mining::OpenExtendedMiningChannel(mut downstream_channel_request) = message
- else {
- return Err(JDCError::UnexpectedMessage(message_type));
- };
- let user_identity = format!(
- "{}#{}",
- downstream_channel_request.user_identity.as_utf8_or_hex(),
- downstream_id
- );
- downstream_channel_request.user_identity = user_identity.try_into()?;
-
- let downstream_msg = downstream_channel_request.clone().into_static();
-
- match self.upstream_state.get() {
- UpstreamState::NoChannel => {
- self.channel_manager_data.super_safe_lock(|data| {
- data.pending_downstream_requests
- .push_front(downstream_msg.into());
- });
-
- if self
- .upstream_state
- .compare_and_set(UpstreamState::NoChannel, UpstreamState::Pending)
- .is_ok()
- {
- let mut upstream_message = downstream_channel_request;
- upstream_message.user_identity =
- self.user_identity.clone().try_into()?;
- upstream_message.request_id = 1;
- upstream_message.min_extranonce_size +=
- JDC_SEARCH_SPACE_BYTES as u16;
- let upstream_message = AnyMessage::Mining(
- Mining::OpenExtendedMiningChannel(upstream_message)
- .into_static(),
- );
- let frame: StdFrame = upstream_message.try_into()?;
-
- self.channel_manager_channel
- .upstream_sender
- .send(frame)
- .await
- .map_err(|_| JDCError::ChannelErrorSender)?;
- }
- }
- UpstreamState::Pending => {
- self.channel_manager_data.super_safe_lock(|data| {
- data.pending_downstream_requests
- .push_back(downstream_msg.into());
- });
- }
- UpstreamState::Connected => {
- self.send_open_channel_request_to_mining_handler(
- Mining::OpenExtendedMiningChannel(downstream_msg),
- message_type,
- )
- .await?;
- }
- UpstreamState::SoloMining => {
- self.send_open_channel_request_to_mining_handler(
- Mining::OpenExtendedMiningChannel(downstream_msg),
- message_type,
- )
- .await?;
- }
- }
- }
- MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL => {
- let message: Mining = (message_type, sv2_frame.payload()).try_into()?;
- let Mining::OpenStandardMiningChannel(mut downstream_channel_request) = message
- else {
- return Err(JDCError::UnexpectedMessage(message_type));
- };
-
- let user_identity = format!(
- "{:?}#{}",
- downstream_channel_request.user_identity, downstream_id
- );
- downstream_channel_request.user_identity = user_identity.try_into()?;
-
- let downstream_msg = downstream_channel_request.clone().into_static();
-
- match self.upstream_state.get() {
- UpstreamState::NoChannel => {
- self.channel_manager_data.super_safe_lock(|data| {
- data.pending_downstream_requests
- .push_front(downstream_msg.into())
- });
-
- if self
- .upstream_state
- .compare_and_set(UpstreamState::NoChannel, UpstreamState::Pending)
- .is_ok()
- {
- let upstream_open = OpenExtendedMiningChannel {
- user_identity: self.user_identity.clone().try_into().unwrap(),
- request_id: 1,
- nominal_hash_rate: downstream_channel_request.nominal_hash_rate,
- max_target: downstream_channel_request.max_target,
- min_extranonce_size: JDC_SEARCH_SPACE_BYTES as u16,
- };
-
- let frame: StdFrame = AnyMessage::Mining(
- Mining::OpenExtendedMiningChannel(upstream_open).into_static(),
- )
- .try_into()?;
- self.channel_manager_channel
- .upstream_sender
- .send(frame)
- .await
- .map_err(|_| JDCError::ChannelErrorSender)?;
- }
- }
- UpstreamState::Pending => {
- self.channel_manager_data.super_safe_lock(|data| {
- data.pending_downstream_requests
- .push_back(downstream_msg.into())
- });
- }
- UpstreamState::Connected => {
- self.send_open_channel_request_to_mining_handler(
- Mining::OpenStandardMiningChannel(downstream_msg),
- message_type,
- )
- .await?;
- }
- UpstreamState::SoloMining => {
- self.send_open_channel_request_to_mining_handler(
- Mining::OpenStandardMiningChannel(downstream_msg),
- message_type,
- )
- .await?;
- }
- }
- }
- _ => {
- self.handle_mining_message_frame_from_client(
- None,
- message_type,
- sv2_frame.payload(),
- )
- .await?;
- }
- }
- }
-
- Ok(())
- }
-
- // Utility method to send open channel request from downstream to message handler.
- async fn send_open_channel_request_to_mining_handler(
- &mut self,
- mining_msg: Mining<'static>,
- message_type: u8,
- ) -> Result<(), JDCError> {
- let sv2_frame: Sv2Frame, Vec> = match Sv2Frame::from_message(
- mining_msg,
- message_type,
- 0,
- false,
- ) {
- Some(f) => f,
- None => {
- warn!(%message_type, "Failed to build Sv2Frame from mining message; dropping request");
- return Err(JDCError::FrameConversionError);
- }
- };
-
- let mut serialized = vec![0u8; sv2_frame.encoded_length()];
- if let Err(e) = sv2_frame.serialize(&mut serialized) {
- warn!(?e, %message_type, len = serialized.len(), "Failed to serialize Sv2Frame; dropping request");
- return Err(JDCError::FramingSv2(e));
- }
-
- let mut deserialized_frame =
- match Sv2Frame::, Vec>::from_bytes(serialized) {
- Ok(f) => f,
- Err(e) => {
- warn!(?e, %message_type, "Failed to deserialize Sv2Frame; dropping request");
- return Err(JDCError::FrameConversionError);
- }
- };
-
- let payload = deserialized_frame.payload();
- self.handle_mining_message_frame_from_client(None, message_type, payload)
- .await?;
- Ok(())
- }
-
- /// Utility method to request for more token to JDS.
- pub async fn allocate_tokens(&self, token_to_allocate: u32) -> Result<(), JDCError> {
- debug!("Allocating {} job tokens", token_to_allocate);
-
- for i in 0..token_to_allocate {
- let request_id = self
- .channel_manager_data
- .super_safe_lock(|data| data.request_id_factory.fetch_add(1, Ordering::Relaxed));
-
- debug!(
- request_id,
- "Allocating token {}/{}",
- i + 1,
- token_to_allocate
- );
-
- let message = JobDeclaration::AllocateMiningJobToken(AllocateMiningJobToken {
- user_identifier: self
- .user_identity
- .to_string()
- .try_into()
- .expect("Static string should always convert"),
- request_id,
- });
-
- let frame: StdFrame = AnyMessage::JobDeclaration(message)
- .try_into()
- .map_err(|e| {
- info!(error = ?e, "Failed to convert AllocateMiningJobToken to frame");
- e
- })?;
-
- self.channel_manager_channel
- .jd_sender
- .send(frame)
- .await
- .map_err(|e| {
- info!(error = ?e, "Failed to send AllocateMiningJobToken frame");
- JDCError::ChannelErrorSender
- })?;
- }
-
- info!("Requested allocation of {token_to_allocate} mining job tokens to JDS");
- Ok(())
- }
-
- // Runs the vardiff on extended channel.
- fn run_vardiff_on_extended_channel(
- downstream_id: u32,
- channel_id: u32,
- channel_state: &mut stratum_apps::stratum_core::channels_sv2::server::extended::ExtendedChannel<
- 'static,
- DefaultJobStore>,
- >,
- vardiff_state: &mut VardiffState,
- updates: &mut Vec,
- ) {
- let (hashrate, target, shares_per_minute) = (
- channel_state.get_nominal_hashrate(),
- channel_state.get_target(),
- channel_state.get_shares_per_minute(),
- );
-
- let Ok(new_hashrate_opt) = vardiff_state.try_vardiff(hashrate, target, shares_per_minute)
- else {
- debug!("Vardiff computation failed for extended channel {channel_id}");
- return;
- };
-
- let Some(new_hashrate) = new_hashrate_opt else {
- return;
- };
-
- match channel_state.update_channel(new_hashrate, None) {
- Ok(()) => {
- let updated_target = channel_state.get_target();
- updates.push(
- (
- downstream_id,
- Mining::SetTarget(SetTarget {
- channel_id,
- maximum_target: updated_target.to_le_bytes().into(),
- }),
- )
- .into(),
- );
- debug!("Updated target for extended channel_id={channel_id} to {updated_target:?}",);
- }
- Err(e) => warn!(
- "Failed to update extended channel channel_id={channel_id} during vardiff {e:?}"
- ),
- }
- }
-
- // Runs the vardiff on the standard channel.
- fn run_vardiff_on_standard_channel(
- downstream_id: u32,
- channel_id: u32,
- channel: &mut StandardChannel<'static, DefaultJobStore>>,
- vardiff_state: &mut VardiffState,
- updates: &mut Vec,
- ) {
- let hashrate = channel.get_nominal_hashrate();
- let target = channel.get_target();
- let shares_per_minute = channel.get_shares_per_minute();
-
- let Ok(new_hashrate_opt) = vardiff_state.try_vardiff(hashrate, target, shares_per_minute)
- else {
- debug!("Vardiff computation failed for standard channel {channel_id}");
- return;
- };
-
- if let Some(new_hashrate) = new_hashrate_opt {
- match channel.update_channel(new_hashrate, None) {
- Ok(()) => {
- let updated_target = channel.get_target();
- updates.push(
- (
- downstream_id,
- Mining::SetTarget(SetTarget {
- channel_id,
- maximum_target: updated_target.to_le_bytes().into(),
- }),
- )
- .into(),
- );
- debug!("Updated target for standard channel channel_id={channel_id} to {updated_target:?}");
- }
- Err(e) => warn!(
- "Failed to update standard channel channel_id={channel_id} during vardiff {e:?}"
- ),
- }
- }
- }
-
- // Periodic vardiff task loop.
- //
- // # Purpose
- // - Executes the vardiff cycle every 60 seconds for all downstreams.
- // - Delegates to [`Self::run_vardiff`] on each tick.
- async fn run_vardiff_loop(&self) -> Result<(), JDCError> {
- let mut ticker = tokio::time::interval(std::time::Duration::from_secs(60));
- loop {
- ticker.tick().await;
- info!("Starting vardiff loop for downstreams");
-
- if let Err(e) = self.run_vardiff().await {
- error!(error = ?e, "Vardiff iteration failed");
- }
- }
- }
-
- // Runs vardiff across **all channels** and generates updates.
- //
- // # Purpose
- // - Iterates through all downstream channels (both standard and extended).
- // - Runs vardiff for each channel and collects the resulting updates.
- // - Propagates difficulty changes to downstreams and also sends an `UpdateChannel` message
- // upstream if applicable.
- async fn run_vardiff(&self) -> Result<(), JDCError> {
- let mut messages: Vec = vec![];
- self.channel_manager_data
- .super_safe_lock(|channel_manager_data| {
- for ((channel_id, downstream_id), vardiff_state) in
- channel_manager_data.vardiff.iter_mut()
- {
- let Some(downstream) = channel_manager_data.downstream.get_mut(downstream_id)
- else {
- continue;
- };
- downstream.downstream_data.super_safe_lock(|data| {
- if let Some(standard_channel) = data.standard_channels.get_mut(channel_id) {
- Self::run_vardiff_on_standard_channel(
- *downstream_id,
- *channel_id,
- standard_channel,
- vardiff_state,
- &mut messages,
- );
- }
- if let Some(extended_channel) = data.extended_channels.get_mut(channel_id) {
- Self::run_vardiff_on_extended_channel(
- *downstream_id,
- *channel_id,
- extended_channel,
- vardiff_state,
- &mut messages,
- );
- }
- });
- }
-
- if !messages.is_empty() {
- let mut downstream_hashrate = 0.0;
- let mut min_target = [0xff; 32];
-
- for (_, downstream) in channel_manager_data.downstream.iter() {
- downstream.downstream_data.super_safe_lock(|data| {
- let mut update_from_channel = |hashrate: f32, target: &Target| {
- downstream_hashrate += hashrate;
- min_target = std::cmp::min(target.to_le_bytes(), min_target);
- };
-
- for (_, channel) in data.standard_channels.iter() {
- update_from_channel(
- channel.get_nominal_hashrate(),
- channel.get_target(),
- );
- }
-
- for (_, channel) in data.extended_channels.iter() {
- update_from_channel(
- channel.get_nominal_hashrate(),
- channel.get_target(),
- );
- }
- });
- }
-
- if let Some(ref upstream_channel) = channel_manager_data.upstream_channel {
- debug!(
- "Checking upstream channel {} with hashrate {} and target {:?}",
- upstream_channel.get_channel_id(),
- upstream_channel.get_nominal_hashrate(),
- upstream_channel.get_target()
- );
-
- info!("Sending update channel message upstream");
- messages.push(
- Mining::UpdateChannel(UpdateChannel {
- channel_id: upstream_channel.get_channel_id(),
- nominal_hash_rate: downstream_hashrate,
- maximum_target: min_target.into(),
- })
- .into(),
- )
- }
- }
- });
-
- for message in messages {
- message.forward(&self.channel_manager_channel).await;
- }
-
- info!("Vardiff update cycle complete");
- Ok(())
- }
-}
diff --git a/roles/jd-client/src/lib/channel_manager/template_message_handler.rs b/roles/jd-client/src/lib/channel_manager/template_message_handler.rs
deleted file mode 100644
index e0e97a4bc3..0000000000
--- a/roles/jd-client/src/lib/channel_manager/template_message_handler.rs
+++ /dev/null
@@ -1,611 +0,0 @@
-use std::sync::atomic::Ordering;
-
-use stratum_apps::stratum_core::{
- binary_sv2::{Seq064K, U256},
- bitcoin::{consensus, hashes::Hash, Amount, Transaction},
- channels_sv2::{chain_tip::ChainTip, outputs::deserialize_outputs},
- handlers_sv2::HandleTemplateDistributionMessagesFromServerAsync,
- job_declaration_sv2::DeclareMiningJob,
- mining_sv2::SetNewPrevHash as SetNewPrevHashMp,
- parsers_sv2::{AnyMessage, JobDeclaration, Mining, TemplateDistribution},
- template_distribution_sv2::*,
-};
-use tracing::{error, info, warn};
-
-use crate::{
- channel_manager::{downstream_message_handler::RouteMessageTo, ChannelManager, DeclaredJob},
- error::JDCError,
- jd_mode::{get_jd_mode, JdMode},
- utils::StdFrame,
-};
-
-impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager {
- type Error = JDCError;
-
- // Handles a `NewTemplate` message from the Template Provider.
- //
- // Behavior depends on the JD mode:
- // - FullTemplate: sends a `RequestTransactionData` to start the declare-mining-job flow.
- // - CoinbaseOnly: sends a `SetCustomMiningJob` and continues with that flow.
- //
- // In both modes, the new template is stored and propagated to all
- // downstream channels, updating their state and dispatching the
- // appropriate mining job messages (standard, group, or extended).
- //
- // Also updates future/active template state and triggers token
- // allocation if needed.
- async fn handle_new_template(
- &mut self,
- _server_id: Option,
- msg: NewTemplate<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
-
- let coinbase_outputs = self.channel_manager_data.super_safe_lock(|data| {
- data.template_store
- .insert(msg.template_id, msg.clone().into_static());
- if msg.future_template {
- data.last_future_template = Some(msg.clone().into_static());
- }
- data.coinbase_outputs.clone()
- });
-
- let mut coinbase_outputs = deserialize_outputs(coinbase_outputs)
- .map_err(|_| JDCError::ChannelManagerHasBadCoinbaseOutputs)?;
-
- if get_jd_mode() == JdMode::FullTemplate {
- let tx_data_request = AnyMessage::TemplateDistribution(
- TemplateDistribution::RequestTransactionData(RequestTransactionData {
- template_id: msg.template_id,
- }),
- );
- let frame: StdFrame = tx_data_request.try_into()?;
- self.channel_manager_channel
- .tp_sender
- .send(frame)
- .await
- .map_err(|_e| JDCError::ChannelErrorSender)?;
- }
-
- let messages = self.channel_manager_data.super_safe_lock(|channel_manager_data| {
- let mut messages: Vec = Vec::new();
- coinbase_outputs[0].value = Amount::from_sat(msg.coinbase_tx_value_remaining);
-
- for (downstream_id, downstream) in channel_manager_data.downstream.iter_mut() {
-
- let messages_ = downstream.downstream_data.super_safe_lock(|data| {
-
- let mut messages: Vec = vec![];
-
- let group_channel_job = if let Some(ref mut group_channel) = data.group_channels {
- if group_channel.on_new_template(msg.clone().into_static(), coinbase_outputs.clone()).is_ok() {
- match msg.future_template {
- true => {
- let future_job_id = group_channel
- .get_future_template_to_job_id()
- .get(&msg.template_id)
- .expect("job_id must exist");
- Some(group_channel
- .get_future_jobs()
- .get(future_job_id)
- .expect("future job must exist")).cloned()
- },
- false => {
- Some(group_channel
- .get_active_job()
- .expect("active job must exist")).cloned()
- }
- }
- } else {
- tracing::error!("Some issue with downstream: {downstream_id}, group channel");
- None
- }
- } else {
- None
- };
-
- if let Some(upstream_channel) = channel_manager_data.upstream_channel.as_mut() {
- if !msg.future_template && get_jd_mode() == JdMode::CoinbaseOnly {
- if let (Some(token), Some(prevhash)) = (
- channel_manager_data.allocate_tokens.clone(),
- channel_manager_data.last_new_prev_hash.clone(),
- ) {
- let request_id = channel_manager_data.request_id_factory.fetch_add(1, Ordering::Relaxed);
- let job_factory = channel_manager_data.job_factory.as_mut().unwrap();
- let full_extranonce_size = upstream_channel.get_full_extranonce_size();
- let custom_job = job_factory.new_custom_job(upstream_channel.get_channel_id(), request_id, token.clone().mining_job_token, prevhash.clone().into(), msg.clone(), coinbase_outputs.clone(), full_extranonce_size);
-
- if let Ok(custom_job) = custom_job{
- let last_declare = DeclaredJob {
- declare_mining_job: None,
- template: msg.clone().into_static(),
- prev_hash: Some(prevhash),
- set_custom_mining_job: Some(custom_job.clone().into_static()),
- coinbase_output: channel_manager_data.coinbase_outputs.clone(),
- tx_list: Vec::new(),
- };
- channel_manager_data
- .last_declare_job_store
- .insert(request_id, last_declare);
- messages.push(
- Mining::SetCustomMiningJob(custom_job).into()
- );
- }
- }
- }
- }
- match msg.future_template {
- true => {
- for (channel_id, standard_channel) in data.standard_channels.iter_mut() {
- if data.group_channels.is_none() {
- if let Err(e) = standard_channel.on_new_template(msg.clone().into_static(), coinbase_outputs.clone()) {
- tracing::error!("Error while adding template to standard channel: {channel_id:?} {e:?}");
- continue;
- }
- let standard_job_id = standard_channel.get_future_template_to_job_id().get(&msg.template_id).expect("job_id must exist");
- let standard_job = standard_channel.get_future_jobs().get(standard_job_id).expect("standard job must exist");
- channel_manager_data.downstream_channel_id_and_job_id_to_template_id.insert((*channel_id, *standard_job_id), msg.template_id);
- let standard_job_message = standard_job.get_job_message();
- messages.push((*downstream_id, Mining::NewMiningJob(standard_job_message.clone())).into());
- }
- if let Some(ref group_channel_job) = group_channel_job {
- if let Err(e) = standard_channel.on_new_template(msg.clone().into_static(), coinbase_outputs.clone()) {
- tracing::error!("Error while adding template to standard channel: {channel_id:?} {e:?}");
- continue;
- }
- _ = standard_channel
- .on_group_channel_job(group_channel_job.clone());
- }
- }
- if let Some(group_channel_job) = group_channel_job {
- let job_message = group_channel_job.get_job_message();
- messages.push((*downstream_id, Mining::NewExtendedMiningJob(job_message.clone())).into());
- }
-
- for (channel_id, extended_channel) in data.extended_channels.iter_mut() {
- if let Err(e) = extended_channel.on_new_template(msg.clone().into_static(), coinbase_outputs.clone()) {
- tracing::error!("Error while adding template to standard channel: {channel_id:?} {e:?}");
- continue;
- }
- let extended_job_id = extended_channel
- .get_future_template_to_job_id()
- .get(&msg.template_id)
- .expect("job_id must exist");
-
- let extended_job = extended_channel
- .get_future_jobs()
- .get(extended_job_id)
- .expect("extended job must exist");
-
- channel_manager_data.downstream_channel_id_and_job_id_to_template_id.insert((*channel_id, *extended_job_id), msg.template_id);
- let extended_job_message = extended_job.get_job_message();
-
- messages.push((*downstream_id,Mining::NewExtendedMiningJob(extended_job_message.clone())).into());
- }
- }
- false => {
- for (channel_id, standard_channel) in data.standard_channels.iter_mut() {
- if data.group_channels.is_none() {
- if let Err(e) = standard_channel.on_new_template(msg.clone().into_static(), coinbase_outputs.clone()) {
- tracing::error!("Error while adding template to standard channel: {channel_id:?} {e:?}");
- continue;
- }
- let standard_job = standard_channel.get_active_job().expect("standard job must exist");
- channel_manager_data.downstream_channel_id_and_job_id_to_template_id.insert((*channel_id, standard_job.get_job_id()), msg.template_id);
- let standard_job_message = standard_job.get_job_message();
- messages.push((*downstream_id, Mining::NewMiningJob(standard_job_message.clone())).into());
- }
- if let Some(ref group_channel_job) = group_channel_job {
- if let Err(e) = standard_channel.on_new_template(msg.clone().into_static(), coinbase_outputs.clone()) {
- tracing::error!("Error while adding template to standard channel: {channel_id:?} {e:?}");
- continue;
- }
- _ = standard_channel
- .on_group_channel_job(group_channel_job.clone());
- }
- }
- if let Some(group_channel_job) = group_channel_job {
- let job_message = group_channel_job.get_job_message();
- messages.push((*downstream_id, Mining::NewExtendedMiningJob(job_message.clone())).into());
- }
-
- for (channel_id, extended_channel) in data.extended_channels.iter_mut() {
- if let Err(e) = extended_channel.on_new_template(msg.clone().into_static(), coinbase_outputs.clone()) {
- tracing::error!("Error while adding template to standard channel: {channel_id:?} {e:?}");
- continue;
- }
- let extended_job = extended_channel
- .get_active_job()
- .expect("extended job must exist");
-
- channel_manager_data.downstream_channel_id_and_job_id_to_template_id.insert((*channel_id, extended_job.get_job_id()), msg.template_id);
- let extended_job_message = extended_job.get_job_message();
-
- messages.push((*downstream_id,Mining::NewExtendedMiningJob(extended_job_message.clone())).into());
- }
- }
- }
-
- messages
-
- });
- messages.extend(messages_);
- }
- messages
- });
-
- if get_jd_mode() == JdMode::CoinbaseOnly && !msg.future_template {
- _ = self.allocate_tokens(1).await;
- }
-
- for message in messages {
- message.forward(&self.channel_manager_channel).await;
- }
-
- Ok(())
- }
-
- // Handles a `RequestTransactionDataError` message from the Template Provider.
- async fn handle_request_tx_data_error(
- &mut self,
- _server_id: Option,
- msg: RequestTransactionDataError<'_>,
- ) -> Result<(), Self::Error> {
- warn!("Received: {}", msg);
- let error_code = msg.error_code.as_utf8_or_hex();
-
- if matches!(
- error_code.as_str(),
- "template-id-not-found" | "stale-template-id"
- ) {
- return Ok(());
- }
- Err(JDCError::TxDataError)
- }
-
- // Handles a `RequestTransactionDataSuccess` message from the Template Provider.
- //
- // Flow:
- // - If the template is not a future template, immediately declare a mining job to JDS.
- // - If the template is a future template:
- // - Check if the current `prevhash` activates this template.
- // - If activated β proceed with the normal declare job flow.
- // - If not activated β cache it as a declare job for later propagation.
- async fn handle_request_tx_data_success(
- &mut self,
- _server_id: Option,
- msg: RequestTransactionDataSuccess<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
-
- let transactions_data = msg.transaction_list;
- let excess_data = msg.excess_data;
-
- let coinbase_outputs = self
- .channel_manager_data
- .super_safe_lock(|data| data.coinbase_outputs.clone());
-
- let mut deserialized_outputs = deserialize_outputs(coinbase_outputs)
- .map_err(|_| JDCError::ChannelManagerHasBadCoinbaseOutputs)?;
-
- let (token, template_message, request_id, prevhash) =
- self.channel_manager_data.super_safe_lock(|data| {
- (
- data.allocate_tokens.clone(),
- data.template_store.remove(&msg.template_id),
- data.request_id_factory.fetch_add(1, Ordering::Relaxed),
- data.last_new_prev_hash.clone(),
- )
- });
-
- _ = self.allocate_tokens(1).await;
- let Some(token) = token else {
- error!("Token not found, template id: {}", msg.template_id);
- return Err(JDCError::TokenNotFound);
- };
-
- let Some(template_message) = template_message else {
- error!("Template not found, template id: {}", msg.template_id);
- return Err(JDCError::TemplateNotFound(msg.template_id));
- };
-
- let mining_token = token.mining_job_token.clone();
- deserialized_outputs[0].value =
- Amount::from_sat(template_message.coinbase_tx_value_remaining);
- let reserialized_outputs = consensus::serialize(&deserialized_outputs);
-
- let tx_list: Vec = transactions_data
- .to_vec()
- .iter()
- .map(|raw_tx| consensus::deserialize(raw_tx).expect("invalid tx"))
- .collect();
-
- let txids_as_u256: Vec> = tx_list
- .iter()
- .map(|tx| {
- let txid = tx.compute_txid();
- let byte_array: [u8; 32] = *txid.as_byte_array();
- U256::Owned(byte_array.to_vec())
- })
- .collect();
-
- let tx_ids = Seq064K::new(txids_as_u256).map_err(JDCError::BinarySv2)?;
- let is_activated_future_template = template_message.future_template
- && prevhash
- .map(|prev_hash| prev_hash.template_id != template_message.template_id)
- .unwrap_or(true);
-
- let declare_job = self.channel_manager_data.super_safe_lock(|data| {
- let job_factory = data.job_factory.as_mut()?;
-
- let full_extranonce_size = data
- .upstream_channel
- .as_ref()
- .map(|channel| channel.get_full_extranonce_size())
- .unwrap_or(32);
-
- if let Ok((coinbase_tx_prefix, coinbase_tx_suffix)) = job_factory
- .new_coinbase_tx_prefix_and_suffix(
- template_message.clone(),
- deserialized_outputs.clone(),
- full_extranonce_size,
- )
- {
- let version = template_message.version;
-
- let declare_job = DeclareMiningJob {
- request_id,
- mining_job_token: mining_token.to_vec().try_into().unwrap(),
- version,
- coinbase_tx_prefix: coinbase_tx_prefix.try_into().unwrap(),
- coinbase_tx_suffix: coinbase_tx_suffix.try_into().unwrap(),
- tx_ids_list: tx_ids,
- excess_data: excess_data.to_vec().try_into().unwrap(),
- };
-
- let last_declare = DeclaredJob {
- declare_mining_job: Some(declare_job.clone()),
- template: template_message,
- prev_hash: data.last_new_prev_hash.clone(),
- set_custom_mining_job: None,
- coinbase_output: reserialized_outputs,
- tx_list: transactions_data.to_vec(),
- };
-
- data.last_declare_job_store.insert(request_id, last_declare);
-
- return Some(declare_job);
- }
- None
- });
-
- if is_activated_future_template {
- return Ok(());
- }
-
- if let Some(declare_job) = declare_job {
- let frame: StdFrame =
- AnyMessage::JobDeclaration(JobDeclaration::DeclareMiningJob(declare_job))
- .try_into()?;
-
- _ = self.channel_manager_channel.jd_sender.send(frame).await;
- }
-
- Ok(())
- }
-
- // Handles a `SetNewPrevHash` message:
- //
- // - Check `declare_job_cache` to see if the `prevhash` activates a future template.
- // - In FullTemplate mode β send a `DeclareMiningJob`.
- // - In CoinbaseOnly mode β send a `CustomMiningJob` for the activated future template.
- // - Update the upstream channel state.
- // - Update all downstream channels and propagate the new `prevhash` via `SetNewPrevHash`.
- async fn handle_set_new_prev_hash(
- &mut self,
- _server_id: Option,
- msg: SetNewPrevHash<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
-
- let coinbase_outputs = self
- .channel_manager_data
- .super_safe_lock(|data| data.coinbase_outputs.clone());
-
- let outputs = deserialize_outputs(coinbase_outputs)
- .map_err(|_| JDCError::ChannelManagerHasBadCoinbaseOutputs)?;
-
- let (future_template, declare_job) = self.channel_manager_data.super_safe_lock(|data| {
- if let Some(upstream_channel) = data.upstream_channel.as_mut() {
- if let Err(e) = upstream_channel.on_chain_tip_update(msg.clone().into()) {
- error!(
- "Couldn't update chaintip of the upstream channel: {msg}, error: {e:#?}"
- );
- }
- }
-
- let declare_job = data
- .last_declare_job_store
- .values()
- .find(|declared_job| {
- Some(declared_job.template.template_id)
- == data.last_future_template.as_ref().map(|t| t.template_id)
- })
- .map(|declared_job| declared_job.declare_mining_job.clone());
-
- (data.last_future_template.clone(), declare_job)
- });
-
- if get_jd_mode() == JdMode::FullTemplate {
- if let Some(Some(job)) = declare_job {
- let frame: StdFrame =
- AnyMessage::JobDeclaration(JobDeclaration::DeclareMiningJob(job)).try_into()?;
-
- self.channel_manager_channel
- .jd_sender
- .send(frame)
- .await
- .map_err(|_e| JDCError::ChannelErrorSender)?;
- }
- }
-
- let messages = self.channel_manager_data.super_safe_lock(|data| {
- data.last_new_prev_hash = Some(msg.clone().into_static());
- data.last_declare_job_store.iter_mut().for_each(|(_k, v)| {
- if v.template.future_template && v.template.template_id == msg.template_id {
- v.prev_hash = Some(msg.clone().into_static());
- v.template.future_template = false;
- }
- });
-
- let mut messages: Vec = vec![];
-
- if let Some(ref mut upstream_channel) = data.upstream_channel {
- _ = upstream_channel.on_chain_tip_update(msg.clone().into());
-
- if get_jd_mode() == JdMode::CoinbaseOnly {
- if let (Some(job_factory), Some(token), Some(template)) = (
- data.job_factory.as_mut(),
- data.allocate_tokens.clone(),
- future_template.clone(),
- ) {
- let request_id = data.request_id_factory.fetch_add(1, Ordering::Relaxed);
- let chain_tip = ChainTip::new(
- msg.prev_hash.clone().into_static(),
- msg.n_bits,
- msg.header_timestamp,
- );
-
- let full_extranonce_size = upstream_channel.get_full_extranonce_size();
-
- if let Ok(custom_job) = job_factory.new_custom_job(
- upstream_channel.get_channel_id(),
- request_id,
- token.clone().mining_job_token,
- chain_tip,
- template.clone(),
- outputs,
- full_extranonce_size,
- ) {
- let last_declare = DeclaredJob {
- declare_mining_job: None,
- template: template.into_static(),
- prev_hash: Some(msg.clone().into_static()),
- set_custom_mining_job: Some(custom_job.clone().into_static()),
- coinbase_output: data.coinbase_outputs.clone(),
- tx_list: vec![],
- };
-
- data.last_declare_job_store.insert(request_id, last_declare);
- messages.push(Mining::SetCustomMiningJob(custom_job).into());
- }
- }
- }
- }
-
- for (downstream_id, downstream) in data.downstream.iter_mut() {
- let downstream_messages = downstream.downstream_data.super_safe_lock(|data| {
- let mut messages: Vec = vec![];
- if let Some(ref mut group_channel) = data.group_channels {
- _ = group_channel.on_set_new_prev_hash(msg.clone().into_static());
- let group_channel_id = group_channel.get_group_channel_id();
- let activated_group_job_id = group_channel
- .get_active_job()
- .expect("active job must exist")
- .get_job_id();
-
- let set_new_prev_hash_message = SetNewPrevHashMp {
- channel_id: group_channel_id,
- job_id: activated_group_job_id,
- prev_hash: msg.prev_hash.clone(),
- min_ntime: msg.header_timestamp,
- nbits: msg.n_bits,
- };
- messages.push(
- (
- *downstream_id,
- Mining::SetNewPrevHash(set_new_prev_hash_message),
- )
- .into(),
- );
- }
-
- for (channel_id, standard_channel) in data.standard_channels.iter_mut() {
- if let Err(_e) =
- standard_channel.on_set_new_prev_hash(msg.clone().into_static())
- {
- continue;
- };
-
- // did SetupConnection have the REQUIRES_STANDARD_JOBS flag set?
- // if yes, there's no group channel, so we need to send the SetNewPrevHashMp
- // to each standard channel
- if data.group_channels.is_none() {
- let activated_standard_job_id = standard_channel
- .get_active_job()
- .expect("active job must exist")
- .get_job_id();
- let set_new_prev_hash_message = SetNewPrevHashMp {
- channel_id: *channel_id,
- job_id: activated_standard_job_id,
- prev_hash: msg.prev_hash.clone(),
- min_ntime: msg.header_timestamp,
- nbits: msg.n_bits,
- };
- messages.push(
- (
- *downstream_id,
- Mining::SetNewPrevHash(set_new_prev_hash_message),
- )
- .into(),
- );
- }
- }
-
- for (channel_id, extended_channel) in data.extended_channels.iter_mut() {
- if let Err(_e) =
- extended_channel.on_set_new_prev_hash(msg.clone().into_static())
- {
- continue;
- };
-
- let activated_extended_job_id = extended_channel
- .get_active_job()
- .expect("active job must exist")
- .get_job_id();
- let set_new_prev_hash_message = SetNewPrevHashMp {
- channel_id: *channel_id,
- job_id: activated_extended_job_id,
- prev_hash: msg.prev_hash.clone(),
- min_ntime: msg.header_timestamp,
- nbits: msg.n_bits,
- };
- messages.push(
- (
- *downstream_id,
- Mining::SetNewPrevHash(set_new_prev_hash_message),
- )
- .into(),
- );
- }
-
- messages
- });
-
- messages.extend(downstream_messages);
- }
-
- messages
- });
-
- if get_jd_mode() == JdMode::CoinbaseOnly {
- _ = self.allocate_tokens(1).await;
- }
-
- for message in messages {
- message.forward(&self.channel_manager_channel).await;
- }
-
- Ok(())
- }
-}
diff --git a/roles/jd-client/src/lib/channel_manager/upstream_message_handler.rs b/roles/jd-client/src/lib/channel_manager/upstream_message_handler.rs
deleted file mode 100644
index d1d95116ef..0000000000
--- a/roles/jd-client/src/lib/channel_manager/upstream_message_handler.rs
+++ /dev/null
@@ -1,619 +0,0 @@
-use std::sync::atomic::Ordering;
-
-use stratum_apps::stratum_core::{
- bitcoin::Target,
- channels_sv2::{
- client::extended::ExtendedChannel, outputs::deserialize_outputs,
- server::jobs::factory::JobFactory,
- },
- handlers_sv2::{HandleMiningMessagesFromServerAsync, SupportedChannelTypes},
- mining_sv2::*,
- parsers_sv2::{AnyMessage, Mining, TemplateDistribution},
- template_distribution_sv2::RequestTransactionData,
-};
-use tracing::{debug, error, info, warn};
-
-use crate::{
- channel_manager::{
- downstream_message_handler::RouteMessageTo, ChannelManager, DeclaredJob,
- JDC_SEARCH_SPACE_BYTES,
- },
- error::{ChannelSv2Error, JDCError},
- jd_mode::{get_jd_mode, JdMode},
- status::{State, Status},
- utils::{create_close_channel_msg, PendingChannelRequest, StdFrame, UpstreamState},
-};
-
-impl HandleMiningMessagesFromServerAsync for ChannelManager {
- type Error = JDCError;
-
- fn get_channel_type_for_server(&self, _server_id: Option) -> SupportedChannelTypes {
- SupportedChannelTypes::Extended
- }
- fn is_work_selection_enabled_for_server(&self, _server_id: Option) -> bool {
- true
- }
-
- // Handles an unexpected `OpenStandardMiningChannelSuccess` message from the upstream.
- //
- // The Job Declarator Client (JDC) only supports extended channel when
- // communicating with upstream peer. Receiving a standard channel success
- // indicates either misbehavior or a protocol violation by the upstream.
- //
- // In such cases, the event is treated as malicious, and a fallback
- // (`UpstreamShutdownFallback`) is immediately triggered to protect the system.
- async fn handle_open_standard_mining_channel_success(
- &mut self,
- _server_id: Option,
- msg: OpenStandardMiningChannelSuccess<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
- info!(
- "β οΈ JDC can only open extended channels with the upstream server, preparing fallback."
- );
- _ = self
- .channel_manager_channel
- .status_sender
- .send(Status {
- state: State::UpstreamShutdownFallback(JDCError::Shutdown),
- })
- .await;
- Ok(())
- }
-
- // Handles `OpenExtendedMiningChannelSuccess` messages from upstream.
- //
- // On success, this establishes a client-side extended channel:
- // - If initialization fails at any step, the upstream state is reverted from `Pending` to
- // `NoChannel`.
- // - If initialization succeeds, we configure the extranonce factory, create a new
- // `ExtendedChannel` and `JobFactory`, and update the upstream state from `Pending` to
- // `Connected`.
- //
- // Once the upstream state transitions to `Connected`, all pending downstream requests are
- // processed, and downstream channels are opened accordingly.
- async fn handle_open_extended_mining_channel_success(
- &mut self,
- _server_id: Option,
- msg: OpenExtendedMiningChannelSuccess<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
-
- let coinbase_outputs = self
- .channel_manager_data
- .super_safe_lock(|data| data.coinbase_outputs.clone());
-
- let outputs = deserialize_outputs(coinbase_outputs)
- .map_err(|_| JDCError::DeclaredJobHasBadCoinbaseOutputs)?;
-
- let (channel_state, template, custom_job, close_channel) =
- self.channel_manager_data.super_safe_lock(|data| {
- let Some(pending_request) = data.pending_downstream_requests.front() else {
- self.upstream_state.set(UpstreamState::NoChannel);
- let close_channel =
- create_close_channel_msg(msg.channel_id, "downstream not available");
- return (self.upstream_state.get(), None, None, Some(close_channel));
- };
-
- let hashrate = match pending_request {
- PendingChannelRequest::ExtendedChannel(m) => m.nominal_hash_rate,
- PendingChannelRequest::StandardChannel(m) => m.nominal_hash_rate,
- };
-
- let prefix_len = msg.extranonce_prefix.len();
-
- let total_len = prefix_len + msg.extranonce_size as usize;
- let range_0 = 0..prefix_len;
- let range_1 = prefix_len..prefix_len + JDC_SEARCH_SPACE_BYTES;
- let range_2 = prefix_len + JDC_SEARCH_SPACE_BYTES..total_len;
-
- debug!(
- prefix_len,
- extranonce_size = msg.extranonce_size,
- total_len,
- "Calculated extranonce ranges"
- );
-
- let extranonces = match ExtendedExtranonce::from_upstream_extranonce(
- msg.extranonce_prefix.clone().into(),
- range_0,
- range_1,
- range_2,
- ) {
- Ok(e) => e,
- Err(e) => {
- warn!("Failed to build extranonce factory: {e:?}");
- self.upstream_state.set(UpstreamState::NoChannel);
- let close_channel =
- create_close_channel_msg(msg.channel_id, "downstream not available");
- return (self.upstream_state.get(), None, None, Some(close_channel));
- }
- };
-
- let job_factory = JobFactory::new(
- true,
- data.pool_tag_string.clone(),
- Some(self.miner_tag_string.clone()),
- );
-
- let mut extended_channel = ExtendedChannel::new(
- msg.channel_id,
- self.user_identity.clone(),
- msg.extranonce_prefix.to_vec(),
- Target::from_le_bytes(msg.target.inner_as_ref().try_into().unwrap()),
- hashrate,
- true,
- msg.extranonce_size,
- );
-
- if let Some(ref mut prevhash) = data.last_new_prev_hash {
- _ = extended_channel.on_chain_tip_update(prevhash.clone().into());
- debug!("Applied last_new_prev_hash to new extended channel");
- }
-
- let set_custom_job = if get_jd_mode() == JdMode::CoinbaseOnly {
- if let (Some(job_factory), Some(token), Some(template), Some(prevhash)) = (
- data.job_factory.as_mut(),
- data.allocate_tokens.clone(),
- data.last_future_template.clone(),
- data.last_new_prev_hash.clone(),
- ) {
- let request_id = data.request_id_factory.fetch_add(1, Ordering::Relaxed);
-
- let full_extranonce_size = extended_channel.get_full_extranonce_size();
-
- if let Ok(custom_job) = job_factory.new_custom_job(
- extended_channel.get_channel_id(),
- request_id,
- token.clone().mining_job_token,
- prevhash.clone().into(),
- template.clone(),
- outputs,
- full_extranonce_size,
- ) {
- let last_declare = DeclaredJob {
- declare_mining_job: None,
- template: template.into_static(),
- prev_hash: Some(prevhash.into_static()),
- set_custom_mining_job: Some(custom_job.clone().into_static()),
- coinbase_output: data.coinbase_outputs.clone(),
- tx_list: vec![],
- };
-
- data.last_declare_job_store.insert(request_id, last_declare);
- Some(custom_job)
- } else {
- None
- }
- } else {
- None
- }
- } else {
- None
- };
-
- data.extranonce_prefix_factory_extended = extranonces.clone();
- data.extranonce_prefix_factory_standard = extranonces;
- data.upstream_channel = Some(extended_channel);
- data.job_factory = Some(job_factory);
- self.upstream_state.set(UpstreamState::Connected);
-
- info!("Extended mining channel successfully initialized");
- (
- self.upstream_state.get(),
- data.last_future_template.clone(),
- set_custom_job,
- None,
- )
- });
-
- if channel_state == UpstreamState::Connected {
- if get_jd_mode() == JdMode::FullTemplate {
- if let Some(template) = template {
- let tx_data_request = AnyMessage::TemplateDistribution(
- TemplateDistribution::RequestTransactionData(RequestTransactionData {
- template_id: template.template_id,
- }),
- );
- let frame: StdFrame = tx_data_request.try_into()?;
- self.channel_manager_channel
- .tp_sender
- .send(frame)
- .await
- .map_err(|_e| JDCError::ChannelErrorSender)?;
- }
- }
-
- if get_jd_mode() == JdMode::CoinbaseOnly {
- if let Some(custom_job) = custom_job {
- let set_custom_job = AnyMessage::Mining(Mining::SetCustomMiningJob(custom_job));
- let frame: StdFrame = set_custom_job.try_into()?;
- self.channel_manager_channel
- .jd_sender
- .send(frame)
- .await
- .map_err(|_e| JDCError::ChannelErrorSender)?;
- _ = self.allocate_tokens(1).await;
- }
- }
-
- let pending_downstreams = self
- .channel_manager_data
- .super_safe_lock(|data| std::mem::take(&mut data.pending_downstream_requests));
-
- for pending_downstream in pending_downstreams {
- let message_type = pending_downstream.message_type();
- self.send_open_channel_request_to_mining_handler(
- pending_downstream.into(),
- message_type,
- )
- .await?;
- }
- }
-
- // In case of failure, close the channel with upstream.
- if let Some(close_channel) = close_channel {
- let close_channel = AnyMessage::Mining(Mining::CloseChannel(close_channel));
- let frame: StdFrame = close_channel.try_into()?;
- self.channel_manager_channel
- .upstream_sender
- .send(frame)
- .await
- .map_err(|_e| JDCError::ChannelErrorSender)?;
- _ = self.allocate_tokens(1).await;
- }
-
- Ok(())
- }
-
- // Handles `OpenMiningChannelError` messages received from upstream.
- //
- // Receiving this message is treated as malicious behavior, since JDC only supports
- // extended channels. When encountered, we immediately trigger the fallback mechanism
- // by transitioning the upstream state into a shutdown-fallback mode.
- async fn handle_open_mining_channel_error(
- &mut self,
- _server_id: Option,
- msg: OpenMiningChannelError<'_>,
- ) -> Result<(), Self::Error> {
- warn!("Received: {}", msg);
- warn!("β οΈ Cannot open extended channel with the upstream server, preparing fallback.");
-
- _ = self
- .channel_manager_channel
- .status_sender
- .send(Status {
- state: State::UpstreamShutdownFallback(JDCError::Shutdown),
- })
- .await;
- Ok(())
- }
-
- // Handles `UpdateChannelError` messages from upstream.
- async fn handle_update_channel_error(
- &mut self,
- _server_id: Option,
- msg: UpdateChannelError<'_>,
- ) -> Result<(), Self::Error> {
- warn!("Received: {}", msg);
- Ok(())
- }
-
- // Handles `CloseChannel` messages from upstream.
- //
- // Upon receiving this message, the upstream channel is immediately closed and
- // the system transitions into the upstream shutdown fallback state.
- async fn handle_close_channel(
- &mut self,
- _server_id: Option,
- msg: CloseChannel<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
-
- self.channel_manager_data.super_safe_lock(|data| {
- data.upstream_channel = None;
- });
- _ = self
- .channel_manager_channel
- .status_sender
- .send(Status {
- state: State::UpstreamShutdownFallback(JDCError::Shutdown),
- })
- .await;
- Ok(())
- }
-
- // Handles `SetExtranoncePrefix` messages from upstream.
- //
- // When received, this updates the current extranonce prefix and rebuilds both the
- // standard and extended extranonce factories. Each active downstream channel is then
- // assigned a new extranonce prefix, and a corresponding `SetExtranoncePrefix` message
- // is sent downstream to synchronize state.
- async fn handle_set_extranonce_prefix(
- &mut self,
- _server_id: Option,
- msg: SetExtranoncePrefix<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
- let messages_results =
- self.channel_manager_data
- .super_safe_lock(|channel_manager_data| {
- let mut messages_results: Vec> = vec![];
- if let Some(upstream_channel) = channel_manager_data.upstream_channel.as_mut() {
- if let Err(e) =
- upstream_channel.set_extranonce_prefix(msg.extranonce_prefix.to_vec())
- {
- return Err(JDCError::ChannelSv2(
- ChannelSv2Error::ExtendedChannelClientSide(e),
- ));
- }
-
- let new_prefix_len = msg.extranonce_prefix.len();
- let rollable_extranonce_size =
- upstream_channel.get_rollable_extranonce_size();
- let full_extranonce_size =
- new_prefix_len + rollable_extranonce_size as usize;
- if full_extranonce_size > MAX_EXTRANONCE_LEN {
- return Err(JDCError::ExtranonceSizeTooLarge);
- }
-
- let range_0 = 0..new_prefix_len;
- let range_1 = new_prefix_len..new_prefix_len + JDC_SEARCH_SPACE_BYTES;
- let range_2 = new_prefix_len + JDC_SEARCH_SPACE_BYTES..full_extranonce_size;
-
- debug!(
- new_prefix_len,
- rollable_extranonce_size,
- full_extranonce_size,
- "Calculated extranonce ranges"
- );
- let extranonces = match ExtendedExtranonce::from_upstream_extranonce(
- msg.extranonce_prefix.clone().into(),
- range_0,
- range_1,
- range_2,
- ) {
- Ok(e) => e,
- Err(e) => {
- warn!("Failed to build extranonce factory: {e:?}");
- return Err(JDCError::ExtranoncePrefixFactoryError(e));
- }
- };
-
- channel_manager_data.extranonce_prefix_factory_extended =
- extranonces.clone();
- channel_manager_data.extranonce_prefix_factory_standard = extranonces;
-
- for (downstream_id, downstream) in
- channel_manager_data.downstream.iter_mut()
- {
- downstream.downstream_data.super_safe_lock(|data| {
- for (channel_id, standard_channel) in
- data.standard_channels.iter_mut()
- {
- match channel_manager_data
- .extranonce_prefix_factory_standard
- .next_prefix_standard()
- {
- Ok(prefix) => match standard_channel
- .set_extranonce_prefix(prefix.clone().to_vec())
- {
- Ok(_) => {
- messages_results.push(Ok((
- *downstream_id,
- Mining::SetExtranoncePrefix(
- SetExtranoncePrefix {
- channel_id: *channel_id,
- extranonce_prefix: prefix.into(),
- },
- ),
- )
- .into()));
- }
- Err(e) => {
- messages_results.push(Err(JDCError::ChannelSv2(
- ChannelSv2Error::StandardChannelServerSide(e),
- )));
- }
- },
- Err(e) => {
- messages_results.push(Err(
- JDCError::ExtranoncePrefixFactoryError(e),
- ));
- }
- }
- }
- for (channel_id, extended_channel) in
- data.extended_channels.iter_mut()
- {
- match channel_manager_data
- .extranonce_prefix_factory_extended
- .next_prefix_extended(
- extended_channel.get_rollable_extranonce_size()
- as usize,
- ) {
- Ok(prefix) => match extended_channel
- .set_extranonce_prefix(prefix.clone().to_vec())
- {
- Ok(_) => {
- messages_results.push(Ok((
- *downstream_id,
- Mining::SetExtranoncePrefix(
- SetExtranoncePrefix {
- channel_id: *channel_id,
- extranonce_prefix: prefix.into(),
- },
- ),
- )
- .into()));
- }
- Err(e) => {
- messages_results.push(Err(JDCError::ChannelSv2(
- ChannelSv2Error::ExtendedChannelServerSide(e),
- )));
- }
- },
- Err(e) => {
- messages_results.push(Err(
- JDCError::ExtranoncePrefixFactoryError(e),
- ));
- }
- }
- }
- });
- }
- }
- Ok(messages_results)
- })?;
-
- for message in messages_results.into_iter().flatten() {
- message.forward(&self.channel_manager_channel).await;
- }
- Ok(())
- }
-
- // Handles `SubmitSharesSuccess` messages from upstream.
- async fn handle_submit_shares_success(
- &mut self,
- _server_id: Option,
- msg: SubmitSharesSuccess,
- ) -> Result<(), Self::Error> {
- info!("Received: {} β
", msg);
- Ok(())
- }
-
- // Handles `SubmitSharesError` messages from upstream.
- async fn handle_submit_shares_error(
- &mut self,
- _server_id: Option,
- msg: SubmitSharesError<'_>,
- ) -> Result<(), Self::Error> {
- warn!("Received: {} β", msg);
- Ok(())
- }
-
- // Handles `NewMiningJob` messages from upstream. JDC ignores it.
- async fn handle_new_mining_job(
- &mut self,
- _server_id: Option,
- msg: NewMiningJob<'_>,
- ) -> Result<(), Self::Error> {
- warn!("Received: {}", msg);
- warn!("β οΈ JDC does not expect jobs from the upstream server β ignoring.");
- Ok(())
- }
-
- // Handles `NewExtendedMiningJob` messages from upstream. JDC ignores it.
- async fn handle_new_extended_mining_job(
- &mut self,
- _server_id: Option,
- msg: NewExtendedMiningJob<'_>,
- ) -> Result<(), Self::Error> {
- warn!("Received: {}", msg);
- warn!("β οΈ JDC does not expect jobs from the upstream server β ignoring.");
- Ok(())
- }
-
- // Handles `SetNewPrevHash` messages from upstream. JDC ignores it.
- async fn handle_set_new_prev_hash(
- &mut self,
- _server_id: Option,
- msg: SetNewPrevHash<'_>,
- ) -> Result<(), Self::Error> {
- warn!("Received: {}", msg);
- warn!("β οΈ JDC does not expect prevhash updates from the upstream server β ignoring.");
- Ok(())
- }
-
- // Handles `SetCustomMiningJobSuccess` messages from upstream.
- //
- // On success:
- // - Updates the `job_id_to_template_id` mapping.
- // - Updates the channel state accordingly.
- // - Removes the associated `last_declare_job`, completing its lifecycle.
- async fn handle_set_custom_mining_job_success(
- &mut self,
- _server_id: Option,
- msg: SetCustomMiningJobSuccess,
- ) -> Result<(), Self::Error> {
- info!("Received: {} β
", msg);
- self.channel_manager_data.super_safe_lock(|data| {
- if let Some(last_declare_job) = data.last_declare_job_store.remove(&msg.request_id) {
- let template_id = last_declare_job.template.template_id;
- data.last_declare_job_store
- .retain(|_, job| job.template.template_id != template_id);
-
- data.template_id_to_upstream_job_id
- .insert(last_declare_job.template.template_id, msg.job_id as u64);
- debug!(job_id = msg.job_id, "Mapped custom job into template store");
- if let (Some(upstream_channel), Some(set_custom_job)) = (
- data.upstream_channel.as_mut(),
- last_declare_job.set_custom_mining_job,
- ) {
- if let Err(e) =
- upstream_channel.on_set_custom_mining_job_success(set_custom_job, msg)
- {
- error!("Custom mining job success validation failed: {e:#?}");
- }
- }
- } else {
- warn!(
- request_id = msg.request_id,
- "No matching declare job found for custom job success"
- );
- }
- });
- Ok(())
- }
-
- // Handles a `SetCustomMiningJobError` from upstream.
- //
- // Receiving this is treated as malicious behavior, so we immediately
- // trigger the fallback mechanism.
- async fn handle_set_custom_mining_job_error(
- &mut self,
- _server_id: Option,
- msg: SetCustomMiningJobError<'_>,
- ) -> Result<(), Self::Error> {
- warn!("β οΈ Received: {} β", msg);
- warn!("β οΈ Starting fallback mechanism.");
- _ = self
- .channel_manager_channel
- .status_sender
- .send(Status {
- state: State::UpstreamShutdownFallback(JDCError::Shutdown),
- })
- .await;
- Ok(())
- }
-
- // Handles a `SetTarget` message from upstream.
- //
- // Updates the corresponding upstream channel's target state.
- async fn handle_set_target(
- &mut self,
- _server_id: Option,
- msg: SetTarget<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
- self.channel_manager_data.super_safe_lock(|data| {
- if let Some(ref mut upstream) = data.upstream_channel {
- upstream.set_target(Target::from_le_bytes(
- msg.maximum_target.clone().as_ref().try_into().unwrap(),
- ));
- }
- });
- Ok(())
- }
-
- // Handles `SetGroupChannel` messages from upstream. JDC ignores it.
- async fn handle_set_group_channel(
- &mut self,
- _server_id: Option,
- msg: SetGroupChannel<'_>,
- ) -> Result<(), Self::Error> {
- warn!("Received: {}", msg);
- warn!("β οΈ JDC does not expect group channel updates from the upstream server β ignoring.");
- Ok(())
- }
-}
diff --git a/roles/jd-client/src/lib/config.rs b/roles/jd-client/src/lib/config.rs
deleted file mode 100644
index de72efb5be..0000000000
--- a/roles/jd-client/src/lib/config.rs
+++ /dev/null
@@ -1,294 +0,0 @@
-use serde::Deserialize;
-use std::{
- net::SocketAddr,
- path::{Path, PathBuf},
- str::FromStr,
-};
-use stratum_apps::{
- config_helpers::CoinbaseRewardScript,
- key_utils::{Secp256k1PublicKey, Secp256k1SecretKey},
- stratum_core::bitcoin::{Amount, TxOut},
-};
-
-#[derive(Debug, Deserialize, Clone)]
-pub struct JobDeclaratorClientConfig {
- // The address on which the JDC will listen for incoming connections when acting as an
- // upstream.
- listening_address: SocketAddr,
- // The maximum supported SV2 protocol version.
- max_supported_version: u16,
- // The minimum supported SV2 protocol version.
- min_supported_version: u16,
- // The public key used by this JDC for noise encryption.
- authority_public_key: Secp256k1PublicKey,
- /// The secret key used by this JDC for noise encryption.
- authority_secret_key: Secp256k1SecretKey,
- /// The validity period (in seconds) for the certificate used in noise.
- cert_validity_sec: u64,
- /// The address of the TP that this JDC will connect to.
- tp_address: String,
- /// The expected public key of the TP's authority for authentication (optional).
- tp_authority_public_key: Option,
- /// A list of upstream Job Declarator Servers (JDS) that this JDC can connect to.
- /// JDC can fallover between these upstreams.
- upstreams: Vec,
- /// This is only used during solo-mining.
- pub coinbase_reward_script: CoinbaseRewardScript,
- /// A signature string identifying this JDC instance.
- jdc_signature: String,
- /// The path to the log file where JDC will write logs.
- log_file: Option,
- /// User Identity
- user_identity: String,
- /// Shares per minute
- shares_per_minute: f64,
- /// share batch size
- share_batch_size: u64,
- /// JDC mode: FullTemplate or CoinbaseOnly
- #[serde(deserialize_with = "deserialize_jdc_mode", default)]
- pub mode: ConfigJDCMode,
-}
-
-impl JobDeclaratorClientConfig {
- #[allow(clippy::too_many_arguments)]
- pub fn new(
- listening_address: SocketAddr,
- protocol_config: ProtocolConfig,
- user_identity: String,
- shares_per_minute: f64,
- share_batch_size: u64,
- pool_config: PoolConfig,
- tp_config: TPConfig,
- upstreams: Vec,
- jdc_signature: String,
- jdc_mode: Option,
- ) -> Self {
- Self {
- listening_address,
- max_supported_version: protocol_config.max_supported_version,
- min_supported_version: protocol_config.min_supported_version,
- authority_public_key: pool_config.authority_public_key,
- authority_secret_key: pool_config.authority_secret_key,
- cert_validity_sec: tp_config.cert_validity_sec,
- tp_address: tp_config.tp_address,
- tp_authority_public_key: tp_config.tp_authority_public_key,
- upstreams,
- coinbase_reward_script: protocol_config.coinbase_reward_script,
- jdc_signature,
- log_file: None,
- user_identity,
- shares_per_minute,
- share_batch_size,
- mode: jdc_mode
- .map(|s| s.parse::().unwrap_or_default())
- .unwrap_or_default(),
- }
- }
-
- /// Returns the listening address of the Job Declartor Client.
- pub fn listening_address(&self) -> &SocketAddr {
- &self.listening_address
- }
-
- /// Returns the list of upstreams.
- ///
- /// JDC will try to fallback to the next upstream in case of failure of the current one.
- pub fn upstreams(&self) -> &Vec {
- &self.upstreams
- }
-
- /// Returns the authority public key.
- pub fn authority_public_key(&self) -> &Secp256k1PublicKey {
- &self.authority_public_key
- }
-
- /// Returns the authority secret key.
- pub fn authority_secret_key(&self) -> &Secp256k1SecretKey {
- &self.authority_secret_key
- }
-
- /// Returns the certificate validity in seconds.
- pub fn cert_validity_sec(&self) -> u64 {
- self.cert_validity_sec
- }
-
- /// Returns Template Provider address.
- pub fn tp_address(&self) -> &str {
- &self.tp_address
- }
-
- /// Returns Template Provider authority public key.
- pub fn tp_authority_public_key(&self) -> Option<&Secp256k1PublicKey> {
- self.tp_authority_public_key.as_ref()
- }
-
- /// Returns the minimum supported version.
- pub fn min_supported_version(&self) -> u16 {
- self.min_supported_version
- }
-
- /// Returns the maximum supported version.
- pub fn max_supported_version(&self) -> u16 {
- self.max_supported_version
- }
-
- /// Returns the JDC signature.
- pub fn jdc_signature(&self) -> &str {
- &self.jdc_signature
- }
-
- pub fn get_txout(&self) -> TxOut {
- TxOut {
- value: Amount::from_sat(0),
- script_pubkey: self.coinbase_reward_script.script_pubkey().to_owned(),
- }
- }
-
- pub fn log_file(&self) -> Option<&Path> {
- self.log_file.as_deref()
- }
- pub fn set_log_file(&mut self, log_file: Option) {
- if let Some(log_file) = log_file {
- self.log_file = Some(log_file);
- }
- }
- pub fn user_identity(&self) -> &str {
- &self.user_identity
- }
-
- pub fn shares_per_minute(&self) -> f64 {
- self.shares_per_minute
- }
-
- pub fn share_batch_size(&self) -> u64 {
- self.share_batch_size
- }
-}
-
-#[derive(Debug, Deserialize, Clone, Default)]
-#[serde(rename_all = "UPPERCASE")]
-pub enum ConfigJDCMode {
- #[default]
- FullTemplate,
- CoinbaseOnly,
-}
-
-impl std::str::FromStr for ConfigJDCMode {
- type Err = ();
-
- fn from_str(s: &str) -> Result {
- match s.to_uppercase().as_str() {
- "COINBASEONLY" => Ok(ConfigJDCMode::CoinbaseOnly),
- _ => Ok(ConfigJDCMode::FullTemplate),
- }
- }
-}
-
-fn deserialize_jdc_mode<'de, D>(deserializer: D) -> Result
-where
- D: serde::Deserializer<'de>,
-{
- let s: String = String::deserialize(deserializer)?;
- Ok(ConfigJDCMode::from_str(&s).unwrap_or_default())
-}
-
-/// Represents pool specific encryption keys.
-pub struct PoolConfig {
- authority_public_key: Secp256k1PublicKey,
- authority_secret_key: Secp256k1SecretKey,
-}
-
-impl PoolConfig {
- /// Creates a new instance of [`PoolConfig`].
- pub fn new(
- authority_public_key: Secp256k1PublicKey,
- authority_secret_key: Secp256k1SecretKey,
- ) -> Self {
- Self {
- authority_public_key,
- authority_secret_key,
- }
- }
-}
-
-/// Represent template provider config for JDC to connect.
-pub struct TPConfig {
- // The validity period (in seconds) expected for the Template Provider's certificate.
- cert_validity_sec: u64,
- // The network address of the Template Provider.
- tp_address: String,
- // The expected public key of the Template Provider's authority (optional).
- tp_authority_public_key: Option,
-}
-
-impl TPConfig {
- // Creates a new instance of [`TPConfig`].
- pub fn new(
- cert_validity_sec: u64,
- tp_address: String,
- tp_authority_public_key: Option,
- ) -> Self {
- Self {
- cert_validity_sec,
- tp_address,
- tp_authority_public_key,
- }
- }
-}
-
-/// Represent protocol versioning the JDC supports.
-pub struct ProtocolConfig {
- // The maximum supported SV2 protocol version.
- max_supported_version: u16,
- // The minimum supported SV2 protocol version.
- min_supported_version: u16,
- // A coinbase output to be included in block templates.
- coinbase_reward_script: CoinbaseRewardScript,
-}
-
-impl ProtocolConfig {
- // Creates a new instance of [`ProtocolConfig`].
- pub fn new(
- max_supported_version: u16,
- min_supported_version: u16,
- coinbase_reward_script: CoinbaseRewardScript,
- ) -> Self {
- Self {
- max_supported_version,
- min_supported_version,
- coinbase_reward_script,
- }
- }
-}
-
-/// Represents necessary fields required to connect to JDS
-#[derive(Debug, Deserialize, Clone)]
-pub struct Upstream {
- // The public key of the upstream pool's authority for authentication.
- pub authority_pubkey: Secp256k1PublicKey,
- // The address of the upstream pool's main server.
- pub pool_address: String,
- pub pool_port: u16,
- // The network address of the JDS.
- pub jds_address: String,
- pub jds_port: u16,
-}
-
-impl Upstream {
- /// Creates a new instance of [`Upstream`].
- pub fn new(
- authority_pubkey: Secp256k1PublicKey,
- pool_address: String,
- pool_port: u16,
- jds_address: String,
- jds_port: u16,
- ) -> Self {
- Self {
- authority_pubkey,
- pool_address,
- pool_port,
- jds_address,
- jds_port,
- }
- }
-}
diff --git a/roles/jd-client/src/lib/downstream/message_handler.rs b/roles/jd-client/src/lib/downstream/message_handler.rs
deleted file mode 100644
index 552fec7601..0000000000
--- a/roles/jd-client/src/lib/downstream/message_handler.rs
+++ /dev/null
@@ -1,89 +0,0 @@
-use crate::{downstream::Downstream, error::JDCError, utils::StdFrame};
-use std::convert::TryInto;
-use stratum_apps::stratum_core::{
- common_messages_sv2::{
- has_requires_std_job, has_work_selection, Protocol, SetupConnection, SetupConnectionError,
- SetupConnectionSuccess,
- },
- handlers_sv2::HandleCommonMessagesFromClientAsync,
- parsers_sv2::AnyMessage,
-};
-use tracing::info;
-
-impl HandleCommonMessagesFromClientAsync for Downstream {
- type Error = JDCError;
- // Handles the initial [`SetupConnection`] message from a downstream client.
- //
- // This method validates that the connection request is compatible with the
- // supported mining protocol and feature set. The flow is:
- //
- // 1. Protocol validation
- // - Only the `MiningProtocol` is supported.
- // - If the client requests another protocol, the connection is rejected with a
- // [`SetupConnectionError`] (`unsupported-protocol`).
- //
- // 2. Feature flag validation
- // - Work selection (`work_selection`) is not allowed.
- // - If requested, the connection is rejected with a [`SetupConnectionError`]
- // (`unsupported-feature-flags`).
- //
- // 3. Standard job requirement
- // - If the downstream sets the `requires_standard_job` flag, it is recorded in
- // [`DownstreamData::require_std_job`].
- //
- // 4. Successful setup
- // - If all validations pass, a [`SetupConnectionSuccess`] message is
- async fn handle_setup_connection(
- &mut self,
- _client_id: Option,
- msg: SetupConnection<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
-
- if msg.protocol != Protocol::MiningProtocol {
- info!("Rejecting connection: SetupConnection asking for other protocols than mining protocol.");
- let response = SetupConnectionError {
- flags: 0,
- error_code: "unsupported-protocol"
- .to_string()
- .try_into()
- .expect("error code must be valid string"),
- };
- let frame: StdFrame = AnyMessage::Common(response.into_static().into()).try_into()?;
- _ = self.downstream_channel.downstream_sender.send(frame).await;
-
- return Err(JDCError::Shutdown);
- }
-
- if has_work_selection(msg.flags) {
- info!("Rejecting: work selection not allowed.");
- let response = SetupConnectionError {
- flags: 0b0000_0000_0000_0010,
- error_code: "unsupported-feature-flags"
- .to_string()
- .try_into()
- .expect("error code must be valid string"),
- };
- let frame: StdFrame = AnyMessage::Common(response.into_static().into())
- .try_into()
- .unwrap();
- _ = self.downstream_channel.downstream_sender.send(frame).await;
-
- return Err(JDCError::Shutdown);
- }
-
- if has_requires_std_job(msg.flags) {
- self.downstream_data
- .super_safe_lock(|data| data.require_std_job = true);
- }
- let response = SetupConnectionSuccess {
- used_version: 2,
- flags: msg.flags,
- };
- let frame: StdFrame = AnyMessage::Common(response.into_static().into()).try_into()?;
-
- _ = self.downstream_channel.downstream_sender.send(frame).await;
-
- Ok(())
- }
-}
diff --git a/roles/jd-client/src/lib/downstream/mod.rs b/roles/jd-client/src/lib/downstream/mod.rs
deleted file mode 100644
index a9a37d8014..0000000000
--- a/roles/jd-client/src/lib/downstream/mod.rs
+++ /dev/null
@@ -1,284 +0,0 @@
-use std::{collections::HashMap, sync::Arc};
-
-use async_channel::{unbounded, Receiver, Sender};
-use stratum_apps::{
- custom_mutex::Mutex,
- network_helpers::noise_stream::NoiseTcpStream,
- stratum_core::{
- channels_sv2::server::{
- extended::ExtendedChannel,
- group::GroupChannel,
- jobs::{extended::ExtendedJob, job_store::DefaultJobStore, standard::StandardJob},
- standard::StandardChannel,
- },
- common_messages_sv2::MESSAGE_TYPE_SETUP_CONNECTION,
- handlers_sv2::HandleCommonMessagesFromClientAsync,
- parsers_sv2::{AnyMessage, IsSv2Message},
- },
-};
-
-use tokio::sync::broadcast;
-use tracing::{debug, error, warn};
-
-use crate::{
- error::JDCError,
- status::{handle_error, Status, StatusSender},
- task_manager::TaskManager,
- utils::{
- protocol_message_type, spawn_io_tasks, Message, MessageType, SV2Frame, ShutdownMessage,
- StdFrame,
- },
-};
-
-mod message_handler;
-
-/// Holds state related to a downstream connection's mining channels.
-///
-/// This includes:
-/// - Whether the downstream requires a standard job (`require_std_job`).
-/// - An optional [`GroupChannel`] if group channeling is used.
-/// - Active [`ExtendedChannel`]s keyed by channel ID.
-/// - Active [`StandardChannel`]s keyed by channel ID.
-pub struct DownstreamData {
- pub require_std_job: bool,
- pub group_channels: Option>>>,
- pub extended_channels:
- HashMap>>>,
- pub standard_channels:
- HashMap>>>,
-}
-
-/// Communication layer for a downstream connection.
-///
-/// Provides the messaging primitives for interacting with the
-/// channel manager and the downstream peer:
-/// - `channel_manager_sender`: sends frames to the channel manager.
-/// - `channel_manager_receiver`: receives messages from the channel manager.
-/// - `downstream_sender`: sends frames to the downstream.
-/// - `downstream_receiver`: receives frames from the downstream.
-#[derive(Clone)]
-pub struct DownstreamChannel {
- channel_manager_sender: Sender<(u32, SV2Frame)>,
- channel_manager_receiver: broadcast::Sender<(u32, Message)>,
- downstream_sender: Sender,
- downstream_receiver: Receiver,
-}
-
-/// Represents a downstream client connected to this node.
-#[derive(Clone)]
-pub struct Downstream {
- pub downstream_data: Arc>,
- downstream_channel: DownstreamChannel,
- pub downstream_id: u32,
-}
-
-impl Downstream {
- /// Creates a new [`Downstream`] instance and spawns the necessary I/O tasks.
- pub fn new(
- downstream_id: u32,
- channel_manager_sender: Sender<(u32, SV2Frame)>,
- channel_manager_receiver: broadcast::Sender<(u32, Message)>,
- noise_stream: NoiseTcpStream,
- notify_shutdown: broadcast::Sender,
- task_manager: Arc,
- status_sender: Sender,
- ) -> Self {
- let (noise_stream_reader, noise_stream_writer) = noise_stream.into_split();
- let status_sender = StatusSender::Downstream {
- downstream_id,
- tx: status_sender,
- };
- let (inbound_tx, inbound_rx) = unbounded::();
- let (outbound_tx, outbound_rx) = unbounded::();
- spawn_io_tasks(
- task_manager,
- noise_stream_reader,
- noise_stream_writer,
- outbound_rx,
- inbound_tx,
- notify_shutdown,
- status_sender,
- );
-
- let downstream_channel = DownstreamChannel {
- channel_manager_receiver,
- channel_manager_sender,
- downstream_sender: outbound_tx,
- downstream_receiver: inbound_rx,
- };
- let downstream_data = Arc::new(Mutex::new(DownstreamData {
- require_std_job: false,
- extended_channels: HashMap::new(),
- standard_channels: HashMap::new(),
- group_channels: None,
- }));
- Downstream {
- downstream_channel,
- downstream_data,
- downstream_id,
- }
- }
-
- /// Starts the downstream loop.
- ///
- /// Responsibilities:
- /// - Performs the initial `SetupConnection` handshake with the downstream.
- /// - Forwards mining-related messages to the channel manager.
- /// - Forwards channel manager messages back to the downstream peer.
- pub async fn start(
- mut self,
- notify_shutdown: broadcast::Sender,
- status_sender: Sender,
- task_manager: Arc,
- ) {
- let status_sender = StatusSender::Downstream {
- downstream_id: self.downstream_id,
- tx: status_sender,
- };
-
- let mut shutdown_rx = notify_shutdown.subscribe();
-
- // Setup initial connection
- if let Err(e) = self.setup_connection_with_downstream().await {
- error!(?e, "Failed to set up downstream connection");
- handle_error(&status_sender, e).await;
- return;
- }
-
- let mut receiver = self.downstream_channel.channel_manager_receiver.subscribe();
- task_manager.spawn(async move {
- loop {
- let self_clone_1 = self.clone();
- let downstream_id = self_clone_1.downstream_id;
- let self_clone_2 = self.clone();
- tokio::select! {
- message = shutdown_rx.recv() => {
- match message {
- Ok(ShutdownMessage::ShutdownAll) => {
- debug!("Downstream {downstream_id}: Received global shutdown");
- break;
- }
- Ok(ShutdownMessage::DownstreamShutdown(id)) if downstream_id == id => {
- debug!("Downstream {downstream_id}: Received downstream {id} shutdown");
- break;
- }
- Ok(ShutdownMessage::JobDeclaratorShutdownFallback(_)) => {
- debug!("Downstream {downstream_id}: Received job declaratorShutdown shutdown");
- break;
- }
- Ok(ShutdownMessage::UpstreamShutdownFallback(_)) => {
- debug!("Downstream {downstream_id}: Received job Upstream shutdown");
- break;
- }
- _ => {}
- }
- }
- res = self_clone_1.handle_downstream_message() => {
- if let Err(e) = res {
- error!(?e, "Error handling downstream message for {downstream_id}");
- handle_error(&status_sender, e).await;
- break;
- }
- }
- res = self_clone_2.handle_channel_manager_message(&mut receiver) => {
- if let Err(e) = res {
- error!(?e, "Error handling channel manager message for {downstream_id}");
- handle_error(&status_sender, e).await;
- break;
- }
- }
-
- }
- }
- warn!("Downstream: unified message loop exited.");
- });
- }
-
- // Performs the initial handshake with a downstream peer.
- async fn setup_connection_with_downstream(&mut self) -> Result<(), JDCError> {
- let mut frame = self.downstream_channel.downstream_receiver.recv().await?;
-
- let Some(message_type) = frame.get_header().map(|m| m.msg_type()) else {
- return Err(JDCError::UnexpectedMessage(0));
- };
- if message_type == MESSAGE_TYPE_SETUP_CONNECTION {
- self.handle_common_message_frame_from_client(None, message_type, frame.payload())
- .await?;
- return Ok(());
- }
- Err(JDCError::UnexpectedMessage(message_type))
- }
-
- // Handles messages sent from the channel manager to this downstream.
- async fn handle_channel_manager_message(
- self,
- receiver: &mut broadcast::Receiver<(u32, AnyMessage<'static>)>,
- ) -> Result<(), JDCError> {
- let (downstream_id, frame) = match receiver.recv().await {
- Ok(msg) => msg,
- Err(e) => {
- warn!(?e, "Broadcast receive failed");
- return Ok(());
- }
- };
-
- if downstream_id != self.downstream_id {
- debug!(
- ?downstream_id,
- "Message ignored for non-matching downstream"
- );
- return Ok(());
- }
-
- let message_type = frame.message_type();
- let std_frame = match StdFrame::from_message(frame, message_type, 0, true) {
- Some(f) => f,
- None => {
- debug!("Invalid frame conversion; skipping message");
- return Ok(());
- }
- };
-
- self.downstream_channel
- .downstream_sender
- .send(std_frame)
- .await
- .map_err(|e| {
- error!(?e, "Downstream send failed");
- JDCError::CodecNoise(
- stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage,
- )
- })?;
-
- Ok(())
- }
-
- // Handles incoming messages from the downstream peer.
- async fn handle_downstream_message(self) -> Result<(), JDCError> {
- let sv2_frame = self.downstream_channel.downstream_receiver.recv().await?;
-
- let Some(message_type) = sv2_frame.get_header().map(|h| h.msg_type()) else {
- return Ok(());
- };
-
- if protocol_message_type(message_type) != MessageType::Mining {
- warn!(
- ?message_type,
- "Received unsupported message type from downstream."
- );
- return Ok(());
- }
-
- debug!("Received mining SV2 frame from downstream.");
- self.downstream_channel
- .channel_manager_sender
- .send((self.downstream_id, sv2_frame))
- .await
- .map_err(|e| {
- error!(error=?e, "Failed to send mining message to channel manager.");
- JDCError::ChannelErrorSender
- })?;
-
- Ok(())
- }
-}
diff --git a/roles/jd-client/src/lib/error.rs b/roles/jd-client/src/lib/error.rs
deleted file mode 100644
index f9a10997c2..0000000000
--- a/roles/jd-client/src/lib/error.rs
+++ /dev/null
@@ -1,336 +0,0 @@
-//! ## Error Module
-//!
-//! Defines [`Error`], the central error enum used throughout the Job Declarator Client (JDC).
-//!
-//! It unifies errors from:
-//! - I/O operations
-//! - Channels (send/recv)
-//! - SV2 stack: Binary, Codec, Noise, Framing, RolesLogic
-//! - Locking logic (PoisonError)
-//! - Domain-specific issues
-//!
-//! This module ensures that all errors can be passed around consistently, including across async
-//! boundaries.
-use ext_config::ConfigError;
-use std::fmt;
-use stratum_apps::{
- network_helpers,
- stratum_core::{
- binary_sv2, bitcoin,
- channels_sv2::{
- client::error::ExtendedChannelError as ExtendedChannelClientError,
- server::error::{
- ExtendedChannelError as ExtendedChannelServerError, GroupChannelError,
- StandardChannelError,
- },
- },
- framing_sv2,
- handlers_sv2::HandlerErrorType,
- mining_sv2::ExtendedExtranonceError,
- noise_sv2,
- parsers_sv2::ParserError,
- },
-};
-use tokio::{sync::broadcast, time::error::Elapsed};
-
-#[derive(Debug)]
-pub enum ChannelSv2Error {
- ExtendedChannelClientSide(ExtendedChannelClientError),
- ExtendedChannelServerSide(ExtendedChannelServerError),
- StandardChannelServerSide(StandardChannelError),
- GroupChannelServerSide(GroupChannelError),
-}
-
-#[derive(Debug)]
-pub enum JDCError {
- #[allow(dead_code)]
- VecToSlice32(Vec),
- /// Errors on bad CLI argument input.
- BadCliArgs,
- /// Errors on bad `config` TOML deserialize.
- BadConfigDeserialize(ConfigError),
- /// Errors from `binary_sv2` crate.
- BinarySv2(binary_sv2::Error),
- /// Errors on bad noise handshake.
- CodecNoise(noise_sv2::Error),
- /// Errors from `framing_sv2` crate.
- FramingSv2(framing_sv2::Error),
- /// Errors on bad `TcpStream` connection.
- Io(std::io::Error),
- /// Errors on bad `String` to `int` conversion.
- ParseInt(std::num::ParseIntError),
- #[allow(dead_code)]
- SubprotocolMining(String),
- // Locking Errors
- PoisonLock,
- TokioChannelErrorRecv(tokio::sync::broadcast::error::RecvError),
- Infallible(std::convert::Infallible),
- Parser(ParserError),
- /// Channel receiver error
- ChannelErrorReceiver(async_channel::RecvError),
- /// Channel sender error
- ChannelErrorSender,
- /// Broadcast channel receiver error
- BroadcastChannelErrorReceiver(broadcast::error::RecvError),
- Shutdown,
- NetworkHelpersError(network_helpers::Error),
- UnexpectedMessage(u8),
- InvalidUserIdentity(String),
- BitcoinEncodeError(bitcoin::consensus::encode::Error),
- InvalidSocketAddress(String),
- Timeout,
- LastDeclareJobNotFound(u32),
- ActiveJobNotFound(u32),
- TokenNotFound,
- TemplateNotFound(u64),
- DownstreamNotFoundWithChannelId(u32),
- DownstreamNotFound(u32),
- DownstreamIdNotFound,
- FutureTemplateNotPresent,
- LastNewPrevhashNotFound,
- VardiffNotFound(u32),
- TxDataError,
- FrameConversionError,
- FailedToCreateCustomJob,
- AllocateMiningJobTokenSuccessCoinbaseOutputsError,
- ChannelManagerHasBadCoinbaseOutputs,
- DeclaredJobHasBadCoinbaseOutputs,
- ExtranonceSizeTooLarge,
- FailedToCreateGroupChannel(GroupChannelError),
- ///Channel Errors
- ChannelSv2(ChannelSv2Error),
- ExtranoncePrefixFactoryError(ExtendedExtranonceError),
-}
-
-impl std::error::Error for JDCError {}
-
-impl fmt::Display for JDCError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use JDCError::*;
- match self {
- BadCliArgs => write!(f, "Bad CLI arg input"),
- BadConfigDeserialize(ref e) => write!(f, "Bad `config` TOML deserialize: `{e:?}`"),
- BinarySv2(ref e) => write!(f, "Binary SV2 error: `{e:?}`"),
- CodecNoise(ref e) => write!(f, "Noise error: `{e:?}"),
- FramingSv2(ref e) => write!(f, "Framing SV2 error: `{e:?}`"),
- Io(ref e) => write!(f, "I/O error: `{e:?}"),
- ParseInt(ref e) => write!(f, "Bad convert from `String` to `int`: `{e:?}`"),
- SubprotocolMining(ref e) => write!(f, "Subprotocol Mining Error: `{e:?}`"),
- PoisonLock => write!(f, "Poison Lock error"),
- ChannelErrorReceiver(ref e) => write!(f, "Channel receive error: `{e:?}`"),
- TokioChannelErrorRecv(ref e) => write!(f, "Channel receive error: `{e:?}`"),
- VecToSlice32(ref e) => write!(f, "Standard Error: `{e:?}`"),
- Infallible(ref e) => write!(f, "Infallible Error:`{e:?}`"),
- Parser(ref e) => write!(f, "Parser error: `{e:?}`"),
- BroadcastChannelErrorReceiver(ref e) => {
- write!(f, "Broadcast channel receive error: {e:?}")
- }
- ChannelErrorSender => write!(f, "Sender error"),
- Shutdown => write!(f, "Shutdown"),
- NetworkHelpersError(ref e) => write!(f, "Network error: {e:?}"),
- UnexpectedMessage(message_type) => write!(f, "Unexpected Message: {message_type}"),
- InvalidUserIdentity(_) => write!(f, "User ID is invalid"),
- BitcoinEncodeError(_) => write!(f, "Error generated during encoding"),
- InvalidSocketAddress(ref s) => write!(f, "Invalid socket address: {s}"),
- Timeout => write!(f, "Time out error"),
- LastDeclareJobNotFound(request_id) => {
- write!(f, "last declare job not found for request id: {request_id}")
- }
- ActiveJobNotFound(request_id) => {
- write!(f, "Active Job not found for request_id: {request_id}")
- }
- TokenNotFound => {
- write!(f, "Token Not found")
- }
- TemplateNotFound(template_id) => {
- write!(f, "Template not found, template_id: {template_id}")
- }
- DownstreamNotFoundWithChannelId(channel_id) => {
- write!(f, "Downstream not found with channel id: {channel_id}")
- }
- DownstreamNotFound(downstream_id) => {
- write!(
- f,
- "Downstream not found with downstream_id: {downstream_id}"
- )
- }
- DownstreamIdNotFound => {
- write!(f, "Downstream id not found")
- }
- FutureTemplateNotPresent => {
- write!(f, "Future template not present")
- }
- LastNewPrevhashNotFound => {
- write!(f, "Last new prevhash not found")
- }
- VardiffNotFound(channel_id) => {
- write!(f, "Vardiff not found for channel id: {channel_id:?}")
- }
- TxDataError => {
- write!(f, "Transaction data error")
- }
- FrameConversionError => {
- write!(f, "Could not convert message to frame")
- }
- FailedToCreateCustomJob => {
- write!(f, "failed to create custom job")
- }
- AllocateMiningJobTokenSuccessCoinbaseOutputsError => {
- write!(
- f,
- "AllocateMiningJobToken.Success coinbase outputs are not deserializable"
- )
- }
- ChannelManagerHasBadCoinbaseOutputs => {
- write!(f, "Channel Manager coinbase outputs are not deserializable")
- }
- DeclaredJobHasBadCoinbaseOutputs => {
- write!(f, "Declared job coinbase outputs are not deserializable")
- }
- ExtranonceSizeTooLarge => {
- write!(f, "Extranonce size too large")
- }
- FailedToCreateGroupChannel(ref e) => {
- write!(f, "Failed to create group channel: {e:?}")
- }
- ExtranoncePrefixFactoryError(e) => {
- write!(f, "Failed to create ExtranoncePrefixFactory: {e:?}")
- }
- ChannelSv2(channel_error) => {
- write!(f, "Channel error: {channel_error:?}")
- }
- }
- }
-}
-
-impl JDCError {
- fn is_non_critical_variant(&self) -> bool {
- matches!(
- self,
- JDCError::LastNewPrevhashNotFound
- | JDCError::FutureTemplateNotPresent
- | JDCError::LastDeclareJobNotFound(_)
- | JDCError::ActiveJobNotFound(_)
- | JDCError::TokenNotFound
- | JDCError::TemplateNotFound(_)
- | JDCError::DownstreamNotFoundWithChannelId(_)
- | JDCError::DownstreamNotFound(_)
- | JDCError::DownstreamIdNotFound
- | JDCError::VardiffNotFound(_)
- | JDCError::TxDataError
- | JDCError::FrameConversionError
- | JDCError::FailedToCreateCustomJob
- )
- }
-
- /// Adds basic priority to error types:
- /// todo: design a better error priority system.
- pub fn is_critical(&self) -> bool {
- if self.is_non_critical_variant() {
- tracing::error!("Non-critical error: {self}");
- return false;
- }
-
- true
- }
-}
-
-impl From for JDCError {
- fn from(e: ParserError) -> Self {
- JDCError::Parser(e)
- }
-}
-
-impl From for JDCError {
- fn from(e: binary_sv2::Error) -> Self {
- JDCError::BinarySv2(e)
- }
-}
-
-impl From for JDCError {
- fn from(e: noise_sv2::Error) -> Self {
- JDCError::CodecNoise(e)
- }
-}
-
-impl From for JDCError {
- fn from(e: framing_sv2::Error) -> Self {
- JDCError::FramingSv2(e)
- }
-}
-
-impl From for JDCError {
- fn from(e: std::io::Error) -> Self {
- JDCError::Io(e)
- }
-}
-
-impl From for JDCError {
- fn from(e: std::num::ParseIntError) -> Self {
- JDCError::ParseInt(e)
- }
-}
-
-impl From for JDCError {
- fn from(e: ConfigError) -> Self {
- JDCError::BadConfigDeserialize(e)
- }
-}
-
-impl From for JDCError {
- fn from(e: async_channel::RecvError) -> Self {
- JDCError::ChannelErrorReceiver(e)
- }
-}
-
-impl From for JDCError {
- fn from(e: tokio::sync::broadcast::error::RecvError) -> Self {
- JDCError::TokioChannelErrorRecv(e)
- }
-}
-
-impl From for JDCError {
- fn from(value: network_helpers::Error) -> Self {
- JDCError::NetworkHelpersError(value)
- }
-}
-
-impl From for JDCError {
- fn from(value: stratum_apps::stratum_core::bitcoin::consensus::encode::Error) -> Self {
- JDCError::BitcoinEncodeError(value)
- }
-}
-
-impl From for JDCError {
- fn from(_value: Elapsed) -> Self {
- Self::Timeout
- }
-}
-
-impl HandlerErrorType for JDCError {
- fn parse_error(error: ParserError) -> Self {
- JDCError::Parser(error)
- }
-
- fn unexpected_message(message_type: u8) -> Self {
- JDCError::UnexpectedMessage(message_type)
- }
-}
-
-impl From for JDCError {
- fn from(value: ExtendedChannelClientError) -> Self {
- JDCError::ChannelSv2(ChannelSv2Error::ExtendedChannelClientSide(value))
- }
-}
-
-impl From for JDCError {
- fn from(value: ExtendedChannelServerError) -> Self {
- JDCError::ChannelSv2(ChannelSv2Error::ExtendedChannelServerSide(value))
- }
-}
-
-impl From for JDCError {
- fn from(value: StandardChannelError) -> Self {
- JDCError::ChannelSv2(ChannelSv2Error::StandardChannelServerSide(value))
- }
-}
diff --git a/roles/jd-client/src/lib/jd_mode.rs b/roles/jd-client/src/lib/jd_mode.rs
deleted file mode 100644
index 0533afc718..0000000000
--- a/roles/jd-client/src/lib/jd_mode.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-//! Global configuration for Job Declarator (JD) operating mode.
-//!
-//! This module defines different operating modes for the Job Declarator
-//! and provides atomic accessors for setting and retrieving the current mode.
-//!
-//! Modes are stored in a global [`AtomicU8`] to allow safe concurrent access
-//! across threads.
-use std::sync::atomic::{AtomicU8, Ordering};
-
-/// Operating modes for the Job Declarator.
-#[repr(u8)]
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum JdMode {
- /// Runs in Coinbase only mode.
- CoinbaseOnly = 0,
- /// Runs in Full template mode,
- FullTemplate = 1,
- /// Runs in solo mining mode,
- SoloMining = 2,
-}
-
-impl From for JdMode {
- fn from(val: u8) -> Self {
- match val {
- 0 => JdMode::CoinbaseOnly,
- 1 => JdMode::FullTemplate,
- 2 => JdMode::SoloMining,
- _ => JdMode::SoloMining,
- }
- }
-}
-
-impl From for JdMode {
- fn from(val: u32) -> Self {
- match val {
- 0 => JdMode::CoinbaseOnly,
- 1 => JdMode::FullTemplate,
- 2 => JdMode::SoloMining,
- _ => JdMode::SoloMining,
- }
- }
-}
-
-impl From for u8 {
- fn from(mode: JdMode) -> Self {
- mode as u8
- }
-}
-
-/// Global atomic variable storing the current JD mode.
-pub static JD_MODE: AtomicU8 = AtomicU8::new(JdMode::SoloMining as u8);
-
-/// Updates the global JD mode.
-pub fn set_jd_mode(mode: JdMode) {
- JD_MODE.store(mode as u8, Ordering::SeqCst);
-}
-
-/// Returns the current global JD mode.
-pub fn get_jd_mode() -> JdMode {
- JD_MODE.load(Ordering::SeqCst).into()
-}
diff --git a/roles/jd-client/src/lib/job_declarator/message_handler.rs b/roles/jd-client/src/lib/job_declarator/message_handler.rs
deleted file mode 100644
index 932e0645aa..0000000000
--- a/roles/jd-client/src/lib/job_declarator/message_handler.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-use stratum_apps::stratum_core::{
- common_messages_sv2::{
- ChannelEndpointChanged, Reconnect, SetupConnectionError, SetupConnectionSuccess,
- },
- handlers_sv2::HandleCommonMessagesFromServerAsync,
-};
-use tracing::{info, warn};
-
-use crate::{
- error::JDCError,
- jd_mode::{set_jd_mode, JdMode},
- job_declarator::JobDeclarator,
-};
-
-impl HandleCommonMessagesFromServerAsync for JobDeclarator {
- type Error = JDCError;
-
- async fn handle_setup_connection_success(
- &mut self,
- _server_id: Option,
- msg: SetupConnectionSuccess,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
-
- let jd_mode = match msg.flags {
- 0 => JdMode::CoinbaseOnly,
- 1 => JdMode::FullTemplate,
- _ => JdMode::SoloMining,
- };
- set_jd_mode(jd_mode);
-
- if jd_mode == JdMode::SoloMining {
- return Err(JDCError::Shutdown);
- }
-
- Ok(())
- }
-
- async fn handle_channel_endpoint_changed(
- &mut self,
- _server_id: Option,
- msg: ChannelEndpointChanged,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
- Ok(())
- }
-
- async fn handle_reconnect(
- &mut self,
- _server_id: Option,
- msg: Reconnect<'_>,
- ) -> Result<(), Self::Error> {
- info!("Received: {}", msg);
- Ok(())
- }
-
- async fn handle_setup_connection_error(
- &mut self,
- _server_id: Option,
- msg: SetupConnectionError<'_>,
- ) -> Result<(), Self::Error> {
- warn!("Received: {}", msg);
- Err(JDCError::Shutdown)
- }
-}
diff --git a/roles/jd-client/src/lib/job_declarator/mod.rs b/roles/jd-client/src/lib/job_declarator/mod.rs
deleted file mode 100644
index 3fc1fb6fea..0000000000
--- a/roles/jd-client/src/lib/job_declarator/mod.rs
+++ /dev/null
@@ -1,313 +0,0 @@
-use std::{net::SocketAddr, sync::Arc};
-
-use async_channel::{unbounded, Receiver, Sender};
-use stratum_apps::{
- custom_mutex::Mutex,
- key_utils::Secp256k1PublicKey,
- network_helpers::noise_stream::NoiseTcpStream,
- stratum_core::{
- codec_sv2::HandshakeRole, framing_sv2, handlers_sv2::HandleCommonMessagesFromServerAsync,
- noise_sv2::Initiator,
- },
-};
-use tokio::{
- net::TcpStream,
- sync::{broadcast, mpsc},
-};
-use tracing::{debug, error, info, warn};
-
-use crate::{
- config::ConfigJDCMode,
- error::JDCError,
- status::{handle_error, Status, StatusSender},
- task_manager::TaskManager,
- utils::{
- get_setup_connection_message_jds, protocol_message_type, spawn_io_tasks, Message,
- MessageType, SV2Frame, ShutdownMessage, StdFrame,
- },
-};
-
-mod message_handler;
-
-/// Shared state for Job Declarator
-pub struct JobDeclaratorData;
-
-/// Holds all channels required for Job Declarator communication.
-#[derive(Clone)]
-pub struct JobDeclaratorChannel {
- channel_manager_sender: Sender,
- channel_manager_receiver: Receiver,
- jds_sender: Sender,
- jds_receiver: Receiver,
-}
-
-/// Manages the lifecycle and communication with a Job Declarator (JDS)
-#[allow(warnings)]
-#[derive(Clone)]
-pub struct JobDeclarator {
- /// Internal state
- job_declarator_data: Arc>,
- /// Messaging channels to/from the channel manager and JD.
- job_declarator_channel: JobDeclaratorChannel,
- /// Socket address of the Job Declarator server.
- socket_address: SocketAddr,
- /// Config JDC mode
- mode: ConfigJDCMode,
-}
-
-impl JobDeclarator {
- /// Creates a new JobDeclarator instance by connecting and performing a Noise handshake.
- ///
- /// - Establishes TCP connection.
- /// - Performs SV2 Noise handshake.
- /// - Spawns background IO tasks for reading/writing frames.
- pub async fn new(
- upstreams: &(SocketAddr, SocketAddr, Secp256k1PublicKey, bool),
- channel_manager_sender: Sender,
- channel_manager_receiver: Receiver,
- notify_shutdown: broadcast::Sender,
- mode: ConfigJDCMode,
- task_manager: Arc,
- status_sender: Sender,
- ) -> Result {
- let (_, addr, pubkey, _) = upstreams;
- info!("Connecting to JD Server at {addr}");
- let stream = tokio::time::timeout(
- tokio::time::Duration::from_secs(5),
- TcpStream::connect(addr),
- )
- .await??;
- info!("Connection established with JD Server at {addr} in mode: {mode:?}");
- let initiator = Initiator::from_raw_k(pubkey.into_bytes())?;
- let (noise_stream_reader, noise_stream_writer) =
- NoiseTcpStream::::new(stream, HandshakeRole::Initiator(initiator))
- .await?
- .into_split();
-
- let status_sender = StatusSender::JobDeclarator(status_sender);
- let (inbound_tx, inbound_rx) = unbounded::();
- let (outbound_tx, outbound_rx) = unbounded::();
-
- spawn_io_tasks(
- task_manager,
- noise_stream_reader,
- noise_stream_writer,
- outbound_rx,
- inbound_tx,
- notify_shutdown,
- status_sender,
- );
- let job_declarator_data = Arc::new(Mutex::new(JobDeclaratorData));
- let job_declarator_channel = JobDeclaratorChannel {
- channel_manager_receiver,
- channel_manager_sender,
- jds_sender: outbound_tx,
- jds_receiver: inbound_rx,
- };
- Ok(JobDeclarator {
- job_declarator_channel,
- job_declarator_data,
- socket_address: *addr,
- mode,
- })
- }
-
- /// Starts the JobDeclarator message loop.
- ///
- /// - Waits for shutdown signals.
- /// - Handles incoming messages from Job Declarator and Channel Manager.
- /// - Cleans up on termination.
- pub async fn start(
- mut self,
- notify_shutdown: broadcast::Sender,
- shutdown_complete_tx: mpsc::Sender<()>,
- status_sender: Sender,
- task_manager: Arc,
- ) {
- let status_sender = StatusSender::JobDeclarator(status_sender);
- let mut shutdown_rx = notify_shutdown.subscribe();
-
- if let Err(e) = self.setup_connection().await {
- handle_error(&status_sender, e).await;
- return;
- }
-
- task_manager.spawn(
- async move {
- loop {
- let mut self_clone_1 = self.clone();
- let self_clone_2 = self.clone();
- tokio::select! {
- message = shutdown_rx.recv() => {
- match message {
- Ok(ShutdownMessage::ShutdownAll) => {
- info!("Job Declarator: received shutdown signal.");
- break;
- }
- Ok(ShutdownMessage::JobDeclaratorShutdownFallback(_)) => {
- info!("Job Declarator: Received Job declarator shutdown.");
- break;
- }
- Ok(ShutdownMessage::UpstreamShutdownFallback(_)) => {
- info!("Job Declarator: Received Upstream shutdown.");
- break;
- }
- Ok(ShutdownMessage::UpstreamShutdown(tx)) => {
- info!("Job declarator shutdown requested");
- drop(tx);
- break;
- }
- Ok(ShutdownMessage::JobDeclaratorShutdown(tx)) => {
- info!("Job declarator shutdown requested");
- drop(tx);
- break;
- }
- Err(e) => {
- warn!(error = ?e, "Job Declarator: shutdown channel closed unexpectedly");
- break;
- }
- _ => {}
- }
- }
- res = self_clone_1.handle_job_declarator_message() => {
- if let Err(e) = res {
- error!(error = ?e, "Job Declarator message handling failed");
- handle_error(&status_sender, e).await;
- break;
- }
- }
- res = self_clone_2.handle_channel_manager_message() => {
- if let Err(e) = res {
- error!(error = ?e, "Channel Manager message handling failed");
- handle_error(&status_sender, e).await;
- break;
- }
- },
- }
- }
- drop(shutdown_complete_tx);
- warn!("JobDeclarator: unified message loop exited.");
- },
- );
- }
-
- /// Performs SV2 setup connection handshake with Job Declarator server.
- ///
- /// - Sends `SetupConnection` message.
- /// - Waits for and validates server response.
- /// - Completes SV2 protocol handshake.
- pub async fn setup_connection(&mut self) -> Result<(), JDCError> {
- info!("Sending SetupConnection to JDS at {}", self.socket_address);
-
- let setup_connection = get_setup_connection_message_jds(&self.socket_address, &self.mode);
- let sv2_frame: StdFrame = Message::Common(setup_connection.into())
- .try_into()
- .map_err(|e| {
- error!(error=?e, "Failed to serialize SetupConnection message.");
- JDCError::CodecNoise(
- stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage,
- )
- })?;
-
- if let Err(e) = self.job_declarator_channel.jds_sender.send(sv2_frame).await {
- error!(error=?e, "Failed to send SetupConnection frame.");
- return Err(JDCError::ChannelErrorSender);
- }
- debug!("SetupConnection frame sent successfully.");
-
- let mut incoming = self
- .job_declarator_channel
- .jds_receiver
- .recv()
- .await
- .map_err(|e| {
- error!(error=?e, "No handshake response received from Job declarator.");
- JDCError::CodecNoise(
- stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage,
- )
- })?;
-
- let message_type = incoming
- .get_header()
- .ok_or_else(|| {
- error!("Handshake frame missing header.");
- framing_sv2::Error::ExpectedHandshakeFrame
- })?
- .msg_type();
-
- debug!(?message_type, "Processing handshake response.");
-
- self.handle_common_message_frame_from_server(None, message_type, incoming.payload())
- .await?;
-
- info!("Job declarator: SV2 handshake completed successfully.");
- Ok(())
- }
-
- // Handles messages coming from the Channel Manager and forwards them to the Job Declarator.
- async fn handle_channel_manager_message(&self) -> Result<(), JDCError> {
- match self
- .job_declarator_channel
- .channel_manager_receiver
- .recv()
- .await
- {
- Ok(msg) => {
- debug!("Forwarding message from channel manager to JDS.");
- self.job_declarator_channel
- .jds_sender
- .send(msg)
- .await
- .map_err(|e| {
- error!("Failed to send message to outbound channel: {:?}", e);
- JDCError::ChannelErrorSender
- })?;
- }
- Err(e) => {
- warn!("Channel manager receiver closed or errored: {:?}", e);
- }
- }
- Ok(())
- }
-
- // Handles messages received from the Job Declarator.
- //
- // - Forwards `JobDeclaration` messages to Channel Manager.
- // - Processes `Common` messages via handler.
- // - Rejects unsupported message types.
- async fn handle_job_declarator_message(&mut self) -> Result<(), JDCError> {
- let mut sv2_frame = self.job_declarator_channel.jds_receiver.recv().await?;
-
- debug!("Received SV2 frame from JDS.");
- let Some(message_type) = sv2_frame.get_header().map(|m| m.msg_type()) else {
- return Ok(());
- };
-
- match protocol_message_type(message_type) {
- MessageType::Common => {
- info!(?message_type, "Handling common message from Upstream.");
- self.handle_common_message_frame_from_server(
- None,
- message_type,
- sv2_frame.payload(),
- )
- .await?;
- }
- MessageType::JobDeclaration => {
- self.job_declarator_channel
- .channel_manager_sender
- .send(sv2_frame)
- .await
- .map_err(|e| {
- error!(error=?e, "Failed to send Job declaration message to channel manager.");
- JDCError::ChannelErrorSender
- })?;
- }
- _ => {
- warn!("Received unsupported message type from Job declarator: {message_type}");
- }
- }
-
- Ok(())
- }
-}
diff --git a/roles/jd-client/src/lib/mod.rs b/roles/jd-client/src/lib/mod.rs
deleted file mode 100644
index c228508f85..0000000000
--- a/roles/jd-client/src/lib/mod.rs
+++ /dev/null
@@ -1,473 +0,0 @@
-use std::{net::SocketAddr, sync::Arc, time::Duration};
-
-use async_channel::{unbounded, Receiver, Sender};
-use stratum_apps::{key_utils::Secp256k1PublicKey, stratum_core::bitcoin::consensus::Encodable};
-use tokio::sync::{broadcast, mpsc};
-use tracing::{debug, info, warn};
-
-use crate::{
- channel_manager::ChannelManager,
- config::{ConfigJDCMode, JobDeclaratorClientConfig},
- error::JDCError,
- jd_mode::{set_jd_mode, JdMode},
- job_declarator::JobDeclarator,
- status::{State, Status},
- task_manager::TaskManager,
- template_receiver::TemplateReceiver,
- upstream::Upstream,
- utils::{SV2Frame, ShutdownMessage, UpstreamState},
-};
-
-mod channel_manager;
-pub mod config;
-mod downstream;
-pub mod error;
-pub mod jd_mode;
-mod job_declarator;
-mod status;
-mod task_manager;
-mod template_receiver;
-mod upstream;
-pub mod utils;
-
-/// Represent Job Declarator Client
-#[derive(Clone)]
-pub struct JobDeclaratorClient {
- config: JobDeclaratorClientConfig,
- notify_shutdown: broadcast::Sender,
-}
-
-impl JobDeclaratorClient {
- /// Creates a new [`JobDeclaratorClient`] instance.
- pub fn new(config: JobDeclaratorClientConfig) -> Self {
- let (notify_shutdown, _) = tokio::sync::broadcast::channel::(100);
- Self {
- config,
- notify_shutdown,
- }
- }
-
- /// Starts the Job Declarator Client (JDC) main loop.
- pub async fn start(&self) {
- info!(
- "Job declarator client starting... setting up subsystems, User Identity: {}",
- self.config.user_identity()
- );
-
- let miner_coinbase_outputs = vec![self.config.get_txout()];
- let mut encoded_outputs = vec![];
-
- miner_coinbase_outputs
- .consensus_encode(&mut encoded_outputs)
- .expect("Invalid coinbase output in config");
-
- let notify_shutdown = self.notify_shutdown.clone();
- let (shutdown_complete_tx, mut shutdown_complete_rx) = mpsc::channel::<()>(1);
- let task_manager = Arc::new(TaskManager::new());
-
- let (status_sender, status_receiver) = async_channel::unbounded::();
-
- let (channel_manager_to_upstream_sender, channel_manager_to_upstream_receiver) =
- unbounded::();
- let (upstream_to_channel_manager_sender, upstream_to_channel_manager_receiver) =
- unbounded::();
-
- let (channel_manager_to_jd_sender, channel_manager_to_jd_receiver) =
- unbounded::();
- let (jd_to_channel_manager_sender, jd_to_channel_manager_receiver) =
- unbounded::();
-
- let (channel_manager_to_downstream_sender, _channel_manager_to_downstream_receiver) =
- broadcast::channel(10);
- let (downstream_to_channel_manager_sender, downstream_to_channel_manager_receiver) =
- unbounded();
-
- let (channel_manager_to_tp_sender, channel_manager_to_tp_receiver) =
- unbounded::();
- let (tp_to_channel_manager_sender, tp_to_channel_manager_receiver) =
- unbounded::();
-
- debug!("Channels initialized.");
-
- let channel_manager = ChannelManager::new(
- self.config.clone(),
- channel_manager_to_upstream_sender.clone(),
- upstream_to_channel_manager_receiver.clone(),
- channel_manager_to_jd_sender.clone(),
- jd_to_channel_manager_receiver.clone(),
- channel_manager_to_tp_sender.clone(),
- tp_to_channel_manager_receiver.clone(),
- channel_manager_to_downstream_sender.clone(),
- downstream_to_channel_manager_receiver,
- status_sender.clone(),
- encoded_outputs.clone(),
- )
- .await
- .unwrap();
-
- let channel_manager_clone = channel_manager.clone();
-
- // Initialize the template Receiver
- let tp_address = self.config.tp_address().to_string();
- let tp_pubkey = self.config.tp_authority_public_key().copied();
-
- let template_receiver = TemplateReceiver::new(
- tp_address.clone(),
- tp_pubkey,
- channel_manager_to_tp_receiver,
- tp_to_channel_manager_sender,
- notify_shutdown.clone(),
- task_manager.clone(),
- status_sender.clone(),
- )
- .await
- .unwrap();
-
- info!("Template provider setup done");
-
- let notify_shutdown_cl = notify_shutdown.clone();
- let status_sender_cl = status_sender.clone();
- let task_manager_cl = task_manager.clone();
-
- template_receiver
- .start(
- tp_address,
- notify_shutdown_cl,
- status_sender_cl,
- task_manager_cl,
- encoded_outputs.clone(),
- )
- .await;
-
- let mut upstream_addresses: Vec<_> = self
- .config
- .upstreams()
- .iter()
- .map(|u| {
- let pool_addr = SocketAddr::new(
- u.pool_address.parse().expect("Invalid pool address"),
- u.pool_port,
- );
- let jd_addr = SocketAddr::new(
- u.jds_address.parse().expect("Invalid JD address"),
- u.jds_port,
- );
- (pool_addr, jd_addr, u.authority_pubkey, false)
- })
- .collect();
-
- channel_manager
- .start(
- notify_shutdown.clone(),
- status_sender.clone(),
- task_manager.clone(),
- )
- .await;
-
- info!("Attempting to initialize upstream...");
-
- match self
- .initialize_jd(
- &mut upstream_addresses,
- channel_manager_to_upstream_receiver.clone(),
- upstream_to_channel_manager_sender.clone(),
- channel_manager_to_jd_receiver.clone(),
- jd_to_channel_manager_sender.clone(),
- notify_shutdown.clone(),
- status_sender.clone(),
- self.config.mode.clone(),
- task_manager.clone(),
- )
- .await
- {
- Ok((upstream, job_declarator)) => {
- upstream
- .start(
- self.config.min_supported_version(),
- self.config.max_supported_version(),
- notify_shutdown.clone(),
- shutdown_complete_tx.clone(),
- status_sender.clone(),
- task_manager.clone(),
- )
- .await;
-
- job_declarator
- .start(
- notify_shutdown.clone(),
- shutdown_complete_tx,
- status_sender.clone(),
- task_manager.clone(),
- )
- .await;
-
- channel_manager_clone
- .upstream_state
- .set(UpstreamState::NoChannel);
- _ = channel_manager_clone.allocate_tokens(1).await;
- }
- Err(e) => {
- tracing::error!("Failed to initialize upstream: {:?}", e);
- set_jd_mode(jd_mode::JdMode::SoloMining);
- }
- };
-
- _ = channel_manager_clone
- .clone()
- .start_downstream_server(
- *self.config.authority_public_key(),
- *self.config.authority_secret_key(),
- self.config.cert_validity_sec(),
- *self.config.listening_address(),
- task_manager.clone(),
- notify_shutdown.clone(),
- status_sender.clone(),
- downstream_to_channel_manager_sender.clone(),
- channel_manager_to_downstream_sender.clone(),
- )
- .await;
-
- info!("Spawning status listener task...");
- let notify_shutdown_clone = notify_shutdown.clone();
-
- loop {
- tokio::select! {
- _ = tokio::signal::ctrl_c() => {
- info!("Ctrl+C received β initiating graceful shutdown...");
- let _ = notify_shutdown_clone.send(ShutdownMessage::ShutdownAll);
- break;
- }
- message = status_receiver.recv() => {
- if let Ok(status) = message {
- match status.state {
- State::DownstreamShutdown{downstream_id,..} => {
- warn!("Downstream {downstream_id:?} disconnected β Channel manager.");
- let _ = notify_shutdown_clone.send(ShutdownMessage::DownstreamShutdown(downstream_id));
- }
- State::TemplateReceiverShutdown(_) => {
- warn!("Template Receiver shutdown requested β initiating full shutdown.");
- let _ = notify_shutdown_clone.send(ShutdownMessage::ShutdownAll);
- break;
- }
- State::ChannelManagerShutdown(_) => {
- warn!("Channel Manager shutdown requested β initiating full shutdown.");
- let _ = notify_shutdown_clone.send(ShutdownMessage::ShutdownAll);
- break;
- }
- State::UpstreamShutdownFallback(_) | State::JobDeclaratorShutdownFallback(_) => {
- warn!("Upstream/Job Declarator connection dropped β attempting reconnection...");
- let (tx, mut rx) = mpsc::channel::<()>(1);
- let _ = notify_shutdown_clone.send(ShutdownMessage::UpstreamShutdownFallback((encoded_outputs.clone(), tx)));
- set_jd_mode(JdMode::SoloMining);
- shutdown_complete_rx.recv().await;
- tracing::error!("Existing Upstream or JD instance taken out");
- rx.recv().await;
- tracing::error!("All entities acknowledged Upstream fallback. Preparing fallback.");
-
- let (shutdown_complete_tx_fallback, shutdown_complete_rx_fallback) = mpsc::channel::<()>(1);
-
- shutdown_complete_rx = shutdown_complete_rx_fallback;
-
- info!("Attempting to initialize Jd and upstream...");
-
- match self
- .initialize_jd(
- &mut upstream_addresses,
- channel_manager_to_upstream_receiver.clone(),
- upstream_to_channel_manager_sender.clone(),
- channel_manager_to_jd_receiver.clone(),
- jd_to_channel_manager_sender.clone(),
- notify_shutdown.clone(),
- status_sender.clone(),
- self.config.mode.clone(),
- task_manager.clone(),
- )
- .await
- {
- Ok((upstream, job_declarator)) => {
- upstream
- .start(
- self.config.min_supported_version(),
- self.config.max_supported_version(),
- notify_shutdown.clone(),
- shutdown_complete_tx_fallback.clone(),
- status_sender.clone(),
- task_manager.clone(),
- )
- .await;
-
- job_declarator
- .start(
- notify_shutdown.clone(),
- shutdown_complete_tx_fallback,
- status_sender.clone(),
- task_manager.clone(),
- )
- .await;
-
- channel_manager_clone.upstream_state.set(UpstreamState::NoChannel);
-
- _ = channel_manager_clone.allocate_tokens(1).await;
- }
- Err(e) => {
- tracing::error!("Failed to initialize upstream: {:?}", e);
- channel_manager_clone.upstream_state.set(UpstreamState::SoloMining);
- set_jd_mode(jd_mode::JdMode::SoloMining);
- info!("Fallback to solo mining mode");
- }
- };
-
- _ = channel_manager_clone.clone()
- .start_downstream_server(
- *self.config.authority_public_key(),
- *self.config.authority_secret_key(),
- self.config.cert_validity_sec(),
- *self.config.listening_address(),
- task_manager.clone(),
- notify_shutdown.clone(),
- status_sender.clone(),
- downstream_to_channel_manager_sender.clone(),
- channel_manager_to_downstream_sender.clone(),
- )
- .await;
- }
- }
- }
- }
- }
- }
-
- warn!("Graceful shutdown");
- task_manager.abort_all().await;
-
- info!("Joining remaining tasks...");
- task_manager.join_all().await;
- info!("JD Client shutdown complete.");
- }
-
- /// Initializes an upstream pool + JD connection pair.
- #[allow(clippy::too_many_arguments)]
- pub async fn initialize_jd(
- &self,
- upstreams: &mut [(SocketAddr, SocketAddr, Secp256k1PublicKey, bool)],
- channel_manager_to_upstream_receiver: Receiver,
- upstream_to_channel_manager_sender: Sender,
- channel_manager_to_jd_receiver: Receiver,
- jd_to_channel_manager_sender: Sender,
- notify_shutdown: broadcast::Sender,
- status_sender: Sender,
- mode: ConfigJDCMode,
- task_manager: Arc,
- ) -> Result<(Upstream, JobDeclarator), JDCError> {
- const MAX_RETRIES: usize = 3;
- let upstream_len = upstreams.len();
- for (i, upstream_addr) in upstreams.iter_mut().enumerate() {
- info!(
- "Trying upstream {} of {}: {:?}",
- i + 1,
- upstream_len,
- upstream_addr
- );
-
- tokio::time::sleep(Duration::from_secs(1)).await;
-
- if upstream_addr.3 {
- info!(
- "Upstream previously marked as malicious, skipping initial attempt warnings."
- );
- continue;
- }
-
- for attempt in 1..=MAX_RETRIES {
- info!("Connection attempt {}/{}...", attempt, MAX_RETRIES);
-
- match try_initialize_single(
- upstream_addr,
- upstream_to_channel_manager_sender.clone(),
- channel_manager_to_upstream_receiver.clone(),
- jd_to_channel_manager_sender.clone(),
- channel_manager_to_jd_receiver.clone(),
- notify_shutdown.clone(),
- status_sender.clone(),
- mode.clone(),
- task_manager.clone(),
- )
- .await
- {
- Ok(pair) => {
- upstream_addr.3 = true;
- return Ok(pair);
- }
- Err(e) => {
- let (tx, mut rx) = mpsc::channel::<()>(1);
- let _ = notify_shutdown.send(ShutdownMessage::JobDeclaratorShutdown(tx));
- rx.recv().await;
- tracing::error!("All sparsed upstream and JDS connection is be terminated");
- tokio::time::sleep(Duration::from_secs(1)).await;
- warn!(
- "Attempt {}/{} failed for {:?}: {:?}",
- attempt, MAX_RETRIES, upstream_addr, e
- );
- if attempt == MAX_RETRIES {
- warn!(
- "Max retries reached for {:?}, moving to next upstream",
- upstream_addr
- );
- }
- }
- }
- }
- upstream_addr.3 = true;
- }
-
- tracing::error!("All upstreams failed after {} retries each", MAX_RETRIES);
- Err(JDCError::Shutdown)
- }
-}
-
-// Attempts to initialize a single upstream (pool + JDS pair).
-#[allow(clippy::too_many_arguments)]
-async fn try_initialize_single(
- upstream_addr: &(SocketAddr, SocketAddr, Secp256k1PublicKey, bool),
- upstream_to_channel_manager_sender: Sender,
- channel_manager_to_upstream_receiver: Receiver,
- jd_to_channel_manager_sender: Sender,
- channel_manager_to_jd_receiver: Receiver,
- notify_shutdown: broadcast::Sender,
- status_sender: Sender,
- mode: ConfigJDCMode,
- task_manager: Arc,
-) -> Result<(Upstream, JobDeclarator), JDCError> {
- info!("Upstream connection in-progress at initialize single");
- let upstream = Upstream::new(
- upstream_addr,
- upstream_to_channel_manager_sender,
- channel_manager_to_upstream_receiver,
- notify_shutdown.clone(),
- task_manager.clone(),
- status_sender.clone(),
- )
- .await?;
-
- info!("Upstream connection done at initialize single");
-
- let job_declarator = JobDeclarator::new(
- upstream_addr,
- jd_to_channel_manager_sender,
- channel_manager_to_jd_receiver,
- notify_shutdown,
- mode,
- task_manager.clone(),
- status_sender.clone(),
- )
- .await?;
-
- Ok((upstream, job_declarator))
-}
-
-impl Drop for JobDeclaratorClient {
- fn drop(&mut self) {
- info!("JobDeclaratorClient dropped");
- let _ = self.notify_shutdown.send(ShutdownMessage::ShutdownAll);
- }
-}
diff --git a/roles/jd-client/src/lib/status.rs b/roles/jd-client/src/lib/status.rs
deleted file mode 100644
index feea1d2015..0000000000
--- a/roles/jd-client/src/lib/status.rs
+++ /dev/null
@@ -1,154 +0,0 @@
-//! Status reporting and error propagation Utility.
-//!
-//! This module provides mechanisms for communicating shutdown events and
-//! component state changes across the system. Each component (downstream,
-//! upstream, job declarator, template receiver, channel manager) can send
-//! and receive status updates via typed channels. Errors are automatically
-//! converted into shutdown signals, allowing coordinated teardown of tasks.
-
-use tracing::{debug, error, warn};
-
-use crate::error::JDCError;
-
-/// Sender type for propagating status updates from different system components.
-#[derive(Debug, Clone)]
-pub enum StatusSender {
- /// Status updates from a specific downstream connection.
- Downstream {
- downstream_id: u32,
- tx: async_channel::Sender,
- },
- /// Status updates from the template receiver.
- TemplateReceiver(async_channel::Sender),
- /// Status updates from the channel manager.
- ChannelManager(async_channel::Sender),
- /// Status updates from the upstream.
- Upstream(async_channel::Sender),
- /// Status updates from the job declarator.
- JobDeclarator(async_channel::Sender),
-}
-
-/// High-level identifier of a component type that can send status updates.
-#[derive(Debug, PartialEq, Eq)]
-pub enum StatusType {
- /// A downstream connection identified by its ID.
- Downstream(u32),
- /// The template receiver component.
- TemplateReceiver,
- /// The channel manager component.
- ChannelManager,
- /// The upstream component.
- Upstream,
- /// The job declarator component.
- JobDeclarator,
-}
-
-impl From<&StatusSender> for StatusType {
- fn from(value: &StatusSender) -> Self {
- match value {
- StatusSender::ChannelManager(_) => StatusType::ChannelManager,
- StatusSender::Downstream {
- downstream_id,
- tx: _,
- } => StatusType::Downstream(*downstream_id),
- StatusSender::JobDeclarator(_) => StatusType::JobDeclarator,
- StatusSender::Upstream(_) => StatusType::Upstream,
- StatusSender::TemplateReceiver(_) => StatusType::TemplateReceiver,
- }
- }
-}
-
-impl StatusSender {
- /// Sends a status update for the associated component.
- pub async fn send(&self, status: Status) -> Result<(), async_channel::SendError> {
- match self {
- Self::Downstream { downstream_id, tx } => {
- debug!(
- "Sending status from Downstream [{}]: {:?}",
- downstream_id, status.state
- );
- tx.send(status).await
- }
- Self::TemplateReceiver(tx) => {
- debug!("Sending status from TemplateReceiver: {:?}", status.state);
- tx.send(status).await
- }
- Self::ChannelManager(tx) => {
- debug!("Sending status from ChannelManager: {:?}", status.state);
- tx.send(status).await
- }
- Self::Upstream(tx) => {
- debug!("Sending status from Upstream: {:?}", status.state);
- tx.send(status).await
- }
- Self::JobDeclarator(tx) => {
- debug!("Sending status from JobDeclarator: {:?}", status.state);
- tx.send(status).await
- }
- }
- }
-}
-
-/// Represents the state of a component, typically triggered by an error or shutdown event.
-#[derive(Debug)]
-pub enum State {
- /// A downstream connection has shut down with a reason.
- DownstreamShutdown {
- downstream_id: u32,
- reason: JDCError,
- },
- /// Template receiver has shut down with a reason.
- TemplateReceiverShutdown(JDCError),
- /// Job declarator has shut down during fallback with a reason.
- JobDeclaratorShutdownFallback(JDCError),
- /// Channel manager has shut down with a reason.
- ChannelManagerShutdown(JDCError),
- /// Upstream has shut down during fallback with a reason.
- UpstreamShutdownFallback(JDCError),
-}
-
-/// Wrapper around a componentβs state, sent as status updates across the system.
-#[derive(Debug)]
-pub struct Status {
- /// The current state being reported.
- pub state: State,
-}
-
-/// Sends a shutdown status for the given component, logging the error cause.
-async fn send_status(sender: &StatusSender, error: JDCError) {
- let state = match sender {
- StatusSender::Downstream { downstream_id, .. } => {
- warn!("Downstream [{downstream_id}] shutting down due to error: {error:?}");
- State::DownstreamShutdown {
- downstream_id: *downstream_id,
- reason: error,
- }
- }
- StatusSender::TemplateReceiver(_) => {
- warn!("Template Receiver shutting down due to error: {error:?}");
- State::TemplateReceiverShutdown(error)
- }
- StatusSender::ChannelManager(_) => {
- warn!("ChannelManager shutting down due to error: {error:?}");
- State::ChannelManagerShutdown(error)
- }
- StatusSender::Upstream(_) => {
- warn!("Upstream shutting down due to error: {error:?}");
- State::UpstreamShutdownFallback(error)
- }
- StatusSender::JobDeclarator(_) => {
- warn!("Job declarator shutting down due to error: {error:?}");
- State::JobDeclaratorShutdownFallback(error)
- }
- };
-
- if let Err(e) = sender.send(Status { state }).await {
- tracing::error!("Failed to send status update from {sender:?}: {e:?}");
- }
-}
-
-/// Logs an error and propagates a corresponding shutdown status for the component.
-pub async fn handle_error(sender: &StatusSender, e: JDCError) {
- error!("Error in {:?}: {:?}", sender, e);
- send_status(sender, e).await;
-}
diff --git a/roles/jd-client/src/lib/task_manager.rs b/roles/jd-client/src/lib/task_manager.rs
deleted file mode 100644
index 95435a020c..0000000000
--- a/roles/jd-client/src/lib/task_manager.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-use std::sync::Mutex as StdMutex;
-use tokio::task::JoinHandle;
-
-/// Manages a collection of spawned tokio tasks.
-///
-/// This struct provides a centralized way to spawn, track, and manage the lifecycle
-/// of async tasks. It maintains a list of join handles that can
-/// be used to wait for all tasks to complete or abort them during shutdown.
-pub struct TaskManager {
- tasks: StdMutex>>,
-}
-
-impl Default for TaskManager {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl TaskManager {
- /// Creates a new TaskManager instance.
- ///
- /// Initializes an empty task manager ready to spawn and track tasks.
- pub fn new() -> Self {
- Self {
- tasks: StdMutex::new(Vec::new()),
- }
- }
-
- /// Spawns a new async task and adds it to the managed collection.
- ///
- /// The task will be tracked by this manager and can be waited for or aborted
- /// using the other methods.
- ///
- /// # Arguments
- /// * `fut` - The future to spawn as a task
- pub fn spawn(&self, fut: F)
- where
- F: std::future::Future