From 2a7cc199354e44ac94de90680ecd05e0ee9fbe36 Mon Sep 17 00:00:00 2001 From: Lucas Balieiro Date: Tue, 21 Oct 2025 15:23:38 -0400 Subject: [PATCH 1/3] Restructure repo to now only contain the low-level crates - Moved roles to its own repository: https://github.com/stratum-mining/sv2-apps - Move protocols/v2 --> sv2/ - Move protocols/v2 --> sv1/ - Move protocols/stratum-translation --> stratum-core/stratum-translation - Move utils/buffer --> sv2/buffer-sv2 - Adapted scripts and workflows where needed --- .github/workflows/clippy-lint.yaml | 5 +- .github/workflows/coverage-protocols.yaml | 74 +- .github/workflows/coverage-roles.yaml | 86 - .github/workflows/coverage-utils.yaml | 39 - .github/workflows/docs.yaml | 31 +- .github/workflows/fmt.yaml | 6 +- .github/workflows/integration-tests.yaml | 7 +- .github/workflows/lockfiles.yaml | 26 - .github/workflows/release-libs.yaml | 34 +- .github/workflows/rust-msrv.yaml | 24 +- .github/workflows/semver-check.yaml | 58 +- .github/workflows/test.yaml | 104 +- .gitignore | 29 +- roles/Cargo.toml => Cargo.toml | 13 +- README.md | 117 +- examples/ping-pong-encrypted/Cargo.toml | 13 - examples/ping-pong-encrypted/README.md | 20 - examples/ping-pong-encrypted/src/client.rs | 82 - examples/ping-pong-encrypted/src/error.rs | 64 - examples/ping-pong-encrypted/src/main.rs | 33 - examples/ping-pong-encrypted/src/messages.rs | 83 - examples/ping-pong-encrypted/src/server.rs | 105 - examples/ping-pong/Cargo.toml | 11 - examples/ping-pong/README.md | 17 - examples/ping-pong/src/client.rs | 86 - examples/ping-pong/src/error.rs | 31 - examples/ping-pong/src/main.rs | 19 - examples/ping-pong/src/messages.rs | 39 - examples/ping-pong/src/server.rs | 104 - .../fuzz-tests => fuzz-tests}/Cargo.lock | 501 +-- .../fuzz-tests => fuzz-tests}/Cargo.toml | 8 +- .../fuzz-tests => fuzz-tests}/src/main.rs | 0 protocols/Cargo.toml | 28 - .../v2/binary-sv2/derive_codec/.gitignore | 1 - roles/Cargo.lock | 3790 ----------------- roles/jd-client/Cargo.toml | 25 - roles/jd-client/README.md | 188 - .../jdc-config-hosted-example.toml | 66 - .../jdc-config-local-example.toml | 66 - roles/jd-client/src/args.rs | 43 - .../downstream_message_handler.rs | 1317 ------ .../lib/channel_manager/jd_message_handler.rs | 294 -- .../jd-client/src/lib/channel_manager/mod.rs | 1119 ----- .../template_message_handler.rs | 611 --- .../upstream_message_handler.rs | 619 --- roles/jd-client/src/lib/config.rs | 294 -- .../src/lib/downstream/message_handler.rs | 89 - roles/jd-client/src/lib/downstream/mod.rs | 284 -- roles/jd-client/src/lib/error.rs | 336 -- roles/jd-client/src/lib/jd_mode.rs | 61 - .../src/lib/job_declarator/message_handler.rs | 65 - roles/jd-client/src/lib/job_declarator/mod.rs | 313 -- roles/jd-client/src/lib/mod.rs | 473 -- roles/jd-client/src/lib/status.rs | 154 - roles/jd-client/src/lib/task_manager.rs | 72 - .../lib/template_receiver/message_handler.rs | 50 - .../src/lib/template_receiver/mod.rs | 426 -- .../src/lib/upstream/message_handler.rs | 50 - roles/jd-client/src/lib/upstream/mod.rs | 322 -- roles/jd-client/src/lib/utils.rs | 585 --- roles/jd-client/src/main.rs | 17 - roles/jd-server/Cargo.toml | 44 - .../jds-config-hosted-example.toml | 30 - .../jds-config-local-example.toml | 30 - roles/jd-server/src/args.rs | 67 - roles/jd-server/src/lib/config.rs | 296 -- roles/jd-server/src/lib/error.rs | 153 - .../src/lib/job_declarator/message_handler.rs | 296 -- roles/jd-server/src/lib/job_declarator/mod.rs | 645 --- roles/jd-server/src/lib/mempool/error.rs | 66 - roles/jd-server/src/lib/mempool/mod.rs | 200 - roles/jd-server/src/lib/mod.rs | 251 -- roles/jd-server/src/lib/status.rs | 451 -- roles/jd-server/src/main.rs | 28 - roles/pool/Cargo.toml | 28 - roles/pool/README.md | 54 - .../pool-config-hosted-tp-example.toml | 32 - .../pool-config-local-tp-example.toml | 30 - roles/pool/src/args.rs | 42 - .../channel_manager/mining_message_handler.rs | 1030 ----- roles/pool/src/lib/channel_manager/mod.rs | 564 --- .../template_distribution_message_handler.rs | 306 -- roles/pool/src/lib/config.rs | 199 - .../lib/downstream/common_message_handler.rs | 42 - roles/pool/src/lib/downstream/mod.rs | 283 -- roles/pool/src/lib/error.rs | 267 -- roles/pool/src/lib/mod.rs | 177 - roles/pool/src/lib/status.rs | 124 - roles/pool/src/lib/task_manager.rs | 72 - .../common_message_handler.rs | 58 - roles/pool/src/lib/template_receiver/mod.rs | 377 -- roles/pool/src/lib/utils.rs | 355 -- roles/pool/src/main.rs | 15 - roles/stratum-apps/Cargo.toml | 74 - roles/stratum-apps/README.md | 92 - .../config_helpers/coinbase_output/errors.rs | 65 - .../src/config_helpers/coinbase_output/mod.rs | 377 -- .../coinbase_output/serde_types.rs | 136 - .../src/config_helpers/logging.rs | 49 - roles/stratum-apps/src/config_helpers/mod.rs | 16 - roles/stratum-apps/src/config_helpers/toml.rs | 33 - roles/stratum-apps/src/custom_mutex.rs | 115 - roles/stratum-apps/src/key_utils/mod.rs | 265 -- roles/stratum-apps/src/lib.rs | 60 - roles/stratum-apps/src/network_helpers/mod.rs | 53 - .../src/network_helpers/noise_connection.rs | 139 - .../src/network_helpers/noise_stream.rs | 332 -- .../src/network_helpers/plain_connection.rs | 135 - .../src/network_helpers/sv1_connection.rs | 214 - roles/stratum-apps/src/rpc/mini_rpc_client.rs | 215 - roles/stratum-apps/src/rpc/mod.rs | 24 - roles/tarpaulin.toml | 8 - roles/test-utils/mining-device/Cargo.toml | 61 - roles/test-utils/mining-device/README.md | 137 - .../mining-device/benches/hasher_bench.rs | 54 - .../mining-device/benches/microbatch_bench.rs | 105 - .../mining-device/benches/scaling_bench.rs | 169 - roles/test-utils/mining-device/src/lib/mod.rs | 1079 ----- roles/test-utils/mining-device/src/main.rs | 89 - .../tests/fast_hasher_equivalence.rs | 41 - roles/translator/Cargo.toml | 35 - roles/translator/README.md | 204 - .../tproxy-config-hosted-pool-example.toml | 46 - .../tproxy-config-local-jdc-example.toml | 40 - .../tproxy-config-local-pool-example.toml | 39 - roles/translator/src/args.rs | 52 - roles/translator/src/lib/config.rs | 268 -- roles/translator/src/lib/error.rs | 213 - roles/translator/src/lib/mod.rs | 255 -- roles/translator/src/lib/status.rs | 117 - .../src/lib/sv1/downstream/channel.rs | 35 - .../translator/src/lib/sv1/downstream/data.rs | 98 - .../src/lib/sv1/downstream/downstream.rs | 531 --- .../src/lib/sv1/downstream/message_handler.rs | 165 - .../translator/src/lib/sv1/downstream/mod.rs | 43 - roles/translator/src/lib/sv1/mod.rs | 16 - .../src/lib/sv1/sv1_server/channel.rs | 41 - .../translator/src/lib/sv1/sv1_server/data.rs | 47 - .../lib/sv1/sv1_server/difficulty_manager.rs | 751 ---- .../translator/src/lib/sv1/sv1_server/mod.rs | 4 - .../src/lib/sv1/sv1_server/sv1_server.rs | 1025 ----- .../src/lib/sv2/channel_manager/channel.rs | 36 - .../sv2/channel_manager/channel_manager.rs | 864 ---- .../src/lib/sv2/channel_manager/data.rs | 110 - .../sv2/channel_manager/message_handler.rs | 528 --- .../src/lib/sv2/channel_manager/mod.rs | 6 - roles/translator/src/lib/sv2/mod.rs | 5 - .../src/lib/sv2/upstream/channel.rs | 40 - .../src/lib/sv2/upstream/message_handler.rs | 48 - roles/translator/src/lib/sv2/upstream/mod.rs | 4 - .../src/lib/sv2/upstream/upstream.rs | 457 -- roles/translator/src/lib/task_manager.rs | 80 - roles/translator/src/lib/utils.rs | 297 -- roles/translator/src/main.rs | 25 - rustfmt.toml | 10 - scripts/build-on-all-workspaces.sh | 24 - scripts/clippy-fmt-and-test.sh | 28 - scripts/coverage-protocols.sh | 28 +- scripts/coverage-roles.sh | 27 - scripts/coverage-utils.sh | 23 - scripts/release-libs.sh | 2 +- scripts/run-integration-tests.sh | 67 + scripts/rust/clippy.sh | 20 - stratum-core/Cargo.toml | 28 +- .../stratum-translation/Cargo.toml | 8 +- .../stratum-translation/src/error.rs | 0 .../stratum-translation/src/lib.rs | 0 .../stratum-translation/src/sv1_to_sv2.rs | 0 .../stratum-translation/src/sv2_to_sv1.rs | 0 {protocols/v1 => sv1}/Cargo.toml | 2 +- {protocols/v1 => sv1}/README.md | 0 .../v1 => sv1}/examples/client_and_server.rs | 0 {protocols/v1 => sv1}/src/error.rs | 0 {protocols/v1 => sv1}/src/json_rpc.rs | 0 {protocols/v1 => sv1}/src/lib.rs | 0 .../src/methods/client_to_server.rs | 0 {protocols/v1 => sv1}/src/methods/mod.rs | 0 .../src/methods/server_to_client.rs | 0 {protocols/v1 => sv1}/src/utils.rs | 0 {protocols/v2 => sv2}/binary-sv2/Cargo.toml | 2 +- {protocols/v2 => sv2}/binary-sv2/README.md | 0 .../binary-sv2/derive_codec}/.gitignore | 0 .../binary-sv2/derive_codec/Cargo.toml | 0 .../binary-sv2/derive_codec/README.md | 0 .../binary-sv2/derive_codec/src/lib.rs | 0 .../binary-sv2/examples/encode_decode.rs | 0 .../binary-sv2/src/codec/decodable.rs | 0 .../binary-sv2/src/codec/encodable.rs | 0 .../v2 => sv2}/binary-sv2/src/codec/impls.rs | 0 .../v2 => sv2}/binary-sv2/src/codec/mod.rs | 0 .../src/datatypes/copy_data_types.rs | 0 .../binary-sv2/src/datatypes/mod.rs | 0 .../datatypes/non_copy_data_types/inner.rs | 0 .../src/datatypes/non_copy_data_types/mod.rs | 0 .../non_copy_data_types/seq_inner.rs | 0 {protocols/v2 => sv2}/binary-sv2/src/lib.rs | 0 .../v2 => sv2}/binary-sv2/tests/test.rs | 0 {utils/buffer => sv2/buffer-sv2}/BENCHES.md | 0 {utils/buffer => sv2/buffer-sv2}/Cargo.toml | 0 {utils/buffer => sv2/buffer-sv2}/README.md | 0 .../buffer-sv2}/benches/control_struct.rs | 0 .../buffer-sv2}/benches/pool_benchmark.rs | 0 .../buffer-sv2}/benches/pool_iai.rs | 0 .../buffer-sv2}/examples/basic_buffer_pool.rs | 0 .../examples/buffer_pool_exhaustion.rs | 0 .../examples/variable_sized_messages.rs | 0 .../buffer => sv2/buffer-sv2}/fuzz/.gitignore | 0 .../buffer => sv2/buffer-sv2}/fuzz/Cargo.toml | 0 .../buffer-sv2}/fuzz/fuzz_targets/faster.rs | 0 .../buffer-sv2}/fuzz/fuzz_targets/slower.rs | 0 {utils/buffer => sv2/buffer-sv2}/fuzz/random | Bin {utils/buffer => sv2/buffer-sv2}/fuzz/run.sh | 0 .../buffer-sv2}/fuzz/rust-toolchain.toml | 0 .../buffer => sv2/buffer-sv2}/src/buffer.rs | 0 .../buffer-sv2}/src/buffer_pool/mod.rs | 0 .../buffer-sv2}/src/buffer_pool/pool_back.rs | 0 {utils/buffer => sv2/buffer-sv2}/src/lib.rs | 0 {utils/buffer => sv2/buffer-sv2}/src/slice.rs | 0 {utils/buffer => sv2/buffer-sv2}/src/test.rs | 0 {protocols/v2 => sv2}/channels-sv2/Cargo.toml | 0 {protocols/v2 => sv2}/channels-sv2/README.md | 0 .../v2 => sv2}/channels-sv2/src/bip141.rs | 0 .../v2 => sv2}/channels-sv2/src/chain_tip.rs | 0 .../channels-sv2/src/client/error.rs | 0 .../channels-sv2/src/client/extended.rs | 0 .../channels-sv2/src/client/group.rs | 0 .../v2 => sv2}/channels-sv2/src/client/mod.rs | 0 .../src/client/share_accounting.rs | 0 .../channels-sv2/src/client/standard.rs | 0 {protocols/v2 => sv2}/channels-sv2/src/lib.rs | 0 .../channels-sv2/src/merkle_root.rs | 0 .../v2 => sv2}/channels-sv2/src/outputs.rs | 0 .../channels-sv2/src/server/error.rs | 0 .../channels-sv2/src/server/extended.rs | 0 .../channels-sv2/src/server/group.rs | 0 .../channels-sv2/src/server/jobs/error.rs | 0 .../channels-sv2/src/server/jobs/extended.rs | 0 .../channels-sv2/src/server/jobs/factory.rs | 0 .../channels-sv2/src/server/jobs/job_store.rs | 0 .../channels-sv2/src/server/jobs/mod.rs | 0 .../channels-sv2/src/server/jobs/standard.rs | 0 .../v2 => sv2}/channels-sv2/src/server/mod.rs | 0 .../src/server/share_accounting.rs | 0 .../channels-sv2/src/server/standard.rs | 0 .../v2 => sv2}/channels-sv2/src/target.rs | 0 .../channels-sv2/src/vardiff/classic.rs | 0 .../channels-sv2/src/vardiff/error.rs | 0 .../channels-sv2/src/vardiff/mod.rs | 0 .../channels-sv2/src/vardiff/test/classic.rs | 0 .../channels-sv2/src/vardiff/test/mod.rs | 0 {protocols/v2 => sv2}/codec-sv2/Cargo.toml | 8 +- {protocols/v2 => sv2}/codec-sv2/README.md | 0 .../codec-sv2/examples/encrypted.rs | 0 .../codec-sv2/examples/unencrypted.rs | 0 .../v2 => sv2}/codec-sv2/src/decoder.rs | 8 +- .../v2 => sv2}/codec-sv2/src/encoder.rs | 0 {protocols/v2 => sv2}/codec-sv2/src/error.rs | 0 {protocols/v2 => sv2}/codec-sv2/src/lib.rs | 0 {protocols/v2 => sv2}/framing-sv2/Cargo.toml | 8 +- {protocols/v2 => sv2}/framing-sv2/README.md | 0 .../framing-sv2/examples/sv2_frame.rs | 0 .../v2 => sv2}/framing-sv2/src/error.rs | 0 .../v2 => sv2}/framing-sv2/src/framing.rs | 0 .../v2 => sv2}/framing-sv2/src/header.rs | 0 {protocols/v2 => sv2}/framing-sv2/src/lib.rs | 0 {protocols/v2 => sv2}/handlers-sv2/Cargo.toml | 0 {protocols/v2 => sv2}/handlers-sv2/README.md | 0 .../v2 => sv2}/handlers-sv2/src/common.rs | 0 .../v2 => sv2}/handlers-sv2/src/error.rs | 0 .../handlers-sv2/src/job_declaration.rs | 0 {protocols/v2 => sv2}/handlers-sv2/src/lib.rs | 0 .../v2 => sv2}/handlers-sv2/src/mining.rs | 0 .../handlers-sv2/src/template_distribution.rs | 0 {protocols/v2 => sv2}/noise-sv2/Cargo.toml | 0 {protocols/v2 => sv2}/noise-sv2/README.md | 0 .../noise-sv2/examples/handshake.rs | 0 .../v2 => sv2}/noise-sv2/src/aed_cipher.rs | 0 .../v2 => sv2}/noise-sv2/src/cipher_state.rs | 0 {protocols/v2 => sv2}/noise-sv2/src/error.rs | 0 .../v2 => sv2}/noise-sv2/src/handshake.rs | 0 .../v2 => sv2}/noise-sv2/src/initiator.rs | 0 {protocols/v2 => sv2}/noise-sv2/src/lib.rs | 0 .../v2 => sv2}/noise-sv2/src/responder.rs | 0 .../noise-sv2/src/signature_message.rs | 0 {protocols/v2 => sv2}/noise-sv2/src/test.rs | 0 {protocols/v2 => sv2}/parsers-sv2/Cargo.toml | 0 {protocols/v2 => sv2}/parsers-sv2/README.md | 0 .../v2 => sv2}/parsers-sv2/src/error.rs | 0 {protocols/v2 => sv2}/parsers-sv2/src/lib.rs | 0 .../subprotocols/common-messages/Cargo.toml | 0 .../subprotocols/common-messages/README.md | 0 .../src/channel_endpoint_changed.rs | 0 .../subprotocols/common-messages/src/lib.rs | 0 .../common-messages/src/reconnect.rs | 0 .../common-messages/src/setup_connection.rs | 0 .../subprotocols/job-declaration/Cargo.toml | 0 .../subprotocols/job-declaration/README.md | 0 .../job-declaration/job-negotiation-flow.png | Bin .../src/allocate_mining_job_token.rs | 0 .../job-declaration/src/declare_mining_job.rs | 0 .../subprotocols/job-declaration/src/lib.rs | 0 .../src/provide_missing_transactions.rs | 0 .../job-declaration/src/push_solution.rs | 0 .../v2 => sv2}/subprotocols/mining/Cargo.toml | 0 .../v2 => sv2}/subprotocols/mining/README.md | 0 .../subprotocols/mining/src/close_channel.rs | 0 .../v2 => sv2}/subprotocols/mining/src/lib.rs | 0 .../subprotocols/mining/src/new_mining_job.rs | 0 .../subprotocols/mining/src/open_channel.rs | 0 .../mining/src/set_custom_mining_job.rs | 0 .../mining/src/set_extranonce_prefix.rs | 0 .../mining/src/set_group_channel.rs | 0 .../mining/src/set_new_prev_hash.rs | 0 .../subprotocols/mining/src/set_target.rs | 0 .../subprotocols/mining/src/submit_shares.rs | 0 .../subprotocols/mining/src/update_channel.rs | 0 .../template-distribution/Cargo.toml | 0 .../template-distribution/README.md | 0 .../src/coinbase_output_constraints.rs | 0 .../template-distribution/src/lib.rs | 0 .../template-distribution/src/new_template.rs | 0 .../src/request_transaction_data.rs | 0 .../src/set_new_prev_hash.rs | 0 .../src/submit_solution.rs | 0 protocols/tarpaulin.toml => tarpaulin.toml | 0 test/integration-tests/.config/nextest.toml | 17 - test/integration-tests/Cargo.lock | 3249 -------------- test/integration-tests/Cargo.toml | 33 - test/integration-tests/README.md | 30 - .../high_diff_chain/blocks/.lock | 0 .../high_diff_chain/blocks/blk00000.dat | Bin 16777216 -> 0 bytes .../high_diff_chain/blocks/index/000005.ldb | Bin 3080200 -> 0 bytes .../high_diff_chain/blocks/index/000255.ldb | Bin 3452584 -> 0 bytes .../high_diff_chain/blocks/index/000257.ldb | Bin 376 -> 0 bytes .../high_diff_chain/blocks/index/000260.ldb | Bin 2946 -> 0 bytes .../high_diff_chain/blocks/index/000261.log | Bin 751 -> 0 bytes .../high_diff_chain/blocks/index/CURRENT | 1 - .../high_diff_chain/blocks/index/LOCK | 0 .../blocks/index/MANIFEST-000259 | Bin 301 -> 0 bytes .../high_diff_chain/blocks/rev00000.dat | Bin 2097152 -> 0 bytes .../high_diff_chain/blocks/xor.dat | 1 - .../high_diff_chain/chainstate/000289.log | Bin 580 -> 0 bytes .../high_diff_chain/chainstate/000290.ldb | Bin 4070295 -> 0 bytes .../high_diff_chain/chainstate/CURRENT | 1 - .../high_diff_chain/chainstate/LOCK | 0 .../chainstate/MANIFEST-000287 | Bin 281 -> 0 bytes test/integration-tests/lib/interceptor.rs | 116 - .../lib/message_aggregator.rs | 92 - test/integration-tests/lib/mock_roles.rs | 191 - test/integration-tests/lib/mod.rs | 334 -- test/integration-tests/lib/sniffer.rs | 390 -- test/integration-tests/lib/sniffer_error.rs | 5 - .../integration-tests/lib/sv1_minerd/error.rs | 70 - test/integration-tests/lib/sv1_minerd/mod.rs | 5 - .../lib/sv1_minerd/process.rs | 592 --- test/integration-tests/lib/sv1_sniffer.rs | 193 - .../lib/template_provider.rs | 236 - test/integration-tests/lib/types.rs | 4 - test/integration-tests/lib/utils.rs | 448 -- .../integration-tests/tests/jd_integration.rs | 244 -- .../tests/jd_provide_missing_transaction.rs | 41 - .../tests/jd_tproxy_integration.rs | 48 - .../tests/jdc_block_propagation.rs | 38 - .../jdc_receives_submit_shares_success.rs | 22 - .../tests/jds_block_propagation.rs | 37 - .../tests/pool_integration.rs | 225 - .../tests/sniffer_integration.rs | 136 - test/integration-tests/tests/sv1.rs | 25 - .../tests/sv2_mining_device.rs | 20 - .../tests/template_provider_integration.rs | 25 - .../tests/translator_integration.rs | 51 - utils/Cargo.lock | 813 ---- utils/Cargo.toml | 10 - utils/tarpaulin.toml | 7 - 374 files changed, 359 insertions(+), 38585 deletions(-) delete mode 100644 .github/workflows/coverage-roles.yaml delete mode 100644 .github/workflows/coverage-utils.yaml delete mode 100644 .github/workflows/lockfiles.yaml rename roles/Cargo.toml => Cargo.toml (53%) delete mode 100644 examples/ping-pong-encrypted/Cargo.toml delete mode 100644 examples/ping-pong-encrypted/README.md delete mode 100644 examples/ping-pong-encrypted/src/client.rs delete mode 100644 examples/ping-pong-encrypted/src/error.rs delete mode 100644 examples/ping-pong-encrypted/src/main.rs delete mode 100644 examples/ping-pong-encrypted/src/messages.rs delete mode 100644 examples/ping-pong-encrypted/src/server.rs delete mode 100644 examples/ping-pong/Cargo.toml delete mode 100644 examples/ping-pong/README.md delete mode 100644 examples/ping-pong/src/client.rs delete mode 100644 examples/ping-pong/src/error.rs delete mode 100644 examples/ping-pong/src/main.rs delete mode 100644 examples/ping-pong/src/messages.rs delete mode 100644 examples/ping-pong/src/server.rs rename {protocols/fuzz-tests => fuzz-tests}/Cargo.lock (56%) rename {protocols/fuzz-tests => fuzz-tests}/Cargo.toml (77%) rename {protocols/fuzz-tests => fuzz-tests}/src/main.rs (100%) delete mode 100644 protocols/Cargo.toml delete mode 100644 protocols/v2/binary-sv2/derive_codec/.gitignore delete mode 100644 roles/Cargo.lock delete mode 100644 roles/jd-client/Cargo.toml delete mode 100644 roles/jd-client/README.md delete mode 100644 roles/jd-client/config-examples/jdc-config-hosted-example.toml delete mode 100644 roles/jd-client/config-examples/jdc-config-local-example.toml delete mode 100644 roles/jd-client/src/args.rs delete mode 100644 roles/jd-client/src/lib/channel_manager/downstream_message_handler.rs delete mode 100644 roles/jd-client/src/lib/channel_manager/jd_message_handler.rs delete mode 100644 roles/jd-client/src/lib/channel_manager/mod.rs delete mode 100644 roles/jd-client/src/lib/channel_manager/template_message_handler.rs delete mode 100644 roles/jd-client/src/lib/channel_manager/upstream_message_handler.rs delete mode 100644 roles/jd-client/src/lib/config.rs delete mode 100644 roles/jd-client/src/lib/downstream/message_handler.rs delete mode 100644 roles/jd-client/src/lib/downstream/mod.rs delete mode 100644 roles/jd-client/src/lib/error.rs delete mode 100644 roles/jd-client/src/lib/jd_mode.rs delete mode 100644 roles/jd-client/src/lib/job_declarator/message_handler.rs delete mode 100644 roles/jd-client/src/lib/job_declarator/mod.rs delete mode 100644 roles/jd-client/src/lib/mod.rs delete mode 100644 roles/jd-client/src/lib/status.rs delete mode 100644 roles/jd-client/src/lib/task_manager.rs delete mode 100644 roles/jd-client/src/lib/template_receiver/message_handler.rs delete mode 100644 roles/jd-client/src/lib/template_receiver/mod.rs delete mode 100644 roles/jd-client/src/lib/upstream/message_handler.rs delete mode 100644 roles/jd-client/src/lib/upstream/mod.rs delete mode 100644 roles/jd-client/src/lib/utils.rs delete mode 100644 roles/jd-client/src/main.rs delete mode 100644 roles/jd-server/Cargo.toml delete mode 100644 roles/jd-server/config-examples/jds-config-hosted-example.toml delete mode 100644 roles/jd-server/config-examples/jds-config-local-example.toml delete mode 100644 roles/jd-server/src/args.rs delete mode 100644 roles/jd-server/src/lib/config.rs delete mode 100644 roles/jd-server/src/lib/error.rs delete mode 100644 roles/jd-server/src/lib/job_declarator/message_handler.rs delete mode 100644 roles/jd-server/src/lib/job_declarator/mod.rs delete mode 100644 roles/jd-server/src/lib/mempool/error.rs delete mode 100644 roles/jd-server/src/lib/mempool/mod.rs delete mode 100644 roles/jd-server/src/lib/mod.rs delete mode 100644 roles/jd-server/src/lib/status.rs delete mode 100644 roles/jd-server/src/main.rs delete mode 100644 roles/pool/Cargo.toml delete mode 100644 roles/pool/README.md delete mode 100644 roles/pool/config-examples/pool-config-hosted-tp-example.toml delete mode 100644 roles/pool/config-examples/pool-config-local-tp-example.toml delete mode 100644 roles/pool/src/args.rs delete mode 100644 roles/pool/src/lib/channel_manager/mining_message_handler.rs delete mode 100644 roles/pool/src/lib/channel_manager/mod.rs delete mode 100644 roles/pool/src/lib/channel_manager/template_distribution_message_handler.rs delete mode 100644 roles/pool/src/lib/config.rs delete mode 100644 roles/pool/src/lib/downstream/common_message_handler.rs delete mode 100644 roles/pool/src/lib/downstream/mod.rs delete mode 100644 roles/pool/src/lib/error.rs delete mode 100644 roles/pool/src/lib/mod.rs delete mode 100644 roles/pool/src/lib/status.rs delete mode 100644 roles/pool/src/lib/task_manager.rs delete mode 100644 roles/pool/src/lib/template_receiver/common_message_handler.rs delete mode 100644 roles/pool/src/lib/template_receiver/mod.rs delete mode 100644 roles/pool/src/lib/utils.rs delete mode 100644 roles/pool/src/main.rs delete mode 100644 roles/stratum-apps/Cargo.toml delete mode 100644 roles/stratum-apps/README.md delete mode 100644 roles/stratum-apps/src/config_helpers/coinbase_output/errors.rs delete mode 100644 roles/stratum-apps/src/config_helpers/coinbase_output/mod.rs delete mode 100644 roles/stratum-apps/src/config_helpers/coinbase_output/serde_types.rs delete mode 100644 roles/stratum-apps/src/config_helpers/logging.rs delete mode 100644 roles/stratum-apps/src/config_helpers/mod.rs delete mode 100644 roles/stratum-apps/src/config_helpers/toml.rs delete mode 100644 roles/stratum-apps/src/custom_mutex.rs delete mode 100644 roles/stratum-apps/src/key_utils/mod.rs delete mode 100644 roles/stratum-apps/src/lib.rs delete mode 100644 roles/stratum-apps/src/network_helpers/mod.rs delete mode 100644 roles/stratum-apps/src/network_helpers/noise_connection.rs delete mode 100644 roles/stratum-apps/src/network_helpers/noise_stream.rs delete mode 100644 roles/stratum-apps/src/network_helpers/plain_connection.rs delete mode 100644 roles/stratum-apps/src/network_helpers/sv1_connection.rs delete mode 100644 roles/stratum-apps/src/rpc/mini_rpc_client.rs delete mode 100644 roles/stratum-apps/src/rpc/mod.rs delete mode 100644 roles/tarpaulin.toml delete mode 100644 roles/test-utils/mining-device/Cargo.toml delete mode 100644 roles/test-utils/mining-device/README.md delete mode 100644 roles/test-utils/mining-device/benches/hasher_bench.rs delete mode 100644 roles/test-utils/mining-device/benches/microbatch_bench.rs delete mode 100644 roles/test-utils/mining-device/benches/scaling_bench.rs delete mode 100644 roles/test-utils/mining-device/src/lib/mod.rs delete mode 100644 roles/test-utils/mining-device/src/main.rs delete mode 100644 roles/test-utils/mining-device/tests/fast_hasher_equivalence.rs delete mode 100644 roles/translator/Cargo.toml delete mode 100644 roles/translator/README.md delete mode 100644 roles/translator/config-examples/tproxy-config-hosted-pool-example.toml delete mode 100644 roles/translator/config-examples/tproxy-config-local-jdc-example.toml delete mode 100644 roles/translator/config-examples/tproxy-config-local-pool-example.toml delete mode 100644 roles/translator/src/args.rs delete mode 100644 roles/translator/src/lib/config.rs delete mode 100644 roles/translator/src/lib/error.rs delete mode 100644 roles/translator/src/lib/mod.rs delete mode 100644 roles/translator/src/lib/status.rs delete mode 100644 roles/translator/src/lib/sv1/downstream/channel.rs delete mode 100644 roles/translator/src/lib/sv1/downstream/data.rs delete mode 100644 roles/translator/src/lib/sv1/downstream/downstream.rs delete mode 100644 roles/translator/src/lib/sv1/downstream/message_handler.rs delete mode 100644 roles/translator/src/lib/sv1/downstream/mod.rs delete mode 100644 roles/translator/src/lib/sv1/mod.rs delete mode 100644 roles/translator/src/lib/sv1/sv1_server/channel.rs delete mode 100644 roles/translator/src/lib/sv1/sv1_server/data.rs delete mode 100644 roles/translator/src/lib/sv1/sv1_server/difficulty_manager.rs delete mode 100644 roles/translator/src/lib/sv1/sv1_server/mod.rs delete mode 100644 roles/translator/src/lib/sv1/sv1_server/sv1_server.rs delete mode 100644 roles/translator/src/lib/sv2/channel_manager/channel.rs delete mode 100644 roles/translator/src/lib/sv2/channel_manager/channel_manager.rs delete mode 100644 roles/translator/src/lib/sv2/channel_manager/data.rs delete mode 100644 roles/translator/src/lib/sv2/channel_manager/message_handler.rs delete mode 100644 roles/translator/src/lib/sv2/channel_manager/mod.rs delete mode 100644 roles/translator/src/lib/sv2/mod.rs delete mode 100644 roles/translator/src/lib/sv2/upstream/channel.rs delete mode 100644 roles/translator/src/lib/sv2/upstream/message_handler.rs delete mode 100644 roles/translator/src/lib/sv2/upstream/mod.rs delete mode 100644 roles/translator/src/lib/sv2/upstream/upstream.rs delete mode 100644 roles/translator/src/lib/task_manager.rs delete mode 100644 roles/translator/src/lib/utils.rs delete mode 100644 roles/translator/src/main.rs delete mode 100644 rustfmt.toml delete mode 100755 scripts/build-on-all-workspaces.sh delete mode 100755 scripts/clippy-fmt-and-test.sh delete mode 100755 scripts/coverage-roles.sh delete mode 100755 scripts/coverage-utils.sh create mode 100755 scripts/run-integration-tests.sh delete mode 100755 scripts/rust/clippy.sh rename {protocols => stratum-core}/stratum-translation/Cargo.toml (54%) rename {protocols => stratum-core}/stratum-translation/src/error.rs (100%) rename {protocols => stratum-core}/stratum-translation/src/lib.rs (100%) rename {protocols => stratum-core}/stratum-translation/src/sv1_to_sv2.rs (100%) rename {protocols => stratum-core}/stratum-translation/src/sv2_to_sv1.rs (100%) rename {protocols/v1 => sv1}/Cargo.toml (92%) rename {protocols/v1 => sv1}/README.md (100%) rename {protocols/v1 => sv1}/examples/client_and_server.rs (100%) rename {protocols/v1 => sv1}/src/error.rs (100%) rename {protocols/v1 => sv1}/src/json_rpc.rs (100%) rename {protocols/v1 => sv1}/src/lib.rs (100%) rename {protocols/v1 => sv1}/src/methods/client_to_server.rs (100%) rename {protocols/v1 => sv1}/src/methods/mod.rs (100%) rename {protocols/v1 => sv1}/src/methods/server_to_client.rs (100%) rename {protocols/v1 => sv1}/src/utils.rs (100%) rename {protocols/v2 => sv2}/binary-sv2/Cargo.toml (90%) rename {protocols/v2 => sv2}/binary-sv2/README.md (100%) rename {protocols/fuzz-tests => sv2/binary-sv2/derive_codec}/.gitignore (100%) rename {protocols/v2 => sv2}/binary-sv2/derive_codec/Cargo.toml (100%) rename {protocols/v2 => sv2}/binary-sv2/derive_codec/README.md (100%) rename {protocols/v2 => sv2}/binary-sv2/derive_codec/src/lib.rs (100%) rename {protocols/v2 => sv2}/binary-sv2/examples/encode_decode.rs (100%) rename {protocols/v2 => sv2}/binary-sv2/src/codec/decodable.rs (100%) rename {protocols/v2 => sv2}/binary-sv2/src/codec/encodable.rs (100%) rename {protocols/v2 => sv2}/binary-sv2/src/codec/impls.rs (100%) rename {protocols/v2 => sv2}/binary-sv2/src/codec/mod.rs (100%) rename {protocols/v2 => sv2}/binary-sv2/src/datatypes/copy_data_types.rs (100%) rename {protocols/v2 => sv2}/binary-sv2/src/datatypes/mod.rs (100%) rename {protocols/v2 => sv2}/binary-sv2/src/datatypes/non_copy_data_types/inner.rs (100%) rename {protocols/v2 => sv2}/binary-sv2/src/datatypes/non_copy_data_types/mod.rs (100%) rename {protocols/v2 => sv2}/binary-sv2/src/datatypes/non_copy_data_types/seq_inner.rs (100%) rename {protocols/v2 => sv2}/binary-sv2/src/lib.rs (100%) rename {protocols/v2 => sv2}/binary-sv2/tests/test.rs (100%) rename {utils/buffer => sv2/buffer-sv2}/BENCHES.md (100%) rename {utils/buffer => sv2/buffer-sv2}/Cargo.toml (100%) rename {utils/buffer => sv2/buffer-sv2}/README.md (100%) rename {utils/buffer => sv2/buffer-sv2}/benches/control_struct.rs (100%) rename {utils/buffer => sv2/buffer-sv2}/benches/pool_benchmark.rs (100%) rename {utils/buffer => sv2/buffer-sv2}/benches/pool_iai.rs (100%) rename {utils/buffer => sv2/buffer-sv2}/examples/basic_buffer_pool.rs (100%) rename {utils/buffer => sv2/buffer-sv2}/examples/buffer_pool_exhaustion.rs (100%) rename {utils/buffer => sv2/buffer-sv2}/examples/variable_sized_messages.rs (100%) rename {utils/buffer => sv2/buffer-sv2}/fuzz/.gitignore (100%) rename {utils/buffer => sv2/buffer-sv2}/fuzz/Cargo.toml (100%) rename {utils/buffer => sv2/buffer-sv2}/fuzz/fuzz_targets/faster.rs (100%) rename {utils/buffer => sv2/buffer-sv2}/fuzz/fuzz_targets/slower.rs (100%) rename {utils/buffer => sv2/buffer-sv2}/fuzz/random (100%) rename {utils/buffer => sv2/buffer-sv2}/fuzz/run.sh (100%) rename {utils/buffer => sv2/buffer-sv2}/fuzz/rust-toolchain.toml (100%) rename {utils/buffer => sv2/buffer-sv2}/src/buffer.rs (100%) rename {utils/buffer => sv2/buffer-sv2}/src/buffer_pool/mod.rs (100%) rename {utils/buffer => sv2/buffer-sv2}/src/buffer_pool/pool_back.rs (100%) rename {utils/buffer => sv2/buffer-sv2}/src/lib.rs (100%) rename {utils/buffer => sv2/buffer-sv2}/src/slice.rs (100%) rename {utils/buffer => sv2/buffer-sv2}/src/test.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/Cargo.toml (100%) rename {protocols/v2 => sv2}/channels-sv2/README.md (100%) rename {protocols/v2 => sv2}/channels-sv2/src/bip141.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/chain_tip.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/client/error.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/client/extended.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/client/group.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/client/mod.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/client/share_accounting.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/client/standard.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/lib.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/merkle_root.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/outputs.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/server/error.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/server/extended.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/server/group.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/server/jobs/error.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/server/jobs/extended.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/server/jobs/factory.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/server/jobs/job_store.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/server/jobs/mod.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/server/jobs/standard.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/server/mod.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/server/share_accounting.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/server/standard.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/target.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/vardiff/classic.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/vardiff/error.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/vardiff/mod.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/vardiff/test/classic.rs (100%) rename {protocols/v2 => sv2}/channels-sv2/src/vardiff/test/mod.rs (100%) rename {protocols/v2 => sv2}/codec-sv2/Cargo.toml (70%) rename {protocols/v2 => sv2}/codec-sv2/README.md (100%) rename {protocols/v2 => sv2}/codec-sv2/examples/encrypted.rs (100%) rename {protocols/v2 => sv2}/codec-sv2/examples/unencrypted.rs (100%) rename {protocols/v2 => sv2}/codec-sv2/src/decoder.rs (97%) rename {protocols/v2 => sv2}/codec-sv2/src/encoder.rs (100%) rename {protocols/v2 => sv2}/codec-sv2/src/error.rs (100%) rename {protocols/v2 => sv2}/codec-sv2/src/lib.rs (100%) rename {protocols/v2 => sv2}/framing-sv2/Cargo.toml (70%) rename {protocols/v2 => sv2}/framing-sv2/README.md (100%) rename {protocols/v2 => sv2}/framing-sv2/examples/sv2_frame.rs (100%) rename {protocols/v2 => sv2}/framing-sv2/src/error.rs (100%) rename {protocols/v2 => sv2}/framing-sv2/src/framing.rs (100%) rename {protocols/v2 => sv2}/framing-sv2/src/header.rs (100%) rename {protocols/v2 => sv2}/framing-sv2/src/lib.rs (100%) rename {protocols/v2 => sv2}/handlers-sv2/Cargo.toml (100%) rename {protocols/v2 => sv2}/handlers-sv2/README.md (100%) rename {protocols/v2 => sv2}/handlers-sv2/src/common.rs (100%) rename {protocols/v2 => sv2}/handlers-sv2/src/error.rs (100%) rename {protocols/v2 => sv2}/handlers-sv2/src/job_declaration.rs (100%) rename {protocols/v2 => sv2}/handlers-sv2/src/lib.rs (100%) rename {protocols/v2 => sv2}/handlers-sv2/src/mining.rs (100%) rename {protocols/v2 => sv2}/handlers-sv2/src/template_distribution.rs (100%) rename {protocols/v2 => sv2}/noise-sv2/Cargo.toml (100%) rename {protocols/v2 => sv2}/noise-sv2/README.md (100%) rename {protocols/v2 => sv2}/noise-sv2/examples/handshake.rs (100%) rename {protocols/v2 => sv2}/noise-sv2/src/aed_cipher.rs (100%) rename {protocols/v2 => sv2}/noise-sv2/src/cipher_state.rs (100%) rename {protocols/v2 => sv2}/noise-sv2/src/error.rs (100%) rename {protocols/v2 => sv2}/noise-sv2/src/handshake.rs (100%) rename {protocols/v2 => sv2}/noise-sv2/src/initiator.rs (100%) rename {protocols/v2 => sv2}/noise-sv2/src/lib.rs (100%) rename {protocols/v2 => sv2}/noise-sv2/src/responder.rs (100%) rename {protocols/v2 => sv2}/noise-sv2/src/signature_message.rs (100%) rename {protocols/v2 => sv2}/noise-sv2/src/test.rs (100%) rename {protocols/v2 => sv2}/parsers-sv2/Cargo.toml (100%) rename {protocols/v2 => sv2}/parsers-sv2/README.md (100%) rename {protocols/v2 => sv2}/parsers-sv2/src/error.rs (100%) rename {protocols/v2 => sv2}/parsers-sv2/src/lib.rs (100%) rename {protocols/v2 => sv2}/subprotocols/common-messages/Cargo.toml (100%) rename {protocols/v2 => sv2}/subprotocols/common-messages/README.md (100%) rename {protocols/v2 => sv2}/subprotocols/common-messages/src/channel_endpoint_changed.rs (100%) rename {protocols/v2 => sv2}/subprotocols/common-messages/src/lib.rs (100%) rename {protocols/v2 => sv2}/subprotocols/common-messages/src/reconnect.rs (100%) rename {protocols/v2 => sv2}/subprotocols/common-messages/src/setup_connection.rs (100%) rename {protocols/v2 => sv2}/subprotocols/job-declaration/Cargo.toml (100%) rename {protocols/v2 => sv2}/subprotocols/job-declaration/README.md (100%) rename {protocols/v2 => sv2}/subprotocols/job-declaration/job-negotiation-flow.png (100%) rename {protocols/v2 => sv2}/subprotocols/job-declaration/src/allocate_mining_job_token.rs (100%) rename {protocols/v2 => sv2}/subprotocols/job-declaration/src/declare_mining_job.rs (100%) rename {protocols/v2 => sv2}/subprotocols/job-declaration/src/lib.rs (100%) rename {protocols/v2 => sv2}/subprotocols/job-declaration/src/provide_missing_transactions.rs (100%) rename {protocols/v2 => sv2}/subprotocols/job-declaration/src/push_solution.rs (100%) rename {protocols/v2 => sv2}/subprotocols/mining/Cargo.toml (100%) rename {protocols/v2 => sv2}/subprotocols/mining/README.md (100%) rename {protocols/v2 => sv2}/subprotocols/mining/src/close_channel.rs (100%) rename {protocols/v2 => sv2}/subprotocols/mining/src/lib.rs (100%) rename {protocols/v2 => sv2}/subprotocols/mining/src/new_mining_job.rs (100%) rename {protocols/v2 => sv2}/subprotocols/mining/src/open_channel.rs (100%) rename {protocols/v2 => sv2}/subprotocols/mining/src/set_custom_mining_job.rs (100%) rename {protocols/v2 => sv2}/subprotocols/mining/src/set_extranonce_prefix.rs (100%) rename {protocols/v2 => sv2}/subprotocols/mining/src/set_group_channel.rs (100%) rename {protocols/v2 => sv2}/subprotocols/mining/src/set_new_prev_hash.rs (100%) rename {protocols/v2 => sv2}/subprotocols/mining/src/set_target.rs (100%) rename {protocols/v2 => sv2}/subprotocols/mining/src/submit_shares.rs (100%) rename {protocols/v2 => sv2}/subprotocols/mining/src/update_channel.rs (100%) rename {protocols/v2 => sv2}/subprotocols/template-distribution/Cargo.toml (100%) rename {protocols/v2 => sv2}/subprotocols/template-distribution/README.md (100%) rename {protocols/v2 => sv2}/subprotocols/template-distribution/src/coinbase_output_constraints.rs (100%) rename {protocols/v2 => sv2}/subprotocols/template-distribution/src/lib.rs (100%) rename {protocols/v2 => sv2}/subprotocols/template-distribution/src/new_template.rs (100%) rename {protocols/v2 => sv2}/subprotocols/template-distribution/src/request_transaction_data.rs (100%) rename {protocols/v2 => sv2}/subprotocols/template-distribution/src/set_new_prev_hash.rs (100%) rename {protocols/v2 => sv2}/subprotocols/template-distribution/src/submit_solution.rs (100%) rename protocols/tarpaulin.toml => tarpaulin.toml (100%) delete mode 100644 test/integration-tests/.config/nextest.toml delete mode 100644 test/integration-tests/Cargo.lock delete mode 100644 test/integration-tests/Cargo.toml delete mode 100644 test/integration-tests/README.md delete mode 100644 test/integration-tests/high_diff_chain/blocks/.lock delete mode 100644 test/integration-tests/high_diff_chain/blocks/blk00000.dat delete mode 100644 test/integration-tests/high_diff_chain/blocks/index/000005.ldb delete mode 100644 test/integration-tests/high_diff_chain/blocks/index/000255.ldb delete mode 100644 test/integration-tests/high_diff_chain/blocks/index/000257.ldb delete mode 100644 test/integration-tests/high_diff_chain/blocks/index/000260.ldb delete mode 100644 test/integration-tests/high_diff_chain/blocks/index/000261.log delete mode 100644 test/integration-tests/high_diff_chain/blocks/index/CURRENT delete mode 100644 test/integration-tests/high_diff_chain/blocks/index/LOCK delete mode 100644 test/integration-tests/high_diff_chain/blocks/index/MANIFEST-000259 delete mode 100644 test/integration-tests/high_diff_chain/blocks/rev00000.dat delete mode 100644 test/integration-tests/high_diff_chain/blocks/xor.dat delete mode 100644 test/integration-tests/high_diff_chain/chainstate/000289.log delete mode 100644 test/integration-tests/high_diff_chain/chainstate/000290.ldb delete mode 100644 test/integration-tests/high_diff_chain/chainstate/CURRENT delete mode 100644 test/integration-tests/high_diff_chain/chainstate/LOCK delete mode 100644 test/integration-tests/high_diff_chain/chainstate/MANIFEST-000287 delete mode 100644 test/integration-tests/lib/interceptor.rs delete mode 100644 test/integration-tests/lib/message_aggregator.rs delete mode 100644 test/integration-tests/lib/mock_roles.rs delete mode 100644 test/integration-tests/lib/mod.rs delete mode 100644 test/integration-tests/lib/sniffer.rs delete mode 100644 test/integration-tests/lib/sniffer_error.rs delete mode 100644 test/integration-tests/lib/sv1_minerd/error.rs delete mode 100644 test/integration-tests/lib/sv1_minerd/mod.rs delete mode 100644 test/integration-tests/lib/sv1_minerd/process.rs delete mode 100644 test/integration-tests/lib/sv1_sniffer.rs delete mode 100644 test/integration-tests/lib/template_provider.rs delete mode 100644 test/integration-tests/lib/types.rs delete mode 100644 test/integration-tests/lib/utils.rs delete mode 100644 test/integration-tests/tests/jd_integration.rs delete mode 100644 test/integration-tests/tests/jd_provide_missing_transaction.rs delete mode 100644 test/integration-tests/tests/jd_tproxy_integration.rs delete mode 100644 test/integration-tests/tests/jdc_block_propagation.rs delete mode 100644 test/integration-tests/tests/jdc_receives_submit_shares_success.rs delete mode 100644 test/integration-tests/tests/jds_block_propagation.rs delete mode 100644 test/integration-tests/tests/pool_integration.rs delete mode 100644 test/integration-tests/tests/sniffer_integration.rs delete mode 100644 test/integration-tests/tests/sv1.rs delete mode 100644 test/integration-tests/tests/sv2_mining_device.rs delete mode 100644 test/integration-tests/tests/template_provider_integration.rs delete mode 100644 test/integration-tests/tests/translator_integration.rs delete mode 100644 utils/Cargo.lock delete mode 100644 utils/Cargo.toml delete mode 100644 utils/tarpaulin.toml diff --git a/.github/workflows/clippy-lint.yaml b/.github/workflows/clippy-lint.yaml index e73915f245..5fca2b93a1 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 -- -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..6ace665587 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,11 @@ -


SRI
-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 ๐Ÿฆ€

codecov @@ -18,104 +15,81 @@ 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 +# Stratum Repository -To get started with the Stratum V2 Reference Implementation (SRI), please follow the detailed setup instructions available on the official website: +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, and may contain bugs. They are intended primarily as examples of how to use the low-level crates as dependencies. -[Getting Started with Stratum V2](https://stratumprotocol.org/blog/getting-started/) +## Contents -This guide provides all the necessary information on prerequisites, installation, and configuration to help you begin using, testing or contributing to SRI. +- `sv1/` - Stratum V1 protocol implementation and utilities +- `sv2/` - Stratum V2 protocol implementations + - `buffer/` - Buffer management and pooling + - `binary-sv2/` - Binary encoding/decoding for SV2 messages + - `codec-sv2/` - SV2 message codec with encryption support + - `framing-sv2/` - SV2 message framing utilities + - `noise-sv2/` - Noise protocol implementation for SV2 + - `subprotocols/` - SV2 subprotocol implementations + - `channels-sv2/` - Channel management for SV2 + - `roles-logic-sv2/` - Common logic for SV2 roles + - `parsers-sv2/` - Message parsing utilities + - `sv2-ffi/` - Foreign Function Interface for SV2 +- `stratum-core/` - Entrypoint for all the low-level crates in `sv2/` and `sv1/`implementations + - `stratum-translation` - Stratum V1 โ†” Stratum V2 translation utilities -## ๐Ÿš€ Use Cases +## Local Integration Testing -The library is modular to address different use-cases and desired functionality. Examples include: +To run integration tests locally: -### ๐Ÿ‘ท Miners +```bash +./scripts/run-integration-tests.sh +``` -- 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.) - -### ๐Ÿ› ๏ธ 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. +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. [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. - -### ๐ŸŽฏ 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. - ## ๐Ÿ’ป 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 pool applications! 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 -If you wish to support the development and maintenance of the Stratum V2 Reference Implementation, individual donations are greatly appreciated. You can donate through OpenSats, a 501(c)(3) public charity dedicated to supporting open-source Bitcoin projects. +Support the development of Stratum V2 and these pool applications through OpenSats: [Donate through OpenSats](https://opensats.org/projects/stratumv2) ### ๐Ÿข 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. +For corporate support and grants, contact us directly: -Email us at: stratumv2@gmail.com +Email: stratumv2@gmail.com ## ๐Ÿ™ Supporters -SRI contributors are independently, financially supported by following entities: +SRI contributors are independently supported by these organizations:

@@ -134,4 +108,5 @@ 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)  ·  +> Main Repository [stratum-mining/stratum](https://github.com/stratum-mining/stratum) 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 + Send + 'static, - { - let handle = tokio::spawn(async move { - fut.await; - }); - self.tasks.lock().unwrap().push(handle); - } - - /// Waits for all managed tasks to complete. - /// - /// This method will block until all tasks that were spawned through this - /// manager have finished executing. Tasks are joined in reverse order - /// (most recently spawned first). - pub async fn join_all(&self) { - let handles = { - let mut tasks = self.tasks.lock().unwrap(); - std::mem::take(&mut *tasks) - }; - - for handle in handles { - let _ = handle.await; - } - } - - /// Aborts all managed tasks. - /// - /// This method immediately cancels all tasks that were spawned through this - /// manager. The tasks will be terminated without waiting for them to complete. - pub async fn abort_all(&self) { - let mut tasks = self.tasks.lock().unwrap(); - for handle in tasks.drain(..) { - handle.abort(); - } - } -} diff --git a/roles/jd-client/src/lib/template_receiver/message_handler.rs b/roles/jd-client/src/lib/template_receiver/message_handler.rs deleted file mode 100644 index 61a8f56a4f..0000000000 --- a/roles/jd-client/src/lib/template_receiver/message_handler.rs +++ /dev/null @@ -1,50 +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, template_receiver::TemplateReceiver}; - -impl HandleCommonMessagesFromServerAsync for TemplateReceiver { - type Error = JDCError; - - async fn handle_setup_connection_success( - &mut self, - _server_id: Option, - msg: SetupConnectionSuccess, - ) -> Result<(), Self::Error> { - info!("Received: {}", msg); - - 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/template_receiver/mod.rs b/roles/jd-client/src/lib/template_receiver/mod.rs deleted file mode 100644 index 9512680b7f..0000000000 --- a/roles/jd-client/src/lib/template_receiver/mod.rs +++ /dev/null @@ -1,426 +0,0 @@ -//! Template Receiver module -//! -//! This module defines the [`TemplateReceiver`] struct, which manages a connection -//! to a Template Provider (TP). -//! -//! Responsibilities: -//! - Establish TCP + Noise encrypted connection to the template provider -//! - Perform `SetupConnection` handshake -//! - Forward SV2 `TemplateDistribution` messages to the channel manager -//! - Forward messages from the channel manager upstream to the template provider -//! - Send [`CoinbaseOutputConstraints`] to the template provider - -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::{ - bitcoin::{ - self, absolute::LockTime, transaction::Version, OutPoint, ScriptBuf, Sequence, - Transaction, TxIn, TxOut, Witness, - }, - codec_sv2::HandshakeRole, - framing_sv2, - handlers_sv2::HandleCommonMessagesFromServerAsync, - noise_sv2::Initiator, - parsers_sv2::{AnyMessage, TemplateDistribution}, - template_distribution_sv2::CoinbaseOutputConstraints, - }, -}; -use tokio::{net::TcpStream, sync::broadcast}; -use tracing::{debug, error, info, warn}; - -use crate::{ - error::JDCError, - status::{handle_error, Status, StatusSender}, - task_manager::TaskManager, - utils::{ - get_setup_connection_message_tp, protocol_message_type, spawn_io_tasks, Message, - MessageType, SV2Frame, ShutdownMessage, StdFrame, - }, -}; - -mod message_handler; - -/// Placeholder for future template receiverโ€“specific state. -pub struct TemplateReceiverData; - -/// Holds communication channels between the template receiver, channel manager, -/// and upstream template provider. -/// -/// - `channel_manager_sender` โ†’ sends frames to the channel manager -/// - `channel_manager_receiver` โ†’ receives frames from the channel manager -/// - `outbound_tx` โ†’ sends frames upstream to the template provider -/// - `inbound_rx` โ†’ receives frames from the template provider -#[derive(Clone)] -pub struct TemplateReceiverChannel { - channel_manager_sender: Sender, - channel_manager_receiver: Receiver, - tp_sender: Sender, - tp_receiver: Receiver, -} - -/// Manages communication with a Stratum V2 Template Provider. -/// -/// Responsibilities: -/// - Establishes TCP + Noise connection to TP -/// - Performs handshake (`SetupConnection`) -/// - Sends [`CoinbaseOutputConstraints`] to TP -/// - Routes messages between TP and channel manager -/// - Handles shutdown/fallback notifications -#[allow(warnings)] -#[derive(Clone)] -pub struct TemplateReceiver { - /// Internal state - template_receiver_data: Arc>, - /// Messaging channels to/from the channel manager and TP. - template_receiver_channel: TemplateReceiverChannel, - /// Address of the template provider (string form) - tp_address: String, -} - -impl TemplateReceiver { - /// Establish a new connection to a Template Provider. - /// - /// - Opens a TCP connection - /// - Performs Noise handshake - /// - Spawns IO tasks for inbound/outbound frames - /// - /// Retries up to 3 times before returning [`JDCError::Shutdown`]. - pub async fn new( - tp_address: String, - public_key: Option, - channel_manager_receiver: Receiver, - channel_manager_sender: Sender, - notify_shutdown: broadcast::Sender, - task_manager: Arc, - status_sender: Sender, - ) -> Result { - const MAX_RETRIES: usize = 3; - - for attempt in 1..=MAX_RETRIES { - info!(attempt, MAX_RETRIES, "Connecting to template provider"); - - let initiator = match public_key { - Some(pub_key) => { - debug!(attempt, "Using public key for initiator handshake"); - Initiator::from_raw_k(pub_key.into_bytes()) - } - None => { - debug!(attempt, "Using anonymous initiator (no public key)"); - Initiator::without_pk() - } - }?; - - match TcpStream::connect(tp_address.as_str()).await { - Ok(stream) => { - info!( - attempt, - "TCP connection established, starting Noise handshake" - ); - - match NoiseTcpStream::::new( - stream, - HandshakeRole::Initiator(initiator), - ) - .await - { - Ok(noise_stream) => { - info!(attempt, "Noise handshake completed successfully"); - - let (noise_stream_reader, noise_stream_writer) = - noise_stream.into_split(); - - let status_sender = StatusSender::TemplateReceiver(status_sender); - let (inbound_tx, inbound_rx) = unbounded::(); - let (outbound_tx, outbound_rx) = unbounded::(); - - info!(attempt, "Spawning IO tasks for template receiver"); - spawn_io_tasks( - task_manager.clone(), - noise_stream_reader, - noise_stream_writer, - outbound_rx, - inbound_tx, - notify_shutdown, - status_sender, - ); - - let template_receiver_data = Arc::new(Mutex::new(TemplateReceiverData)); - let template_receiver_channel = TemplateReceiverChannel { - channel_manager_receiver, - channel_manager_sender, - tp_receiver: inbound_rx, - tp_sender: outbound_tx, - }; - - info!(attempt, "TemplateReceiver initialized successfully"); - return Ok(TemplateReceiver { - template_receiver_channel, - template_receiver_data, - tp_address, - }); - } - Err(e) => { - error!(attempt, error = ?e, "Noise handshake failed"); - } - } - } - Err(e) => { - warn!(attempt, MAX_RETRIES, error = ?e, "Failed to connect to template provider"); - } - } - - if attempt < MAX_RETRIES { - debug!(attempt, "Retrying connection after backoff"); - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - } - } - - error!("Exhausted all connection attempts, shutting down TemplateReceiver"); - Err(JDCError::Shutdown) - } - - /// Start unified message loop for template receiver. - /// - /// Responsibilities: - /// - Run handshake (`setup_connection`) - /// - Send [`CoinbaseOutputConstraints`] - /// - Handle: - /// - Messages from template provider - /// - Messages from channel manager - /// - Shutdown signals (upstream/job-declarator fallback) - pub async fn start( - mut self, - socket_address: String, - notify_shutdown: broadcast::Sender, - status_sender: Sender, - task_manager: Arc, - coinbase_outputs: Vec, - ) { - let status_sender = StatusSender::TemplateReceiver(status_sender); - let mut shutdown_rx = notify_shutdown.subscribe(); - - info!("Initialized state for starting template receiver"); - _ = self.setup_connection(socket_address).await; - - _ = self.coinbase_constraints(coinbase_outputs).await; - - info!("Setup Connection done. connection with template receiver is now done"); - 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!("Template Receiver: received shutdown signal"); - break; - }, - Ok(ShutdownMessage::UpstreamShutdownFallback((coinbase_outputs,tx))) => { - info!("Template provider: Received Upstream shutdown."); - _ = self.coinbase_constraints(coinbase_outputs).await; - drop(tx); - } - Ok(ShutdownMessage::JobDeclaratorShutdownFallback((coinbase_outputs, tx))) => { - info!("Template provider: Received Job declarator shutdown."); - _ = self.coinbase_constraints(coinbase_outputs).await; - drop(tx); - } - Err(e) => { - warn!(error = ?e, "Template Receiver: shutdown channel closed unexpectedly"); - break; - } - _ => {} - } - } - res = self_clone_1.handle_template_provider_message() => { - if let Err(e) = res { - error!("TemplateReceiver template provider handler failed: {e:?}"); - handle_error(&status_sender, e).await; - break; - } - } - res = self_clone_2.handle_channel_manager_message() => { - if let Err(e) = res { - error!("TemplateReceiver channel manager handler failed: {e:?}"); - handle_error(&status_sender, e).await; - break; - } - }, - } - } - warn!("TemplateReceiver: unified message loop exited."); - }, - ); - } - - /// Handle inbound messages from the template provider. - /// - /// Routes: - /// - `Common` messages โ†’ handled locally - /// - `TemplateDistribution` messages โ†’ forwarded to channel manager - /// - Unsupported messages โ†’ logged and ignored - pub async fn handle_template_provider_message(&mut self) -> Result<(), JDCError> { - let mut sv2_frame = self.template_receiver_channel.tp_receiver.recv().await?; - - debug!("Received SV2 frame from Template provider."); - 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 Template provider." - ); - self.handle_common_message_frame_from_server( - None, - message_type, - sv2_frame.payload(), - ) - .await?; - } - MessageType::TemplateDistribution => { - self.template_receiver_channel - .channel_manager_sender - .send(sv2_frame) - .await - .map_err(|e| { - error!(error=?e, "Failed to send template distribution message to channel manager."); - JDCError::ChannelErrorSender - })?; - } - _ => { - warn!("Received unsupported message type from template provider: {message_type}"); - } - } - Ok(()) - } - - /// Handle messages from channel manager โ†’ template provider. - /// - /// Forwards outbound frames upstream - pub async fn handle_channel_manager_message(&self) -> Result<(), JDCError> { - let msg = self - .template_receiver_channel - .channel_manager_receiver - .recv() - .await?; - debug!("Forwarding message from channel manager to outbound_tx"); - self.template_receiver_channel - .tp_sender - .send(msg) - .await - .map_err(|_| JDCError::ChannelErrorSender)?; - - Ok(()) - } - - /// Build and send [`CoinbaseOutputConstraints`] upstream TP. - pub async fn coinbase_constraints( - &mut self, - coinbase_outputs: Vec, - ) -> Result<(), JDCError> { - debug!( - "Deserializing coinbase outputs ({} bytes)", - coinbase_outputs.len() - ); - let outputs: Vec = bitcoin::consensus::deserialize(&coinbase_outputs)?; - - let max_size: u32 = outputs.iter().map(|o| o.size() as u32).sum(); - debug!( - max_size, - outputs_count = outputs.len(), - "Calculated max coinbase output size" - ); - - 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: outputs, - }; - - let max_sigops = dummy_coinbase.total_sigop_cost(|_| None) as u16; - debug!(max_sigops, "Calculated max sigops for coinbase"); - - let constraints = CoinbaseOutputConstraints { - coinbase_output_max_additional_size: max_size, - coinbase_output_max_additional_sigops: max_sigops, - }; - - let msg = AnyMessage::TemplateDistribution( - TemplateDistribution::CoinbaseOutputConstraints(constraints), - ); - - let frame: StdFrame = msg.try_into()?; - info!("Sending CoinbaseOutputConstraints message upstream"); - self.template_receiver_channel - .tp_sender - .send(frame) - .await - .map_err(|_| { - error!("Failed to send CoinbaseOutputConstraints message upstream"); - JDCError::ChannelErrorSender - })?; - - Ok(()) - } - - // Performs the initial handshake with template provider. - pub async fn setup_connection(&mut self, addr: String) -> Result<(), JDCError> { - let socket: SocketAddr = addr.parse().map_err(|_| { - error!(%addr, "Invalid socket address"); - JDCError::InvalidSocketAddress(addr.clone()) - })?; - - info!(%socket, "Building setup connection message for upstream"); - let setup_msg = get_setup_connection_message_tp(socket); - let frame: StdFrame = Message::Common(setup_msg.into()).try_into()?; - - info!("Sending setup connection message to upstream"); - self.template_receiver_channel - .tp_sender - .send(frame) - .await - .map_err(|_| { - error!("Failed to send setup connection message upstream"); - JDCError::ChannelErrorSender - })?; - - info!("Waiting for upstream handshake response"); - let mut incoming: StdFrame = self - .template_receiver_channel - .tp_receiver - .recv() - .await - .map_err(|e| { - error!(?e, "Upstream connection closed during handshake"); - JDCError::CodecNoise( - stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage, - ) - })?; - - let msg_type = incoming - .get_header() - .ok_or(framing_sv2::Error::ExpectedHandshakeFrame)? - .msg_type(); - debug!(?msg_type, "Received upstream handshake response"); - - self.handle_common_message_frame_from_server(None, msg_type, incoming.payload()) - .await?; - info!("Handshake with upstream completed successfully"); - Ok(()) - } -} diff --git a/roles/jd-client/src/lib/upstream/message_handler.rs b/roles/jd-client/src/lib/upstream/message_handler.rs deleted file mode 100644 index ac7d663fb6..0000000000 --- a/roles/jd-client/src/lib/upstream/message_handler.rs +++ /dev/null @@ -1,50 +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, upstream::Upstream}; - -impl HandleCommonMessagesFromServerAsync for Upstream { - type Error = JDCError; - - async fn handle_setup_connection_success( - &mut self, - _server_id: Option, - msg: SetupConnectionSuccess, - ) -> Result<(), Self::Error> { - info!("Received: {}", msg); - - 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/upstream/mod.rs b/roles/jd-client/src/lib/upstream/mod.rs deleted file mode 100644 index 15d9903c1a..0000000000 --- a/roles/jd-client/src/lib/upstream/mod.rs +++ /dev/null @@ -1,322 +0,0 @@ -//! Upstream module -//! -//! This module defines the [`Upstream`] struct, which manages communication -//! with an upstream SV2 server (e.g., pool). -//! -//! Responsibilities: -//! - Establish a TCP + Noise encrypted connection to upstream -//! - Perform `SetupConnection` handshake -//! - Forward SV2 mining messages between upstream and channel manager -//! - Handle common messages from upstream - -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::{ - error::JDCError, - status::{handle_error, Status, StatusSender}, - task_manager::TaskManager, - utils::{ - get_setup_connection_message, protocol_message_type, spawn_io_tasks, Message, MessageType, - SV2Frame, ShutdownMessage, StdFrame, - }, -}; - -mod message_handler; - -/// Placeholder for future upstream-specific data/state. -pub struct UpstreamData; - -/// Holds channels for communication between upstream and channel manager. -/// -/// - `channel_manager_sender` โ†’ sends frames to channel manager -/// - `channel_manager_receiver` โ†’ receives frames from channel manager -/// - `outbound_tx` โ†’ sends frames outbound to upstream -/// - `inbound_rx` โ†’ receives frames inbound from upstream -#[derive(Clone)] -pub struct UpstreamChannel { - channel_manager_sender: Sender, - channel_manager_receiver: Receiver, - upstream_sender: Sender, - upstream_receiver: Receiver, -} - -/// Represents an upstream connection (e.g., a pool). -#[derive(Clone)] -pub struct Upstream { - #[allow(dead_code)] - /// Internal state - upstream_data: Arc>, - /// Messaging channels to/from the channel manager and Upstream. - upstream_channel: UpstreamChannel, -} - -impl Upstream { - /// Create a new [`Upstream`] connection to the given address. - /// - /// - Establishes TCP + Noise connection - /// - Spawns IO tasks to handle inbound/outbound traffic - pub async fn new( - upstreams: &(SocketAddr, SocketAddr, Secp256k1PublicKey, bool), - channel_manager_sender: Sender, - channel_manager_receiver: Receiver, - notify_shutdown: broadcast::Sender, - task_manager: Arc, - status_sender: Sender, - ) -> Result { - let (addr, _, pubkey, _) = upstreams; - let stream = tokio::time::timeout( - tokio::time::Duration::from_secs(5), - TcpStream::connect(addr), - ) - .await??; - info!("Connected to upstream at {}", addr); - let initiator = Initiator::from_raw_k(pubkey.into_bytes())?; - debug!("Begin with noise setup in upstream connection"); - let (noise_stream_reader, noise_stream_writer) = - NoiseTcpStream::::new(stream, HandshakeRole::Initiator(initiator)) - .await? - .into_split(); - - let status_sender = StatusSender::Upstream(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, - ); - - debug!("Noise setup done in upstream connection"); - let upstream_data = Arc::new(Mutex::new(UpstreamData)); - let upstream_channel = UpstreamChannel { - channel_manager_receiver, - channel_manager_sender, - upstream_sender: outbound_tx, - upstream_receiver: inbound_rx, - }; - Ok(Upstream { - upstream_data, - upstream_channel, - }) - } - - /// Perform `SetupConnection` handshake with upstream. - /// - /// Sends [`SetupConnection`] and awaits response. - pub async fn setup_connection( - &mut self, - min_version: u16, - max_version: u16, - ) -> Result<(), JDCError> { - info!("Upstream: initiating SV2 handshake..."); - let setup_connection = get_setup_connection_message(min_version, max_version)?; - debug!(?setup_connection, "Prepared `SetupConnection` message"); - let sv2_frame: StdFrame = Message::Common(setup_connection.into()).try_into()?; - debug!(?sv2_frame, "Encoded `SetupConnection` frame"); - - // Send SetupConnection - if let Err(e) = self.upstream_channel.upstream_sender.send(sv2_frame).await { - error!(?e, "Failed to send `SetupConnection` frame to upstream"); - return Err(JDCError::CodecNoise( - stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage, - )); - } - info!("Sent `SetupConnection` to upstream, awaiting response..."); - - let incoming_frame = match self.upstream_channel.upstream_receiver.recv().await { - Ok(frame) => { - debug!(?frame, "Received raw inbound frame during handshake"); - frame - } - Err(e) => { - error!(?e, "Upstream closed connection during handshake"); - return Err(JDCError::CodecNoise( - stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage, - )); - } - }; - - let mut incoming: StdFrame = incoming_frame; - debug!(?incoming, "Decoded inbound handshake frame"); - - let message_type = incoming - .get_header() - .ok_or(framing_sv2::Error::ExpectedHandshakeFrame)? - .msg_type(); - - info!(?message_type, "Dispatching inbound handshake message"); - self.handle_common_message_frame_from_server(None, message_type, incoming.payload()) - .await?; - Ok(()) - } - - /// Start unified upstream loop. - /// - /// Responsibilities: - /// - Run `setup_connection` - /// - Handle messages from upstream (pool) and channel manager - /// - React to shutdown signals - /// - /// This function spawns an async task and returns immediately. - pub async fn start( - mut self, - min_version: u16, - max_version: u16, - notify_shutdown: broadcast::Sender, - shutdown_complete_tx: mpsc::Sender<()>, - status_sender: Sender, - task_manager: Arc, - ) { - let status_sender = StatusSender::Upstream(status_sender); - let mut shutdown_rx = notify_shutdown.subscribe(); - - if let Err(e) = self.setup_connection(min_version, max_version).await { - error!(error = ?e, "Upstream: connection setup failed."); - return; - } - - task_manager.spawn(async move { - let mut self_clone_1 = self.clone(); - let mut self_clone_2 = self.clone(); - loop { - tokio::select! { - message = shutdown_rx.recv() => { - match message { - Ok(ShutdownMessage::ShutdownAll) => { - info!("Upstream: received shutdown signal."); - break; - } - Ok(ShutdownMessage::JobDeclaratorShutdownFallback(_)) => { - info!("Upstream: Received Job declarator shutdown."); - break; - } - Ok(ShutdownMessage::UpstreamShutdownFallback(_)) => { - info!("Upstream: Received Upstream shutdown."); - break; - } - Ok(ShutdownMessage::UpstreamShutdown(tx)) => { - info!("Upstream shutdown requested"); - drop(tx); - break; - } - Ok(ShutdownMessage::JobDeclaratorShutdown(tx)) => { - info!("Upstream shutdown requested"); - drop(tx); - break; - } - Err(_) => { - warn!("Upstream: shutdown channel closed unexpectedly."); - break; - } - _ => {} - } - } - res = self_clone_1.handle_pool_message() => { - if let Err(e) = res { - error!(error = ?e, "Upstream: error handling pool message."); - handle_error(&status_sender, e).await; - break; - } - } - res = self_clone_2.handle_channel_manager_message() => { - if let Err(e) = res { - error!(error = ?e, "Upstream: error handling channel manager message."); - handle_error(&status_sender, e).await; - break; - } - } - - } - } - drop(shutdown_complete_tx); - warn!("Upstream: unified message loop exited."); - }); - } - - // Handle incoming frames from upstream (pool). - // - // Routes: - // - `Common` messages โ†’ handled locally - // - `Mining` messages โ†’ forwarded to channel manager - // - Unsupported โ†’ error - async fn handle_pool_message(&mut self) -> Result<(), JDCError> { - let mut sv2_frame = self.upstream_channel.upstream_receiver.recv().await?; - - debug!("Received SV2 frame from upstream."); - 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::Mining => { - self.upstream_channel - .channel_manager_sender - .send(sv2_frame) - .await - .map_err(|e| { - error!(error=?e, "Failed to send mining message to channel manager."); - JDCError::ChannelErrorSender - })?; - } - _ => { - warn!("Received unsupported message type from upstream: {message_type}"); - } - } - Ok(()) - } - - // Handle outbound frames from channel manager โ†’ upstream. - // - // Forwards messages upstream. - async fn handle_channel_manager_message(&mut self) -> Result<(), JDCError> { - match self.upstream_channel.channel_manager_receiver.recv().await { - Ok(msg) => { - debug!("Received message from channel manager, forwarding upstream."); - self.upstream_channel - .upstream_sender - .send(msg) - .await - .map_err(|e| { - error!(error=?e, "Failed to send outbound message to upstream."); - JDCError::CodecNoise( - stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage, - ) - })?; - } - Err(e) => { - warn!(error=?e, "Channel manager receiver closed or errored."); - } - } - Ok(()) - } -} diff --git a/roles/jd-client/src/lib/utils.rs b/roles/jd-client/src/lib/utils.rs deleted file mode 100644 index 6d19a894c2..0000000000 --- a/roles/jd-client/src/lib/utils.rs +++ /dev/null @@ -1,585 +0,0 @@ -//! Utilities for managing JDC communication, connection setup, -//! shutdown signaling, and upstream state tracking. -//! -//! This module provides: -//! - Construction of `SetupConnection` messages for mining, job declarator, and template -//! distribution protocols. -//! - Helpers for parsing frames into typed Stratum messages. -//! - An async I/O task spawner for handling framed network communication with shutdown -//! coordination. -//! - Deserialization of coinbase transaction outputs. -//! - Shutdown signaling types for orchestrating controlled shutdown of upstream, downstream, and -//! job declarator components. -//! - An atomic wrapper for managing the upstream connection state safely across threads. -use std::{ - net::SocketAddr, - sync::{ - atomic::{AtomicU8, Ordering}, - Arc, - }, -}; - -use async_channel::{Receiver, Sender}; -use stratum_apps::{ - network_helpers::noise_stream::{NoiseTcpReadHalf, NoiseTcpWriteHalf}, - stratum_core::{ - binary_sv2::Str0255, - buffer_sv2, - codec_sv2::{StandardEitherFrame, StandardSv2Frame}, - common_messages_sv2::{ - Protocol, SetupConnection, MESSAGE_TYPE_CHANNEL_ENDPOINT_CHANGED, - MESSAGE_TYPE_RECONNECT, MESSAGE_TYPE_SETUP_CONNECTION, - MESSAGE_TYPE_SETUP_CONNECTION_ERROR, MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS, - }, - framing_sv2::framing::{Frame, Sv2Frame}, - job_declaration_sv2::{ - MESSAGE_TYPE_ALLOCATE_MINING_JOB_TOKEN, MESSAGE_TYPE_ALLOCATE_MINING_JOB_TOKEN_SUCCESS, - MESSAGE_TYPE_DECLARE_MINING_JOB, MESSAGE_TYPE_DECLARE_MINING_JOB_ERROR, - MESSAGE_TYPE_DECLARE_MINING_JOB_SUCCESS, MESSAGE_TYPE_PROVIDE_MISSING_TRANSACTIONS, - MESSAGE_TYPE_PROVIDE_MISSING_TRANSACTIONS_SUCCESS, MESSAGE_TYPE_PUSH_SOLUTION, - }, - mining_sv2::{ - CloseChannel, OpenExtendedMiningChannel, OpenStandardMiningChannel, - MESSAGE_TYPE_CLOSE_CHANNEL, MESSAGE_TYPE_MINING_SET_NEW_PREV_HASH, - MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB, MESSAGE_TYPE_NEW_MINING_JOB, - MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL, - MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL_SUCCESS, - MESSAGE_TYPE_OPEN_MINING_CHANNEL_ERROR, MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL, - MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL_SUCCESS, MESSAGE_TYPE_SET_CUSTOM_MINING_JOB, - MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_ERROR, MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_SUCCESS, - MESSAGE_TYPE_SET_EXTRANONCE_PREFIX, MESSAGE_TYPE_SET_GROUP_CHANNEL, - MESSAGE_TYPE_SET_TARGET, MESSAGE_TYPE_SUBMIT_SHARES_ERROR, - MESSAGE_TYPE_SUBMIT_SHARES_EXTENDED, MESSAGE_TYPE_SUBMIT_SHARES_STANDARD, - MESSAGE_TYPE_SUBMIT_SHARES_SUCCESS, MESSAGE_TYPE_UPDATE_CHANNEL, - MESSAGE_TYPE_UPDATE_CHANNEL_ERROR, - }, - parsers_sv2::{AnyMessage, Mining}, - template_distribution_sv2::{ - MESSAGE_TYPE_COINBASE_OUTPUT_CONSTRAINTS, MESSAGE_TYPE_NEW_TEMPLATE, - MESSAGE_TYPE_REQUEST_TRANSACTION_DATA, MESSAGE_TYPE_REQUEST_TRANSACTION_DATA_ERROR, - MESSAGE_TYPE_REQUEST_TRANSACTION_DATA_SUCCESS, MESSAGE_TYPE_SET_NEW_PREV_HASH, - MESSAGE_TYPE_SUBMIT_SOLUTION, - }, - }, -}; -use tokio::sync::broadcast; -use tracing::{error, trace, warn, Instrument}; - -use crate::{ - config::ConfigJDCMode, - error::JDCError, - status::{StatusSender, StatusType}, - task_manager::TaskManager, -}; - -pub type Message = AnyMessage<'static>; -pub type StdFrame = StandardSv2Frame; -pub type EitherFrame = StandardEitherFrame; -pub type SV2Frame = Sv2Frame; -/// Represents a message that can trigger shutdown of various system components. -#[derive(Debug, Clone)] -pub enum ShutdownMessage { - /// Shutdown all components immediately - ShutdownAll, - /// Shutdown all downstream connections - DownstreamShutdownAll, - /// Shutdown a specific downstream connection by ID - DownstreamShutdown(u32), - /// Shutdown Upstream and JD part of JDC during fallback - JobDeclaratorShutdownFallback((Vec, tokio::sync::mpsc::Sender<()>)), - /// Shutdown Upstream and JD part during fallback - UpstreamShutdownFallback((Vec, tokio::sync::mpsc::Sender<()>)), - /// Shutdown Job Declarator during initialization. - JobDeclaratorShutdown(tokio::sync::mpsc::Sender<()>), - /// Shutdown Job Declarator during initialization. - UpstreamShutdown(tokio::sync::mpsc::Sender<()>), -} - -/// Constructs a `SetupConnection` message for the mining protocol. -pub fn get_setup_connection_message( - min_version: u16, - max_version: u16, -) -> Result, JDCError> { - let endpoint_host = "0.0.0.0".to_string().into_bytes().try_into()?; - let vendor = String::new().try_into()?; - let hardware_version = String::new().try_into()?; - let firmware = String::new().try_into()?; - let device_id = String::new().try_into()?; - let flags = 0b0000_0000_0000_0000_0000_0000_0000_0110; - Ok(SetupConnection { - protocol: Protocol::MiningProtocol, - min_version, - max_version, - flags, - endpoint_host, - endpoint_port: 50, - vendor, - hardware_version, - firmware, - device_id, - }) -} - -/// Constructs a `SetupConnection` message for the Job Declarator (JDS). -pub fn get_setup_connection_message_jds( - proxy_address: &SocketAddr, - mode: &ConfigJDCMode, -) -> SetupConnection<'static> { - let endpoint_host = proxy_address - .ip() - .to_string() - .into_bytes() - .try_into() - .unwrap(); - let vendor = String::new().try_into().unwrap(); - let hardware_version = String::new().try_into().unwrap(); - let firmware = String::new().try_into().unwrap(); - let device_id = String::new().try_into().unwrap(); - let mut setup_connection = SetupConnection { - protocol: Protocol::JobDeclarationProtocol, - min_version: 2, - max_version: 2, - flags: 0b0000_0000_0000_0000_0000_0000_0000_0000, - endpoint_host, - endpoint_port: proxy_address.port(), - vendor, - hardware_version, - firmware, - device_id, - }; - - if matches!(mode, ConfigJDCMode::FullTemplate) { - setup_connection.allow_full_template_mode(); - } - - setup_connection -} - -/// Constructs a `SetupConnection` message for the Template Provider (TP). -pub fn get_setup_connection_message_tp(address: SocketAddr) -> SetupConnection<'static> { - let endpoint_host = address.ip().to_string().into_bytes().try_into().unwrap(); - let vendor = String::new().try_into().unwrap(); - let hardware_version = String::new().try_into().unwrap(); - let firmware = String::new().try_into().unwrap(); - let device_id = String::new().try_into().unwrap(); - SetupConnection { - protocol: Protocol::TemplateDistributionProtocol, - min_version: 2, - max_version: 2, - flags: 0b0000_0000_0000_0000_0000_0000_0000_0000, - endpoint_host, - endpoint_port: address.port(), - vendor, - hardware_version, - firmware, - device_id, - } -} - -/// Spawns async reader and writer tasks for handling framed I/O with shutdown support. -#[track_caller] -#[allow(clippy::too_many_arguments)] -pub fn spawn_io_tasks( - task_manager: Arc, - mut reader: NoiseTcpReadHalf, - mut writer: NoiseTcpWriteHalf, - outbound_rx: Receiver, - inbound_tx: Sender, - notify_shutdown: broadcast::Sender, - status_sender: StatusSender, -) { - let caller = std::panic::Location::caller(); - let inbound_tx_clone = inbound_tx.clone(); - let outbound_rx_clone = outbound_rx.clone(); - { - let mut shutdown_rx = notify_shutdown.subscribe(); - let status_sender = status_sender.clone(); - let status_type: StatusType = StatusType::from(&status_sender); - - task_manager.spawn(async move { - trace!("Reader task started"); - loop { - tokio::select! { - message = shutdown_rx.recv() => { - match message { - Ok(ShutdownMessage::ShutdownAll) => { - trace!("Received global shutdown"); - inbound_tx.close(); - break; - } - Ok(ShutdownMessage::DownstreamShutdown(down_id)) if matches!(status_type, StatusType::Downstream(id) if id == down_id) => { - trace!(down_id, "Received downstream shutdown"); - if status_type != StatusType::TemplateReceiver { - inbound_tx.close(); - break; - } - } - Ok(ShutdownMessage::JobDeclaratorShutdownFallback(_)) if !matches!(status_type, StatusType::TemplateReceiver) => { - trace!("Received job declarator shutdown"); - if status_type != StatusType::TemplateReceiver { - inbound_tx.close(); - break; - } - } - Ok(ShutdownMessage::UpstreamShutdownFallback(_)) if !matches!(status_type, StatusType::TemplateReceiver) => { - trace!("Received upstream shutdown"); - if status_type != StatusType::TemplateReceiver { - inbound_tx.close(); - break; - } - } - - Ok(ShutdownMessage::UpstreamShutdown(tx)) if !matches!(status_type, StatusType::TemplateReceiver) => { - trace!("Received upstream shutdown"); - if status_type != StatusType::TemplateReceiver { - inbound_tx.close(); - break; - } - drop(tx); - } - Ok(ShutdownMessage::JobDeclaratorShutdown(tx)) if !matches!(status_type, StatusType::TemplateReceiver) => { - trace!("Received upstream shutdown"); - if status_type != StatusType::TemplateReceiver { - inbound_tx.close(); - break; - } - drop(tx); - } - _ => {} - } - } - res = reader.read_frame() => { - match res { - Ok(frame) => { - match frame { - Frame::HandShake(frame) => { - error!(?frame, "Received handshake frame"); - drop(frame); - break; - }, - Frame::Sv2(sv2_frame) => { - trace!("Received inbound frame"); - if let Err(e) = inbound_tx.send(sv2_frame).await { - inbound_tx.close(); - error!(error=?e, "Failed to forward inbound frame"); - break; - } - }, - } - } - Err(e) => { - error!(error=?e, "Reader error"); - inbound_tx.close(); - break; - } - } - } - } - } - inbound_tx.close(); - outbound_rx_clone.close(); - drop(inbound_tx); - drop(outbound_rx_clone); - warn!("Reader task exited."); - }.instrument(tracing::trace_span!( - "reader_task", - spawned_at = %format!("{}:{}", caller.file(), caller.line()) - ))); - } - - { - let mut shutdown_rx = notify_shutdown.subscribe(); - let status_type: StatusType = StatusType::from(&status_sender); - - task_manager.spawn(async move { - trace!("Writer task started"); - loop { - tokio::select! { - message = shutdown_rx.recv() => { - match message { - Ok(ShutdownMessage::ShutdownAll) => { - trace!("Received global shutdown"); - outbound_rx.close(); - break; - } - Ok(ShutdownMessage::DownstreamShutdown(down_id)) if matches!(status_type, StatusType::Downstream(id) if id == down_id) => { - trace!(down_id, "Received downstream shutdown"); - if status_type != StatusType::TemplateReceiver { - outbound_rx.close(); - break; - } - } - Ok(ShutdownMessage::JobDeclaratorShutdownFallback(_)) if !matches!(status_type, StatusType::TemplateReceiver) => { - trace!("Received job declarator shutdown"); - if status_type != StatusType::TemplateReceiver { - outbound_rx.close(); - break; - } - } - Ok(ShutdownMessage::UpstreamShutdownFallback(_)) if !matches!(status_type, StatusType::TemplateReceiver) => { - trace!("Received upstream shutdown"); - if status_type != StatusType::TemplateReceiver { - outbound_rx.close(); - break; - } - } - Ok(ShutdownMessage::UpstreamShutdown(tx)) if !matches!(status_type, StatusType::TemplateReceiver) => { - trace!("Received upstream shutdown"); - if status_type != StatusType::TemplateReceiver { - outbound_rx.close(); - break; - } - drop(tx); - } - Ok(ShutdownMessage::JobDeclaratorShutdown(tx)) if !matches!(status_type, StatusType::TemplateReceiver) => { - trace!("Received upstream shutdown"); - if status_type != StatusType::TemplateReceiver { - outbound_rx.close(); - break; - } - drop(tx); - } - _ => {} - } - } - res = outbound_rx.recv() => { - match res { - Ok(frame) => { - trace!("Sending outbound frame"); - if let Err(e) = writer.write_frame(frame.into()).await { - error!(error=?e, "Writer error"); - outbound_rx.close(); - break; - } - } - Err(_) => { - outbound_rx.close(); - warn!("Outbound channel closed"); - break; - } - } - } - } - } - outbound_rx.close(); - inbound_tx_clone.close(); - drop(outbound_rx); - drop(inbound_tx_clone); - warn!("Writer task exited."); - }.instrument(tracing::trace_span!( - "writer_task", - spawned_at = %format!("{}:{}", caller.file(), caller.line()) - ))); - } -} - -/// Represents the state of the upstream connection. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum UpstreamState { - /// No channel established with upstream. - NoChannel = 0, - /// Channel is being established undergoing. - Pending = 1, - /// Channel is active and connected. - Connected = 2, - /// Running in solo mining mode. - SoloMining = 3, -} - -/// Atomic wrapper for managing upstream connection state safely across threads. -#[derive(Clone)] -pub struct AtomicUpstreamState { - inner: Arc, -} - -impl AtomicUpstreamState { - /// Creates a new atomic upstream state. - pub fn new(state: UpstreamState) -> Self { - Self { - inner: Arc::new(AtomicU8::new(state as u8)), - } - } - - /// Returns the current upstream state. - pub fn get(&self) -> UpstreamState { - match self.inner.load(Ordering::SeqCst) { - 0 => UpstreamState::NoChannel, - 1 => UpstreamState::Pending, - 2 => UpstreamState::Connected, - 3 => UpstreamState::SoloMining, - _ => unreachable!("invalid upstream state"), - } - } - - /// Updates the upstream state - pub fn set(&self, state: UpstreamState) { - self.inner.store(state as u8, Ordering::SeqCst); - } - - /// Conditionally updates the upstream state if the current value matches. - pub fn compare_and_set( - &self, - current: UpstreamState, - new: UpstreamState, - ) -> Result<(), UpstreamState> { - self.inner - .compare_exchange(current as u8, new as u8, Ordering::SeqCst, Ordering::SeqCst) - .map(|_| ()) - .map_err(|v| match v { - 0 => UpstreamState::NoChannel, - 1 => UpstreamState::Pending, - 2 => UpstreamState::Connected, - 3 => UpstreamState::SoloMining, - _ => unreachable!("invalid upstream state"), - }) - } -} - -/// Represents a pending channel request during the bootstrap phase -/// of the Job Declarator Client (JDC). -/// -/// These requests are created by downstreams that want to open -/// a mining channel but cannot proceed immediately. -/// They remain queued until an upstream channel is successfully opened, -/// at which point they can be processed. -/// -/// Two types of requests can be pending: -/// - [`OpenExtendedMiningChannel`] for extended mining channels -/// - [`OpenStandardMiningChannel`] for standard mining channels -pub enum PendingChannelRequest { - /// A request to open an extended mining channel. - ExtendedChannel(OpenExtendedMiningChannel<'static>), - /// A request to open a standard mining channel. - StandardChannel(OpenStandardMiningChannel<'static>), -} - -impl From> for PendingChannelRequest { - fn from(value: OpenExtendedMiningChannel<'static>) -> Self { - PendingChannelRequest::ExtendedChannel(value) - } -} - -impl From> for PendingChannelRequest { - fn from(value: OpenStandardMiningChannel<'static>) -> Self { - PendingChannelRequest::StandardChannel(value) - } -} - -impl From for Mining<'_> { - fn from(value: PendingChannelRequest) -> Self { - match value { - PendingChannelRequest::ExtendedChannel(m) => Mining::OpenExtendedMiningChannel(m), - PendingChannelRequest::StandardChannel(m) => Mining::OpenStandardMiningChannel(m), - } - } -} - -impl PendingChannelRequest { - pub fn message_type(&self) -> u8 { - match self { - PendingChannelRequest::ExtendedChannel(_) => MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL, - PendingChannelRequest::StandardChannel(_) => MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL, - } - } -} - -/// Creates a [`CloseChannel`] message for the given channel ID and reason. -/// -/// The `msg` is converted into a [`Str0255`] reason code. -/// If conversion fails, this function will panic. -pub(crate) fn create_close_channel_msg(channel_id: u32, msg: &str) -> CloseChannel<'_> { - CloseChannel { - channel_id, - reason_code: Str0255::try_from(msg.to_string()).expect("Could not convert message."), - } -} - -pub fn is_common_message(message_type: u8) -> bool { - matches!( - message_type, - MESSAGE_TYPE_SETUP_CONNECTION - | MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS - | MESSAGE_TYPE_SETUP_CONNECTION_ERROR - | MESSAGE_TYPE_CHANNEL_ENDPOINT_CHANGED - | MESSAGE_TYPE_RECONNECT - ) -} - -pub fn is_mining_message(message_type: u8) -> bool { - matches!( - message_type, - MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL - | MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL_SUCCESS - | MESSAGE_TYPE_OPEN_MINING_CHANNEL_ERROR - | MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL - | MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL_SUCCESS - | MESSAGE_TYPE_NEW_MINING_JOB - | MESSAGE_TYPE_UPDATE_CHANNEL - | MESSAGE_TYPE_UPDATE_CHANNEL_ERROR - | MESSAGE_TYPE_CLOSE_CHANNEL - | MESSAGE_TYPE_SET_EXTRANONCE_PREFIX - | MESSAGE_TYPE_SUBMIT_SHARES_STANDARD - | MESSAGE_TYPE_SUBMIT_SHARES_EXTENDED - | MESSAGE_TYPE_SUBMIT_SHARES_SUCCESS - | MESSAGE_TYPE_SUBMIT_SHARES_ERROR - // | MESSAGE_TYPE_RESERVED - | 0x1e - | MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB - | MESSAGE_TYPE_MINING_SET_NEW_PREV_HASH - | MESSAGE_TYPE_SET_TARGET - | MESSAGE_TYPE_SET_CUSTOM_MINING_JOB - | MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_SUCCESS - | MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_ERROR - | MESSAGE_TYPE_SET_GROUP_CHANNEL - ) -} - -pub fn is_job_declaration_message(message_type: u8) -> bool { - matches!( - message_type, - MESSAGE_TYPE_ALLOCATE_MINING_JOB_TOKEN - | MESSAGE_TYPE_ALLOCATE_MINING_JOB_TOKEN_SUCCESS - | MESSAGE_TYPE_PROVIDE_MISSING_TRANSACTIONS - | MESSAGE_TYPE_PROVIDE_MISSING_TRANSACTIONS_SUCCESS - | MESSAGE_TYPE_DECLARE_MINING_JOB - | MESSAGE_TYPE_DECLARE_MINING_JOB_SUCCESS - | MESSAGE_TYPE_DECLARE_MINING_JOB_ERROR - | MESSAGE_TYPE_PUSH_SOLUTION - ) -} - -pub fn is_template_distribution_message(message_type: u8) -> bool { - matches!( - message_type, - MESSAGE_TYPE_COINBASE_OUTPUT_CONSTRAINTS - | MESSAGE_TYPE_NEW_TEMPLATE - | MESSAGE_TYPE_SET_NEW_PREV_HASH - | MESSAGE_TYPE_REQUEST_TRANSACTION_DATA - | MESSAGE_TYPE_REQUEST_TRANSACTION_DATA_SUCCESS - | MESSAGE_TYPE_REQUEST_TRANSACTION_DATA_ERROR - | MESSAGE_TYPE_SUBMIT_SOLUTION - ) -} - -#[derive(Debug, PartialEq, Eq)] -pub enum MessageType { - Common, - Mining, - JobDeclaration, - TemplateDistribution, - Unknown, -} - -pub fn protocol_message_type(message_type: u8) -> MessageType { - if is_common_message(message_type) { - MessageType::Common - } else if is_mining_message(message_type) { - MessageType::Mining - } else if is_job_declaration_message(message_type) { - MessageType::JobDeclaration - } else if is_template_distribution_message(message_type) { - MessageType::TemplateDistribution - } else { - MessageType::Unknown - } -} diff --git a/roles/jd-client/src/main.rs b/roles/jd-client/src/main.rs deleted file mode 100644 index 9b6047d8f9..0000000000 --- a/roles/jd-client/src/main.rs +++ /dev/null @@ -1,17 +0,0 @@ -use jd_client_sv2::JobDeclaratorClient; -use stratum_apps::config_helpers::logging::init_logging; - -use crate::args::process_cli_args; - -mod args; - -#[tokio::main] -async fn main() { - let jdc_config = process_cli_args().unwrap_or_else(|e| { - eprintln!("Job Declarator Client config error: {e}"); - std::process::exit(1); - }); - - init_logging(jdc_config.log_file()); - JobDeclaratorClient::new(jdc_config).start().await; -} diff --git a/roles/jd-server/Cargo.toml b/roles/jd-server/Cargo.toml deleted file mode 100644 index 19f9568431..0000000000 --- a/roles/jd-server/Cargo.toml +++ /dev/null @@ -1,44 +0,0 @@ -[package] -name = "jd_server" -version = "0.1.3" -authors = ["The Stratum V2 Developers"] -edition = "2021" -description = "Job Declarator Server (JDS) role" -documentation = "https://github.com/stratum-mining/stratum" -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_server" -path = "src/lib/mod.rs" - -[dependencies] -stratum-apps = { path = "../stratum-apps", features = ["jd_server"] } -roles_logic_sv2 = "5.0.0" -binary_sv2 = "4.0.0" -error_handling = "1.0.0" -codec_sv2 = "3.0.1" -framing_sv2 = "5.0.1" -mining_sv2 = "5.0.1" -noise_sv2 = "1.4.0" -parsers_sv2 = "0.1.1" -job_declaration_sv2 = "5.0.1" -common_messages_sv2 = "6.0.1" -network_helpers_sv2 = "4.0.1" -rpc_sv2 = "1.1.1" -bitcoin = "0.32.5" -async-channel = "1.5.1" -rand = "0.8.4" -tokio = { version = "1.44.1", features = ["full"] } -ext-config = { version = "0.14.0", features = ["toml"], package = "config" } -tracing = { version = "0.1" } -nohash-hasher = "0.2.0" -serde_json = { version = "1.0", default-features = false, features = ["alloc","raw_value"] } -serde = { version = "1.0.89", features = ["derive", "alloc"], default-features = false } -hashbrown = { version = "0.11", default-features = false, features = ["ahash", "serde"] } -hex = "0.4.3" -clap = { version = "4.5.39", features = ["derive"] } diff --git a/roles/jd-server/config-examples/jds-config-hosted-example.toml b/roles/jd-server/config-examples/jds-config-hosted-example.toml deleted file mode 100644 index 4f3b4039e6..0000000000 --- a/roles/jd-server/config-examples/jds-config-hosted-example.toml +++ /dev/null @@ -1,30 +0,0 @@ -# If set to true, JDS require JDC to reveal the transactions they are going to mine on -full_template_mode_required = true - -# SRI Pool config -authority_public_key = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" -authority_secret_key = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n" -cert_validity_sec = 3600 - -# 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-server.log" - -# SRI Pool JD config -listen_jd_address = "0.0.0.0:34264" -# RPC config for mempool (it can be also the same TP if correctly configured) -core_rpc_url = "http://75.119.150.111" -core_rpc_port = 48332 -core_rpc_user = "username" -core_rpc_pass = "password" -# Time interval used for JDS mempool update -[mempool_update_interval] -unit = "secs" -value = 1 diff --git a/roles/jd-server/config-examples/jds-config-local-example.toml b/roles/jd-server/config-examples/jds-config-local-example.toml deleted file mode 100644 index f26adfbf48..0000000000 --- a/roles/jd-server/config-examples/jds-config-local-example.toml +++ /dev/null @@ -1,30 +0,0 @@ -# If set to true, JDS require JDC to reveal the transactions they are going to mine on -full_template_mode_required = true - -# SRI Pool config -authority_public_key = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" -authority_secret_key = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n" -cert_validity_sec = 3600 - -# 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-server.log" - -# SRI Pool JD config -listen_jd_address = "127.0.0.1:34264" -# RPC config for mempool (it can be also the same TP if correctly configured) -core_rpc_url = "http://127.0.0.1" -core_rpc_port = 48332 -core_rpc_user = "username" -core_rpc_pass = "password" -# Time interval used for JDS mempool update -[mempool_update_interval] -unit = "secs" -value = 1 diff --git a/roles/jd-server/src/args.rs b/roles/jd-server/src/args.rs deleted file mode 100644 index e0e0d26400..0000000000 --- a/roles/jd-server/src/args.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::path::PathBuf; - -use clap::Parser; -use ext_config::{Config, File, FileFormat}; -use jd_server::{ - config::JobDeclaratorServerConfig, - error::JdsError, - // error::{Error, ProxyResult}, -}; - -use tracing::error; - -/// CLI argument parser for the JDS binary. -/// -/// Supports the following flags: -/// - `-c`, `--config`: specify a custom config file path -/// - `-h`, `--help`: print help and usage info -#[derive(Parser, Debug)] -#[command(author, version, about = "Job Declarator Server (JDS)", long_about = None)] -pub struct Args { - #[arg( - short = 'c', - long = "config", - help = "Path to the TOML configuration file", - default_value = "jds-config.toml" - )] - pub config_path: std::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, -} - -/// Process CLI args and load configuration. -#[allow(clippy::result_large_err)] -pub fn process_cli_args() -> Result { - // Parse CLI arguments - let args = Args::parse(); - - // Build configuration from the provided file path - let config_path = args.config_path.to_str().ok_or_else(|| { - error!("Invalid configuration path."); - JdsError::BadCliArgs - })?; - - let settings = Config::builder() - .add_source(File::new(config_path, FileFormat::Toml)) - .build() - .map_err(|e| { - error!("Failed to build config: {}", e); - JdsError::BadCliArgs - })?; - - // Deserialize settings into JobDeclaratorServerConfig - let mut config = settings - .try_deserialize::() - .map_err(|e| { - error!("Failed to deserialize config: {}", e); - JdsError::BadCliArgs - })?; - - config.set_log_file(args.log_file); - - Ok(config) -} diff --git a/roles/jd-server/src/lib/config.rs b/roles/jd-server/src/lib/config.rs deleted file mode 100644 index ee01a046ec..0000000000 --- a/roles/jd-server/src/lib/config.rs +++ /dev/null @@ -1,296 +0,0 @@ -//! ## Configuration Module -//! -//! Defines [`JobDeclaratorServerConfig`], the configuration structure for the Job Declarator Server -//! (JDS). -//! -//! This module handles: -//! - Parsing TOML files via `serde` -//! - Accessing Bitcoin Core RPC parameters -//! - Managing cryptographic keys for Noise authentication -//! - Setting networking and coinbase logic -//! -//! Also defines a helper struct [`CoreRpc`] to group RPC parameters. - -use serde::Deserialize; -use std::{ - path::{Path, PathBuf}, - time::Duration, -}; -use stratum_apps::{ - config_helpers::CoinbaseRewardScript, - key_utils::{Secp256k1PublicKey, Secp256k1SecretKey}, -}; - -#[derive(Debug, serde::Deserialize, Clone)] -pub struct JobDeclaratorServerConfig { - #[serde(default = "default_true")] - full_template_mode_required: bool, - listen_jd_address: String, - authority_public_key: Secp256k1PublicKey, - authority_secret_key: Secp256k1SecretKey, - cert_validity_sec: u64, - coinbase_reward_script: CoinbaseRewardScript, - core_rpc_url: String, - core_rpc_port: u16, - core_rpc_user: String, - core_rpc_pass: String, - #[serde(deserialize_with = "stratum_apps::config_helpers::duration_from_toml")] - mempool_update_interval: Duration, - log_file: Option, -} - -impl JobDeclaratorServerConfig { - /// Creates a new instance of [`JobDeclaratorServerConfig`]. - /// - /// # Panics - /// - /// Panics if `coinbase_reward_scripts` is empty. - pub fn new( - listen_jd_address: String, - authority_public_key: Secp256k1PublicKey, - authority_secret_key: Secp256k1SecretKey, - cert_validity_sec: u64, - coinbase_reward_script: CoinbaseRewardScript, - core_rpc: CoreRpc, - mempool_update_interval: Duration, - ) -> Self { - Self { - full_template_mode_required: true, - listen_jd_address, - authority_public_key, - authority_secret_key, - cert_validity_sec, - coinbase_reward_script, - core_rpc_url: core_rpc.url, - core_rpc_port: core_rpc.port, - core_rpc_user: core_rpc.user, - core_rpc_pass: core_rpc.pass, - mempool_update_interval, - log_file: None, - } - } - - /// Returns the listening address of the Job Declarator Server. - pub fn listen_jd_address(&self) -> &str { - &self.listen_jd_address - } - - /// Returns the public key of the authority. - pub fn authority_public_key(&self) -> &Secp256k1PublicKey { - &self.authority_public_key - } - - /// Returns the secret key of the authority. - pub fn authority_secret_key(&self) -> &Secp256k1SecretKey { - &self.authority_secret_key - } - - /// Returns the URL of the core RPC. - pub fn core_rpc_url(&self) -> &str { - &self.core_rpc_url - } - - /// Returns the port of the core RPC. - pub fn core_rpc_port(&self) -> u16 { - self.core_rpc_port - } - - /// Returns the user of the core RPC. - pub fn core_rpc_user(&self) -> &str { - &self.core_rpc_user - } - - /// Returns the password of the core RPC. - pub fn core_rpc_pass(&self) -> &str { - &self.core_rpc_pass - } - - /// Returns the coinbase outputs. - pub fn coinbase_reward_scripts(&self) -> &CoinbaseRewardScript { - &self.coinbase_reward_script - } - - /// Returns the certificate validity in seconds. - pub fn cert_validity_sec(&self) -> u64 { - self.cert_validity_sec - } - - /// Returns whether [`Full Template`] is required. Otherwise, [`Coinbase Only`] mode will be - /// used. - /// - /// [`Full Template`]: https://github.com/stratum-mining/sv2-spec/blob/main/06-Job-Declaration-Protocol.md#632-full-template-mode - /// [`Coinbase Only`]: https://github.com/stratum-mining/sv2-spec/blob/main/06-Job-Declaration-Protocol.md#631-coinbase-only-mode - pub fn full_template_mode_required(&self) -> bool { - self.full_template_mode_required - } - - /// Returns the mempool update interval. - pub fn mempool_update_interval(&self) -> Duration { - self.mempool_update_interval - } - - /// Sets the listening address of Bitcoin core RPC. - pub fn set_core_rpc_url(&mut self, url: String) { - self.core_rpc_url = url; - } - - /// Sets coinbase outputs. - pub fn set_coinbase_reward_scripts(&mut self, output: CoinbaseRewardScript) { - self.coinbase_reward_script = output; - } - - 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(path) = log_file { - self.log_file = Some(path); - } - } -} - -fn default_true() -> bool { - true -} - -#[derive(Debug, Deserialize, Clone)] -pub struct CoreRpc { - url: String, - port: u16, - user: String, - pass: String, -} - -impl CoreRpc { - pub fn new(url: String, port: u16, user: String, pass: String) -> Self { - Self { - url, - port, - user, - pass, - } - } -} - -#[cfg(test)] -mod tests { - use super::super::JobDeclaratorServer; - use ext_config::{Config, ConfigError, File, FileFormat}; - use std::path::PathBuf; - use stratum_apps::stratum_core::bitcoin::{self, Amount, ScriptBuf, TxOut}; - - use crate::config::JobDeclaratorServerConfig; - - const COINBASE_CONFIG_TEMPLATE: &'static str = r#" - full_template_mode_required = true - authority_public_key = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" - authority_secret_key = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n" - cert_validity_sec = 3600 - - coinbase_reward_script = %COINBASE_REWARD_SCRIPT% - - listen_jd_address = "127.0.0.1:34264" - core_rpc_url = "http://127.0.0.1" - core_rpc_port = 48332 - core_rpc_user = "username" - core_rpc_pass = "password" - [mempool_update_interval] - unit = "secs" - value = 1 - "#; - const TEST_PK_HEX: &'static str = - "036adc3bdf21e6f9a0f0fb0066bf517e5b7909ed1563d6958a10993849a7554075"; - const TEST_INVALID_PK_HEX: &'static str = - "036adc3bdf21e6f9a0f0fb0066bf517e5b7909ed1563d6958a10993849a7ffffff"; - - fn load_config(path: &str) -> JobDeclaratorServerConfig { - let config_path = PathBuf::from(path); - assert!( - config_path.exists(), - "No config file found at {:?}", - config_path - ); - - let config_path = config_path.to_str().unwrap(); - - let settings = Config::builder() - .add_source(File::new(config_path, FileFormat::Toml)) - .build() - .expect("Failed to build config"); - - settings.try_deserialize().expect("Failed to parse config") - } - - fn load_coinbase_config_str(path: &str) -> Result { - let s = COINBASE_CONFIG_TEMPLATE.replace("%COINBASE_REWARD_SCRIPT%", path); - let settings = Config::builder() - .add_source(File::from_str(&s, FileFormat::Toml)) - .build() - .expect("Failed to build config"); - - settings.try_deserialize() - } - - #[tokio::test] - async fn test_offline_rpc_url() { - let mut config = load_config("config-examples/jds-config-hosted-example.toml"); - config.set_core_rpc_url("http://127.0.0.1".to_string()); - let jd = JobDeclaratorServer::new(config); - assert!(jd.start().await.is_err()); - } - - #[test] - fn test_get_non_empty_coinbase_reward_script() { - let pk = TEST_PK_HEX - .parse::() - .expect("Failed to parse public key"); - let config = - load_coinbase_config_str(&format!("\"wpkh({pk})\"")).expect("Failed to parse config"); - - let output = TxOut { - value: Amount::from_sat(0), - script_pubkey: config.coinbase_reward_scripts().script_pubkey(), - }; - let expected_script = ScriptBuf::from_hex(&format!( - "0014{}", - pk.wpubkey_hash().expect("compressed key") - )) - .expect("hex"); - let expected_transaction_output = TxOut { - value: Amount::from_sat(0), - script_pubkey: expected_script, - }; - - assert_eq!(output, expected_transaction_output); - } - - #[test] - fn test_get_coinbase_reward_script_empty() { - let error = - load_coinbase_config_str("\"\"").expect_err("cannot parse config with empty txout"); - assert_eq!( - error.to_string(), - "Miniscript: unexpected ยซ(0 args) while parsing Miniscriptยป", - ); - } - - #[test] - fn test_get_invalid_miniscript_in_coinbase_reward_script() { - let error = load_coinbase_config_str(&format!("\"INVALID\"")) - .expect_err("Cannot parse config with bad miniscript"); - assert_eq!( - error.to_string(), - "Miniscript: unexpected ยซINVALID(0 args) while parsing Miniscriptยป", - ); - } - - #[test] - fn test_get_invalid_value_in_coinbase_reward_script() { - let error = load_coinbase_config_str(&format!("\"wpkh({TEST_INVALID_PK_HEX})\"")) - .expect_err("Cannot parse config with bad pubkeys"); - assert_eq!( - error.to_string(), - "Miniscript: unexpected ยซError while parsing simple public keyยป", - ); - } -} diff --git a/roles/jd-server/src/lib/error.rs b/roles/jd-server/src/lib/error.rs deleted file mode 100644 index 7f1ef71615..0000000000 --- a/roles/jd-server/src/lib/error.rs +++ /dev/null @@ -1,153 +0,0 @@ -//! ## Error Module -//! -//! Defines [`JdsError`], the central error enum used throughout the Job Declarator Server (JDS). -//! -//! It unifies errors from: -//! - I/O operations -//! - Channels (send/recv) -//! - SV2 stack: Binary, Codec, Noise, Framing, RolesLogic -//! - Mempool layer -//! - Locking logic (PoisonError) -//! - Domain-specific issues (e.g., missing job, invalid URL, reconstruction failures) -//! -//! This module ensures that all errors can be passed around consistently, including across async -//! boundaries. - -use binary_sv2; -use codec_sv2; -use framing_sv2; -use noise_sv2; -use parsers_sv2::Mining; -use roles_logic_sv2; -use std::{ - convert::From, - fmt::Debug, - sync::{MutexGuard, PoisonError}, -}; - -use crate::mempool::error::JdsMempoolError; - -#[derive(std::fmt::Debug)] -pub enum JdsError { - Io(std::io::Error), - ChannelSend(Box), - ChannelRecv(async_channel::RecvError), - BinarySv2(binary_sv2::Error), - Codec(codec_sv2::Error), - Noise(noise_sv2::Error), - RolesLogic(roles_logic_sv2::Error), - Framing(framing_sv2::Error), - PoisonLock(String), - Custom(String), - Sv2ProtocolError((u32, Mining<'static>)), - MempoolError(JdsMempoolError), - ImpossibleToReconstructBlock(String), - NoLastDeclaredJob, - InvalidRPCUrl, - BadCliArgs, - InvalidPrevHash, - InvalidCoinbase, - InvalidMerkleRoot, -} - -impl std::fmt::Display for JdsError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use JdsError::*; - match self { - Io(ref e) => write!(f, "I/O error: `{e:?}"), - ChannelSend(ref e) => write!(f, "Channel send failed: `{e:?}`"), - ChannelRecv(ref e) => write!(f, "Channel recv failed: `{e:?}`"), - BinarySv2(ref e) => write!(f, "Binary SV2 error: `{e:?}`"), - Codec(ref e) => write!(f, "Codec SV2 error: `{e:?}"), - Framing(ref e) => write!(f, "Framing SV2 error: `{e:?}`"), - Noise(ref e) => write!(f, "Noise SV2 error: `{e:?}"), - RolesLogic(ref e) => write!(f, "Roles Logic SV2 error: `{e:?}`"), - PoisonLock(ref e) => write!(f, "Poison lock: {e:?}"), - Custom(ref e) => write!(f, "Custom SV2 error: `{e:?}`"), - Sv2ProtocolError(ref e) => { - write!(f, "Received Sv2 Protocol Error from upstream: `{e:?}`") - } - MempoolError(ref e) => write!(f, "Mempool error: `{e:?}`"), - ImpossibleToReconstructBlock(e) => { - write!(f, "Error in reconstructing the block: {e:?}") - } - NoLastDeclaredJob => write!(f, "Last declared job not found"), - InvalidRPCUrl => write!(f, "Invalid Template Provider RPC URL"), - BadCliArgs => write!(f, "Bad CLI arg input"), - InvalidPrevHash => write!(f, "Invalid previous hash"), - InvalidCoinbase => write!(f, "Invalid coinbase"), - InvalidMerkleRoot => write!(f, "Invalid merkle root"), - } - } -} - -impl From for JdsError { - fn from(e: std::io::Error) -> JdsError { - JdsError::Io(e) - } -} - -impl From for JdsError { - fn from(e: async_channel::RecvError) -> JdsError { - JdsError::ChannelRecv(e) - } -} - -impl From for JdsError { - fn from(e: binary_sv2::Error) -> JdsError { - JdsError::BinarySv2(e) - } -} - -impl From for JdsError { - fn from(e: codec_sv2::Error) -> JdsError { - JdsError::Codec(e) - } -} - -impl From for JdsError { - fn from(e: noise_sv2::Error) -> JdsError { - JdsError::Noise(e) - } -} - -impl From for JdsError { - fn from(e: roles_logic_sv2::Error) -> JdsError { - JdsError::RolesLogic(e) - } -} - -impl From> for JdsError { - fn from(e: async_channel::SendError) -> JdsError { - JdsError::ChannelSend(Box::new(e)) - } -} - -impl From for JdsError { - fn from(e: String) -> JdsError { - JdsError::Custom(e) - } -} -impl From for JdsError { - fn from(e: framing_sv2::Error) -> JdsError { - JdsError::Framing(e) - } -} - -impl From>> for JdsError { - fn from(e: PoisonError>) -> JdsError { - JdsError::PoisonLock(e.to_string()) - } -} - -impl From<(u32, Mining<'static>)> for JdsError { - fn from(e: (u32, Mining<'static>)) -> Self { - JdsError::Sv2ProtocolError(e) - } -} - -impl From for JdsError { - fn from(error: JdsMempoolError) -> Self { - JdsError::MempoolError(error) - } -} diff --git a/roles/jd-server/src/lib/job_declarator/message_handler.rs b/roles/jd-server/src/lib/job_declarator/message_handler.rs deleted file mode 100644 index b5644bad41..0000000000 --- a/roles/jd-server/src/lib/job_declarator/message_handler.rs +++ /dev/null @@ -1,296 +0,0 @@ -use binary_sv2::{Decodable, Serialize, U256}; -use bitcoin::{ - consensus::Decodable as BitcoinDecodable, - hashes::{sha256d, Hash}, - Transaction, Txid, -}; -use job_declaration_sv2::{ - AllocateMiningJobToken, AllocateMiningJobTokenSuccess, DeclareMiningJob, DeclareMiningJobError, - DeclareMiningJobSuccess, ProvideMissingTransactions, ProvideMissingTransactionsSuccess, - PushSolution, -}; -use parsers_sv2::JobDeclaration; -use roles_logic_sv2::{ - errors::Error, - handlers::{job_declaration::ParseJobDeclarationMessagesFromDownstream, SendTo_}, - utils::Mutex, -}; -use std::{ - convert::TryInto, - io::Cursor, - sync::{atomic::Ordering, Arc}, -}; -pub type SendTo = SendTo_, ()>; -use crate::mempool::JDsMempool; - -use super::{signed_token, TransactionState}; -use parsers_sv2::AnyMessage as AllMessages; -use tracing::{debug, info}; - -use super::JobDeclaratorDownstream; - -impl JobDeclaratorDownstream { - fn verify_job(&mut self, message: &DeclareMiningJob) -> bool { - // Convert token from B0255 to u32 - let four_byte_array: [u8; 4] = message - .mining_job_token - .clone() - .to_vec() - .as_slice() - .try_into() - .unwrap(); - let token_u32 = u32::from_le_bytes(four_byte_array); - // TODO Function to implement, it must be checked if the requested job has: - // 1. right coinbase - // 2. right version field - // 3. right prev-hash - // 4. right nbits - self.token_to_job_map.contains_key(&(token_u32)) - } -} - -impl ParseJobDeclarationMessagesFromDownstream for JobDeclaratorDownstream { - fn handle_allocate_mining_job_token( - &mut self, - message: AllocateMiningJobToken, - ) -> Result { - info!( - "Received `AllocateMiningJobToken` with id: {}", - message.request_id - ); - debug!("`AllocateMiningJobToken`: {:?}", message.request_id); - let token = self.tokens.fetch_add(1, Ordering::Relaxed); - self.token_to_job_map.insert(token, None); - let message_success = AllocateMiningJobTokenSuccess { - request_id: message.request_id, - mining_job_token: token.to_le_bytes().to_vec().try_into().unwrap(), - coinbase_outputs: self.coinbase_output.clone().try_into().unwrap(), - }; - let message_enum = JobDeclaration::AllocateMiningJobTokenSuccess(message_success); - info!( - "Sending AllocateMiningJobTokenSuccess to proxy {}", - message_enum - ); - Ok(SendTo::Respond(message_enum)) - } - - // Transactions that are present in the mempool are stored here, that is sent to the - // mempool which use the rpc client to retrieve the whole data for each transaction. - // The unknown transactions is a vector that contains the transactions that are not in the - // jds mempool, and will be non-empty in the ProvideMissingTransactionsSuccess message - fn handle_declare_mining_job(&mut self, message: DeclareMiningJob) -> Result { - info!( - "Received `DeclareMiningJob` with id: {}", - message.request_id - ); - debug!("`DeclareMiningJob`: {}", message); - if let Some(old_mining_job) = self.declared_mining_job.0.take() { - clear_declared_mining_job(old_mining_job, &message, self.mempool.clone())?; - } - let mut known_transactions: Vec = vec![]; - if self.verify_job(&message) { - let txids = message.tx_ids_list.inner_as_ref(); - let mempool = self.mempool.safe_lock(|x| x.mempool.clone())?; - let mut transactions_with_state = vec![TransactionState::Missing; txids.len()]; - let mut missing_txs: Vec = Vec::new(); - for (i, txid) in txids.iter().enumerate() { - let hash = sha256d::Hash::from_slice(txid)?; - let txid = Txid::from(hash); - match mempool.contains_key(&txid) { - true => { - transactions_with_state[i] = TransactionState::PresentInMempool(txid); - known_transactions.push(txid); - } - false => { - missing_txs.push(i as u16); - } - } - } - self.declared_mining_job = ( - Some(message.clone().into_static()), - transactions_with_state, - missing_txs.clone(), - ); - // here we send the transactions that we want to be stored in jds mempool with full data - - self.add_txs_to_mempool - .add_txs_to_mempool_inner - .known_transactions - .append(&mut known_transactions); - let mut full_token = [0u8; 255]; - message.mining_job_token.to_bytes(&mut full_token)?; - let mining_job_token = &mut full_token[..32]; - if missing_txs.is_empty() { - let message_success = DeclareMiningJobSuccess { - request_id: message.request_id, - new_mining_job_token: signed_token( - U256::from_bytes(mining_job_token)?, - &self.public_key.clone(), - &self.private_key.clone(), - ), - }; - let message_enum_success = JobDeclaration::DeclareMiningJobSuccess(message_success); - Ok(SendTo::Respond(message_enum_success)) - } else { - let message_provide_missing_transactions = ProvideMissingTransactions { - request_id: message.request_id, - unknown_tx_position_list: missing_txs.into(), - }; - let message_enum_provide_missing_transactions = - JobDeclaration::ProvideMissingTransactions( - message_provide_missing_transactions, - ); - Ok(SendTo::Respond(message_enum_provide_missing_transactions)) - } - } else { - let message_error = DeclareMiningJobError { - request_id: message.request_id, - error_code: Vec::new().try_into().unwrap(), - error_details: Vec::new().try_into().unwrap(), - }; - let message_enum_error = JobDeclaration::DeclareMiningJobError(message_error); - Ok(SendTo::Respond(message_enum_error)) - } - } - - fn handle_provide_missing_transactions_success( - &mut self, - message: ProvideMissingTransactionsSuccess, - ) -> Result { - info!( - "Received `ProvideMissingTransactionsSuccess` with id: {}", - message.request_id - ); - debug!("`ProvideMissingTransactionsSuccess`: {}", message); - let (declared_mining_job, ref mut transactions_with_state, missing_indexes) = - &mut self.declared_mining_job; - let mut unknown_transactions: Vec = vec![]; - match declared_mining_job { - Some(declared_job) => { - let id = declared_job.request_id; - // check request_id in order to ignore old ProvideMissingTransactionsSuccess (see - // issue #860) - if id == message.request_id { - for (i, tx) in message.transaction_list.inner_as_ref().iter().enumerate() { - let mut cursor = Cursor::new(tx); - let transaction = - Transaction::consensus_decode_from_finite_reader(&mut cursor) - .map_err(|e| Error::TxDecodingError(e.to_string()))?; - Vec::push(&mut unknown_transactions, transaction.clone()); - let index = - *missing_indexes - .get(i) - .ok_or(Error::LogicErrorMessage(Box::new( - AllMessages::JobDeclaration( - JobDeclaration::ProvideMissingTransactionsSuccess( - message.clone().into_static(), - ), - ), - )))? as usize; - // insert the missing transactions in the mempool - transactions_with_state[index] = - TransactionState::PresentInMempool(transaction.compute_txid()); - } - self.add_txs_to_mempool - .add_txs_to_mempool_inner - .unknown_transactions - .append(&mut unknown_transactions); - // if there still a missing transaction return an error - for tx_with_state in transactions_with_state { - match tx_with_state { - TransactionState::PresentInMempool(_) => continue, - TransactionState::Missing => return Err(Error::JDSMissingTransactions), - } - } - let mut full_token = [0u8; 255]; - declared_job - .mining_job_token - .clone() - .to_bytes(&mut full_token)?; - let mining_job_token = &mut full_token[..32]; - let message_success = DeclareMiningJobSuccess { - request_id: message.request_id, - new_mining_job_token: signed_token( - U256::from_bytes(mining_job_token)?, - &self.public_key.clone(), - &self.private_key.clone(), - ), - }; - let message_enum_success = - JobDeclaration::DeclareMiningJobSuccess(message_success); - return Ok(SendTo::Respond(message_enum_success)); - } - } - None => return Err(Error::NoValidJob), - } - Ok(SendTo::None(None)) - } - - fn handle_push_solution(&mut self, message: PushSolution<'_>) -> Result { - info!("Received PushSolution from JDC"); - debug!("`PushSolution`: {}", message); - let m = JobDeclaration::PushSolution(message.clone().into_static()); - Ok(SendTo::None(Some(m))) - } -} - -fn clear_declared_mining_job( - old_mining_job: DeclareMiningJob, - new_mining_job: &DeclareMiningJob, - mempool: Arc>, -) -> Result<(), Error> { - let old_transactions = old_mining_job.tx_ids_list.inner_as_ref(); - let new_transactions = new_mining_job.tx_ids_list.inner_as_ref(); - - if old_transactions.is_empty() { - info!("No transactions to remove from mempool"); - return Ok(()); - } - - let result = mempool.safe_lock(|mempool_| -> Result<(), Error> { - let mempool_txs = mempool_.mempool.clone(); - - for old_txid in old_transactions - .iter() - .filter(|&id| !new_transactions.contains(id)) - { - if let Some(tx) = mempool_txs.get(*old_txid) { - if let Some((transaction, _)) = tx.as_ref() { - let txid = transaction.compute_txid(); - match mempool_.mempool.get_mut(&txid) { - Some(Some((_transaction, counter))) => { - if *counter > 1 { - *counter -= 1; - debug!( - "Fat transaction {:?} counter decremented; job id {:?} dropped", - txid, old_mining_job.request_id - ); - } else { - mempool_.mempool.remove(&txid); - debug!( - "Fat transaction {:?} with job id {:?} removed from mempool", - txid, old_mining_job.request_id - ); - } - } - Some(None) => debug!( - "Thin transaction {:?} with job id {:?} removed from mempool", - txid, old_mining_job.request_id - ), - None => {} - } - } else { - debug!("Transaction with id {:?} is None in mempool", old_txid); - } - } else { - debug!( - "Transaction with id {:?} not found in mempool for old jobs", - old_txid - ); - } - } - Ok(()) - })?; - - result.map_err(|err| Error::PoisonLock(err.to_string())) -} diff --git a/roles/jd-server/src/lib/job_declarator/mod.rs b/roles/jd-server/src/lib/job_declarator/mod.rs deleted file mode 100644 index bc11a649cb..0000000000 --- a/roles/jd-server/src/lib/job_declarator/mod.rs +++ /dev/null @@ -1,645 +0,0 @@ -//! # Job Declarator Server - Protocol and Downstream Handling -//! -//! This module implements the core logic of the **Job Declarator Server (JDS)**. -//! -//! Responsibilities include: -//! - Listening for downstream client connections (JDCs) -//! - Handling the Job Declaration Protocol (AllocateMiningJobToken, DeclareMiningJob, PushSolution, -//! etc.) -//! - Tracking job state and transaction presence -//! - Managing transaction flow into the local mempool -//! - Assembling and submitting full blocks to the upstream node -//! -//! Structure: -//! - [`JobDeclarator`] handles server-level responsibilities like accepting new TCP connections. -//! - [`JobDeclaratorDownstream`] manages the per-client state and protocol interaction. -//! -//! The design is one-task-per-downstream, with communication via channels and internal -//! synchronization. - -pub mod message_handler; -use super::{ - error::JdsError, mempool::JDsMempool, status, EitherFrame, JobDeclaratorServerConfig, StdFrame, -}; -use async_channel::{Receiver, Sender}; -use binary_sv2::{self, B0255, U256}; -use bitcoin::{ - block::{Header, Version}, - consensus::{deserialize, encode::serialize}, - hashes::{sha256d::Hash as DHash, Hash}, - Amount, Block, BlockHash, CompactTarget, Transaction, TxOut, Txid, -}; -use codec_sv2::HandshakeRole; -use common_messages_sv2::{ - Protocol, SetupConnection, SetupConnectionError, SetupConnectionSuccess, -}; -use core::panic; -use error_handling::handle_result; -use job_declaration_sv2::{DeclareMiningJob, PushSolution}; -use network_helpers_sv2::noise_connection::Connection; -use nohash_hasher::BuildNoHashHasher; -use noise_sv2::Responder; -use parsers_sv2::{AnyMessage as JdsMessages, JobDeclaration}; -use roles_logic_sv2::{ - handlers::job_declaration::{ParseJobDeclarationMessagesFromDownstream, SendTo}, - utils::Mutex, -}; -use std::{ - collections::HashMap, - convert::TryInto, - sync::{atomic::AtomicU32, Arc}, -}; -use stratum_apps::key_utils::{Secp256k1PublicKey, Secp256k1SecretKey, SignatureService}; -use tokio::{net::TcpListener, time::Duration}; -use tracing::{debug, error, info}; - -/// Represents whether a transaction declared in a mining job is known to the JDS mempool -/// or still missing and needs to be fetched/provided. -#[derive(Clone, Debug)] -pub enum TransactionState { - PresentInMempool(Txid), - Missing, -} - -/// Contains transaction identifiers and full transaction data that need to be -/// added or completed in the JDS mempool. -/// -/// Used internally during the job declaration lifecycle. -#[derive(Clone, Debug)] -pub struct AddTrasactionsToMempoolInner { - pub known_transactions: Vec, - pub unknown_transactions: Vec, -} - -/// Wrapper struct enabling transaction updates to be sent via a channel to the mempool task. -#[derive(Clone, Debug)] -pub struct AddTrasactionsToMempool { - pub add_txs_to_mempool_inner: AddTrasactionsToMempoolInner, - pub sender_add_txs_to_mempool: Sender, -} - -/// Represents a single downstream connection to a JDC. -/// -/// This struct tracks all state relevant to one connection, including: -/// - The declared mining job and missing transactions -/// - Mapping between tokens and job IDs -/// - Interaction with the mempool -/// -/// It operates in its own async task and communicates with the rest of the system -/// via channels and locks. - -#[derive(Debug)] -pub struct JobDeclaratorDownstream { - #[allow(dead_code)] - full_template_mode_required: bool, - sender: Sender, - receiver: Receiver, - // TODO this should be computed for each new template so that fees are included - #[allow(dead_code)] - // TODO: use coinbase output - coinbase_output: Vec, - token_to_job_map: HashMap, BuildNoHashHasher>, - tokens: AtomicU32, - public_key: Secp256k1PublicKey, - private_key: Secp256k1SecretKey, - mempool: Arc>, - // Vec is the vector of missing transactions - declared_mining_job: ( - Option>, - Vec, - Vec, - ), - add_txs_to_mempool: AddTrasactionsToMempool, -} - -impl JobDeclaratorDownstream { - /// Creates a new downstream connection context. - pub fn new( - full_template_mode_required: bool, - receiver: Receiver, - sender: Sender, - config: &JobDeclaratorServerConfig, - mempool: Arc>, - sender_add_txs_to_mempool: Sender, - ) -> Self { - // TODO: use next variables - let token_to_job_map = HashMap::with_hasher(BuildNoHashHasher::default()); - let tokens = AtomicU32::new(0); - let add_txs_to_mempool_inner = AddTrasactionsToMempoolInner { - known_transactions: vec![], - unknown_transactions: vec![], - }; - let coinbase_output = serialize(&vec![TxOut { - value: Amount::from_sat(0), - script_pubkey: config.coinbase_reward_scripts().script_pubkey().to_owned(), - }]); - - Self { - full_template_mode_required, - receiver, - sender, - coinbase_output, - token_to_job_map, - tokens, - public_key: *config.authority_public_key(), - private_key: *config.authority_secret_key(), - mempool, - declared_mining_job: (None, Vec::new(), Vec::new()), - add_txs_to_mempool: AddTrasactionsToMempool { - add_txs_to_mempool_inner, - sender_add_txs_to_mempool, - }, - } - } - - fn get_block_hex( - self_mutex: Arc>, - message: PushSolution, - ) -> Result> { - let (last_declare_, _, _) = self_mutex - .clone() - .safe_lock(|x| x.declared_mining_job.clone()) - .map_err(|e| Box::new(JdsError::PoisonLock(e.to_string())))?; - let last_declare = last_declare_.ok_or(Box::new(JdsError::NoLastDeclaredJob))?; - let mut transactions_list = Self::collect_txs_in_job(self_mutex)?; - - let hash: [u8; 32] = message - .prev_hash - .to_vec() - .try_into() - .map_err(|_| Box::new(JdsError::InvalidPrevHash))?; - let hash = Hash::from_slice(&hash).expect("32 bytes should always be valid sha256d hash"); - let prev_blockhash = BlockHash::from_raw_hash(hash); - - let dummy_merkle_root = - DHash::from_slice(&[0u8; 32]).expect("32 bytes should always be valid sha256d hash"); - - let header = Header { - version: Version::from_consensus(message.version as i32), - prev_blockhash, - merkle_root: dummy_merkle_root.into(), - time: message.ntime, - bits: CompactTarget::from_consensus(message.nbits), - nonce: message.nonce, - }; - - let mut serialized_coinbase = Vec::new(); - serialized_coinbase.extend_from_slice(last_declare.coinbase_tx_prefix.to_vec().as_slice()); - serialized_coinbase.extend_from_slice(message.extranonce.to_vec().as_slice()); - serialized_coinbase.extend_from_slice(last_declare.coinbase_tx_suffix.to_vec().as_slice()); - let coinbase = deserialize(&serialized_coinbase[..]) - .map_err(|_| Box::new(JdsError::InvalidCoinbase))?; - transactions_list.insert(0, coinbase); - - let mut block = Block { - header, - txdata: transactions_list, - }; - - let merkle_root = block - .compute_merkle_root() - .ok_or(Box::new(JdsError::InvalidMerkleRoot))?; - block.header.merkle_root = merkle_root; - - Ok(hex::encode(serialize(&block))) - } - - fn collect_txs_in_job(self_mutex: Arc>) -> Result, Box> { - let (_, transactions_with_state, _) = self_mutex - .clone() - .safe_lock(|x| x.declared_mining_job.clone()) - .map_err(|e| Box::new(JdsError::PoisonLock(e.to_string())))?; - let mempool = self_mutex - .safe_lock(|x| x.mempool.clone()) - .map_err(|e| Box::new(JdsError::PoisonLock(e.to_string())))?; - let mut transactions_list: Vec = Vec::new(); - for tx_with_state in transactions_with_state.iter().enumerate() { - if let TransactionState::PresentInMempool(txid) = tx_with_state.1 { - let tx = mempool - .safe_lock(|x| x.mempool.get(txid).cloned()) - .map_err(|e| JdsError::PoisonLock(e.to_string()))? - .ok_or(Box::new(JdsError::ImpossibleToReconstructBlock( - "Txid not found in jds mempool".to_string(), - )))? - .ok_or(Box::new(JdsError::ImpossibleToReconstructBlock( - "Txid found in jds mempool but transactions not present".to_string(), - )))?; - transactions_list.push(tx.0); - } else { - return Err(Box::new(JdsError::ImpossibleToReconstructBlock( - "Unknown transaction".to_string(), - ))); - }; - } - Ok(transactions_list) - } - - async fn send_txs_to_mempool(self_mutex: Arc>) { - let add_txs_to_mempool = self_mutex - .safe_lock(|a| a.add_txs_to_mempool.clone()) - .unwrap(); - let sender_add_txs_to_mempool = add_txs_to_mempool.sender_add_txs_to_mempool; - let add_txs_to_mempool_inner = add_txs_to_mempool.add_txs_to_mempool_inner; - let _ = sender_add_txs_to_mempool - .send(add_txs_to_mempool_inner) - .await; - // the trasnactions sent to the mempool can be freed - let _ = self_mutex.safe_lock(|a| { - a.add_txs_to_mempool.add_txs_to_mempool_inner = AddTrasactionsToMempoolInner { - known_transactions: vec![], - unknown_transactions: vec![], - }; - }); - } - - fn get_transactions_in_job(self_mutex: Arc>) -> Vec { - let mut known_transactions: Vec = Vec::new(); - let job_transactions = self_mutex - .safe_lock(|a| a.declared_mining_job.1.clone()) - .unwrap(); - for transaction in job_transactions { - match transaction { - TransactionState::PresentInMempool(txid) => known_transactions.push(txid), - TransactionState::Missing => { - continue; - } - } - } - known_transactions - } - - /// Sends a single Job Declaration message back to the downstream client. - /// - /// Wraps the message into a `StdFrame` and sends it through the established channel. - pub async fn send( - self_mutex: Arc>, - message: parsers_sv2::JobDeclaration<'static>, - ) -> Result<(), ()> { - let sv2_frame: StdFrame = JdsMessages::JobDeclaration(message).try_into().unwrap(); - let sender = self_mutex.safe_lock(|self_| self_.sender.clone()).unwrap(); - sender.send(sv2_frame.into()).await.map_err(|_| ())?; - Ok(()) - } - - /// Starts the message processing loop for this downstream connection. - /// - /// - Waits for incoming SV2 messages - /// - Delegates message parsing to [`ParseJobDeclarationMessagesFromDownstream`] - /// - Sends appropriate responses back to the client - /// - Updates the JDS mempool as needed - /// - /// This loop runs until the client disconnects or a critical error is encountered. - pub fn start( - self_mutex: Arc>, - tx_status: status::Sender, - new_block_sender: Sender, - ) { - let recv = self_mutex.safe_lock(|s| s.receiver.clone()).unwrap(); - tokio::spawn(async move { - loop { - match recv.recv().await { - Ok(message) => { - let mut frame: StdFrame = handle_result!(tx_status, message.try_into()); - let header = frame - .get_header() - .ok_or_else(|| JdsError::Custom(String::from("No header set"))); - let header = handle_result!(tx_status, header); - let message_type = header.msg_type(); - let payload = frame.payload(); - let next_message_to_send = - ParseJobDeclarationMessagesFromDownstream::handle_message_job_declaration( - self_mutex.clone(), - message_type, - payload, - ); - // How works the txs recognition and txs storing in JDS mempool - // when a DMJ arrives, the JDS compares the received transactions with the - // ids in the the JDS mempool. Then there are two scenarios - // 1. the JDS recognizes all the transactions. Then, just before a DMJS is - // sent, the JDS mempool is triggered to fill in the JDS mempool the id - // of declared job with the full transaction (with send_tx_to_mempool - // method(), that eventually will ask the transactions to a bitcoin node - // via RPC) - // 2. there are some unknown txids. Just before sending PMT, the JDS mempool - // is triggered to fill the known txids with the full transactions. When - // a PMTS arrives, just before sending a DMJS, the unknown full - // transactions provided by the downstream are added to the JDS mempool - match next_message_to_send { - Ok(SendTo::Respond(m)) => { - match m { - JobDeclaration::AllocateMiningJobToken(_) => { - error!("Send unexpected message: AMJT"); - } - JobDeclaration::AllocateMiningJobTokenSuccess(_) => { - debug!("Send message: AMJTS"); - } - JobDeclaration::DeclareMiningJob(_) => { - error!("Send unexpected message: DMJ"); - } - JobDeclaration::DeclareMiningJobError(_) => { - debug!("Send nmessage: DMJE"); - } - JobDeclaration::DeclareMiningJobSuccess(_) => { - debug!("Send message: DMJS. Updating the JDS mempool."); - Self::send_txs_to_mempool(self_mutex.clone()).await; - } - JobDeclaration::ProvideMissingTransactions(_) => { - debug!("Send message: PMT. Updating the JDS mempool."); - Self::send_txs_to_mempool(self_mutex.clone()).await; - } - JobDeclaration::ProvideMissingTransactionsSuccess(_) => { - error!("Send unexpected PMTS"); - } - JobDeclaration::PushSolution(_) => todo!(), - } - Self::send(self_mutex.clone(), m).await.unwrap(); - } - Ok(SendTo::RelayNewMessage(message)) => { - error!("JD Server: unexpected relay new message {}", message); - } - Ok(SendTo::RelayNewMessageToRemote(remote, message)) => { - error!( - "JD Server: unexpected relay new message to remote. Remote: {:?}, Message: {}", - remote, - message - ); - } - Ok(SendTo::RelaySameMessageToRemote(remote)) => { - error!( - "JD Server: unexpected relay same message to remote. Remote: {:?}", - remote - ); - } - Ok(SendTo::Multiple(multiple)) => { - error!("JD Server: unexpected multiple messages: {:?}", multiple); - } - Ok(SendTo::None(m)) => { - match m { - Some(JobDeclaration::PushSolution(message)) => { - match Self::collect_txs_in_job(self_mutex.clone()) { - Ok(_) => { - info!( - "All transactions in downstream job are recognized correctly by the JD Server" - ); - let hexdata = - match JobDeclaratorDownstream::get_block_hex( - self_mutex.clone(), - message, - ) { - Ok(inner) => inner, - Err(e) => { - error!( - "Received solution but encountered error: {:?}", - e - ); - recv.close(); - //TODO should we brake it? - break; - } - }; - let _ = new_block_sender.send(hexdata).await; - } - Err(error) => { - error!("Missing transactions: {:?}", error); - // TODO print here the ip of the downstream - let known_transactions = - JobDeclaratorDownstream::get_transactions_in_job( - self_mutex.clone() - ); - let retrieve_transactions = - AddTrasactionsToMempoolInner { - known_transactions, - unknown_transactions: Vec::new(), - }; - let mempool = self_mutex - .clone() - .safe_lock(|a| a.mempool.clone()) - .unwrap(); - tokio::select! { - _ = JDsMempool::add_tx_data_to_mempool(mempool, retrieve_transactions) => { - match JobDeclaratorDownstream::get_block_hex( - self_mutex.clone(), - message.clone(), - ) { - Ok(hexdata) => { - let _ = new_block_sender.send(hexdata).await; - }, - Err(e) => { - handle_result!( - tx_status, - Err(*e) - ); - } - }; - } - _ = tokio::time::sleep(Duration::from_secs(60)) => {} - } - } - }; - } - Some(JobDeclaration::DeclareMiningJob(_)) => { - error!("JD Server received an unexpected message {:?}", m); - } - Some(JobDeclaration::DeclareMiningJobSuccess(_)) => { - error!("JD Server received an unexpected message {:?}", m); - } - Some(JobDeclaration::DeclareMiningJobError(_)) => { - error!("JD Server received an unexpected message {:?}", m); - } - Some(JobDeclaration::AllocateMiningJobToken(_)) => { - error!("JD Server received an unexpected message {:?}", m); - } - Some(JobDeclaration::AllocateMiningJobTokenSuccess(_)) => { - error!("JD Server received an unexpected message {:?}", m); - } - Some(JobDeclaration::ProvideMissingTransactions(_)) => { - error!("JD Server received an unexpected message {:?}", m); - } - Some(JobDeclaration::ProvideMissingTransactionsSuccess(_)) => { - error!("JD Server received an unexpected message {:?}", m); - } - None => (), - } - } - Err(e) => { - error!("{:?}", e); - handle_result!( - tx_status, - Err(JdsError::Custom("Invalid message received".to_string())) - ); - recv.close(); - break; - } - } - } - Err(err) => { - handle_result!(tx_status, Err(JdsError::ChannelRecv(err))); - break; - } - } - } - }); - } -} - -pub fn signed_token( - tx_hash_list_hash: U256, - _pub_key: &Secp256k1PublicKey, - prv_key: &Secp256k1SecretKey, -) -> B0255<'static> { - let secp = SignatureService::default(); - - let signature = secp.sign(tx_hash_list_hash.to_vec(), prv_key.0); - - // Sign message - signature.as_ref().to_vec().try_into().unwrap() -} - -fn _get_random_token() -> B0255<'static> { - let inner: [u8; 32] = rand::random(); - inner.to_vec().try_into().unwrap() -} - -/// The entry point of the Job Declarator Server. -/// -/// Responsible for initializing server state and accepting incoming TCP connections -/// from downstream clients (JDCs). Each client gets a dedicated [`JobDeclaratorDownstream`] -/// instance. -/// -/// Responsibilities: -/// - Listening on the configured address -/// - Performing the SV2 Noise handshake -/// - Handling `SetupConnection` messages -/// - Spawning the downstream message loop -pub struct JobDeclarator {} - -impl JobDeclarator { - /// Starts the Job Declarator server. - /// - /// - Accepts configuration and shared components (status sender, mempool, etc.). - /// - Initializes internal state. - /// - Begins listening for downstream connections via - /// [`JobDeclarator::accept_incoming_connection`]. - pub async fn start( - config: JobDeclaratorServerConfig, - status_tx: crate::status::Sender, - mempool: Arc>, - new_block_sender: Sender, - sender_add_txs_to_mempool: Sender, - ) { - let self_ = Arc::new(Mutex::new(Self {})); - info!("JD INITIALIZED"); - Self::accept_incoming_connection( - self_, - config, - status_tx, - mempool, - new_block_sender, - sender_add_txs_to_mempool, - ) - .await; - } - async fn accept_incoming_connection( - _self_: Arc>, - config: JobDeclaratorServerConfig, - status_tx: crate::status::Sender, - mempool: Arc>, - new_block_sender: Sender, - sender_add_txs_to_mempool: Sender, - ) { - let listener = TcpListener::bind(config.listen_jd_address()).await.unwrap(); - - while let Ok((stream, _)) = listener.accept().await { - let responder = Responder::from_authority_kp( - &config.authority_public_key().into_bytes(), - &config.authority_secret_key().into_bytes(), - std::time::Duration::from_secs(config.cert_validity_sec()), - ) - .unwrap(); - - let addr = stream.peer_addr(); - - if let Ok((receiver, sender)) = - Connection::new(stream, HandshakeRole::Responder(responder)).await - { - match receiver.recv().await { - Ok(EitherFrame::Sv2(mut sv2_message)) => { - debug!("Received SV2 message: {:?}", sv2_message); - let payload = sv2_message.payload(); - - if let Ok(setup_connection) = - binary_sv2::from_bytes::(payload) - { - let flag = setup_connection.flags; - let is_valid = SetupConnection::check_flags( - Protocol::JobDeclarationProtocol, - config.full_template_mode_required() as u32, - flag, - ); - - if is_valid { - let success_message = SetupConnectionSuccess { - used_version: 2, - flags: (setup_connection.flags & 1u32), - }; - info!("Sending success message for proxy"); - let sv2_frame: StdFrame = JdsMessages::Common(success_message.into()) - .try_into() - .expect("Failed to convert setup connection response message to standard frame"); - - sender.send(sv2_frame.into()).await.unwrap(); - - let jddownstream = Arc::new(Mutex::new( - JobDeclaratorDownstream::new( - (setup_connection.flags & 1u32) != 0u32, /* this takes a - * bool instead - * of u32 */ - receiver.clone(), - sender.clone(), - &config, - mempool.clone(), - sender_add_txs_to_mempool.clone(), /* each downstream has its own sender (multi producer single consumer) */ - ), - )); - - JobDeclaratorDownstream::start( - jddownstream, - status_tx.clone(), - new_block_sender.clone(), - ); - } else { - let error_message = SetupConnectionError { - flags: flag, - error_code: "unsupported-feature-flags" - .to_string() - .into_bytes() - .try_into() - .unwrap(), - }; - info!("Sending error message for proxy"); - let sv2_frame: StdFrame = JdsMessages::Common(error_message.into()) - .try_into() - .expect("Failed to convert setup connection response message to standard frame"); - - sender.send(sv2_frame.into()).await.unwrap(); - } - } else { - error!("Error parsing SetupConnection message"); - } - } - Ok(EitherFrame::HandShake(handshake_message)) => { - error!( - "Unexpected handshake message from upstream: {:?} at {:?}", - handshake_message, addr - ); - } - Err(e) => { - error!("Error receiving message: {:?}", e); - } - } - } else { - error!("Cannot connect to {:?}", addr); - } - } - } -} diff --git a/roles/jd-server/src/lib/mempool/error.rs b/roles/jd-server/src/lib/mempool/error.rs deleted file mode 100644 index cb0e2a2971..0000000000 --- a/roles/jd-server/src/lib/mempool/error.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! ## JDS Mempool Errors -//! -//! This module defines the error types and handling utilities related to the mempool logic in the -//! Job Declarator Server (JDS). -//! -//! These errors are mostly used when interacting with: -//! - the internal mempool data structure -//! - the RPC client that communicates with the Bitcoin node -//! - the synchronization/update routines -//! -//! It also includes a centralized error logging helper (`handle_error`) to standardize warnings -//! and diagnostics across components. - -use rpc_sv2::mini_rpc_client::RpcError; -use std::{convert::From, sync::PoisonError}; -use tracing::{error, warn}; - -/// Errors that may occur during JDS mempool operations. -#[derive(Debug)] -pub enum JdsMempoolError { - /// The mempool was found to be empty (likely due to testnet/signet conditions). - EmptyMempool, - /// Failed to construct a valid RPC client (e.g. invalid URL, malformed credentials). - NoClient, - /// An RPC call to the Bitcoin node failed. - Rpc(RpcError), - /// A poisoned lock was encountered while accessing the mempool - PoisonLock(String), -} - -impl From for JdsMempoolError { - fn from(value: RpcError) -> Self { - JdsMempoolError::Rpc(value) - } -} - -impl From> for JdsMempoolError { - fn from(value: PoisonError) -> Self { - JdsMempoolError::PoisonLock(value.to_string()) - } -} - -/// Logs a structured diagnostic message for a given mempool error. -/// -/// This function is used throughout the codebase to provide more meaningful context -/// in logs when mempool-related operations fail. -pub fn handle_error(err: &JdsMempoolError) { - match err { - JdsMempoolError::EmptyMempool => { - warn!("{:?}", err); - warn!("Template Provider is running, but its MEMPOOL is empty (possible reasons: you're testing in testnet, signet, or regtest)"); - } - JdsMempoolError::NoClient => { - error!("{:?}", err); - error!("Unable to establish RPC connection with Template Provider (possible reasons: not fully synced, down)"); - } - JdsMempoolError::Rpc(_) => { - error!("{:?}", err); - error!("Unable to establish RPC connection with Template Provider (possible reasons: not fully synced, down)"); - } - JdsMempoolError::PoisonLock(_) => { - error!("{:?}", err); - error!("Poison lock error)"); - } - } -} diff --git a/roles/jd-server/src/lib/mempool/mod.rs b/roles/jd-server/src/lib/mempool/mod.rs deleted file mode 100644 index 796463ae4a..0000000000 --- a/roles/jd-server/src/lib/mempool/mod.rs +++ /dev/null @@ -1,200 +0,0 @@ -//! ## Mempool Management for the Job Declarator Server (JDS) -//! -//! This module defines the internal mempool of the JDS, responsible for keeping track of known -//! transactions and interacting with the Bitcoin node via RPC. -//! -//! Its core responsibilities are: -//! - Keeping a local copy of txids and (optionally) their full transaction data -//! - Pulling known transactions from the Bitcoin node on demand (via `getrawtransaction`) -//! - Accepting and tracking raw transactions received from clients -//! - Forwarding valid blocks to the Bitcoin node via `submitblock` -//! -//! Internally, `JDsMempool` uses a `HashMap>`: -//! - `None`: transaction only known by ID, data is missing -//! - `Some`: full transaction is known, `u32` is a reference counter for eviction -//! -//! Most methods are `Arc>`-wrapped and should be reviewed for locking efficiency. - -pub mod error; -use super::job_declarator::AddTrasactionsToMempoolInner; -use crate::mempool::error::JdsMempoolError; -use async_channel::Receiver; -use bitcoin::{blockdata::transaction::Transaction, hash_types::Txid}; -use hashbrown::HashMap; -use rpc_sv2::{mini_rpc_client, mini_rpc_client::RpcError}; -use std::{str::FromStr, sync::Arc}; - -use roles_logic_sv2::utils::Mutex; -/// Wrapper around a known transaction and its hash. -#[derive(Clone, Debug)] -pub struct TransactionWithHash { - pub id: Txid, - pub tx: Option<(Transaction, u32)>, // Full data and ref count -} - -/// Internal representation of the JDS mempool. -#[derive(Clone, Debug)] -pub struct JDsMempool { - /// Local map of known txids and their associated data (if available). - pub mempool: HashMap>, - /// Auth for RPC connection to the node. - auth: mini_rpc_client::Auth, - /// URI of the Bitcoin node. - url: rpc_sv2::Uri, - /// Receiver for new block solutions coming from JDC. - new_block_receiver: Receiver, -} - -impl JDsMempool { - /// Returns a MiniRpcClient if the URL looks valid. - pub fn get_client(&self) -> Option { - let url = self.url.to_string(); - if url.contains("http") { - let client = mini_rpc_client::MiniRpcClient::new(self.url.clone(), self.auth.clone()); - Some(client) - } else { - None - } - } - - /// This function is used only for debug purposes and should not be used - /// in production code. - #[cfg(debug_assertions)] - pub fn _get_transaction_list(self_: Arc>) -> Vec { - let tx_list = self_.safe_lock(|x| x.mempool.clone()).unwrap(); - let tx_list_: Vec = tx_list.iter().map(|n| *n.0).collect(); - tx_list_ - } - - /// Instantiates a new empty mempool for JDS. - pub fn new( - url: rpc_sv2::Uri, - username: String, - password: String, - new_block_receiver: Receiver, - ) -> Self { - let auth = mini_rpc_client::Auth::new(username, password); - let empty_mempool: HashMap> = HashMap::new(); - JDsMempool { - mempool: empty_mempool, - auth, - url, - new_block_receiver, - } - } - - /// Simple RPC ping to verify connection to Bitcoin node. - pub async fn health(self_: Arc>) -> Result<(), JdsMempoolError> { - let client = self_ - .safe_lock(|a| a.get_client())? - .ok_or(JdsMempoolError::NoClient)?; - client.health().await.map_err(JdsMempoolError::Rpc) - } - - /// Inserts transactions into the mempool: - /// - known txids are fetched from the Bitcoin node - /// - unknown txs are directly inserted - pub async fn add_tx_data_to_mempool( - self_: Arc>, - add_txs_to_mempool_inner: AddTrasactionsToMempoolInner, - ) -> Result<(), JdsMempoolError> { - let txids = add_txs_to_mempool_inner.known_transactions; - let transactions = add_txs_to_mempool_inner.unknown_transactions; - let client = self_ - .safe_lock(|a| a.get_client())? - .ok_or(JdsMempoolError::NoClient)?; - // fill in the mempool the transactions id in the mempool with the full transactions - // retrieved from the jd client - for txid in txids { - if let Some(None) = self_ - .safe_lock(|a| a.mempool.get(&txid).cloned()) - .map_err(|e| JdsMempoolError::PoisonLock(e.to_string()))? - { - let transaction = client - .get_raw_transaction(&txid.to_string(), None) - .await - .map_err(JdsMempoolError::Rpc)?; - let _ = self_.safe_lock(|a| { - a.mempool - .entry(transaction.compute_txid()) - .and_modify(|entry| { - if let Some((_, count)) = entry { - *count += 1; - } else { - *entry = Some((transaction.clone(), 1)); - } - }) - .or_insert(Some((transaction, 1))); - }); - } - } - - // fill in the mempool the transactions given in input - for transaction in transactions { - let _ = self_.safe_lock(|a| { - a.mempool - .entry(transaction.compute_txid()) - .and_modify(|entry| { - if let Some((_, count)) = entry { - *count += 1; - } else { - *entry = Some((transaction.clone(), 1)); - } - }) - .or_insert(Some((transaction, 1))); - }); - } - Ok(()) - } - - /// Periodically synchronizes the mempool with the Bitcoin node. - /// This only inserts thin entries (`None` as value), not full transactions. - pub async fn update_mempool(self_: Arc>) -> Result<(), JdsMempoolError> { - let client = self_ - .safe_lock(|x| x.get_client())? - .ok_or(JdsMempoolError::NoClient)?; - - let mempool = client.get_raw_mempool().await?; - - let raw_mempool_txids: Result, _> = mempool - .into_iter() - .map(|id| { - Txid::from_str(&id) - .map_err(|err| JdsMempoolError::Rpc(RpcError::Deserialization(err.to_string()))) - }) - .collect(); - - let raw_mempool_txids = raw_mempool_txids?; - - // Holding the lock till the light mempool updation is complete. - let is_mempool_empty = self_.safe_lock(|x| { - raw_mempool_txids.iter().for_each(|txid| { - x.mempool.entry(*txid).or_insert(None); - }); - x.mempool.is_empty() - })?; - - if is_mempool_empty { - Err(JdsMempoolError::EmptyMempool) - } else { - Ok(()) - } - } - - /// Listens for block submissions (hex-encoded) and propagates them to the Bitcoin node. - pub async fn on_submit(self_: Arc>) -> Result<(), JdsMempoolError> { - let new_block_receiver: Receiver = - self_.safe_lock(|x| x.new_block_receiver.clone())?; - let client = self_ - .safe_lock(|x| x.get_client())? - .ok_or(JdsMempoolError::NoClient)?; - - while let Ok(block_hex) = new_block_receiver.recv().await { - match mini_rpc_client::MiniRpcClient::submit_block(&client, block_hex).await { - Ok(_) => return Ok(()), - Err(e) => JdsMempoolError::Rpc(e), - }; - } - Ok(()) - } -} diff --git a/roles/jd-server/src/lib/mod.rs b/roles/jd-server/src/lib/mod.rs deleted file mode 100644 index 28e3c72d6e..0000000000 --- a/roles/jd-server/src/lib/mod.rs +++ /dev/null @@ -1,251 +0,0 @@ -//! ## JDS Core Runtime Module -//! -//! This module serves as the central coordination layer of the Job Declarator Server (JDS). -//! -//! It connects all core components: -//! - `mempool`: a local cache of Bitcoin transactions, synchronized via RPC. -//! - `job_declarator`: protocol logic for handling downstream job declaration clients. -//! - `status`: a simple health/error propagation mechanism. -//! - `config`: configuration loader and accessor. -//! -//! The [`JobDeclaratorServer`] struct represents the entrypoint to the system's async runtime. -//! It is launched from `main.rs` and responsible for: -//! - validating config -//! - initializing the mempool -//! - spawning all background tasks -//! - handling graceful shutdowns and task health reporting -//! -//! All components communicate asynchronously using `async_channel`. - -pub mod config; -pub mod error; -pub mod job_declarator; -pub mod mempool; -pub mod status; -use async_channel::{bounded, unbounded, Receiver, Sender}; -use config::JobDeclaratorServerConfig; -use error::JdsError; -use error_handling::handle_result; -use job_declarator::JobDeclarator; -use mempool::error::JdsMempoolError; -pub use rpc_sv2::Uri; -use std::{ops::Sub, str::FromStr, sync::Arc}; - -use codec_sv2::{StandardEitherFrame, StandardSv2Frame}; -use parsers_sv2::AnyMessage as JdsMessages; -use roles_logic_sv2::utils::Mutex; -use tokio::{select, task}; -use tracing::{error, info, warn}; - -/// Type alias for incoming SV2 messages. -pub type Message = JdsMessages<'static>; - -/// SV2 frame carrying a parsed JDS message. -pub type StdFrame = StandardSv2Frame; - -/// SV2 frame that can be either a standard message or handshake frame. -pub type EitherFrame = StandardEitherFrame; - -/// The core runtime orchestrator for the JDS system. -/// -/// Starts all essential services (mempool polling, block submission, job declaration protocol) -/// and monitors for shutdown conditions or task failures via a `status` channel. -#[derive(Debug, Clone)] -pub struct JobDeclaratorServer { - config: JobDeclaratorServerConfig, -} - -impl JobDeclaratorServer { - /// Constructs a new instance using the given TOML configuration. - pub fn new(config: JobDeclaratorServerConfig) -> Self { - Self { config } - } - - /// Starts the Job Declarator Server runtime. - /// - /// This method spawns the following: - /// - a task for polling the Bitcoin Core mempool - /// - a task for processing new block submissions from downstream clients - /// - a task for listening to incoming downstream connections - /// - a task for integrating transaction data into the local mempool - /// - /// It concludes with a `select!` loop that reacts to: - /// - SIGINT (`tokio::signal::ctrl_c()`) - /// - messages from the `status` channel - /// - /// When a critical error or interrupt is received, the server shuts down cleanly. - pub async fn start(&self) -> Result<(), JdsError> { - let mut config = self.config.clone(); - // Normalize URL to avoid trailing slashes. - if config.core_rpc_url().ends_with('/') { - config.set_core_rpc_url(config.core_rpc_url().trim_end_matches('/').to_string()); - } - let url = config.core_rpc_url().to_string() + ":" + &config.core_rpc_port().to_string(); - let username = config.core_rpc_user(); - let password = config.core_rpc_pass(); - // Channel for sending new blocks to the Bitcoin node - let (new_block_sender, new_block_receiver): (Sender, Receiver) = - bounded(10); - let url = Uri::from_str(&url.clone()).expect("Invalid core rpc url"); - // Shared mempool instance - let mempool = Arc::new(Mutex::new(mempool::JDsMempool::new( - url, - username.to_string(), - password.to_string(), - new_block_receiver, - ))); - let mempool_update_interval = config.mempool_update_interval(); - let mempool_cloned_ = mempool.clone(); - let mempool_cloned_1 = mempool.clone(); - // Pre-flight check: can we reach the RPC node - if let Err(e) = mempool::JDsMempool::health(mempool_cloned_1.clone()).await { - error!("JDS Connection with bitcoin core failed {:?}", e); - return Err(JdsError::MempoolError(e)); - } - let (status_tx, status_rx) = unbounded(); - let sender = status::Sender::Downstream(status_tx.clone()); - let mut last_empty_mempool_warning = - std::time::Instant::now().sub(std::time::Duration::from_secs(60)); - - let sender_update_mempool = sender.clone(); - // ========== Task: Periodically update the mempool via RPC ========== // - task::spawn(async move { - loop { - let update_mempool_result: Result<(), mempool::error::JdsMempoolError> = - mempool::JDsMempool::update_mempool(mempool_cloned_.clone()).await; - if let Err(err) = update_mempool_result { - match err { - JdsMempoolError::EmptyMempool => { - if last_empty_mempool_warning.elapsed().as_secs() >= 60 { - warn!("{:?}", err); - warn!("Template Provider is running, but its mempool is empty (possible reasons: you're testing in testnet, signet, or regtest)"); - last_empty_mempool_warning = std::time::Instant::now(); - } - } - JdsMempoolError::NoClient => { - mempool::error::handle_error(&err); - handle_result!(sender_update_mempool, Err(err)); - } - JdsMempoolError::Rpc(_) => { - mempool::error::handle_error(&err); - handle_result!(sender_update_mempool, Err(err)); - } - JdsMempoolError::PoisonLock(_) => { - mempool::error::handle_error(&err); - handle_result!(sender_update_mempool, Err(err)); - } - } - } - tokio::time::sleep(mempool_update_interval).await; - // DO NOT REMOVE THIS LINE - //let _transactions = - // mempool::JDsMempool::_get_transaction_list(mempool_cloned_.clone()); - } - }); - - // ========== Task: Listen for SubmitSolution events ========== // - let mempool_cloned = mempool.clone(); - let sender_submit_solution = sender.clone(); - task::spawn(async move { - loop { - let result = mempool::JDsMempool::on_submit(mempool_cloned.clone()).await; - if let Err(err) = result { - match err { - JdsMempoolError::EmptyMempool => { - if last_empty_mempool_warning.elapsed().as_secs() >= 60 { - warn!("{:?}", err); - warn!("Template Provider is running, but its mempool is empty (possible reasons: you're testing in testnet, signet, or regtest)"); - last_empty_mempool_warning = std::time::Instant::now(); - } - } - _ => { - // TODO here there should be a better error managmenet - mempool::error::handle_error(&err); - handle_result!(sender_submit_solution, Err(err)); - } - } - } - } - }); - - // ========== Task: Launch Job Declarator server ========== // - let cloned = config.clone(); - let mempool_cloned = mempool.clone(); - let (sender_add_txs_to_mempool, receiver_add_txs_to_mempool) = unbounded(); - task::spawn(async move { - JobDeclarator::start( - cloned, - sender, - mempool_cloned, - new_block_sender, - sender_add_txs_to_mempool, - ) - .await - }); - - // ========== Task: Add transactions to mempool when received ========== // - task::spawn(async move { - loop { - if let Ok(add_transactions_to_mempool) = receiver_add_txs_to_mempool.recv().await { - let mempool_cloned = mempool.clone(); - task::spawn(async move { - match mempool::JDsMempool::add_tx_data_to_mempool( - mempool_cloned, - add_transactions_to_mempool, - ) - .await - { - Ok(_) => (), - Err(err) => { - // TODO - // here there should be a better error management - mempool::error::handle_error(&err); - } - } - }); - } - } - }); - - // ========== Central Runtime Loop: Shutdown and Error Reactions ========== // - loop { - let task_status = select! { - task_status = status_rx.recv() => task_status, - interrupt_signal = tokio::signal::ctrl_c() => { - match interrupt_signal { - Ok(()) => { - info!("Interrupt received"); - }, - Err(err) => { - error!("Unable to listen for interrupt signal: {}", err); - // we also shut down in case of error - }, - } - break; - } - }; - let task_status: status::Status = task_status.unwrap(); - - match task_status.state { - // Should only be sent by the downstream listener - status::State::DownstreamShutdown(err) => { - error!( - "SHUTDOWN from Downstream: {}\nTry to restart the downstream listener", - err - ); - } - status::State::TemplateProviderShutdown(err) => { - error!("SHUTDOWN from Upstream: {}\nTry to reconnecting or connecting to a new upstream", err); - break; - } - status::State::Healthy(msg) => { - info!("HEALTHY message: {}", msg); - } - status::State::DownstreamInstanceDropped(downstream_id) => { - warn!("Dropping downstream instance {} from jds", downstream_id); - } - } - } - Ok(()) - } -} diff --git a/roles/jd-server/src/lib/status.rs b/roles/jd-server/src/lib/status.rs deleted file mode 100644 index 39b17fc2f6..0000000000 --- a/roles/jd-server/src/lib/status.rs +++ /dev/null @@ -1,451 +0,0 @@ -//! ## Status Reporting System for JDS -//! -//! This module defines how internal components of the Job Declarator Server (JDS) report -//! health, errors, and shutdown conditions back to the main runtime loop in `lib/mod.rs`. -//! -//! At the core, tasks send a [`Status`] (wrapping a [`State`]) through a channel, -//! which is tagged with a [`Sender`] enum to indicate the origin of the message. -//! -//! This allows for centralized, consistent error handling across the application. - -use error_handling; -use parsers_sv2::Mining; - -use super::error::JdsError; - -/// Identifies the component that originated a [`Status`] update. -/// -/// Each sender is associated with a dedicated side of the status channel. -/// This lets the central loop distinguish between errors from different parts of the system. -#[derive(Debug)] -pub enum Sender { - /// Downstream task (e.g. per-client connection handler) - Downstream(async_channel::Sender), - /// Listener for incoming downstream connections - DownstreamListener(async_channel::Sender), - /// Template Provider (Bitcoin Core RPC) - Upstream(async_channel::Sender), -} - -impl Clone for Sender { - fn clone(&self) -> Self { - match self { - Self::Downstream(inner) => Self::Downstream(inner.clone()), - Self::DownstreamListener(inner) => Self::DownstreamListener(inner.clone()), - Self::Upstream(inner) => Self::Upstream(inner.clone()), - } - } -} - -/// The kind of event or status being reported by a task. -#[derive(Debug)] -pub enum State { - /// A downstream component (e.g. client) failed and should be shut down. - DownstreamShutdown(JdsError), - /// The Template Provider (upstream Bitcoin Core) failed. - TemplateProviderShutdown(JdsError), - /// A specific downstream instance was dropped (e.g., due to protocol error). - DownstreamInstanceDropped(u32), - /// A generic message to indicate health or non-critical errors. - Healthy(String), -} - -/// Wraps a status update, to be passed through a status channel. -#[derive(Debug)] -pub struct Status { - pub state: State, -} - -/// Sends a [`Status`] message tagged with its [`Sender`] to the central loop. -/// -/// This is the core logic used to determine which status variant should be sent -/// based on the error type and sender context. -async fn send_status( - sender: &Sender, - e: JdsError, - outcome: error_handling::ErrorBranch, -) -> error_handling::ErrorBranch { - match sender { - Sender::Downstream(tx) => match e { - JdsError::Sv2ProtocolError((id, Mining::OpenMiningChannelError(_))) => { - tx.send(Status { - state: State::DownstreamInstanceDropped(id), - }) - .await - .unwrap_or(()); - } - JdsError::ChannelRecv(_) => { - tx.send(Status { - state: State::DownstreamShutdown(e), - }) - .await - .unwrap_or(()); - } - JdsError::MempoolError(_) => { - tx.send(Status { - state: State::TemplateProviderShutdown(e), - }) - .await - .unwrap_or(()); - } - _ => { - let string_err = e.to_string(); - tx.send(Status { - state: State::Healthy(string_err), - }) - .await - .unwrap_or(()); - } - }, - Sender::DownstreamListener(tx) => { - tx.send(Status { - state: State::DownstreamShutdown(e), - }) - .await - .unwrap_or(()); - } - Sender::Upstream(tx) => { - tx.send(Status { - state: State::TemplateProviderShutdown(e), - }) - .await - .unwrap_or(()); - } - } - outcome -} - -/// Centralized error dispatcher for the JDS. -/// -/// Used by the `handle_result!` macro across the codebase. -/// Decides whether the task should `Continue` or `Break` based on the error type and source. -pub async fn handle_error(sender: &Sender, e: JdsError) -> error_handling::ErrorBranch { - tracing::debug!("Error: {:?}", &e); - match e { - JdsError::Io(_) => send_status(sender, e, error_handling::ErrorBranch::Break).await, - JdsError::ChannelSend(_) => { - //This should be a continue because if we fail to send to 1 downstream we should - // continue processing the other downstreams in the loop we are in. - // Otherwise if a downstream fails to send to then subsequent downstreams in - // the map won't get send called on them - send_status(sender, e, error_handling::ErrorBranch::Continue).await - } - JdsError::ChannelRecv(_) => { - send_status(sender, e, error_handling::ErrorBranch::Break).await - } - JdsError::BinarySv2(_) => send_status(sender, e, error_handling::ErrorBranch::Break).await, - JdsError::Codec(_) => send_status(sender, e, error_handling::ErrorBranch::Break).await, - JdsError::Noise(_) => send_status(sender, e, error_handling::ErrorBranch::Continue).await, - JdsError::RolesLogic(_) => send_status(sender, e, error_handling::ErrorBranch::Break).await, - JdsError::Custom(_) => send_status(sender, e, error_handling::ErrorBranch::Break).await, - JdsError::Framing(_) => send_status(sender, e, error_handling::ErrorBranch::Break).await, - JdsError::PoisonLock(_) => send_status(sender, e, error_handling::ErrorBranch::Break).await, - JdsError::Sv2ProtocolError(_) => { - send_status(sender, e, error_handling::ErrorBranch::Break).await - } - JdsError::MempoolError(_) => { - send_status(sender, e, error_handling::ErrorBranch::Break).await - } - JdsError::ImpossibleToReconstructBlock(_) => { - send_status(sender, e, error_handling::ErrorBranch::Continue).await - } - JdsError::NoLastDeclaredJob => { - send_status(sender, e, error_handling::ErrorBranch::Continue).await - } - JdsError::InvalidRPCUrl => send_status(sender, e, error_handling::ErrorBranch::Break).await, - JdsError::BadCliArgs => send_status(sender, e, error_handling::ErrorBranch::Break).await, - JdsError::InvalidPrevHash => { - send_status(sender, e, error_handling::ErrorBranch::Break).await - } - JdsError::InvalidCoinbase => { - send_status(sender, e, error_handling::ErrorBranch::Break).await - } - JdsError::InvalidMerkleRoot => { - send_status(sender, e, error_handling::ErrorBranch::Break).await - } - } -} - -#[cfg(test)] -mod tests { - use std::{convert::TryInto, io::Error}; - - use super::*; - use async_channel::{bounded, RecvError}; - use binary_sv2; - use codec_sv2; - use framing_sv2; - use mining_sv2::OpenMiningChannelError; - use noise_sv2; - use roles_logic_sv2; - - #[tokio::test] - async fn test_send_status_downstream_listener_shutdown() { - let (tx, rx) = bounded(1); - let sender = Sender::DownstreamListener(tx); - let error = JdsError::ChannelRecv(async_channel::RecvError); - - send_status(&sender, error, error_handling::ErrorBranch::Continue).await; - match rx.recv().await { - Ok(status) => match status.state { - State::DownstreamShutdown(e) => { - assert_eq!(e.to_string(), "Channel recv failed: `RecvError`") - } - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_send_status_upstream_shutdown() { - let (tx, rx) = bounded(1); - let sender = Sender::Upstream(tx); - let error = JdsError::MempoolError(crate::mempool::error::JdsMempoolError::EmptyMempool); - let error_string = error.to_string(); - send_status(&sender, error, error_handling::ErrorBranch::Continue).await; - - match rx.recv().await { - Ok(status) => match status.state { - State::TemplateProviderShutdown(e) => assert_eq!(e.to_string(), error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_handle_error_io_error() { - let (tx, rx) = bounded(1); - let sender = Sender::Downstream(tx); - let error = JdsError::Io(Error::new(std::io::ErrorKind::Interrupted, "IO error")); - let error_string = error.to_string(); - - handle_error(&sender, error).await; - match rx.recv().await { - Ok(status) => match status.state { - State::Healthy(e) => assert_eq!(e, error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_handle_error_channel_send_error() { - let (tx, rx) = bounded(1); - let sender = Sender::Downstream(tx); - let error = JdsError::ChannelSend(Box::new("error")); - let error_string = error.to_string(); - - handle_error(&sender, error).await; - match rx.recv().await { - Ok(status) => match status.state { - State::Healthy(e) => assert_eq!(e, error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_handle_error_channel_receive_error() { - let (tx, rx) = bounded(1); - let sender = Sender::Downstream(tx); - let error = JdsError::ChannelRecv(RecvError); - let error_string = error.to_string(); - - handle_error(&sender, error).await; - match rx.recv().await { - Ok(status) => match status.state { - State::DownstreamShutdown(e) => assert_eq!(e.to_string(), error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_handle_error_binary_sv2_error() { - let (tx, rx) = bounded(1); - let sender = Sender::Downstream(tx); - let error = JdsError::BinarySv2(binary_sv2::Error::IoError); - let error_string = error.to_string(); - handle_error(&sender, error).await; - match rx.recv().await { - Ok(status) => match status.state { - State::Healthy(e) => assert_eq!(e, error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_handle_error_codec_error() { - let (tx, rx) = bounded(1); - let sender = Sender::Downstream(tx); - let error = JdsError::Codec(codec_sv2::Error::InvalidStepForInitiator); - let error_string = error.to_string(); - handle_error(&sender, error).await; - match rx.recv().await { - Ok(status) => match status.state { - State::Healthy(e) => assert_eq!(e, error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_handle_error_noise_error() { - let (tx, rx) = bounded(1); - let sender = Sender::Downstream(tx); - let error = JdsError::Noise(noise_sv2::Error::HandshakeNotFinalized); - let error_string = error.to_string(); - handle_error(&sender, error).await; - match rx.recv().await { - Ok(status) => match status.state { - State::Healthy(e) => assert_eq!(e, error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_handle_error_roles_logic_error() { - let (tx, rx) = bounded(1); - let sender = Sender::Downstream(tx); - let error = JdsError::RolesLogic(roles_logic_sv2::Error::BadPayloadSize); - let error_string = error.to_string(); - handle_error(&sender, error).await; - match rx.recv().await { - Ok(status) => match status.state { - State::Healthy(e) => assert_eq!(e, error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_handle_error_custom_error() { - let (tx, rx) = bounded(1); - let sender = Sender::Downstream(tx); - let error = JdsError::Custom("error".to_string()); - let error_string = error.to_string(); - handle_error(&sender, error).await; - match rx.recv().await { - Ok(status) => match status.state { - State::Healthy(e) => assert_eq!(e, error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_handle_error_framing_error() { - let (tx, rx) = bounded(1); - let sender = Sender::Downstream(tx); - let error = JdsError::Framing(framing_sv2::Error::ExpectedHandshakeFrame); - let error_string = error.to_string(); - handle_error(&sender, error).await; - match rx.recv().await { - Ok(status) => match status.state { - State::Healthy(e) => assert_eq!(e, error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_handle_error_poison_lock_error() { - let (tx, rx) = bounded(1); - let sender = Sender::Downstream(tx); - let error = JdsError::PoisonLock("error".to_string()); - let error_string = error.to_string(); - handle_error(&sender, error).await; - match rx.recv().await { - Ok(status) => match status.state { - State::Healthy(e) => assert_eq!(e, error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_handle_error_impossible_to_reconstruct_block_error() { - let (tx, rx) = bounded(1); - let sender = Sender::Downstream(tx); - let error = JdsError::ImpossibleToReconstructBlock("Impossible".to_string()); - let error_string = error.to_string(); - handle_error(&sender, error).await; - match rx.recv().await { - Ok(status) => match status.state { - State::Healthy(e) => assert_eq!(e, error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_handle_error_no_last_declared_job_error() { - let (tx, rx) = bounded(1); - let sender = Sender::Downstream(tx); - let error = JdsError::NoLastDeclaredJob; - let error_string = error.to_string(); - handle_error(&sender, error).await; - match rx.recv().await { - Ok(status) => match status.state { - State::Healthy(e) => assert_eq!(e, error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_handle_error_last_mempool_error() { - let (tx, rx) = bounded(1); - let sender = Sender::Downstream(tx); - let error = JdsError::MempoolError(crate::mempool::error::JdsMempoolError::EmptyMempool); - let error_string = error.to_string(); - handle_error(&sender, error).await; - match rx.recv().await { - Ok(status) => match status.state { - State::TemplateProviderShutdown(e) => assert_eq!(e.to_string(), error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } - - #[tokio::test] - async fn test_handle_error_sv2_protocol_error() { - let (tx, rx) = bounded(1); - let sender = Sender::Downstream(tx); - let inner: [u8; 32] = rand::random(); - let value = inner.to_vec().try_into().unwrap(); - let error = JdsError::Sv2ProtocolError(( - 12, - Mining::OpenMiningChannelError(OpenMiningChannelError { - request_id: 1, - error_code: value, - }), - )); - let error_string = "12"; - handle_error(&sender, error).await; - match rx.recv().await { - Ok(status) => match status.state { - State::DownstreamInstanceDropped(e) => assert_eq!(e.to_string(), error_string), - _ => panic!("Unexpected state received"), - }, - Err(_) => panic!("Failed to receive status"), - } - } -} diff --git a/roles/jd-server/src/main.rs b/roles/jd-server/src/main.rs deleted file mode 100644 index 762a6e5808..0000000000 --- a/roles/jd-server/src/main.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Entry point for the Job Declarator Server (JDS). -//! -//! This binary parses CLI arguments, loads the TOML configuration file, and -//! starts the main runtime defined in `jd_server::JobDeclaratorServer`. -//! -//! The actual task orchestration and shutdown logic are managed in `lib/mod.rs`. -mod args; -use args::process_cli_args; -use jd_server::JobDeclaratorServer; -use stratum_apps::config_helpers::logging::init_logging; -use tracing::error; - -/// Entrypoint for the Job Declarator Server binary. -/// -/// Loads the configuration from TOML and initializes the main runtime -/// defined in `jd_server::JobDeclaratorServer`. Errors during startup are logged. -#[tokio::main] -async fn main() { - let config = match process_cli_args() { - Ok(cfg) => cfg, - Err(e) => { - error!("Failed to process CLI arguments: {}", e); - return; - } - }; - init_logging(config.log_file()); - let _ = JobDeclaratorServer::new(config).start().await; -} diff --git a/roles/pool/Cargo.toml b/roles/pool/Cargo.toml deleted file mode 100644 index dffd724e02..0000000000 --- a/roles/pool/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "pool_sv2" -version = "0.2.0" -authors = ["The Stratum V2 Developers"] -edition = "2021" -description = "SV2 pool role" -documentation = "https://docs.rs/pool_sv2" -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 = "pool_sv2" -path = "src/lib/mod.rs" - -[dependencies] -stratum-apps = { path = "../stratum-apps", features = ["pool"] } -async-channel = "1.5.1" -rand = "0.8.4" -serde = { version = "1.0.89", features = ["derive", "alloc"], default-features = false } -secp256k1 = { version = "0.28.2", default-features = false, features = ["alloc", "rand", "rand-std"] } -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/pool/README.md b/roles/pool/README.md deleted file mode 100644 index 1e608b80d4..0000000000 --- a/roles/pool/README.md +++ /dev/null @@ -1,54 +0,0 @@ - -# SRI Pool - -SRI Pool is designed to communicate with Downstream role (most typically a Translator Proxy or a Mining Proxy) running SV2 protocol to exploit features introduced by its sub-protocols. - -The most typical high level configuration is: - -``` -<--- Most Downstream ----------------------------------------- Most Upstream ---> - -+---------------------------------------------------+ +------------------------+ -| Mining Farm | | Remote Pool | -| | | | -| +-------------------+ +------------------+ | | +-----------------+ | -| | SV1 Mining Device | <-> | Translator Proxy | <------> | SV2 Pool Server | | -| +-------------------+ +------------------+ | | +-----------------+ | -| | | | -+---------------------------------------------------+ +------------------------+ - -``` - -## Setup - -### Configuration File - -`pool-config-hosted-tp-example.toml` and `pool-config-local-tp-example.toml` are examples of configuration files. - -The configuration file contains the following information: - -1. The SRI Pool information which includes the SRI Pool authority public key - (`authority_public_key`), the SRI Pool authority secret key (`authority_secret_key`). -2. The address which it will use to listen to new connection from downstream roles (`listen_address`) -3. The list of uncompressed pubkeys for coinbase payout (`coinbase_outputs`) -4. A string that serves as signature on the coinbase tx (`pool_signature`). -5. The Template Provider address (`tp_address`). -6. Optionally, you may want to verify that your TP connection is authentic. You may get `tp_authority_public_key` from the logs of your TP, for example: - -``` -# 2024-02-13T14:59:24Z Template Provider authority key: EguTM8URcZDQVeEBsM4B5vg9weqEUnufA8pm85fG4bZd -``` - -### Run - -There are two files found in `roles/pool/config-examples` - -1. `pool-config-hosted-tp-example.toml` runs on our community hosted server. -2. `pool-config-example-tp-example.toml` runs with your local config. - -Run the Pool: - -```bash -cd roles/pool/config-examples -cargo run -- -c pool-config-hosted-tp-example.toml -``` diff --git a/roles/pool/config-examples/pool-config-hosted-tp-example.toml b/roles/pool/config-examples/pool-config-hosted-tp-example.toml deleted file mode 100644 index cb9747e9fb..0000000000 --- a/roles/pool/config-examples/pool-config-hosted-tp-example.toml +++ /dev/null @@ -1,32 +0,0 @@ -# SRI Pool config -authority_public_key = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" -authority_secret_key = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n" -cert_validity_sec = 3600 -test_only_listen_adress_plain = "0.0.0.0:34250" -listen_address = "0.0.0.0:34254" - -# 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)" - -# Server Id (number to guarantee unique search space allocation across different Pool servers) -server_id = 1 - -# Pool signature (string to be included in coinbase tx) -pool_signature = "Stratum V2 SRI Pool" - -# 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 = "./pool.log" - -# 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" -shares_per_minute = 6.0 -share_batch_size = 10 \ No newline at end of file diff --git a/roles/pool/config-examples/pool-config-local-tp-example.toml b/roles/pool/config-examples/pool-config-local-tp-example.toml deleted file mode 100644 index 000e3e0fd2..0000000000 --- a/roles/pool/config-examples/pool-config-local-tp-example.toml +++ /dev/null @@ -1,30 +0,0 @@ -# SRI Pool config -authority_public_key = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" -authority_secret_key = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n" -cert_validity_sec = 3600 -test_only_listen_adress_plain = "0.0.0.0:34250" -listen_address = "0.0.0.0:34254" - -# 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)" - -# Server Id (number to guarantee unique search space allocation across different Pool servers) -server_id = 1 - -# Pool signature (string to be included in coinbase tx) -pool_signature = "Stratum V2 SRI Pool" - -# 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 = "./pool.log" - - -# 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" -shares_per_minute = 6.0 -share_batch_size = 10 \ No newline at end of file diff --git a/roles/pool/src/args.rs b/roles/pool/src/args.rs deleted file mode 100644 index c2c2186b34..0000000000 --- a/roles/pool/src/args.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! CLI argument parsing for the Pool binary. -//! -//! Defines the `Args` struct and a function to process CLI arguments into a PoolConfig. - -use clap::Parser; -use ext_config::{Config, File, FileFormat}; -use pool_sv2::config::PoolConfig; -use std::path::PathBuf; - -/// Holds the parsed CLI arguments for the Pool binary. -#[derive(Parser, Debug)] -#[command(author, version, about = "Pool CLI", long_about = None)] -pub struct Args { - #[arg( - short = 'c', - long = "config", - help = "Path to the TOML configuration file", - default_value = "pool-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, -} - -/// Parses CLI arguments and loads the PoolConfig from the specified file. -pub fn process_cli_args() -> PoolConfig { - let args = Args::parse(); - let config_path = args.config_path.to_str().expect("Invalid config path"); - let mut config: PoolConfig = Config::builder() - .add_source(File::new(config_path, FileFormat::Toml)) - .build() - .and_then(|settings| settings.try_deserialize::()) - .expect("Failed to load or deserialize config"); - - config.set_log_dir(args.log_file); - - config -} diff --git a/roles/pool/src/lib/channel_manager/mining_message_handler.rs b/roles/pool/src/lib/channel_manager/mining_message_handler.rs deleted file mode 100644 index d3b713dec0..0000000000 --- a/roles/pool/src/lib/channel_manager/mining_message_handler.rs +++ /dev/null @@ -1,1030 +0,0 @@ -use std::sync::atomic::Ordering; - -use stratum_apps::stratum_core::{ - binary_sv2::Str0255, - bitcoin::{consensus::Decodable, Amount, Target, TxOut}, - channels_sv2::{ - server::{ - error::{ExtendedChannelError, StandardChannelError}, - extended::ExtendedChannel, - group::GroupChannel, - jobs::job_store::DefaultJobStore, - share_accounting::{ShareValidationError, ShareValidationResult}, - standard::StandardChannel, - }, - Vardiff, VardiffState, - }, - handlers_sv2::{HandleMiningMessagesFromClientAsync, SupportedChannelTypes}, - mining_sv2::*, - parsers_sv2::{Mining, TemplateDistribution}, - template_distribution_sv2::SubmitSolution, -}; -use tracing::{error, info}; - -use crate::{ - channel_manager::{ChannelManager, RouteMessageTo, FULL_EXTRANONCE_SIZE}, - error::PoolError, -}; - -impl HandleMiningMessagesFromClientAsync for ChannelManager { - type Error = PoolError; - - 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 { - true - } - - fn is_client_authorized( - &self, - _client_id: Option, - _user_identity: &Str0255, - ) -> Result { - Ok(true) - } - - async fn handle_close_channel( - &mut self, - client_id: Option, - msg: CloseChannel<'_>, - ) -> Result<(), Self::Error> { - info!("Received Close Channel: {msg}"); - let downstream_id = - client_id.expect("client_id must be present for downstream_id extraction"); - self.channel_manager_data - .super_safe_lock(|channel_manager_data| { - let Some(downstream) = channel_manager_data.downstream.get_mut(&downstream_id) - else { - return Err(PoolError::DownstreamNotFound(downstream_id)); - }; - - downstream - .downstream_data - .super_safe_lock(|downstream_data| { - downstream_data.standard_channels.remove(&msg.channel_id); - downstream_data.extended_channels.remove(&msg.channel_id); - }); - Ok(()) - }) - } - - 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_identity = msg.user_identity.as_utf8_or_hex(); - let downstream_id = - client_id.expect("client_id must be present for downstream_id extraction"); - - info!("Received OpenStandardMiningChannel: {}", msg); - - let messages = self.channel_manager_data.super_safe_lock(|channel_manager_data| { - let Some(downstream) = channel_manager_data.downstream.get_mut(&downstream_id) else { - return Err(PoolError::DownstreamIdNotFound); - }; - - if downstream.requires_custom_work.load(Ordering::SeqCst) { - error!("OpenStandardMiningChannel: Standard Channels are not supported for this connection"); - let open_standard_mining_channel_error = OpenMiningChannelError { - request_id, - error_code: "standard-channels-not-supported-for-custom-work" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - return Ok(vec![(downstream_id, Mining::OpenMiningChannelError(open_standard_mining_channel_error)).into()]); - } - - let Some(last_future_template) = channel_manager_data.last_future_template.clone() else { - return Err(PoolError::FutureTemplateNotPresent); - }; - - let Some(last_set_new_prev_hash_tdp) = channel_manager_data.last_new_prev_hash.clone() else { - return Err(PoolError::LastNewPrevhashNotFound); - }; - - - let pool_coinbase_output = TxOut { - value: Amount::from_sat(last_future_template.coinbase_tx_value_remaining), - script_pubkey: self.coinbase_reward_script.script_pubkey(), - }; - - downstream.downstream_data.super_safe_lock(|downstream_data| { - if !downstream.requires_standard_jobs.load(Ordering::SeqCst) && downstream_data.group_channels.is_none() { - let group_channel_id = downstream_data.channel_id_factory.fetch_add(1, Ordering::SeqCst); - let job_store = DefaultJobStore::new(); - - let mut group_channel = match GroupChannel::new_for_pool(group_channel_id as u32, job_store, FULL_EXTRANONCE_SIZE, self.pool_tag_string.clone()) { - Ok(channel) => channel, - Err(e) => { - error!(?e, "Failed to create group channel"); - return Err(PoolError::FailedToCreateGroupChannel(e)); - } - }; - group_channel.on_new_template(last_future_template.clone(), vec![pool_coinbase_output.clone()])?; - - group_channel.on_set_new_prev_hash(last_set_new_prev_hash_tdp.clone())?; - downstream_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 extranonce_prefix = channel_manager_data.extranonce_prefix_factory_standard.next_prefix_standard()?; - - let channel_id = downstream_data.channel_id_factory.fetch_add(1, Ordering::SeqCst); - let job_store = DefaultJobStore::new(); - - let mut standard_channel = match StandardChannel::new_for_pool(channel_id as u32, user_identity.to_string(), extranonce_prefix.to_vec(), requested_max_target, nominal_hash_rate, self.share_batch_size, self.shares_per_minute, job_store, self.pool_tag_string.clone()) { - Ok(channel) => channel, - Err(e) => match e { - StandardChannelError::InvalidNominalHashrate => { - error!("OpenMiningChannelError: invalid-nominal-hashrate"); - let open_standard_mining_channel_error = OpenMiningChannelError { - request_id, - error_code: "invalid-nominal-hashrate" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - return Ok(vec![(downstream_id, Mining::OpenMiningChannelError(open_standard_mining_channel_error)).into()]); - } - StandardChannelError::RequestedMaxTargetOutOfRange => { - error!("OpenMiningChannelError: max-target-out-of-range"); - let open_standard_mining_channel_error = OpenMiningChannelError { - request_id, - error_code: "max-target-out-of-range" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - return Ok(vec![(downstream_id, Mining::OpenMiningChannelError(open_standard_mining_channel_error)).into()]); - } - _ => { - error!("error in handle_open_standard_mining_channel: {:?}", e); - return Err(PoolError::ChannelErrorSender); - } - }, - }; - - let group_channel_id = downstream_data.group_channels.as_ref().map(|channel| channel.get_group_channel_id()).unwrap_or(0); - - let open_standard_mining_channel_success = OpenStandardMiningChannelSuccess { - request_id: msg.request_id, - channel_id: channel_id as u32, - 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(); - - let mut messages: Vec = Vec::new(); - - messages.push((downstream_id, Mining::OpenStandardMiningChannelSuccess(open_standard_mining_channel_success)).into()); - - let template_id = last_future_template.template_id; - - // create a future standard job based on the last future template - standard_channel.on_new_template(last_future_template, vec![pool_coinbase_output.clone()])?; - let future_standard_job_id = standard_channel - .get_future_template_to_job_id() - .get(&template_id) - .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_set_new_prev_hash_tdp.prev_hash.clone(); - let header_timestamp = last_set_new_prev_hash_tdp.header_timestamp; - let n_bits = last_set_new_prev_hash_tdp.n_bits; - let set_new_prev_hash_mining = SetNewPrevHash { - channel_id: channel_id as u32, - job_id: *future_standard_job_id, - prev_hash, - min_ntime: header_timestamp, - nbits: n_bits, - }; - - - standard_channel - .on_set_new_prev_hash(last_set_new_prev_hash_tdp.clone())?; - - messages.push((downstream_id, Mining::SetNewPrevHash(set_new_prev_hash_mining)).into()); - - downstream_data.standard_channels.insert(channel_id as u32, standard_channel); - if let Some(group_channel) = downstream_data.group_channels.as_mut() { - group_channel.add_standard_channel_id(channel_id as u32); - } - let vardiff = VardiffState::new()?; - channel_manager_data.vardiff.insert((downstream_id, channel_id as u32).into(), vardiff); - - Ok(messages) - }) - })?; - - for message in messages { - message.forward(&self.channel_manager_channel).await; - } - - Ok(()) - } - - async fn handle_open_extended_mining_channel( - &mut self, - client_id: Option, - msg: OpenExtendedMiningChannel<'_>, - ) -> Result<(), Self::Error> { - let request_id = msg.get_request_id_as_u32(); - let user_identity = msg.user_identity.as_utf8_or_hex(); - let downstream_id = - client_id.expect("client_id must be present for downstream_id extraction"); - info!("Received OpenExtendedMiningChannel: {}", msg); - - 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 messages = self - .channel_manager_data - .super_safe_lock(|channel_manager_data| { - let Some(downstream) = channel_manager_data.downstream.get_mut(&downstream_id) - else { - return Err(PoolError::DownstreamIdNotFound); - }; - downstream - .downstream_data - .super_safe_lock(|downstream_data| { - let mut messages: Vec = Vec::new(); - - let extranonce_prefix = match channel_manager_data - .extranonce_prefix_factory_extended - .next_prefix_extended(requested_min_rollable_extranonce_size.into()) - { - Ok(extranonce_prefix) => extranonce_prefix.to_vec(), - Err(_) => { - error!("OpenMiningChannelError: min-extranonce-size-too-large"); - let open_extended_mining_channel_error = OpenMiningChannelError { - request_id, - error_code: "min-extranonce-size-too-large" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - return Ok(vec![( - downstream_id, - Mining::OpenMiningChannelError( - open_extended_mining_channel_error, - ), - ) - .into()]); - } - }; - - let channel_id = downstream_data - .channel_id_factory - .fetch_add(1, Ordering::SeqCst); - let job_store = DefaultJobStore::new(); - - let mut extended_channel = match ExtendedChannel::new_for_pool( - channel_id as u32, - user_identity.to_string(), - extranonce_prefix, - requested_max_target, - nominal_hash_rate, - true, // version rolling always allowed - requested_min_rollable_extranonce_size, - self.share_batch_size, - self.shares_per_minute, - job_store, - self.pool_tag_string.clone(), - ) { - Ok(channel) => channel, - Err(e) => match e { - ExtendedChannelError::InvalidNominalHashrate => { - error!("OpenMiningChannelError: invalid-nominal-hashrate"); - let open_extended_mining_channel_error = - OpenMiningChannelError { - request_id, - error_code: "invalid-nominal-hashrate" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - return Ok(vec![( - downstream_id, - Mining::OpenMiningChannelError( - open_extended_mining_channel_error, - ), - ) - .into()]); - } - ExtendedChannelError::RequestedMaxTargetOutOfRange => { - error!("OpenMiningChannelError: max-target-out-of-range"); - let open_extended_mining_channel_error = - OpenMiningChannelError { - request_id, - error_code: "max-target-out-of-range" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - return Ok(vec![( - downstream_id, - Mining::OpenMiningChannelError( - open_extended_mining_channel_error, - ), - ) - .into()]); - } - ExtendedChannelError::RequestedMinExtranonceSizeTooLarge => { - error!("OpenMiningChannelError: min-extranonce-size-too-large"); - let open_extended_mining_channel_error = - OpenMiningChannelError { - request_id, - error_code: "min-extranonce-size-too-large" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - return Ok(vec![( - downstream_id, - Mining::OpenMiningChannelError( - open_extended_mining_channel_error, - ), - ) - .into()]); - } - e => { - error!("error in handle_open_extended_mining_channel: {:?}", e); - return Err(e)?; - } - }, - }; - - let open_extended_mining_channel_success = - OpenExtendedMiningChannelSuccess { - request_id, - channel_id: channel_id as u32, - target: extended_channel.get_target().to_le_bytes().into(), - extranonce_prefix: extended_channel - .get_extranonce_prefix() - .clone() - .try_into()?, - extranonce_size: extended_channel.get_rollable_extranonce_size(), - } - .into_static(); - info!("Sending OpenExtendedMiningChannel.Success (downstream_id: {downstream_id}): {open_extended_mining_channel_success}"); - - messages.push( - ( - downstream_id, - Mining::OpenExtendedMiningChannelSuccess( - open_extended_mining_channel_success, - ), - ) - .into(), - ); - - let Some(last_set_new_prev_hash_tdp) = - channel_manager_data.last_new_prev_hash.clone() - else { - return Err(PoolError::LastNewPrevhashNotFound); - }; - - let Some(last_future_template) = - channel_manager_data.last_future_template.clone() - else { - return Err(PoolError::FutureTemplateNotPresent); - }; - - // if the client requires custom work, we don't need to send any extended - // jobs so we just process the SetNewPrevHash - // message - if downstream.requires_custom_work.load(Ordering::SeqCst) { - extended_channel.on_set_new_prev_hash(last_set_new_prev_hash_tdp)?; - // if the client does not require custom work, we need to send the - // future extended job - // and the SetNewPrevHash message - } else { - let pool_coinbase_output = TxOut { - value: Amount::from_sat( - last_future_template.coinbase_tx_value_remaining, - ), - script_pubkey: self.coinbase_reward_script.script_pubkey(), - }; - - extended_channel.on_new_template( - last_future_template.clone(), - vec![pool_coinbase_output], - )?; - - let future_extended_job_id = extended_channel - .get_future_template_to_job_id() - .get(&last_future_template.template_id) - .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_set_new_prev_hash_tdp.prev_hash.clone(); - let header_timestamp = last_set_new_prev_hash_tdp.header_timestamp; - let n_bits = last_set_new_prev_hash_tdp.n_bits; - let set_new_prev_hash_mining = SetNewPrevHash { - channel_id: channel_id as u32, - job_id: *future_extended_job_id, - prev_hash, - min_ntime: header_timestamp, - nbits: n_bits, - }; - - extended_channel.on_set_new_prev_hash(last_set_new_prev_hash_tdp)?; - - messages.push( - ( - downstream_id, - Mining::SetNewPrevHash(set_new_prev_hash_mining), - ) - .into(), - ); - } - - downstream_data - .extended_channels - .insert(channel_id as u32, extended_channel); - let vardiff = VardiffState::new()?; - channel_manager_data - .vardiff - .insert((downstream_id, channel_id as u32).into(), vardiff); - - Ok(messages) - }) - })?; - - for message in messages { - message.forward(&self.channel_manager_channel).await; - } - - Ok(()) - } - - async fn handle_submit_shares_standard( - &mut self, - client_id: Option, - msg: SubmitSharesStandard, - ) -> Result<(), Self::Error> { - info!("Received SubmitSharesStandard: {msg}"); - let downstream_id = - client_id.expect("client_id must be present for downstream_id extraction"); - - let messages = self.channel_manager_data.super_safe_lock(|channel_manager_data| { - let channel_id = msg.channel_id; - - let Some(downstream) = channel_manager_data.downstream.get(&downstream_id) else { - return Err(PoolError::DownstreamNotFound(downstream_id)); - }; - - downstream.downstream_data.super_safe_lock(|downstream_data| { - let mut messages: Vec = Vec::new(); - let Some(standard_channel) = downstream_data.standard_channels.get_mut(&channel_id) else { - let submit_shares_error = SubmitSharesError { - channel_id, - sequence_number: msg.sequence_number, - error_code: "invalid-channel-id" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - error!("SubmitSharesError: downstream_id: {}, channel_id: {}, sequence_number: {}, error_code: invalid-channel-id โŒ", downstream_id, channel_id, msg.sequence_number); - return Ok(vec![(downstream_id, Mining::SubmitSharesError(submit_shares_error)).into()]); - }; - - let Some(vardiff) = channel_manager_data.vardiff.get_mut(&(downstream_id, channel_id).into()) else { - return Err(PoolError::VardiffNotFound(channel_id)); - }; - - let res = standard_channel.validate_share(msg.clone()); - vardiff.increment_shares_since_last_update(); - - - 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: {} โœ…", success); - messages.push((downstream_id, Mining::SubmitSharesSuccess(success)).into()); - } else { - let share_work = standard_channel.get_target().difficulty_float(); - info!( - "SubmitSharesStandard: valid share | downstream_id: {}, channel_id: {}, sequence_number: {}, share_hash: {}, share_work: {} โœ…", - downstream_id, channel_id, msg.sequence_number, share_hash, share_work - ); - } - - } - Ok(ShareValidationResult::BlockFound(share_hash, template_id, coinbase)) => { - info!("SubmitSharesStandard: ๐Ÿ’ฐ Block Found!!! ๐Ÿ’ฐ{share_hash}"); - // if we have a template id (i.e.: this was not a custom job) - // we can propagate the solution to the TP - 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).into()); - } - let share_accounting = standard_channel.get_share_accounting(); - 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_id, Mining::SubmitSharesSuccess(success)).into()); - } - Err(ShareValidationError::Invalid) => { - error!("SubmitSharesError: downstream_id: {}, channel_id: {}, sequence_number: {}, error_code: invalid-share โŒ", downstream_id, channel_id, msg.sequence_number); - let error = SubmitSharesError { - channel_id: msg.channel_id, - sequence_number: msg.sequence_number, - error_code: "invalid-share" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - - messages.push((downstream_id, Mining::SubmitSharesError(error)).into()); - } - Err(ShareValidationError::Stale) => { - error!("SubmitSharesError: downstream_id: {}, channel_id: {}, sequence_number: {}, error_code: stale-share โŒ", downstream_id, channel_id, msg.sequence_number); - let error = SubmitSharesError { - channel_id: msg.channel_id, - sequence_number: msg.sequence_number, - error_code: "stale-share" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::SubmitSharesError(error)).into()); - } - Err(ShareValidationError::InvalidJobId) => { - error!("SubmitSharesError: downstream_id: {}, channel_id: {}, sequence_number: {}, error_code: invalid-job-id โŒ", downstream_id, channel_id, msg.sequence_number); - let error = SubmitSharesError { - channel_id: msg.channel_id, - sequence_number: msg.sequence_number, - error_code: "invalid-job-id" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::SubmitSharesError(error)).into()); - } - Err(ShareValidationError::DoesNotMeetTarget) => { - error!("SubmitSharesError: downstream_id: {}, channel_id: {}, sequence_number: {}, error_code: difficulty-too-low โŒ", downstream_id, channel_id, msg.sequence_number); - let error = SubmitSharesError { - channel_id: msg.channel_id, - sequence_number: msg.sequence_number, - error_code: "difficulty-too-low" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::SubmitSharesError(error)).into()); - } - Err(ShareValidationError::DuplicateShare) => { - error!("SubmitSharesError: downstream_id: {}, channel_id: {}, sequence_number: {}, error_code: duplicate-share โŒ", downstream_id, channel_id, msg.sequence_number); - let error = SubmitSharesError { - channel_id: msg.channel_id, - sequence_number: msg.sequence_number, - error_code: "duplicate-share" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::SubmitSharesError(error)).into()); - } - Err(e) => { - return Err(e)?; - } - } - - Ok(messages) - }) - })?; - - for message in messages { - message.forward(&self.channel_manager_channel).await; - } - - Ok(()) - } - - async fn handle_submit_shares_extended( - &mut self, - client_id: Option, - msg: SubmitSharesExtended<'_>, - ) -> Result<(), Self::Error> { - info!("Received SubmitSharesExtended: {msg}"); - let downstream_id = - client_id.expect("client_id must be present for downstream_id extraction"); - let messages = self.channel_manager_data.super_safe_lock(|channel_manager_data| { - let channel_id = msg.channel_id; - let Some(downstream) = channel_manager_data.downstream.get(&downstream_id) else { - return Err(PoolError::DownstreamNotFound(downstream_id)); - }; - - downstream.downstream_data.super_safe_lock(|downstream_data| { - let mut messages: Vec = Vec::new(); - let Some(extended_channel) = downstream_data.extended_channels.get_mut(&channel_id) else { - let error = SubmitSharesError { - channel_id, - sequence_number: msg.sequence_number, - error_code: "invalid-channel-id" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - error!("SubmitSharesError: downstream_id: {}, channel_id: {}, sequence_number: {}, error_code: invalid-channel-id โŒ", downstream_id, channel_id, msg.sequence_number); - return Ok(vec![(downstream_id, Mining::SubmitSharesError(error)).into()]); - }; - - let Some(vardiff) = channel_manager_data.vardiff.get_mut(&(downstream_id, channel_id).into()) else { - return Err(PoolError::VardiffNotFound(channel_id)); - }; - - let res = extended_channel.validate_share(msg.clone()); - vardiff.increment_shares_since_last_update(); - - 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: {} โœ…", success); - messages.push((downstream_id, Mining::SubmitSharesSuccess(success)).into()); - } else { - let share_work = extended_channel.get_target().difficulty_float(); - info!( - "SubmitSharesExtended: valid share | downstream_id: {}, channel_id: {}, sequence_number: {}, share_hash: {}, share_work: {} โœ…", - downstream_id, channel_id, msg.sequence_number, share_hash, share_work - ); - } - } - Ok(ShareValidationResult::BlockFound(share_hash, template_id, coinbase)) => { - info!("SubmitSharesExtended: ๐Ÿ’ฐ Block Found!!! ๐Ÿ’ฐ{share_hash}"); - // if we have a template id (i.e.: this was not a custom job) - // we can propagate the solution to the TP - 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).into()); - } - let share_accounting = extended_channel.get_share_accounting(); - 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_id, Mining::SubmitSharesSuccess(success)).into()); - } - Err(ShareValidationError::Invalid) => { - error!("SubmitSharesError: downstream_id: {}, channel_id: {}, sequence_number: {}, error_code: invalid-share โŒ", downstream_id, channel_id, msg.sequence_number); - let error = SubmitSharesError { - channel_id: msg.channel_id, - sequence_number: msg.sequence_number, - error_code: "invalid-share" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::SubmitSharesError(error)).into()); - } - Err(ShareValidationError::Stale) => { - error!("SubmitSharesError: downstream_id: {}, channel_id: {}, sequence_number: {}, error_code: stale-share โŒ", downstream_id, channel_id, msg.sequence_number); - let error = SubmitSharesError { - channel_id: msg.channel_id, - sequence_number: msg.sequence_number, - error_code: "stale-share" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::SubmitSharesError(error)).into()); - } - Err(ShareValidationError::InvalidJobId) => { - error!("SubmitSharesError: downstream_id: {}, channel_id: {}, sequence_number: {}, error_code: invalid-job-id โŒ", downstream_id, channel_id, msg.sequence_number); - let error = SubmitSharesError { - channel_id: msg.channel_id, - sequence_number: msg.sequence_number, - error_code: "invalid-job-id" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::SubmitSharesError(error)).into()); - } - Err(ShareValidationError::DoesNotMeetTarget) => { - error!("SubmitSharesError: downstream_id: {}, channel_id: {}, sequence_number: {}, error_code: difficulty-too-low โŒ", downstream_id, channel_id, msg.sequence_number); - let error = SubmitSharesError { - channel_id: msg.channel_id, - sequence_number: msg.sequence_number, - error_code: "difficulty-too-low" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::SubmitSharesError(error)).into()); - } - Err(ShareValidationError::DuplicateShare) => { - error!("SubmitSharesError: downstream_id: {}, channel_id: {}, sequence_number: {}, error_code: duplicate-share โŒ", downstream_id, channel_id, msg.sequence_number); - let error = SubmitSharesError { - channel_id: msg.channel_id, - sequence_number: msg.sequence_number, - error_code: "duplicate-share" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::SubmitSharesError(error)).into()); - } - Err(ShareValidationError::BadExtranonceSize) => { - error!("SubmitSharesError: downstream_id: {}, channel_id: {}, sequence_number: {}, error_code: bad-extranonce-size โŒ", downstream_id, channel_id, msg.sequence_number); - let error = SubmitSharesError { - channel_id: msg.channel_id, - sequence_number: msg.sequence_number, - error_code: "bad-extranonce-size" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::SubmitSharesError(error)).into()); - } - Err(e) => { - return Err(e)?; - } - } - - Ok(messages) - }) - })?; - - for message in messages { - message.forward(&self.channel_manager_channel).await; - } - - Ok(()) - } - - async fn handle_update_channel( - &mut self, - client_id: Option, - msg: UpdateChannel<'_>, - ) -> Result<(), Self::Error> { - info!("Received: {}", msg); - - let downstream_id = - client_id.expect("client_id must be present for downstream_id extraction"); - - let messages: Vec = self.channel_manager_data.super_safe_lock(|channel_manager_data| { - let Some(downstream) = channel_manager_data.downstream.get(&downstream_id) else { - return Err(PoolError::DownstreamNotFound(downstream_id)); - }; - - downstream.downstream_data.super_safe_lock(|downstream_data| { - let mut messages = Vec::new(); - 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()); - - if let Some(standard_channel) = downstream_data.standard_channels.get_mut(&channel_id) { - let res = standard_channel - .update_channel(new_nominal_hash_rate, Some(requested_maximum_target)); - match res { - Ok(_) => {} - Err(e) => { - error!("UpdateChannelError: {:?}", e); - match e { - StandardChannelError::InvalidNominalHashrate => { - error!("UpdateChannelError: invalid-nominal-hashrate"); - let update_channel_error = UpdateChannelError { - channel_id, - error_code: "invalid-nominal-hashrate" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::UpdateChannelError(update_channel_error)).into()); - } - StandardChannelError::RequestedMaxTargetOutOfRange => { - error!("UpdateChannelError: requested-max-target-out-of-range"); - let update_channel_error = UpdateChannelError { - channel_id, - error_code: "requested-max-target-out-of-range" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::UpdateChannelError(update_channel_error)).into()); - } - standard_channel_error => { - return Err(standard_channel_error)?; - } - } - } - } - let new_target = standard_channel.get_target(); - let set_target = SetTarget { - channel_id, - maximum_target: new_target.to_le_bytes().into(), - }; - messages.push((downstream_id, Mining::SetTarget(set_target)).into()); - } else if let Some(extended_channel) = downstream_data.extended_channels.get_mut(&channel_id) { - let res = extended_channel - .update_channel(new_nominal_hash_rate, Some(requested_maximum_target)); - match res { - Ok(_) => {} - Err(e) => { - error!("UpdateChannelError: {:?}", e); - match e { - ExtendedChannelError::InvalidNominalHashrate => { - error!("UpdateChannelError: invalid-nominal-hashrate"); - let update_channel_error = UpdateChannelError { - channel_id, - error_code: "invalid-nominal-hashrate" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::UpdateChannelError(update_channel_error)).into()); - } - ExtendedChannelError::RequestedMaxTargetOutOfRange => { - error!("UpdateChannelError: max-target-out-of-range"); - let update_channel_error = UpdateChannelError { - channel_id, - error_code: "max-target-out-of-range" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::UpdateChannelError(update_channel_error)).into()); - } - extended_channel_error => { - return Err(extended_channel_error)?; - } - } - } - } - let new_target = extended_channel.get_target(); - let set_target = SetTarget { - channel_id, - maximum_target: new_target.to_le_bytes().into(), - }; - messages.push((downstream_id, Mining::SetTarget(set_target)).into()); - } else { - error!("UpdateChannelError: invalid-channel-id"); - let update_channel_error = UpdateChannelError { - channel_id, - error_code: "invalid-channel-id" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - messages.push((downstream_id, Mining::UpdateChannelError(update_channel_error)).into()); - } - - Ok(messages) - }) - })?; - - for message in messages { - message.forward(&self.channel_manager_channel).await; - } - - Ok(()) - } - - async fn handle_set_custom_mining_job( - &mut self, - client_id: Option, - msg: SetCustomMiningJob<'_>, - ) -> Result<(), Self::Error> { - info!("Received: {}", msg); - let downstream_id = - client_id.expect("client_id must be present for downstream_id extraction"); - - // this is a naive implementation, but ideally we should check the SetCustomMiningJob - // message parameters, especially: - // - the mining_job_token - // - the amount of the pool payout output - let custom_job_coinbase_outputs = Vec::::consensus_decode( - &mut msg.coinbase_tx_outputs.inner_as_ref().to_vec().as_slice(), - )?; - - let message: RouteMessageTo = - self.channel_manager_data - .super_safe_lock(|channel_manager_data| { - // check that the script_pubkey from self.coinbase_reward_script - // is present in the custom job coinbase outputs - let missing_script = !custom_job_coinbase_outputs.iter().any(|pool_output| { - *pool_output.script_pubkey == *self.coinbase_reward_script.script_pubkey() - }); - - if missing_script { - error!("SetCustomMiningJobError: pool-payout-script-missing"); - - let error = SetCustomMiningJobError { - request_id: msg.request_id, - channel_id: msg.channel_id, - error_code: "pool-payout-script-missing" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - - return Ok((downstream_id, Mining::SetCustomMiningJobError(error)).into()); - } - - let Some(downstream) = channel_manager_data.downstream.get_mut(&downstream_id) - else { - return Err(PoolError::DownstreamNotFound(downstream_id)); - }; - - downstream - .downstream_data - .super_safe_lock(|downstream_data| { - let Some(extended_channel) = - downstream_data.extended_channels.get_mut(&msg.channel_id) - else { - error!("SetCustomMiningJobError: invalid-channel-id"); - let error = SetCustomMiningJobError { - request_id: msg.request_id, - channel_id: msg.channel_id, - error_code: "invalid-channel-id" - .to_string() - .try_into() - .expect("error code must be valid string"), - }; - return Ok( - (downstream_id, Mining::SetCustomMiningJobError(error)).into() - ); - }; - - let job_id = extended_channel - .on_set_custom_mining_job(msg.clone().into_static())?; - - let success = SetCustomMiningJobSuccess { - channel_id: msg.channel_id, - request_id: msg.request_id, - job_id, - }; - Ok((downstream_id, Mining::SetCustomMiningJobSuccess(success)).into()) - }) - })?; - - message.forward(&self.channel_manager_channel).await; - Ok(()) - } -} diff --git a/roles/pool/src/lib/channel_manager/mod.rs b/roles/pool/src/lib/channel_manager/mod.rs deleted file mode 100644 index ea876f3c5b..0000000000 --- a/roles/pool/src/lib/channel_manager/mod.rs +++ /dev/null @@ -1,564 +0,0 @@ -use std::{ - collections::HashMap, - net::SocketAddr, - sync::{atomic::AtomicUsize, Arc}, -}; - -use async_channel::{Receiver, Sender}; -use core::sync::atomic::Ordering; -use stratum_apps::{ - config_helpers::CoinbaseRewardScript, - custom_mutex::Mutex, - key_utils::{Secp256k1PublicKey, Secp256k1SecretKey}, - network_helpers::noise_stream::NoiseTcpStream, - stratum_core::{ - channels_sv2::{ - server::{ - extended::ExtendedChannel, - jobs::{extended::ExtendedJob, job_store::DefaultJobStore, standard::StandardJob}, - standard::StandardChannel, - }, - Vardiff, VardiffState, - }, - codec_sv2::HandshakeRole, - handlers_sv2::{ - HandleMiningMessagesFromClientAsync, HandleTemplateDistributionMessagesFromServerAsync, - }, - mining_sv2::{ExtendedExtranonce, SetTarget}, - noise_sv2::Responder, - parsers_sv2::{Mining, TemplateDistribution}, - template_distribution_sv2::{NewTemplate, SetNewPrevHash}, - }, -}; -use tokio::{net::TcpListener, select, sync::broadcast}; -use tracing::{debug, error, info, warn}; - -use crate::{ - config::PoolConfig, - downstream::Downstream, - error::PoolResult, - status::{handle_error, Status, StatusSender}, - task_manager::TaskManager, - utils::{Message, ShutdownMessage, VardiffKey}, -}; - -mod mining_message_handler; -mod template_distribution_message_handler; - -const POOL_ALLOCATION_BYTES: usize = 4; -const CLIENT_SEARCH_SPACE_BYTES: usize = 8; -pub const FULL_EXTRANONCE_SIZE: usize = POOL_ALLOCATION_BYTES + CLIENT_SEARCH_SPACE_BYTES; - -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 assigns a unique ID to each new **downstream connection**. - downstream_id_factory: AtomicUsize, - // Mapping of `(downstream_id, channel_id)` โ†’ vardiff controller. - // Each entry manages variable difficulty for a specific downstream channel. - vardiff: HashMap, - // Coinbase outputs - coinbase_outputs: Vec, - // Last new prevhash - last_new_prev_hash: Option>, - // Last future template - last_future_template: Option>, -} - -#[derive(Clone)] -pub struct ChannelManagerChannel { - tp_sender: Sender>, - tp_receiver: Receiver>, - downstream_sender: broadcast::Sender<(usize, Mining<'static>)>, - downstream_receiver: Receiver<(usize, Mining<'static>)>, -} - -/// 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, - pool_tag_string: String, - share_batch_size: usize, - shares_per_minute: f32, - coinbase_reward_script: CoinbaseRewardScript, -} - -impl ChannelManager { - /// Constructor method used to instantiate the ChannelManager - #[allow(clippy::too_many_arguments)] - pub async fn new( - config: PoolConfig, - tp_sender: Sender>, - tp_receiver: Receiver>, - downstream_sender: broadcast::Sender<(usize, Mining<'static>)>, - downstream_receiver: Receiver<(usize, Mining<'static>)>, - coinbase_outputs: Vec, - ) -> PoolResult { - let range_0 = 0..0; - let range_1 = 0..POOL_ALLOCATION_BYTES; - let range_2 = POOL_ALLOCATION_BYTES..POOL_ALLOCATION_BYTES + CLIENT_SEARCH_SPACE_BYTES; - - let make_extranonce_factory = || { - // simulating a scenario where there are multiple mining servers - // this static prefix allows unique extranonce_prefix allocation - // for this mining server - let static_prefix = config.server_id().to_be_bytes().to_vec(); - - ExtendedExtranonce::new( - range_0.clone(), - range_1.clone(), - range_2.clone(), - Some(static_prefix), - ) - .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: AtomicUsize::new(1), - vardiff: HashMap::new(), - coinbase_outputs, - last_future_template: None, - last_new_prev_hash: None, - })); - - let channel_manager_channel = ChannelManagerChannel { - tp_sender, - tp_receiver, - downstream_sender, - downstream_receiver, - }; - - let channel_manager = ChannelManager { - channel_manager_data, - channel_manager_channel, - share_batch_size: config.share_batch_size(), - shares_per_minute: config.shares_per_minute(), - pool_tag_string: config.pool_signature().to_string(), - coinbase_reward_script: config.coinbase_reward_script().clone(), - }; - - 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<(usize, Mining<'static>)>, - channel_manager_receiver: broadcast::Sender<(usize, Mining<'static>)>, - ) -> PoolResult<()> { - 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; - } - 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, - 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::SeqCst)); - - - 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( - self, - notify_shutdown: broadcast::Sender, - status_sender: Sender, - task_manager: Arc, - ) -> PoolResult<()> { - let status_sender = StatusSender::ChannelManager(status_sender); - let mut shutdown_rx = notify_shutdown.subscribe(); - - task_manager.spawn(async move { - let cm = self.clone(); - let vardiff_future = self.run_vardiff_loop(); - tokio::pin!(vardiff_future); - loop { - 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"); - } - } - Err(e) => { - warn!(error = ?e, "shutdown channel closed unexpectedly"); - break; - } - _ => {} - } - } - res = &mut vardiff_future => { - info!("Vardiff loop completed with: {res:?}"); - } - res = cm_template.handle_template_provider_message() => { - if let Err(e) = res { - error!(error = ?e, "Error handling Template Receiver message"); - handle_error(&status_sender, e).await; - break; - } - } - res = cm_downstreams.handle_downstream_mining_message() => { - if let Err(e) = res { - error!(error = ?e, "Error handling Downstreams message"); - handle_error(&status_sender, e).await; - break; - } - } - } - } - }); - Ok(()) - } - - // Removes a Downstream entry from the ChannelManagerโ€™s state. - // - // Given a `downstream_id`, this method: - // 1. Removes the corresponding Downstream from the `downstream` map. - #[allow(clippy::result_large_err)] - fn remove_downstream(&self, downstream_id: usize) -> PoolResult<()> { - self.channel_manager_data.super_safe_lock(|cm_data| { - cm_data.downstream.remove(&downstream_id); - }); - 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) -> PoolResult<()> { - if let Ok(message) = self.channel_manager_channel.tp_receiver.recv().await { - self.handle_template_distribution_message_from_server(None, message) - .await?; - } - Ok(()) - } - - async fn handle_downstream_mining_message(&mut self) -> PoolResult<()> { - if let Ok((downstream_id, message)) = self - .channel_manager_channel - .downstream_receiver - .recv() - .await - { - self.handle_mining_message_from_client(Some(downstream_id), message) - .await?; - } - - Ok(()) - } - - // Runs the vardiff on extended channel. - fn run_vardiff_on_extended_channel( - downstream_id: usize, - channel_id: u32, - channel_state: &mut 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: usize, - 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) -> PoolResult<()> { - 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) -> PoolResult<()> { - let mut messages: Vec = vec![]; - self.channel_manager_data - .super_safe_lock(|channel_manager_data| { - for (vardiff_key, vardiff_state) in channel_manager_data.vardiff.iter_mut() { - let downstream_id = &vardiff_key.downstream_id; - let channel_id = &vardiff_key.channel_id; - - 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, - ); - } - }); - } - }); - - for message in messages { - message.forward(&self.channel_manager_channel).await; - } - - info!("Vardiff update cycle complete"); - Ok(()) - } -} - -#[derive(Clone)] -pub enum RouteMessageTo<'a> { - /// Route to the template provider subsystem. - TemplateProvider(TemplateDistribution<'a>), - /// Route to a specific downstream client by ID, along with its mining message. - Downstream((usize, Mining<'a>)), -} - -impl<'a> From> for RouteMessageTo<'a> { - fn from(value: TemplateDistribution<'a>) -> Self { - Self::TemplateProvider(value) - } -} - -impl<'a> From<(usize, Mining<'a>)> for RouteMessageTo<'a> { - fn from(value: (usize, Mining<'a>)) -> Self { - Self::Downstream(value) - } -} - -impl RouteMessageTo<'_> { - pub async fn forward(self, channel_manager_channel: &ChannelManagerChannel) { - match self { - RouteMessageTo::Downstream((downstream_id, message)) => { - _ = channel_manager_channel - .downstream_sender - .send((downstream_id, message.into_static())); - } - RouteMessageTo::TemplateProvider(message) => { - _ = channel_manager_channel - .tp_sender - .send(message.into_static()) - .await; - } - } - } -} diff --git a/roles/pool/src/lib/channel_manager/template_distribution_message_handler.rs b/roles/pool/src/lib/channel_manager/template_distribution_message_handler.rs deleted file mode 100644 index 9327e5da6d..0000000000 --- a/roles/pool/src/lib/channel_manager/template_distribution_message_handler.rs +++ /dev/null @@ -1,306 +0,0 @@ -use std::sync::atomic::Ordering; - -use stratum_apps::stratum_core::{ - bitcoin::Amount, channels_sv2::outputs::deserialize_outputs, - handlers_sv2::HandleTemplateDistributionMessagesFromServerAsync, - mining_sv2::SetNewPrevHash as SetNewPrevHashMp, parsers_sv2::Mining, - template_distribution_sv2::*, -}; -use tracing::{info, warn}; - -use crate::{ - channel_manager::{ChannelManager, RouteMessageTo}, - error::PoolError, -}; - -impl HandleTemplateDistributionMessagesFromServerAsync for ChannelManager { - type Error = PoolError; - - async fn handle_new_template( - &mut self, - _server_id: Option, - msg: NewTemplate<'_>, - ) -> Result<(), Self::Error> { - info!("Received: {}", msg); - - let messages = self.channel_manager_data.super_safe_lock(|channel_manager_data| { - if msg.future_template { - channel_manager_data.last_future_template = Some(msg.clone().into_static()); - } - - let mut messages: Vec = Vec::new(); - let mut coinbase_output = deserialize_outputs(channel_manager_data.coinbase_outputs.clone()).expect("deserialization failed"); - coinbase_output[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_output.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 - }; - - 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_output.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"); - 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_output.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_output.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"); - - 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_output.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"); - 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_output.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_output.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"); - - 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 - }); - - for message in messages { - message.forward(&self.channel_manager_channel).await; - } - - Ok(()) - } - - async fn handle_request_tx_data_error( - &mut self, - _server_id: Option, - msg: RequestTransactionDataError<'_>, - ) -> Result<(), Self::Error> { - warn!("Received: {}", msg); - Ok(()) - } - - async fn handle_request_tx_data_success( - &mut self, - _server_id: Option, - msg: RequestTransactionDataSuccess<'_>, - ) -> Result<(), Self::Error> { - info!("Received: {}", msg); - Ok(()) - } - - async fn handle_set_new_prev_hash( - &mut self, - _server_id: Option, - msg: SetNewPrevHash<'_>, - ) -> Result<(), Self::Error> { - info!("Received: {}", msg); - - let messages = self.channel_manager_data.super_safe_lock(|data| { - data.last_new_prev_hash = Some(msg.clone().into_static()); - - let mut messages: Vec = vec![]; - - 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()) { - tracing::error!("Error while adding new prev hash to standard channel: {channel_id:?} {e:?}"); - 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()) { - tracing::error!("Error while adding new prev hash to extended channel: {channel_id:?} {e:?}"); - continue; - }; - - // don't send any SetNewPrevHash messages to Extended Channels - // if the downstream requires custom work - if downstream.requires_custom_work.load(Ordering::SeqCst) { - 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 - }); - - for message in messages { - message.forward(&self.channel_manager_channel).await; - } - - Ok(()) - } -} diff --git a/roles/pool/src/lib/config.rs b/roles/pool/src/lib/config.rs deleted file mode 100644 index 51e74317b4..0000000000 --- a/roles/pool/src/lib/config.rs +++ /dev/null @@ -1,199 +0,0 @@ -//! ## Configuration Module -//! -//! Defines [`PoolConfig`], the configuration structure for the Pool, along with its supporting -//! types. -//! -//! This module handles: -//! - Initializing [`PoolConfig`] -//! - Managing [`TemplateProviderConfig`], [`AuthorityConfig`], [`CoinbaseOutput`], and -//! [`ConnectionConfig`] -//! - Validating and converting coinbase outputs -use std::{ - net::SocketAddr, - path::{Path, PathBuf}, -}; - -use stratum_apps::{ - config_helpers::CoinbaseRewardScript, - key_utils::{Secp256k1PublicKey, Secp256k1SecretKey}, - stratum_core::bitcoin::{Amount, TxOut}, -}; - -/// Configuration for the Pool, including connection, authority, and coinbase settings. -#[derive(Clone, Debug, serde::Deserialize)] -pub struct PoolConfig { - listen_address: SocketAddr, - tp_address: String, - tp_authority_public_key: Option, - authority_public_key: Secp256k1PublicKey, - authority_secret_key: Secp256k1SecretKey, - cert_validity_sec: u64, - coinbase_reward_script: CoinbaseRewardScript, - pool_signature: String, - shares_per_minute: f32, - share_batch_size: usize, - log_file: Option, - server_id: u16, -} - -impl PoolConfig { - /// Creates a new instance of the [`PoolConfig`]. - /// - /// # Panics - /// - /// Panics if `coinbase_reward_script` is empty. - pub fn new( - pool_connection: ConnectionConfig, - template_provider: TemplateProviderConfig, - authority_config: AuthorityConfig, - coinbase_reward_script: CoinbaseRewardScript, - shares_per_minute: f32, - share_batch_size: usize, - server_id: u16, - ) -> Self { - Self { - listen_address: pool_connection.listen_address, - tp_address: template_provider.address, - tp_authority_public_key: template_provider.authority_public_key, - authority_public_key: authority_config.public_key, - authority_secret_key: authority_config.secret_key, - cert_validity_sec: pool_connection.cert_validity_sec, - coinbase_reward_script, - pool_signature: pool_connection.signature, - shares_per_minute, - share_batch_size, - log_file: None, - server_id, - } - } - - /// Returns the coinbase output. - pub fn coinbase_reward_script(&self) -> &CoinbaseRewardScript { - &self.coinbase_reward_script - } - - /// Returns Pool listenining address. - pub fn listen_address(&self) -> &SocketAddr { - &self.listen_address - } - - /// 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 the Pool signature. - pub fn pool_signature(&self) -> &String { - &self.pool_signature - } - - /// Return the Template Provider authority public key. - pub fn tp_authority_public_key(&self) -> Option<&Secp256k1PublicKey> { - self.tp_authority_public_key.as_ref() - } - - /// Returns the Template Provider address. - pub fn tp_address(&self) -> &String { - &self.tp_address - } - - /// Returns the share batch size. - pub fn share_batch_size(&self) -> usize { - self.share_batch_size - } - - /// Sets the coinbase output. - pub fn set_coinbase_reward_script(&mut self, coinbase_output: CoinbaseRewardScript) { - self.coinbase_reward_script = coinbase_output; - } - - /// Returns the shares per minute. - pub fn shares_per_minute(&self) -> f32 { - self.shares_per_minute - } - - /// Change TP address. - pub fn set_tp_address(&mut self, tp_address: String) { - self.tp_address = tp_address; - } - - /// Sets the log directory. - pub fn set_log_dir(&mut self, log_dir: Option) { - if let Some(dir) = log_dir { - self.log_file = Some(dir); - } - } - /// Returns the log directory. - pub fn log_dir(&self) -> Option<&Path> { - self.log_file.as_deref() - } - - /// Returns the server id. - pub fn server_id(&self) -> u16 { - self.server_id - } - - pub fn get_txout(&self) -> TxOut { - TxOut { - value: Amount::from_sat(0), - script_pubkey: self.coinbase_reward_script.script_pubkey().to_owned(), - } - } -} - -/// Configuration for connecting to a Template Provider. -pub struct TemplateProviderConfig { - address: String, - authority_public_key: Option, -} - -impl TemplateProviderConfig { - pub fn new(address: String, authority_public_key: Option) -> Self { - Self { - address, - authority_public_key, - } - } -} - -/// Pool's authority public and secret keys. -pub struct AuthorityConfig { - pub public_key: Secp256k1PublicKey, - pub secret_key: Secp256k1SecretKey, -} - -impl AuthorityConfig { - pub fn new(public_key: Secp256k1PublicKey, secret_key: Secp256k1SecretKey) -> Self { - Self { - public_key, - secret_key, - } - } -} - -/// Connection settings for the Pool listener. -pub struct ConnectionConfig { - listen_address: SocketAddr, - cert_validity_sec: u64, - signature: String, -} - -impl ConnectionConfig { - pub fn new(listen_address: SocketAddr, cert_validity_sec: u64, signature: String) -> Self { - Self { - listen_address, - cert_validity_sec, - signature, - } - } -} diff --git a/roles/pool/src/lib/downstream/common_message_handler.rs b/roles/pool/src/lib/downstream/common_message_handler.rs deleted file mode 100644 index 028ef8bef8..0000000000 --- a/roles/pool/src/lib/downstream/common_message_handler.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::{downstream::Downstream, error::PoolError, utils::StdFrame}; -use std::{convert::TryInto, sync::atomic::Ordering}; -use stratum_apps::stratum_core::{ - common_messages_sv2::{ - has_requires_std_job, has_work_selection, SetupConnection, SetupConnectionSuccess, - }, - handlers_sv2::HandleCommonMessagesFromClientAsync, - parsers_sv2::AnyMessage, -}; -use tracing::info; - -impl HandleCommonMessagesFromClientAsync for Downstream { - type Error = PoolError; - - async fn handle_setup_connection( - &mut self, - _client_id: Option, - msg: SetupConnection<'_>, - ) -> Result<(), Self::Error> { - info!( - "Received `SetupConnection`: version={}, flags={:b}", - msg.min_version, msg.flags - ); - - self.requires_custom_work - .store(has_work_selection(msg.flags), Ordering::SeqCst); - self.requires_standard_jobs - .store(has_requires_std_job(msg.flags), Ordering::SeqCst); - - 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/pool/src/lib/downstream/mod.rs b/roles/pool/src/lib/downstream/mod.rs deleted file mode 100644 index c16d06f87e..0000000000 --- a/roles/pool/src/lib/downstream/mod.rs +++ /dev/null @@ -1,283 +0,0 @@ -use std::{ - collections::HashMap, - sync::{ - atomic::{AtomicBool, AtomicUsize}, - 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, - noise_sv2::Error, - parsers_sv2::{AnyMessage, Mining}, - }, -}; -use tokio::sync::broadcast; -use tracing::{debug, error, warn}; - -use crate::{ - error::{PoolError, PoolResult}, - status::{handle_error, Status, StatusSender}, - task_manager::TaskManager, - utils::{ - protocol_message_type, spawn_io_tasks, Message, MessageType, SV2Frame, ShutdownMessage, - StdFrame, - }, -}; - -mod common_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 group_channels: Option>>>, - pub extended_channels: - HashMap>>>, - pub standard_channels: - HashMap>>>, - pub channel_id_factory: AtomicUsize, -} - -/// 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<(usize, Mining<'static>)>, - channel_manager_receiver: broadcast::Sender<(usize, Mining<'static>)>, - 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: usize, - pub requires_standard_jobs: Arc, - pub requires_custom_work: Arc, -} - -impl Downstream { - /// Creates a new [`Downstream`] instance and spawns the necessary I/O tasks. - pub fn new( - downstream_id: usize, - channel_manager_sender: Sender<(usize, Mining<'static>)>, - channel_manager_receiver: broadcast::Sender<(usize, Mining<'static>)>, - 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 { - extended_channels: HashMap::new(), - standard_channels: HashMap::new(), - group_channels: None, - channel_id_factory: AtomicUsize::new(1), - })); - Downstream { - downstream_channel, - downstream_data, - downstream_id, - requires_standard_jobs: Arc::new(AtomicBool::new(false)), - requires_custom_work: Arc::new(AtomicBool::new(false)), - } - } - - /// 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; - } - _ => {} - } - } - res = self_clone_1.handle_downstream_mining_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) -> PoolResult<()> { - 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(PoolError::UnexpectedMessage(0)); - }; - - // The first ever message received on a new downstream connection - // should always be a setup connection message. - if message_type == MESSAGE_TYPE_SETUP_CONNECTION { - self.handle_common_message_frame_from_client(None, message_type, frame.payload()) - .await?; - return Ok(()); - } - Err(PoolError::UnexpectedMessage(message_type)) - } - - // Handles messages sent from the channel manager to this downstream. - async fn handle_channel_manager_message( - self, - receiver: &mut broadcast::Receiver<(usize, Mining<'static>)>, - ) -> PoolResult<()> { - let (downstream_id, msg) = 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 = AnyMessage::Mining(msg); - let std_frame: StdFrame = message.try_into()?; - - self.downstream_channel - .downstream_sender - .send(std_frame) - .await - .map_err(|e| { - error!(?e, "Downstream send failed"); - PoolError::Noise(Error::ExpectedIncomingHandshakeMessage) - })?; - - Ok(()) - } - - // Handles incoming messages from the downstream peer. - async fn handle_downstream_mining_message(self) -> PoolResult<()> { - let mut 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(()); - } - - let mining = Mining::try_from((message_type, sv2_frame.payload()))?.into_static(); - - debug!("Received mining SV2 frame from downstream."); - self.downstream_channel - .channel_manager_sender - .send((self.downstream_id, mining)) - .await - .map_err(|e| { - error!(error=?e, "Failed to send mining message to channel manager."); - PoolError::ChannelErrorSender - })?; - - Ok(()) - } -} diff --git a/roles/pool/src/lib/error.rs b/roles/pool/src/lib/error.rs deleted file mode 100644 index 4c2b171e3c..0000000000 --- a/roles/pool/src/lib/error.rs +++ /dev/null @@ -1,267 +0,0 @@ -use std::{ - convert::From, - fmt::Debug, - sync::{MutexGuard, PoisonError}, -}; - -use stratum_apps::stratum_core::{ - binary_sv2, bitcoin, - channels_sv2::{ - server::{ - error::{ExtendedChannelError, GroupChannelError, StandardChannelError}, - share_accounting::ShareValidationError, - }, - vardiff::error::VardiffError, - }, - codec_sv2, framing_sv2, - handlers_sv2::HandlerErrorType, - mining_sv2::ExtendedExtranonceError, - noise_sv2, - parsers_sv2::{Mining, ParserError}, -}; - -pub type PoolResult = Result; - -#[derive(Debug)] -pub enum ChannelSv2Error { - ExtendedChannelServerSide(ExtendedChannelError), - StandardChannelServerSide(StandardChannelError), - GroupChannelServerSide(GroupChannelError), - ExtranonceError(ExtendedExtranonceError), - ShareValidationError(ShareValidationError), -} - -/// Represents various errors that can occur in the pool implementation. -#[derive(std::fmt::Debug)] -pub enum PoolError { - /// I/O-related error. - Io(std::io::Error), - ChannelSv2(ChannelSv2Error), - /// Error when sending a message through a channel. - ChannelSend(Box), - /// Error when receiving a message from an asynchronous channel. - ChannelRecv(async_channel::RecvError), - /// Error from the `binary_sv2` crate. - BinarySv2(binary_sv2::Error), - /// Error from the `codec_sv2` crate. - Codec(codec_sv2::Error), - /// Error related to parsing a coinbase output specification. - CoinbaseOutput(stratum_apps::config_helpers::CoinbaseOutputError), - /// Error from the `noise_sv2` crate. - Noise(noise_sv2::Error), - /// Error related to SV2 message framing. - Framing(framing_sv2::Error), - /// Error due to a poisoned lock, typically from a failed mutex operation. - PoisonLock(String), - /// Error indicating that a component has shut down unexpectedly. - ComponentShutdown(String), - /// Custom error message. - Custom(String), - /// Error related to the SV2 protocol, including an error code and a `Mining` message. - Sv2ProtocolError((u32, Mining<'static>)), - /// Vardiff Error - Vardiff(VardiffError), - /// Parser Error - Parser(ParserError), - /// Shutdown - Shutdown, - /// Unexpected message - UnexpectedMessage(u8), - /// Channel error sender - ChannelErrorSender, - /// Invalid socket address - InvalidSocketAddress(String), - /// Bitcoin Encode Error - BitcoinEncodeError(bitcoin::consensus::encode::Error), - /// Downstream not found for the channel - DownstreamNotFoundWithChannelId(u32), - /// Downstream not found - DownstreamNotFound(usize), - /// Downstream Id not found - DownstreamIdNotFound, - /// Future template not present - FutureTemplateNotPresent, - /// Last new prevhash not found - LastNewPrevhashNotFound, - /// Vardiff associated to channel not found - VardiffNotFound(u32), - /// Errors on bad `String` to `int` conversion. - ParseInt(std::num::ParseIntError), - /// Failed to create group channel - FailedToCreateGroupChannel(GroupChannelError), -} - -impl std::fmt::Display for PoolError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use PoolError::*; - match self { - Io(e) => write!(f, "I/O error: `{e:?}"), - ChannelSend(e) => write!(f, "Channel send failed: `{e:?}`"), - ChannelRecv(e) => write!(f, "Channel recv failed: `{e:?}`"), - BinarySv2(e) => write!(f, "Binary SV2 error: `{e:?}`"), - Codec(e) => write!(f, "Codec SV2 error: `{e:?}"), - CoinbaseOutput(e) => write!(f, "Coinbase output error: `{e:?}"), - Framing(e) => write!(f, "Framing SV2 error: `{e:?}`"), - Noise(e) => write!(f, "Noise SV2 error: `{e:?}"), - PoisonLock(e) => write!(f, "Poison lock: {e:?}"), - ComponentShutdown(e) => write!(f, "Component shutdown: {e:?}"), - Custom(e) => write!(f, "Custom SV2 error: `{e:?}`"), - Sv2ProtocolError(e) => { - write!(f, "Received Sv2 Protocol Error from upstream: `{e:?}`") - } - PoolError::Vardiff(e) => { - write!(f, "Received Vardiff Error : {e:?}") - } - Parser(e) => write!(f, "Parser error: `{e:?}`"), - Shutdown => write!(f, "Shutdown"), - UnexpectedMessage(message_type) => write!(f, "message type: {message_type:?}"), - ChannelErrorSender => write!(f, "Channel sender error"), - InvalidSocketAddress(address) => write!(f, "Invalid socket address: {address:?}"), - BitcoinEncodeError(_) => write!(f, "Error generated during encoding"), - DownstreamNotFoundWithChannelId(channel_id) => { - write!(f, "Downstream not found for 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 prev hash not present"), - VardiffNotFound(downstream_id) => write!( - f, - "Vardiff not found available for downstream id: {downstream_id}" - ), - ParseInt(e) => write!(f, "Conversion error: {e:?}"), - ChannelSv2(channel_error) => { - write!(f, "Channel error: {channel_error:?}") - } - FailedToCreateGroupChannel(ref e) => { - write!(f, "Failed to create group channel: {e:?}") - } - } - } -} - -impl From for PoolError { - fn from(e: std::io::Error) -> PoolError { - PoolError::Io(e) - } -} - -impl From for PoolError { - fn from(e: async_channel::RecvError) -> PoolError { - PoolError::ChannelRecv(e) - } -} - -impl From for PoolError { - fn from(e: binary_sv2::Error) -> PoolError { - PoolError::BinarySv2(e) - } -} - -impl From for PoolError { - fn from(e: codec_sv2::Error) -> PoolError { - PoolError::Codec(e) - } -} - -impl From for PoolError { - fn from(e: stratum_apps::config_helpers::CoinbaseOutputError) -> PoolError { - PoolError::CoinbaseOutput(e) - } -} - -impl From for PoolError { - fn from(e: noise_sv2::Error) -> PoolError { - PoolError::Noise(e) - } -} - -impl From> for PoolError { - fn from(e: async_channel::SendError) -> PoolError { - PoolError::ChannelSend(Box::new(e)) - } -} - -impl From for PoolError { - fn from(e: String) -> PoolError { - PoolError::Custom(e) - } -} -impl From for PoolError { - fn from(e: framing_sv2::Error) -> PoolError { - PoolError::Framing(e) - } -} - -impl From>> for PoolError { - fn from(e: PoisonError>) -> PoolError { - PoolError::PoisonLock(e.to_string()) - } -} - -impl From<(u32, Mining<'static>)> for PoolError { - fn from(e: (u32, Mining<'static>)) -> Self { - PoolError::Sv2ProtocolError(e) - } -} - -impl HandlerErrorType for PoolError { - fn parse_error(error: ParserError) -> Self { - PoolError::Parser(error) - } - - fn unexpected_message(message_type: u8) -> Self { - PoolError::UnexpectedMessage(message_type) - } -} - -impl From for PoolError { - fn from(value: stratum_apps::stratum_core::bitcoin::consensus::encode::Error) -> Self { - PoolError::BitcoinEncodeError(value) - } -} - -impl From for PoolError { - fn from(value: ExtendedChannelError) -> Self { - PoolError::ChannelSv2(ChannelSv2Error::ExtendedChannelServerSide(value)) - } -} - -impl From for PoolError { - fn from(value: StandardChannelError) -> Self { - PoolError::ChannelSv2(ChannelSv2Error::StandardChannelServerSide(value)) - } -} - -impl From for PoolError { - fn from(value: GroupChannelError) -> Self { - PoolError::ChannelSv2(ChannelSv2Error::GroupChannelServerSide(value)) - } -} - -impl From for PoolError { - fn from(value: ExtendedExtranonceError) -> Self { - PoolError::ChannelSv2(ChannelSv2Error::ExtranonceError(value)) - } -} - -impl From for PoolError { - fn from(value: VardiffError) -> Self { - PoolError::Vardiff(value) - } -} - -impl From for PoolError { - fn from(value: ParserError) -> Self { - PoolError::Parser(value) - } -} - -impl From for PoolError { - fn from(value: ShareValidationError) -> Self { - PoolError::ChannelSv2(ChannelSv2Error::ShareValidationError(value)) - } -} diff --git a/roles/pool/src/lib/mod.rs b/roles/pool/src/lib/mod.rs deleted file mode 100644 index df2381d661..0000000000 --- a/roles/pool/src/lib/mod.rs +++ /dev/null @@ -1,177 +0,0 @@ -use std::sync::Arc; - -use async_channel::unbounded; -use stratum_apps::stratum_core::{ - bitcoin::consensus::Encodable, parsers_sv2::TemplateDistribution, -}; -use tokio::sync::broadcast; -use tracing::{debug, info, warn}; - -use crate::{ - channel_manager::ChannelManager, - config::PoolConfig, - error::PoolResult, - status::{State, Status}, - task_manager::TaskManager, - template_receiver::TemplateReceiver, - utils::ShutdownMessage, -}; - -pub mod channel_manager; -pub mod config; -pub mod downstream; -pub mod error; -pub mod status; -pub mod task_manager; -pub mod template_receiver; -pub mod utils; - -#[derive(Debug, Clone)] -pub struct PoolSv2 { - config: PoolConfig, - notify_shutdown: broadcast::Sender, -} - -impl PoolSv2 { - pub fn new(config: PoolConfig) -> Self { - let (notify_shutdown, _) = tokio::sync::broadcast::channel::(100); - Self { - config, - notify_shutdown, - } - } - - /// Starts the Pool main loop. - pub async fn start(&self) -> PoolResult<()> { - let coinbase_outputs = vec![self.config.get_txout()]; - let mut encoded_outputs = vec![]; - - coinbase_outputs - .consensus_encode(&mut encoded_outputs) - .expect("Invalid coinbase output in config"); - - let notify_shutdown = self.notify_shutdown.clone(); - - let task_manager = Arc::new(TaskManager::new()); - - let (status_sender, status_receiver) = async_channel::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_tp_sender, - tp_to_channel_manager_receiver, - channel_manager_to_downstream_sender.clone(), - downstream_to_channel_manager_receiver, - encoded_outputs.clone(), - ) - .await?; - - 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?; - - info!("Template provider setup done"); - - template_receiver - .start( - tp_address, - notify_shutdown.clone(), - status_sender.clone(), - task_manager.clone(), - encoded_outputs, - ) - .await?; - - channel_manager - .start( - notify_shutdown.clone(), - status_sender.clone(), - task_manager.clone(), - ) - .await?; - - channel_manager_clone - .start_downstream_server( - *self.config.authority_public_key(), - *self.config.authority_secret_key(), - self.config.cert_validity_sec(), - *self.config.listen_address(), - task_manager.clone(), - notify_shutdown.clone(), - status_sender, - downstream_to_channel_manager_sender, - channel_manager_to_downstream_sender, - ) - .await?; - - info!("Spawning status listener task..."); - loop { - tokio::select! { - _ = tokio::signal::ctrl_c() => { - info!("Ctrl+C received โ€” initiating graceful shutdown..."); - let _ = notify_shutdown.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.send(ShutdownMessage::DownstreamShutdown(downstream_id)); - } - State::TemplateReceiverShutdown(_) => { - warn!("Template Receiver shutdown requested โ€” initiating full shutdown."); - let _ = notify_shutdown.send(ShutdownMessage::ShutdownAll); - break; - } - State::ChannelManagerShutdown(_) => { - warn!("Channel Manager shutdown requested โ€” initiating full shutdown."); - let _ = notify_shutdown.send(ShutdownMessage::ShutdownAll); - break; - } - } - } - } - } - } - - warn!("Graceful shutdown"); - task_manager.abort_all().await; - info!("Joining remaining tasks..."); - task_manager.join_all().await; - info!("Pool shutdown complete."); - Ok(()) - } -} - -impl Drop for PoolSv2 { - fn drop(&mut self) { - info!("PoolSv2 dropped"); - let _ = self.notify_shutdown.send(ShutdownMessage::ShutdownAll); - } -} diff --git a/roles/pool/src/lib/status.rs b/roles/pool/src/lib/status.rs deleted file mode 100644 index 95765e3ac3..0000000000 --- a/roles/pool/src/lib/status.rs +++ /dev/null @@ -1,124 +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::PoolError; - -/// 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: usize, - tx: async_channel::Sender, - }, - /// Status updates from the template receiver. - TemplateReceiver(async_channel::Sender), - /// Status updates from the channel manager. - ChannelManager(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(usize), - /// The template receiver component. - TemplateReceiver, - /// The channel manager component. - ChannelManager, -} - -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::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 - } - } - } -} - -/// 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: usize, - reason: PoolError, - }, - /// Template receiver has shut down with a reason. - TemplateReceiverShutdown(PoolError), - /// Channel manager has shut down with a reason. - ChannelManagerShutdown(PoolError), -} - -/// 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: PoolError) { - 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) - } - }; - - 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: PoolError) { - error!("Error in {:?}: {:?}", sender, e); - send_status(sender, e).await; -} diff --git a/roles/pool/src/lib/task_manager.rs b/roles/pool/src/lib/task_manager.rs deleted file mode 100644 index 95435a020c..0000000000 --- a/roles/pool/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 + Send + 'static, - { - let handle = tokio::spawn(async move { - fut.await; - }); - self.tasks.lock().unwrap().push(handle); - } - - /// Waits for all managed tasks to complete. - /// - /// This method will block until all tasks that were spawned through this - /// manager have finished executing. Tasks are joined in reverse order - /// (most recently spawned first). - pub async fn join_all(&self) { - let handles = { - let mut tasks = self.tasks.lock().unwrap(); - std::mem::take(&mut *tasks) - }; - - for handle in handles { - let _ = handle.await; - } - } - - /// Aborts all managed tasks. - /// - /// This method immediately cancels all tasks that were spawned through this - /// manager. The tasks will be terminated without waiting for them to complete. - pub async fn abort_all(&self) { - let mut tasks = self.tasks.lock().unwrap(); - for handle in tasks.drain(..) { - handle.abort(); - } - } -} diff --git a/roles/pool/src/lib/template_receiver/common_message_handler.rs b/roles/pool/src/lib/template_receiver/common_message_handler.rs deleted file mode 100644 index c23e06422b..0000000000 --- a/roles/pool/src/lib/template_receiver/common_message_handler.rs +++ /dev/null @@ -1,58 +0,0 @@ -use stratum_apps::stratum_core::{ - common_messages_sv2::{ - ChannelEndpointChanged, Reconnect, SetupConnectionError, SetupConnectionSuccess, - }, - handlers_sv2::HandleCommonMessagesFromServerAsync, -}; -use tracing::{error, info}; - -use crate::{error::PoolError, template_receiver::TemplateReceiver}; - -impl HandleCommonMessagesFromServerAsync for TemplateReceiver { - type Error = PoolError; - - async fn handle_setup_connection_success( - &mut self, - _server_id: Option, - msg: SetupConnectionSuccess, - ) -> Result<(), Self::Error> { - info!( - "Received `SetupConnectionSuccess` from TP: version={}, flags={:b}", - msg.used_version, msg.flags - ); - Ok(()) - } - - async fn handle_channel_endpoint_changed( - &mut self, - _server_id: Option, - msg: ChannelEndpointChanged, - ) -> Result<(), Self::Error> { - info!( - "Received ChannelEndpointChanged with channel id: {}", - msg.channel_id - ); - Err(PoolError::Shutdown) - } - - 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> { - error!( - "Received `SetupConnectionError` from TP with error code {}", - std::str::from_utf8(msg.error_code.as_ref()).unwrap_or("unknown error code") - ); - Err(PoolError::Shutdown) - } -} diff --git a/roles/pool/src/lib/template_receiver/mod.rs b/roles/pool/src/lib/template_receiver/mod.rs deleted file mode 100644 index 31697e7538..0000000000 --- a/roles/pool/src/lib/template_receiver/mod.rs +++ /dev/null @@ -1,377 +0,0 @@ -use std::{net::SocketAddr, sync::Arc}; -mod common_message_handler; -use async_channel::{unbounded, Receiver, Sender}; -use stratum_apps::{ - key_utils::Secp256k1PublicKey, - network_helpers::noise_stream::NoiseTcpStream, - stratum_core::{ - bitcoin::{ - self, absolute::LockTime, transaction::Version, OutPoint, ScriptBuf, Sequence, - Transaction, TxIn, TxOut, Witness, - }, - codec_sv2::HandshakeRole, - framing_sv2, - handlers_sv2::HandleCommonMessagesFromServerAsync, - noise_sv2::{Error, Initiator}, - parsers_sv2::{AnyMessage, TemplateDistribution}, - template_distribution_sv2::CoinbaseOutputConstraints, - }, -}; -use tokio::{net::TcpStream, sync::broadcast}; -use tracing::{debug, error, info, warn}; - -use crate::{ - error::{PoolError, PoolResult}, - status::{handle_error, Status, StatusSender}, - task_manager::TaskManager, - utils::{ - get_setup_connection_message_tp, protocol_message_type, spawn_io_tasks, Message, - MessageType, SV2Frame, ShutdownMessage, StdFrame, - }, -}; - -#[derive(Clone)] -pub struct TemplateReceiverChannel { - channel_manager_sender: Sender>, - channel_manager_receiver: Receiver>, - tp_sender: Sender, - tp_receiver: Receiver, -} - -#[derive(Clone)] -pub struct TemplateReceiver { - template_receiver_channel: TemplateReceiverChannel, -} - -impl TemplateReceiver { - /// Establish a new connection to a Template Provider. - /// - /// - Opens a TCP connection - /// - Performs Noise handshake - /// - Spawns IO tasks for inbound/outbound frames - /// - /// Retries up to 3 times before returning [`PoolError::Shutdown`]. - pub async fn new( - tp_address: String, - public_key: Option, - channel_manager_receiver: Receiver>, - channel_manager_sender: Sender>, - notify_shutdown: broadcast::Sender, - task_manager: Arc, - status_sender: Sender, - ) -> PoolResult { - const MAX_RETRIES: usize = 3; - - for attempt in 1..=MAX_RETRIES { - info!(attempt, MAX_RETRIES, "Connecting to template provider"); - - let initiator = match public_key { - Some(pub_key) => { - debug!(attempt, "Using public key for initiator handshake"); - Initiator::from_raw_k(pub_key.into_bytes()) - } - None => { - debug!(attempt, "Using anonymous initiator (no public key)"); - Initiator::without_pk() - } - }?; - - match TcpStream::connect(tp_address.as_str()).await { - Ok(stream) => { - info!( - attempt, - "TCP connection established, starting Noise handshake" - ); - - match NoiseTcpStream::::new( - stream, - HandshakeRole::Initiator(initiator), - ) - .await - { - Ok(noise_stream) => { - info!(attempt, "Noise handshake completed successfully"); - - let (noise_stream_reader, noise_stream_writer) = - noise_stream.into_split(); - - let status_sender = StatusSender::TemplateReceiver(status_sender); - let (inbound_tx, inbound_rx) = unbounded::(); - let (outbound_tx, outbound_rx) = unbounded::(); - - info!(attempt, "Spawning IO tasks for template receiver"); - spawn_io_tasks( - task_manager.clone(), - noise_stream_reader, - noise_stream_writer, - outbound_rx, - inbound_tx, - notify_shutdown, - status_sender, - ); - - let template_receiver_channel = TemplateReceiverChannel { - channel_manager_receiver, - channel_manager_sender, - tp_receiver: inbound_rx, - tp_sender: outbound_tx, - }; - - info!(attempt, "TemplateReceiver initialized successfully"); - return Ok(TemplateReceiver { - template_receiver_channel, - }); - } - Err(e) => { - error!(attempt, error = ?e, "Noise handshake failed"); - } - } - } - Err(e) => { - warn!(attempt, MAX_RETRIES, error = ?e, "Failed to connect to template provider"); - } - } - - if attempt < MAX_RETRIES { - debug!(attempt, "Retrying connection after backoff"); - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - } - } - - error!("Exhausted all connection attempts, shutting down TemplateReceiver"); - Err(PoolError::Shutdown) - } - - /// Start unified message loop for TemplateReceiver. - /// - /// Responsibilities: - /// - Run handshake (`setup_connection`) - /// - Send [`CoinbaseOutputConstraints`] - /// - Handle: - /// - Messages from Template Provider - /// - Messages from ChannelManager - /// - Shutdown signals (upstream/job-declarator fallback) - pub async fn start( - mut self, - socket_address: String, - notify_shutdown: broadcast::Sender, - status_sender: Sender, - task_manager: Arc, - coinbase_outputs: Vec, - ) -> PoolResult<()> { - let status_sender = StatusSender::TemplateReceiver(status_sender); - let mut shutdown_rx = notify_shutdown.subscribe(); - - info!("Initialized state for starting template receiver"); - self.setup_connection(socket_address).await?; - - self.coinbase_constraints(coinbase_outputs).await?; - - info!("Setup Connection done. connection with template receiver is now done"); - 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!("Template Receiver: received shutdown signal"); - break; - }, - Err(e) => { - warn!(error = ?e, "Template Receiver: shutdown channel closed unexpectedly"); - break; - } - _ => {} - } - } - res = self_clone_1.handle_template_provider_message() => { - if let Err(e) = res { - error!("TemplateReceiver template provider handler failed: {e:?}"); - handle_error(&status_sender, e).await; - break; - } - } - res = self_clone_2.handle_channel_manager_message() => { - if let Err(e) = res { - error!("TemplateReceiver channel manager handler failed: {e:?}"); - handle_error(&status_sender, e).await; - break; - } - }, - } - } - warn!("TemplateReceiver: unified message loop exited."); - }, - ); - Ok(()) - } - - /// Handle inbound messages from the template provider. - /// - /// Routes: - /// - `Common` messages โ†’ handled locally - /// - `TemplateDistribution` messages โ†’ forwarded to ChannelManager - /// - Unsupported messages โ†’ logged and ignored - pub async fn handle_template_provider_message(&mut self) -> PoolResult<()> { - let mut sv2_frame = self.template_receiver_channel.tp_receiver.recv().await?; - debug!("Received SV2 frame from Template provider."); - 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 Template provider." - ); - - self.handle_common_message_frame_from_server( - None, - message_type, - sv2_frame.payload(), - ) - .await?; - } - MessageType::TemplateDistribution => { - let message = TemplateDistribution::try_from((message_type, sv2_frame.payload()))? - .into_static(); - - self.template_receiver_channel - .channel_manager_sender - .send(message) - .await - .map_err(|e| { - error!(error=?e, "Failed to send template distribution message to channel manager."); - PoolError::ChannelErrorSender - })?; - } - _ => { - warn!("Received unsupported message type from template provider: {message_type}"); - } - } - Ok(()) - } - - /// Handle messages from channel manager โ†’ template provider. - /// - /// Forwards outbound frames upstream - pub async fn handle_channel_manager_message(&self) -> PoolResult<()> { - let msg = self - .template_receiver_channel - .channel_manager_receiver - .recv() - .await?; - let message = AnyMessage::TemplateDistribution(msg).into_static(); - let frame: StdFrame = message.try_into()?; - - debug!("Forwarding message from channel manager to outbound_tx"); - self.template_receiver_channel - .tp_sender - .send(frame) - .await - .map_err(|_| PoolError::ChannelErrorSender)?; - - Ok(()) - } - - /// Build and send [`CoinbaseOutputConstraints`] to the TP. - pub async fn coinbase_constraints(&mut self, coinbase_outputs: Vec) -> PoolResult<()> { - debug!( - "Deserializing coinbase outputs ({} bytes)", - coinbase_outputs.len() - ); - let outputs: Vec = bitcoin::consensus::deserialize(&coinbase_outputs)?; - - let max_size: u32 = outputs.iter().map(|o| o.size() as u32).sum(); - debug!( - max_size, - outputs_count = outputs.len(), - "Calculated max coinbase output size" - ); - - 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: outputs, - }; - - let max_sigops = dummy_coinbase.total_sigop_cost(|_| None) as u16; - debug!(max_sigops, "Calculated max sigops for coinbase"); - - let constraints = CoinbaseOutputConstraints { - coinbase_output_max_additional_size: max_size, - coinbase_output_max_additional_sigops: max_sigops, - }; - - let msg = AnyMessage::TemplateDistribution( - TemplateDistribution::CoinbaseOutputConstraints(constraints), - ); - - let frame: StdFrame = msg.try_into()?; - info!("Sending CoinbaseOutputConstraints message upstream"); - self.template_receiver_channel - .tp_sender - .send(frame) - .await - .map_err(|_| { - error!("Failed to send CoinbaseOutputConstraints message upstream"); - PoolError::ChannelErrorSender - })?; - - Ok(()) - } - - // Performs the initial handshake with Template Provider. - pub async fn setup_connection(&mut self, addr: String) -> PoolResult<()> { - let socket: SocketAddr = addr.parse().map_err(|_| { - error!(%addr, "Invalid socket address"); - PoolError::InvalidSocketAddress(addr.clone()) - })?; - - debug!(%socket, "Building SetupConnection message to the Template Provider"); - let setup_msg = get_setup_connection_message_tp(socket); - let frame: StdFrame = Message::Common(setup_msg.into()).try_into()?; - - info!("Sending SetupConnection message to the Template Provider"); - self.template_receiver_channel - .tp_sender - .send(frame) - .await - .map_err(|_| { - error!("Failed to send setup connection message upstream"); - PoolError::ChannelErrorSender - })?; - - info!("Waiting for upstream handshake response"); - let mut incoming: StdFrame = self - .template_receiver_channel - .tp_receiver - .recv() - .await - .map_err(|e| { - error!(?e, "Upstream connection closed during handshake"); - PoolError::Noise(Error::ExpectedIncomingHandshakeMessage) - })?; - - let msg_type = incoming - .get_header() - .ok_or(framing_sv2::Error::ExpectedHandshakeFrame)? - .msg_type(); - debug!(?msg_type, "Received upstream handshake response"); - - self.handle_common_message_frame_from_server(None, msg_type, incoming.payload()) - .await?; - info!("Handshake with upstream completed successfully"); - Ok(()) - } -} diff --git a/roles/pool/src/lib/utils.rs b/roles/pool/src/lib/utils.rs deleted file mode 100644 index f5136fbcec..0000000000 --- a/roles/pool/src/lib/utils.rs +++ /dev/null @@ -1,355 +0,0 @@ -use std::{net::SocketAddr, sync::Arc}; - -use async_channel::{Receiver, Sender}; -use stratum_apps::{ - network_helpers::noise_stream::{NoiseTcpReadHalf, NoiseTcpWriteHalf}, - stratum_core::{ - buffer_sv2, - codec_sv2::{StandardEitherFrame, StandardSv2Frame}, - common_messages_sv2::{ - Protocol, SetupConnection, MESSAGE_TYPE_CHANNEL_ENDPOINT_CHANGED, - MESSAGE_TYPE_RECONNECT, MESSAGE_TYPE_SETUP_CONNECTION, - MESSAGE_TYPE_SETUP_CONNECTION_ERROR, MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS, - }, - framing_sv2::framing::{Frame, Sv2Frame}, - job_declaration_sv2::{ - MESSAGE_TYPE_ALLOCATE_MINING_JOB_TOKEN, MESSAGE_TYPE_ALLOCATE_MINING_JOB_TOKEN_SUCCESS, - MESSAGE_TYPE_DECLARE_MINING_JOB, MESSAGE_TYPE_DECLARE_MINING_JOB_ERROR, - MESSAGE_TYPE_DECLARE_MINING_JOB_SUCCESS, MESSAGE_TYPE_PROVIDE_MISSING_TRANSACTIONS, - MESSAGE_TYPE_PROVIDE_MISSING_TRANSACTIONS_SUCCESS, MESSAGE_TYPE_PUSH_SOLUTION, - }, - mining_sv2::{ - MESSAGE_TYPE_CLOSE_CHANNEL, MESSAGE_TYPE_MINING_SET_NEW_PREV_HASH, - MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB, MESSAGE_TYPE_NEW_MINING_JOB, - MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL, - MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL_SUCCESS, - MESSAGE_TYPE_OPEN_MINING_CHANNEL_ERROR, MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL, - MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL_SUCCESS, MESSAGE_TYPE_SET_CUSTOM_MINING_JOB, - MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_ERROR, MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_SUCCESS, - MESSAGE_TYPE_SET_EXTRANONCE_PREFIX, MESSAGE_TYPE_SET_GROUP_CHANNEL, - MESSAGE_TYPE_SET_TARGET, MESSAGE_TYPE_SUBMIT_SHARES_ERROR, - MESSAGE_TYPE_SUBMIT_SHARES_EXTENDED, MESSAGE_TYPE_SUBMIT_SHARES_STANDARD, - MESSAGE_TYPE_SUBMIT_SHARES_SUCCESS, MESSAGE_TYPE_UPDATE_CHANNEL, - MESSAGE_TYPE_UPDATE_CHANNEL_ERROR, - }, - parsers_sv2::AnyMessage, - template_distribution_sv2::{ - MESSAGE_TYPE_COINBASE_OUTPUT_CONSTRAINTS, MESSAGE_TYPE_NEW_TEMPLATE, - MESSAGE_TYPE_REQUEST_TRANSACTION_DATA, MESSAGE_TYPE_REQUEST_TRANSACTION_DATA_ERROR, - MESSAGE_TYPE_REQUEST_TRANSACTION_DATA_SUCCESS, MESSAGE_TYPE_SET_NEW_PREV_HASH, - MESSAGE_TYPE_SUBMIT_SOLUTION, - }, - }, -}; -use tokio::sync::broadcast; -use tracing::{error, trace, warn, Instrument}; - -use crate::{ - error::PoolResult, - status::{StatusSender, StatusType}, - task_manager::TaskManager, -}; - -pub type Message = AnyMessage<'static>; -pub type StdFrame = StandardSv2Frame; -pub type EitherFrame = StandardEitherFrame; -pub type SV2Frame = Sv2Frame; - -/// Represents a message that can trigger shutdown of various system components. -#[derive(Debug, Clone)] -pub enum ShutdownMessage { - /// Shutdown all components immediately - ShutdownAll, - /// Shutdown all downstream connections - DownstreamShutdownAll, - /// Shutdown a specific downstream connection by ID - DownstreamShutdown(usize), -} - -/// Constructs a `SetupConnection` message for the mining protocol. -#[allow(clippy::result_large_err)] -pub fn get_setup_connection_message( - min_version: u16, - max_version: u16, -) -> PoolResult> { - let endpoint_host = "0.0.0.0".to_string().into_bytes().try_into()?; - let vendor = String::new().try_into()?; - let hardware_version = String::new().try_into()?; - let firmware = String::new().try_into()?; - let device_id = String::new().try_into()?; - let flags = 0b0000_0000_0000_0000_0000_0000_0000_0110; - Ok(SetupConnection { - protocol: Protocol::MiningProtocol, - min_version, - max_version, - flags, - endpoint_host, - endpoint_port: 50, - vendor, - hardware_version, - firmware, - device_id, - }) -} - -/// Constructs a `SetupConnection` message for the Template Provider (TP). -pub fn get_setup_connection_message_tp(address: SocketAddr) -> SetupConnection<'static> { - let endpoint_host = address.ip().to_string().into_bytes().try_into().unwrap(); - let vendor = String::new().try_into().unwrap(); - let hardware_version = String::new().try_into().unwrap(); - let firmware = String::new().try_into().unwrap(); - let device_id = String::new().try_into().unwrap(); - SetupConnection { - protocol: Protocol::TemplateDistributionProtocol, - min_version: 2, - max_version: 2, - flags: 0b0000_0000_0000_0000_0000_0000_0000_0000, - endpoint_host, - endpoint_port: address.port(), - vendor, - hardware_version, - firmware, - device_id, - } -} - -/// Spawns async reader and writer tasks for handling framed I/O with shutdown support. -#[track_caller] -#[allow(clippy::too_many_arguments)] -pub fn spawn_io_tasks( - task_manager: Arc, - mut reader: NoiseTcpReadHalf, - mut writer: NoiseTcpWriteHalf, - outbound_rx: Receiver, - inbound_tx: Sender, - notify_shutdown: broadcast::Sender, - status_sender: StatusSender, -) { - let caller = std::panic::Location::caller(); - let inbound_tx_clone = inbound_tx.clone(); - let outbound_rx_clone = outbound_rx.clone(); - { - let mut shutdown_rx = notify_shutdown.subscribe(); - let status_sender = status_sender.clone(); - let status_type: StatusType = StatusType::from(&status_sender); - - task_manager.spawn(async move { - trace!("Reader task started"); - loop { - tokio::select! { - message = shutdown_rx.recv() => { - match message { - Ok(ShutdownMessage::ShutdownAll) => { - trace!("Received global shutdown"); - inbound_tx.close(); - break; - } - Ok(ShutdownMessage::DownstreamShutdown(down_id)) if matches!(status_type, StatusType::Downstream(id) if id == down_id) => { - trace!(down_id, "Received downstream shutdown"); - if status_type != StatusType::TemplateReceiver { - inbound_tx.close(); - break; - } - } - _ => {} - } - } - res = reader.read_frame() => { - match res { - Ok(frame) => { - match frame { - Frame::HandShake(frame) => { - error!(?frame, "Received handshake frame"); - drop(frame); - break; - }, - Frame::Sv2(sv2_frame) => { - trace!("Received inbound frame"); - if let Err(e) = inbound_tx.send(sv2_frame).await { - inbound_tx.close(); - error!(error=?e, "Failed to forward inbound frame"); - break; - } - }, - } - } - Err(e) => { - error!(error=?e, "Reader error"); - inbound_tx.close(); - break; - } - } - } - } - } - inbound_tx.close(); - outbound_rx_clone.close(); - drop(inbound_tx); - drop(outbound_rx_clone); - warn!("Reader task exited."); - }.instrument(tracing::trace_span!( - "reader_task", - spawned_at = %format!("{}:{}", caller.file(), caller.line()) - ))); - } - - { - let mut shutdown_rx = notify_shutdown.subscribe(); - let status_type: StatusType = StatusType::from(&status_sender); - - task_manager.spawn(async move { - trace!("Writer task started"); - loop { - tokio::select! { - message = shutdown_rx.recv() => { - match message { - Ok(ShutdownMessage::ShutdownAll) => { - trace!("Received global shutdown"); - outbound_rx.close(); - break; - } - Ok(ShutdownMessage::DownstreamShutdown(down_id)) if matches!(status_type, StatusType::Downstream(id) if id == down_id) => { - trace!(down_id, "Received downstream shutdown"); - if status_type != StatusType::TemplateReceiver { - outbound_rx.close(); - break; - } - } - _ => {} - } - } - res = outbound_rx.recv() => { - match res { - Ok(frame) => { - trace!("Sending outbound frame"); - if let Err(e) = writer.write_frame(frame.into()).await { - error!(error=?e, "Writer error"); - outbound_rx.close(); - break; - } - } - Err(_) => { - outbound_rx.close(); - warn!("Outbound channel closed"); - break; - } - } - } - } - } - outbound_rx.close(); - inbound_tx_clone.close(); - drop(outbound_rx); - drop(inbound_tx_clone); - warn!("Writer task exited."); - }.instrument(tracing::trace_span!( - "writer_task", - spawned_at = %format!("{}:{}", caller.file(), caller.line()) - ))); - } -} - -pub fn is_common_message(message_type: u8) -> bool { - matches!( - message_type, - MESSAGE_TYPE_SETUP_CONNECTION - | MESSAGE_TYPE_SETUP_CONNECTION_SUCCESS - | MESSAGE_TYPE_SETUP_CONNECTION_ERROR - | MESSAGE_TYPE_CHANNEL_ENDPOINT_CHANGED - | MESSAGE_TYPE_RECONNECT - ) -} - -pub fn is_mining_message(message_type: u8) -> bool { - matches!( - message_type, - MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL - | MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL_SUCCESS - | MESSAGE_TYPE_OPEN_MINING_CHANNEL_ERROR - | MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL - | MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL_SUCCESS - | MESSAGE_TYPE_NEW_MINING_JOB - | MESSAGE_TYPE_UPDATE_CHANNEL - | MESSAGE_TYPE_UPDATE_CHANNEL_ERROR - | MESSAGE_TYPE_CLOSE_CHANNEL - | MESSAGE_TYPE_SET_EXTRANONCE_PREFIX - | MESSAGE_TYPE_SUBMIT_SHARES_STANDARD - | MESSAGE_TYPE_SUBMIT_SHARES_EXTENDED - | MESSAGE_TYPE_SUBMIT_SHARES_SUCCESS - | MESSAGE_TYPE_SUBMIT_SHARES_ERROR - // | MESSAGE_TYPE_RESERVED - | 0x1e - | MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB - | MESSAGE_TYPE_MINING_SET_NEW_PREV_HASH - | MESSAGE_TYPE_SET_TARGET - | MESSAGE_TYPE_SET_CUSTOM_MINING_JOB - | MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_SUCCESS - | MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_ERROR - | MESSAGE_TYPE_SET_GROUP_CHANNEL - ) -} - -pub fn is_job_declaration_message(message_type: u8) -> bool { - matches!( - message_type, - MESSAGE_TYPE_ALLOCATE_MINING_JOB_TOKEN - | MESSAGE_TYPE_ALLOCATE_MINING_JOB_TOKEN_SUCCESS - | MESSAGE_TYPE_PROVIDE_MISSING_TRANSACTIONS - | MESSAGE_TYPE_PROVIDE_MISSING_TRANSACTIONS_SUCCESS - | MESSAGE_TYPE_DECLARE_MINING_JOB - | MESSAGE_TYPE_DECLARE_MINING_JOB_SUCCESS - | MESSAGE_TYPE_DECLARE_MINING_JOB_ERROR - | MESSAGE_TYPE_PUSH_SOLUTION - ) -} - -pub fn is_template_distribution_message(message_type: u8) -> bool { - matches!( - message_type, - MESSAGE_TYPE_COINBASE_OUTPUT_CONSTRAINTS - | MESSAGE_TYPE_NEW_TEMPLATE - | MESSAGE_TYPE_SET_NEW_PREV_HASH - | MESSAGE_TYPE_REQUEST_TRANSACTION_DATA - | MESSAGE_TYPE_REQUEST_TRANSACTION_DATA_SUCCESS - | MESSAGE_TYPE_REQUEST_TRANSACTION_DATA_ERROR - | MESSAGE_TYPE_SUBMIT_SOLUTION - ) -} - -#[derive(Debug, PartialEq, Eq)] -pub enum MessageType { - Common, - Mining, - JobDeclaration, - TemplateDistribution, - Unknown, -} - -pub fn protocol_message_type(message_type: u8) -> MessageType { - if is_common_message(message_type) { - MessageType::Common - } else if is_mining_message(message_type) { - MessageType::Mining - } else if is_job_declaration_message(message_type) { - MessageType::JobDeclaration - } else if is_template_distribution_message(message_type) { - MessageType::TemplateDistribution - } else { - MessageType::Unknown - } -} - -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct VardiffKey { - pub downstream_id: usize, - pub channel_id: u32, -} - -impl From<(usize, u32)> for VardiffKey { - fn from(value: (usize, u32)) -> Self { - VardiffKey { - downstream_id: value.0, - channel_id: value.1, - } - } -} diff --git a/roles/pool/src/main.rs b/roles/pool/src/main.rs deleted file mode 100644 index 7020ef206a..0000000000 --- a/roles/pool/src/main.rs +++ /dev/null @@ -1,15 +0,0 @@ -use pool_sv2::PoolSv2; -use stratum_apps::config_helpers::logging::init_logging; - -use crate::args::process_cli_args; - -mod args; - -#[tokio::main] -async fn main() { - let config = process_cli_args(); - init_logging(config.log_dir()); - if let Err(e) = PoolSv2::new(config).start().await { - tracing::error!("Pool Error'ed out: {e}"); - }; -} diff --git a/roles/stratum-apps/Cargo.toml b/roles/stratum-apps/Cargo.toml deleted file mode 100644 index 8b6331991e..0000000000 --- a/roles/stratum-apps/Cargo.toml +++ /dev/null @@ -1,74 +0,0 @@ -[package] -name = "stratum-apps" -version = "0.1.0" -authors = ["The Stratum V2 Developers"] -edition = "2021" -readme = "README.md" -description = "Complete Stratum V2 application development kit - all utilities in one crate" -documentation = "https://docs.rs/stratum-apps" -license = "MIT OR Apache-2.0" -repository = "https://github.com/stratum-mining/stratum" -homepage = "https://stratumprotocol.org" -keywords = ["stratum", "mining", "bitcoin", "protocol", "sv2"] - -[dependencies] -# Core protocol layer -stratum-core = { path = "../../stratum-core", version = "0.1.0", optional = true} - -# External dependencies needed by the modules -# Network helpers dependencies -async-channel = { version = "1.8.0" } -tokio = { version = "1.44.1", features = ["full"] } -futures = { version = "0.3.28" } -tokio-util = { version = "0.7.10", default-features = false, features = ["codec"], optional = true } - -# Config helpers dependencies -serde = { version = "1.0.89", features = ["derive", "alloc"], default-features = false } -miniscript = { version = "12.3.4", default-features = false, features = ["no-std"] } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -tracing = { version = "0.1" } - -# Key utils dependencies -bs58 = { version = "0.4.0", default-features = false, features = ["check", "alloc"] } -secp256k1 = { version = "0.28.2", default-features = false, features = ["alloc", "rand"] } -rand = { version = "0.8.5", default-features = false } -rustversion = "1.0" -generic-array = "=0.14.7" - -# RPC optional dependencies -serde_json = { version = "1.0", default-features = false, features = ["alloc", "raw_value"], optional = true } -hex = { version = "0.4.3", optional = true } -base64 = { version = "0.21.5", optional = true } -hyper = { version = "1.1.0", features = ["full"], optional = true } -hyper-util = { version = "0.1", features = ["full"], optional = true } -http-body-util = { version = "0.1", optional = true } - -# Common external dependencies that roles always need -clap = { version = "4.5.39", features = ["derive"] } -ext-config = { version = "0.14.0", features = ["toml"], package = "config" } - -[features] -default = ["network", "config", "std"] - -# Core module features -network = ["tokio-util", "core"] -config = [] -rpc = ["serde_json", "hex", "base64", "hyper", "hyper-util", "http-body-util"] -std = ["bs58/std", "secp256k1/rand-std", "rand/std", "rand/std_rng"] -core = ["stratum-core"] - -# Protocol features passed through to stratum-core -sv1 = ["stratum-core/sv1", "stratum-core/translation", "tokio-util", "serde_json"] -with_buffer_pool = ["stratum-core/with_buffer_pool"] - -# Convenience feature bundles for different role types -pool = ["network", "config", "with_buffer_pool", "core"] -jd_client = ["network", "config", "with_buffer_pool", "core"] -# Note: jd_server intentionally excludes 'core', 'network', and 'rpc' - it uses crates.io crates directly -jd_server = ["config"] -translator = ["network", "config", "sv1", "with_buffer_pool", "core"] -# Note: mining_device intentionally excludes 'core', 'network', and 'rpc' - it uses crates.io crates directly -mining_device = ["config"] - -[package.metadata.docs.rs] -features = ["pool", "jd_client", "jd_server", "translator", "sv1", "rpc"] diff --git a/roles/stratum-apps/README.md b/roles/stratum-apps/README.md deleted file mode 100644 index 82b56c5399..0000000000 --- a/roles/stratum-apps/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# Stratum Apps - -Complete Stratum V2 application development kit - all utilities in one crate. - -## Overview - -`stratum-apps` is a unified crate that provides all the utilities needed for building Stratum V2 applications. - -## Architecture - -This crate is organized into three main modules: - -- **`network_helpers`** - High-level networking utilities (from `network_helpers_sv2`) -- **`config_helpers`** - Configuration management helpers (from `config_helpers_sv2`) -- **`rpc`** - RPC utilities with custom serializable types (from `rpc_sv2`) - *feature-gated* - -The crate also re-exports `stratum-core`, the central hub for the Stratum V2 ecosystem that provides a cohesive API for all low-level protocol functionality. - -## Quick Start - -Add to your `Cargo.toml`: - -```toml -[dependencies] -stratum-apps = { version = "0.1.0", features = ["pool"] } -``` - -Basic usage: - -```rust -use stratum_apps::{network_helpers, config_helpers}; - -// For RPC functionality (when rpc feature is enabled) -#[cfg(feature = "rpc")] -use stratum_apps::rpc::{BlockHash, MiniRpcClient}; -``` - -## Features - -### Core Features -- `network` - Networking utilities (enabled by default) -- `config` - Configuration helpers (enabled by default) -- `rpc` - RPC utilities with custom serializable types (optional) - - Provides `Hash`, `BlockHash`, `Amount` types with proper JSON serialization - - `MiniRpcClient` for Bitcoin RPC communication - -### Protocol Features -- `sv1` - Enable SV1 protocol support (includes translation utilities) -- `with_buffer_pool` - Enable buffer pooling for better performance - -### Role-Specific Bundles -- `pool` - Everything needed for pool applications -- `jd_client` - Everything needed for JD client applications -- `jd_server` - Everything needed for JD server applications (includes RPC) -- `translator` - Everything needed for translator applications (includes SV1 + translation) -- `mining_device` - Everything needed for mining device applications - -## Usage Examples - -### Pool Application - -```toml -[dependencies] -stratum-apps = { version = "1.0", features = ["pool"] } -``` - -```rust -use stratum_apps::{network_helpers, config_helpers}; - -// Use networking -let connection = network_helpers::Connection::new(stream, HandshakeRole::Responder).await?; - -// Use configuration -let config: PoolConfig = config_helpers::parse_config("pool.toml")?; -``` - -### JD Server Application - -```toml -[dependencies] -stratum-apps = { version = "1.0", features = ["jd_server"] } -``` - -```rust -use stratum_apps::{network_helpers, config_helpers, rpc}; - -// RPC functionality with custom types -use stratum_apps::rpc::{BlockHash, MiniRpcClient}; - -// All networking and configuration utilities available -// Plus RPC server utilities with proper serialization -``` \ No newline at end of file diff --git a/roles/stratum-apps/src/config_helpers/coinbase_output/errors.rs b/roles/stratum-apps/src/config_helpers/coinbase_output/errors.rs deleted file mode 100644 index e3c1492b30..0000000000 --- a/roles/stratum-apps/src/config_helpers/coinbase_output/errors.rs +++ /dev/null @@ -1,65 +0,0 @@ -use core::fmt; - -use miniscript::bitcoin::{address, hex}; - -/// Error enum -#[derive(Debug)] -pub enum Error { - /// Error parsing a Bitcoin address - Address(address::ParseError), - // TODO rust-miniscript 13 will have functions to do these checks for us so we don't - // need to pollute our own error enum with this fiddly stuff - /// addr() descriptor did not have exactly 1 child - AddrDescriptorNChildren(usize), - /// raw() descriptor child did not have 0 children - AddrDescriptorGrandchild, - /// raw() descriptor did not have exactly 1 child - RawDescriptorNChildren(usize), - /// addr() descriptor child did not have 0 children - RawDescriptorGrandchild, - /// Error parsing a raw descriptor as hex. - Hex(hex::HexToBytesError), - /// Invalid `output_script_value` for script type. It must be a valid public key/script - InvalidOutputScript, - /// Unknown script type in config - UnknownOutputScriptType, - /// Error from the `miniscript` crate. - Miniscript(miniscript::Error), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use Error::*; - match self { - Address(ref e) => write!(f, "Bitcoin address: {e}"), - AddrDescriptorNChildren(0) => write!(f, "Found addr() descriptor with no address"), - AddrDescriptorNChildren(n) => write!(f, "Found addr() descriptor with {n} children; must be exactly one valid address"), - AddrDescriptorGrandchild => write!(f, "Found descriptor of the form addr(X(y)); X must be a valid address and have no subexpression"), - RawDescriptorNChildren(0) => write!(f, "Found raw() descriptor with no hex-encoded script"), - RawDescriptorNChildren(n) => write!(f, "Found raw() descriptor with {n} children; must be exactly one hex-encoded script"), - RawDescriptorGrandchild => write!(f, "Found descriptor of the form raw(X(y)); X must be a hex-encoded script and have no subexpression"), - Hex(ref e) => write!(f, "Decoding hex-formatted script: {e}"), - UnknownOutputScriptType => write!(f, "Unknown script type in config"), - InvalidOutputScript => write!(f, "Invalid output_script_value for your script type. It must be a valid public key/script"), - Miniscript(ref e) => write!(f, "Miniscript: {e}"), - } - } -} - -impl From for Error { - fn from(e: address::ParseError) -> Self { - Error::Address(e) - } -} - -impl From for Error { - fn from(e: hex::HexToBytesError) -> Self { - Error::Hex(e) - } -} - -impl From for Error { - fn from(e: miniscript::Error) -> Self { - Error::Miniscript(e) - } -} diff --git a/roles/stratum-apps/src/config_helpers/coinbase_output/mod.rs b/roles/stratum-apps/src/config_helpers/coinbase_output/mod.rs deleted file mode 100644 index d4657a3530..0000000000 --- a/roles/stratum-apps/src/config_helpers/coinbase_output/mod.rs +++ /dev/null @@ -1,377 +0,0 @@ -mod errors; -mod serde_types; - -use miniscript::{ - bitcoin::{address::NetworkUnchecked, hex::FromHex as _, Address, Network, ScriptBuf}, - DefiniteDescriptorKey, Descriptor, -}; - -pub use errors::Error; - -/// Coinbase output transaction. -/// -/// Typically used for parsing coinbase outputs defined in SRI role configuration files. -#[derive(Debug, serde::Deserialize, Clone)] -#[serde(try_from = "serde_types::SerdeCoinbaseOutput")] -pub struct CoinbaseRewardScript { - script_pubkey: ScriptBuf, - ok_for_mainnet: bool, -} - -impl CoinbaseRewardScript { - /// Creates a new [`CoinbaseRewardScript`] from a descriptor string. - pub fn from_descriptor(mut s: &str) -> Result { - // Taproot descriptors cannot be parsed with `expression::Tree::from_str` and - // need special handling. So we special-case them early and just pass to - // rust-miniscript. In Miniscript 13 we will not need to do this. - if s.starts_with("tr") { - let desc = s.parse::>()?; - return Ok(Self { - script_pubkey: desc.script_pubkey(), - // Descriptors don't have a way to specify a network, so we assume - // they are OK to be used on mainnet. - ok_for_mainnet: true, - }); - } - - // Manually verify the checksum. FIXME in Miniscript 13 we will not need - // to do this, since `expression::Tree::from_str` will do the checksum - // validation for us. (And yield a much less horrible error type.) - if let Some((desc_str, checksum_str)) = s.rsplit_once('#') { - let expected_sum = miniscript::descriptor::checksum::desc_checksum(desc_str)?; - if checksum_str != expected_sum { - return Err(miniscript::Error::BadDescriptor(format!( - "Invalid checksum '{checksum_str}', expected '{expected_sum}'" - )) - .into()); - } - s = desc_str; - } - - let tree = miniscript::expression::Tree::from_str(s)?; - match tree.name { - "addr" => { - // In rust-miniscript 13 these can be replaced with a call to - // TreeIterItem::verify_toplevel which will these checks for us - // in a uniform way. - if tree.args.len() != 1 { - return Err(Error::AddrDescriptorNChildren(tree.args.len())); - } - if !tree.args[0].args.is_empty() { - return Err(Error::AddrDescriptorGrandchild); - } - - let addr = tree.args[0].name.parse::>()?; - Ok(Self { - script_pubkey: addr.assume_checked_ref().script_pubkey(), - ok_for_mainnet: addr.is_valid_for_network(Network::Bitcoin), - }) - } - "raw" => { - // In rust-miniscript 13 these can be replaced with a call to - // TreeIterItem::verify_toplevel which will these checks for us - // in a uniform way. - if tree.args.len() != 1 { - return Err(Error::RawDescriptorNChildren(tree.args.len())); - } - if !tree.args[0].args.is_empty() { - return Err(Error::RawDescriptorGrandchild); - } - - let bytes = Vec::::from_hex(tree.args[0].name)?; - Ok(Self { - script_pubkey: ScriptBuf::from(bytes), - // Users of hex scriptpubkeys are on their own. - ok_for_mainnet: true, - }) - } - _ => { - let desc = s.parse::>()?; - Ok(Self { - script_pubkey: desc.script_pubkey(), - // Descriptors don't have a way to specify a network, so we assume - // they are OK to be used on mainnet. - ok_for_mainnet: true, - }) - } - } - } - - /// Whether this coinbase output is okay for use on mainnet. - /// - /// This is a "best effort" check and currently only returns false if the user - /// provides an addr() descriptor in which they specified a testnet or regtest - /// address. - pub fn ok_for_mainnet(&self) -> bool { - self.ok_for_mainnet - } - - /// The `scriptPubKey` associated with the coinbase output - pub fn script_pubkey(&self) -> ScriptBuf { - self.script_pubkey.clone() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn fixed_vector_addr() { - // Valid - assert_eq!( - CoinbaseRewardScript::from_descriptor( - "addr(1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2)#wdnlkpe8" - ) - .unwrap() - .script_pubkey() - .to_hex_string(), - "76a91477bff20c60e522dfaa3350c39b030a5d004e839a88ac", - ); - assert_eq!( - CoinbaseRewardScript::from_descriptor( - "addr(3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy)#rsjl0crt" - ) - .unwrap() - .script_pubkey() - .to_hex_string(), - "a914b472a266d0bd89c13706a4132ccfb16f7c3b9fcb87", - ); - assert_eq!( - CoinbaseRewardScript::from_descriptor( - "addr(bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4)#uyjndxcw" - ) - .unwrap() - .script_pubkey() - .to_hex_string(), - "0014751e76e8199196d454941c45d1b3a323f1433bd6", - ); - assert_eq!( - CoinbaseRewardScript::from_descriptor( - "addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)#8kzm8txf" - ) - .unwrap() - .script_pubkey() - .to_hex_string(), - "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", - ); - // no checksum is ok - assert_eq!( - CoinbaseRewardScript::from_descriptor("addr(1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2)") - .unwrap() - .script_pubkey() - .to_hex_string(), - "76a91477bff20c60e522dfaa3350c39b030a5d004e839a88ac", - ); - assert_eq!( - CoinbaseRewardScript::from_descriptor("addr(1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2,)") - .unwrap_err() - .to_string(), - "Found addr() descriptor with 2 children; must be exactly one valid address", - ); - - // Invalid - // But empty checksum is not (in Miniscript 13 these error messages will be cleaner) - assert_eq!( - CoinbaseRewardScript::from_descriptor("addr(1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2)#") - .unwrap_err() - .to_string(), - "Miniscript: Invalid descriptor: Invalid checksum '', expected 'wdnlkpe8'", - ); - assert_eq!( - CoinbaseRewardScript::from_descriptor( - "addr(1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2)#wdnlkpe7" - ) - .unwrap_err() - .to_string(), - "Miniscript: Invalid descriptor: Invalid checksum 'wdnlkpe7', expected 'wdnlkpe8'", - ); - // Bad base58ck checksum even though the descriptor checksum is OK. Note that rust-bitcoin - // 0.32 interprets bad bech32 checksums as "base58 errors" because it doesn't know - // what encoding an invalid string is supposed to have. See https://github.com/rust-bitcoin/rust-bitcoin/issues/3044 - // Expected error: "Bitcoin address: base58 error: incorrect checksum: base58 checksum - // 0x6c7615f4 does not match expected 0x6b7615f4" (hex-conservative v0.3.0) - // or "Bitcoin address: base58 error" (hex-conservative v0.2.1) - assert!(CoinbaseRewardScript::from_descriptor( - "addr(1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN3)#5v55uzec" - ) - .is_err()); - // Expected error: "Bitcoin address: base58 error: decode: invalid base58 character 0x30" - // (hex-conservative v0.3.0) or "Bitcoin address: base58 error" (hex-conservative - // v0.2.1) - assert!(CoinbaseRewardScript::from_descriptor( - "addr(bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t3)#wfr7lfxf" - ) - .is_err()); - // Flagrantly bad stuff -- should probably PR these upstream to rust-miniscript. - // Expected error: "Bitcoin address: base58 error: too short: base58 decoded data was not - // long enough, must be at least 4 byte: 0" (hex-conservative v0.3.0) or "Bitcoin - // address: base58 error" (hex-conservative v0.2.1) - assert!(CoinbaseRewardScript::from_descriptor("addr()").is_err()); - assert_eq!( - CoinbaseRewardScript::from_descriptor("addr(It's a mad mad world!?! ๐Ÿ™ƒ)") - .unwrap_err() - .to_string(), - "Miniscript: unprintable character 0xf0", - ); - // This error is just wrong lol. Fixed in Miniscript 13. - assert_eq!( - CoinbaseRewardScript::from_descriptor("addr(It's a mad mad world!?! ๐Ÿ™ƒ)#abcdefg") - .unwrap_err() - .to_string(), - "Miniscript: Invalid descriptor: Invalid character in checksum: '๐Ÿ™ƒ'", - ); - // Expected error: "Bitcoin address: base58 error: decode: invalid base58 character 0x49" - // (hex-conservative v0.3.0) or "Bitcoin address: base58 error" (hex-conservative - // v0.2.1) - assert!( - CoinbaseRewardScript::from_descriptor("addr(It's a mad mad world!?!)#hmeprl29") - .is_err() - ); - assert_eq!( - CoinbaseRewardScript::from_descriptor("addr(It's a mad mad world!?!)#๐Ÿ™ƒ๐Ÿ™ƒ๐Ÿ™ƒ๐Ÿ™ƒ๐Ÿ™ƒ๐Ÿ™ƒ") - .unwrap_err() - .to_string(), - "Miniscript: Invalid descriptor: Invalid checksum '๐Ÿ™ƒ๐Ÿ™ƒ๐Ÿ™ƒ๐Ÿ™ƒ๐Ÿ™ƒ๐Ÿ™ƒ', expected 'hmeprl29'", - ); - } - - #[test] - fn fixed_vector_combo() { - // We do not support combo descriptors. Nobody should. - assert_eq!( - CoinbaseRewardScript::from_descriptor( - "combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)" - ) - .unwrap_err() - .to_string(), - "Miniscript: unexpected ยซcombo(1 args) while parsing Miniscriptยป" - ); - } - - #[test] - fn fixed_vector_musig() { - // We do not support musig descriptors. One day. - assert_eq!( - CoinbaseRewardScript::from_descriptor("musig(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556)").unwrap_err().to_string(), - "Miniscript: unexpected ยซmusig(2 args) while parsing Miniscriptยป" - ); - assert_eq!( - CoinbaseRewardScript::from_descriptor("tr(musig(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))").unwrap_err().to_string(), - "Miniscript: expected )", - ); - } - - #[test] - fn fixed_vector_raw() { - // Empty raw descriptors are OK; correspond to the empty script. - assert_eq!( - CoinbaseRewardScript::from_descriptor("raw()") - .unwrap() - .script_pubkey() - .to_hex_string(), - "", - ); - assert_eq!( - CoinbaseRewardScript::from_descriptor("raw(deadbeef)") - .unwrap() - .script_pubkey() - .to_hex_string(), - "deadbeef", - ); - assert_eq!( - CoinbaseRewardScript::from_descriptor("raw(DEADBEEF)") - .unwrap() - .script_pubkey() - .to_hex_string(), - "deadbeef", - ); - // Should we allow this? We do, so I guess we should test it and make sure we don't stop.. - assert_eq!( - CoinbaseRewardScript::from_descriptor("raw(DEADbeef)") - .unwrap() - .script_pubkey() - .to_hex_string(), - "deadbeef", - ); - // Expected error: "Decoding hex-formatted script: odd length, failed to create bytes from - // hex: odd hex string length 1" (hex-conservative v0.3.0) or "Decoding - // hex-formatted script: odd length, failed to create bytes from hex" (hex-conservative - // v0.2.1) - assert!(CoinbaseRewardScript::from_descriptor("raw(0)").is_err()); - assert_eq!( - CoinbaseRewardScript::from_descriptor("raw(0,1)") - .unwrap_err() - .to_string(), - "Found raw() descriptor with 2 children; must be exactly one hex-encoded script", - ); - } - - #[test] - fn fixed_vector_miniscript() { - assert_eq!( - CoinbaseRewardScript::from_descriptor("sh(wsh(multi(2,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556)))#qpcmf2lu").unwrap().script_pubkey().to_hex_string(), - "a9141cb55de50b72c67709ab16307d69557e6bb1a98787", - ); - assert_eq!( - CoinbaseRewardScript::from_descriptor( - "tr(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)" - ) - .unwrap() - .script_pubkey() - .to_hex_string(), - "5120da4710964f7852695de2da025290e24af6d8c281de5a0b902b7135fd9fd74d21", - ); - assert_eq!( - CoinbaseRewardScript::from_descriptor("tr(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,{pk(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),{multi_a(2,026a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4,0231ecbfac95d972f0b8f81ec6e01e9c621d91a4b48d5f9d12d7e95febe9f34d64),multi_a(2,026a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4,0231ecbfac95d972f0b8f81ec6e01e9c621d91a4b48d5f9d12d7e95febe9f34d64)}})") - .unwrap() - .script_pubkey() - .to_hex_string(), - "5120493bdae0d225af5cb88c4cb2a1e1e89e391153ba7699c91ebee2fd082ed1636c", - ); - } - - #[test] - fn fixed_vector_keys() { - // xpub - assert_eq!( - CoinbaseRewardScript::from_descriptor("pkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)").unwrap().script_pubkey().to_hex_string(), - "76a9143442193e1bb70916e914552172cd4e2dbc9df81188ac", - ); - // xpub with non-hardened path - assert_eq!( - CoinbaseRewardScript::from_descriptor("pkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/1/2/3)").unwrap().script_pubkey().to_hex_string(), - "76a914f2d2e1401c88353c2298d1a928d4ed827ff46ff688ac", - ); - // xpub with hardened path (not allowed) - assert_eq!( - CoinbaseRewardScript::from_descriptor("pkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/1'/2/3)").unwrap_err().to_string(), - "Miniscript: unexpected ยซcannot parse multi-path keys, keys with a wildcard or keys with hardened derivation steps as a DerivedDescriptorKeyยป", - ); - // no wildcards allowed (at least for now; gmax thinks it would be cool if we would - // instantiate it with the blockheight or something, but need to work out UX) - assert_eq!( - CoinbaseRewardScript::from_descriptor("pkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/*)").unwrap_err().to_string(), - "Miniscript: unexpected ยซcannot parse multi-path keys, keys with a wildcard or keys with hardened derivation steps as a DerivedDescriptorKeyยป", - ); - // No multipath descriptors allowed; this is not a wallet with change - assert_eq!( - CoinbaseRewardScript::from_descriptor("pkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/<0;1>)").unwrap_err().to_string(), - "Miniscript: unexpected ยซcannot parse multi-path keys, keys with a wildcard or keys with hardened derivation steps as a DerivedDescriptorKeyยป", - ); - // Private keys are not allowed, or xprvs. - assert_eq!( - CoinbaseRewardScript::from_descriptor( - "pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)" - ) - .unwrap_err() - .to_string(), - "Miniscript: unexpected ยซKey too short (<66 char), doesn't match any formatยป", - ); - // This is a confusing error message which should be fixed in Miniscript 13. - assert_eq!( - CoinbaseRewardScript::from_descriptor("pkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi)").unwrap_err().to_string(), - "Miniscript: unexpected ยซPublic keys must be 64/66/130 characters in sizeยป", - ); - } -} diff --git a/roles/stratum-apps/src/config_helpers/coinbase_output/serde_types.rs b/roles/stratum-apps/src/config_helpers/coinbase_output/serde_types.rs deleted file mode 100644 index d0399fef92..0000000000 --- a/roles/stratum-apps/src/config_helpers/coinbase_output/serde_types.rs +++ /dev/null @@ -1,136 +0,0 @@ -use core::convert::TryFrom; -use miniscript::bitcoin::{ - secp256k1::{All, Secp256k1}, - PublicKey, ScriptBuf, ScriptHash, WScriptHash, XOnlyPublicKey, -}; - -use super::Error; - -#[derive(serde::Deserialize)] -pub(super) struct LegacyCoinbaseOutput { - /// Specifies type of the script used in the output. - /// - /// Supported values include: - /// - `"P2PK"`: Pay-to-Public-Key - /// - `"P2PKH"`: Pay-to-Public-Key-Hash - /// - `"P2SH"`: Pay-to-Script-Hash - /// - `"P2WPKH"`: Pay-to-Witness-Public-Key-Hash:w - - /// - `"P2WSH"`: Pay-to-Witness-Script-Hash - /// - `"P2TR"`: Pay-to-Taproot - pub(super) output_script_type: String, - - /// Value associated with the script, typically a public key or script hash. - /// - /// This field's interpretation depends on the `output_script_type`: - /// - For `"P2PK"`: The raw public key. - /// - For `"P2PKH"`: A public key hash. - /// - For `"P2WPKH"`: A witness public key hash. - /// - For `"P2SH"`: A script hash. - /// - For `"P2WSH"`: A witness script hash. - /// - For `"P2TR"`: An x-only public key. - pub(super) output_script_value: String, -} - -impl TryFrom for super::CoinbaseRewardScript { - type Error = super::Error; - fn try_from(value: LegacyCoinbaseOutput) -> Result { - let script_pubkey = match value.output_script_type.as_str() { - "TEST" => { - let pub_key_hash = value - .output_script_value - .parse::() - .map_err(|_| Error::InvalidOutputScript)? - .pubkey_hash(); - ScriptBuf::new_p2pkh(&pub_key_hash) - } - "P2PK" => { - let pub_key = value - .output_script_value - .parse::() - .map_err(|_| Error::InvalidOutputScript)?; - ScriptBuf::new_p2pk(&pub_key) - } - "P2PKH" => { - let pub_key_hash = value - .output_script_value - .parse::() - .map_err(|_| Error::InvalidOutputScript)? - .pubkey_hash(); - ScriptBuf::new_p2pkh(&pub_key_hash) - } - "P2WPKH" => { - let w_pub_key_hash = value - .output_script_value - .parse::() - .map_err(|_| Error::InvalidOutputScript)? - .wpubkey_hash() - .unwrap(); - ScriptBuf::new_p2wpkh(&w_pub_key_hash) - } - "P2SH" => { - let script_hashed = value - .output_script_value - .parse::() - .map_err(|_| Error::InvalidOutputScript)?; - ScriptBuf::new_p2sh(&script_hashed) - } - "P2WSH" => { - let w_script_hashed = value - .output_script_value - .parse::() - .map_err(|_| Error::InvalidOutputScript)?; - ScriptBuf::new_p2wsh(&w_script_hashed) - } - "P2TR" => { - // From the bip - // - // Conceptually, every Taproot output corresponds to a combination of - // a single public key condition (the internal key), - // and zero or more general conditions encoded in scripts organized in a tree. - let pub_key = value - .output_script_value - .parse::() - .map_err(|_| Error::InvalidOutputScript)?; - ScriptBuf::new_p2tr::(&Secp256k1::::new(), pub_key, None) - } - _ => return Err(Error::UnknownOutputScriptType), - }; - Ok(Self { - script_pubkey, - // legacy encoding gives no way to specify testnet or mainnet - ok_for_mainnet: true, - }) - } -} - -/// A coinbase output script as it appears in a configuration file. -/// -/// Private to avoid exposing the enum constructors. -#[derive(serde::Deserialize)] -#[serde(untagged)] // decode as whichever variant makes sense for the input -enum SerdeCoinbaseOutputInner { - Legacy(LegacyCoinbaseOutput), - Descriptor(String), -} - -/// A structure representing a coinbase output script as it appears in a -/// configuration file. -/// -/// Can only be constructed via serde, and supports no operations except conversion -/// to a [`super::CoinbaseOutput`] via [`TryFrom`]. -#[derive(serde::Deserialize)] -#[serde(transparent)] -pub struct SerdeCoinbaseOutput { - inner: SerdeCoinbaseOutputInner, -} - -impl TryFrom for super::CoinbaseRewardScript { - type Error = super::Error; - fn try_from(value: SerdeCoinbaseOutput) -> Result { - match value.inner { - SerdeCoinbaseOutputInner::Legacy(legacy) => Self::try_from(legacy), - SerdeCoinbaseOutputInner::Descriptor(ref s) => Self::from_descriptor(s), - } - } -} diff --git a/roles/stratum-apps/src/config_helpers/logging.rs b/roles/stratum-apps/src/config_helpers/logging.rs deleted file mode 100644 index 4273c8c602..0000000000 --- a/roles/stratum-apps/src/config_helpers/logging.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::{ - fs::OpenOptions, - io::{self, IsTerminal}, - path::Path, - str::FromStr, -}; -use tracing::level_filters::LevelFilter; -use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry}; - -/// Initialize logging to stdout and optionally to a file. -/// -/// If `log_file` is Some, logs will be written to both stdout and the file. -/// If `log_level` is not provided or is invalid, it defaults to "info". -pub fn init_logging(log_file: Option<&Path>) { - let rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()); - let log_level_filter = LevelFilter::from_str(&rust_log).unwrap_or(LevelFilter::INFO); - let env_filter = EnvFilter::new(log_level_filter.to_string()); - let stdout_layer = fmt::layer() - .with_writer(io::stdout) - .with_ansi(io::stdout().is_terminal()); - - let subscriber: Box = match log_file { - Some(path) => { - // Log to both file and stdout - let path = path.to_owned(); - let file_layer = fmt::layer() - .with_writer(move || { - OpenOptions::new() - .create(true) - .append(true) - .open(&path) - .expect("Failed to open log file") - }) - .with_ansi(false); - Box::new( - Registry::default() - .with(env_filter) - .with(stdout_layer) - .with(file_layer), - ) - } - None => { - // Log only to stdout - Box::new(Registry::default().with(env_filter).with(stdout_layer)) - } - }; - - tracing::subscriber::set_global_default(subscriber).expect("Failed to set global subscriber"); -} diff --git a/roles/stratum-apps/src/config_helpers/mod.rs b/roles/stratum-apps/src/config_helpers/mod.rs deleted file mode 100644 index 1f051d9460..0000000000 --- a/roles/stratum-apps/src/config_helpers/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Configuration management helpers for SV2 applications -//! -//! This module provides utilities for: -//! - Parsing configuration files (TOML, etc.) -//! - Handling coinbase output specifications -//! - Setting up logging and tracing -//! -//! Originally from the `config_helpers_sv2` crate. - -mod coinbase_output; -pub use coinbase_output::{CoinbaseRewardScript, Error as CoinbaseOutputError}; - -pub mod logging; - -mod toml; -pub use toml::duration_from_toml; diff --git a/roles/stratum-apps/src/config_helpers/toml.rs b/roles/stratum-apps/src/config_helpers/toml.rs deleted file mode 100644 index 9e11497aa7..0000000000 --- a/roles/stratum-apps/src/config_helpers/toml.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::time::Duration; - -/// Deserialize a duration from a TOML string. -pub fn duration_from_toml<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - use serde::Deserialize; - - #[derive(serde::Deserialize)] - struct Helper { - unit: String, - value: u64, - } - - let helper = Helper::deserialize(deserializer)?; - match helper.unit.as_str() { - "seconds" => Ok(Duration::from_secs(helper.value)), - "secs" => Ok(Duration::from_secs(helper.value)), - "s" => Ok(Duration::from_secs(helper.value)), - "milliseconds" => Ok(Duration::from_millis(helper.value)), - "millis" => Ok(Duration::from_millis(helper.value)), - "ms" => Ok(Duration::from_millis(helper.value)), - "microseconds" => Ok(Duration::from_micros(helper.value)), - "micros" => Ok(Duration::from_micros(helper.value)), - "us" => Ok(Duration::from_micros(helper.value)), - "nanoseconds" => Ok(Duration::from_nanos(helper.value)), - "nanos" => Ok(Duration::from_nanos(helper.value)), - "ns" => Ok(Duration::from_nanos(helper.value)), - // ... add other units as needed - _ => Err(serde::de::Error::custom("Unsupported duration unit")), - } -} diff --git a/roles/stratum-apps/src/custom_mutex.rs b/roles/stratum-apps/src/custom_mutex.rs deleted file mode 100644 index 8b23742da9..0000000000 --- a/roles/stratum-apps/src/custom_mutex.rs +++ /dev/null @@ -1,115 +0,0 @@ -//! # Collection of Helper Primitives -//! -//! Provides a collection of utilities and helper structures used throughout the Stratum V2 -//! protocol implementation. These utilities simplify common tasks, such as ID generation and -//! management, mutex management, difficulty target calculations, merkle root calculations, and -//! more. - -use std::sync::{Mutex as Mutex_, MutexGuard, PoisonError}; - -/// Custom synchronization primitive for managing shared mutable state. -/// -/// This custom mutex implementation builds on [`std::sync::Mutex`] to enhance usability and safety -/// in concurrent environments. It provides ergonomic methods to safely access and modify inner -/// values while reducing the risk of deadlocks and panics. It is used throughout SRI applications -/// to managed shared state across multiple threads, such as tracking active mining sessions, -/// routing jobs, and managing connections safely and efficiently. -/// -/// ## Advantages -/// - **Closure-Based Locking:** The `safe_lock` method encapsulates the locking process, ensuring -/// the lock is automatically released after the closure completes. -/// - **Error Handling:** `safe_lock` enforces explicit handling of potential [`PoisonError`] -/// conditions, reducing the risk of panics caused by poisoned locks. -/// - **Panic-Safe Option:** The `super_safe_lock` method provides an alternative that unwraps the -/// result of `safe_lock`, with optional runtime safeguards against panics. -/// - **Extensibility:** Includes feature-gated functionality to customize behavior, such as -/// stricter runtime checks using external tools like -/// [`no-panic`](https://github.com/dtolnay/no-panic). -#[derive(Debug)] -pub struct Mutex(Mutex_); - -impl Mutex { - /// Mutex safe lock. - /// - /// Safely locks the `Mutex` and executes a closer (`thunk`) with a mutable reference to the - /// inner value. This ensures that the lock is automatically released after the closure - /// completes, preventing deadlocks. It explicitly returns a [`PoisonError`] containing a - /// [`MutexGuard`] to the inner value in cases where the lock is poisoned. - /// - /// To prevent poison lock errors, unwraps should never be used within the closure. The result - /// should always be returned and handled outside of the sage lock. - pub fn safe_lock(&self, thunk: F) -> Result>> - where - F: FnOnce(&mut T) -> Ret, - { - let mut lock = self.0.lock()?; - let return_value = thunk(&mut *lock); - drop(lock); - Ok(return_value) - } - - /// Mutex super safe lock. - /// - /// Locks the `Mutex` and executes a closure (`thunk`) with a mutable reference to the inner - /// value, panicking if the lock is poisoned. - /// - /// This is a convenience wrapper around `safe_lock` for cases where explicit error handling is - /// unnecessary or undesirable. Use with caution in production code. - pub fn super_safe_lock(&self, thunk: F) -> Ret - where - F: FnOnce(&mut T) -> Ret, - { - //#[cfg(feature = "disable_nopanic")] - { - self.safe_lock(thunk).unwrap() - } - //#[cfg(not(feature = "disable_nopanic"))] - //{ - // // based on https://github.com/dtolnay/no-panic - // struct __NoPanic; - // extern "C" { - // #[link_name = "super_safe_lock called on a function that may panic"] - // fn trigger() -> !; - // } - // impl core::ops::Drop for __NoPanic { - // fn drop(&mut self) { - // unsafe { - // trigger(); - // } - // } - // } - // let mut lock = self.0.lock().expect("threads to never panic"); - // let __guard = __NoPanic; - // let return_value = thunk(&mut *lock); - // core::mem::forget(__guard); - // drop(lock); - // return_value - //} - } - - /// Creates a new [`Mutex`] instance, storing the initial value inside. - pub fn new(v: T) -> Self { - Mutex(Mutex_::new(v)) - } - - /// Removes lock for direct access. - /// - /// Acquires a lock on the [`Mutex`] and returns a [`MutexGuard`] for direct access to the - /// inner value. Allows for manual lock handling and is useful in scenarios where closures are - /// not convenient. - pub fn to_remove(&self) -> Result, PoisonError>> { - self.0.lock() - } -} - -#[cfg(test)] -mod tests { - - #[test] - fn test_super_safe_lock() { - let m = super::Mutex::new(1u32); - m.safe_lock(|i| *i += 1).unwrap(); - // m.super_safe_lock(|i| *i = (*i).checked_add(1).unwrap()); // will not compile - m.super_safe_lock(|i| *i = (*i).checked_add(1).unwrap_or_default()); // compiles - } -} diff --git a/roles/stratum-apps/src/key_utils/mod.rs b/roles/stratum-apps/src/key_utils/mod.rs deleted file mode 100644 index bc02fe4b3b..0000000000 --- a/roles/stratum-apps/src/key_utils/mod.rs +++ /dev/null @@ -1,265 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate alloc; - -use alloc::{ - string::{String, ToString}, - vec::Vec, -}; -use bs58::{decode, decode::Error as Bs58DecodeError}; -use core::{convert::TryFrom, fmt::Display, str::FromStr}; -use secp256k1::{ - schnorr::Signature, Keypair, Message as SecpMessage, Secp256k1, SecretKey, SignOnly, - VerifyOnly, XOnlyPublicKey, -}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug)] -pub enum Error { - Bs58Decode(Bs58DecodeError), - Secp256k1(secp256k1::Error), - KeyVersion(u16), - KeyLength, - Custom(String), -} - -impl Display for Error { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Bs58Decode(error) => write!(f, "Base58 code error: {error}"), - Self::Secp256k1(error) => write!(f, "Secp256k1 error: {error}"), - Self::KeyVersion(obtained) => { - write!(f, "Unknown public key version. version found: {obtained}") - } - Self::KeyLength => write!(f, "Bad key length"), - Self::Custom(error) => write!(f, "Custom error: {error}"), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for Error {} -#[cfg(not(feature = "std"))] -#[rustversion::since(1.81)] -impl core::error::Error for Error {} - -impl From for Error { - fn from(e: Bs58DecodeError) -> Self { - Error::Bs58Decode(e) - } -} - -impl From for Error { - fn from(e: secp256k1::Error) -> Self { - Error::Secp256k1(e) - } -} - -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] -#[serde(into = "String", try_from = "String")] -pub struct Secp256k1SecretKey(pub SecretKey); - -impl TryFrom for Secp256k1SecretKey { - type Error = Error; - - fn try_from(value: String) -> Result { - value.parse() - } -} - -impl FromStr for Secp256k1SecretKey { - type Err = Error; - - fn from_str(value: &str) -> Result { - let decoded = decode(value).with_check(None).into_vec()?; - let secret = SecretKey::from_slice(&decoded)?; - Ok(Secp256k1SecretKey(secret)) - } -} - -impl From for String { - fn from(secret: Secp256k1SecretKey) -> Self { - secret.to_string() - } -} - -impl Display for Secp256k1SecretKey { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - let bytes = self.0.secret_bytes(); - f.write_str(&bs58::encode(bytes).with_check().into_string()) - } -} - -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] -#[serde(into = "String", try_from = "String")] -pub struct Secp256k1PublicKey(pub XOnlyPublicKey); - -impl TryFrom for Secp256k1PublicKey { - type Error = Error; - - fn try_from(value: String) -> Result { - value.parse() - } -} - -impl FromStr for Secp256k1PublicKey { - type Err = Error; - - fn from_str(value: &str) -> Result { - let decoded = decode(value).with_check(None).into_vec()?; - if decoded.len() < 34 { - return Err(Error::KeyLength); - } - let key_version = - u16::from_le_bytes(decoded[..2].try_into().expect("Invalid array length")); - if key_version != 1 { - return Err(Error::KeyVersion(key_version)); - } - let public = XOnlyPublicKey::from_slice(&decoded[2..]).map_err(Error::Secp256k1)?; - Ok(Secp256k1PublicKey(public)) - } -} - -impl From for String { - fn from(public: Secp256k1PublicKey) -> Self { - public.to_string() - } -} - -impl Display for Secp256k1PublicKey { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let mut output = [0_u8; 34]; - output[0] = 1; - let bytes = self.0.serialize(); - output[2..].copy_from_slice(&bytes); - f.write_str(&bs58::encode(&output).with_check().into_string()) - } -} - -impl Secp256k1PublicKey { - pub fn into_bytes(self) -> [u8; 32] { - self.0.serialize() - } -} -impl Secp256k1SecretKey { - pub fn into_bytes(self) -> [u8; 32] { - self.0.secret_bytes() - } -} - -impl From for Secp256k1PublicKey { - fn from(value: Secp256k1SecretKey) -> Self { - let context = secp256k1::Secp256k1::new(); - let (x_coordinate, _) = value.0.public_key(&context).x_only_public_key(); - Self(x_coordinate) - } -} - -pub struct SignatureService { - secp_sign: Secp256k1, - secp_verify: Secp256k1, -} - -impl SignatureService { - pub fn new() -> Self { - SignatureService { - secp_sign: Secp256k1::signing_only(), - secp_verify: Secp256k1::verification_only(), - } - } - - #[cfg(feature = "std")] - pub fn sign(&self, message: Vec, private_key: SecretKey) -> Signature { - self.sign_with_rng(message, private_key, &mut rand::thread_rng()) - } - - #[inline] - pub fn sign_with_rng( - &self, - message: Vec, - private_key: SecretKey, - rng: &mut R, - ) -> Signature { - let secret_key = private_key; - let kp = Keypair::from_secret_key(&self.secp_sign, &secret_key); - - self.secp_sign.sign_schnorr_with_rng( - &SecpMessage::from_digest_slice(&message).unwrap(), - &kp, - rng, - ) - } - - pub fn verify( - &self, - message: Vec, - signature: secp256k1::schnorr::Signature, - public_key: XOnlyPublicKey, - ) -> Result<(), secp256k1::Error> { - let x_only_public_key = public_key; - - // Verify signature - self.secp_verify.verify_schnorr( - &signature, - &secp256k1::Message::from_digest_slice(&message)?, - &x_only_public_key, - ) - } -} - -impl Default for SignatureService { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn key_conversions() { - let secret_key = "zmBEmPhqo3A92FkiLVvyCz6htc3e53ph3ZbD4ASqGaLjwnFLi"; - let public_key = "9bDuixKmZqAJnrmP746n8zU1wyAQRrus7th9dxnkPg6RzQvCnan"; - let bad_public_key1 = "9bDuixKmZqAJnrmP746n8zU1wyAQRrus7th9dxnkPg6RzQvCnam"; // invalid checksum (swapped char) - let bad_public_key2 = "2myPhc5vkPzuC5FXNK5tee79WmP7uoLh55SxezoF8iqwF3E3rnPY"; // invalid version (version 12) - let bad_public_key3 = "2wmHTKZkLg2QzXyEXGMBXzKP7JXDUt8yy9SA5hoQwERc92qR6c"; // invalid length (1 B missing) - - let error = bad_public_key1 - .parse::() - .expect_err("Bad bud public key failed to raise error"); - assert!( - matches!(error, Error::Bs58Decode(_)), - "expected failed checksum error, got {}", - error - ); - let error = bad_public_key2 - .parse::() - .expect_err("Bad bud public key failed to raise error"); - assert!( - matches!(error, Error::KeyVersion(_)), - "expected invalid key version error, got {}", - error - ); - let error = bad_public_key3 - .parse::() - .expect_err("Bad bud public key failed to raise error"); - assert!( - matches!(error, Error::KeyLength), - "expected invalid key length error, got {}", - error - ); - - let parsed_key = secret_key - .parse::() - .expect("Invalid test key"); - - let calculated_public_key = Secp256k1PublicKey::from(parsed_key); - assert_eq!(calculated_public_key.to_string(), public_key); - - let parsed_public_key = public_key - .parse::() - .expect("Invalid test pubkey"); - assert_eq!(calculated_public_key.0, parsed_public_key.0); - } -} diff --git a/roles/stratum-apps/src/lib.rs b/roles/stratum-apps/src/lib.rs deleted file mode 100644 index 37a88b8cdd..0000000000 --- a/roles/stratum-apps/src/lib.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! # Stratum Apps - SV2 Application Utilities -//! -//! This crate consolidates the essential utilities needed for building Stratum V2 applications. -//! It combines the functionality from the original separate utility crates into a single, -//! well-organized library with feature-based compilation. -//! -//! ## Features -//! -//! ### Core Features -//! - `network` - High-level networking utilities (enabled by default) -//! - `config` - Configuration management helpers (enabled by default) -//! - `rpc` - RPC utilities with custom types for JSON-RPC communication (optional) -//! -//! ### Role-Specific Feature Bundles -//! - `pool` - Everything needed for pool applications -//! - `jd_client` - Everything needed for JD client applications -//! - `jd_server` - Everything needed for JD server applications (includes RPC) -//! - `translator` - Everything needed for translator applications (includes SV1) -//! - `mining_device` - Everything needed for mining device applications -//! -//! ## Modules -//! -//! - [`network_helpers`] - High-level networking utilities for SV2 connections -//! - [`config_helpers`] - Configuration management and parsing utilities -//! - [`rpc`] - RPC utilities with custom serializable types (`Hash`, `BlockHash`, `Amount`) - -/// Re-export all the modules from `stratum_core` -#[cfg(feature = "core")] -pub use stratum_core; - -/// High-level networking utilities for SV2 connections -/// -/// Provides connection management, encrypted streams, and protocol handling. -/// Originally from the `network_helpers_sv2` crate. -#[cfg(feature = "network")] -pub mod network_helpers; - -/// Configuration management helpers -/// -/// Utilities for parsing configuration files, handling coinbase outputs, -/// and setting up logging. Originally from the `config_helpers_sv2` crate. -#[cfg(feature = "config")] -pub mod config_helpers; - -/// Custom Mutex -/// -/// A wrapper around std::sync::Mutex -pub mod custom_mutex; -/// RPC utilities for Job Declaration Server -/// -/// HTTP-based RPC server implementation for JD Server functionality. -/// Originally from the `rpc_sv2` crate. -#[cfg(feature = "rpc")] -pub mod rpc; - -/// Key utilities for cryptographic operations -/// -/// Provides Secp256k1 key management, serialization/deserialization, and signature services. -/// Supports both standard and no_std environments. -pub mod key_utils; diff --git a/roles/stratum-apps/src/network_helpers/mod.rs b/roles/stratum-apps/src/network_helpers/mod.rs deleted file mode 100644 index 93ee6a0242..0000000000 --- a/roles/stratum-apps/src/network_helpers/mod.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! High-level networking utilities for SV2 connections -//! -//! This module provides connection management, encrypted streams, and protocol handling -//! for Stratum V2 applications. It includes support for: -//! -//! - Noise-encrypted connections ([`noise_connection`], [`noise_stream`]) -//! - Plain TCP connections ([`plain_connection`]) -//! - SV1 protocol connections ([`sv1_connection`]) - when `sv1` feature is enabled -//! -//! Originally from the `network_helpers_sv2` crate. - -pub mod noise_connection; -pub mod noise_stream; -pub mod plain_connection; - -#[cfg(feature = "sv1")] -pub mod sv1_connection; - -use async_channel::{RecvError, SendError}; -use stratum_core::codec_sv2::Error as CodecError; - -/// Networking errors that can occur in SV2 connections -#[derive(Debug)] -pub enum Error { - /// Invalid handshake message received from remote peer - HandshakeRemoteInvalidMessage, - /// Error from the codec layer - CodecError(CodecError), - /// Error receiving from async channel - RecvError, - /// Error sending to async channel - SendError, - /// Socket was closed, likely by the peer - SocketClosed, -} - -impl From for Error { - fn from(e: CodecError) -> Self { - Error::CodecError(e) - } -} - -impl From for Error { - fn from(_: RecvError) -> Self { - Error::RecvError - } -} - -impl From> for Error { - fn from(_: SendError) -> Self { - Error::SendError - } -} diff --git a/roles/stratum-apps/src/network_helpers/noise_connection.rs b/roles/stratum-apps/src/network_helpers/noise_connection.rs deleted file mode 100644 index 4e82b609e9..0000000000 --- a/roles/stratum-apps/src/network_helpers/noise_connection.rs +++ /dev/null @@ -1,139 +0,0 @@ -#![allow(clippy::new_ret_no_self)] -use crate::network_helpers::{ - noise_stream::{NoiseTcpReadHalf, NoiseTcpStream, NoiseTcpWriteHalf}, - Error, -}; -use async_channel::{unbounded, Receiver, Sender}; -use std::sync::Arc; -use stratum_core::{ - binary_sv2::{Deserialize, GetSize, Serialize}, - codec_sv2::{HandshakeRole, StandardEitherFrame}, -}; -use tokio::{net::TcpStream, task}; -use tracing::{debug, error}; - -pub struct Connection; - -struct ConnectionState { - sender_incoming: Sender>, - receiver_incoming: Receiver>, - sender_outgoing: Sender>, - receiver_outgoing: Receiver>, -} - -impl ConnectionState { - fn close_all(&self) { - self.sender_incoming.close(); - self.receiver_incoming.close(); - self.sender_outgoing.close(); - self.receiver_outgoing.close(); - } -} - -impl Connection { - pub async fn new( - stream: TcpStream, - role: HandshakeRole, - ) -> Result< - ( - Receiver>, - Sender>, - ), - Error, - > - where - Message: Serialize + Deserialize<'static> + GetSize + Send + 'static, - { - let (sender_incoming, receiver_incoming) = unbounded(); - let (sender_outgoing, receiver_outgoing) = unbounded(); - - let conn_state = Arc::new(ConnectionState { - sender_incoming, - receiver_incoming: receiver_incoming.clone(), - sender_outgoing: sender_outgoing.clone(), - receiver_outgoing, - }); - - let (read_half, write_half) = NoiseTcpStream::::new(stream, role) - .await? - .into_split(); - - Self::spawn_reader(read_half, Arc::clone(&conn_state)); - Self::spawn_writer(write_half, conn_state); - - Ok((receiver_incoming, sender_outgoing)) - } - fn spawn_reader( - mut read_half: NoiseTcpReadHalf, - conn_state: Arc>, - ) -> task::JoinHandle<()> - where - Message: Serialize + Deserialize<'static> + GetSize + Send + 'static, - { - let sender_incoming = conn_state.sender_incoming.clone(); - - task::spawn(async move { - loop { - tokio::select! { - _ = tokio::signal::ctrl_c() => { - debug!("Reader received shutdown signal."); - break; - } - res = read_half.read_frame() => match res { - Ok(frame) => { - if sender_incoming.send(frame).await.is_err() { - error!("Reader: channel closed, shutting down."); - break; - } - } - Err(e) => { - error!("Reader: error while reading frame: {e:?}"); - break; - } - } - } - } - - conn_state.close_all(); - }) - } - - fn spawn_writer( - mut write_half: NoiseTcpWriteHalf, - conn_state: Arc>, - ) -> task::JoinHandle<()> - where - Message: Serialize + Deserialize<'static> + GetSize + Send + 'static, - { - let receiver_outgoing = conn_state.receiver_outgoing.clone(); - - task::spawn(async move { - loop { - tokio::select! { - _ = tokio::signal::ctrl_c() => { - debug!("Writer received shutdown signal."); - break; - } - res = receiver_outgoing.recv() => match res { - Ok(frame) => { - if let Err(e) = write_half.write_frame(frame).await { - error!("Writer: error while writing frame: {e:?}"); - break; - } - } - Err(_) => { - debug!("Writer: channel closed, shutting down."); - break; - } - } - } - } - - if let Err(e) = write_half.shutdown().await { - error!("Writer: error during shutdown: {e:?}"); - } - - conn_state.close_all(); - }) - } -} diff --git a/roles/stratum-apps/src/network_helpers/noise_stream.rs b/roles/stratum-apps/src/network_helpers/noise_stream.rs deleted file mode 100644 index a7078059b5..0000000000 --- a/roles/stratum-apps/src/network_helpers/noise_stream.rs +++ /dev/null @@ -1,332 +0,0 @@ -//! A Noise-encrypted wrapper around a `TcpStream`, providing framed read/write I/O using the SV2 -//! protocol and a stateful Noise handshake. -//! -//! This module provides `NoiseTcpStream`, which wraps a `TcpStream` and performs a Noise-based -//! authenticated key exchange based on the provided [`HandshakeRole`]. -//! -//! After a successful handshake, the stream can be split into a `NoiseTcpReadHalf` and -//! `NoiseTcpWriteHalf`, which support frame-based encoding/decoding of SV2 messages with optional -//! non-blocking behavior. - -use crate::network_helpers::Error; -use stratum_core::{ - binary_sv2::{Deserialize, GetSize, Serialize}, - codec_sv2::{HandshakeRole, NoiseEncoder, StandardNoiseDecoder, State}, - noise_sv2::INITIATOR_EXPECTED_HANDSHAKE_MESSAGE_SIZE, -}; -use tokio::net::{ - tcp::{OwnedReadHalf, OwnedWriteHalf}, - TcpStream, -}; - -use stratum_core::{ - codec_sv2::StandardEitherFrame, framing_sv2::framing::HandShakeFrame, - noise_sv2::ELLSWIFT_ENCODING_SIZE, -}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tracing::{debug, error}; - -/// A Noise-secured duplex stream over TCP that wraps a `TcpStream` -/// and provides secure read/write capabilities using the Noise protocol. -/// -/// This stream performs the full Noise handshake during construction -/// and returns a bidirectional encrypted stream split into read and write halves. -/// -/// **Note:** This struct is **not cancellation-safe**. -/// If `read_frame()` or `write_frame()` is canceled mid-way, -/// internal state may be left in an inconsistent state, which can lead to -/// protocol errors or dropped frames. -pub struct NoiseTcpStream + GetSize + Send + 'static> { - reader: NoiseTcpReadHalf, - writer: NoiseTcpWriteHalf, -} - -/// The reading half of a `NoiseTcpStream`. -/// -/// It buffers incoming encrypted bytes, attempts to decode full Noise frames, -/// and exposes a method to retrieve structured messages of type `Message`. -pub struct NoiseTcpReadHalf + GetSize + Send + 'static> { - reader: OwnedReadHalf, - decoder: StandardNoiseDecoder, - state: State, - current_frame_buf: Vec, - bytes_read: usize, -} - -/// The writing half of a `NoiseTcpStream`. -/// -/// It accepts structured messages, encodes them via the Noise protocol, -/// and writes the result to the socket. -pub struct NoiseTcpWriteHalf + GetSize + Send + 'static> { - writer: OwnedWriteHalf, - encoder: NoiseEncoder, - state: State, -} - -impl NoiseTcpStream -where - Message: Serialize + Deserialize<'static> + GetSize + Send + 'static, -{ - /// Constructs a new `NoiseTcpStream` over the given TCP stream, - /// performing the Noise handshake in the given `role`. - /// - /// On success, returns a stream with encrypted communication channels. - pub async fn new(stream: TcpStream, role: HandshakeRole) -> Result { - let (mut reader, mut writer) = stream.into_split(); - - let mut decoder = StandardNoiseDecoder::::new(); - let mut encoder = NoiseEncoder::::new(); - let mut state = State::initialized(role.clone()); - - match role { - HandshakeRole::Initiator(_) => { - let mut responder_state = State::not_initialized(&role); - let first_msg = state.step_0()?; - send_message(&mut writer, first_msg.into(), &mut state, &mut encoder).await?; - debug!("First handshake message sent"); - - loop { - match receive_message(&mut reader, &mut responder_state, &mut decoder).await { - Ok(second_msg) => { - debug!("Second handshake message received"); - let handshake_frame: HandShakeFrame = second_msg - .try_into() - .map_err(|_| Error::HandshakeRemoteInvalidMessage)?; - let payload: [u8; INITIATOR_EXPECTED_HANDSHAKE_MESSAGE_SIZE] = - handshake_frame - .get_payload_when_handshaking() - .try_into() - .map_err(|_| Error::HandshakeRemoteInvalidMessage)?; - let transport_state = state.step_2(payload)?; - state = transport_state; - break; - } - Err(Error::CodecError(stratum_core::codec_sv2::Error::MissingBytes(_))) => { - debug!("Waiting for more bytes during handshake"); - } - Err(e) => { - error!("Handshake failed with upstream: {:?}", e); - return Err(e); - } - } - } - } - HandshakeRole::Responder(_) => { - let mut initiator_state = State::not_initialized(&role); - - loop { - match receive_message(&mut reader, &mut initiator_state, &mut decoder).await { - Ok(first_msg) => { - debug!("First handshake message received"); - let handshake_frame: HandShakeFrame = first_msg - .try_into() - .map_err(|_| Error::HandshakeRemoteInvalidMessage)?; - let payload: [u8; ELLSWIFT_ENCODING_SIZE] = handshake_frame - .get_payload_when_handshaking() - .try_into() - .map_err(|_| Error::HandshakeRemoteInvalidMessage)?; - let (second_msg, transport_state) = state.step_1(payload)?; - send_message(&mut writer, second_msg.into(), &mut state, &mut encoder) - .await?; - debug!("Second handshake message sent"); - state = transport_state; - break; - } - Err(Error::CodecError(stratum_core::codec_sv2::Error::MissingBytes(_))) => { - debug!("Waiting for more bytes during handshake"); - } - Err(e) => { - error!("Handshake failed with downstream: {:?}", e); - return Err(e); - } - } - } - } - }; - Ok(Self { - reader: NoiseTcpReadHalf { - reader, - decoder, - state: state.clone(), - current_frame_buf: vec![], - bytes_read: 0, - }, - writer: NoiseTcpWriteHalf { - writer, - encoder, - state, - }, - }) - } - - /// Consumes the stream and returns its reader and writer halves. - pub fn into_split(self) -> (NoiseTcpReadHalf, NoiseTcpWriteHalf) { - (self.reader, self.writer) - } -} - -impl NoiseTcpWriteHalf -where - Message: Serialize + Deserialize<'static> + GetSize + Send + 'static, -{ - /// Encrypts and writes a full message frame to the socket. - /// - /// Returns an error if the socket is closed or the message cannot be encoded. - /// - /// Not cancellation-safe: A canceled write may cause partial writes or state corruption. - pub async fn write_frame(&mut self, frame: StandardEitherFrame) -> Result<(), Error> { - let buf = self.encoder.encode(frame, &mut self.state)?; - self.writer - .write_all(buf.as_ref()) - .await - .map_err(|_| Error::SocketClosed)?; - Ok(()) - } - - /// Attempts to write a message without blocking. - /// - /// Returns: - /// - `Ok(true)` if the entire frame was written successfully. - /// - `Ok(false)` if the socket is not ready (would block). - /// - `Err(_)` on socket or encoding errors. - pub fn try_write_frame(&mut self, frame: StandardEitherFrame) -> Result { - let buf = self.encoder.encode(frame, &mut self.state)?; - - match self.writer.try_write(buf.as_ref()) { - Ok(n) if n == buf.len() => Ok(true), - Ok(_) => Err(Error::SocketClosed), - Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(false), - Err(_) => Err(Error::SocketClosed), - } - } - - /// Gracefully shuts down the writing half of the stream. - /// - /// Returns an error if the shutdown fails. - pub async fn shutdown(&mut self) -> Result<(), Error> { - self.writer - .shutdown() - .await - .map_err(|_| Error::SocketClosed) - } -} - -impl NoiseTcpReadHalf -where - Message: Serialize + Deserialize<'static> + GetSize + Send + 'static, -{ - /// Reads and decodes a complete frame from the socket. - /// - /// This method blocks until a full frame is read and decoded, - /// handling `MissingBytes` errors from the codec automatically. - /// - /// Not cancellation-safe: Cancellation may leave partially-read state behind. - pub async fn read_frame(&mut self) -> Result, Error> { - loop { - let expected = self.decoder.writable_len(); - - if self.current_frame_buf.len() != expected { - self.current_frame_buf.resize(expected, 0); - self.bytes_read = 0; - } - - while self.bytes_read < expected { - let n = self - .reader - .read(&mut self.current_frame_buf[self.bytes_read..]) - .await - .map_err(|_| Error::SocketClosed)?; - - if n == 0 { - return Err(Error::SocketClosed); - } - - self.bytes_read += n; - } - - self.decoder - .writable() - .copy_from_slice(&self.current_frame_buf[..]); - - self.bytes_read = 0; - - match self.decoder.next_frame(&mut self.state) { - Ok(frame) => return Ok(frame), - Err(stratum_core::codec_sv2::Error::MissingBytes(_)) => { - tokio::task::yield_now().await; - continue; - } - Err(e) => return Err(Error::CodecError(e)), - } - } - } - - /// Attempts to read and decode a frame without blocking. - /// - /// Returns: - /// - `Ok(Some(frame))` if a full frame is successfully decoded. - /// - `Ok(None)` if not enough data is available yet. - /// - `Err(_)` on socket or decoding errors. - pub fn try_read_frame(&mut self) -> Result>, Error> { - let expected = self.decoder.writable_len(); - - if self.current_frame_buf.len() != expected { - self.current_frame_buf.resize(expected, 0); - self.bytes_read = 0; - } - - match self - .reader - .try_read(&mut self.current_frame_buf[self.bytes_read..]) - { - Ok(0) => return Err(Error::SocketClosed), - Ok(n) => self.bytes_read += n, - Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => return Ok(None), - Err(_) => return Err(Error::SocketClosed), - } - - if self.bytes_read < expected { - return Ok(None); - } - - self.decoder - .writable() - .copy_from_slice(&self.current_frame_buf[..]); - - self.bytes_read = 0; - - match self.decoder.next_frame(&mut self.state) { - Ok(frame) => Ok(Some(frame)), - Err(stratum_core::codec_sv2::Error::MissingBytes(_)) => Ok(None), - Err(e) => Err(Error::CodecError(e)), - } - } -} - -async fn send_message + GetSize + Send + 'static>( - writer: &mut OwnedWriteHalf, - msg: StandardEitherFrame, - state: &mut State, - encoder: &mut NoiseEncoder, -) -> Result<(), Error> { - let buffer = encoder.encode(msg, state)?; - writer - .write_all(buffer.as_ref()) - .await - .map_err(|_| Error::SocketClosed)?; - Ok(()) -} - -async fn receive_message + GetSize + Send + 'static>( - reader: &mut OwnedReadHalf, - state: &mut State, - decoder: &mut StandardNoiseDecoder, -) -> Result, Error> { - let mut buffer = vec![0u8; decoder.writable_len()]; - reader - .read_exact(&mut buffer) - .await - .map_err(|_| Error::SocketClosed)?; - decoder.writable().copy_from_slice(&buffer); - decoder.next_frame(state).map_err(Error::CodecError) -} diff --git a/roles/stratum-apps/src/network_helpers/plain_connection.rs b/roles/stratum-apps/src/network_helpers/plain_connection.rs deleted file mode 100644 index 291eca626c..0000000000 --- a/roles/stratum-apps/src/network_helpers/plain_connection.rs +++ /dev/null @@ -1,135 +0,0 @@ -use async_channel::{bounded, Receiver, Sender}; -use core::convert::TryInto; -use stratum_core::binary_sv2::{Deserialize, Serialize}; -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - net::{TcpListener, TcpStream}, - task, -}; - -use stratum_core::{ - binary_sv2::GetSize, - codec_sv2::{Error::MissingBytes, StandardDecoder, StandardEitherFrame}, -}; -use tracing::{error, trace}; - -#[derive(Debug)] -pub struct PlainConnection {} - -impl PlainConnection { - /// - /// - /// # Arguments - /// - /// * `strict` - true - will disconnect a connection that sends a message that can't be - /// translated, false - will ignore messages that can't be translated - #[allow(clippy::new_ret_no_self)] - pub async fn new<'a, Message: Serialize + Deserialize<'a> + GetSize + Send + 'static>( - stream: TcpStream, - ) -> ( - Receiver>, - Sender>, - ) { - const NOISE_HANDSHAKE_SIZE_HINT: usize = 3363412; - - let (mut reader, mut writer) = stream.into_split(); - - let (sender_incoming, receiver_incoming): ( - Sender>, - Receiver>, - ) = bounded(10); // TODO caller should provide this param - let (sender_outgoing, receiver_outgoing): ( - Sender>, - Receiver>, - ) = bounded(10); // TODO caller should provide this param - - // RECEIVE AND PARSE INCOMING MESSAGES FROM TCP STREAM - task::spawn(async move { - let mut decoder = StandardDecoder::::new(); - - loop { - let writable = decoder.writable(); - match reader.read_exact(writable).await { - Ok(_) => { - match decoder.next_frame() { - Ok(frame) => { - if let Err(e) = sender_incoming.send(frame.into()).await { - error!("Failed to send incoming message: {}", e); - task::yield_now().await; - break; - } - } - Err(MissingBytes(size)) => { - // Only disconnect if we get noise handshake message - this - // shouldn't - // happen in plain_connection - if size == NOISE_HANDSHAKE_SIZE_HINT { - error!("Got noise message on unencrypted connection - disconnecting"); - break; - } else { - trace!("MissingBytes({}) on incoming message - ignoring", size); - } - } - Err(e) => { - error!("Failed to read from stream: {}", e); - sender_incoming.close(); - task::yield_now().await; - break; - } - } - } - Err(e) => { - // Just fail and force to reinitialize everything - error!("Failed to read from stream: {}", e); - sender_incoming.close(); - task::yield_now().await; - break; - } - } - } - }); - - // ENCODE AND SEND INCOMING MESSAGES TO TCP STREAM - task::spawn(async move { - let mut encoder = stratum_core::codec_sv2::Encoder::::new(); - - loop { - let received = receiver_outgoing.recv().await; - match received { - Ok(frame) => { - let b = encoder.encode(frame.try_into().unwrap()).unwrap(); - - match (writer).write_all(b).await { - Ok(_) => (), - Err(_) => { - let _ = writer.shutdown().await; - } - } - } - Err(_) => { - // Just fail and force to reinitilize everything - let _ = writer.shutdown().await; - error!("Failed to read from stream - terminating connection"); - task::yield_now().await; - break; - } - }; - } - }); - - (receiver_incoming, sender_outgoing) - } -} - -pub async fn plain_listen(address: &str, sender: Sender) { - let listener = TcpListener::bind(address).await.unwrap(); - loop { - if let Ok((stream, _)) = listener.accept().await { - let _ = sender.send(stream).await; - } - } -} -pub async fn plain_connect(address: &str) -> Result { - let stream = TcpStream::connect(address).await.map_err(|_| ())?; - Ok(stream) -} diff --git a/roles/stratum-apps/src/network_helpers/sv1_connection.rs b/roles/stratum-apps/src/network_helpers/sv1_connection.rs deleted file mode 100644 index 29f00e41b7..0000000000 --- a/roles/stratum-apps/src/network_helpers/sv1_connection.rs +++ /dev/null @@ -1,214 +0,0 @@ -use async_channel::{unbounded, Receiver, Sender}; -use futures::StreamExt; -use stratum_core::sv1_api::json_rpc; -use tokio::{ - io::{AsyncWriteExt, BufReader, BufWriter}, - net::TcpStream, -}; -use tokio_util::codec::{FramedRead, LinesCodec}; -use tracing::{error, trace, warn}; - -/// Represents a connection between two roles communicating using SV1 protocol. -/// -/// This struct can be used to read and write messages to the other side of the connection. The -/// channel is unidirectional, i.e., each [`ConnectionSV1`] instance handles the connection either -/// from the upstream perspective or the downstream perspective. In order to communicate in both -/// directions, you will need two instances of this struct. -#[derive(Debug)] -pub struct ConnectionSV1 { - receiver: Receiver, - sender: Sender, -} - -struct ConnectionState { - receiver_outgoing: Receiver, - sender_outgoing: Sender, - receiver_incoming: Receiver, - sender_incoming: Sender, -} - -impl ConnectionState { - fn new( - receiver_outgoing: Receiver, - sender_outgoing: Sender, - receiver_incoming: Receiver, - sender_incoming: Sender, - ) -> Self { - Self { - receiver_incoming, - receiver_outgoing, - sender_incoming, - sender_outgoing, - } - } - - fn close(&self) { - self.receiver_incoming.close(); - self.receiver_outgoing.close(); - self.sender_incoming.close(); - self.sender_outgoing.close(); - } -} - -const MAX_LINE_LENGTH: usize = 1 << 16; - -impl ConnectionSV1 { - pub async fn new(stream: TcpStream) -> Self { - let (read_half, write_half) = stream.into_split(); - let (sender_incoming, receiver_incoming) = unbounded(); - let (sender_outgoing, receiver_outgoing) = unbounded(); - - let buffer_read_half = BufReader::new(read_half); - let buffer_write_half = BufWriter::new(write_half); - - let connection_state = ConnectionState::new( - receiver_outgoing.clone(), - sender_outgoing.clone(), - receiver_incoming.clone(), - sender_incoming.clone(), - ); - - tokio::spawn(async move { - tokio::select! { - _ = Self::run_reader(buffer_read_half, sender_incoming.clone()) => { - trace!("Reader task exited. Closing writer sender."); - connection_state.close(); - } - _ = Self::run_writer(buffer_write_half, receiver_outgoing.clone()) => { - trace!("Writer task exited. Closing reader sender."); - connection_state.close(); - } - } - }); - - Self { - receiver: receiver_incoming, - sender: sender_outgoing, - } - } - - async fn run_reader( - reader: BufReader, - sender: Sender, - ) { - let mut lines = FramedRead::new(reader, LinesCodec::new_with_max_length(MAX_LINE_LENGTH)); - while let Some(result) = lines.next().await { - match result { - Ok(line) => match serde_json::from_str::(&line) { - Ok(msg) => { - if sender.send(msg).await.is_err() { - warn!("Receiver dropped, stopping reader"); - break; - } - } - Err(e) => { - error!("Failed to deserialize message: {e:?}"); - } - }, - Err(e) => { - error!("Error reading from stream: {e:?}"); - break; - } - } - } - } - - async fn run_writer( - mut writer: BufWriter, - receiver: Receiver, - ) { - while let Ok(msg) = receiver.recv().await { - match serde_json::to_string(&msg) { - Ok(line) => { - let data = format!("{line}\n"); - if writer.write_all(data.as_bytes()).await.is_err() { - error!("Failed to write to stream"); - break; - } - if writer.flush().await.is_err() { - error!("Failed to flush writer."); - break; - } - } - Err(e) => { - error!("Failed to serialize message: {e:?}"); - break; - } - } - } - } - - /// Send a message to the other side of the connection. - pub async fn send(&self, msg: json_rpc::Message) -> bool { - self.sender.send(msg).await.is_ok() - } - - /// Receive a message from the other side of the connection. - pub async fn receive(&self) -> Option { - self.receiver.recv().await.ok() - } - - /// Get a clone of the receiver channel. - pub fn receiver(&self) -> Receiver { - self.receiver.clone() - } - - /// Get a clone of the sender channel. - pub fn sender(&self) -> Sender { - self.sender.clone() - } -} - -#[cfg(test)] -mod tests { - use tokio::net::TcpListener; - - use super::*; - - #[tokio::test] - async fn test_sv1_connection() { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - let downstream_stream = TcpStream::connect(addr).await.unwrap(); - let (upstream_stream, _) = listener.accept().await.unwrap(); - - let upstream_connection = ConnectionSV1::new(upstream_stream).await; - let downstream_connection = ConnectionSV1::new(downstream_stream).await; - let message = json_rpc::Message::StandardRequest(json_rpc::StandardRequest { - id: 1, - method: "test".to_string(), - params: serde_json::Value::Null, - }); - assert!(downstream_connection.send(message).await); - let received_on_upstream = upstream_connection.receive().await.unwrap(); - match received_on_upstream { - json_rpc::Message::StandardRequest(received) => { - assert_eq!(received.id, 1); - assert_eq!(received.method, "test".to_string()); - assert_eq!(received.params, serde_json::Value::Null); - } - _ => { - panic!("Unexpected message type"); - } - } - let upstream_response = json_rpc::Message::OkResponse(json_rpc::Response { - id: 1, - result: serde_json::Value::String("response".to_string()), - error: None, - }); - assert!(upstream_connection.send(upstream_response).await); - let received_upstream = downstream_connection.receive().await.unwrap(); - match received_upstream { - json_rpc::Message::OkResponse(received) => { - assert_eq!(received.id, 1); - assert_eq!( - received.result, - serde_json::Value::String("response".to_string()) - ); - } - _ => { - panic!("Unexpected message type"); - } - } - } -} diff --git a/roles/stratum-apps/src/rpc/mini_rpc_client.rs b/roles/stratum-apps/src/rpc/mini_rpc_client.rs deleted file mode 100644 index 1b98ca2f63..0000000000 --- a/roles/stratum-apps/src/rpc/mini_rpc_client.rs +++ /dev/null @@ -1,215 +0,0 @@ -// TODO -// - manage id in RpcResult messages -use base64::Engine; -use hex::decode; -use http_body_util::{BodyExt, Full}; -use hyper::{ - body::Bytes, - header::{AUTHORIZATION, CONTENT_TYPE}, - Request, -}; -use hyper_util::{ - client::legacy::{connect::HttpConnector, Client}, - rt::TokioExecutor, -}; -use serde::{Deserialize, Serialize}; -use serde_json::json; -use stratum_core::bitcoin::{consensus::encode::deserialize as consensus_decode, Transaction}; - -use super::BlockHash; - -#[derive(Clone, Debug)] -pub struct MiniRpcClient { - client: Client>, - url: hyper::Uri, - auth: Auth, -} - -impl MiniRpcClient { - pub fn new(url: hyper::Uri, auth: Auth) -> MiniRpcClient { - let client: Client<_, Full> = Client::builder(TokioExecutor::new()).build_http(); - MiniRpcClient { client, url, auth } - } - - pub async fn get_raw_transaction( - &self, - txid: &String, - block_hash: Option<&BlockHash>, - ) -> Result { - let response = match block_hash { - Some(hash) => { - self.send_json_rpc_request("getrawtransaction", json!([txid, false, hash])) - } - None => self.send_json_rpc_request("getrawtransaction", json!([txid, false])), - } - .await; - match response { - Ok(result_hex) => { - let result_deserialized: JsonRpcResult = serde_json::from_str(&result_hex) - .map_err(|e| { - RpcError::Deserialization(e.to_string()) // TODO manage message ids - })?; - let transaction_hex: String = result_deserialized - .result - .ok_or_else(|| RpcError::Other("Result not found".to_string()))?; - let transaction_bytes = decode(transaction_hex).expect("Decoding failed"); - Ok(consensus_decode(&transaction_bytes).expect("Deserialization failed")) - } - Err(error) => Err(error), - } - } - - pub async fn get_raw_mempool(&self) -> Result, RpcError> { - let response = self.send_json_rpc_request("getrawmempool", json!([])).await; - match response { - Ok(result_hex) => { - let result_deserialized: JsonRpcResult> = - serde_json::from_str(&result_hex).map_err(|e| { - RpcError::Deserialization(e.to_string()) // TODO manage message ids - })?; - let mempool: Vec = result_deserialized - .result - .ok_or_else(|| RpcError::Other("Result not found".to_string()))?; - Ok(mempool) - } - Err(error) => Err(error), - } - } - - pub async fn submit_block(&self, block_hex: String) -> Result<(), RpcError> { - let response = self - .send_json_rpc_request("submitblock", json!([block_hex])) - .await; - - match response { - Ok(_) => Ok(()), - Err(error) => Err(error), - } - } - - /// Checks the health of the RPC connection by sending a request to the blockchain info - /// endpoint - pub async fn health(&self) -> Result<(), RpcError> { - let response = self - .send_json_rpc_request("getblockchaininfo", json!([])) - .await; - match response { - Ok(_) => Ok(()), - Err(error) => Err(error), - } - } - - async fn send_json_rpc_request( - &self, - method: &str, - params: serde_json::Value, - ) -> Result { - let client = &self.client; - let (username, password) = self.auth.clone().get_user_pass(); - let request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - method: method.to_string(), - params, - id: 1, //TODO manage message ids - }; - - let request_body = match serde_json::to_string(&request) { - Ok(body) => body, - Err(e) => return Err(RpcError::Serialization(e.to_string())), - }; - - let req = Request::builder() - .method("POST") - .uri(self.url.clone()) - .header(CONTENT_TYPE, "application/json") - .header( - AUTHORIZATION, - format!( - "Basic {}", - base64::engine::general_purpose::STANDARD - .encode(format!("{username}:{password}")) - ), - ) - .body(Full::::from(request_body)) - .map_err(|e| RpcError::Http(e.to_string()))?; - - let response = client - .request(req) - .await - .map_err(|e| RpcError::Http(e.to_string()))?; - - let status = response.status(); - let body = response - .into_body() - .collect() - .await - .map_err(|e| RpcError::Http(e.to_string()))? - .to_bytes() - .to_vec(); - - if status.is_success() { - String::from_utf8(body).map_err(|e| { - RpcError::Deserialization(e.to_string()) // TODO manage message ids - }) - } else { - let error_result: Result, _> = serde_json::from_slice(&body); - match error_result { - Ok(error_response) => Err(error_response.into()), - Err(e) => Err(RpcError::Deserialization(e.to_string())), - } - } - } -} - -#[derive(Clone, Debug)] -pub struct Auth { - username: String, - password: String, -} - -impl Auth { - pub fn get_user_pass(self) -> (String, String) { - (self.username, self.password) - } - pub fn new(username: String, password: String) -> Auth { - Auth { username, password } - } -} - -#[derive(Debug, Serialize)] -struct JsonRpcRequest { - jsonrpc: String, - method: String, - params: serde_json::Value, - id: u64, -} - -#[derive(Debug, Deserialize)] -pub struct JsonRpcResult { - result: Option, - pub error: Option, - pub id: u64, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct JsonRpcError { - pub code: i32, - pub message: String, -} - -#[derive(Debug, Deserialize)] -pub enum RpcError { - // TODO this type is slightly incorrect, as the JsonRpcError evaluates a generic that is meant - // for the result field of JsonRpcResult struct. This should be corrected - JsonRpc(JsonRpcResult), - Deserialization(String), - Serialization(String), - Http(String), - Other(String), -} - -impl From> for RpcError { - fn from(error: JsonRpcResult) -> Self { - Self::JsonRpc(error) - } -} diff --git a/roles/stratum-apps/src/rpc/mod.rs b/roles/stratum-apps/src/rpc/mod.rs deleted file mode 100644 index fd7eb9432e..0000000000 --- a/roles/stratum-apps/src/rpc/mod.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! RPC utilities for Job Declaration Server -//! -//! This module provides HTTP-based RPC server implementation for JD Server functionality. -//! It includes utilities for handling RPC requests and responses. -//! -//! Originally from the `rpc_sv2` crate. -//! -//! This module is only available when the `rpc` feature is enabled. - -pub mod mini_rpc_client; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct Hash([u8; 32]); - -#[allow(dead_code)] -#[derive(Clone, Deserialize)] -pub struct Amount(f64); - -#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct BlockHash(Hash); - -pub use hyper::Uri; diff --git a/roles/tarpaulin.toml b/roles/tarpaulin.toml deleted file mode 100644 index 96f8f04b3d..0000000000 --- a/roles/tarpaulin.toml +++ /dev/null @@ -1,8 +0,0 @@ -[default] -features = "with_buffer_pool default" -run-types = [ "Lib" ] -timeout = "120s" -fail-under = 0 - -[report] -out = ["Xml"] diff --git a/roles/test-utils/mining-device/Cargo.toml b/roles/test-utils/mining-device/Cargo.toml deleted file mode 100644 index 7e5234e4e0..0000000000 --- a/roles/test-utils/mining-device/Cargo.toml +++ /dev/null @@ -1,61 +0,0 @@ -[package] -name = "mining_device" -version = "0.1.3" -authors = ["The Stratum V2 Developers"] -edition = "2021" -publish = false -documentation = "https://github.com/stratum-mining/stratum" -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"] - - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -name = "mining_device" -path = "src/lib/mod.rs" - - -[dependencies] -stratum-apps = { path = "../../stratum-apps", features = ["mining_device"] } -roles_logic_sv2 = "5.0.0" -binary_sv2 = "4.0.0" -codec_sv2 = "3.0.1" -framing_sv2 = "5.0.1" -noise_sv2 = "1.4.0" -parsers_sv2 = "0.1.1" -common_messages_sv2 = "6.0.1" -mining_sv2 = "5.0.1" -network_helpers_sv2 = "4.0.1" -async-channel = "1.5.1" -async-recursion = "0.3.2" -rand = "0.8.4" -futures = "0.3.5" -clap = { version = "^4.5.4", features = ["derive"] } -tracing = { version = "0.1" } -tracing-subscriber = "0.3" -sha2 = { version = "0.10.6", features = ["compress", "asm"] } -tokio = "1.44.1" -primitive-types = "0.13.1" -num-format = "0.4" - -[dev-dependencies] -# Criterion 0.5 without default features; combined with a dev pin of `half = 2.3.1` to stay Rust 1.75-compatible. -criterion = { version = "0.5", default-features = false, features = ["stable"] } -half = "=2.3.1" -num_cpus = "1" - -[[bench]] -name = "hasher_bench" -harness = false - -[[bench]] -name = "microbatch_bench" -harness = false - -[[bench]] -name = "scaling_bench" -harness = false diff --git a/roles/test-utils/mining-device/README.md b/roles/test-utils/mining-device/README.md deleted file mode 100644 index dc9db27d7e..0000000000 --- a/roles/test-utils/mining-device/README.md +++ /dev/null @@ -1,137 +0,0 @@ -# CPU Sv2 mining device - -Header only sv2 cpu miner. - -``` -Usage: mining_device [OPTIONS] --address-pool - -Options: - -p, --pubkey-pool - Pool pub key, when left empty the pool certificate is not checked - -i, --id-device - Sometimes used by the pool to identify the device - -a, --address-pool - Address of the pool in this format ip:port or domain:port - --handicap - This value is used to slow down the cpu miner, it represents the number of micro-seconds that are awaited between hashes [default: 0] - --id-user - User id, used when a new channel is opened, it can be used by the pool to identify the miner - --nominal-hashrate-multiplier - This floating point number is used to modify the advertised nominal hashrate when opening a channel with the upstream. - If 0.0 < nominal_hashrate_multiplier < 1.0, the CPU miner will advertise a nominal hashrate that is smaller than its real capacity. - If nominal_hashrate_multiplier > 1.0, the CPU miner will advertise a nominal hashrate that is bigger than its real capacity. - If empty, the CPU miner will simply advertise its real capacity. - --nonces-per-call - Number of nonces to try per mining loop iteration when fast hashing is available (micro-batching). [default: 32] - --cores - Number of worker threads to use for mining. Defaults to logical CPUs minus one (leaves one core free). - -h, --help - Print help - -V, --version - Print version -``` - -Usage example: -``` -cargo run --release -- --address-pool 127.0.0.1:20000 --id-device device_id::SOLO::bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh -``` - -To adjust micro-batching (see below), you can pass for example `--nonces-per-call 64`: - -``` -cargo run --release -- --address-pool 127.0.0.1:20000 \ - --id-device device_id::SOLO::bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh \ - --nonces-per-call 64 -``` - -## handicap - -CPU mining could damage the system due to excessive heat. - -The `--handicap` parameter should be used as a safety mechanism to slow down the hashrate in order to preserve hardware. - -## nominal hashrate multiplier - -Let's imagine that: -- the upstream wants to receive shares every ~100s (on average) -- the CPU miner nominal hashrate is 1k H/s - -Maybe we want to do a test where we don't want to wait ~100s before a share is submitted by the CPU miner. - -In that case, we need the CPU miner to advertise a smaller hashrate, which will force the upstream to set a lower -difficulty target. - -The `--nominal-hashrate-multiplier` can be used to advertise a custom nominal hashrate. - -In the scenario described above, we could launch the CPU miner with `--nominal-hashrate-multiplier 0.01`. - -The CPU miner would advertise 0.01k H/s, which would cause the upstream to set the difficulty target such that the CPU miner would find a share within ~1s. - -This feature can also be used to advertise a bigger nominal hashrate by using values above `1.0`. - -That can also be useful for testing difficulty adjustment algorithms on Sv2 upstreams. - -## Micro-batching (nonces per call) - -The miner supports hashing multiple consecutive nonces per loop iteration when the fast hashing path is available. This reduces outer-loop overhead and can slightly increase throughput on some CPUs. - -- Flag: `--nonces-per-call ` -- Default: `32` -- Trade-off: larger batches can increase latency to detecting a found share because the loop advances in steps of `N`. Choose smaller values (e.g., `4`โ€“`16`) if you care more about latency; larger values (e.g., `32`โ€“`128`) may squeeze a bit more throughput. - -This setting only affects the CPU loop structure; it does not change the hash function or correctness. - -## Worker threads - -By default, the miner uses one worker thread per logical CPU minus one (N-1). This leaves a core available for the operating system and scheduling overhead. - -You can override this with `--cores `, clamped between `1` and the number of logical CPUs. - -Examples: - -```zsh -# Pin to a small fixed number of workers -cargo run --release -- --address-pool 127.0.0.1:20000 --cores 2 -``` - -If `--cores` is omitted, auto mode (N-1) is used. - -## Benchmarks - -You can measure performance with Criterion. From this directory: - -```zsh -cargo bench --bench hasher_bench -- --quiet -``` - -- `hasher_bench` compares baseline `block_hash()` against the optimized midstate+compress256 path. - -To analyze the effect of micro-batching on an end-of-loop iteration, run: - -```zsh -cargo bench --bench microbatch_bench -- --quiet -``` - -- `microbatch_bench` sweeps several batch sizes and sets Criterion throughput to `Elements = N` where each element is one nonce. This means: - - The reported time per iteration divides roughly by `N` to get per-nonce time. - - Criterion also prints throughput in elements/s (hashes/s). For convenience, the bench additionally prints a concise `MH/s` per configuration. - -By default the bench runs a concise subset of batch sizes: `1,8,32,128`. You can override the list via an environment variable: - -```zsh -MINING_DEVICE_BATCH_SIZES=1,4,8,16,32,64,128 cargo bench --bench microbatch_bench -- --quiet -``` - -Tip: pick the smallest `N` that gives you near-peak throughput to keep share-finding latency low. - -### Total scaling (multi-core) - -Total throughput doesnโ€™t always scale linearly with more workers (due to CPU topology, turbo, thermal limits, etc.). Use the scaling bench to measure aggregate MH/s while ramping worker counts from 1 up to your number of logical CPUs: - -```zsh -cargo bench --bench scaling_bench -- --quiet -``` - -- The bench automatically detects the number of logical CPUs and iterates workers from `1..=N` (no environment variable needed). - -The bench prints one concise summary line per configuration and shows incremental improvements versus the previous worker count, including the approximate MH/s gained per additional worker. It also sets Criterion throughput to Elements equal to total nonces hashed, so Elements/s equals total hashes/s. diff --git a/roles/test-utils/mining-device/benches/hasher_bench.rs b/roles/test-utils/mining-device/benches/hasher_bench.rs deleted file mode 100644 index 7f4284eec8..0000000000 --- a/roles/test-utils/mining-device/benches/hasher_bench.rs +++ /dev/null @@ -1,54 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; -use mining_device::FastSha256d; -use rand::{thread_rng, Rng}; -use stratum_apps::stratum_core::bitcoin::{ - block::Version, blockdata::block::Header, hash_types::BlockHash, hashes::Hash, CompactTarget, -}; - -fn random_header() -> Header { - let mut rng = thread_rng(); - let prev_hash: [u8; 32] = rng.gen(); - let prev_hash = Hash::from_byte_array(prev_hash); - let merkle_root: [u8; 32] = rng.gen(); - let merkle_root = Hash::from_byte_array(merkle_root); - Header { - version: Version::from_consensus(rng.gen::()), - prev_blockhash: BlockHash::from_raw_hash(prev_hash), - merkle_root, - time: std::time::SystemTime::now() - .duration_since(std::time::SystemTime::UNIX_EPOCH - std::time::Duration::from_secs(60)) - .unwrap() - .as_secs() as u32, - bits: CompactTarget::from_consensus(rng.gen()), - nonce: 0, - } -} - -fn bench_hasher(c: &mut Criterion) { - let mut group = c.benchmark_group("mining_device_hasher"); - let header = random_header(); - - // Baseline using rust-bitcoin block_hash() - group.bench_function(BenchmarkId::new("baseline_block_hash", "full"), |b| { - let mut h = header; - b.iter(|| { - h.nonce = h.nonce.wrapping_add(1); - let _ = black_box(h.block_hash()); - }); - }); - - // Optimized midstate+compress256 - group.bench_function(BenchmarkId::new("fast_midstate", "compress256"), |b| { - let mut h = header; - let mut fast = FastSha256d::from_header_static(&h); - b.iter(|| { - h.nonce = h.nonce.wrapping_add(1); - let _ = black_box(fast.hash_with_nonce_time(h.nonce, h.time)); - }); - }); - - group.finish(); -} - -criterion_group!(benches, bench_hasher); -criterion_main!(benches); diff --git a/roles/test-utils/mining-device/benches/microbatch_bench.rs b/roles/test-utils/mining-device/benches/microbatch_bench.rs deleted file mode 100644 index f5b4e88cd0..0000000000 --- a/roles/test-utils/mining-device/benches/microbatch_bench.rs +++ /dev/null @@ -1,105 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; -use mining_device::{set_nonces_per_call, FastSha256d}; -use rand::{thread_rng, Rng}; -use std::time::Duration; -use stratum_apps::stratum_core::bitcoin::{ - block::Version, blockdata::block::Header, hash_types::BlockHash, hashes::Hash, CompactTarget, -}; - -fn random_header() -> Header { - let mut rng = thread_rng(); - let prev_hash: [u8; 32] = rng.gen(); - let prev_hash = Hash::from_byte_array(prev_hash); - let merkle_root: [u8; 32] = rng.gen(); - let merkle_root = Hash::from_byte_array(merkle_root); - Header { - version: Version::from_consensus(rng.gen::()), - prev_blockhash: BlockHash::from_raw_hash(prev_hash), - merkle_root, - time: std::time::SystemTime::now() - .duration_since(std::time::SystemTime::UNIX_EPOCH - std::time::Duration::from_secs(60)) - .unwrap() - .as_secs() as u32, - bits: CompactTarget::from_consensus(rng.gen()), - nonce: 0, - } -} - -fn bench_microbatch(c: &mut Criterion) { - // Report hardware SHA availability once at start - #[cfg(target_arch = "x86_64")] - println!( - "Hardware SHA available (x86 SHA-NI): {}", - std::is_x86_feature_detected!("sha") - ); - #[cfg(target_arch = "aarch64")] - println!( - "Hardware SHA available (ARMv8 SHA2): {}", - std::arch::is_aarch64_feature_detected!("sha2") - ); - #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] - println!("Hardware SHA detection: not applicable for this arch"); - - let mut group = c.benchmark_group("mining_device_microbatch"); - // Keep output and run-time concise - group.sample_size(10); - group.warm_up_time(Duration::from_millis(100)); - group.measurement_time(Duration::from_secs(1)); - let header = random_header(); - let mut fast = FastSha256d::from_header_static(&header); - // Fewer defaults for less verbose output; allow override via env var - let batches: Vec = std::env::var("MINING_DEVICE_BATCH_SIZES") - .ok() - .and_then(|s| { - s.split(',') - .map(|p| p.trim().parse::().ok()) - .collect::>>() - }) - .filter(|v| !v.is_empty()) - .unwrap_or_else(|| vec![1, 8, 32, 128]); - - for &b in &batches { - group.throughput(Throughput::Elements(b as u64)); - group.bench_function(BenchmarkId::from_parameter(b), |bencher| { - set_nonces_per_call(b); - let mut h = header; - bencher.iter(|| { - // Simulate one mining-loop iteration: hash "b" nonces - let start = h.nonce; - let time = h.time; - for i in 0..b { - let hsh = fast.hash_with_nonce_time(start.wrapping_add(i), time); - black_box(hsh); - } - h.nonce = start.wrapping_add(b); - }); - }); - - // Print a concise MH/s estimate per configuration (outside Criterion's stats) - // Do a quick one-shot timing over a small fixed workload to avoid noisy output. - // Note: This is a convenience display; for rigorous numbers, rely on Criterion results. - let mut h = header; - set_nonces_per_call(b); - let reps: u32 = 200_000 / b.max(1); // ~200k hashes in total; fast and stable - let total_hashes: u64 = reps as u64 * b as u64; - let start_inst = std::time::Instant::now(); - for _ in 0..reps { - let start = h.nonce; - let time = h.time; - for i in 0..b { - let _ = black_box(fast.hash_with_nonce_time(start.wrapping_add(i), time)); - } - h.nonce = start.wrapping_add(b); - } - let dur = start_inst.elapsed(); - let secs = dur.as_secs_f64().max(1e-9); - let hps = (total_hashes as f64) / secs; // hashes per second - let mhps = hps / 1_000_000.0; - println!("batch={b}: ~{mhps:.3} MH/s"); - } - - group.finish(); -} - -criterion_group!(benches, bench_microbatch); -criterion_main!(benches); diff --git a/roles/test-utils/mining-device/benches/scaling_bench.rs b/roles/test-utils/mining-device/benches/scaling_bench.rs deleted file mode 100644 index 1efda8ea99..0000000000 --- a/roles/test-utils/mining-device/benches/scaling_bench.rs +++ /dev/null @@ -1,169 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; -use mining_device::FastSha256d; -use rand::{thread_rng, Rng}; -use std::{ - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Barrier, - }, - thread, - time::Instant, -}; -use stratum_apps::stratum_core::bitcoin::{ - block::Version, blockdata::block::Header, hash_types::BlockHash, hashes::Hash, CompactTarget, -}; - -fn random_header() -> Header { - let mut rng = thread_rng(); - let prev_hash: [u8; 32] = rng.gen(); - let prev_hash = Hash::from_byte_array(prev_hash); - let merkle_root: [u8; 32] = rng.gen(); - let merkle_root = Hash::from_byte_array(merkle_root); - Header { - version: Version::from_consensus(rng.gen::()), - prev_blockhash: BlockHash::from_raw_hash(prev_hash), - merkle_root, - time: std::time::SystemTime::now() - .duration_since(std::time::SystemTime::UNIX_EPOCH - std::time::Duration::from_secs(60)) - .unwrap() - .as_secs() as u32, - bits: CompactTarget::from_consensus(rng.gen()), - nonce: 0, - } -} - -fn bench_scaling(c: &mut Criterion) { - let mut group = c.benchmark_group("mining_device_scaling"); - // Measure logical CPUs and test scaling from 1..=N - let logical_cpus = num_cpus::get().max(1); - let workers: Vec = (1..=logical_cpus).collect(); - - // Keep runs short but representative - group.sample_size(10); - group.warm_up_time(std::time::Duration::from_millis(100)); - group.measurement_time(std::time::Duration::from_secs(1)); - - let header = random_header(); - - // Helper: quick one-shot timing used only for concise logging (outside Criterion loop) - let quick_measure_total_mhps = |n: usize| -> f64 { - // Each measurement hashes this many nonces total across n threads - let per_thread: u32 = 200_000 / (n as u32).max(1); - let total_hashes = per_thread as u64 * n as u64; - let stop = Arc::new(AtomicBool::new(false)); - let barrier = Arc::new(Barrier::new(n)); - let mut handles = Vec::with_capacity(n); - for i in 0..n { - let stop = stop.clone(); - let barrier = barrier.clone(); - let mut h = header; - h.nonce = i as u32; - let mut fast = FastSha256d::from_header_static(&h); - let per = per_thread; - handles.push(thread::spawn(move || { - barrier.wait(); - let start = Instant::now(); - let time = h.time; - let mut nonce = h.nonce; - for _ in 0..per { - let _ = black_box(fast.hash_with_nonce_time(nonce, time)); - nonce = nonce.wrapping_add(n as u32); // stride to avoid overlap - if stop.load(Ordering::Relaxed) { - break; - } - } - start.elapsed() - })); - } - let mut max_elapsed = std::time::Duration::ZERO; - for h in handles { - let d = h.join().unwrap(); - if d > max_elapsed { - max_elapsed = d; - } - } - let secs = max_elapsed.as_secs_f64().max(1e-9); - let hps = (total_hashes as f64) / secs; - hps / 1_000_000.0 - }; - - // Print one concise line per worker count, including incremental gain vs previous - let mut prev_workers: Option = None; - let mut prev_mhps: Option = None; - - for &n in &workers { - // Each iteration hashes this many nonces total across n threads - let per_thread: u32 = 200_000 / (n as u32).max(1); - let total_hashes = per_thread as u64 * n as u64; - group.throughput(Throughput::Elements(total_hashes)); - - // One-shot concise summary print (not part of Criterion timing) - let mhps = quick_measure_total_mhps(n); - if let (Some(pn), Some(prev)) = (prev_workers, prev_mhps) { - let added = n.saturating_sub(pn).max(1); - let delta = mhps - prev; - let pct = if prev > 0.0 { - (delta / prev) * 100.0 - } else { - 0.0 - }; - let per_cpu = delta / (added as f64); - println!( - "workers={n}: ~{mhps:.3} MH/s (total) | +{delta:.3} vs prev (+{pct:.1}%), ~{per_cpu:.3} MH/s per added worker" - ); - } else { - println!("workers={n}: ~{mhps:.3} MH/s (total)"); - } - prev_workers = Some(n); - prev_mhps = Some(mhps); - - group.bench_function(BenchmarkId::from_parameter(n), |b| { - b.iter(|| { - let stop = Arc::new(AtomicBool::new(false)); - let barrier = Arc::new(Barrier::new(n)); - let mut handles = Vec::with_capacity(n); - for i in 0..n { - let stop = stop.clone(); - let barrier = barrier.clone(); - let mut h = header; - h.nonce = i as u32; - let mut fast = FastSha256d::from_header_static(&h); - let per = per_thread; - handles.push(thread::spawn(move || { - // start together - barrier.wait(); - let start = Instant::now(); - let time = h.time; - let mut nonce = h.nonce; - for _ in 0..per { - // One hash per step; inner batching isn't necessary here - let _ = black_box(fast.hash_with_nonce_time(nonce, time)); - nonce = nonce.wrapping_add(n as u32); // stride to avoid overlap - if stop.load(Ordering::Relaxed) { - break; - } - } - start.elapsed() - })); - } - // Collect times and compute MH/s - let mut max_elapsed = std::time::Duration::ZERO; - for h in handles { - let d = h.join().unwrap(); - if d > max_elapsed { - max_elapsed = d; - } - } - let _secs = max_elapsed.as_secs_f64().max(1e-9); - let _hps = (total_hashes as f64) / _secs; - let _mhps = _hps / 1_000_000.0; - // Intentionally no println! inside Criterion iteration to keep output concise - }); - }); - } - - group.finish(); -} - -criterion_group!(benches, bench_scaling); -criterion_main!(benches); diff --git a/roles/test-utils/mining-device/src/lib/mod.rs b/roles/test-utils/mining-device/src/lib/mod.rs deleted file mode 100644 index 96cec9a7bf..0000000000 --- a/roles/test-utils/mining-device/src/lib/mod.rs +++ /dev/null @@ -1,1079 +0,0 @@ -#![allow(clippy::option_map_unit_fn)] -use async_channel::{Receiver, Sender}; -use codec_sv2::{self, StandardEitherFrame, StandardSv2Frame}; -use common_messages_sv2::{Protocol, SetupConnection, SetupConnectionSuccess}; -use mining_sv2::*; -use network_helpers_sv2::noise_connection::Connection; -use noise_sv2::Initiator; -use num_format::{Locale, ToFormattedString}; -use parsers_sv2::{Mining, MiningDeviceMessages}; -use primitive_types::U256; -use rand::{thread_rng, Rng}; -use roles_logic_sv2::{ - errors::Error, - handlers::{ - common::ParseCommonMessagesFromUpstream, - mining::{ParseMiningMessagesFromUpstream, SendTo, SupportedChannelTypes}, - }, - utils::Mutex, -}; -use std::{ - net::{SocketAddr, ToSocketAddrs}, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - thread::available_parallelism, - time::{Duration, Instant}, -}; -use stratum_apps::{ - key_utils::Secp256k1PublicKey, - stratum_core::bitcoin::{ - blockdata::block::Header, hash_types::BlockHash, hashes::Hash, CompactTarget, - }, -}; -use tokio::net::TcpStream; -use tracing::{debug, error, info}; - -use stratum_apps::stratum_core::bitcoin::consensus::encode::serialize as btc_serialize; - -// Fast SHA256d midstate hasher -use sha2::{ - compress256, - digest::generic_array::{typenum::U64, GenericArray}, -}; - -// Tuneable: how many nonces to try per mining loop iteration when fast hasher is available. -// Runtime-configurable so the binary and benches can adjust it without changing code. -use std::sync::atomic::AtomicU32; -static NONCES_PER_CALL_RUNTIME: AtomicU32 = AtomicU32::new(32); -// Runtime-configurable number of worker threads; 0 means "auto" (N-1) -static WORKER_OVERRIDE: AtomicU32 = AtomicU32::new(0); - -#[inline] -pub fn set_nonces_per_call(n: u32) { - // Avoid zero (would stall the loop); clamp to at least 1 - let n = n.max(1); - NONCES_PER_CALL_RUNTIME.store(n, Ordering::Relaxed); -} - -#[inline] -fn nonces_per_call() -> u32 { - NONCES_PER_CALL_RUNTIME.load(Ordering::Relaxed).max(1) -} - -/// Override the number of mining worker threads. If set to 0, auto mode (N-1) is used. -#[inline] -pub fn set_cores(n: u32) { - WORKER_OVERRIDE.store(n, Ordering::Relaxed); -} - -/// Resolve effective worker count: if override is 0, use max(1, logical_cpus-1). -#[inline] -fn worker_count() -> u32 { - let total_cpus = available_parallelism().map(|p| p.get()).unwrap_or(1) as u32; - let auto = total_cpus.saturating_sub(1).max(1); - let override_n = WORKER_OVERRIDE.load(Ordering::Relaxed); - if override_n == 0 { - auto - } else { - // Clamp to [1, total_cpus] to avoid oversubscription or zero - override_n.clamp(1, total_cpus) - } -} - -/// Public helper: current effective worker threads (after considering override and auto mode) -#[inline] -pub fn effective_worker_count() -> u32 { - worker_count() -} - -/// Public helper: total logical CPUs detected -#[inline] -pub fn total_logical_cpus() -> u32 { - available_parallelism().map(|p| p.get()).unwrap_or(1) as u32 -} - -pub async fn connect( - address: String, - pub_key: Option, - device_id: Option, - user_id: Option, - handicap: u32, - nominal_hashrate_multiplier: Option, - single_submit: bool, -) { - let address = address - .clone() - .to_socket_addrs() - .expect("Invalid pool address, use one of this formats: ip:port, domain:port") - .next() - .expect("Invalid pool address, use one of this formats: ip:port, domain:port"); - info!("Connecting to pool at {}", address); - let socket = loop { - let pool = tokio::time::timeout(Duration::from_secs(5), TcpStream::connect(address)).await; - match pool { - Ok(result) => match result { - Ok(socket) => break socket, - Err(e) => { - error!( - "Failed to connect to Upstream role at {}, retrying in 5s: {}", - address, e - ); - tokio::time::sleep(Duration::from_secs(5)).await; - } - }, - Err(_) => { - error!("Pool is unresponsive, terminating"); - std::process::exit(1); - } - } - }; - info!("Pool tcp connection established at {}", address); - let address = socket.peer_addr().unwrap(); - let initiator = Initiator::new(pub_key.map(|e| e.0)); - let (receiver, sender) = - Connection::new(socket, codec_sv2::HandshakeRole::Initiator(initiator)) - .await - .unwrap(); - info!("Pool noise connection established at {}", address); - Device::start( - receiver, - sender, - address, - device_id, - user_id, - handicap, - nominal_hashrate_multiplier, - single_submit, - ) - .await -} - -pub type Message = MiningDeviceMessages<'static>; -pub type StdFrame = StandardSv2Frame; -pub type EitherFrame = StandardEitherFrame; - -struct SetupConnectionHandler {} -use common_messages_sv2::Reconnect; -use std::convert::TryInto; -use stratum_apps::stratum_core::bitcoin::block::Version; - -impl SetupConnectionHandler { - pub fn new() -> Self { - SetupConnectionHandler {} - } - fn get_setup_connection_message( - address: SocketAddr, - device_id: Option, - ) -> SetupConnection<'static> { - let endpoint_host = address.ip().to_string().into_bytes().try_into().unwrap(); - let vendor = String::new().try_into().unwrap(); - let hardware_version = String::new().try_into().unwrap(); - let firmware = String::new().try_into().unwrap(); - let device_id = device_id.unwrap_or_default(); - info!( - "Creating SetupConnection message with device id: {:?}", - device_id - ); - SetupConnection { - protocol: Protocol::MiningProtocol, - min_version: 2, - max_version: 2, - flags: 0b0000_0000_0000_0000_0000_0000_0000_0001, - endpoint_host, - endpoint_port: address.port(), - vendor, - hardware_version, - firmware, - device_id: device_id.try_into().unwrap(), - } - } - pub async fn setup( - self_: Arc>, - receiver: &mut Receiver, - sender: &mut Sender, - device_id: Option, - address: SocketAddr, - ) { - let setup_connection = Self::get_setup_connection_message(address, device_id); - - let sv2_frame: StdFrame = MiningDeviceMessages::Common(setup_connection.into()) - .try_into() - .unwrap(); - let sv2_frame = sv2_frame.into(); - sender.send(sv2_frame).await.unwrap(); - info!("Setup connection sent to {}", address); - - let mut incoming: StdFrame = receiver.recv().await.unwrap().try_into().unwrap(); - let message_type = incoming.get_header().unwrap().msg_type(); - let payload = incoming.payload(); - ParseCommonMessagesFromUpstream::handle_message_common(self_, message_type, payload) - .unwrap(); - } -} - -impl ParseCommonMessagesFromUpstream for SetupConnectionHandler { - fn handle_setup_connection_success( - &mut self, - m: SetupConnectionSuccess, - ) -> Result { - use roles_logic_sv2::handlers::common::SendTo; - info!( - "Received `SetupConnectionSuccess`: version={}, flags={:b}", - m.used_version, m.flags - ); - Ok(SendTo::None(None)) - } - - fn handle_setup_connection_error( - &mut self, - _: common_messages_sv2::SetupConnectionError, - ) -> Result { - error!("Setup connection error"); - todo!() - } - - fn handle_channel_endpoint_changed( - &mut self, - _: common_messages_sv2::ChannelEndpointChanged, - ) -> Result { - todo!() - } - - fn handle_reconnect( - &mut self, - _m: Reconnect, - ) -> Result { - todo!() - } -} - -#[derive(Debug, Clone)] -struct NewWorkNotifier { - should_send: bool, - sender: Sender<()>, -} - -#[derive(Debug)] -pub struct Device { - #[allow(dead_code)] - receiver: Receiver, - sender: Sender, - #[allow(dead_code)] - channel_opened: bool, - channel_id: Option, - miner: Arc>, - jobs: Vec>, - prev_hash: Option>, - sequence_numbers: AtomicU32, - notify_changes_to_mining_thread: NewWorkNotifier, -} - -fn open_channel( - device_id: Option, - nominal_hashrate_multiplier: Option, - handicap: u32, -) -> OpenStandardMiningChannel<'static> { - let user_identity = device_id.unwrap_or_default().try_into().unwrap(); - let id: u32 = 10; - info!("Measuring CPU hashrate"); - let measured_total_hs = measure_hashrate(5, handicap); - let measured_total_mhs = measured_total_hs / 1_000_000.0; - info!( - "Measured CPU hashrate โ‰ˆ {} MH/s", - format_mhs(measured_total_mhs) - ); - let measured_hashrate = measured_total_hs as f32; - let nominal_hash_rate = match nominal_hashrate_multiplier { - Some(m) => measured_hashrate * m, - None => measured_hashrate, - }; - - info!("MINING DEVICE: send open channel with request id {}", id); - - OpenStandardMiningChannel { - request_id: id.into(), - user_identity, - nominal_hash_rate, - max_target: vec![0xFF_u8; 32].try_into().unwrap(), - } -} - -impl Device { - #[allow(clippy::too_many_arguments)] - async fn start( - mut receiver: Receiver, - mut sender: Sender, - addr: SocketAddr, - device_id: Option, - user_id: Option, - handicap: u32, - nominal_hashrate_multiplier: Option, - single_submit: bool, - ) { - let setup_connection_handler = Arc::new(Mutex::new(SetupConnectionHandler::new())); - SetupConnectionHandler::setup( - setup_connection_handler, - &mut receiver, - &mut sender, - device_id, - addr, - ) - .await; - info!("Pool sv2 connection established at {}", addr); - let miner = Arc::new(Mutex::new(Miner::new(handicap))); - let (notify_changes_to_mining_thread, update_miners) = async_channel::unbounded(); - let self_ = Self { - channel_opened: false, - receiver: receiver.clone(), - sender: sender.clone(), - miner: miner.clone(), - jobs: Vec::new(), - prev_hash: None, - channel_id: None, - sequence_numbers: AtomicU32::new(0), - notify_changes_to_mining_thread: NewWorkNotifier { - should_send: true, - sender: notify_changes_to_mining_thread, - }, - }; - let open_channel = MiningDeviceMessages::Mining(Mining::OpenStandardMiningChannel( - open_channel(user_id, nominal_hashrate_multiplier, handicap), - )); - let frame: StdFrame = open_channel.try_into().unwrap(); - self_.sender.send(frame.into()).await.unwrap(); - let self_mutex = std::sync::Arc::new(Mutex::new(self_)); - let cloned = self_mutex.clone(); - - let (share_send, share_recv) = async_channel::unbounded(); - - start_mining_threads(update_miners, miner, share_send); - tokio::task::spawn(async move { - let recv = share_recv.clone(); - loop { - let (nonce, job_id, version, ntime) = recv.recv().await.unwrap(); - Self::send_share(cloned.clone(), nonce, job_id, version, ntime).await; - if single_submit { - break; - } - } - }); - - loop { - let mut incoming: StdFrame = receiver.recv().await.unwrap().try_into().unwrap(); - let message_type = incoming.get_header().unwrap().msg_type(); - let payload = incoming.payload(); - let next = - Device::handle_message_mining(self_mutex.clone(), message_type, payload).unwrap(); - let mut notify_changes_to_mining_thread = self_mutex - .safe_lock(|s| s.notify_changes_to_mining_thread.clone()) - .unwrap(); - if notify_changes_to_mining_thread.should_send - && (message_type == stratum_apps::stratum_core::mining_sv2::MESSAGE_TYPE_NEW_MINING_JOB - || message_type - == stratum_apps::stratum_core::mining_sv2::MESSAGE_TYPE_MINING_SET_NEW_PREV_HASH - || message_type == stratum_apps::stratum_core::mining_sv2::MESSAGE_TYPE_SET_TARGET) - { - notify_changes_to_mining_thread - .sender - .send(()) - .await - .unwrap(); - notify_changes_to_mining_thread.should_send = false; - }; - match next { - SendTo::RelayNewMessageToRemote(_, m) => { - let sv2_frame: StdFrame = MiningDeviceMessages::Mining(m).try_into().unwrap(); - let either_frame: EitherFrame = sv2_frame.into(); - sender.send(either_frame).await.unwrap(); - } - SendTo::None(_) => (), - _ => panic!(), - } - } - } - - async fn send_share( - self_mutex: Arc>, - nonce: u32, - job_id: u32, - version: u32, - ntime: u32, - ) { - let share = - MiningDeviceMessages::Mining(Mining::SubmitSharesStandard(SubmitSharesStandard { - channel_id: self_mutex.safe_lock(|s| s.channel_id.unwrap()).unwrap(), - sequence_number: self_mutex - .safe_lock(|s| s.sequence_numbers.fetch_add(1, Ordering::Relaxed)) - .unwrap(), - job_id, - nonce, - ntime, - version, - })); - let frame: StdFrame = share.try_into().unwrap(); - let sender = self_mutex.safe_lock(|s| s.sender.clone()).unwrap(); - sender.send(frame.into()).await.unwrap(); - } -} - -impl ParseMiningMessagesFromUpstream<()> for Device { - fn get_channel_type(&self) -> SupportedChannelTypes { - SupportedChannelTypes::Standard - } - - fn is_work_selection_enabled(&self) -> bool { - false - } - - fn handle_open_standard_mining_channel_success( - &mut self, - m: OpenStandardMiningChannelSuccess, - ) -> Result, Error> { - self.channel_opened = true; - self.channel_id = Some(m.channel_id); - let req_id = m.get_request_id_as_u32(); - info!( - "MINING DEVICE: channel opened with: group id {}, channel id {}, request id {}", - m.group_channel_id, m.channel_id, req_id - ); - self.miner - .safe_lock(|miner| miner.new_target(m.target.to_vec())) - .unwrap(); - self.notify_changes_to_mining_thread.should_send = true; - Ok(SendTo::None(None)) - } - - fn handle_open_extended_mining_channel_success( - &mut self, - _: OpenExtendedMiningChannelSuccess, - ) -> Result, Error> { - unreachable!() - } - - fn handle_open_mining_channel_error( - &mut self, - _: OpenMiningChannelError, - ) -> Result, Error> { - todo!() - } - - fn handle_update_channel_error(&mut self, _: UpdateChannelError) -> Result, Error> { - todo!() - } - - fn handle_close_channel(&mut self, _: CloseChannel) -> Result, Error> { - todo!() - } - - fn handle_set_extranonce_prefix( - &mut self, - _: SetExtranoncePrefix, - ) -> Result, Error> { - todo!() - } - - fn handle_submit_shares_success( - &mut self, - m: SubmitSharesSuccess, - ) -> Result, Error> { - info!("Received SubmitSharesSuccess"); - debug!("SubmitSharesSuccess: {}", m); - Ok(SendTo::None(None)) - } - - fn handle_submit_shares_error(&mut self, m: SubmitSharesError) -> Result, Error> { - error!( - "Received SubmitSharesError with error code {}", - std::str::from_utf8(m.error_code.as_ref()).unwrap_or("unknown error code") - ); - Ok(SendTo::None(None)) - } - - fn handle_new_mining_job(&mut self, m: NewMiningJob) -> Result, Error> { - info!( - "Received new mining job for channel id: {} with job id: {} is future: {}", - m.channel_id, - m.job_id, - m.is_future() - ); - debug!("NewMiningJob: {}", m); - match (m.is_future(), self.prev_hash.as_ref()) { - (false, Some(p_h)) => { - self.miner - .safe_lock(|miner| miner.new_header(p_h, &m)) - .unwrap(); - self.jobs = vec![m.as_static()]; - self.notify_changes_to_mining_thread.should_send = true; - } - (true, _) => self.jobs.push(m.as_static()), - (false, None) => { - panic!() - } - } - Ok(SendTo::None(None)) - } - - fn handle_new_extended_mining_job( - &mut self, - _: NewExtendedMiningJob, - ) -> Result, Error> { - todo!() - } - - fn handle_set_new_prev_hash(&mut self, m: SetNewPrevHash) -> Result, Error> { - info!( - "Received SetNewPrevHash channel id: {}, job id: {}", - m.channel_id, m.job_id - ); - debug!("SetNewPrevHash: {}", m); - let jobs: Vec<&NewMiningJob<'static>> = self - .jobs - .iter() - .filter(|j| j.job_id == m.job_id && j.is_future()) - .collect(); - match jobs.len() { - 0 => { - self.prev_hash = Some(m.as_static()); - } - 1 => { - self.miner - .safe_lock(|miner| miner.new_header(&m, jobs[0])) - .unwrap(); - self.jobs = vec![jobs[0].clone()]; - self.prev_hash = Some(m.as_static()); - self.notify_changes_to_mining_thread.should_send = true; - } - _ => panic!(), - } - Ok(SendTo::None(None)) - } - - fn handle_set_custom_mining_job_success( - &mut self, - _: SetCustomMiningJobSuccess, - ) -> Result, Error> { - todo!() - } - - fn handle_set_custom_mining_job_error( - &mut self, - _: SetCustomMiningJobError, - ) -> Result, Error> { - todo!() - } - - fn handle_set_target(&mut self, m: SetTarget) -> Result, Error> { - info!("Received SetTarget for channel id: {}", m.channel_id); - debug!("SetTarget: {}", m); - self.miner - .safe_lock(|miner| miner.new_target(m.maximum_target.to_vec())) - .unwrap(); - self.notify_changes_to_mining_thread.should_send = true; - Ok(SendTo::None(None)) - } - - fn handle_set_group_channel(&mut self, _m: SetGroupChannel) -> Result, Error> { - todo!() - } -} - -#[derive(Debug, Clone)] -struct Miner { - header: Option
, - target: Option, - job_id: Option, - version: Option, - handicap: u32, - // Optimized hashing state - fast_hasher: Option, -} - -impl Miner { - fn new(handicap: u32) -> Self { - Self { - target: None, - header: None, - job_id: None, - version: None, - handicap, - fast_hasher: None, - } - } - - fn new_target(&mut self, target: Vec) { - // target is sent in LE format, we'll keep it that way - let hex_string = target - .iter() - .fold("".to_string(), |acc, b| acc + format!("{b:02x}").as_str()); - info!("Set target to {}", hex_string); - // Store the target as U256 in little-endian format - self.target = Some(U256::from_little_endian(target.as_slice())); - } - - // Same as new_target but without logging (useful for internal probes) - fn new_target_silent(&mut self, target: Vec) { - self.target = Some(U256::from_little_endian(target.as_slice())); - } - - fn new_header(&mut self, set_new_prev_hash: &SetNewPrevHash, new_job: &NewMiningJob) { - self.job_id = Some(new_job.job_id); - self.version = Some(new_job.version); - let prev_hash: [u8; 32] = set_new_prev_hash.prev_hash.to_vec().try_into().unwrap(); - let prev_hash = Hash::from_byte_array(prev_hash); - let merkle_root: [u8; 32] = new_job.merkle_root.to_vec().try_into().unwrap(); - let merkle_root = Hash::from_byte_array(merkle_root); - // fields need to be added as BE and the are converted to LE in the background before - // hashing - let header = Header { - version: Version::from_consensus(new_job.version as i32), - prev_blockhash: BlockHash::from_raw_hash(prev_hash), - merkle_root, - time: std::time::SystemTime::now() - .duration_since( - std::time::SystemTime::UNIX_EPOCH - std::time::Duration::from_secs(60), - ) - .unwrap() - .as_secs() as u32, - bits: CompactTarget::from_consensus(set_new_prev_hash.nbits), - nonce: 0, - }; - self.header = Some(header); - // Build a fast hasher with midstate prepared for the static parts of the header - if let Some(h) = &self.header { - self.fast_hasher = Some(FastSha256d::from_header_static(h)); - } else { - self.fast_hasher = None; - } - } - pub fn next_share(&mut self) -> NextShareOutcome { - if let Some(header) = self.header.as_ref() { - // Use optimized path if available - let hash: [u8; 32] = if let Some(fast) = &mut self.fast_hasher { - fast.hash_with_nonce_time(header.nonce, header.time) - } else { - let hash_ = header.block_hash(); - *hash_.to_raw_hash().as_ref() - }; - - // Compare hash against target quickly in little-endian u32 words (most significant at - // index 7) - if let Some(target) = self.target { - let tgt_le = target.to_little_endian(); - // Interpret as 8 little-endian u32 words - let mut is_below = false; - let mut is_equal = true; - // Compare from most significant word (index 7) to least (index 0) - for i in (0..8).rev() { - let off = i * 4; - let hw = u32::from_le_bytes([ - hash[off], - hash[off + 1], - hash[off + 2], - hash[off + 3], - ]); - let tw = u32::from_le_bytes([ - tgt_le[off], - tgt_le[off + 1], - tgt_le[off + 2], - tgt_le[off + 3], - ]); - match hw.cmp(&tw) { - core::cmp::Ordering::Less => { - is_below = true; - is_equal = false; - break; - } - core::cmp::Ordering::Greater => { - is_below = false; - is_equal = false; - break; - } - core::cmp::Ordering::Equal => {} - } - } - - if is_below || is_equal { - info!( - "Found share with nonce: {}, for target: {:?}, with hash: {:?}", - header.nonce, self.target, hash, - ); - NextShareOutcome::ValidShare - } else { - NextShareOutcome::InvalidShare - } - } else { - std::thread::yield_now(); - NextShareOutcome::NoTarget - } - } else { - std::thread::yield_now(); - NextShareOutcome::NoHeader - } - } -} - -// A fast double-SHA256 hasher specialized for Bitcoin block headers. -// It precomputes the midstate of the first 64 bytes (version, prev_blockhash, merkle_root[0..28]) -// and allows quickly hashing varying (time, nonce) fields. -#[derive(Clone, Debug)] -pub struct FastSha256d { - // Midstate after processing the first 64 bytes of the header (chunk 0) - state0: [u32; 8], - // Second block for the first SHA256 (contains merkle tail, time, bits, nonce, padding, length) - // We mutate only the time (bytes 4..8) and nonce (bytes 12..16) per attempt. - block1: GenericArray, - // Reusable buffer for the second SHA256 block. Bytes 32 and 56..64 are constant; we only - // overwrite the first 32 bytes with the first digest each attempt. - second_block: GenericArray, -} - -impl FastSha256d { - pub fn from_header_static(h: &Header) -> Self { - // Use consensus serialization to get correct 80-byte header (proper endianness). - let header_ser = btc_serialize(h); - debug_assert_eq!(header_ser.len(), 80, "Serialized header must be 80 bytes"); - let mut header_bytes = [0u8; 80]; - header_bytes.copy_from_slice(&header_ser); - - // First SHA256 pass: split into two 64-byte chunks - let chunk0 = &header_bytes[0..64]; - let chunk1_last16 = &header_bytes[64..80]; // 16 bytes: merkle_tail(4), time(4), bits(4), nonce(4) - - // Compute midstate after chunk0 using compress256 on an initial state - let mut state0 = sha256_initial_state(); - let mut block = [0u8; 64]; - block.copy_from_slice(chunk0); - let ga0 = GenericArray::::clone_from_slice(&block); - compress256(&mut state0, std::slice::from_ref(&ga0)); - - // Prepare block1 template (64 bytes) which will be: - // bytes 0..16: last 16 bytes of header (time, bits, nonce) - // bytes 16: 0x80 padding - // bytes 17..56: zeros - // bytes 56..64: length in bits of the message (80 bytes -> 640 bits) in big-endian - let mut block1 = GenericArray::::default(); - block1[0..16].copy_from_slice(chunk1_last16); - block1[16] = 0x80; - block1[56..64].copy_from_slice(&640u64.to_be_bytes()); - - // Prepare reusable second block: set constants once - let mut second_block = GenericArray::::default(); - second_block[32] = 0x80; - // 33..56 are already zero via default - second_block[56..64].copy_from_slice(&256u64.to_be_bytes()); - - Self { - state0, - block1, - second_block, - } - } - - // Hashes header where only time and nonce vary, returns double-SHA256 as [u8;32] (little-endian - // like rust-bitcoin output) - pub fn hash_with_nonce_time(&mut self, nonce: u32, time: u32) -> [u8; 32] { - // First SHA256 second chunk: update time and nonce at offsets 68..72 and 76..80 within - // 80-byte header In our block1_template (offset 0..16 == 64..80 of header): - // time at 0..4, bits at 4..8, nonce at 12..16 - // Update time and nonce in place - self.block1[4..8].copy_from_slice(&time.to_le_bytes()); - self.block1[12..16].copy_from_slice(&nonce.to_le_bytes()); - - // Compute first SHA256 digest using midstate + block1 - let mut state1 = self.state0; - compress256(&mut state1, std::slice::from_ref(&self.block1)); - - // Now perform the second SHA256 over the 32-byte first digest Build 64-byte block: - // [digest(32)] + [0x80] + [zeros] + [length=256 bits] - // state1 words -> big-endian bytes per SHA-256 spec (fill first 32 bytes) - for (i, word) in state1.iter().enumerate() { - self.second_block[i * 4..i * 4 + 4].copy_from_slice(&word.to_be_bytes()); - } - - let mut state2 = sha256_initial_state(); - compress256(&mut state2, std::slice::from_ref(&self.second_block)); - - // Convert state2 words to bytes (big-endian), then reverse for Bitcoin-style - // little-endian - let mut out = [0u8; 32]; - for (i, word) in state2.iter().enumerate() { - out[i * 4..i * 4 + 4].copy_from_slice(&word.to_be_bytes()); - } - out - } -} - -fn sha256_initial_state() -> [u32; 8] { - [ - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, - 0x5be0cd19, - ] -} - -enum NextShareOutcome { - ValidShare, - InvalidShare, - NoTarget, - NoHeader, -} - -impl NextShareOutcome { - pub fn is_valid(&self) -> bool { - matches!(self, NextShareOutcome::ValidShare) - } -} - -#[inline] -fn hash_meets_target_le(hash: &[u8; 32], tgt_le: &[u8; 32]) -> bool { - // Compare from most significant u32 word (index 7) to least (index 0) - let mut is_below = false; - let mut is_equal = true; - for i in (0..8).rev() { - let off = i * 4; - let hw = u32::from_le_bytes([hash[off], hash[off + 1], hash[off + 2], hash[off + 3]]); - let tw = u32::from_le_bytes([ - tgt_le[off], - tgt_le[off + 1], - tgt_le[off + 2], - tgt_le[off + 3], - ]); - match hw.cmp(&tw) { - core::cmp::Ordering::Less => { - is_below = true; - is_equal = false; - break; - } - core::cmp::Ordering::Greater => { - is_below = false; - is_equal = false; - break; - } - core::cmp::Ordering::Equal => {} - } - } - is_below || is_equal -} - -// Format MH/s with thousands separators and 2 decimal places using en locale separators -fn format_mhs(val_mhs: f64) -> String { - let rounded = val_mhs.round() as i64; - rounded.to_formatted_string(&Locale::en) -} - -// returns hashrate by running all worker threads in parallel for the given duration -fn measure_hashrate(duration_secs: u64, handicap: u32) -> f64 { - use std::sync::Barrier; - - // Prepare a random header template to hash - let mut rng = thread_rng(); - let prev_hash: [u8; 32] = generate_random_32_byte_array().to_vec().try_into().unwrap(); - let prev_hash = Hash::from_byte_array(prev_hash); - let merkle_root: [u8; 32] = generate_random_32_byte_array().to_vec().try_into().unwrap(); - let merkle_root = Hash::from_byte_array(merkle_root); - let header_template = Header { - version: Version::from_consensus(rng.gen()), - prev_blockhash: BlockHash::from_raw_hash(prev_hash), - merkle_root, - time: std::time::SystemTime::now() - .duration_since(std::time::SystemTime::UNIX_EPOCH - std::time::Duration::from_secs(60)) - .unwrap() - .as_secs() as u32, - bits: CompactTarget::from_consensus(rng.gen()), - nonce: 0, - }; - - let duration = Duration::from_secs(duration_secs); - let p = worker_count() as usize; - let barrier = Arc::new(Barrier::new(p + 1)); // +1 for coordinator - - let mut handles = Vec::with_capacity(p); - // Log a single consolidated target-setting message for the probe - info!("Set target to {}", "0".repeat(64)); - for _ in 0..p { - let barrier = barrier.clone(); - // Each thread gets its own miner and header copy - let mut miner = Miner::new(handicap); - // Set target to zero (silently) so we never trigger share submits; we're only counting - // hashes - miner.new_target_silent(vec![0_u8; 32]); - miner.header = Some(header_template); - if let Some(h) = miner.header.as_ref() { - miner.fast_hasher = Some(FastSha256d::from_header_static(h)); - } - handles.push(std::thread::spawn(move || { - // Synchronize start across threads - barrier.wait(); - let start = Instant::now(); - let mut hashes: u64 = 0; - while start.elapsed() < duration { - miner.next_share(); - hashes += 1; - } - hashes - })); - } - - // Release all workers simultaneously - barrier.wait(); - let mut total_hashes: u64 = 0; - for h in handles { - total_hashes += h.join().unwrap_or(0); - } - // Each thread ran for approximately `duration`, so total hashes per second is total/duration - (total_hashes as f64) / (duration_secs as f64) -} -fn generate_random_32_byte_array() -> [u8; 32] { - let mut rng = thread_rng(); - let mut arr = [0u8; 32]; - rng.fill(&mut arr[..]); - arr -} - -fn start_mining_threads( - have_new_job: Receiver<()>, - miner: Arc>, - share_send: Sender<(u32, u32, u32, u32)>, -) { - tokio::task::spawn(async move { - let mut killers: Vec> = vec![]; - loop { - // Determine number of workers based on override or auto (N-1) - let p = worker_count(); - let unit = u32::MAX / p; - while have_new_job.recv().await.is_ok() { - while let Some(killer) = killers.pop() { - killer.store(true, Ordering::Relaxed); - } - let miner = miner.safe_lock(|m| m.clone()).unwrap(); - for i in 0..p { - let mut miner = miner.clone(); - let share_send = share_send.clone(); - let killer = Arc::new(AtomicBool::new(false)); - miner.header.as_mut().map(|h| h.nonce = i * unit); - killers.push(killer.clone()); - std::thread::spawn(move || { - mine(miner, share_send, killer); - }); - } - } - } - }); -} - -fn mine(mut miner: Miner, share_send: Sender<(u32, u32, u32, u32)>, kill: Arc) { - if miner.handicap != 0 { - loop { - if kill.load(Ordering::Relaxed) { - break; - } - std::thread::sleep(std::time::Duration::from_micros(miner.handicap.into())); - // Prefer fast path with micro-batching when possible - let can_fast = - miner.fast_hasher.is_some() && miner.target.is_some() && miner.header.is_some(); - if can_fast { - let header = miner.header.as_mut().unwrap(); - let time = header.time; - let start = header.nonce; - let tgt_le = miner.target.unwrap().to_little_endian(); - let fast = miner.fast_hasher.as_mut().unwrap(); - let mut found = None; - let batch = nonces_per_call(); - for i in 0..batch { - let nonce = start.wrapping_add(i); - let hash = fast.hash_with_nonce_time(nonce, time); - if hash_meets_target_le(&hash, &tgt_le) { - found = Some((nonce, hash)); - break; - } - } - if let Some((nonce, hash)) = found { - header.nonce = nonce; - info!( - "Found share with nonce: {}, for target: {:?}, with hash: {:?}", - header.nonce, miner.target, hash, - ); - let job_id = miner.job_id.unwrap(); - let version = miner.version; - share_send - .try_send((nonce, job_id, version.unwrap(), time)) - .unwrap(); - } - // Advance nonce window - header.nonce = start.wrapping_add(batch); - } else { - if miner.next_share().is_valid() { - let nonce = miner.header.unwrap().nonce; - let time = miner.header.unwrap().time; - let job_id = miner.job_id.unwrap(); - let version = miner.version; - share_send - .try_send((nonce, job_id, version.unwrap(), time)) - .unwrap(); - } - miner - .header - .as_mut() - .map(|h| h.nonce = h.nonce.wrapping_add(1)); - } - } - } else { - loop { - // Prefer fast path with micro-batching when possible - if kill.load(Ordering::Relaxed) { - break; - } - let can_fast = - miner.fast_hasher.is_some() && miner.target.is_some() && miner.header.is_some(); - if can_fast { - let header = miner.header.as_mut().unwrap(); - let time = header.time; - let start = header.nonce; - let tgt_le = miner.target.unwrap().to_little_endian(); - let fast = miner.fast_hasher.as_mut().unwrap(); - let mut found = None; - let batch = nonces_per_call(); - for i in 0..batch { - let nonce = start.wrapping_add(i); - let hash = fast.hash_with_nonce_time(nonce, time); - if hash_meets_target_le(&hash, &tgt_le) { - found = Some((nonce, hash)); - break; - } - } - if let Some((nonce, hash)) = found { - header.nonce = nonce; - info!( - "Found share with nonce: {}, for target: {:?}, with hash: {:?}", - header.nonce, miner.target, hash, - ); - let job_id = miner.job_id.unwrap(); - let version = miner.version; - share_send - .try_send((nonce, job_id, version.unwrap(), time)) - .unwrap(); - } - // Advance nonce window - header.nonce = start.wrapping_add(batch); - } else { - if miner.next_share().is_valid() { - if kill.load(Ordering::Relaxed) { - break; - } - let nonce = miner.header.unwrap().nonce; - let time = miner.header.unwrap().time; - let job_id = miner.job_id.unwrap(); - let version = miner.version; - share_send - .try_send((nonce, job_id, version.unwrap(), time)) - .unwrap(); - } - miner - .header - .as_mut() - .map(|h| h.nonce = h.nonce.wrapping_add(1)); - } - } - } -} diff --git a/roles/test-utils/mining-device/src/main.rs b/roles/test-utils/mining-device/src/main.rs deleted file mode 100644 index be70942157..0000000000 --- a/roles/test-utils/mining-device/src/main.rs +++ /dev/null @@ -1,89 +0,0 @@ -#![allow(special_module_name)] -#![allow(clippy::option_map_unit_fn)] -use stratum_apps::key_utils::Secp256k1PublicKey; - -use clap::Parser; -use tracing::info; - -#[derive(Parser, Debug)] -#[command(version, about, long_about = None)] -struct Args { - #[arg( - short, - long, - help = "Pool pub key, when left empty the pool certificate is not checked" - )] - pubkey_pool: Option, - #[arg( - short, - long, - help = "Sometimes used by the pool to identify the device" - )] - id_device: Option, - #[arg( - short, - long, - help = "Address of the pool in this format ip:port or domain:port" - )] - address_pool: String, - #[arg( - long, - help = "This value is used to slow down the cpu miner, it represents the number of micro-seconds that are awaited between hashes", - default_value = "0" - )] - handicap: u32, - #[arg( - long, - help = "User id, used when a new channel is opened, it can be used by the pool to identify the miner" - )] - id_user: Option, - #[arg( - long, - help = "This floating point number is used to modify the advertised nominal hashrate when opening a channel with the upstream.\ - \nIf 0.0 < nominal_hashrate_multiplier < 1.0, the CPU miner will advertise a nominal hashrate that is smaller than its real capacity.\ - \nIf nominal_hashrate_multiplier > 1.0, the CPU miner will advertise a nominal hashrate that is bigger than its real capacity.\ - \nIf empty, the CPU miner will simply advertise its real capacity." - )] - nominal_hashrate_multiplier: Option, - #[arg( - long, - help = "Number of nonces to try per mining loop iteration when fast hashing is available (micro-batching)", - default_value = "32" - )] - nonces_per_call: u32, - #[arg( - long, - help = "Number of worker threads to use for mining. Defaults to logical CPUs minus one (leaves one core free)." - )] - cores: Option, -} - -#[tokio::main(flavor = "current_thread")] -async fn main() { - let args = Args::parse(); - tracing_subscriber::fmt::init(); - info!("start"); - // Configure micro-batch size - mining_device::set_nonces_per_call(args.nonces_per_call); - // Optional override of worker threads - if let Some(n) = args.cores { - mining_device::set_cores(n); - } - // Log worker usage (after applying overrides) - let used = mining_device::effective_worker_count(); - let total = mining_device::total_logical_cpus(); - info!( - "Using {} worker threads out of {} logical CPUs", - used, total - ); - let _ = mining_device::connect( - args.address_pool, - args.pubkey_pool, - args.id_device, - args.id_user, - args.handicap, - args.nominal_hashrate_multiplier, - false, - ) - .await; -} diff --git a/roles/test-utils/mining-device/tests/fast_hasher_equivalence.rs b/roles/test-utils/mining-device/tests/fast_hasher_equivalence.rs deleted file mode 100644 index 01e5225cf3..0000000000 --- a/roles/test-utils/mining-device/tests/fast_hasher_equivalence.rs +++ /dev/null @@ -1,41 +0,0 @@ -use mining_device::FastSha256d; -use rand::{thread_rng, Rng}; -use stratum_apps::stratum_core::bitcoin::{ - block::Version, blockdata::block::Header, hash_types::BlockHash, hashes::Hash, CompactTarget, -}; - -fn random_header() -> Header { - let mut rng = thread_rng(); - let prev_hash: [u8; 32] = rng.gen(); - let prev_hash = Hash::from_byte_array(prev_hash); - let merkle_root: [u8; 32] = rng.gen(); - let merkle_root = Hash::from_byte_array(merkle_root); - Header { - version: Version::from_consensus(rng.gen::()), - prev_blockhash: BlockHash::from_raw_hash(prev_hash), - merkle_root, - time: std::time::SystemTime::now() - .duration_since(std::time::SystemTime::UNIX_EPOCH - std::time::Duration::from_secs(60)) - .unwrap() - .as_secs() as u32, - bits: CompactTarget::from_consensus(rng.gen()), - nonce: 0, - } -} - -#[test] -fn fast_hasher_matches_baseline() { - let mut h = random_header(); - let mut fast = FastSha256d::from_header_static(&h); - - for _ in 0..1000 { - // Advance nonce, occasionally tweak time - h.nonce = h.nonce.wrapping_add(1); - if h.nonce % 128 == 0 { - h.time = h.time.wrapping_add(1); - } - let fast_hash = fast.hash_with_nonce_time(h.nonce, h.time); - let baseline: [u8; 32] = *h.block_hash().to_raw_hash().as_ref(); - assert_eq!(fast_hash, baseline, "Fast hasher must match baseline"); - } -} diff --git a/roles/translator/Cargo.toml b/roles/translator/Cargo.toml deleted file mode 100644 index 529f8ceae1..0000000000 --- a/roles/translator/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "translator_sv2" -version = "2.0.0" -authors = ["The Stratum V2 Developers"] -edition = "2021" -description = "SV1 to SV2 translation proxy" -documentation = "https://docs.rs/translator_sv2" -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", "translator", "proxy"] - -[lib] -name = "translator_sv2" -path = "src/lib/mod.rs" - -[[bin]] -name = "translator_sv2" -path = "src/main.rs" - -[dependencies] -stratum-apps = { path = "../stratum-apps", features = ["translator"] } -async-channel = "1.5.1" -serde = { version = "1.0.89", default-features = false, features = ["derive", "alloc"] } -serde_json = { version = "1.0.64", default-features = false, features = ["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"] } - - -[dev-dependencies] -sha2 = "0.10.6" - diff --git a/roles/translator/README.md b/roles/translator/README.md deleted file mode 100644 index ee14bf3aa1..0000000000 --- a/roles/translator/README.md +++ /dev/null @@ -1,204 +0,0 @@ -# SV1 to SV2 Translator Proxy - -A proxy that translates between Stratum V1 (SV1) and Stratum V2 (SV2) mining protocols. This translator enables SV1 mining devices to connect to SV2 pools and infrastructure, bridging the gap between legacy mining hardware and modern mining protocols. - -## Architecture Overview - -The translator sits between SV1 downstream roles (mining devices) and SV2 upstream roles (pool servers or proxies), providing seamless protocol translation and advanced features like channel aggregation and failover. - -``` -<--- Most Downstream ----------------------------------------- Most Upstream ---> - -+---------------------------------------------------+ +------------------------+ -| Mining Farm | | Remote Pool | -| | | | -| +-------------------+ +------------------+ | | +-----------------+ | -| | SV1 Mining Device | <-> | Translator Proxy | <------> | SV2 Pool Server | | -| +-------------------+ +------------------+ | | +-----------------+ | -| | | | -+---------------------------------------------------+ +------------------------+ -``` - -## Configuration - -### Configuration File Structure - -The translator uses TOML configuration files with the following structure: - -```toml -# Downstream SV1 Connection (where miners connect) -downstream_address = "0.0.0.0" -downstream_port = 34255 - -# Protocol Version Support -max_supported_version = 2 -min_supported_version = 2 - -# Extranonce Configuration -downstream_extranonce2_size = 4 # Min: 2, Max: 16 (CGminer max: 8) - -# User Identity (appended with counter for each miner) -user_identity = "your_username_here" - -# Channel Configuration -aggregate_channels = true # true: shared channel, false: individual channels - -# Downstream Difficulty Configuration -[downstream_difficulty_config] -min_individual_miner_hashrate = 10_000_000_000_000.0 # 10 TH/s -shares_per_minute = 6.0 -enable_vardiff = true # Set to false when using with Job Declarator Client (JDC) - -# Upstream SV2 Connections (supports multiple with failover) -[[upstreams]] -address = "127.0.0.1" -port = 34254 -authority_pubkey = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" - -[[upstreams]] -address = "backup.pool.com" -port = 34254 -authority_pubkey = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" -``` - -### Configuration Parameters - -#### **Downstream Configuration** -- `downstream_address`: IP address for SV1 miners to connect to -- `downstream_port`: Port for SV1 miners to connect to - -#### **Protocol Configuration** -- `max_supported_version`/`min_supported_version`: SV2 protocol version support -- `min_extranonce2_size`: Minimum extranonce2 size (affects mining efficiency) - -#### **Channel Configuration** -- `aggregate_channels`: - - `true`: All miners share one upstream extended channel (more efficient) - - `false`: Each miner gets its own upstream extended channel (more isolated) -- `user_identity`: Username for pool authentication (auto-suffixed per miner) - -#### **Difficulty Configuration** -- `min_individual_miner_hashrate`: Expected hashrate of weakest miner (in H/s) -- `shares_per_minute`: Target share submission rate -- `enable_vardiff`: Enable/disable variable difficulty adjustment (set to false when using with JDC) - - When `true`: Translator manages difficulty adjustments based on share submission rates - - When `false`: Upstream manages difficulty, translator forwards SetTarget messages to miners - -#### **Upstream Configuration** -- `address`/`port`: SV2 upstream server connection details -- `authority_pubkey`: Public key for SV2 connection authentication - -## Usage - -### Installation & Build - -```bash -# Clone the repository -git clone https://github.com/stratum-mining/stratum.git -cd stratum - -# Build the translator -cargo build --release -p translator_sv2 -``` - -### Running the Translator - -#### **With Local Pool** -```bash -cd roles/translator -cargo run -- -c config-examples/tproxy-config-local-pool-example.toml -``` - -#### **With Job Declaration Client** -```bash -cd roles/translator -cargo run -- -c config-examples/tproxy-config-local-jdc-example.toml -``` - -#### **With Hosted Pool** -```bash -cd roles/translator -cargo run -- -c config-examples/tproxy-config-hosted-pool-example.toml -``` - -### Command Line Options - -```bash -# Use specific config file -translator_sv2 -c /path/to/config.toml -translator_sv2 --config /path/to/config.toml - -# Show help -translator_sv2 -h -translator_sv2 --help -``` - -## Configuration Examples - -### Example 1: Local Pool Setup -For connecting to a local SV2 pool server: - -```toml -downstream_address = "0.0.0.0" -downstream_port = 34255 -user_identity = "miner_farm_1" -aggregate_channels = true - -[downstream_difficulty_config] -min_individual_miner_hashrate = 10_000_000_000_000.0 -shares_per_minute = 6.0 -enable_vardiff = true - -[[upstreams]] -address = "127.0.0.1" -port = 34254 -authority_pubkey = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" -``` - -### Example 2: High-Availability Setup -For production environments with failover: - -```toml -downstream_address = "0.0.0.0" -downstream_port = 34255 -user_identity = "production_farm" -aggregate_channels = true - -[downstream_difficulty_config] -min_individual_miner_hashrate = 50_000_000_000_000.0 # 50 TH/s -shares_per_minute = 10.0 -enable_vardiff = true - -# Primary upstream -[[upstreams]] -address = "primary.pool.com" -port = 34254 -authority_pubkey = "primary_pool_pubkey" - -# Backup upstream -[[upstreams]] -address = "backup.pool.com" -port = 34254 -authority_pubkey = "backup_pool_pubkey" -``` - -## Architecture Details - -### **Component Overview** - -1. **SV1 Server**: Handles incoming SV1 connections from mining devices -2. **SV2 Upstream**: Manages connections to SV2 pool servers with failover -3. **Channel Manager**: Orchestrates message routing and protocol translation -4. **Task Manager**: Manages async task lifecycle and coordination -5. **Status System**: Provides real-time monitoring and health reporting - -### **Channel Modes** - -- **Aggregated Mode**: All miners share one extended channel - - More efficient for large farms - - Reduced upstream connection overhead - - Shared work distribution - -- **Non-Aggregated Mode**: Each miner gets individual upstream channel - - Better isolation between miners - - Individual difficulty adjustment by the upstream Pool \ No newline at end of file diff --git a/roles/translator/config-examples/tproxy-config-hosted-pool-example.toml b/roles/translator/config-examples/tproxy-config-hosted-pool-example.toml deleted file mode 100644 index 020c66a404..0000000000 --- a/roles/translator/config-examples/tproxy-config-hosted-pool-example.toml +++ /dev/null @@ -1,46 +0,0 @@ -# Local Mining Device Downstream Connection -downstream_address = "0.0.0.0" -downstream_port = 34255 - -# Version support -max_supported_version = 2 -min_supported_version = 2 - -# Extranonce2 size for downstream connections -# This controls the rollable part of the extranonce for downstream SV1 miners -# Max value for CGminer: 8 -# Min value: 2 -downstream_extranonce2_size = 4 - -# User identity/username for pool connection -# This will be appended with a counter for each mining client (e.g., username.miner1, username.miner2) -user_identity = "your_username_here" - -# Aggregate channels: if true, all miners share one upstream channel; if false, each miner gets its own channel -aggregate_channels = true - -# 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 = "./tproxy.log" - -# Difficulty params -[downstream_difficulty_config] -# hashes/s of the weakest miner that will be connecting (e.g.: 10 Th/s = 10_000_000_000_000.0) -min_individual_miner_hashrate=10_000_000_000_000.0 -# target number of shares per minute the miner should be sending -shares_per_minute = 6.0 -# enable variable difficulty adjustment (true by default, set to false when using with JDC) -enable_vardiff = true - -[[upstreams]] -# SRI Pool Primary Pool -address = "75.119.150.111" -port = 34254 -authority_pubkey = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" - -# Braiins Pool Backup Pool -[[upstreams]] -address = "107.170.42.64" -port = 3333 -authority_pubkey = "9awtMD5KQgvRUh2yFbjVeT7b6hjipWcAsQHd6wEhgtDT9soosna" \ No newline at end of file diff --git a/roles/translator/config-examples/tproxy-config-local-jdc-example.toml b/roles/translator/config-examples/tproxy-config-local-jdc-example.toml deleted file mode 100644 index 28645afa8d..0000000000 --- a/roles/translator/config-examples/tproxy-config-local-jdc-example.toml +++ /dev/null @@ -1,40 +0,0 @@ -# Local Mining Device Downstream Connection -downstream_address = "0.0.0.0" -downstream_port = 34255 - -# Version support -max_supported_version = 2 -min_supported_version = 2 - -# Extranonce2 size for downstream connections -# This controls the rollable part of the extranonce for downstream miners -# Max value for CGminer: 8 -# Min value: 2 -downstream_extranonce2_size = 4 - -# User identity/username for pool connection -# This will be appended with a counter for each mining client (e.g., username.miner1, username.miner2) -user_identity = "your_username_here" - -# Aggregate channels: if true, all miners share one upstream channel; if false, each miner gets its own channel -aggregate_channels = false - -# 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 = "./tproxy.log" - -# Difficulty params -[downstream_difficulty_config] -# hashes/s of the weakest miner that will be connecting (e.g.: 10 Th/s = 10_000_000_000_000.0) -min_individual_miner_hashrate=10_000_000_000_000.0 -# target number of shares per minute the miner should be sending -shares_per_minute = 6.0 -# disable variable difficulty adjustment when using with JDC (JDC handles vardiff) -enable_vardiff = false - - -[[upstreams]] -address = "127.0.0.1" -port = 34265 -authority_pubkey = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" \ No newline at end of file diff --git a/roles/translator/config-examples/tproxy-config-local-pool-example.toml b/roles/translator/config-examples/tproxy-config-local-pool-example.toml deleted file mode 100644 index 65ee173af8..0000000000 --- a/roles/translator/config-examples/tproxy-config-local-pool-example.toml +++ /dev/null @@ -1,39 +0,0 @@ -# Local Mining Device Downstream Connection -downstream_address = "0.0.0.0" -downstream_port = 34255 - -# Version support -max_supported_version = 2 -min_supported_version = 2 - -# Extranonce2 size for downstream connections -# This controls the rollable part of the extranonce for downstream miners -# Max value for CGminer: 8 -# Min value: 2 -downstream_extranonce2_size = 4 - -# User identity/username for pool connection -# This will be appended with a counter for each mining client (e.g., username.miner1, username.miner2) -user_identity = "your_username_here" - -# Aggregate channels: if true, all miners share one upstream channel; if false, each miner gets its own channel -aggregate_channels = true - -# 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 = "./tproxy.log" - -# Difficulty params -[downstream_difficulty_config] -# hashes/s of the weakest miner that will be connecting (e.g.: 10 Th/s = 10_000_000_000_000.0) -min_individual_miner_hashrate=10_000_000_000_000.0 -# target number of shares per minute the miner should be sending -shares_per_minute = 6.0 -# enable variable difficulty adjustment (true by default, set to false when using with JDC) -enable_vardiff = true - -[[upstreams]] -address = "127.0.0.1" -port = 34254 -authority_pubkey = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" \ No newline at end of file diff --git a/roles/translator/src/args.rs b/roles/translator/src/args.rs deleted file mode 100644 index e43746ccaa..0000000000 --- a/roles/translator/src/args.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! Defines the structure and parsing logic for command-line arguments. -//! -//! It provides the `Args` struct to hold parsed arguments, -//! and the `from_args` function to parse them from the command line. -use clap::Parser; -use ext_config::{Config, File, FileFormat}; -use std::path::PathBuf; -use tracing::error; -use translator_sv2::{config::TranslatorConfig, error::TproxyError}; - -/// Holds the parsed CLI arguments. -#[derive(Parser, Debug)] -#[command(author, version, about = "Translator Proxy", long_about = None)] -pub struct Args { - #[arg( - short = 'c', - long = "config", - help = "Path to the TOML configuration file", - default_value = "proxy-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, -} - -/// Process CLI args, if any. -#[allow(clippy::result_large_err)] -pub fn process_cli_args() -> Result { - // Parse CLI arguments - let args = Args::parse(); - - // Build configuration from the provided file path - let config_path = args.config_path.to_str().ok_or_else(|| { - error!("Invalid configuration path."); - TproxyError::BadCliArgs - })?; - - let settings = Config::builder() - .add_source(File::new(config_path, FileFormat::Toml)) - .build()?; - - // Deserialize settings into TranslatorConfig - let mut config = settings.try_deserialize::()?; - - config.set_log_dir(args.log_file); - - Ok(config) -} diff --git a/roles/translator/src/lib/config.rs b/roles/translator/src/lib/config.rs deleted file mode 100644 index 8094147f93..0000000000 --- a/roles/translator/src/lib/config.rs +++ /dev/null @@ -1,268 +0,0 @@ -//! ## Translator Configuration Module -//! -//! Defines [`TranslatorConfig`], the primary configuration structure for the Translator. -//! -//! This module provides the necessary structures to configure the Translator, -//! managing connections and settings for both upstream and downstream interfaces. -//! -//! This module handles: -//! - Upstream server address, port, and authentication key ([`UpstreamConfig`]) -//! - Downstream interface address and port ([`DownstreamConfig`]) -//! - Supported protocol versions -//! - Downstream difficulty adjustment parameters ([`DownstreamDifficultyConfig`]) -use std::path::{Path, PathBuf}; - -use serde::Deserialize; -use stratum_apps::key_utils::Secp256k1PublicKey; - -/// Configuration for the Translator. -#[derive(Debug, Deserialize, Clone)] -pub struct TranslatorConfig { - pub upstreams: Vec, - /// The address for the downstream interface. - pub downstream_address: String, - /// The port for the downstream interface. - pub downstream_port: u16, - /// The maximum supported protocol version for communication. - pub max_supported_version: u16, - /// The minimum supported protocol version for communication. - pub min_supported_version: u16, - /// The size of the extranonce2 field for downstream mining connections. - pub downstream_extranonce2_size: u16, - /// The user identity/username to use when connecting to the pool. - /// This will be appended with a counter for each mining channel (e.g., username.miner1, - /// username.miner2). - pub user_identity: String, - /// Configuration settings for managing difficulty on the downstream connection. - pub downstream_difficulty_config: DownstreamDifficultyConfig, - /// Whether to aggregate all downstream connections into a single upstream channel. - /// If true, all miners share one channel. If false, each miner gets its own channel. - pub aggregate_channels: bool, - /// The path to the log file for the Translator. - log_file: Option, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Upstream { - /// The address of the upstream server. - pub address: String, - /// The port of the upstream server. - pub port: u16, - /// The Secp256k1 public key used to authenticate the upstream authority. - pub authority_pubkey: Secp256k1PublicKey, -} - -impl Upstream { - /// Creates a new `UpstreamConfig` instance. - pub fn new(address: String, port: u16, authority_pubkey: Secp256k1PublicKey) -> Self { - Self { - address, - port, - authority_pubkey, - } - } -} - -impl TranslatorConfig { - /// Creates a new `TranslatorConfig` instance with the specified upstream and downstream - /// configurations and version constraints. - #[allow(clippy::too_many_arguments)] - pub fn new( - upstreams: Vec, - downstream_address: String, - downstream_port: u16, - downstream_difficulty_config: DownstreamDifficultyConfig, - max_supported_version: u16, - min_supported_version: u16, - downstream_extranonce2_size: u16, - user_identity: String, - aggregate_channels: bool, - ) -> Self { - Self { - upstreams, - downstream_address, - downstream_port, - max_supported_version, - min_supported_version, - downstream_extranonce2_size, - user_identity, - downstream_difficulty_config, - aggregate_channels, - log_file: None, - } - } - - pub fn set_log_dir(&mut self, log_dir: Option) { - if let Some(dir) = log_dir { - self.log_file = Some(dir); - } - } - pub fn log_dir(&self) -> Option<&Path> { - self.log_file.as_deref() - } -} - -/// Configuration settings for managing difficulty adjustments on the downstream connection. -#[derive(Debug, Deserialize, Clone)] -pub struct DownstreamDifficultyConfig { - /// The minimum hashrate expected from an individual miner on the downstream connection. - pub min_individual_miner_hashrate: f32, - /// The target number of shares per minute for difficulty adjustment. - pub shares_per_minute: f32, - /// Whether to enable variable difficulty adjustment mechanism. - /// If false, difficulty will be managed by upstream (useful with JDC). - pub enable_vardiff: bool, -} - -impl DownstreamDifficultyConfig { - /// Creates a new `DownstreamDifficultyConfig` instance. - pub fn new( - min_individual_miner_hashrate: f32, - shares_per_minute: f32, - enable_vardiff: bool, - ) -> Self { - Self { - min_individual_miner_hashrate, - shares_per_minute, - enable_vardiff, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::str::FromStr; - - fn create_test_upstream() -> Upstream { - // Use a valid base58-encoded public key from the key-utils test cases - let pubkey_str = "9bDuixKmZqAJnrmP746n8zU1wyAQRrus7th9dxnkPg6RzQvCnan"; - let pubkey = Secp256k1PublicKey::from_str(pubkey_str).unwrap(); - Upstream::new("127.0.0.1".to_string(), 4444, pubkey) - } - - fn create_test_difficulty_config() -> DownstreamDifficultyConfig { - DownstreamDifficultyConfig::new(100.0, 5.0, true) - } - - #[test] - fn test_upstream_creation() { - let upstream = create_test_upstream(); - assert_eq!(upstream.address, "127.0.0.1"); - assert_eq!(upstream.port, 4444); - } - - #[test] - fn test_downstream_difficulty_config_creation() { - let config = create_test_difficulty_config(); - assert_eq!(config.min_individual_miner_hashrate, 100.0); - assert_eq!(config.shares_per_minute, 5.0); - assert!(config.enable_vardiff); - } - - #[test] - fn test_translator_config_creation() { - let upstreams = vec![create_test_upstream()]; - let difficulty_config = create_test_difficulty_config(); - - let config = TranslatorConfig::new( - upstreams, - "0.0.0.0".to_string(), - 3333, - difficulty_config, - 2, - 1, - 4, - "test_user".to_string(), - true, - ); - - assert_eq!(config.upstreams.len(), 1); - assert_eq!(config.downstream_address, "0.0.0.0"); - assert_eq!(config.downstream_port, 3333); - assert_eq!(config.max_supported_version, 2); - assert_eq!(config.min_supported_version, 1); - assert_eq!(config.downstream_extranonce2_size, 4); - assert_eq!(config.user_identity, "test_user"); - assert!(config.aggregate_channels); - assert!(config.log_file.is_none()); - } - - #[test] - fn test_translator_config_log_dir() { - let upstreams = vec![create_test_upstream()]; - let difficulty_config = create_test_difficulty_config(); - - let mut config = TranslatorConfig::new( - upstreams, - "0.0.0.0".to_string(), - 3333, - difficulty_config, - 2, - 1, - 4, - "test_user".to_string(), - false, - ); - - assert!(config.log_dir().is_none()); - - let log_path = PathBuf::from("/tmp/logs"); - config.set_log_dir(Some(log_path.clone())); - assert_eq!(config.log_dir(), Some(log_path.as_path())); - - config.set_log_dir(None); - assert_eq!(config.log_dir(), Some(log_path.as_path())); // Should remain unchanged - } - - #[test] - fn test_multiple_upstreams() { - let upstream1 = create_test_upstream(); - let mut upstream2 = create_test_upstream(); - upstream2.address = "192.168.1.1".to_string(); - upstream2.port = 5555; - - let upstreams = vec![upstream1, upstream2]; - let difficulty_config = create_test_difficulty_config(); - - let config = TranslatorConfig::new( - upstreams, - "0.0.0.0".to_string(), - 3333, - difficulty_config, - 2, - 1, - 4, - "test_user".to_string(), - true, - ); - - assert_eq!(config.upstreams.len(), 2); - assert_eq!(config.upstreams[0].address, "127.0.0.1"); - assert_eq!(config.upstreams[0].port, 4444); - assert_eq!(config.upstreams[1].address, "192.168.1.1"); - assert_eq!(config.upstreams[1].port, 5555); - } - - #[test] - fn test_vardiff_disabled_config() { - let mut difficulty_config = create_test_difficulty_config(); - difficulty_config.enable_vardiff = false; - - let upstreams = vec![create_test_upstream()]; - let config = TranslatorConfig::new( - upstreams, - "0.0.0.0".to_string(), - 3333, - difficulty_config, - 2, - 1, - 4, - "test_user".to_string(), - false, - ); - - assert!(!config.downstream_difficulty_config.enable_vardiff); - assert!(!config.aggregate_channels); - } -} diff --git a/roles/translator/src/lib/error.rs b/roles/translator/src/lib/error.rs deleted file mode 100644 index 655e2d9626..0000000000 --- a/roles/translator/src/lib/error.rs +++ /dev/null @@ -1,213 +0,0 @@ -//! ## Translator Error Module -//! -//! Defines the custom error types used throughout the translator proxy. -//! -//! This module centralizes error handling by providing: -//! - A primary `Error` enum encompassing various error kinds from different sources (I/O, parsing, -//! protocol logic, channels, configuration, etc.). -//! - A specific `ChannelSendError` enum for errors occurring during message sending over -//! asynchronous channels. - -use ext_config::ConfigError; -use std::{fmt, sync::PoisonError}; -use stratum_apps::stratum_core::{ - binary_sv2, framing_sv2, handlers_sv2::HandlerErrorType, noise_sv2, - parsers_sv2::ParserError as RolesParserError, sv1_api::server_to_client::SetDifficulty, -}; -use tokio::sync::broadcast; - -#[derive(Debug)] -pub enum TproxyError { - /// Generic SV1 protocol error - SV1Error, - /// Error from the network helpers library - NetworkHelpersError(stratum_apps::network_helpers::Error), - /// Error from roles logic parser library - ParserError(RolesParserError), - /// Errors on bad CLI argument input. - BadCliArgs, - /// Errors on bad `serde_json` serialize/deserialize. - BadSerdeJson(serde_json::Error), - /// 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), - /// Mutex poison lock error - PoisonLock, - /// Channel receiver error - ChannelErrorReceiver(async_channel::RecvError), - /// Channel sender error - ChannelErrorSender, - /// Broadcast channel receiver error - BroadcastChannelErrorReceiver(broadcast::error::RecvError), - /// Tokio channel receiver error - TokioChannelErrorRecv(tokio::sync::broadcast::error::RecvError), - /// Error converting SetDifficulty to Message - SetDifficultyToMessage(SetDifficulty), - /// Received an unexpected message type - UnexpectedMessage(u8), - /// Job not found during share validation - JobNotFound, - /// Invalid merkle root during share validation - InvalidMerkleRoot, - /// Shutdown signal received - Shutdown, - /// Pending channel not found for the given request ID - PendingChannelNotFound(u32), - /// Represents a generic channel send failure, described by a string. - General(String), - /// Error bubbling up from translator-core library - TranslatorCore(stratum_apps::stratum_core::stratum_translation::error::StratumTranslationError), -} - -impl std::error::Error for TproxyError {} - -impl fmt::Display for TproxyError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use TproxyError::*; - match self { - General(e) => write!(f, "{e}"), - BadCliArgs => write!(f, "Bad CLI arg input"), - BadSerdeJson(ref e) => write!(f, "Bad serde json: `{e:?}`"), - 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:?}`"), - PoisonLock => write!(f, "Poison Lock error"), - ChannelErrorReceiver(ref e) => write!(f, "Channel receive error: `{e:?}`"), - BroadcastChannelErrorReceiver(ref e) => { - write!(f, "Broadcast channel receive error: {e:?}") - } - ChannelErrorSender => write!(f, "Sender error"), - TokioChannelErrorRecv(ref e) => write!(f, "Channel receive error: `{e:?}`"), - SetDifficultyToMessage(ref e) => { - write!(f, "Error converting SetDifficulty to Message: `{e:?}`") - } - UnexpectedMessage(message_type) => { - write!( - f, - "Received a message type that was not expected: {message_type}" - ) - } - JobNotFound => write!(f, "Job not found during share validation"), - InvalidMerkleRoot => write!(f, "Invalid merkle root during share validation"), - Shutdown => write!(f, "Shutdown signal"), - PendingChannelNotFound(request_id) => { - write!(f, "No pending channel found for request_id: {}", request_id) - } - SV1Error => write!(f, "Sv1 error"), - TranslatorCore(ref e) => write!(f, "Translator core error: {e:?}"), - NetworkHelpersError(ref e) => write!(f, "Network helpers error: {e:?}"), - ParserError(ref e) => write!(f, "Roles logic parser error: {e:?}"), - } - } -} - -impl From for TproxyError { - fn from(e: binary_sv2::Error) -> Self { - TproxyError::BinarySv2(e) - } -} - -impl From for TproxyError { - fn from(e: noise_sv2::Error) -> Self { - TproxyError::CodecNoise(e) - } -} - -impl From for TproxyError { - fn from(e: framing_sv2::Error) -> Self { - TproxyError::FramingSv2(e) - } -} - -impl From for TproxyError { - fn from(e: std::io::Error) -> Self { - TproxyError::Io(e) - } -} - -impl From for TproxyError { - fn from(e: std::num::ParseIntError) -> Self { - TproxyError::ParseInt(e) - } -} - -impl From for TproxyError { - fn from(e: serde_json::Error) -> Self { - TproxyError::BadSerdeJson(e) - } -} - -impl From for TproxyError { - fn from(e: ConfigError) -> Self { - TproxyError::BadConfigDeserialize(e) - } -} - -impl From for TproxyError { - fn from(e: async_channel::RecvError) -> Self { - TproxyError::ChannelErrorReceiver(e) - } -} - -impl From for TproxyError { - fn from(e: tokio::sync::broadcast::error::RecvError) -> Self { - TproxyError::TokioChannelErrorRecv(e) - } -} - -//*** LOCK ERRORS *** -impl From> for TproxyError { - fn from(_e: PoisonError) -> Self { - TproxyError::PoisonLock - } -} - -impl From for TproxyError { - fn from(e: SetDifficulty) -> Self { - TproxyError::SetDifficultyToMessage(e) - } -} - -impl<'a> From> for TproxyError { - fn from(_: stratum_apps::stratum_core::sv1_api::error::Error<'a>) -> Self { - TproxyError::SV1Error - } -} - -impl From for TproxyError { - fn from(value: stratum_apps::network_helpers::Error) -> Self { - TproxyError::NetworkHelpersError(value) - } -} - -impl From - for TproxyError -{ - fn from( - e: stratum_apps::stratum_core::stratum_translation::error::StratumTranslationError, - ) -> Self { - TproxyError::TranslatorCore(e) - } -} - -impl HandlerErrorType for TproxyError { - fn parse_error(error: RolesParserError) -> Self { - TproxyError::ParserError(error) - } - - fn unexpected_message(message_type: u8) -> Self { - TproxyError::UnexpectedMessage(message_type) - } -} diff --git a/roles/translator/src/lib/mod.rs b/roles/translator/src/lib/mod.rs deleted file mode 100644 index 298d12a0c1..0000000000 --- a/roles/translator/src/lib/mod.rs +++ /dev/null @@ -1,255 +0,0 @@ -//! ## Translator Sv2 -//! -//! Provides the core logic and main struct (`TranslatorSv2`) for running a -//! Stratum V1 to Stratum V2 translation proxy. -//! -//! This module orchestrates the interaction between downstream SV1 miners and upstream SV2 -//! applications (proxies or pool servers). -//! -//! The central component is the `TranslatorSv2` struct, which encapsulates the state and -//! provides the `start` method as the main entry point for running the translator service. -//! It relies on several sub-modules (`config`, `downstream_sv1`, `upstream_sv2`, `proxy`, `status`, -//! etc.) for specialized functionalities. -#![allow(clippy::module_inception)] -use async_channel::unbounded; -use std::{net::SocketAddr, sync::Arc}; -use tokio::sync::mpsc; -use tracing::{debug, error, info, warn}; - -pub use stratum_apps::stratum_core::sv1_api::server_to_client; - -use config::TranslatorConfig; - -use crate::{ - status::{State, Status}, - sv1::sv1_server::sv1_server::Sv1Server, - sv2::{channel_manager::ChannelMode, ChannelManager, Upstream}, - task_manager::TaskManager, - utils::ShutdownMessage, -}; - -pub mod config; -pub mod error; -pub mod status; -pub mod sv1; -pub mod sv2; -mod task_manager; -pub mod utils; - -/// The main struct that manages the SV1/SV2 translator. -#[derive(Clone, Debug)] -pub struct TranslatorSv2 { - config: TranslatorConfig, -} - -impl TranslatorSv2 { - /// Creates a new `TranslatorSv2`. - /// - /// Initializes the translator with the given configuration and sets up - /// the reconnect wait time. - pub fn new(config: TranslatorConfig) -> Self { - Self { config } - } - - /// Starts the translator. - /// - /// This method starts the main event loop, which handles connections, - /// protocol translation, job management, and status reporting. - pub async fn start(self) { - info!("Starting Translator Proxy..."); - - let (notify_shutdown, _) = tokio::sync::broadcast::channel::(1); - 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_sv1_server_sender, channel_manager_to_sv1_server_receiver) = - unbounded(); - let (sv1_server_to_channel_manager_sender, sv1_server_to_channel_manager_receiver) = - unbounded(); - - debug!("Channels initialized."); - - let upstream_addresses = self - .config - .upstreams - .iter() - .map(|upstream| { - let upstream_addr = - SocketAddr::new(upstream.address.parse().unwrap(), upstream.port); - (upstream_addr, upstream.authority_pubkey) - }) - .collect::>(); - - let upstream = match Upstream::new( - &upstream_addresses, - upstream_to_channel_manager_sender.clone(), - channel_manager_to_upstream_receiver.clone(), - notify_shutdown.clone(), - shutdown_complete_tx.clone(), - ) - .await - { - Ok(upstream) => { - debug!("Upstream initialized successfully."); - upstream - } - Err(e) => { - error!("Failed to initialize upstream connection: {e:?}"); - return; - } - }; - - let channel_manager = Arc::new(ChannelManager::new( - channel_manager_to_upstream_sender, - upstream_to_channel_manager_receiver, - channel_manager_to_sv1_server_sender.clone(), - sv1_server_to_channel_manager_receiver, - if self.config.aggregate_channels { - ChannelMode::Aggregated - } else { - ChannelMode::NonAggregated - }, - )); - - let downstream_addr = SocketAddr::new( - self.config.downstream_address.parse().unwrap(), - self.config.downstream_port, - ); - - let sv1_server = Arc::new(Sv1Server::new( - downstream_addr, - channel_manager_to_sv1_server_receiver, - sv1_server_to_channel_manager_sender, - self.config.clone(), - )); - - ChannelManager::run_channel_manager_tasks( - channel_manager.clone(), - notify_shutdown.clone(), - shutdown_complete_tx.clone(), - status_sender.clone(), - task_manager.clone(), - ) - .await; - - if let Err(e) = upstream - .start( - notify_shutdown.clone(), - shutdown_complete_tx.clone(), - status_sender.clone(), - task_manager.clone(), - ) - .await - { - error!("Failed to start upstream listener: {e:?}"); - return; - } - - let notify_shutdown_clone = notify_shutdown.clone(); - let shutdown_complete_tx_clone = shutdown_complete_tx.clone(); - let status_sender_clone = status_sender.clone(); - let task_manager_clone = task_manager.clone(); - task_manager.spawn(async move { - 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 โ€” notifying SV1 server."); - let _ = notify_shutdown_clone.send(ShutdownMessage::DownstreamShutdown(downstream_id)); - } - State::Sv1ServerShutdown(_) => { - warn!("SV1 Server 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::UpstreamShutdown(msg) => { - warn!("Upstream connection dropped: {msg:?} โ€” attempting reconnection..."); - - match Upstream::new( - &upstream_addresses, - upstream_to_channel_manager_sender.clone(), - channel_manager_to_upstream_receiver.clone(), - notify_shutdown_clone.clone(), - shutdown_complete_tx_clone.clone(), - ).await { - Ok(upstream) => { - if let Err(e) = upstream - .start( - notify_shutdown_clone.clone(), - shutdown_complete_tx_clone.clone(), - status_sender_clone.clone(), - task_manager_clone.clone() - ) - .await - { - error!("Restarted upstream failed to start: {e:?}"); - let _ = notify_shutdown_clone.send(ShutdownMessage::ShutdownAll); - break; - } else { - info!("Upstream restarted successfully."); - // Reset channel manager state and shutdown downstreams in one message - let _ = notify_shutdown_clone.send(ShutdownMessage::UpstreamReconnectedResetAndShutdownDownstreams); - } - } - Err(e) => { - error!("Failed to reinitialize upstream after disconnect: {e:?}"); - let _ = notify_shutdown_clone.send(ShutdownMessage::ShutdownAll); - break; - } - } - } - } - } - } - } - } - }); - - if let Err(e) = Sv1Server::start( - sv1_server, - notify_shutdown.clone(), - shutdown_complete_tx.clone(), - status_sender.clone(), - task_manager.clone(), - ) - .await - { - error!("SV1 server startup failed: {e:?}"); - notify_shutdown.send(ShutdownMessage::ShutdownAll).unwrap(); - } - - drop(shutdown_complete_tx); - info!("Waiting for shutdown completion signals from subsystems..."); - let shutdown_timeout = tokio::time::Duration::from_secs(5); - tokio::select! { - _ = shutdown_complete_rx.recv() => { - info!("All subsystems reported shutdown complete."); - } - _ = tokio::time::sleep(shutdown_timeout) => { - warn!("Graceful shutdown timed out after {shutdown_timeout:?} โ€” forcing shutdown."); - task_manager.abort_all().await; - } - } - info!("Joining remaining tasks..."); - task_manager.join_all().await; - info!("TranslatorSv2 shutdown complete."); - } -} diff --git a/roles/translator/src/lib/status.rs b/roles/translator/src/lib/status.rs deleted file mode 100644 index 896cff9a93..0000000000 --- a/roles/translator/src/lib/status.rs +++ /dev/null @@ -1,117 +0,0 @@ -//! ## Status Reporting System -//! -//! This module provides a centralized way for components of the Translator to report -//! health updates, shutdown reasons, or fatal errors to the main runtime loop. -//! -//! Each task wraps its report in a [`Status`] and sends it over an async channel, -//! tagged with a [`Sender`] variant that identifies the source subsystem. - -use tracing::{debug, error, warn}; - -use crate::error::TproxyError; - -/// Identifies the component that originated a [`Status`] update. -/// -/// Each variant contains a channel to the main coordinator, and optionally a component ID -/// (e.g. a downstream connection ID). -#[derive(Debug, Clone)] -pub enum StatusSender { - /// A specific downstream connection. - Downstream { - downstream_id: u32, - tx: async_channel::Sender, - }, - /// The SV1 server listener. - Sv1Server(async_channel::Sender), - /// The SV2 <-> SV1 bridge manager. - ChannelManager(async_channel::Sender), - /// The upstream SV2 connection handler. - Upstream(async_channel::Sender), -} - -impl StatusSender { - /// Sends a [`Status`] update. - 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::Sv1Server(tx) => { - debug!("Sending status from Sv1Server: {:?}", 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 - } - } - } -} - -/// The type of event or error being reported by a component. -#[derive(Debug)] -pub enum State { - /// Downstream task exited or encountered an unrecoverable error. - DownstreamShutdown { - downstream_id: u32, - reason: TproxyError, - }, - /// SV1 server listener exited unexpectedly. - Sv1ServerShutdown(TproxyError), - /// Channel manager shut down (SV2 bridge manager). - ChannelManagerShutdown(TproxyError), - /// Upstream SV2 connection closed or failed. - UpstreamShutdown(TproxyError), -} - -/// A message reporting the current [`State`] of a component. -#[derive(Debug)] -pub struct Status { - pub state: State, -} - -/// Constructs and sends a [`Status`] update based on the [`Sender`] and error context. -async fn send_status(sender: &StatusSender, error: TproxyError) { - 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::Sv1Server(_) => { - warn!("Sv1Server shutting down due to error: {error:?}"); - State::Sv1ServerShutdown(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::UpstreamShutdown(error) - } - }; - - if let Err(e) = sender.send(Status { state }).await { - error!("Failed to send status update from {sender:?}: {e:?}"); - } -} - -/// Centralized error dispatcher for the Translator. -/// -/// Used by the `handle_result!` macro across the codebase. -/// Decides whether the task should `Continue` or `Break` based on the error type and source. -pub async fn handle_error(sender: &StatusSender, e: TproxyError) { - error!("Error in {:?}: {:?}", sender, e); - send_status(sender, e).await; -} diff --git a/roles/translator/src/lib/sv1/downstream/channel.rs b/roles/translator/src/lib/sv1/downstream/channel.rs deleted file mode 100644 index cc53d49024..0000000000 --- a/roles/translator/src/lib/sv1/downstream/channel.rs +++ /dev/null @@ -1,35 +0,0 @@ -use super::DownstreamMessages; -use async_channel::{Receiver, Sender}; -use stratum_apps::stratum_core::sv1_api::json_rpc; -use tokio::sync::broadcast; -use tracing::debug; - -#[derive(Debug)] -pub struct DownstreamChannelState { - pub downstream_sv1_sender: Sender, - pub downstream_sv1_receiver: Receiver, - pub sv1_server_sender: Sender, - pub sv1_server_receiver: broadcast::Receiver<(u32, Option, json_rpc::Message)>, /* channel_id, optional downstream_id, message */ -} - -impl DownstreamChannelState { - pub fn new( - downstream_sv1_sender: Sender, - downstream_sv1_receiver: Receiver, - sv1_server_sender: Sender, - sv1_server_receiver: broadcast::Receiver<(u32, Option, json_rpc::Message)>, - ) -> Self { - Self { - downstream_sv1_receiver, - downstream_sv1_sender, - sv1_server_receiver, - sv1_server_sender, - } - } - - pub fn drop(&self) { - debug!("Dropping downstream channel state"); - self.downstream_sv1_receiver.close(); - self.downstream_sv1_sender.close(); - } -} diff --git a/roles/translator/src/lib/sv1/downstream/data.rs b/roles/translator/src/lib/sv1/downstream/data.rs deleted file mode 100644 index 4dae61e83a..0000000000 --- a/roles/translator/src/lib/sv1/downstream/data.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::{ - cell::RefCell, - sync::{atomic::AtomicBool, Arc}, -}; -use stratum_apps::{ - custom_mutex::Mutex, - stratum_core::{ - bitcoin::Target, - sv1_api::{json_rpc, utils::HexU32Be}, - }, -}; -use tracing::debug; - -use super::SubmitShareWithChannelId; -use crate::sv1::sv1_server::data::Sv1ServerData; - -#[derive(Debug)] -pub struct DownstreamData { - pub channel_id: Option, - pub downstream_id: u32, - pub extranonce1: Vec, - pub extranonce2_len: usize, - pub version_rolling_mask: Option, - pub version_rolling_min_bit: Option, - pub last_job_version_field: Option, - pub authorized_worker_name: String, - pub user_identity: String, - pub target: Target, - pub hashrate: Option, - pub cached_set_difficulty: Option, - pub cached_notify: Option, - pub pending_target: Option, - pub pending_hashrate: Option, - // Flag to track if SV1 handshake is complete (subscribe + authorize) - pub sv1_handshake_complete: AtomicBool, - // Queue of Sv1 handshake messages received while waiting for SV2 channel to open - pub queued_sv1_handshake_messages: Vec, - // Flag to indicate we're processing queued Sv1 handshake message responses - pub processing_queued_sv1_handshake_responses: AtomicBool, - // Stores pending shares to be sent to the sv1_server - pub pending_share: RefCell>, - // Reference to shared sv1_server data for accessing valid_jobs during downstream sv1 - // validation - pub sv1_server_data: Arc>, - // Tracks the upstream target for this downstream, used for vardiff target comparison - pub upstream_target: Option, -} - -impl DownstreamData { - pub fn new( - downstream_id: u32, - target: Target, - hashrate: Option, - sv1_server_data: Arc>, - ) -> Self { - DownstreamData { - channel_id: None, - downstream_id, - extranonce1: vec![0; 8], - extranonce2_len: 4, - version_rolling_mask: None, - version_rolling_min_bit: None, - last_job_version_field: None, - authorized_worker_name: String::new(), - user_identity: String::new(), - target, - hashrate, - cached_set_difficulty: None, - cached_notify: None, - pending_target: None, - pending_hashrate: None, - sv1_handshake_complete: AtomicBool::new(false), - queued_sv1_handshake_messages: Vec::new(), - processing_queued_sv1_handshake_responses: AtomicBool::new(false), - pending_share: RefCell::new(None), - sv1_server_data, - upstream_target: None, - } - } - - pub fn set_pending_target(&mut self, new_target: Target) { - self.pending_target = Some(new_target); - debug!("Downstream {}: Set pending target", self.downstream_id); - } - - pub fn set_pending_hashrate(&mut self, new_hashrate: Option) { - self.pending_hashrate = new_hashrate; - debug!("Downstream {}: Set pending hashrate", self.downstream_id); - } - - pub fn set_upstream_target(&mut self, upstream_target: Target) { - self.upstream_target = Some(upstream_target); - debug!( - "Downstream {}: Set upstream target to {:?}", - self.downstream_id, upstream_target - ); - } -} diff --git a/roles/translator/src/lib/sv1/downstream/downstream.rs b/roles/translator/src/lib/sv1/downstream/downstream.rs deleted file mode 100644 index 4e8923e743..0000000000 --- a/roles/translator/src/lib/sv1/downstream/downstream.rs +++ /dev/null @@ -1,531 +0,0 @@ -use super::DownstreamMessages; -use crate::{ - error::TproxyError, - status::{handle_error, StatusSender}, - sv1::{ - downstream::{channel::DownstreamChannelState, data::DownstreamData}, - sv1_server::data::Sv1ServerData, - }, - task_manager::TaskManager, - utils::ShutdownMessage, -}; -use async_channel::{Receiver, Sender}; -use std::sync::Arc; -use stratum_apps::{ - custom_mutex::Mutex, - stratum_core::{ - bitcoin::Target, - sv1_api::{ - json_rpc::{self, Message}, - server_to_client, IsServer, - }, - }, -}; -use tokio::sync::{broadcast, mpsc}; -use tracing::{debug, error, info, warn}; - -/// Represents a downstream SV1 miner connection. -/// -/// This struct manages the state and communication for a single SV1 miner connected -/// to the translator. It handles: -/// - SV1 protocol message processing (subscribe, authorize, submit) -/// - Bidirectional message routing between miner and SV1 server -/// - Mining job tracking and share validation -/// - Difficulty adjustment coordination -/// - Connection lifecycle management -/// -/// Each downstream connection runs in its own async task that processes messages -/// from both the miner and the server, ensuring proper message ordering and -/// handling connection-specific state. -#[derive(Debug)] -pub struct Downstream { - pub downstream_data: Arc>, - downstream_channel_state: DownstreamChannelState, -} - -impl Downstream { - /// Creates a new downstream connection instance. - #[allow(clippy::too_many_arguments)] - pub fn new( - downstream_id: u32, - downstream_sv1_sender: Sender, - downstream_sv1_receiver: Receiver, - sv1_server_sender: Sender, - sv1_server_receiver: broadcast::Receiver<(u32, Option, json_rpc::Message)>, - target: Target, - hashrate: Option, - sv1_server_data: Arc>, - ) -> Self { - let downstream_data = Arc::new(Mutex::new(DownstreamData::new( - downstream_id, - target, - hashrate, - sv1_server_data, - ))); - let downstream_channel_state = DownstreamChannelState::new( - downstream_sv1_sender, - downstream_sv1_receiver, - sv1_server_sender, - sv1_server_receiver, - ); - Self { - downstream_data, - downstream_channel_state, - } - } - - /// Spawns and runs the main task loop for this downstream connection. - /// - /// This method creates an async task that handles all communication for this - /// downstream connection. The task runs a select loop that processes: - /// - Shutdown signals (global, targeted, or all-downstream) - /// - Messages from the miner (subscribe, authorize, submit) - /// - Messages from the SV1 server (notify, set_difficulty, etc.) - /// - /// The task will continue running until a shutdown signal is received or - /// an unrecoverable error occurs. It ensures graceful cleanup of resources - /// and proper error reporting. - pub fn run_downstream_tasks( - self: Arc, - notify_shutdown: broadcast::Sender, - shutdown_complete_tx: mpsc::Sender<()>, - status_sender: StatusSender, - task_manager: Arc, - ) { - let mut sv1_server_receiver = self - .downstream_channel_state - .sv1_server_receiver - .resubscribe(); - let mut shutdown_rx = notify_shutdown.subscribe(); - let downstream_id = self.downstream_data.super_safe_lock(|d| d.downstream_id); - task_manager.spawn(async move { - loop { - tokio::select! { - msg = shutdown_rx.recv() => { - match msg { - Ok(ShutdownMessage::ShutdownAll) => { - info!("Downstream {downstream_id}: received global shutdown"); - break; - } - Ok(ShutdownMessage::DownstreamShutdown(id)) if id == downstream_id => { - info!("Downstream {downstream_id}: received targeted shutdown"); - break; - } - Ok(ShutdownMessage::DownstreamShutdownAll) => { - info!("All downstream shutdown message received"); - break; - } - Ok(ShutdownMessage::UpstreamReconnectedResetAndShutdownDownstreams) => { - info!("All downstream shutdown message received (upstream reconnected)"); - break; - } - Ok(_) => { - // shutdown for other downstream - } - Err(e) => { - warn!("Downstream {downstream_id}: shutdown channel closed: {e}"); - break; - } - } - } - - // Handle downstream -> server message - res = Self::handle_downstream_message(self.clone()) => { - if let Err(e) = res { - error!("Downstream {downstream_id}: error in downstream message handler: {e:?}"); - handle_error(&status_sender, e).await; - break; - } - } - - // Handle server -> downstream message - res = Self::handle_sv1_server_message(self.clone(),&mut sv1_server_receiver) => { - if let Err(e) = res { - error!("Downstream {downstream_id}: error in server message handler: {e:?}"); - handle_error(&status_sender, e).await; - break; - } - } - - else => { - warn!("Downstream {downstream_id}: all channels closed; exiting task"); - break; - } - } - } - - warn!("Downstream {downstream_id}: unified task shutting down"); - self.downstream_channel_state.drop(); - drop(shutdown_complete_tx); - }); - } - - /// Handles messages received from the SV1 server. - /// - /// This method processes messages broadcast from the SV1 server to downstream - /// connections. Since `mining.notify` messages are guaranteed to never arrive - /// before their corresponding `mining.set_difficulty` message, the logic is - /// simplified to handle only handshake completion timing. - /// - /// Key behaviors: - /// - Filters messages by channel ID and downstream ID - /// - For `mining.set_difficulty`: Always caches the message (never sent immediately) - /// - For `mining.notify`: Sends any pending set_difficulty first, then forwards the notify - /// - For other messages: Forwards directly to the miner - /// - Caches both `mining.set_difficulty` and `mining.notify` messages if handshake is not yet - /// complete - /// - On handshake completion: sends cached messages in correct order (set_difficulty first, - /// then notify) - pub async fn handle_sv1_server_message( - self: Arc, - sv1_server_receiver: &mut broadcast::Receiver<(u32, Option, json_rpc::Message)>, - ) -> Result<(), TproxyError> { - match sv1_server_receiver.recv().await { - Ok((channel_id, downstream_id, message)) => { - let (my_channel_id, my_downstream_id, handshake_complete) = - self.downstream_data.super_safe_lock(|d| { - ( - d.channel_id, - d.downstream_id, - d.sv1_handshake_complete - .load(std::sync::atomic::Ordering::SeqCst), - ) - }); - let id_matches = (my_channel_id == Some(channel_id) || channel_id == 0) - && (downstream_id.is_none() || downstream_id == Some(my_downstream_id)); - if !id_matches { - return Ok(()); // Message not intended for this downstream - } - - // Check if this is a queued message response - let is_queued_sv1_handshake_response = self.downstream_data.super_safe_lock(|d| { - d.processing_queued_sv1_handshake_responses - .load(std::sync::atomic::Ordering::SeqCst) - }); - - // Handle messages based on message type and handshake state - if let Message::Notification(notification) = &message { - // For notifications (mining.set_difficulty, mining.notify), only send if - // handshake is complete - if handshake_complete { - match notification.method.as_str() { - "mining.set_difficulty" => { - // Cache the Sv1 set_difficulty message to be sent before the next - // notify - debug!("Down: Caching mining.set_difficulty to send before next mining.notify"); - self.downstream_data.super_safe_lock(|d| { - d.cached_set_difficulty = Some(message); - }); - return Ok(()); - } - "mining.notify" => { - let (pending_set_difficulty, notify_opt) = - self.downstream_data.super_safe_lock(|d| { - let cached_set_difficulty = d.cached_set_difficulty.take(); - - // Prepare the notify message and update state - let notify_result = server_to_client::Notify::try_from( - notification.clone(), - ); - if let Ok(mut notify) = notify_result { - if cached_set_difficulty.is_some() { - notify.clean_jobs = true; - } - d.last_job_version_field = Some(notify.version.0); - - // Update target and hashrate if we're sending - // set_difficulty - if cached_set_difficulty.is_some() { - if let Some(new_target) = d.pending_target.take() { - d.target = new_target; - } - if let Some(new_hashrate) = - d.pending_hashrate.take() - { - d.hashrate = Some(new_hashrate); - } - } - - (cached_set_difficulty, Some(notify)) - } else { - (cached_set_difficulty, None) - } - }); - - if let Some(set_difficulty_msg) = &pending_set_difficulty { - debug!("Down: Sending pending mining.set_difficulty before mining.notify"); - self.downstream_channel_state - .downstream_sv1_sender - .send(set_difficulty_msg.clone()) - .await - .map_err(|e| { - error!( - "Down: Failed to send mining.set_difficulty to downstream: {:?}", - e - ); - TproxyError::ChannelErrorSender - })?; - } - - if let Some(notify) = notify_opt { - debug!("Down: Sending mining.notify"); - self.downstream_channel_state - .downstream_sv1_sender - .send(notify.into()) - .await - .map_err(|e| { - error!("Down: Failed to send mining.notify to downstream: {:?}", e); - TproxyError::ChannelErrorSender - })?; - } - return Ok(()); - } - _ => { - // Other notifications - forward if handshake complete - self.downstream_channel_state - .downstream_sv1_sender - .send(message.clone()) - .await - .map_err(|e| { - error!( - "Down: Failed to send notification to downstream: {:?}", - e - ); - TproxyError::ChannelErrorSender - })?; - } - } - } else { - // Handshake not complete - cache mining notifications but skip others - match notification.method.as_str() { - "mining.set_difficulty" => { - debug!("Down: SV1 handshake not complete, caching mining.set_difficulty"); - self.downstream_data.super_safe_lock(|d| { - d.cached_set_difficulty = Some(message); - }); - } - "mining.notify" => { - debug!("Down: SV1 handshake not complete, caching mining.notify"); - self.downstream_data.super_safe_lock(|d| { - d.cached_notify = Some(message.clone()); - let notify = - server_to_client::Notify::try_from(notification.clone()) - .expect("this must be a mining.notify"); - d.last_job_version_field = Some(notify.version.0); - }); - } - _ => { - debug!( - "Down: SV1 handshake not complete, skipping other notification" - ); - } - } - } - } else if is_queued_sv1_handshake_response { - // For non-notification messages, send if processing queued handshake responses - self.downstream_channel_state - .downstream_sv1_sender - .send(message.clone()) - .await - .map_err(|e| { - error!("Down: Failed to send queued message to downstream: {:?}", e); - TproxyError::ChannelErrorSender - })?; - } else { - // Neither handshake complete nor queued response - skip non-notification - // messages - debug!("Down: SV1 handshake not complete, skipping non-notification message"); - } - } - Err(e) => { - let downstream_id = self.downstream_data.super_safe_lock(|d| d.downstream_id); - error!( - "Sv1 message handler error for downstream {}: {:?}", - downstream_id, e - ); - return Err(TproxyError::BroadcastChannelErrorReceiver(e)); - } - } - - Ok(()) - } - - /// Handles messages received from the downstream SV1 miner. - /// - /// This method processes SV1 protocol messages sent by the miner, including: - /// - `mining.subscribe` - Subscription requests - /// - `mining.authorize` - Authorization requests - /// - `mining.submit` - Share submissions - /// - Other SV1 protocol messages - /// - /// The method delegates message processing to the downstream data handler, - /// which implements the SV1 protocol logic and generates appropriate responses. - /// Responses are sent back to the miner, while share submissions are forwarded - /// to the SV1 server for upstream processing. - pub async fn handle_downstream_message(self: Arc) -> Result<(), TproxyError> { - let message = match self - .downstream_channel_state - .downstream_sv1_receiver - .recv() - .await - { - Ok(msg) => msg, - Err(e) => { - error!("Error receiving downstream message: {:?}", e); - return Err(TproxyError::ChannelErrorReceiver(e)); - } - }; - - // Check if channel is established - let channel_established = self - .downstream_data - .super_safe_lock(|d| d.channel_id.is_some()); - - if !channel_established { - // Check if this is the first message (queue is empty) and send OpenChannel request - let is_first_message = self - .downstream_data - .super_safe_lock(|d| d.queued_sv1_handshake_messages.is_empty()); - - if is_first_message { - let downstream_id = self.downstream_data.super_safe_lock(|d| d.downstream_id); - self.downstream_channel_state - .sv1_server_sender - .send(DownstreamMessages::OpenChannel(downstream_id)) - .await - .map_err(|e| { - error!("Down: Failed to send OpenChannel request: {:?}", e); - TproxyError::ChannelErrorSender - })?; - debug!( - "Down: Sent OpenChannel request for downstream {}", - downstream_id - ); - } - - // Queue all messages until channel is established - debug!("Down: Queuing Sv1 message until channel is established"); - self.downstream_data.safe_lock(|d| { - d.queued_sv1_handshake_messages.push(message.clone()); - })?; - return Ok(()); - } - - // Channel is established, process message normally - let response = self - .downstream_data - .super_safe_lock(|data| data.handle_message(message.clone())); - - match response { - Ok(Some(response_msg)) => { - debug!( - "Down: Sending Sv1 message to downstream: {:?}", - response_msg - ); - self.downstream_channel_state - .downstream_sv1_sender - .send(response_msg.into()) - .await - .map_err(|e| { - error!("Down: Failed to send message to downstream: {:?}", e); - TproxyError::ChannelErrorSender - })?; - - // Check if this was an authorize message and handle sv1 handshake completion - if let stratum_apps::stratum_core::sv1_api::json_rpc::Message::StandardRequest( - request, - ) = &message - { - if request.method == "mining.authorize" { - info!("Down: Handling mining.authorize after handshake completion"); - if let Err(e) = self.handle_sv1_handshake_completion().await { - error!("Down: Failed to handle handshake completion: {:?}", e); - return Err(e); - } - } - } - } - Ok(None) => { - // Message was handled but no response needed - } - Err(e) => { - error!("Down: Error handling downstream message: {:?}", e); - return Err(e.into()); - } - } - - // Check if there's a pending share to send to the Sv1Server - let pending_share = self - .downstream_data - .super_safe_lock(|d| d.pending_share.take()); - if let Some(share) = pending_share { - self.downstream_channel_state - .sv1_server_sender - .send(DownstreamMessages::SubmitShares(share)) - .await - .map_err(|e| { - error!("Down: Failed to send share to SV1 server: {:?}", e); - TproxyError::ChannelErrorSender - })?; - } - - Ok(()) - } - - /// Handles SV1 handshake completion after mining.authorize. - /// - /// This method is called when the downstream completes the SV1 handshake - /// (subscribe + authorize). It sends any cached messages in the correct order: - /// set_difficulty first, then notify. - async fn handle_sv1_handshake_completion(self: &Arc) -> Result<(), TproxyError> { - let (cached_set_difficulty, cached_notify) = self.downstream_data.super_safe_lock(|d| { - d.sv1_handshake_complete - .store(true, std::sync::atomic::Ordering::SeqCst); - (d.cached_set_difficulty.take(), d.cached_notify.take()) - }); - debug!("Down: SV1 handshake completed for downstream"); - - // Send cached messages in correct order: set_difficulty first, then notify - if let Some(set_difficulty_msg) = cached_set_difficulty { - debug!("Down: Sending cached mining.set_difficulty after handshake completion"); - self.downstream_channel_state - .downstream_sv1_sender - .send(set_difficulty_msg) - .await - .map_err(|e| { - error!( - "Down: Failed to send cached mining.set_difficulty to downstream: {:?}", - e - ); - TproxyError::ChannelErrorSender - })?; - - // Update target and hashrate after sending set_difficulty - self.downstream_data.super_safe_lock(|d| { - if let Some(new_target) = d.pending_target.take() { - d.target = new_target; - } - if let Some(new_hashrate) = d.pending_hashrate.take() { - d.hashrate = Some(new_hashrate); - } - }); - } - - if let Some(notify_msg) = cached_notify { - debug!("Down: Sending cached mining.notify after handshake completion"); - self.downstream_channel_state - .downstream_sv1_sender - .send(notify_msg) - .await - .map_err(|e| { - error!( - "Down: Failed to send cached mining.notify to downstream: {:?}", - e - ); - TproxyError::ChannelErrorSender - })?; - } - - Ok(()) - } -} diff --git a/roles/translator/src/lib/sv1/downstream/message_handler.rs b/roles/translator/src/lib/sv1/downstream/message_handler.rs deleted file mode 100644 index 6ab75c619e..0000000000 --- a/roles/translator/src/lib/sv1/downstream/message_handler.rs +++ /dev/null @@ -1,165 +0,0 @@ -use stratum_apps::stratum_core::sv1_api::{ - client_to_server, json_rpc, server_to_client, - utils::{Extranonce, HexU32Be}, - IsServer, -}; -use tracing::{debug, error, info, warn}; - -use crate::{ - sv1::downstream::{data::DownstreamData, SubmitShareWithChannelId}, - utils::validate_sv1_share, -}; - -// Implements `IsServer` for `Downstream` to handle the Sv1 messages. -impl IsServer<'static> for DownstreamData { - fn handle_configure( - &mut self, - request: &client_to_server::Configure, - ) -> (Option, Option) { - info!("Received mining.configure from Sv1 downstream"); - debug!("Down: Handling mining.configure: {:?}", request); - self.version_rolling_mask = request - .version_rolling_mask() - .map(|mask| HexU32Be(mask & 0x1FFFE000)); - self.version_rolling_min_bit = request.version_rolling_min_bit_count(); - - debug!( - "Negotiated version_rolling_mask is {:?}", - self.version_rolling_mask - ); - ( - Some(server_to_client::VersionRollingParams::new( - self.version_rolling_mask.clone().unwrap_or(HexU32Be(0)), - self.version_rolling_min_bit.clone().unwrap_or(HexU32Be(0)), - ).expect("Version mask invalid, automatic version mask selection not supported, please change it in crate::downstream::mod.rs")), - Some(false), - ) - } - - fn handle_subscribe(&self, request: &client_to_server::Subscribe) -> Vec<(String, String)> { - info!("Received mining.subscribe from Sv1 downstream"); - debug!("Down: Handling mining.subscribe: {:?}", request); - - let set_difficulty_sub = ( - "mining.set_difficulty".to_string(), - self.downstream_id.to_string(), - ); - - let notify_sub = ( - "mining.notify".to_string(), - "ae6812eb4cd7735a302a8a9dd95cf71f".to_string(), - ); - - vec![set_difficulty_sub, notify_sub] - } - - fn handle_authorize(&self, request: &client_to_server::Authorize) -> bool { - info!("Received mining.authorize from Sv1 downstream"); - debug!("Down: Handling mining.authorize: {:?}", request); - true - } - - fn handle_submit(&self, request: &client_to_server::Submit<'static>) -> bool { - if let Some(channel_id) = self.channel_id { - info!( - "Received mining.submit from SV1 downstream for channel id: {}", - channel_id - ); - let is_valid_share = validate_sv1_share( - request, - self.target, - self.extranonce1.clone(), - self.version_rolling_mask.clone(), - self.sv1_server_data.clone(), - channel_id, - ) - .unwrap_or(false); - if !is_valid_share { - error!("Invalid share for channel id: {}", channel_id); - return false; - } - let to_send: SubmitShareWithChannelId = SubmitShareWithChannelId { - channel_id, - downstream_id: self.downstream_id, - share: request.clone(), - extranonce: self.extranonce1.clone(), - extranonce2_len: self.extranonce2_len, - version_rolling_mask: self.version_rolling_mask.clone(), - job_version: self.last_job_version_field, - }; - // Store the share to be sent to the Sv1Server - self.pending_share.replace(Some(to_send)); - true - } else { - error!("Cannot submit share: channel_id is None (waiting for OpenExtendedMiningChannelSuccess)"); - false - } - } - - /// Indicates to the server that the client supports the mining.set_extranonce method. - fn handle_extranonce_subscribe(&self) {} - - /// Checks if a Downstream role is authorized. - fn is_authorized(&self, name: &str) -> bool { - self.authorized_worker_name == *name - } - - /// Authorizes a Downstream role. - fn authorize(&mut self, name: &str) { - let name: String = name.into(); - if !self.is_authorized(&name) { - self.authorized_worker_name = name.to_string(); - } - } - - /// Sets the `extranonce1` field sent in the SV1 `mining.notify` message to the value specified - /// by the SV2 `OpenExtendedMiningChannelSuccess` message sent from the Upstream role. - fn set_extranonce1( - &mut self, - _extranonce1: Option>, - ) -> Extranonce<'static> { - self.extranonce1.clone().try_into().unwrap() - } - - /// Returns the `Downstream`'s `extranonce1` value. - fn extranonce1(&self) -> Extranonce<'static> { - self.extranonce1.clone().try_into().unwrap() - } - - /// Sets the `extranonce2_size` field sent in the SV1 `mining.notify` message to the value - /// specified by the SV2 `OpenExtendedMiningChannelSuccess` message sent from the Upstream role. - fn set_extranonce2_size(&mut self, _extra_nonce2_size: Option) -> usize { - self.extranonce2_len - } - - /// Returns the `Downstream`'s `extranonce2_size` value. - fn extranonce2_size(&self) -> usize { - self.extranonce2_len - } - - /// Returns the version rolling mask. - fn version_rolling_mask(&self) -> Option { - self.version_rolling_mask.clone() - } - - /// Sets the version rolling mask. - fn set_version_rolling_mask(&mut self, mask: Option) { - self.version_rolling_mask = mask; - } - - /// Sets the minimum version rolling bit. - fn set_version_rolling_min_bit(&mut self, mask: Option) { - self.version_rolling_min_bit = mask - } - - fn notify( - &'_ mut self, - ) -> Result> { - warn!("notify() called on DownstreamData - this method is not implemented for the translator proxy"); - Err( - stratum_apps::stratum_core::sv1_api::error::Error::UnexpectedMessage( - "notify".to_string(), - ), - ) - } -} diff --git a/roles/translator/src/lib/sv1/downstream/mod.rs b/roles/translator/src/lib/sv1/downstream/mod.rs deleted file mode 100644 index 9cd602e24c..0000000000 --- a/roles/translator/src/lib/sv1/downstream/mod.rs +++ /dev/null @@ -1,43 +0,0 @@ -pub(super) mod channel; -pub(super) mod data; -pub mod downstream; -mod message_handler; - -use stratum_apps::stratum_core::sv1_api::{client_to_server::Submit, utils::HexU32Be}; - -/// Messages sent from downstream handling logic to the SV1 server. -/// -/// This enum defines the types of messages that downstream connections can send -/// to the central SV1 server for processing and forwarding to upstream. -#[derive(Debug)] -pub enum DownstreamMessages { - /// Represents a submitted share from a downstream miner, - /// wrapped with the relevant channel ID. - SubmitShares(SubmitShareWithChannelId), - /// Request to open an extended mining channel for a downstream that just sent its first - /// message. - OpenChannel(u32), // downstream_id -} - -/// A wrapper around a `mining.submit` message with additional channel information. -/// -/// This struct contains all the necessary information to process a share submission -/// from an SV1 miner, including the share data itself and metadata needed for -/// proper routing and validation. -#[derive(Debug, Clone)] -pub struct SubmitShareWithChannelId { - /// The SV2 channel ID this share belongs to - pub channel_id: u32, - /// The downstream connection ID that submitted this share - pub downstream_id: u32, - /// The actual SV1 share submission data - pub share: Submit<'static>, - /// The complete extranonce used for this share - pub extranonce: Vec, - /// The length of the extranonce2 field - pub extranonce2_len: usize, - /// Optional version rolling mask for the share - pub version_rolling_mask: Option, - /// The version field from the job, used for validation - pub job_version: Option, -} diff --git a/roles/translator/src/lib/sv1/mod.rs b/roles/translator/src/lib/sv1/mod.rs deleted file mode 100644 index 0b62d78494..0000000000 --- a/roles/translator/src/lib/sv1/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! ## Downstream SV1 Module -//! -//! This module defines the structures, messages, and utility functions -//! used for handling the downstream connection with SV1 mining clients. -//! -//! It includes definitions for messages exchanged with a Bridge component, -//! structures for submitting shares and updating targets, and constants -//! and functions for managing client interactions. -//! -//! The module is organized into the following sub-modules: -//! - [`diff_management`]: (Declared here, likely contains downstream difficulty logic) -//! - [`downstream`]: Defines the core [`Downstream`] struct and its functionalities. - -pub mod downstream; -pub mod sv1_server; -pub use sv1_server::sv1_server::Sv1Server; diff --git a/roles/translator/src/lib/sv1/sv1_server/channel.rs b/roles/translator/src/lib/sv1/sv1_server/channel.rs deleted file mode 100644 index 1a306337d6..0000000000 --- a/roles/translator/src/lib/sv1/sv1_server/channel.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::sv1::downstream::DownstreamMessages; -use async_channel::{unbounded, Receiver, Sender}; -use stratum_apps::stratum_core::parsers_sv2::Mining; - -use stratum_apps::stratum_core::sv1_api::json_rpc; -use tokio::sync::broadcast; - -pub struct Sv1ServerChannelState { - pub sv1_server_to_downstream_sender: broadcast::Sender<(u32, Option, json_rpc::Message)>, - pub downstream_to_sv1_server_sender: Sender, - pub downstream_to_sv1_server_receiver: Receiver, - pub channel_manager_receiver: Receiver>, - pub channel_manager_sender: Sender>, -} - -impl Sv1ServerChannelState { - pub fn new( - channel_manager_receiver: Receiver>, - channel_manager_sender: Sender>, - ) -> Self { - let (sv1_server_to_downstream_sender, _) = broadcast::channel(100); - let (downstream_to_sv1_server_sender, downstream_to_sv1_server_receiver) = unbounded(); - - Self { - sv1_server_to_downstream_sender, - downstream_to_sv1_server_receiver, - downstream_to_sv1_server_sender, - channel_manager_receiver, - channel_manager_sender, - } - } - - pub fn drop(&self) { - self.channel_manager_receiver.close(); - self.channel_manager_sender.close(); - self.downstream_to_sv1_server_receiver.close(); - self.downstream_to_sv1_server_sender.close(); - self.channel_manager_receiver.close(); - self.channel_manager_sender.close(); - } -} diff --git a/roles/translator/src/lib/sv1/sv1_server/data.rs b/roles/translator/src/lib/sv1/sv1_server/data.rs deleted file mode 100644 index 043cca44ac..0000000000 --- a/roles/translator/src/lib/sv1/sv1_server/data.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::sv1::downstream::downstream::Downstream; -use std::{ - collections::HashMap, - sync::{atomic::AtomicU32, Arc, RwLock}, -}; -use stratum_apps::stratum_core::{ - bitcoin::Target, channels_sv2::vardiff::classic::VardiffState, mining_sv2::SetNewPrevHash, - sv1_api::server_to_client, -}; - -#[derive(Debug, Clone)] -pub struct PendingTargetUpdate { - pub downstream_id: u32, - pub new_target: Target, - pub new_hashrate: f32, -} - -#[derive(Debug)] -pub struct Sv1ServerData { - pub downstreams: HashMap>, - pub vardiff: HashMap>>, - pub prevhash: Option>, - pub downstream_id_factory: AtomicU32, - /// Job storage for aggregated mode - all Sv1 downstreams share the same jobs - pub aggregated_valid_jobs: Option>>, - /// Job storage for non-aggregated mode - each Sv1 downstream has its own jobs - pub non_aggregated_valid_jobs: Option>>>, - /// Tracks pending target updates that are waiting for SetTarget response from upstream - pub pending_target_updates: Vec, - /// The initial target used when opening channels - used when no downstreams remain - pub initial_target: Option, -} - -impl Sv1ServerData { - pub fn new(aggregate_channels: bool) -> Self { - Self { - downstreams: HashMap::new(), - vardiff: HashMap::new(), - prevhash: None, - downstream_id_factory: AtomicU32::new(0), - aggregated_valid_jobs: aggregate_channels.then(Vec::new), - non_aggregated_valid_jobs: (!aggregate_channels).then(HashMap::new), - pending_target_updates: Vec::new(), - initial_target: None, - } - } -} diff --git a/roles/translator/src/lib/sv1/sv1_server/difficulty_manager.rs b/roles/translator/src/lib/sv1/sv1_server/difficulty_manager.rs deleted file mode 100644 index f4e5ce9ecf..0000000000 --- a/roles/translator/src/lib/sv1/sv1_server/difficulty_manager.rs +++ /dev/null @@ -1,751 +0,0 @@ -use crate::{ - sv1::sv1_server::data::{PendingTargetUpdate, Sv1ServerData}, - utils::ShutdownMessage, -}; -use async_channel::Sender; -use std::{collections::HashMap, sync::Arc, time::Duration}; -use stratum_apps::{ - custom_mutex::Mutex, - stratum_core::{ - bitcoin::Target, - channels_sv2::{target::hash_rate_to_target, Vardiff}, - mining_sv2::{SetTarget, UpdateChannel}, - parsers_sv2::Mining, - stratum_translation::sv2_to_sv1::build_sv1_set_difficulty_from_sv2_target, - sv1_api::json_rpc, - }, -}; -use tokio::{sync::broadcast, time}; -use tracing::{debug, error, info, trace, warn}; - -/// Handles all variable difficulty adjustment logic for the SV1 server. -/// -/// This module contains the core vardiff implementation that: -/// - Periodically adjusts difficulty targets based on share submission rates -/// - Manages the relationship between upstream and downstream targets -/// - Handles both aggregated and non-aggregated channel modes -/// - Coordinates with the channel manager for target updates -pub struct DifficultyManager { - shares_per_minute: f32, - is_aggregated: bool, -} - -impl DifficultyManager { - /// Creates a new difficulty manager instance. - /// - /// # Arguments - /// * `shares_per_minute` - Target shares per minute for difficulty adjustment - /// * `is_aggregated` - Whether channels are operating in aggregated mode - pub fn new(shares_per_minute: f32, is_aggregated: bool) -> Self { - Self { - shares_per_minute, - is_aggregated, - } - } - - /// Spawns the variable difficulty adjustment loop. - /// - /// This method implements the SV1 server's variable difficulty logic for all downstreams. - /// Every 60 seconds, this method updates the difficulty state for each downstream. - pub async fn spawn_vardiff_loop( - sv1_server_data: Arc>, - channel_manager_sender: Sender>, - sv1_server_to_downstream_sender: broadcast::Sender<(u32, Option, json_rpc::Message)>, - shares_per_minute: f32, - is_aggregated: bool, - mut notify_shutdown: broadcast::Receiver, - shutdown_complete_tx: tokio::sync::mpsc::Sender<()>, - ) { - let difficulty_manager = DifficultyManager::new(shares_per_minute, is_aggregated); - - 'vardiff_loop: loop { - tokio::select! { - message = notify_shutdown.recv() => { - match message { - Ok(ShutdownMessage::ShutdownAll) => { - debug!("SV1 Server: Vardiff loop received shutdown signal. Exiting."); - break 'vardiff_loop; - } - Ok(ShutdownMessage::DownstreamShutdown(downstream_id)) => { - sv1_server_data.super_safe_lock(|d| { - d.vardiff.remove(&downstream_id); - }); - } - Ok(ShutdownMessage::DownstreamShutdownAll) => { - sv1_server_data.super_safe_lock(|d|{ - d.vardiff = HashMap::new(); - d.downstreams = HashMap::new(); - }); - info!("๐Ÿ”Œ All downstreams removed from sv1 server as upstream changed"); - - // In aggregated mode, send UpdateChannel to reflect the new state (no downstreams) - Self::send_update_channel_on_downstream_state_change( - &sv1_server_data, - &channel_manager_sender, - is_aggregated, - ).await; - } - Ok(ShutdownMessage::UpstreamReconnectedResetAndShutdownDownstreams) => { - sv1_server_data.super_safe_lock(|d|{ - d.vardiff = HashMap::new(); - d.downstreams = HashMap::new(); - }); - info!("๐Ÿ”Œ All downstreams removed from sv1 server as upstream reconnected"); - - // In aggregated mode, send UpdateChannel to reflect the new state (no downstreams) - Self::send_update_channel_on_downstream_state_change( - &sv1_server_data, - &channel_manager_sender, - is_aggregated, - ).await; - } - _ => {} - } - } - _ = time::sleep(Duration::from_secs(60)) => { - difficulty_manager.handle_vardiff_updates( - &sv1_server_data, - &channel_manager_sender, - &sv1_server_to_downstream_sender, - ).await; - } - } - } - drop(shutdown_complete_tx); - debug!("SV1 Server: Vardiff loop exited."); - } - - /// Handles variable difficulty adjustments for all connected downstreams. - /// - /// This method implements the core vardiff logic: - /// 1. For each downstream, calculate if a target update is needed - /// 2. Always send UpdateChannel to keep upstream informed - /// 3. Compare new target with upstream target to decide when to send set_difficulty: - /// - If new_target >= upstream_target: send set_difficulty immediately - /// - If new_target < upstream_target: wait for SetTarget response before sending - /// set_difficulty - /// 4. Handle aggregated vs non-aggregated modes for UpdateChannel messages - async fn handle_vardiff_updates( - &self, - sv1_server_data: &Arc>, - channel_manager_sender: &Sender>, - sv1_server_to_downstream_sender: &broadcast::Sender<(u32, Option, json_rpc::Message)>, - ) { - let vardiff_map = sv1_server_data.super_safe_lock(|v| v.vardiff.clone()); - let mut immediate_updates = Vec::new(); - let mut all_updates = Vec::new(); // All updates will generate UpdateChannel messages - - // Process each downstream and determine update strategy - for (downstream_id, vardiff_state) in vardiff_map.iter() { - debug!("Updating vardiff for downstream_id: {}", downstream_id); - let mut vardiff = vardiff_state.write().unwrap(); - - // Get current state from downstream - let Some((channel_id, hashrate, target, upstream_target)) = sv1_server_data - .super_safe_lock(|data| { - data.downstreams.get(downstream_id).and_then(|ds| { - ds.downstream_data.super_safe_lock(|d| { - Some(( - d.channel_id, - d.hashrate.unwrap(), /* It's safe to unwrap because we know that - * the downstream has a hashrate (we are - * doing vardiff) */ - d.target, - d.upstream_target, - )) - }) - }) - }) - else { - continue; - }; - - let Some(channel_id) = channel_id else { - error!("Channel id is none for downstream_id: {}", downstream_id); - continue; - }; - - let new_hashrate_opt = vardiff.try_vardiff(hashrate, &target, self.shares_per_minute); - - if let Ok(Some(new_hashrate)) = new_hashrate_opt { - // Calculate new target based on new hashrate - let new_target: Target = - match hash_rate_to_target(new_hashrate as f64, self.shares_per_minute as f64) { - Ok(target) => target, - Err(e) => { - error!( - "Failed to calculate target for hashrate {}: {:?}", - new_hashrate, e - ); - continue; - } - }; - - // Always update the downstream's pending target and hashrate - _ = sv1_server_data.safe_lock(|dmap| { - if let Some(d) = dmap.downstreams.get(downstream_id) { - _ = d.downstream_data.safe_lock(|d| { - d.set_pending_target(new_target); - d.set_pending_hashrate(Some(new_hashrate)); - }); - } - }); - - // All updates will be sent as UpdateChannel messages - all_updates.push((*downstream_id, channel_id, new_target, new_hashrate)); - - // Determine if we should send set_difficulty immediately or wait - match upstream_target { - Some(upstream_target) => { - if new_target >= upstream_target { - // Case 1: new_target >= upstream_target, send set_difficulty - // immediately - trace!( - "โœ… Target comparison: new_target ({:?}) >= upstream_target ({:?}) for downstream {}, will send set_difficulty immediately", - new_target, upstream_target, downstream_id - ); - immediate_updates.push((channel_id, Some(*downstream_id), new_target)); - } else { - // Case 2: new_target < upstream_target, delay set_difficulty until - // SetTarget - trace!( - "โณ Target comparison: new_target ({:?}) < upstream_target ({:?}) for downstream {}, will delay set_difficulty until SetTarget", - new_target, upstream_target, downstream_id - ); - // Store as pending update for when SetTarget arrives - sv1_server_data.super_safe_lock(|data| { - data.pending_target_updates.push(PendingTargetUpdate { - downstream_id: *downstream_id, - new_target, - new_hashrate, - }); - }); - } - } - None => { - // No upstream target set yet, send set_difficulty immediately as fallback - trace!( - "No upstream target set for downstream {}, will send set_difficulty immediately", - downstream_id - ); - immediate_updates.push((channel_id, Some(*downstream_id), new_target)); - } - } - } - } - - // Send UpdateChannel messages for ALL updates (both immediate and delayed) - if !all_updates.is_empty() { - self.send_update_channel_messages(all_updates, sv1_server_data, channel_manager_sender) - .await; - } - - // Process immediate set_difficulty updates (for new_target >= upstream_target) - for (channel_id, downstream_id, target) in immediate_updates { - // Send set_difficulty message immediately - if let Ok(set_difficulty_msg) = build_sv1_set_difficulty_from_sv2_target(target) { - if let Err(e) = sv1_server_to_downstream_sender.send(( - channel_id, - downstream_id, - set_difficulty_msg, - )) { - error!( - "Failed to send immediate SetDifficulty message to downstream {}: {:?}", - downstream_id.unwrap_or(0), - e - ); - } else { - trace!( - "Sent immediate SetDifficulty to downstream {} (new_target >= upstream_target)", - downstream_id.unwrap_or(0) - ); - } - } - } - } - - /// Sends UpdateChannel messages for all target updates. - /// - /// Always sends UpdateChannel to keep upstream informed about target changes. - /// Handles both aggregated and non-aggregated modes: - /// - Aggregated: Send single UpdateChannel with minimum target and sum of hashrates - /// - Non-aggregated: Send individual UpdateChannel for each downstream - async fn send_update_channel_messages( - &self, - all_updates: Vec<(u32, u32, Target, f32)>, /* (downstream_id, channel_id, new_target, - * new_hashrate) */ - sv1_server_data: &Arc>, - channel_manager_sender: &Sender>, - ) { - if self.is_aggregated { - // Aggregated mode: Send single UpdateChannel with minimum target and total hashrate of - // ALL downstreams - if let Some((_, channel_id, _, _)) = all_updates.first() { - // Get minimum target among ALL downstreams, not just the ones with updates - let min_target = sv1_server_data.super_safe_lock(|data| { - data.downstreams - .values() - .map(|downstream| { - downstream.downstream_data.super_safe_lock(|d| { - // Use pending_target if available, otherwise current target - *d.pending_target.as_ref().unwrap_or(&d.target) - }) - }) - .min() - .expect("At least one downstream should exist") - }); - - // Get total hashrate of ALL downstreams, not just the ones with updates - let total_hashrate: f32 = sv1_server_data.super_safe_lock(|data| { - data.downstreams - .values() - .map(|downstream| { - downstream.downstream_data.super_safe_lock(|d| { - // Use pending_hashrate if available, otherwise current hashrate - // It's safe to unwrap because we know that the downstream has a - // hashrate (we are doing vardiff) - d.pending_hashrate.unwrap_or(d.hashrate.unwrap()) - }) - }) - .sum() - }); - - let update_channel = UpdateChannel { - channel_id: *channel_id, - nominal_hash_rate: total_hashrate, - maximum_target: min_target.to_le_bytes().into(), - }; - - debug!( - "Sending UpdateChannel for aggregated mode: channel_id={}, total_hashrate={} (all {} downstreams), min_target={:?}, vardiff_updates={}", - channel_id, total_hashrate, - sv1_server_data.super_safe_lock(|data| data.downstreams.len()), - &min_target, all_updates.len() - ); - - if let Err(e) = channel_manager_sender - .send(Mining::UpdateChannel(update_channel)) - .await - { - error!("Failed to send UpdateChannel message: {:?}", e); - } - } - } else { - // Non-aggregated mode: Send individual UpdateChannel for each downstream - for (downstream_id, channel_id, new_target, new_hashrate) in &all_updates { - let update_channel = UpdateChannel { - channel_id: *channel_id, - nominal_hash_rate: *new_hashrate, - maximum_target: new_target.to_le_bytes().into(), - }; - - debug!( - "Sending UpdateChannel for downstream {}: channel_id={}, hashrate={}, target={:?}", - downstream_id, channel_id, new_hashrate, new_target - ); - - if let Err(e) = channel_manager_sender - .send(Mining::UpdateChannel(update_channel)) - .await - { - error!( - "Failed to send UpdateChannel message for downstream {}: {:?}", - downstream_id, e - ); - } - } - } - } - - /// Handles SetTarget messages from the ChannelManager. - /// - /// Aggregated mode: Single SetTarget updates all downstreams and processes all pending updates - /// Non-aggregated mode: Each SetTarget updates one specific downstream and processes its - /// pending update - pub async fn handle_set_target_message( - set_target: SetTarget<'_>, - sv1_server_data: &Arc>, - channel_manager_sender: &Sender>, - sv1_server_to_downstream_sender: &broadcast::Sender<(u32, Option, json_rpc::Message)>, - is_aggregated: bool, - ) { - let new_upstream_target = - Target::from_le_bytes(set_target.maximum_target.inner_as_ref().try_into().unwrap()); - debug!( - "Received SetTarget for channel {}: new_upstream_target = {:?}", - set_target.channel_id, new_upstream_target - ); - - if is_aggregated { - Self::handle_aggregated_set_target( - new_upstream_target, - set_target.channel_id, - sv1_server_data, - channel_manager_sender, - sv1_server_to_downstream_sender, - ) - .await; - } else { - Self::handle_non_aggregated_set_target( - set_target.channel_id, - new_upstream_target, - sv1_server_data, - channel_manager_sender, - sv1_server_to_downstream_sender, - ) - .await; - } - } - - /// Handles SetTarget in aggregated mode. - /// Updates all downstreams and processes all pending set_difficulty messages. - async fn handle_aggregated_set_target( - new_upstream_target: Target, - channel_id: u32, - sv1_server_data: &Arc>, - _channel_manager_sender: &Sender>, - sv1_server_to_downstream_sender: &broadcast::Sender<(u32, Option, json_rpc::Message)>, - ) { - debug!("Aggregated mode: Updating upstream target for all downstreams"); - - // Update upstream target for ALL downstreams - let downstream_ids: Vec = - sv1_server_data.super_safe_lock(|data| data.downstreams.keys().cloned().collect()); - - for downstream_id in downstream_ids { - _ = sv1_server_data.safe_lock(|data| { - if let Some(downstream) = data.downstreams.get(&downstream_id) { - _ = downstream.downstream_data.safe_lock(|d| { - d.set_upstream_target(new_upstream_target); - }); - } - }); - } - - // Process ALL pending difficulty updates that can now be sent downstream - let applicable_updates = Self::get_pending_difficulty_updates( - new_upstream_target, - None, - channel_id, - sv1_server_data, - ); - Self::send_pending_set_difficulty_messages_to_downstream( - applicable_updates, - sv1_server_data, - sv1_server_to_downstream_sender, - ) - .await; - } - - /// Handles SetTarget in non-aggregated mode. - /// Updates the specific downstream and processes its pending set_difficulty message. - async fn handle_non_aggregated_set_target( - channel_id: u32, - new_upstream_target: Target, - sv1_server_data: &Arc>, - _channel_manager_sender: &Sender>, - sv1_server_to_downstream_sender: &broadcast::Sender<(u32, Option, json_rpc::Message)>, - ) { - debug!( - "Non-aggregated mode: Processing SetTarget for channel {}", - channel_id - ); - - let affected_downstream = sv1_server_data.super_safe_lock(|data| { - data.downstreams - .iter() - .find_map(|(downstream_id, downstream)| { - downstream.downstream_data.super_safe_lock(|d| { - if d.channel_id == Some(channel_id) { - Some(*downstream_id) - } else { - None - } - }) - }) - }); - - if let Some(downstream_id) = affected_downstream { - // Update upstream target for this specific downstream - _ = sv1_server_data.safe_lock(|data| { - if let Some(downstream) = data.downstreams.get(&downstream_id) { - _ = downstream.downstream_data.safe_lock(|d| { - d.set_upstream_target(new_upstream_target); - }); - } - }); - trace!("Updated upstream target for downstream {}", downstream_id); - - // Process pending difficulty updates for this specific downstream only - let applicable_updates = Self::get_pending_difficulty_updates( - new_upstream_target, - Some(downstream_id), - channel_id, - sv1_server_data, - ); - Self::send_pending_set_difficulty_messages_to_downstream( - applicable_updates, - sv1_server_data, - sv1_server_to_downstream_sender, - ) - .await; - } else { - warn!("No downstream found for channel {}", channel_id); - } - } - - /// Gets pending updates that can now be applied based on the new upstream target. - /// If downstream_id is provided, only returns updates for that specific downstream. - /// Logs a warning if the upstream target is higher than any requested target. - fn get_pending_difficulty_updates( - new_upstream_target: Target, - downstream_id: Option, - channel_id: u32, - sv1_server_data: &Arc>, - ) -> Vec { - let mut applicable_updates = Vec::new(); - - sv1_server_data.super_safe_lock(|data| { - data.pending_target_updates.retain(|pending_update| { - // Check if we should process this update - let should_process = match downstream_id { - Some(downstream_id) => pending_update.downstream_id == downstream_id, - None => true, // Process all in aggregated mode - }; - - if should_process { - if pending_update.new_target >= new_upstream_target { - // Target is acceptable, can apply immediately - applicable_updates.push(pending_update.clone()); - false // remove from pending list - } else { - // WARNING: Upstream gave us a target higher than what we requested - error!( - "โŒ Protocol issue: SetTarget response has target ({:?}) which is higher than requested target ({:?}) in UpdateChannel for channel {:?}. Ignoring this pending update for downstream {:?}.", - new_upstream_target, pending_update.new_target, channel_id, pending_update.downstream_id - ); - false // remove from pending list (don't keep invalid requests) - } - } else { - true // keep in pending list (not relevant for this SetTarget) - } - }); - }); - applicable_updates - } - - /// Sends set_difficulty messages for all applicable pending updates. - async fn send_pending_set_difficulty_messages_to_downstream( - difficulty_updates: Vec, - sv1_server_data: &Arc>, - sv1_server_to_downstream_sender: &broadcast::Sender<(u32, Option, json_rpc::Message)>, - ) { - for pending_update in &difficulty_updates { - // Get channel_id for this downstream - let channel_id = sv1_server_data.super_safe_lock(|data| { - data.downstreams - .get(&pending_update.downstream_id) - .and_then(|ds| ds.downstream_data.super_safe_lock(|d| d.channel_id)) - }); - - if let Some(channel_id) = channel_id { - // Send set_difficulty message - if let Ok(set_difficulty_msg) = - build_sv1_set_difficulty_from_sv2_target(pending_update.new_target) - { - if let Err(e) = sv1_server_to_downstream_sender.send(( - channel_id, - Some(pending_update.downstream_id), - set_difficulty_msg, - )) { - error!( - "Failed to send SetDifficulty to downstream {}: {:?}", - pending_update.downstream_id, e - ); - } else { - trace!( - "Sent SetDifficulty to downstream {}", - pending_update.downstream_id - ); - } - } - } - } - } - - /// Sends an UpdateChannel message for aggregated mode when downstream state changes - /// (e.g., disconnect). Calculates total hashrate and minimum target among all remaining - /// downstreams. - pub async fn send_update_channel_on_downstream_state_change( - sv1_server_data: &Arc>, - channel_manager_sender: &Sender>, - is_aggregated: bool, - ) { - if !is_aggregated { - return; // Only applies to aggregated mode - } - - let (total_hashrate, min_target, channel_id, downstream_count) = sv1_server_data - .super_safe_lock(|data| { - // Hardcoded channel_id 0 (the ChannelManager will set this channel_id to the - // upstream extended channel id) - let channel_id = 0; - - let total_hashrate: f32 = data - .downstreams - .values() - .map(|downstream| { - downstream.downstream_data.super_safe_lock(|d| { - // Use pending_hashrate if available, otherwise current hashrate - // It's safe to unwrap because we know that the downstream has a - // hashrate (we are doing vardiff) - d.pending_hashrate.unwrap_or(d.hashrate.unwrap()) - }) - }) - .sum(); - - let min_target = data - .downstreams - .values() - .map(|downstream| { - downstream.downstream_data.super_safe_lock(|d| { - // Use pending_target if available, otherwise current target - *d.pending_target.as_ref().unwrap_or(&d.target) - }) - }) - .min(); - - ( - total_hashrate, - min_target, - Some(channel_id), - data.downstreams.len(), - ) - }); - - if let (Some(min_target), Some(channel_id)) = (min_target, channel_id) { - let update_channel = UpdateChannel { - channel_id, - nominal_hash_rate: total_hashrate, - maximum_target: min_target.to_le_bytes().into(), - }; - - if let Err(e) = channel_manager_sender - .send(Mining::UpdateChannel(update_channel)) - .await - { - error!( - "Failed to send UpdateChannel message after downstream state change: {:?}", - e - ); - } - } else if downstream_count == 0 { - // No downstreams remaining, send UpdateChannel with maximum possible target - let update_channel = UpdateChannel { - channel_id: 0, - nominal_hash_rate: 0.0, // No hashrate when no downstreams - maximum_target: [0xFF; 32].into(), - }; - - if let Err(e) = channel_manager_sender - .send(Mining::UpdateChannel(update_channel)) - .await - { - error!( - "Failed to send UpdateChannel message with maximum target: {:?}", - e - ); - } - } else { - warn!("Cannot send UpdateChannel after downstream state change: no downstreams remaining or no channel_id"); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::sv1::sv1_server::data::Sv1ServerData; - use async_channel::unbounded; - use std::sync::Arc; - - fn create_test_difficulty_manager() -> DifficultyManager { - DifficultyManager::new(5.0, true) // 5 shares per minute, aggregated mode - } - - fn create_test_sv1_server_data() -> Arc> { - let data = Sv1ServerData::new(true); // aggregated mode - Arc::new(Mutex::new(data)) - } - - #[test] - fn test_difficulty_manager_creation() { - let manager = create_test_difficulty_manager(); - assert_eq!(manager.shares_per_minute, 5.0); - assert!(manager.is_aggregated); - - let non_agg_manager = DifficultyManager::new(10.0, false); - assert_eq!(non_agg_manager.shares_per_minute, 10.0); - assert!(!non_agg_manager.is_aggregated); - } - - #[tokio::test] - async fn test_send_update_channel_on_downstream_state_change_aggregated() { - let sv1_server_data = create_test_sv1_server_data(); - let (sender, receiver) = unbounded(); - - // Test with no downstreams - DifficultyManager::send_update_channel_on_downstream_state_change( - &sv1_server_data, - &sender, - true, // aggregated - ) - .await; - - // Should send UpdateChannel with maximum target when no downstreams - let received_message = receiver - .try_recv() - .expect("Should receive UpdateChannel message"); - if let Mining::UpdateChannel(update_channel) = received_message { - assert_eq!(update_channel.channel_id, 0); - assert_eq!(update_channel.nominal_hash_rate, 0.0); - assert_eq!(update_channel.maximum_target, [0xFF; 32].into()); - } else { - panic!( - "Expected UpdateChannel message, got: {:?}", - received_message - ); - } - } - - #[tokio::test] - async fn test_send_update_channel_on_downstream_state_change_non_aggregated() { - let sv1_server_data = create_test_sv1_server_data(); - let (sender, _receiver) = unbounded(); - - DifficultyManager::send_update_channel_on_downstream_state_change( - &sv1_server_data, - &sender, - false, // non-aggregated - ) - .await; - - // Non-aggregated mode should return early and not crash - } - - #[test] - fn test_get_pending_difficulty_updates_basic() { - let sv1_server_data = create_test_sv1_server_data(); - let upstream_target: Target = hash_rate_to_target(150.0, 5.0).unwrap(); - - // Test with empty pending updates - let applicable_updates = DifficultyManager::get_pending_difficulty_updates( - upstream_target, - None, // All downstreams - 1, // channel_id - &sv1_server_data, - ); - - assert_eq!(applicable_updates.len(), 0); - } -} diff --git a/roles/translator/src/lib/sv1/sv1_server/mod.rs b/roles/translator/src/lib/sv1/sv1_server/mod.rs deleted file mode 100644 index 4491b592cc..0000000000 --- a/roles/translator/src/lib/sv1/sv1_server/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub(super) mod channel; -pub mod data; -pub mod difficulty_manager; -pub mod sv1_server; diff --git a/roles/translator/src/lib/sv1/sv1_server/sv1_server.rs b/roles/translator/src/lib/sv1/sv1_server/sv1_server.rs deleted file mode 100644 index 1d81310a28..0000000000 --- a/roles/translator/src/lib/sv1/sv1_server/sv1_server.rs +++ /dev/null @@ -1,1025 +0,0 @@ -use crate::{ - config::TranslatorConfig, - error::TproxyError, - status::{handle_error, Status, StatusSender}, - sv1::{ - downstream::{downstream::Downstream, DownstreamMessages}, - sv1_server::{ - channel::Sv1ServerChannelState, data::Sv1ServerData, - difficulty_manager::DifficultyManager, - }, - }, - task_manager::TaskManager, - utils::ShutdownMessage, -}; -use async_channel::{Receiver, Sender}; -use std::{ - collections::HashMap, - net::SocketAddr, - sync::{ - atomic::{AtomicBool, AtomicU32, Ordering}, - Arc, RwLock, - }, -}; -use stratum_apps::{ - custom_mutex::Mutex, - network_helpers::sv1_connection::ConnectionSV1, - stratum_core::{ - binary_sv2::Str0255, - bitcoin::Target, - channels_sv2::{target::hash_rate_to_target, Vardiff, VardiffState}, - mining_sv2::{CloseChannel, SetTarget}, - parsers_sv2::Mining, - stratum_translation::{ - sv1_to_sv2::{ - build_sv2_open_extended_mining_channel, - build_sv2_submit_shares_extended_from_sv1_submit, - }, - sv2_to_sv1::{build_sv1_notify_from_sv2, build_sv1_set_difficulty_from_sv2_target}, - }, - sv1_api::IsServer, - }, -}; -use tokio::{ - net::TcpListener, - sync::{broadcast, mpsc}, -}; -use tracing::{debug, error, info, warn}; - -/// SV1 server that handles connections from SV1 miners. -/// -/// This struct manages the SV1 server component of the translator, which: -/// - Accepts connections from SV1 miners -/// - Manages difficulty adjustment for connected miners -/// - Coordinates with the SV2 channel manager for upstream communication -/// - Tracks mining jobs and share submissions -/// -/// The server maintains state for multiple downstream connections and implements -/// variable difficulty adjustment based on share submission rates. -pub struct Sv1Server { - sv1_server_channel_state: Sv1ServerChannelState, - sv1_server_data: Arc>, - shares_per_minute: f32, - listener_addr: SocketAddr, - config: TranslatorConfig, - clean_job: AtomicBool, - sequence_counter: AtomicU32, - miner_counter: AtomicU32, -} - -impl Sv1Server { - /// Drops the server's channel state, cleaning up resources. - pub fn drop(&self) { - self.sv1_server_channel_state.drop(); - } - - /// Creates a new SV1 server instance. - /// - /// # Arguments - /// * `listener_addr` - The socket address to bind the server to - /// * `channel_manager_receiver` - Channel to receive messages from the channel manager - /// * `channel_manager_sender` - Channel to send messages to the channel manager - /// * `config` - Configuration settings for the translator - /// - /// # Returns - /// A new Sv1Server instance ready to accept connections - pub fn new( - listener_addr: SocketAddr, - channel_manager_receiver: Receiver>, - channel_manager_sender: Sender>, - config: TranslatorConfig, - ) -> Self { - let shares_per_minute = config.downstream_difficulty_config.shares_per_minute; - let sv1_server_channel_state = - Sv1ServerChannelState::new(channel_manager_receiver, channel_manager_sender); - let sv1_server_data = Arc::new(Mutex::new(Sv1ServerData::new(config.aggregate_channels))); - Self { - sv1_server_channel_state, - sv1_server_data, - config, - listener_addr, - shares_per_minute, - clean_job: AtomicBool::new(true), - miner_counter: AtomicU32::new(0), - sequence_counter: AtomicU32::new(0), - } - } - - /// Starts the SV1 server and begins accepting connections. - /// - /// This method: - /// - Binds to the configured listening address - /// - Spawns the variable difficulty adjustment loop - /// - Enters the main event loop to handle: - /// - New miner connections - /// - Shutdown signals - /// - Messages from downstream miners (submit shares) - /// - Messages from upstream SV2 channel manager - /// - /// The server will continue running until a shutdown signal is received. - /// - /// # Arguments - /// * `notify_shutdown` - Broadcast channel for shutdown coordination - /// * `shutdown_complete_tx` - Channel to signal shutdown completion - /// * `status_sender` - Channel for sending status updates - /// * `task_manager` - Manager for spawned async tasks - /// - /// # Returns - /// * `Ok(())` - Server shut down gracefully - /// * `Err(TproxyError)` - Server encountered an error - pub async fn start( - self: Arc, - notify_shutdown: broadcast::Sender, - shutdown_complete_tx: mpsc::Sender<()>, - status_sender: Sender, - task_manager: Arc, - ) -> Result<(), TproxyError> { - info!("Starting SV1 server on {}", self.listener_addr); - let mut shutdown_rx_main = notify_shutdown.subscribe(); - let shutdown_complete_tx_main_clone = shutdown_complete_tx.clone(); - - // get the first target for the first set difficulty message - let first_target: Target = hash_rate_to_target( - self.config - .downstream_difficulty_config - .min_individual_miner_hashrate as f64, - self.config.downstream_difficulty_config.shares_per_minute as f64, - ) - .unwrap(); - - // Spawn vardiff loop only if enabled - if self.config.downstream_difficulty_config.enable_vardiff { - info!("Variable difficulty adjustment enabled - starting vardiff loop"); - task_manager.spawn(DifficultyManager::spawn_vardiff_loop( - self.sv1_server_data.clone(), - self.sv1_server_channel_state.channel_manager_sender.clone(), - self.sv1_server_channel_state - .sv1_server_to_downstream_sender - .clone(), - self.shares_per_minute, - self.config.aggregate_channels, - notify_shutdown.subscribe(), - shutdown_complete_tx_main_clone.clone(), - )); - } else { - info!("Variable difficulty adjustment disabled - upstream will manage difficulty, SV1 server will forward SetTarget messages to downstreams"); - } - - let listener = TcpListener::bind(self.listener_addr).await.map_err(|e| { - error!("Failed to bind to {}: {}", self.listener_addr, e); - e - })?; - - info!("Translator Proxy: listening on {}", self.listener_addr); - - let sv1_status_sender = StatusSender::Sv1Server(status_sender.clone()); - - loop { - tokio::select! { - message = shutdown_rx_main.recv() => { - match message { - Ok(ShutdownMessage::ShutdownAll) => { - debug!("SV1 Server: Vardiff loop received shutdown signal. Exiting."); - break; - } - Ok(ShutdownMessage::DownstreamShutdown(downstream_id)) => { - let current_downstream = self.sv1_server_data.super_safe_lock(|d| { - // Only remove from vardiff map if vardiff is enabled - if self.config.downstream_difficulty_config.enable_vardiff { - d.vardiff.remove(&downstream_id); - } - d.downstreams.remove(&downstream_id) - }); - if let Some(downstream) = current_downstream { - info!("๐Ÿ”Œ Downstream: {downstream_id} disconnected and removed from sv1 server downstreams"); - - // In aggregated mode, send UpdateChannel to reflect the new state (only if vardiff enabled) - if self.config.downstream_difficulty_config.enable_vardiff { - DifficultyManager::send_update_channel_on_downstream_state_change( - &self.sv1_server_data, - &self.sv1_server_channel_state.channel_manager_sender, - self.config.aggregate_channels, - ).await; - } - - let channel_id = downstream.downstream_data.super_safe_lock(|d| d.channel_id); - - if let Some(channel_id) = channel_id { - if !self.config.aggregate_channels { - info!("Sending CloseChannel message: {channel_id} for downstream: {downstream_id}"); - let reason_code = Str0255::try_from("downstream disconnected".to_string()).unwrap(); - _ = self.sv1_server_channel_state - .channel_manager_sender - .send(Mining::CloseChannel(CloseChannel { - channel_id, - reason_code, - })) - .await; - } - } - } - } - Ok(ShutdownMessage::DownstreamShutdownAll) => { - self.sv1_server_data.super_safe_lock(|d|{ - if self.config.downstream_difficulty_config.enable_vardiff { - d.vardiff = HashMap::new(); - } - d.downstreams = HashMap::new(); - }); - info!("๐Ÿ”Œ All downstreams removed from sv1 server as upstream changed"); - - // In aggregated mode, send UpdateChannel to reflect the new state (no downstreams) - if self.config.downstream_difficulty_config.enable_vardiff { - DifficultyManager::send_update_channel_on_downstream_state_change( - &self.sv1_server_data, - &self.sv1_server_channel_state.channel_manager_sender, - self.config.aggregate_channels, - ).await; - } - } - Ok(ShutdownMessage::UpstreamReconnectedResetAndShutdownDownstreams) => { - self.sv1_server_data.super_safe_lock(|d|{ - if self.config.downstream_difficulty_config.enable_vardiff { - d.vardiff = HashMap::new(); - } - d.downstreams = HashMap::new(); - }); - info!("๐Ÿ”Œ All downstreams removed from sv1 server as upstream reconnected"); - - // In aggregated mode, send UpdateChannel to reflect the new state (no downstreams) - if self.config.downstream_difficulty_config.enable_vardiff { - DifficultyManager::send_update_channel_on_downstream_state_change( - &self.sv1_server_data, - &self.sv1_server_channel_state.channel_manager_sender, - self.config.aggregate_channels, - ).await; - } - } - _ => {} - } - } - result = listener.accept() => { - match result { - Ok((stream, addr)) => { - info!("New SV1 downstream connection from {}", addr); - - let connection = ConnectionSV1::new(stream).await; - let downstream_id = self.sv1_server_data.super_safe_lock(|v| v.downstream_id_factory.fetch_add(1, Ordering::Relaxed)); - let downstream = Arc::new(Downstream::new( - downstream_id, - connection.sender().clone(), - connection.receiver().clone(), - self.sv1_server_channel_state.downstream_to_sv1_server_sender.clone(), - self.sv1_server_channel_state.sv1_server_to_downstream_sender.clone().subscribe(), - first_target, - Some(self.config.downstream_difficulty_config.min_individual_miner_hashrate), - self.sv1_server_data.clone(), - )); - // vardiff initialization (only if enabled) - _ = self.sv1_server_data - .safe_lock(|d| { - d.downstreams.insert(downstream_id, downstream.clone()); - // Insert vardiff state for this downstream only if vardiff is enabled - if self.config.downstream_difficulty_config.enable_vardiff { - let vardiff = Arc::new(RwLock::new(VardiffState::new().expect("Failed to create vardiffstate"))); - d.vardiff.insert(downstream_id, vardiff); - } - }); - info!("Downstream {} registered successfully (channel will be opened after first message)", downstream_id); - - // Start downstream tasks immediately, but defer channel opening until first message - let status_sender = StatusSender::Downstream { - downstream_id, - tx: status_sender.clone(), - }; - - Downstream::run_downstream_tasks( - downstream, - notify_shutdown.clone(), - shutdown_complete_tx.clone(), - status_sender, - task_manager.clone(), - ); - } - Err(e) => { - warn!("Failed to accept new connection: {:?}", e); - } - } - } - res = Self::handle_downstream_message( - Arc::clone(&self) - ) => { - if let Err(e) = res { - handle_error(&sv1_status_sender, e).await; - break; - } - } - res = Self::handle_upstream_message( - Arc::clone(&self), - first_target, - ) => { - if let Err(e) = res { - handle_error(&sv1_status_sender, e).await; - break; - } - } - } - } - self.sv1_server_channel_state.drop(); - drop(shutdown_complete_tx); - debug!("SV1 Server main listener loop exited."); - Ok(()) - } - - /// Handles messages received from downstream SV1 miners. - /// - /// This method processes share submissions from miners by: - /// - Updating variable difficulty counters - /// - Extracting and validating share data - /// - Converting SV1 share format to SV2 SubmitSharesExtended - /// - Forwarding the share to the channel manager for upstream submission - /// - /// # Returns - /// * `Ok(())` - Message processed successfully - /// * `Err(TproxyError)` - Error processing the message - pub async fn handle_downstream_message(self: Arc) -> Result<(), TproxyError> { - let downstream_message = self - .sv1_server_channel_state - .downstream_to_sv1_server_receiver - .recv() - .await - .map_err(TproxyError::ChannelErrorReceiver)?; - - match downstream_message { - DownstreamMessages::SubmitShares(message) => { - return self.handle_submit_shares(message).await; - } - DownstreamMessages::OpenChannel(downstream_id) => { - return self.handle_open_channel_request(downstream_id).await; - } - } - } - - /// Handles share submission messages from downstream. - async fn handle_submit_shares( - self: &Arc, - message: crate::sv1::downstream::SubmitShareWithChannelId, - ) -> Result<(), TproxyError> { - // Increment vardiff counter for this downstream (only if vardiff is enabled) - if self.config.downstream_difficulty_config.enable_vardiff { - self.sv1_server_data.safe_lock(|v| { - if let Some(vardiff_state) = v.vardiff.get(&message.downstream_id) { - vardiff_state - .write() - .unwrap() - .increment_shares_since_last_update(); - } - })?; - } - - let job_version = match message.job_version { - Some(version) => version, - None => { - warn!("Received share submission without valid job version, skipping"); - return Ok(()); - } - }; - - let submit_share_extended = build_sv2_submit_shares_extended_from_sv1_submit( - &message.share, - message.channel_id, - self.sequence_counter.load(Ordering::SeqCst), - job_version, - message.version_rolling_mask, - ) - .map_err(|_| TproxyError::SV1Error)?; - - self.sv1_server_channel_state - .channel_manager_sender - .send(Mining::SubmitSharesExtended(submit_share_extended)) - .await - .map_err(|_| TproxyError::ChannelErrorSender)?; - - self.sequence_counter.fetch_add(1, Ordering::SeqCst); - - Ok(()) - } - - /// Handles channel opening requests from downstream when they send their first message. - async fn handle_open_channel_request( - self: &Arc, - downstream_id: u32, - ) -> Result<(), TproxyError> { - info!("SV1 Server: Opening extended mining channel for downstream {} after receiving first message", downstream_id); - - let downstreams = self - .sv1_server_data - .super_safe_lock(|v| v.downstreams.clone()); - if let Some(downstream) = Self::get_downstream(downstream_id, downstreams) { - self.open_extended_mining_channel(downstream).await?; - } else { - error!( - "Downstream {} not found when trying to open channel", - downstream_id - ); - } - - Ok(()) - } - - /// Handles messages received from the upstream SV2 server via the channel manager. - /// - /// This method processes various SV2 messages including: - /// - OpenExtendedMiningChannelSuccess: Sets up downstream connections - /// - NewExtendedMiningJob: Converts to SV1 notify messages - /// - SetNewPrevHash: Updates block template information - /// - Channel error messages (TODO: implement proper handling) - /// - /// # Arguments - /// * `first_target` - Initial difficulty target for new connections - /// * `notify_shutdown` - Broadcast channel for shutdown coordination - /// * `shutdown_complete_tx` - Channel to signal shutdown completion - /// * `status_sender` - Channel for sending status updates - /// * `task_manager` - Manager for spawned async tasks - /// - /// # Returns - /// * `Ok(())` - Message processed successfully - /// * `Err(TproxyError)` - Error processing the message - pub async fn handle_upstream_message( - self: Arc, - first_target: Target, - ) -> Result<(), TproxyError> { - let message = self - .sv1_server_channel_state - .channel_manager_receiver - .recv() - .await - .map_err(TproxyError::ChannelErrorReceiver)?; - - match message { - Mining::OpenExtendedMiningChannelSuccess(m) => { - debug!( - "Received OpenExtendedMiningChannelSuccess for channel id: {}", - m.channel_id - ); - let downstream_id = m.request_id; - let downstreams = self - .sv1_server_data - .super_safe_lock(|v| v.downstreams.clone()); - if let Some(downstream) = Self::get_downstream(downstream_id, downstreams) { - let initial_target = - Target::from_le_bytes(m.target.inner_as_ref().try_into().unwrap()); - downstream.downstream_data.safe_lock(|d| { - d.extranonce1 = m.extranonce_prefix.to_vec(); - d.extranonce2_len = m.extranonce_size.into(); - d.channel_id = Some(m.channel_id); - // Set the initial upstream target from OpenExtendedMiningChannelSuccess - d.set_upstream_target(initial_target); - })?; - - // Process all queued messages now that channel is established - if let Ok(queued_messages) = downstream.downstream_data.safe_lock(|d| { - let messages = d.queued_sv1_handshake_messages.clone(); - d.queued_sv1_handshake_messages.clear(); - messages - }) { - if !queued_messages.is_empty() { - info!( - "Processing {} queued Sv1 messages for downstream {}", - queued_messages.len(), - downstream_id - ); - - // Set flag to indicate we're processing queued responses - downstream.downstream_data.super_safe_lock(|data| { - data.processing_queued_sv1_handshake_responses - .store(true, std::sync::atomic::Ordering::SeqCst); - }); - - for message in queued_messages { - if let Ok(Some(response_msg)) = downstream - .downstream_data - .super_safe_lock(|data| data.handle_message(message)) - { - self.sv1_server_channel_state - .sv1_server_to_downstream_sender - .send(( - m.channel_id, - Some(downstream_id), - response_msg.into(), - )) - .map_err(|_| TproxyError::ChannelErrorSender)?; - } - } - } - } - - let set_difficulty = build_sv1_set_difficulty_from_sv2_target(first_target) - .map_err(|_| { - TproxyError::General("Failed to generate set_difficulty".into()) - })?; - // send the set_difficulty message to the downstream - self.sv1_server_channel_state - .sv1_server_to_downstream_sender - .send((m.channel_id, None, set_difficulty)) - .map_err(|_| TproxyError::ChannelErrorSender)?; - } else { - error!("Downstream not found for downstream_id: {}", downstream_id); - } - } - - Mining::NewExtendedMiningJob(m) => { - debug!( - "Received NewExtendedMiningJob for channel id: {}", - m.channel_id - ); - if let Some(prevhash) = self.sv1_server_data.super_safe_lock(|v| v.prevhash.clone()) - { - let notify = build_sv1_notify_from_sv2( - prevhash, - m.clone().into_static(), - self.clean_job.load(Ordering::SeqCst), - )?; - let clean_jobs = self.clean_job.load(Ordering::SeqCst); - self.clean_job.store(false, Ordering::SeqCst); - - // Update job storage based on the configured mode - let notify_parsed = notify.clone(); - self.sv1_server_data.super_safe_lock(|server_data| { - if let Some(ref mut aggregated_jobs) = server_data.aggregated_valid_jobs { - // Aggregated mode: all downstreams share the same jobs - if clean_jobs { - aggregated_jobs.clear(); - } - aggregated_jobs.push(notify_parsed); - } else if let Some(ref mut non_aggregated_jobs) = - server_data.non_aggregated_valid_jobs - { - // Non-aggregated mode: per-downstream jobs - let channel_jobs = non_aggregated_jobs - .entry(m.channel_id) - .or_insert_with(Vec::new); - if clean_jobs { - channel_jobs.clear(); - } - channel_jobs.push(notify_parsed); - } - }); - - let _ = self - .sv1_server_channel_state - .sv1_server_to_downstream_sender - .send((m.channel_id, None, notify.into())); - } - } - - Mining::SetNewPrevHash(m) => { - debug!("Received SetNewPrevHash for channel id: {}", m.channel_id); - self.clean_job.store(true, Ordering::SeqCst); - self.sv1_server_data - .super_safe_lock(|v| v.prevhash = Some(m.clone().into_static())); - } - - Mining::SetTarget(m) => { - debug!("Received SetTarget for channel id: {}", m.channel_id); - if self.config.downstream_difficulty_config.enable_vardiff { - // Vardiff enabled - use full difficulty management - DifficultyManager::handle_set_target_message( - m, - &self.sv1_server_data, - &self.sv1_server_channel_state.channel_manager_sender, - &self - .sv1_server_channel_state - .sv1_server_to_downstream_sender, - self.config.aggregate_channels, - ) - .await; - } else { - // Vardiff disabled - just forward the difficulty to downstreams - debug!("Vardiff disabled - forwarding SetTarget to downstreams"); - self.handle_set_target_without_vardiff(m).await; - } - } - - Mining::CloseChannel(_) => { - todo!("Handle CloseChannel message from upstream"); - } - - Mining::OpenMiningChannelError(_) => { - todo!("Handle OpenMiningChannelError message from upstream"); - } - - Mining::UpdateChannelError(_) => { - todo!("Handle UpdateChannelError message from upstream"); - } - - _ => unreachable!("Unexpected message type received from upstream"), - } - - Ok(()) - } - - /// Opens an extended mining channel for a downstream connection. - /// - /// This method initiates the SV2 channel setup process by: - /// - Calculating the initial target based on configuration - /// - Generating a unique user identity for the miner - /// - Creating an OpenExtendedMiningChannel message - /// - Sending the request to the channel manager - /// - /// # Arguments - /// * `downstream` - The downstream connection to set up a channel for - /// - /// # Returns - /// * `Ok(())` - Channel setup request sent successfully - /// * `Err(TproxyError)` - Error setting up the channel - pub async fn open_extended_mining_channel( - &self, - downstream: Arc, - ) -> Result<(), TproxyError> { - let config = &self.config.downstream_difficulty_config; - - let hashrate = config.min_individual_miner_hashrate as f64; - let shares_per_min = config.shares_per_minute as f64; - let min_extranonce_size = self.config.downstream_extranonce2_size; - let vardiff_enabled = config.enable_vardiff; - - let max_target = if vardiff_enabled { - hash_rate_to_target(hashrate, shares_per_min).unwrap() - } else { - // If translator doesn't manage vardiff, we rely on upstream to do that, - // so we give it more freedom by setting max_target to maximum possible value - Target::from_le_bytes([0xff; 32]) - }; - - // Store the initial target for use when no downstreams remain - self.sv1_server_data.super_safe_lock(|data| { - if data.initial_target.is_none() { - data.initial_target = Some(max_target); - } - }); - - let miner_id = self.miner_counter.fetch_add(1, Ordering::SeqCst) + 1; - let user_identity = format!("{}.miner{}", self.config.user_identity, miner_id); - - downstream - .downstream_data - .safe_lock(|d| d.user_identity = user_identity.clone())?; - - if let Ok(open_channel_msg) = build_sv2_open_extended_mining_channel( - downstream - .downstream_data - .super_safe_lock(|d| d.downstream_id), - user_identity.clone(), - hashrate as f32, - max_target, - min_extranonce_size, - ) { - self.sv1_server_channel_state - .channel_manager_sender - .send(Mining::OpenExtendedMiningChannel(open_channel_msg)) - .await - .map_err(|_| TproxyError::ChannelErrorSender)?; - } else { - error!("Failed to build OpenExtendedMiningChannel message"); - } - - Ok(()) - } - - /// Retrieves a downstream connection by ID from the provided map. - /// - /// # Arguments - /// * `downstream_id` - The ID of the downstream connection to find - /// * `downstream` - HashMap containing downstream connections - /// - /// # Returns - /// * `Some(Downstream)` - If a downstream with the given ID exists - /// * `None` - If no downstream with the given ID is found - pub fn get_downstream( - downstream_id: u32, - downstream: HashMap>, - ) -> Option> { - downstream.get(&downstream_id).cloned() - } - - /// Extracts the downstream ID from a Downstream instance. - /// - /// # Arguments - /// * `downstream` - The downstream connection to get the ID from - /// - /// # Returns - /// The downstream ID as a u32 - pub fn get_downstream_id(downstream: Downstream) -> u32 { - downstream - .downstream_data - .super_safe_lock(|s| s.downstream_id) - } - - /// Handles SetTarget messages when vardiff is disabled. - /// - /// This method forwards difficulty changes from upstream directly to downstream miners - /// without any variable difficulty logic. It respects the aggregated/non-aggregated - /// channel configuration. - async fn handle_set_target_without_vardiff(&self, set_target: SetTarget<'_>) { - let new_target = - Target::from_le_bytes(set_target.maximum_target.inner_as_ref().try_into().unwrap()); - debug!( - "Forwarding SetTarget to downstreams: channel_id={}, target={:?}", - set_target.channel_id, new_target - ); - - if self.config.aggregate_channels { - // Aggregated mode: send set_difficulty to ALL downstreams - self.send_set_difficulty_to_all_downstreams(new_target) - .await; - } else { - // Non-aggregated mode: send set_difficulty to specific downstream for this channel - self.send_set_difficulty_to_specific_downstream(set_target.channel_id, new_target) - .await; - } - } - - /// Sends set_difficulty to all downstreams (aggregated mode). - /// Used only when vardiff is disabled. - async fn send_set_difficulty_to_all_downstreams(&self, target: Target) { - let downstreams = self - .sv1_server_data - .super_safe_lock(|data| data.downstreams.clone()); - - for (downstream_id, downstream) in downstreams { - let channel_id = downstream.downstream_data.super_safe_lock(|d| d.channel_id); - - if let Some(channel_id) = channel_id { - // Update the downstream's target - _ = downstream.downstream_data.safe_lock(|d| { - d.set_upstream_target(target); - d.set_pending_target(target); - }); - - // Send set_difficulty message - if let Ok(set_difficulty_msg) = build_sv1_set_difficulty_from_sv2_target(target) { - if let Err(e) = self - .sv1_server_channel_state - .sv1_server_to_downstream_sender - .send((channel_id, Some(downstream_id), set_difficulty_msg)) - { - error!( - "Failed to send SetDifficulty to downstream {}: {:?}", - downstream_id, e - ); - } else { - debug!( - "Sent SetDifficulty to downstream {} (vardiff disabled)", - downstream_id - ); - } - } - } - } - } - - /// Sends set_difficulty to the specific downstream associated with a channel (non-aggregated - /// mode). - /// Used only when vardiff is disabled. - async fn send_set_difficulty_to_specific_downstream(&self, channel_id: u32, target: Target) { - let affected_downstream = self.sv1_server_data.super_safe_lock(|data| { - data.downstreams - .iter() - .find_map(|(downstream_id, downstream)| { - downstream.downstream_data.super_safe_lock(|d| { - if d.channel_id == Some(channel_id) { - Some((*downstream_id, downstream.clone())) - } else { - None - } - }) - }) - }); - - if let Some((downstream_id, downstream)) = affected_downstream { - // Update the downstream's target - _ = downstream.downstream_data.safe_lock(|d| { - d.set_upstream_target(target); - d.set_pending_target(target); - }); - - // Send set_difficulty message - if let Ok(set_difficulty_msg) = build_sv1_set_difficulty_from_sv2_target(target) { - if let Err(e) = self - .sv1_server_channel_state - .sv1_server_to_downstream_sender - .send((channel_id, Some(downstream_id), set_difficulty_msg)) - { - error!( - "Failed to send SetDifficulty to downstream {}: {:?}", - downstream_id, e - ); - } else { - debug!( - "Sent SetDifficulty to downstream {} for channel {} (vardiff disabled)", - downstream_id, channel_id - ); - } - } - } else { - warn!( - "No downstream found for channel {} when vardiff disabled", - channel_id - ); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::config::{DownstreamDifficultyConfig, TranslatorConfig, Upstream}; - use async_channel::unbounded; - use std::{collections::HashMap, str::FromStr}; - use stratum_apps::key_utils::Secp256k1PublicKey; - - fn create_test_config() -> TranslatorConfig { - let pubkey_str = "9bDuixKmZqAJnrmP746n8zU1wyAQRrus7th9dxnkPg6RzQvCnan"; - let pubkey = Secp256k1PublicKey::from_str(pubkey_str).unwrap(); - - let upstream = Upstream::new("127.0.0.1".to_string(), 4444, pubkey); - let difficulty_config = DownstreamDifficultyConfig::new(100.0, 5.0, true); - - TranslatorConfig::new( - vec![upstream], - "0.0.0.0".to_string(), // downstream_address - 3333, // downstream_port - difficulty_config, // downstream_difficulty_config - 2, // max_supported_version - 1, // min_supported_version - 4, // downstream_extranonce2_size - "test_user".to_string(), - true, // aggregate_channels - ) - } - - fn create_test_sv1_server() -> Sv1Server { - let (cm_sender, _cm_receiver) = unbounded(); - let (_downstream_sender, cm_receiver) = unbounded(); - let config = create_test_config(); - let addr = "127.0.0.1:3333".parse().unwrap(); - - Sv1Server::new(addr, cm_receiver, cm_sender, config) - } - - #[test] - fn test_sv1_server_creation() { - let server = create_test_sv1_server(); - - assert_eq!(server.shares_per_minute, 5.0); - assert_eq!(server.listener_addr.ip().to_string(), "127.0.0.1"); - assert_eq!(server.listener_addr.port(), 3333); - assert_eq!(server.config.user_identity, "test_user"); - assert!(server.config.aggregate_channels); - } - - #[test] - fn test_sv1_server_aggregated_config() { - let mut config = create_test_config(); - config.aggregate_channels = true; - config.downstream_difficulty_config.enable_vardiff = true; - - let (cm_sender, _cm_receiver) = unbounded(); - let (_downstream_sender, cm_receiver) = unbounded(); - let addr = "127.0.0.1:3333".parse().unwrap(); - - let server = Sv1Server::new(addr, cm_receiver, cm_sender, config); - - assert!(server.config.aggregate_channels); - assert!(server.config.downstream_difficulty_config.enable_vardiff); - } - - #[test] - fn test_sv1_server_non_aggregated_config() { - let mut config = create_test_config(); - config.aggregate_channels = false; - config.downstream_difficulty_config.enable_vardiff = false; - - let (cm_sender, _cm_receiver) = unbounded(); - let (_downstream_sender, cm_receiver) = unbounded(); - let addr = "127.0.0.1:3333".parse().unwrap(); - - let server = Sv1Server::new(addr, cm_receiver, cm_sender, config); - - assert!(!server.config.aggregate_channels); - assert!(!server.config.downstream_difficulty_config.enable_vardiff); - } - - #[test] - fn test_get_downstream_basic() { - let downstreams = HashMap::new(); - - // Test non-existing downstream - let not_found = Sv1Server::get_downstream(999, downstreams); - assert!(not_found.is_none()); - } - - #[tokio::test] - async fn test_send_set_difficulty_to_all_downstreams_empty() { - let server = create_test_sv1_server(); - let target: Target = hash_rate_to_target(200.0, 5.0).unwrap(); - - // Test with empty downstreams - server.send_set_difficulty_to_all_downstreams(target).await; - - // Should not crash with empty downstreams - } - - #[tokio::test] - async fn test_send_set_difficulty_to_specific_downstream_not_found() { - let server = create_test_sv1_server(); - let target: Target = hash_rate_to_target(200.0, 5.0).unwrap(); - let channel_id = 1u32; - - // Test with no downstreams - server - .send_set_difficulty_to_specific_downstream(channel_id, target) - .await; - - // Should not crash when no downstreams are found - } - - #[tokio::test] - async fn test_handle_set_target_without_vardiff_aggregated() { - let mut config = create_test_config(); - config.downstream_difficulty_config.enable_vardiff = false; - config.aggregate_channels = true; - - let (cm_sender, _cm_receiver) = unbounded(); - let (_downstream_sender, cm_receiver) = unbounded(); - let addr = "127.0.0.1:3333".parse().unwrap(); - - let server = Sv1Server::new(addr, cm_receiver, cm_sender, config); - let target: Target = hash_rate_to_target(200.0, 5.0).unwrap(); - - let set_target = SetTarget { - channel_id: 1, - maximum_target: target.to_le_bytes().into(), - }; - - // Test should not panic and should handle the message - server.handle_set_target_without_vardiff(set_target).await; - } - - #[tokio::test] - async fn test_handle_set_target_without_vardiff_non_aggregated() { - let mut config = create_test_config(); - config.downstream_difficulty_config.enable_vardiff = false; - config.aggregate_channels = false; - - let (cm_sender, _cm_receiver) = unbounded(); - let (_downstream_sender, cm_receiver) = unbounded(); - let addr = "127.0.0.1:3333".parse().unwrap(); - - let server = Sv1Server::new(addr, cm_receiver, cm_sender, config); - let target: Target = hash_rate_to_target(200.0, 5.0).unwrap(); - - let set_target = SetTarget { - channel_id: 1, - maximum_target: target.to_le_bytes().into(), - }; - - // Test should not panic and should handle the message - server.handle_set_target_without_vardiff(set_target).await; - } - - #[test] - fn test_sv1_server_counters() { - let server = create_test_sv1_server(); - - // Test initial values - assert_eq!(server.miner_counter.load(Ordering::SeqCst), 0); - assert_eq!(server.sequence_counter.load(Ordering::SeqCst), 0); - - // Test incrementing - let miner_id = server.miner_counter.fetch_add(1, Ordering::SeqCst); - assert_eq!(miner_id, 0); - assert_eq!(server.miner_counter.load(Ordering::SeqCst), 1); - - let seq_id = server.sequence_counter.fetch_add(1, Ordering::SeqCst); - assert_eq!(seq_id, 0); - assert_eq!(server.sequence_counter.load(Ordering::SeqCst), 1); - } - - #[test] - fn test_sv1_server_clean_job_flag() { - let server = create_test_sv1_server(); - - // Test initial value - assert!(server.clean_job.load(Ordering::SeqCst)); - - // Test setting to false - server.clean_job.store(false, Ordering::SeqCst); - assert!(!server.clean_job.load(Ordering::SeqCst)); - - // Test setting back to true - server.clean_job.store(true, Ordering::SeqCst); - assert!(server.clean_job.load(Ordering::SeqCst)); - } -} diff --git a/roles/translator/src/lib/sv2/channel_manager/channel.rs b/roles/translator/src/lib/sv2/channel_manager/channel.rs deleted file mode 100644 index fc95a2c59a..0000000000 --- a/roles/translator/src/lib/sv2/channel_manager/channel.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::sv2::upstream::upstream::EitherFrame; -use async_channel::{Receiver, Sender}; -use stratum_apps::stratum_core::parsers_sv2::Mining; -use tracing::debug; - -#[derive(Clone, Debug)] -pub struct ChannelState { - pub upstream_sender: Sender, - pub upstream_receiver: Receiver, - pub sv1_server_sender: Sender>, - pub sv1_server_receiver: Receiver>, -} - -impl ChannelState { - pub fn new( - upstream_sender: Sender, - upstream_receiver: Receiver, - sv1_server_sender: Sender>, - sv1_server_receiver: Receiver>, - ) -> Self { - Self { - upstream_sender, - upstream_receiver, - sv1_server_sender, - sv1_server_receiver, - } - } - - pub fn drop(&self) { - debug!("Dropping channel manager channels"); - self.upstream_receiver.close(); - self.upstream_sender.close(); - self.sv1_server_receiver.close(); - self.sv1_server_sender.close(); - } -} diff --git a/roles/translator/src/lib/sv2/channel_manager/channel_manager.rs b/roles/translator/src/lib/sv2/channel_manager/channel_manager.rs deleted file mode 100644 index e242b32a4e..0000000000 --- a/roles/translator/src/lib/sv2/channel_manager/channel_manager.rs +++ /dev/null @@ -1,864 +0,0 @@ -use crate::{ - error::TproxyError, - status::{handle_error, Status, StatusSender}, - sv2::{ - channel_manager::{ - channel::ChannelState, - data::{ChannelManagerData, ChannelMode}, - }, - upstream::upstream::{EitherFrame, Message, StdFrame}, - }, - task_manager::TaskManager, - utils::{into_static, ShutdownMessage}, -}; -use async_channel::{Receiver, Sender}; -use std::sync::{Arc, RwLock}; -use stratum_apps::{ - custom_mutex::Mutex, - stratum_core::{ - channels_sv2::client::extended::ExtendedChannel, - framing_sv2::framing::Frame, - handlers_sv2::HandleMiningMessagesFromServerAsync, - mining_sv2::OpenExtendedMiningChannelSuccess, - parsers_sv2::{AnyMessage, Mining}, - }, -}; -use tokio::sync::{broadcast, mpsc}; -use tracing::{debug, error, info, warn}; - -/// Extra bytes allocated for translator search space in aggregated mode. -/// This allows the translator to manage multiple downstream connections -/// by allocating unique extranonce prefixes to each downstream. -const AGGREGATED_MODE_TRANSLATOR_SEARCH_SPACE_BYTES: usize = 4; - -/// Type alias for SV2 mining messages with static lifetime -pub type Sv2Message = Mining<'static>; - -/// Manages SV2 channels and message routing between upstream and downstream. -/// -/// The ChannelManager serves as the central component that bridges SV2 upstream -/// connections with SV1 downstream connections. It handles: -/// - SV2 channel lifecycle management (open, close, error handling) -/// - Message translation and routing between protocols -/// - Extranonce management for aggregated vs non-aggregated modes -/// - Share submission processing and validation -/// - Job distribution to downstream connections -/// -/// The manager supports two operational modes: -/// - Aggregated: All downstream connections share a single extended channel -/// - Non-aggregated: Each downstream connection gets its own extended channel -/// -/// This design allows the translator to efficiently manage multiple mining -/// connections while maintaining proper isolation and state management. -#[derive(Debug, Clone)] -pub struct ChannelManager { - pub channel_state: ChannelState, - pub channel_manager_data: Arc>, -} - -impl ChannelManager { - /// Creates a new ChannelManager instance. - /// - /// # Arguments - /// * `upstream_sender` - Channel to send messages to upstream - /// * `upstream_receiver` - Channel to receive messages from upstream - /// * `sv1_server_sender` - Channel to send messages to SV1 server - /// * `sv1_server_receiver` - Channel to receive messages from SV1 server - /// * `mode` - Operating mode (Aggregated or NonAggregated) - /// - /// # Returns - /// A new ChannelManager instance ready to handle message routing - pub fn new( - upstream_sender: Sender, - upstream_receiver: Receiver, - sv1_server_sender: Sender>, - sv1_server_receiver: Receiver>, - mode: ChannelMode, - ) -> Self { - let channel_state = ChannelState::new( - upstream_sender, - upstream_receiver, - sv1_server_sender, - sv1_server_receiver, - ); - let channel_manager_data = Arc::new(Mutex::new(ChannelManagerData::new(mode))); - Self { - channel_state, - channel_manager_data, - } - } - - /// Spawns and runs the main channel manager task loop. - /// - /// This method creates an async task that handles all message routing for the - /// channel manager. The task runs a select loop that processes: - /// - Shutdown signals for graceful termination - /// - Messages from upstream SV2 server - /// - Messages from downstream SV1 server - /// - /// The task continues running until a shutdown signal is received or an - /// unrecoverable error occurs. It ensures proper cleanup of resources - /// and error reporting. - /// - /// # Arguments - /// * `notify_shutdown` - Broadcast channel for receiving shutdown signals - /// * `shutdown_complete_tx` - Channel to signal when shutdown is complete - /// * `status_sender` - Channel for sending status updates and errors - /// * `task_manager` - Manager for tracking spawned tasks - pub async fn run_channel_manager_tasks( - self: Arc, - notify_shutdown: broadcast::Sender, - shutdown_complete_tx: mpsc::Sender<()>, - status_sender: Sender, - task_manager: Arc, - ) { - let mut shutdown_rx = notify_shutdown.subscribe(); - let status_sender = StatusSender::ChannelManager(status_sender); - task_manager.spawn(async move { - loop { - tokio::select! { - message = shutdown_rx.recv() => { - match message { - Ok(ShutdownMessage::ShutdownAll) => { - info!("ChannelManager: received shutdown signal."); - break; - } - Ok(ShutdownMessage::UpstreamReconnectedResetAndShutdownDownstreams) => { - info!("ChannelManager: upstream reconnected, resetting channel state."); - self.channel_manager_data.super_safe_lock(|data| { - data.reset_for_upstream_reconnection(); - }); - // Note: DownstreamShutdownAll handling is done by SV1Server separately - } - Ok(_) => { - // Ignore other shutdown message types - } - Err(e) => { - // Handle channel lag gracefully - don't shutdown on lag errors - if let tokio::sync::broadcast::error::RecvError::Lagged(_) = e { - warn!("ChannelManager: broadcast channel lagged, continuing: {e}"); - } else { - error!("ChannelManager: failed to receive shutdown signal: {e}"); - break; - } - } - } - } - res = Self::handle_upstream_message(self.clone()) => { - if let Err(e) = res { - handle_error(&status_sender, e).await; - break; - } - }, - res = Self::handle_downstream_message(self.clone()) => { - if let Err(e) = res { - handle_error(&status_sender, e).await; - break; - } - }, - else => { - warn!("All channel manager message streams closed. Exiting..."); - break; - } - } - } - - self.channel_state.drop(); - drop(shutdown_complete_tx); - warn!("ChannelManager: unified message loop exited."); - }); - } - - /// Handles messages received from the upstream SV2 server. - /// - /// This method processes SV2 messages from upstream and routes them appropriately: - /// - Mining messages: Processed through the roles logic and forwarded to SV1 server - /// - Channel responses: Handled to manage channel lifecycle - /// - Job notifications: Converted and distributed to downstream connections - /// - Error messages: Logged and handled appropriately - /// - /// The method implements the core SV2 protocol logic for channel management, - /// including handling both aggregated and non-aggregated channel modes. - /// - /// # Returns - /// * `Ok(())` - Message processed successfully - /// * `Err(TproxyError)` - Error processing the message - pub async fn handle_upstream_message(self: Arc) -> Result<(), TproxyError> { - let mut channel_manager = self.get_channel_manager(); - let message = self - .channel_state - .upstream_receiver - .recv() - .await - .map_err(TproxyError::ChannelErrorReceiver)?; - - let Frame::Sv2(mut frame) = message else { - warn!("Received non-SV2 frame from upstream"); - return Ok(()); - }; - - let header = frame.get_header().ok_or_else(|| { - error!("Missing header in SV2 frame"); - TproxyError::General("Missing frame header".into()) - })?; - - let message_type = header.msg_type(); - let mut payload = frame.payload().to_vec(); - - let message: AnyMessage<'_> = into_static( - (message_type, payload.as_mut_slice()) - .try_into() - .map_err(|e| { - error!("Failed to parse upstream frame into AnyMessage: {:?}", e); - TproxyError::General("Failed to parse AnyMessage".into()) - })?, - )?; - - match message { - Message::Mining(_) => { - channel_manager - .handle_mining_message_frame_from_server(None, message_type, &mut payload) - .await?; - } - _ => { - warn!("Unhandled upstream message type: {:?}", message); - } - } - - Ok(()) - } - - /// Handles messages received from the downstream SV1 server. - /// - /// This method processes requests from the SV1 server, primarily: - /// - OpenExtendedMiningChannel: Sets up new SV2 channels for downstream connections - /// - SubmitSharesExtended: Processes share submissions from miners - /// - /// For channel opening, the method handles both aggregated and non-aggregated modes: - /// - Aggregated: Creates extended channels using extranonce prefixes - /// - Non-aggregated: Opens individual extended channels with the upstream for each downstream - /// - /// Share submissions are validated, processed through the channel logic, - /// and forwarded to the upstream server with appropriate extranonce handling. - /// - /// # Returns - /// * `Ok(())` - Message processed successfully - /// * `Err(TproxyError)` - Error processing the message - pub async fn handle_downstream_message(self: Arc) -> Result<(), TproxyError> { - let message = self - .channel_state - .sv1_server_receiver - .recv() - .await - .map_err(TproxyError::ChannelErrorReceiver)?; - match message { - Mining::OpenExtendedMiningChannel(m) => { - let mut open_channel_msg = m.clone(); - let mut user_identity = std::str::from_utf8(m.user_identity.as_ref()) - .map(|s| s.to_string()) - .unwrap_or_else(|_| "unknown".to_string()); - let hashrate = m.nominal_hash_rate; - let min_extranonce_size = m.min_extranonce_size as usize; - let mode = self - .channel_manager_data - .super_safe_lock(|c| c.mode.clone()); - - if mode == ChannelMode::Aggregated { - if self - .channel_manager_data - .super_safe_lock(|c| c.upstream_extended_channel.is_some()) - { - // We already have the unique channel open and so we create a new - // extranonce prefix and we send the - // OpenExtendedMiningChannelSuccess message directly to the sv1 - // server - let target = self.channel_manager_data.super_safe_lock(|c| { - *c.upstream_extended_channel - .as_ref() - .unwrap() - .read() - .unwrap() - .get_target() - }); - let new_extranonce_prefix = - self.channel_manager_data.super_safe_lock(|c| { - c.extranonce_prefix_factory - .as_ref() - .unwrap() - .safe_lock(|e| { - e.next_prefix_extended( - open_channel_msg.min_extranonce_size.into(), - ) - }) - .ok() - .and_then(|r| r.ok()) - }); - let new_extranonce_size = self.channel_manager_data.super_safe_lock(|c| { - c.extranonce_prefix_factory - .as_ref() - .unwrap() - .safe_lock(|e| e.get_range2_len()) - .unwrap() - }); - if let Some(new_extranonce_prefix) = new_extranonce_prefix { - if new_extranonce_size >= open_channel_msg.min_extranonce_size as usize - { - let next_channel_id = - self.channel_manager_data.super_safe_lock(|c| { - c.extended_channels.keys().max().unwrap_or(&0) + 1 - }); - let new_downstream_extended_channel = ExtendedChannel::new( - next_channel_id, - user_identity.clone(), - new_extranonce_prefix - .clone() - .into_b032() - .into_static() - .to_vec(), - target, - hashrate, - true, - new_extranonce_size as u16, - ); - self.channel_manager_data.super_safe_lock(|c| { - c.extended_channels.insert( - next_channel_id, - Arc::new(RwLock::new(new_downstream_extended_channel)), - ); - }); - let success_message = Mining::OpenExtendedMiningChannelSuccess( - OpenExtendedMiningChannelSuccess { - request_id: open_channel_msg.request_id, - channel_id: next_channel_id, - target: target.to_le_bytes().into(), - extranonce_size: new_extranonce_size as u16, - extranonce_prefix: new_extranonce_prefix.clone().into(), - }, - ); - - self.channel_state - .sv1_server_sender - .send(success_message) - .await - .map_err(|e| { - error!( - "Failed to send open channel message to upstream: {:?}", - e - ); - TproxyError::ChannelErrorSender - })?; - // get the last active job from the upstream extended channel - let last_active_job = - self.channel_manager_data.super_safe_lock(|c| { - c.upstream_extended_channel - .as_ref() - .and_then(|ch| ch.read().ok()) - .and_then(|ch| ch.get_active_job().map(|j| j.0.clone())) - }); - - // get the last chain tip from the upstream extended channel - let last_chain_tip = - self.channel_manager_data.super_safe_lock(|c| { - c.upstream_extended_channel - .as_ref() - .and_then(|ch| ch.read().ok()) - .and_then(|ch| ch.get_chain_tip().cloned()) - }); - // update the downstream channel with the active job and the chain - // tip - if let Some(mut job) = last_active_job { - if let Some(last_chain_tip) = last_chain_tip { - // update the downstream channel with the active chain tip - self.channel_manager_data.super_safe_lock(|c| { - if let Some(ch) = - c.extended_channels.get(&next_channel_id) - { - ch.write() - .unwrap() - .set_chain_tip(last_chain_tip.clone()); - } - }); - } - job.channel_id = next_channel_id; - // update the downstream channel with the active job - self.channel_manager_data.super_safe_lock(|c| { - if let Some(ch) = c.extended_channels.get(&next_channel_id) - { - let _ = ch - .write() - .unwrap() - .on_new_extended_mining_job(job.clone()); - } - }); - - self.channel_state - .sv1_server_sender - .send(Mining::NewExtendedMiningJob(job.clone())) - .await - .map_err(|e| { - error!("Failed to send last new extended mining job to upstream: {:?}", e); - TproxyError::ChannelErrorSender - })?; - } - } - } - return Ok(()); - } else { - // We don't have the unique channel open yet and so we send the - // OpenExtendedMiningChannel message to the upstream - // Before doing that we need to truncate the user identity at the - // first dot and append .translator-proxy - // Truncate at the first dot and append .translator-proxy - let translator_identity = if let Some(dot_index) = user_identity.find('.') { - format!("{}.translator-proxy", &user_identity[..dot_index]) - } else { - format!("{user_identity}.translator-proxy") - }; - user_identity = translator_identity; - open_channel_msg.user_identity = - user_identity.as_bytes().to_vec().try_into().unwrap(); - } - } - // In aggregated mode, add extra bytes for translator search space allocation - let upstream_min_extranonce_size = self.channel_manager_data.super_safe_lock(|c| { - if c.mode == ChannelMode::Aggregated { - min_extranonce_size + AGGREGATED_MODE_TRANSLATOR_SEARCH_SPACE_BYTES - } else { - min_extranonce_size - } - }); - - // Update the message with the adjusted extranonce size for upstream - open_channel_msg.min_extranonce_size = upstream_min_extranonce_size as u16; - - // Store the user identity, hashrate, and original downstream extranonce size - self.channel_manager_data.super_safe_lock(|c| { - c.pending_channels.insert( - open_channel_msg.request_id, - (user_identity, hashrate, min_extranonce_size), - ); - }); - - info!( - "Sending OpenExtendedMiningChannel message to upstream: {:?}", - open_channel_msg - ); - - let frame = StdFrame::try_from(Message::Mining(Mining::OpenExtendedMiningChannel( - open_channel_msg, - ))) - .map_err(TproxyError::ParserError)?; - self.channel_state - .upstream_sender - .send(frame.into()) - .await - .map_err(|e| { - error!("Failed to send open channel message to upstream: {:?}", e); - TproxyError::ChannelErrorSender - })?; - } - Mining::SubmitSharesExtended(mut m) => { - let value = self.channel_manager_data.super_safe_lock(|c| { - let extended_channel = c.extended_channels.get(&m.channel_id); - if let Some(extended_channel) = extended_channel { - let channel = extended_channel.write(); - if let Ok(mut channel) = channel { - return Some(( - channel.validate_share(m.clone()), - channel.get_share_accounting().clone(), - )); - } - } - None - }); - if let Some((Ok(_result), _share_accounting)) = value { - let mode = self - .channel_manager_data - .super_safe_lock(|c| c.mode.clone()); - - if mode == ChannelMode::Aggregated - && self - .channel_manager_data - .super_safe_lock(|c| c.upstream_extended_channel.is_some()) - { - let upstream_extended_channel_id = - self.channel_manager_data.super_safe_lock(|c| { - let upstream_extended_channel = c - .upstream_extended_channel - .as_ref() - .unwrap() - .read() - .unwrap(); - upstream_extended_channel.get_channel_id() - }); - - // In aggregated mode, use a single sequence counter for all valid shares - m.sequence_number = self.channel_manager_data.super_safe_lock(|c| { - c.next_share_sequence_number(upstream_extended_channel_id) - }); - // Get the downstream channel's extranonce prefix (contains - // upstream prefix + translator proxy prefix) - let downstream_extranonce_prefix = - self.channel_manager_data.super_safe_lock(|c| { - c.extended_channels.get(&m.channel_id).map(|channel| { - channel.read().unwrap().get_extranonce_prefix().clone() - }) - }); - // Get the length of the upstream prefix (range0) - let range0_len = self.channel_manager_data.super_safe_lock(|c| { - c.extranonce_prefix_factory - .as_ref() - .unwrap() - .safe_lock(|e| e.get_range0_len()) - .unwrap() - }); - if let Some(downstream_extranonce_prefix) = downstream_extranonce_prefix { - // Skip the upstream prefix (range0) and take the remaining - // bytes (translator proxy prefix) - let translator_prefix = &downstream_extranonce_prefix[range0_len..]; - // Create new extranonce: translator proxy prefix + miner's - // extranonce - let mut new_extranonce = translator_prefix.to_vec(); - new_extranonce.extend_from_slice(m.extranonce.as_ref()); - // Replace the original extranonce with the modified one for - // upstream submission - m.extranonce = new_extranonce.try_into()?; - } - // We need to set the channel id to the upstream extended - // channel id - m.channel_id = upstream_extended_channel_id; - } else { - // In non-aggregated mode, each downstream channel has its own sequence - // counter - m.sequence_number = self - .channel_manager_data - .super_safe_lock(|c| c.next_share_sequence_number(m.channel_id)); - - // Check if we have a per-channel factory for extranonce adjustment - let channel_factory = self.channel_manager_data.super_safe_lock(|c| { - c.extranonce_factories - .as_ref() - .and_then(|factories| factories.get(&m.channel_id).cloned()) - }); - - if let Some(factory) = channel_factory { - // We need to adjust the extranonce for this channel - let downstream_extranonce_prefix = - self.channel_manager_data.super_safe_lock(|c| { - c.extended_channels.get(&m.channel_id).map(|channel| { - channel.read().unwrap().get_extranonce_prefix().clone() - }) - }); - let range0_len = factory - .safe_lock(|e| e.get_range0_len()) - .expect("Failed to access extranonce factory range - this should not happen"); - if let Some(downstream_extranonce_prefix) = downstream_extranonce_prefix - { - // Skip the upstream prefix (range0) and take the remaining - // bytes (translator proxy prefix) - let translator_prefix = &downstream_extranonce_prefix[range0_len..]; - // Create new extranonce: translator proxy prefix + miner's - // extranonce - let mut new_extranonce = translator_prefix.to_vec(); - new_extranonce.extend_from_slice(m.extranonce.as_ref()); - // Replace the original extranonce with the modified one for - // upstream submission - m.extranonce = new_extranonce.try_into()?; - } - } - } - - info!( - "SubmitSharesExtended: valid share, forwarding it to upstream | channel_id: {}, sequence_number: {} โ˜‘๏ธ", - m.channel_id, m.sequence_number - ); - let frame: StdFrame = Message::Mining(Mining::SubmitSharesExtended(m)) - .try_into() - .map_err(TproxyError::ParserError)?; - let frame: EitherFrame = frame.into(); - self.channel_state - .upstream_sender - .send(frame) - .await - .map_err(|e| { - error!("Error while sending message to upstream: {e:?}"); - TproxyError::ChannelErrorSender - })?; - } - } - Mining::UpdateChannel(mut m) => { - debug!("Received UpdateChannel from SV1Server: {:?}", m); - let mode = self - .channel_manager_data - .super_safe_lock(|c| c.mode.clone()); - - if mode == ChannelMode::Aggregated { - let upstream_extended_channel_id = - self.channel_manager_data.super_safe_lock(|c| { - c.upstream_extended_channel - .as_ref() - .unwrap() - .read() - .unwrap() - .get_channel_id() - }); - // We need to set the channel id to the upstream extended - // channel id - m.channel_id = upstream_extended_channel_id; - } - info!( - "Sending UpdateChannel message to upstream for channel_id: {:?}", - m.channel_id - ); - // Forward UpdateChannel message to upstream - let frame = StdFrame::try_from(Message::Mining(Mining::UpdateChannel(m))) - .map_err(TproxyError::ParserError)?; - - self.channel_state - .upstream_sender - .send(frame.into()) - .await - .map_err(|e| { - error!("Failed to send UpdateChannel message to upstream: {:?}", e); - TproxyError::ChannelErrorSender - })?; - } - Mining::CloseChannel(m) => { - debug!("Received CloseChannel from SV1Server: {m}"); - let frame = StdFrame::try_from(Message::Mining(Mining::CloseChannel(m))) - .map_err(TproxyError::ParserError)?; - - self.channel_state - .upstream_sender - .send(frame.into()) - .await - .map_err(|e| { - error!("Failed to send UpdateChannel message to upstream: {:?}", e); - TproxyError::ChannelErrorSender - })?; - } - _ => { - warn!("Unhandled downstream message: {:?}", message); - } - } - - Ok(()) - } - - pub fn get_channel_manager(&self) -> ChannelManager { - ChannelManager { - channel_manager_data: self.channel_manager_data.clone(), - channel_state: self.channel_state.clone(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::sv2::channel_manager::data::ChannelMode; - use async_channel::unbounded; - use stratum_apps::stratum_core::mining_sv2::{ - OpenExtendedMiningChannel, SubmitSharesExtended, UpdateChannel, - }; - - fn create_test_channel_manager(mode: ChannelMode) -> ChannelManager { - let (upstream_sender, _upstream_receiver) = unbounded(); - let (_upstream_sender2, upstream_receiver) = unbounded(); - let (sv1_server_sender, _sv1_server_receiver) = unbounded(); - let (_sv1_server_sender2, sv1_server_receiver) = unbounded(); - - ChannelManager::new( - upstream_sender, - upstream_receiver, - sv1_server_sender, - sv1_server_receiver, - mode, - ) - } - - #[test] - fn test_channel_manager_creation_aggregated() { - let manager = create_test_channel_manager(ChannelMode::Aggregated); - - let mode = manager - .channel_manager_data - .super_safe_lock(|data| data.mode.clone()); - assert_eq!(mode, ChannelMode::Aggregated); - } - - #[test] - fn test_channel_manager_creation_non_aggregated() { - let manager = create_test_channel_manager(ChannelMode::NonAggregated); - - let mode = manager - .channel_manager_data - .super_safe_lock(|data| data.mode.clone()); - assert_eq!(mode, ChannelMode::NonAggregated); - } - - #[test] - fn test_get_channel_manager() { - let manager = create_test_channel_manager(ChannelMode::Aggregated); - let cloned_manager = manager.get_channel_manager(); - - // Should be a different instance but share the same data - let original_mode = manager - .channel_manager_data - .super_safe_lock(|data| data.mode.clone()); - let cloned_mode = cloned_manager - .channel_manager_data - .super_safe_lock(|data| data.mode.clone()); - - assert_eq!(original_mode, cloned_mode); - } - - #[tokio::test] - async fn test_handle_downstream_open_channel_message() { - let manager = create_test_channel_manager(ChannelMode::NonAggregated); - - // Create an OpenExtendedMiningChannel message - let open_channel = OpenExtendedMiningChannel { - request_id: 1, - user_identity: "test_user".as_bytes().to_vec().try_into().unwrap(), - nominal_hash_rate: 1000.0, - max_target: vec![0xFFu8; 32].try_into().unwrap(), - min_extranonce_size: 4, - }; - - // Store the pending channel information - manager.channel_manager_data.super_safe_lock(|data| { - data.pending_channels - .insert(1, ("test_user".to_string(), 1000.0, 4)); - }); - - // Test that the message can be handled without panicking - // In a real test environment, we would need to mock the upstream sender - // For now, we just verify the channel manager can process the message type - let mining_message = Mining::OpenExtendedMiningChannel(open_channel); - - // Verify the message can be processed (would normally be sent to upstream) - match mining_message { - Mining::OpenExtendedMiningChannel(msg) => { - assert_eq!(msg.request_id, 1); - assert_eq!(msg.nominal_hash_rate, 1000.0); - assert_eq!(msg.min_extranonce_size, 4); - } - _ => panic!("Expected OpenExtendedMiningChannel"), - } - } - - #[tokio::test] - async fn test_handle_downstream_submit_shares_message() { - let _manager = create_test_channel_manager(ChannelMode::NonAggregated); - - // Create a SubmitSharesExtended message - let submit_shares = SubmitSharesExtended { - channel_id: 1, - sequence_number: 100, - job_id: 42, - nonce: 0x12345678, - ntime: 1234567890, - version: 0x20000000, - extranonce: vec![0x01, 0x02, 0x03, 0x04].try_into().unwrap(), - }; - - // Test that the message can be handled - let mining_message = Mining::SubmitSharesExtended(submit_shares); - - // Verify the message structure - match mining_message { - Mining::SubmitSharesExtended(msg) => { - assert_eq!(msg.channel_id, 1); - assert_eq!(msg.sequence_number, 100); - assert_eq!(msg.job_id, 42); - assert_eq!(msg.nonce, 0x12345678); - } - _ => panic!("Expected SubmitSharesExtended"), - } - } - - #[tokio::test] - async fn test_handle_downstream_update_channel_message() { - let _manager = create_test_channel_manager(ChannelMode::Aggregated); - - // Create an UpdateChannel message - let update_channel = UpdateChannel { - channel_id: 1, - nominal_hash_rate: 2000.0, - maximum_target: [0xFFu8; 32].try_into().unwrap(), - }; - - // Test that the message can be handled - let mining_message = Mining::UpdateChannel(update_channel); - - // Verify the message structure - match mining_message { - Mining::UpdateChannel(msg) => { - assert_eq!(msg.channel_id, 1); - assert_eq!(msg.nominal_hash_rate, 2000.0); - } - _ => panic!("Expected UpdateChannel"), - } - } - - #[test] - fn test_channel_manager_debug() { - let manager = create_test_channel_manager(ChannelMode::Aggregated); - - // Test that Debug trait is implemented - let debug_str = format!("{:?}", manager); - assert!(debug_str.contains("ChannelManager")); - } - - #[test] - fn test_channel_manager_clone() { - let manager = create_test_channel_manager(ChannelMode::Aggregated); - let cloned = manager.clone(); - - // Verify that both managers share the same underlying data - let original_mode = manager - .channel_manager_data - .super_safe_lock(|data| data.mode.clone()); - let cloned_mode = cloned - .channel_manager_data - .super_safe_lock(|data| data.mode.clone()); - - assert_eq!(original_mode, cloned_mode); - } - - #[test] - fn test_channel_manager_data_access() { - let manager = create_test_channel_manager(ChannelMode::NonAggregated); - - // Test that we can access and modify channel manager data - manager.channel_manager_data.super_safe_lock(|data| { - // Add a pending channel - data.pending_channels - .insert(1, ("test".to_string(), 100.0, 4)); - }); - - let has_pending = manager - .channel_manager_data - .super_safe_lock(|data| data.pending_channels.contains_key(&1)); - - assert!(has_pending); - } - - #[test] - fn test_channel_manager_mode_consistency() { - let aggregated_manager = create_test_channel_manager(ChannelMode::Aggregated); - let non_aggregated_manager = create_test_channel_manager(ChannelMode::NonAggregated); - - let agg_mode = aggregated_manager - .channel_manager_data - .super_safe_lock(|data| data.mode.clone()); - let non_agg_mode = non_aggregated_manager - .channel_manager_data - .super_safe_lock(|data| data.mode.clone()); - - assert_eq!(agg_mode, ChannelMode::Aggregated); - assert_eq!(non_agg_mode, ChannelMode::NonAggregated); - assert_ne!(agg_mode, non_agg_mode); - } -} diff --git a/roles/translator/src/lib/sv2/channel_manager/data.rs b/roles/translator/src/lib/sv2/channel_manager/data.rs deleted file mode 100644 index c79bae825b..0000000000 --- a/roles/translator/src/lib/sv2/channel_manager/data.rs +++ /dev/null @@ -1,110 +0,0 @@ -use std::{ - collections::HashMap, - sync::{Arc, RwLock}, -}; -use stratum_apps::{ - custom_mutex::Mutex, - stratum_core::{ - channels_sv2::client::extended::ExtendedChannel, mining_sv2::ExtendedExtranonce, - }, -}; - -/// Defines the operational mode for channel management. -/// -/// The channel manager can operate in two different modes that affect how -/// downstream connections are mapped to upstream SV2 channels: -#[derive(Debug, Clone, PartialEq, serde::Deserialize)] -pub enum ChannelMode { - /// All downstream connections share a single extended SV2 channel. - /// This mode uses extranonce prefix allocation to distinguish between - /// different downstream miners while presenting them as a single entity - /// to the upstream server. This is more efficient for pools with many - /// miners. - Aggregated, - /// Each downstream connection gets its own dedicated extended SV2 channel. - /// This mode provides complete isolation between downstream connections - /// but may be less efficient for large numbers of miners. - NonAggregated, -} - -/// Internal data structure for the ChannelManager. -/// -/// This struct maintains all the state needed for SV2 channel management, -/// including pending channel requests, active channels, and mode-specific -/// data structures like extranonce factories for aggregated mode. -#[derive(Debug, Clone)] -pub struct ChannelManagerData { - /// Store pending channel info by downstream_id: (user_identity, hashrate, - /// downstream_extranonce_len) - pub pending_channels: HashMap, - /// Map of active extended channels by channel ID - pub extended_channels: HashMap>>>, - /// The upstream extended channel used in aggregated mode - pub upstream_extended_channel: Option>>>, - /// Extranonce prefix factory for allocating unique prefixes in aggregated mode - pub extranonce_prefix_factory: Option>>, - /// Current operational mode - pub mode: ChannelMode, - /// Share sequence number counter for tracking valid shares forwarded upstream. - /// In aggregated mode: single counter for all shares going to the upstream channel. - /// In non-aggregated mode: one counter per downstream channel. - pub share_sequence_counters: HashMap, - /// Per-channel extranonce factories for non-aggregated mode when extranonce adjustment is - /// needed - pub extranonce_factories: Option>>>, -} - -impl ChannelManagerData { - /// Creates a new ChannelManagerData instance. - /// - /// # Arguments - /// * `mode` - The operational mode (Aggregated or NonAggregated) - /// - /// # Returns - /// A new ChannelManagerData instance with empty state - pub fn new(mode: ChannelMode) -> Self { - Self { - pending_channels: HashMap::new(), - extended_channels: HashMap::new(), - upstream_extended_channel: None, - extranonce_prefix_factory: None, - mode, - share_sequence_counters: HashMap::new(), - extranonce_factories: None, - } - } - - /// Resets all channel state for upstream reconnection. - /// - /// This method clears all existing channel state that becomes invalid - /// when the upstream connection is lost and reestablished. It preserves - /// the operational mode but clears: - /// - All pending channel requests - /// - All active extended channels - /// - The upstream extended channel - /// - The extranonce prefix factory - /// - /// This ensures that new channels will be properly opened with the - /// newly connected upstream server. - pub fn reset_for_upstream_reconnection(&mut self) { - self.pending_channels.clear(); - self.extended_channels.clear(); - self.upstream_extended_channel = None; - self.extranonce_prefix_factory = None; - self.share_sequence_counters.clear(); - self.extranonce_factories = None; - // Note: we intentionally preserve `mode` as it's a configuration setting - } - - /// Gets the next sequence number for a valid share and increments the counter. - /// - /// The counter_key determines which counter to use: - /// - In aggregated mode: use upstream channel ID (single counter for all shares) - /// - In non-aggregated mode: use downstream channel ID (one counter per channel) - pub fn next_share_sequence_number(&mut self, counter_key: u32) -> u32 { - let counter = self.share_sequence_counters.entry(counter_key).or_insert(1); - let current = *counter; - *counter += 1; - current - } -} diff --git a/roles/translator/src/lib/sv2/channel_manager/message_handler.rs b/roles/translator/src/lib/sv2/channel_manager/message_handler.rs deleted file mode 100644 index bfa714e7cd..0000000000 --- a/roles/translator/src/lib/sv2/channel_manager/message_handler.rs +++ /dev/null @@ -1,528 +0,0 @@ -use std::sync::{Arc, RwLock}; - -use crate::{ - error::TproxyError, - sv2::{channel_manager::ChannelMode, ChannelManager}, - utils::proxy_extranonce_prefix_len, -}; -use stratum_apps::{ - custom_mutex::Mutex, - stratum_core::{ - bitcoin::Target, - channels_sv2::client::extended::ExtendedChannel, - handlers_sv2::{HandleMiningMessagesFromServerAsync, SupportedChannelTypes}, - mining_sv2::{ - CloseChannel, ExtendedExtranonce, Extranonce, NewExtendedMiningJob, NewMiningJob, - OpenExtendedMiningChannelSuccess, OpenMiningChannelError, - OpenStandardMiningChannelSuccess, SetCustomMiningJobError, SetCustomMiningJobSuccess, - SetExtranoncePrefix, SetGroupChannel, SetNewPrevHash, SetTarget, SubmitSharesError, - SubmitSharesSuccess, UpdateChannelError, - MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL_SUCCESS, - MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_ERROR, MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_SUCCESS, - MESSAGE_TYPE_SET_GROUP_CHANNEL, - }, - parsers_sv2::Mining, - }, -}; -use tracing::{debug, error, info, warn}; - -impl HandleMiningMessagesFromServerAsync for ChannelManager { - type Error = TproxyError; - - 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 { - false - } - - async fn handle_open_standard_mining_channel_success( - &mut self, - _server_id: Option, - m: OpenStandardMiningChannelSuccess<'_>, - ) -> Result<(), Self::Error> { - warn!("Received: {}", m); - Err(Self::Error::UnexpectedMessage( - MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL_SUCCESS, - )) - } - - async fn handle_open_extended_mining_channel_success( - &mut self, - _server_id: Option, - m: OpenExtendedMiningChannelSuccess<'_>, - ) -> Result<(), Self::Error> { - // Check if we have the pending channel data, return error if not - let (user_identity, nominal_hashrate, downstream_extranonce_len) = self - .channel_manager_data - .safe_lock(|channel_manager_data| { - channel_manager_data.pending_channels.remove(&m.request_id) - }) - .map_err(|e| { - error!("Failed to lock channel manager data: {:?}", e); - TproxyError::PoisonLock - })? - .ok_or_else(|| { - error!("No pending channel found for request_id: {}", m.request_id); - TproxyError::PendingChannelNotFound(m.request_id) - })?; - - let success = self - .channel_manager_data - .safe_lock(|channel_manager_data| { - info!( - "Received: {}, user_identity: {}, nominal_hashrate: {}", - m, user_identity, nominal_hashrate - ); - let extranonce_prefix = m.extranonce_prefix.clone().into_static().to_vec(); - let target = Target::from_le_bytes(m.target.clone().inner_as_ref().try_into().unwrap()); - let version_rolling = true; // we assume this is always true on extended channels - let extended_channel = ExtendedChannel::new( - m.channel_id, - user_identity.clone(), - extranonce_prefix.clone(), - target, - nominal_hashrate, - version_rolling, - m.extranonce_size, - ); - - // If we are in aggregated mode, we need to create a new extranonce prefix and - // insert the extended channel into the map - if channel_manager_data.mode == ChannelMode::Aggregated { - channel_manager_data.upstream_extended_channel = - Some(Arc::new(RwLock::new(extended_channel.clone()))); - - let upstream_extranonce_prefix: Extranonce = m.extranonce_prefix.clone().into(); - let translator_proxy_extranonce_prefix_len = proxy_extranonce_prefix_len( - m.extranonce_size.into(), - downstream_extranonce_len, - ); - - // range 0 is the extranonce1 from upstream - // range 1 is the extranonce1 added by the tproxy - // range 2 is the extranonce2 used by the miner for rolling (this is the one - // that is used for rolling) - let range_0 = 0..extranonce_prefix.len(); - let range1 = range_0.end..range_0.end + translator_proxy_extranonce_prefix_len; - let range2 = range1.end..range1.end + downstream_extranonce_len; - debug!("\n\nrange_0: {:?}, range1: {:?}, range2: {:?}\n\n", range_0, range1, range2); - let extended_extranonce_factory = ExtendedExtranonce::from_upstream_extranonce( - upstream_extranonce_prefix, - range_0, - range1, - range2, - ) - .expect("Failed to create ExtendedExtranonce from upstream extranonce"); - channel_manager_data.extranonce_prefix_factory = - Some(Arc::new(Mutex::new(extended_extranonce_factory))); - - let factory = channel_manager_data - .extranonce_prefix_factory - .as_ref() - .expect("extranonce_prefix_factory should be set after creation"); - let new_extranonce_size = factory - .safe_lock(|f| f.get_range2_len()) - .expect("extranonce_prefix_factory mutex should not be poisoned") - as u16; - let new_extranonce_prefix = factory - .safe_lock(|f| f.next_prefix_extended(new_extranonce_size as usize)) - .expect("extranonce_prefix_factory mutex should not be poisoned") - .expect("next_prefix_extended should return a value for valid input") - .into_b032(); - let new_downstream_extended_channel = ExtendedChannel::new( - m.channel_id, - user_identity.clone(), - new_extranonce_prefix.clone().into_static().to_vec(), - target, - nominal_hashrate, - true, - new_extranonce_size, - ); - channel_manager_data.extended_channels.insert( - m.channel_id, - Arc::new(RwLock::new(new_downstream_extended_channel)), - ); - let new_open_extended_mining_channel_success = - OpenExtendedMiningChannelSuccess { - request_id: m.request_id, - channel_id: m.channel_id, - extranonce_prefix: new_extranonce_prefix, - extranonce_size: new_extranonce_size, - target: m.target.clone(), - }; - new_open_extended_mining_channel_success.into_static() - } else { - // Non-aggregated mode: check if we need to adjust extranonce size - if m.extranonce_size as usize != downstream_extranonce_len { - // We need to create an extranonce factory to ensure proper extranonce2_size - let upstream_extranonce_prefix: Extranonce = m.extranonce_prefix.clone().into(); - let translator_proxy_extranonce_prefix_len = proxy_extranonce_prefix_len( - m.extranonce_size.into(), - downstream_extranonce_len, - ); - - // range 0 is the extranonce1 from upstream - // range 1 is the extranonce1 added by the tproxy - // range 2 is the extranonce2 used by the miner for rolling - let range_0 = 0..extranonce_prefix.len(); - let range1 = range_0.end..range_0.end + translator_proxy_extranonce_prefix_len; - let range2 = range1.end..range1.end + downstream_extranonce_len; - debug!("\n\nrange_0: {:?}, range1: {:?}, range2: {:?}\n\n", range_0, range1, range2); - // Create the factory - this should succeed if configuration is valid - let extended_extranonce_factory = ExtendedExtranonce::from_upstream_extranonce( - upstream_extranonce_prefix, - range_0, - range1, - range2, - ) - .expect("Failed to create ExtendedExtranonce factory - likely extranonce size configuration issue"); - // Store the factory for this specific channel - let factory = Arc::new(Mutex::new(extended_extranonce_factory)); - let new_extranonce_prefix = factory - .safe_lock(|f| f.next_prefix_extended(downstream_extranonce_len)) - .expect("Failed to access extranonce factory") - .expect("Failed to generate extranonce prefix") - .into_b032(); - // Create channel with the configured extranonce size - let new_downstream_extended_channel = ExtendedChannel::new( - m.channel_id, - user_identity.clone(), - new_extranonce_prefix.clone().into_static().to_vec(), - target, - nominal_hashrate, - true, - downstream_extranonce_len as u16, - ); - channel_manager_data.extended_channels.insert( - m.channel_id, - Arc::new(RwLock::new(new_downstream_extended_channel)), - ); - // Store factory for this channel (we'll need it for share processing) - if channel_manager_data.extranonce_factories.is_none() { - channel_manager_data.extranonce_factories = Some(std::collections::HashMap::new()); - } - if let Some(ref mut factories) = channel_manager_data.extranonce_factories { - factories.insert(m.channel_id, factory); - } - let new_open_extended_mining_channel_success = OpenExtendedMiningChannelSuccess { - request_id: m.request_id, - channel_id: m.channel_id, - extranonce_prefix: new_extranonce_prefix, - extranonce_size: downstream_extranonce_len as u16, - target: m.target.clone(), - }; - new_open_extended_mining_channel_success.into_static() - } else { - // Extranonce size matches, use as-is - channel_manager_data - .extended_channels - .insert(m.channel_id, Arc::new(RwLock::new(extended_channel))); - m.into_static() - } - } - }) - .map_err(|e| { - error!("Failed to lock channel manager data: {:?}", e); - TproxyError::PoisonLock - })?; - - self.channel_state - .sv1_server_sender - .send(Mining::OpenExtendedMiningChannelSuccess(success.clone())) - .await - .map_err(|e| { - error!("Failed to send OpenExtendedMiningChannelSuccess: {:?}", e); - TproxyError::ChannelErrorSender - })?; - - Ok(()) - } - - async fn handle_open_mining_channel_error( - &mut self, - _server_id: Option, - m: OpenMiningChannelError<'_>, - ) -> Result<(), Self::Error> { - warn!("Received: {}", m); - todo!("OpenMiningChannelError not handled yet"); - } - - async fn handle_update_channel_error( - &mut self, - _server_id: Option, - m: UpdateChannelError<'_>, - ) -> Result<(), Self::Error> { - warn!("Received: {}", m); - Ok(()) - } - - async fn handle_close_channel( - &mut self, - _server_id: Option, - m: CloseChannel<'_>, - ) -> Result<(), Self::Error> { - info!("Received: {}", m); - _ = self.channel_manager_data.safe_lock(|channel_data_manager| { - if channel_data_manager.mode == ChannelMode::Aggregated { - if channel_data_manager.upstream_extended_channel.is_some() { - channel_data_manager.upstream_extended_channel = None; - } - } else { - channel_data_manager.extended_channels.remove(&m.channel_id); - } - }); - Ok(()) - } - - async fn handle_set_extranonce_prefix( - &mut self, - _server_id: Option, - m: SetExtranoncePrefix<'_>, - ) -> Result<(), Self::Error> { - warn!("Received: {}", m); - warn!("โš ๏ธ Cannot process SetExtranoncePrefix since set_extranonce is not supported for majority of sv1 clients. Ignoring."); - Ok(()) - } - - async fn handle_submit_shares_success( - &mut self, - _server_id: Option, - m: SubmitSharesSuccess, - ) -> Result<(), Self::Error> { - info!("Received: {} โœ…", m); - Ok(()) - } - - async fn handle_submit_shares_error( - &mut self, - _server_id: Option, - m: SubmitSharesError<'_>, - ) -> Result<(), Self::Error> { - warn!("Received: {} โŒ", m); - Ok(()) - } - - async fn handle_new_mining_job( - &mut self, - _server_id: Option, - m: NewMiningJob<'_>, - ) -> Result<(), Self::Error> { - warn!("Received: {}", m); - warn!("โš ๏ธ Cannot process NewMiningJob since Translator Proxy supports only extended mining jobs. Ignoring."); - Ok(()) - } - - async fn handle_new_extended_mining_job( - &mut self, - _server_id: Option, - m: NewExtendedMiningJob<'_>, - ) -> Result<(), Self::Error> { - info!("Received: {}", m); - let mut m_static = m.clone().into_static(); - _ = self.channel_manager_data.safe_lock(|channel_manage_data| { - if channel_manage_data.mode == ChannelMode::Aggregated { - if let Some(upstream_channel) = &channel_manage_data.upstream_extended_channel { - if let Ok(mut upstream_extended_channel) = upstream_channel.write() { - let _ = - upstream_extended_channel.on_new_extended_mining_job(m_static.clone()); - m_static.channel_id = 0; // this is done so that every aggregated downstream - // will - // receive the NewExtendedMiningJob message - } - } - channel_manage_data - .extended_channels - .iter() - .for_each(|(_, channel)| { - if let Ok(mut channel) = channel.write() { - let _ = channel.on_new_extended_mining_job(m_static.clone()); - } - }); - } else if let Some(channel) = channel_manage_data - .extended_channels - .get(&m_static.channel_id) - { - if let Ok(mut channel) = channel.write() { - let _ = channel.on_new_extended_mining_job(m_static.clone()); - } - } - }); - let job = m_static; - if !job.is_future() { - self.channel_state - .sv1_server_sender - .send(Mining::NewExtendedMiningJob(job)) - .await - .map_err(|e| { - error!("Failed to send immediate NewExtendedMiningJob: {:?}", e); - TproxyError::ChannelErrorSender - })?; - } - Ok(()) - } - - async fn handle_set_new_prev_hash( - &mut self, - _server_id: Option, - m: SetNewPrevHash<'_>, - ) -> Result<(), Self::Error> { - info!("Received: {}", m); - let m_static = m.clone().into_static(); - _ = self.channel_manager_data.safe_lock(|channel_manager_data| { - if channel_manager_data.mode == ChannelMode::Aggregated { - if let Some(upstream_channel) = &channel_manager_data.upstream_extended_channel { - if let Ok(mut upstream_extended_channel) = upstream_channel.write() { - _ = upstream_extended_channel.on_set_new_prev_hash(m_static.clone()); - } - } - channel_manager_data - .extended_channels - .iter() - .for_each(|(_, channel)| { - if let Ok(mut channel) = channel.write() { - _ = channel.on_set_new_prev_hash(m_static.clone()); - } - }); - } else if let Some(channel) = channel_manager_data - .extended_channels - .get(&m_static.channel_id) - { - if let Ok(mut channel) = channel.write() { - _ = channel.on_set_new_prev_hash(m_static.clone()); - } - } - }); - - self.channel_state - .sv1_server_sender - .send(Mining::SetNewPrevHash(m_static.clone())) - .await - .map_err(|e| { - error!("Failed to send SetNewPrevHash: {:?}", e); - TproxyError::ChannelErrorSender - })?; - - let mode = self - .channel_manager_data - .super_safe_lock(|c| c.mode.clone()); - - let active_job = if mode == ChannelMode::Aggregated { - self.channel_manager_data.super_safe_lock(|c| { - c.upstream_extended_channel - .as_ref() - .and_then(|ch| ch.read().ok()) - .and_then(|ch| ch.get_active_job().map(|j| j.0.clone())) - }) - } else { - self.channel_manager_data.super_safe_lock(|c| { - c.extended_channels - .get(&m.channel_id) - .and_then(|ch| ch.read().ok()) - .and_then(|ch| ch.get_active_job().map(|j| j.0.clone())) - }) - }; - - if let Some(mut job) = active_job { - if mode == ChannelMode::Aggregated { - job.channel_id = 0; - } - self.channel_state - .sv1_server_sender - .send(Mining::NewExtendedMiningJob(job)) - .await - .map_err(|e| { - error!("Failed to send NewExtendedMiningJob: {:?}", e); - TproxyError::ChannelErrorSender - })?; - } - Ok(()) - } - - async fn handle_set_custom_mining_job_success( - &mut self, - _server_id: Option, - m: SetCustomMiningJobSuccess, - ) -> Result<(), Self::Error> { - warn!("Received: {}", m); - warn!("โš ๏ธ Cannot process SetCustomMiningJobSuccess since Translator Proxy does not support custom mining jobs. Ignoring."); - Err(Self::Error::UnexpectedMessage( - MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_SUCCESS, - )) - } - - async fn handle_set_custom_mining_job_error( - &mut self, - _server_id: Option, - m: SetCustomMiningJobError<'_>, - ) -> Result<(), Self::Error> { - warn!("Received: {}", m); - warn!("โš ๏ธ Cannot process SetCustomMiningJobError since Translator Proxy does not support custom mining jobs. Ignoring."); - Err(Self::Error::UnexpectedMessage( - MESSAGE_TYPE_SET_CUSTOM_MINING_JOB_ERROR, - )) - } - - async fn handle_set_target( - &mut self, - _server_id: Option, - m: SetTarget<'_>, - ) -> Result<(), Self::Error> { - info!("Received: {}", m); - - // Update the channel targets in the channel manager - _ = self.channel_manager_data.safe_lock(|channel_manager_data| { - if channel_manager_data.mode == ChannelMode::Aggregated { - if let Some(upstream_channel) = &channel_manager_data.upstream_extended_channel { - if let Ok(mut upstream_extended_channel) = upstream_channel.write() { - upstream_extended_channel.set_target(Target::from_le_bytes( - m.maximum_target.inner_as_ref().try_into().unwrap(), - )); - } - } - channel_manager_data - .extended_channels - .iter() - .for_each(|(_, channel)| { - if let Ok(mut channel) = channel.write() { - channel.set_target(Target::from_le_bytes( - m.maximum_target.inner_as_ref().try_into().unwrap(), - )); - } - }); - } else if let Some(channel) = channel_manager_data.extended_channels.get(&m.channel_id) - { - if let Ok(mut channel) = channel.write() { - channel.set_target(Target::from_le_bytes( - m.maximum_target.inner_as_ref().try_into().unwrap(), - )); - } - } - }); - - // Forward SetTarget message to SV1Server for vardiff processing - self.channel_state - .sv1_server_sender - .send(Mining::SetTarget(m.clone().into_static())) - .await - .map_err(|e| { - error!("Failed to forward SetTarget message to SV1Server: {:?}", e); - TproxyError::ChannelErrorSender - })?; - - Ok(()) - } - - async fn handle_set_group_channel( - &mut self, - _server_id: Option, - m: SetGroupChannel<'_>, - ) -> Result<(), Self::Error> { - warn!("Received: {}", m); - warn!("โš ๏ธ Cannot process SetGroupChannel since Translator Proxy does not support group channels. Ignoring."); - Err(Self::Error::UnexpectedMessage( - MESSAGE_TYPE_SET_GROUP_CHANNEL, - )) - } -} diff --git a/roles/translator/src/lib/sv2/channel_manager/mod.rs b/roles/translator/src/lib/sv2/channel_manager/mod.rs deleted file mode 100644 index 689a6efc7f..0000000000 --- a/roles/translator/src/lib/sv2/channel_manager/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod channel_manager; -pub mod message_handler; -pub use channel_manager::ChannelManager; -pub(super) mod channel; -pub(crate) mod data; -pub use data::ChannelMode; diff --git a/roles/translator/src/lib/sv2/mod.rs b/roles/translator/src/lib/sv2/mod.rs deleted file mode 100644 index d8cb5e360c..0000000000 --- a/roles/translator/src/lib/sv2/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod channel_manager; -pub mod upstream; - -pub use channel_manager::channel_manager::ChannelManager; -pub use upstream::upstream::Upstream; diff --git a/roles/translator/src/lib/sv2/upstream/channel.rs b/roles/translator/src/lib/sv2/upstream/channel.rs deleted file mode 100644 index 25fb324a58..0000000000 --- a/roles/translator/src/lib/sv2/upstream/channel.rs +++ /dev/null @@ -1,40 +0,0 @@ -use async_channel::{Receiver, Sender}; -use stratum_apps::stratum_core::{codec_sv2::StandardEitherFrame, parsers_sv2::AnyMessage}; -use tracing::debug; - -pub type Message = AnyMessage<'static>; -pub type EitherFrame = StandardEitherFrame; - -#[derive(Debug, Clone)] -pub struct UpstreamChannelState { - /// Receiver for the SV2 Upstream role - pub upstream_receiver: Receiver, - /// Sender for the SV2 Upstream role - pub upstream_sender: Sender, - /// Sender for the ChannelManager thread - pub channel_manager_sender: Sender, - /// Receiver for the ChannelManager thread - pub channel_manager_receiver: Receiver, -} - -impl UpstreamChannelState { - pub fn new( - channel_manager_sender: Sender, - channel_manager_receiver: Receiver, - upstream_receiver: Receiver, - upstream_sender: Sender, - ) -> Self { - Self { - channel_manager_sender, - channel_manager_receiver, - upstream_receiver, - upstream_sender, - } - } - - pub fn drop(&self) { - debug!("Closing all upstream channels"); - self.upstream_receiver.close(); - self.upstream_receiver.close(); - } -} diff --git a/roles/translator/src/lib/sv2/upstream/message_handler.rs b/roles/translator/src/lib/sv2/upstream/message_handler.rs deleted file mode 100644 index 6ca38fcd08..0000000000 --- a/roles/translator/src/lib/sv2/upstream/message_handler.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::{error::TproxyError, sv2::Upstream}; -use stratum_apps::stratum_core::{ - common_messages_sv2::{ - ChannelEndpointChanged, Reconnect, SetupConnectionError, SetupConnectionSuccess, - }, - handlers_sv2::HandleCommonMessagesFromServerAsync, -}; -use tracing::{error, info}; - -impl HandleCommonMessagesFromServerAsync for Upstream { - type Error = TproxyError; - - async fn handle_setup_connection_error( - &mut self, - _server_id: Option, - msg: SetupConnectionError<'_>, - ) -> Result<(), Self::Error> { - error!("Received: {}", msg); - todo!() - } - - async fn handle_setup_connection_success( - &mut self, - _server_id: Option, - msg: SetupConnectionSuccess, - ) -> Result<(), Self::Error> { - info!("Received: {}", msg); - Ok(()) - } - - async fn handle_channel_endpoint_changed( - &mut self, - _server_id: Option, - msg: ChannelEndpointChanged, - ) -> Result<(), Self::Error> { - info!("Received: {}", msg); - todo!() - } - - async fn handle_reconnect( - &mut self, - _server_id: Option, - msg: Reconnect<'_>, - ) -> Result<(), Self::Error> { - info!("Received: {}", msg); - todo!() - } -} diff --git a/roles/translator/src/lib/sv2/upstream/mod.rs b/roles/translator/src/lib/sv2/upstream/mod.rs deleted file mode 100644 index 52cef24ca7..0000000000 --- a/roles/translator/src/lib/sv2/upstream/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod message_handler; -pub mod upstream; -pub use upstream::Upstream; -pub(super) mod channel; diff --git a/roles/translator/src/lib/sv2/upstream/upstream.rs b/roles/translator/src/lib/sv2/upstream/upstream.rs deleted file mode 100644 index 8fa2bf0d25..0000000000 --- a/roles/translator/src/lib/sv2/upstream/upstream.rs +++ /dev/null @@ -1,457 +0,0 @@ -use crate::{ - error::TproxyError, - status::{handle_error, Status, StatusSender}, - sv2::upstream::channel::UpstreamChannelState, - task_manager::TaskManager, - utils::{message_from_frame, ShutdownMessage}, -}; -use async_channel::{Receiver, Sender}; -use std::{net::SocketAddr, sync::Arc}; -use stratum_apps::{ - key_utils::Secp256k1PublicKey, - network_helpers::noise_connection::Connection, - stratum_core::{ - buffer_sv2, - codec_sv2::{HandshakeRole, StandardEitherFrame, StandardSv2Frame}, - common_messages_sv2::{Protocol, SetupConnection}, - framing_sv2, - handlers_sv2::HandleCommonMessagesFromServerAsync, - noise_sv2::Initiator, - parsers_sv2::AnyMessage, - }, -}; -use tokio::{ - net::TcpStream, - sync::{broadcast, mpsc}, - time::{sleep, Duration}, -}; -use tracing::{debug, error, info, warn}; - -/// Type alias for SV2 messages with static lifetime -pub type Message = AnyMessage<'static>; -/// Type alias for standard SV2 frames -pub type StdFrame = StandardSv2Frame; -/// Type alias for either handshake or SV2 frames -pub type EitherFrame = StandardEitherFrame; - -/// Manages the upstream SV2 connection to a mining pool or proxy. -/// -/// This struct handles the SV2 protocol communication with upstream servers, -/// including: -/// - Connection establishment with multiple upstream fallbacks -/// - SV2 handshake and setup procedures -/// - Message routing between channel manager and upstream -/// - Connection monitoring and error handling -/// - Graceful shutdown coordination -/// -/// The upstream connection supports automatic failover between multiple -/// configured upstream servers and implements retry logic for connection -/// establishment. -#[derive(Debug, Clone)] -pub struct Upstream { - upstream_channel_state: UpstreamChannelState, -} - -impl Upstream { - /// Creates a new upstream connection by attempting to connect to configured servers. - /// - /// This method tries to establish a connection to one of the provided upstream - /// servers, implementing retry logic and fallback behavior. It will attempt - /// to connect to each server multiple times before giving up. - /// - /// # Arguments - /// * `upstreams` - List of (address, public_key) pairs for upstream servers - /// * `channel_manager_sender` - Channel to send messages to the channel manager - /// * `channel_manager_receiver` - Channel to receive messages from the channel manager - /// * `notify_shutdown` - Broadcast channel for shutdown coordination - /// * `shutdown_complete_tx` - Channel to signal shutdown completion - /// - /// # Returns - /// * `Ok(Upstream)` - Successfully connected to an upstream server - /// * `Err(TproxyError)` - Failed to connect to any upstream server - pub async fn new( - upstreams: &[(SocketAddr, Secp256k1PublicKey)], - channel_manager_sender: Sender, - channel_manager_receiver: Receiver, - notify_shutdown: broadcast::Sender, - shutdown_complete_tx: mpsc::Sender<()>, - ) -> Result { - let mut shutdown_rx = notify_shutdown.subscribe(); - const RETRIES_PER_UPSTREAM: u8 = 3; - - for (index, (addr, pubkey)) in upstreams.iter().enumerate() { - info!("Trying to connect to upstream {} at {}", index, addr); - - for attempt in 1..=RETRIES_PER_UPSTREAM { - if shutdown_rx.try_recv().is_ok() { - info!("Shutdown signal received during upstream connection attempt. Aborting."); - drop(shutdown_complete_tx); - return Err(TproxyError::Shutdown); - } - - match TcpStream::connect(addr).await { - Ok(socket) => { - info!( - "Connected to upstream at {addr} (attempt {attempt}/{RETRIES_PER_UPSTREAM})" - ); - let initiator = Initiator::from_raw_k(pubkey.into_bytes())?; - match Connection::new(socket, HandshakeRole::Initiator(initiator)).await { - Ok((receiver, sender)) => { - let upstream_channel_state = UpstreamChannelState::new( - channel_manager_sender, - channel_manager_receiver, - receiver, - sender, - ); - debug!("Successfully initialized upstream channel with {addr}"); - - return Ok(Self { - upstream_channel_state, - }); - } - Err(e) => { - error!("Failed Noise handshake with {addr}: {e:?}. Retrying..."); - } - } - } - Err(e) => { - error!( - "Failed to connect to {addr}: {e}. Retry {attempt}/{RETRIES_PER_UPSTREAM}..." - ); - } - } - - sleep(Duration::from_secs(5)).await; - } - - warn!("Exhausted retries for upstream {index} at {addr}"); - } - - error!("Failed to connect to any configured upstream."); - drop(shutdown_complete_tx); - Err(TproxyError::Shutdown) - } - - /// Starts the upstream connection and begins message processing. - /// - /// This method: - /// - Completes the SV2 handshake with the upstream server - /// - Spawns the main message processing task - /// - Handles graceful shutdown coordination - /// - /// The method will first attempt to complete the SV2 setup connection - /// handshake. If successful, it spawns a task to handle bidirectional - /// message flow between the channel manager and upstream server. - pub async fn start( - mut self, - notify_shutdown: broadcast::Sender, - shutdown_complete_tx: mpsc::Sender<()>, - status_sender: Sender, - task_manager: Arc, - ) -> Result<(), TproxyError> { - let mut shutdown_rx = notify_shutdown.subscribe(); - // Wait for connection setup or shutdown signal - tokio::select! { - result = self.setup_connection() => { - if let Err(e) = result { - error!("Upstream: failed to set up SV2 connection: {e:?}"); - drop(shutdown_complete_tx); - return Err(e); - } - } - message = shutdown_rx.recv() => { - match message { - Ok(ShutdownMessage::ShutdownAll) => { - info!("Upstream: shutdown signal received during connection setup."); - drop(shutdown_complete_tx); - return Ok(()); - } - Ok(_) => {} - - Err(e) => { - error!("Upstream: failed to receive shutdown signal: {e}"); - drop(shutdown_complete_tx); - return Ok(()); - } - } - } - } - - // Wrap status sender and start upstream task - let wrapped_status_sender = StatusSender::Upstream(status_sender); - - self.run_upstream_task( - notify_shutdown, - shutdown_complete_tx, - wrapped_status_sender, - task_manager, - )?; - - Ok(()) - } - - /// Performs the SV2 handshake setup with the upstream server. - /// - /// This method handles the initial SV2 protocol handshake by: - /// - Creating and sending a SetupConnection message - /// - Waiting for the handshake response - /// - Validating and processing the response - /// - /// The handshake establishes the protocol version, capabilities, and - /// other connection parameters needed for SV2 communication. - pub async fn setup_connection(&mut self) -> Result<(), TproxyError> { - debug!("Upstream: initiating SV2 handshake..."); - // Build SetupConnection message - let setup_conn_msg = Self::get_setup_connection_message(2, 2, false)?; - let sv2_frame: StdFrame = - Message::Common(setup_conn_msg.into()) - .try_into() - .map_err(|e| { - error!("Failed to serialize SetupConnection message: {:?}", e); - TproxyError::ParserError(e) - })?; - - // Send SetupConnection message to upstream - self.upstream_channel_state - .upstream_sender - .send(sv2_frame.into()) - .await - .map_err(|e| { - error!("Failed to send SetupConnection to upstream: {:?}", e); - TproxyError::ChannelErrorSender - })?; - - let mut incoming: StdFrame = - match self.upstream_channel_state.upstream_receiver.recv().await { - Ok(frame) => { - debug!("Received handshake response from upstream."); - frame.try_into()? - } - Err(e) => { - error!("Failed to receive handshake response from upstream: {}", e); - return Err(TproxyError::CodecNoise( - stratum_apps::stratum_core::noise_sv2::Error::ExpectedIncomingHandshakeMessage, - )); - } - }; - - let message_type = incoming - .get_header() - .ok_or_else(|| { - error!("Expected handshake frame but no header found."); - framing_sv2::Error::ExpectedHandshakeFrame - })? - .msg_type(); - - let payload = incoming.payload(); - - self.handle_common_message_frame_from_server(None, message_type, payload) - .await?; - debug!("Upstream: handshake completed successfully."); - Ok(()) - } - - /// Processes incoming messages from the upstream SV2 server. - /// - /// This method handles different types of frames received from upstream: - /// - SV2 frames: Parses and routes mining/common messages appropriately - /// - Handshake frames: Logs for debugging (shouldn't occur during normal operation) - /// - /// Common messages are handled directly, while mining messages are forwarded - /// to the channel manager for processing and distribution to downstream connections. - pub async fn on_upstream_message(&self, message: EitherFrame) -> Result<(), TproxyError> { - let mut upstream = self.get_upstream(); - match message { - EitherFrame::Sv2(sv2_frame) => { - // Convert to standard frame - let std_frame: StdFrame = sv2_frame; - - // Parse message from frame - let mut frame: stratum_apps::stratum_core::framing_sv2::framing::Frame< - AnyMessage<'static>, - buffer_sv2::Slice, - > = std_frame.clone().into(); - - let (messsage_type, mut payload, parsed_message) = message_from_frame(&mut frame)?; - - match parsed_message { - AnyMessage::Common(_) => { - // Handle common upstream messages - upstream - .handle_common_message_frame_from_server( - None, - messsage_type, - &mut payload, - ) - .await?; - } - - AnyMessage::Mining(_) => { - // Forward mining message to channel manager - let frame_to_forward = EitherFrame::Sv2(std_frame.clone()); - self.upstream_channel_state - .channel_manager_sender - .send(frame_to_forward) - .await - .map_err(|e| { - error!("Failed to send mining message to channel manager: {:?}", e); - TproxyError::ChannelErrorSender - })?; - } - - _ => { - error!("Received unsupported message type from upstream."); - return Err(TproxyError::UnexpectedMessage(0)); - } - } - } - - EitherFrame::HandShake(handshake_frame) => { - debug!("Received handshake frame: {:?}", handshake_frame); - } - } - Ok(()) - } - - /// Spawns a unified task to handle upstream message I/O and shutdown logic. - fn run_upstream_task( - self, - notify_shutdown: broadcast::Sender, - shutdown_complete_tx: mpsc::Sender<()>, - status_sender: StatusSender, - task_manager: Arc, - ) -> Result<(), TproxyError> { - let mut shutdown_rx = notify_shutdown.subscribe(); - let shutdown_complete_tx = shutdown_complete_tx.clone(); - - task_manager.spawn(async move { - loop { - tokio::select! { - // Handle shutdown signals - shutdown = shutdown_rx.recv() => { - match shutdown { - Ok(ShutdownMessage::ShutdownAll) => { - info!("Upstream: received ShutdownAll signal. Exiting loop."); - break; - } - Ok(_) => { - // Ignore other shutdown variants for upstream - } - Err(e) => { - error!("Upstream: failed to receive shutdown signal: {e}"); - break; - } - } - } - - // Handle incoming SV2 messages from upstream - result = self.upstream_channel_state.upstream_receiver.recv() => { - match result { - Ok(frame) => { - debug!("Upstream: received frame."); - if let Err(e) = self.on_upstream_message(frame).await { - error!("Upstream: error while processing message: {e:?}"); - handle_error(&status_sender, TproxyError::ChannelErrorSender).await; - } - } - Err(e) => { - error!("Upstream: receiver channel closed unexpectedly: {e}"); - handle_error(&status_sender, TproxyError::ChannelErrorReceiver(e)).await; - break; - } - } - } - - // Handle messages from channel manager to send upstream - result = self.upstream_channel_state.channel_manager_receiver.recv() => { - match result { - Ok(msg) => { - debug!("Upstream: sending message from channel manager: {:?}", msg); - if let Err(e) = self.send_upstream(msg).await { - error!("Upstream: failed to send message: {e:?}"); - handle_error(&status_sender, TproxyError::ChannelErrorSender).await; - } - } - Err(e) => { - error!("Upstream: channel manager receiver closed: {e}"); - handle_error(&status_sender, TproxyError::ChannelErrorReceiver(e)).await; - break; - } - } - } - } - } - - self.upstream_channel_state.drop(); - warn!("Upstream: task shutting down cleanly."); - drop(shutdown_complete_tx); - }); - - Ok(()) - } - - /// Sends a message to the upstream SV2 server. - /// - /// This method forwards messages from the channel manager to the upstream - /// server. Messages are typically mining-related (share submissions, channel - /// requests, etc.) that need to be sent upstream. - /// - /// # Arguments - /// * `sv2_frame` - The SV2 frame to send to the upstream server - /// - /// # Returns - /// * `Ok(())` - Message sent successfully - /// * `Err(TproxyError)` - Error sending the message - pub async fn send_upstream(&self, sv2_frame: EitherFrame) -> Result<(), TproxyError> { - debug!("Sending message to upstream."); - - self.upstream_channel_state - .upstream_sender - .send(sv2_frame) - .await - .map_err(|e| { - error!("Failed to send message to upstream: {:?}", e); - TproxyError::ChannelErrorSender - })?; - - Ok(()) - } - - /// Constructs the `SetupConnection` message. - #[allow(clippy::result_large_err)] - fn get_setup_connection_message( - min_version: u16, - max_version: u16, - is_work_selection_enabled: bool, - ) -> Result, TproxyError> { - let endpoint_host = "0.0.0.0".to_string().into_bytes().try_into()?; - let vendor = "SRI".to_string().try_into()?; - let hardware_version = "Translator Proxy".to_string().try_into()?; - let firmware = String::new().try_into()?; - let device_id = String::new().try_into()?; - let flags = if is_work_selection_enabled { - 0b110 - } else { - 0b100 - }; - - Ok(SetupConnection { - protocol: Protocol::MiningProtocol, - min_version, - max_version, - flags, - endpoint_host, - endpoint_port: 50, - vendor, - hardware_version, - firmware, - device_id, - }) - } - - fn get_upstream(&self) -> Upstream { - Upstream { - upstream_channel_state: self.upstream_channel_state.clone(), - } - } -} diff --git a/roles/translator/src/lib/task_manager.rs b/roles/translator/src/lib/task_manager.rs deleted file mode 100644 index ac615a3a7b..0000000000 --- a/roles/translator/src/lib/task_manager.rs +++ /dev/null @@ -1,80 +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 in the translator. 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 - #[track_caller] - pub fn spawn(&self, fut: F) - where - F: std::future::Future + Send + 'static, - { - use tracing::Instrument; - let location = std::panic::Location::caller(); - let span = tracing::trace_span!( - "task", - file = location.file(), - line = location.line(), - column = location.column(), - ); - - let handle = tokio::spawn(fut.instrument(span)); - self.tasks.lock().unwrap().push(handle); - } - - /// Waits for all managed tasks to complete. - /// - /// This method will block until all tasks that were spawned through this - /// manager have finished executing. Tasks are joined in reverse order - /// (most recently spawned first). - pub async fn join_all(&self) { - let handles = { - let mut tasks = self.tasks.lock().unwrap(); - std::mem::take(&mut *tasks) - }; - - for handle in handles { - let _ = handle.await; - } - } - - /// Aborts all managed tasks. - /// - /// This method immediately cancels all tasks that were spawned through this - /// manager. The tasks will be terminated without waiting for them to complete. - pub async fn abort_all(&self) { - let mut tasks = self.tasks.lock().unwrap(); - for handle in tasks.drain(..) { - handle.abort(); - } - } -} diff --git a/roles/translator/src/lib/utils.rs b/roles/translator/src/lib/utils.rs deleted file mode 100644 index 944e70471e..0000000000 --- a/roles/translator/src/lib/utils.rs +++ /dev/null @@ -1,297 +0,0 @@ -use stratum_apps::{ - custom_mutex::Mutex, - stratum_core::{ - binary_sv2::{Sv2DataType, U256}, - bitcoin::{ - block::{Header, Version}, - hashes::Hash, - CompactTarget, Target, TxMerkleNode, - }, - buffer_sv2::Slice, - channels_sv2::{ - merkle_root::merkle_root_from_path, - target::{bytes_to_hex, u256_to_block_hash}, - }, - framing_sv2::framing::Frame, - parsers_sv2::{AnyMessage, CommonMessages}, - sv1_api::{client_to_server, utils::HexU32Be}, - }, -}; -use tracing::{debug, error}; - -use crate::error::TproxyError; - -/// Validates an SV1 share against the target difficulty and job parameters. -/// -/// This function performs complete share validation by: -/// 1. Finding the corresponding job from the valid jobs storage -/// 2. Constructing the full extranonce from extranonce1 and extranonce2 -/// 3. Calculating the merkle root from the coinbase transaction and merkle path -/// 4. Building the block header with the share's nonce and timestamp -/// 5. Hashing the header and comparing against the target difficulty -/// -/// # Arguments -/// * `share` - The SV1 submit message containing the share data -/// * `target` - The target difficulty for this share -/// * `extranonce1` - The first part of the extranonce (from server) -/// * `version_rolling_mask` - Optional mask for version rolling -/// * `sv1_server_data` - Reference to shared SV1 server data for accessing valid jobs -/// * `channel_id` - Channel ID for job lookup -/// -/// # Returns -/// * `Ok(true)` if the share is valid and meets the target -/// * `Ok(false)` if the share is valid but doesn't meet the target -/// * `Err(TproxyError)` if validation fails due to missing job or invalid data -pub fn validate_sv1_share( - share: &client_to_server::Submit<'static>, - target: Target, - extranonce1: Vec, - version_rolling_mask: Option, - sv1_server_data: std::sync::Arc>, - channel_id: u32, -) -> Result { - let job_id = share.job_id.clone(); - - // Access valid jobs based on the configured mode - let job = sv1_server_data - .super_safe_lock(|server_data| { - if let Some(ref aggregated_jobs) = server_data.aggregated_valid_jobs { - // Aggregated mode: search in shared jobs - aggregated_jobs - .iter() - .find(|job| job.job_id == job_id) - .cloned() - } else if let Some(ref non_aggregated_jobs) = server_data.non_aggregated_valid_jobs { - // Non-aggregated mode: search in channel-specific jobs - non_aggregated_jobs - .get(&channel_id) - .and_then(|channel_jobs| channel_jobs.iter().find(|job| job.job_id == job_id)) - .cloned() - } else { - None - } - }) - .ok_or(TproxyError::JobNotFound)?; - - let mut full_extranonce = vec![]; - full_extranonce.extend_from_slice(extranonce1.as_slice()); - full_extranonce.extend_from_slice(share.extra_nonce2.0.as_ref()); - - let share_version = share - .version_bits - .clone() - .map(|vb| vb.0) - .unwrap_or(job.version.0); - let mask = version_rolling_mask.unwrap_or(HexU32Be(0x1FFFE000_u32)).0; - let version = (job.version.0 & !mask) | (share_version & mask); - - let prev_hash_vec: Vec = job.prev_hash.clone().into(); - let prev_hash = U256::from_vec_(prev_hash_vec).map_err(TproxyError::BinarySv2)?; - - // calculate the merkle root from: - // - job coinbase_tx_prefix - // - full extranonce - // - job coinbase_tx_suffix - // - job merkle_path - let merkle_root: [u8; 32] = merkle_root_from_path( - job.coin_base1.as_ref(), - job.coin_base2.as_ref(), - full_extranonce.as_ref(), - job.merkle_branch.as_ref(), - ) - .ok_or(TproxyError::InvalidMerkleRoot)? - .try_into() - .map_err(|_| TproxyError::InvalidMerkleRoot)?; - - // create the header for validation - let header = Header { - version: Version::from_consensus(version as i32), - prev_blockhash: u256_to_block_hash(prev_hash), - merkle_root: TxMerkleNode::from_byte_array(merkle_root), - time: share.time.0, - bits: CompactTarget::from_consensus(job.bits.0), - nonce: share.nonce.0, - }; - - // convert the header hash to a target type for easy comparison - let hash = header.block_hash(); - let raw_hash: [u8; 32] = *hash.to_raw_hash().as_ref(); - let hash_as_target = Target::from_le_bytes(raw_hash); - - // print hash_as_target and self.target as human readable hex - let hash_bytes = hash_as_target.to_be_bytes(); - let target_bytes = target.to_be_bytes(); - - debug!( - "share validation \nshare:\t\t{}\ndownstream target:\t{}\n", - bytes_to_hex(&hash_bytes), - bytes_to_hex(&target_bytes), - ); - // check if the share hash meets the downstream target - if hash_as_target < target { - /*if self.share_accounting.is_share_seen(hash.to_raw_hash()) { - return Err(ShareValidationError::DuplicateShare); - }*/ - - return Ok(true); - } - - Ok(false) -} - -/// Calculates the required length of the proxy's extranonce prefix. -/// -/// This function determines how many bytes the proxy needs to reserve for its own -/// extranonce prefix, based on the difference between the channel's rollable extranonce -/// size and the downstream miner's rollable extranonce size. -/// -/// # Arguments -/// * `channel_rollable_extranonce_size` - Size of the rollable extranonce from the channel -/// * `downstream_rollable_extranonce_size` - Size of the rollable extranonce for downstream -/// -/// # Returns -/// The number of bytes needed for the proxy's extranonce prefix -pub fn proxy_extranonce_prefix_len( - channel_rollable_extranonce_size: usize, - downstream_rollable_extranonce_size: usize, -) -> usize { - channel_rollable_extranonce_size - downstream_rollable_extranonce_size -} - -/// Extracts message type, payload, and parsed message from an SV2 frame. -/// -/// This function processes an SV2 frame and extracts the essential components: -/// - Message type identifier -/// - Raw payload bytes -/// - Parsed message structure -/// -/// # Arguments -/// * `frame` - The SV2 frame to process -/// -/// # Returns -/// A tuple containing (message_type, payload, parsed_message) on success, -/// or a TproxyError if the frame is invalid or cannot be parsed -pub fn message_from_frame( - frame: &mut Frame, Slice>, -) -> Result<(u8, Vec, AnyMessage<'static>), TproxyError> { - match frame { - Frame::Sv2(frame) => { - let header = frame - .get_header() - .ok_or(TproxyError::UnexpectedMessage(0))?; - let message_type = header.msg_type(); - let mut payload = frame.payload().to_vec(); - let message: Result, _> = - (message_type, payload.as_mut_slice()).try_into(); - match message { - Ok(message) => { - let message = into_static(message)?; - Ok((message_type, payload.to_vec(), message)) - } - Err(_) => { - error!("Received frame with invalid payload or message type: {frame:?}"); - Err(TproxyError::UnexpectedMessage(message_type)) - } - } - } - Frame::HandShake(f) => { - error!("Received unexpected handshake frame: {f:?}"); - Err(TproxyError::UnexpectedMessage(0)) - } - } -} - -/// Converts a borrowed AnyMessage to a static lifetime version. -/// -/// This function takes an AnyMessage with a borrowed lifetime and converts it to -/// a static lifetime version, which is necessary for storing messages across -/// async boundaries and in data structures. -/// -/// # Arguments -/// * `m` - The AnyMessage to convert to static lifetime -/// -/// # Returns -/// A static lifetime version of the message, or TproxyError if the message -/// type is not supported for static conversion -pub fn into_static(m: AnyMessage<'_>) -> Result, TproxyError> { - match m { - AnyMessage::Mining(m) => Ok(AnyMessage::Mining(m.into_static())), - AnyMessage::Common(m) => match m { - CommonMessages::ChannelEndpointChanged(m) => Ok(AnyMessage::Common( - CommonMessages::ChannelEndpointChanged(m.into_static()), - )), - CommonMessages::SetupConnection(m) => Ok(AnyMessage::Common( - CommonMessages::SetupConnection(m.into_static()), - )), - CommonMessages::SetupConnectionError(m) => Ok(AnyMessage::Common( - CommonMessages::SetupConnectionError(m.into_static()), - )), - CommonMessages::SetupConnectionSuccess(m) => Ok(AnyMessage::Common( - CommonMessages::SetupConnectionSuccess(m.into_static()), - )), - CommonMessages::Reconnect(m) => Ok(AnyMessage::Common(CommonMessages::Reconnect( - m.into_static(), - ))), - }, - _ => Err(TproxyError::UnexpectedMessage(0)), - } -} - -/// Messages used for coordinating shutdown across different components. -/// -/// This enum defines the different types of shutdown signals that can be sent -/// through the broadcast channel to coordinate graceful shutdown of the translator. -#[derive(Debug, Clone)] -pub enum ShutdownMessage { - /// Shutdown all components immediately - ShutdownAll, - /// Shutdown all downstream connections - DownstreamShutdownAll, - /// Shutdown a specific downstream connection by ID - DownstreamShutdown(u32), - /// Reset channel manager state and shutdown downstreams due to upstream reconnection - UpstreamReconnectedResetAndShutdownDownstreams, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_proxy_extranonce_prefix_len() { - assert_eq!(proxy_extranonce_prefix_len(8, 4), 4); - assert_eq!(proxy_extranonce_prefix_len(10, 6), 4); - assert_eq!(proxy_extranonce_prefix_len(4, 4), 0); - } - - #[test] - fn test_shutdown_message_debug() { - let msg1 = ShutdownMessage::ShutdownAll; - let msg2 = ShutdownMessage::DownstreamShutdown(123); - let msg3 = ShutdownMessage::DownstreamShutdownAll; - let msg4 = ShutdownMessage::UpstreamReconnectedResetAndShutdownDownstreams; - - // Test Debug implementation - assert!(format!("{:?}", msg1).contains("ShutdownAll")); - assert!(format!("{:?}", msg2).contains("DownstreamShutdown")); - assert!(format!("{:?}", msg2).contains("123")); - assert!(format!("{:?}", msg3).contains("DownstreamShutdownAll")); - assert!(format!("{:?}", msg4).contains("UpstreamReconnected")); - } - - #[test] - fn test_shutdown_message_clone() { - let msg = ShutdownMessage::DownstreamShutdown(456); - let cloned = msg.clone(); - - match (msg, cloned) { - ( - ShutdownMessage::DownstreamShutdown(id1), - ShutdownMessage::DownstreamShutdown(id2), - ) => { - assert_eq!(id1, id2); - } - _ => panic!("Clone failed"), - } - } -} diff --git a/roles/translator/src/main.rs b/roles/translator/src/main.rs deleted file mode 100644 index 4b09cecea4..0000000000 --- a/roles/translator/src/main.rs +++ /dev/null @@ -1,25 +0,0 @@ -mod args; -use std::process; - -use stratum_apps::config_helpers::logging::init_logging; -pub use translator_sv2::{config, error, status, sv1, sv2, TranslatorSv2}; - -use crate::args::process_cli_args; - -/// Entrypoint for the Translator binary. -/// -/// Loads the configuration from TOML and initializes the main runtime -/// defined in `translator_sv2::TranslatorSv2`. Errors during startup are logged. -#[tokio::main] -async fn main() { - let proxy_config = process_cli_args().unwrap_or_else(|e| { - eprintln!("Translator proxy config error: {e}"); - std::process::exit(1); - }); - - init_logging(proxy_config.log_dir()); - - TranslatorSv2::new(proxy_config).start().await; - - process::exit(1); -} diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 36fbd1dc63..0000000000 --- a/rustfmt.toml +++ /dev/null @@ -1,10 +0,0 @@ -edition = "2018" -imports_indent = "Block" -imports_layout = "Mixed" -imports_granularity = "Crate" -wrap_comments = true -format_code_in_doc_comments = true -comment_width = 100 # Default 80 -normalize_comments = false -normalize_doc_attributes = false -format_strings = false \ No newline at end of file diff --git a/scripts/build-on-all-workspaces.sh b/scripts/build-on-all-workspaces.sh deleted file mode 100755 index 16b6109719..0000000000 --- a/scripts/build-on-all-workspaces.sh +++ /dev/null @@ -1,24 +0,0 @@ - -#!/bin/sh - -WORKSPACES="stratum-core protocols roles utils" - -for workspace in $WORKSPACES; do - echo "Executing build on: $workspace" - cargo +1.75.0 build --manifest-path="$workspace/Cargo.toml" -- - if [ $? -ne 0 ]; then - echo "Build found some errors in: $workspace" - exit 1 - fi - - echo "Running fmt on: $workspace" - (cd $workspace && cargo +nightly fmt) - if [ $? -ne 0 ]; then - echo "Fmt failed in: $workspace" - exit 1 - fi -done - -echo "build success!" - - diff --git a/scripts/clippy-fmt-and-test.sh b/scripts/clippy-fmt-and-test.sh deleted file mode 100755 index fc44c8489b..0000000000 --- a/scripts/clippy-fmt-and-test.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh - -WORKSPACES="stratum-core protocols roles utils" - -for workspace in $WORKSPACES; do - echo "Executing clippy on: $workspace" - cargo +1.75.0 clippy --manifest-path="$workspace/Cargo.toml" -- -D warnings -A dead-code - if [ $? -ne 0 ]; then - echo "Clippy found some errors in: $workspace" - exit 1 - fi - - echo "Running tests on: $workspace" - cargo +1.75 test --manifest-path="$workspace/Cargo.toml" - if [ $? -ne 0 ]; then - echo "Tests failed in: $workspace" - exit 1 - fi - - echo "Running fmt on: $workspace" - (cd $workspace && cargo +nightly fmt) - if [ $? -ne 0 ]; then - echo "Fmt failed in: $workspace" - exit 1 - fi -done - -echo "Clippy success, all tests passed!" diff --git a/scripts/coverage-protocols.sh b/scripts/coverage-protocols.sh index 79c601a622..6724efa6b5 100755 --- a/scripts/coverage-protocols.sh +++ b/scripts/coverage-protocols.sh @@ -10,24 +10,26 @@ cd protocols tarpaulin crates=( - "v1" - "v2/binary-sv2/derive_codec" - "v2/binary-sv2" - "v2/channels-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/parsers-sv2" + "sv1" + "sv2/binary-sv2/derive_codec" + "sv2/binary-sv2" + "sv2/channels-sv2" + "sv2/noise-sv2" + "sv2/framing-sv2" + "sv2/codec-sv2" + "sv2/subprotocols/common-messages" + "sv2/subprotocols/template-distribution" + "sv2/subprotocols/mining" + "sv2/subprotocols/job-declaration" + "sv2/roles-logic-sv2" + "sv2/parsers-sv2" + "sv2/handlers-sv2" ) for crate in "${crates[@]}"; do echo "Running Tarpaulin for $crate..." crate_name=$(basename "$crate") - cd "$crate" || exit 1 + cd "$crate" || exit 1 tarpaulin "$crate_name-coverage" cd - || exit 1 done diff --git a/scripts/coverage-roles.sh b/scripts/coverage-roles.sh deleted file mode 100755 index 95b38fae7a..0000000000 --- a/scripts/coverage-roles.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -tarpaulin() { - crate_name=$1 - output_dir="target/tarpaulin-reports/$crate_name" - mkdir -p "$output_dir" - cargo +nightly tarpaulin --verbose --out Xml --output-dir "$output_dir" --all-features -} - -cd roles -tarpaulin - -crates=( - "pool" - "test-utils/mining-device" - "translator" - "jd-client" - "jd-server" - "stratum-apps" -) - -for crate in "${crates[@]}"; do - echo "Running Tarpaulin for $crate..." - crate_name=$(basename "$crate") - cd "$crate" || exit 1 - tarpaulin "$crate_name-coverage" - cd - || exit 1 -done diff --git a/scripts/coverage-utils.sh b/scripts/coverage-utils.sh deleted file mode 100755 index 4991d2dfe2..0000000000 --- a/scripts/coverage-utils.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -tarpaulin() { - crate_name=$1 - output_dir="target/tarpaulin-reports/$crate_name" - mkdir -p "$output_dir" - cargo +nightly tarpaulin --verbose --out Xml --output-dir "$output_dir" --all-features -} - -cd utils -tarpaulin - -crates=( - "buffer" -) - -for crate in "${crates[@]}"; do - echo "Running Tarpaulin for $crate..." - crate_name=$(basename "$crate") - cd "$crate" || exit 1 - tarpaulin "$crate_name-coverage" - cd - || exit 1 -done diff --git a/scripts/release-libs.sh b/scripts/release-libs.sh index 86c0e29eaf..ff113e5ca2 100755 --- a/scripts/release-libs.sh +++ b/scripts/release-libs.sh @@ -32,4 +32,4 @@ fi echo "Publish command failed for $CRATE_DIR" echo "$OUTPUT" -exit 1 \ No newline at end of file +exit 1 diff --git a/scripts/run-integration-tests.sh b/scripts/run-integration-tests.sh new file mode 100755 index 0000000000..59709a96ab --- /dev/null +++ b/scripts/run-integration-tests.sh @@ -0,0 +1,67 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +SV2_APPS_DIR="$REPO_ROOT/integration-test-framework/sv2-apps" +INTEGRATION_TESTS_DIR="$REPO_ROOT/integration-test-framework/sv2-apps/integration-tests" +SV2_APPS_REPO_URL=https://github.com/stratum-mining/sv2-apps.git + +echo "๐Ÿงช Running integration tests for sv2-miner-apps changes..." +echo "๐Ÿ“ Repository root: $REPO_ROOT" +echo "๐Ÿ“ Integration test dir: $INTEGRATION_TESTS_DIR" +mkdir -p "$REPO_ROOT/integration-test-framework" + +# Clone/update integration test framework +if [ ! -d "$SV2_APPS_DIR" ]; then + echo "๐Ÿ“ฅ Cloning integration test framework..." + cd "$(dirname "$SV2_APPS_DIR")" + git clone $SV2_APPS_REPO_URL +else + echo "๐Ÿ”„ Updating integration test framework..." + cd "$SV2_APPS_DIR" + git fetch origin + git reset --hard origin/main +fi + +if cargo nextest --version &>/dev/null; then + echo "โœ… cargo-nextest is already installed." +else + echo "๐Ÿ”ง Configuring cargo nextest..." + curl -L --proto '=https' --tlsv1.2 -sSf \ + https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + + cargo binstall cargo-nextest --secure --no-confirm + echo "โœ… cargo-nextest installed successfully." +fi + +cd "$INTEGRATION_TESTS_DIR" + +# # Add patch section to override all git dependencies with local paths +echo "๐Ÿ”ง Adding patch section to override git dependencies..." + +# Remove any existing patch section first +if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' '/^# Override git dependencies with local paths/,/^$/d' Cargo.toml +else + sed -i '/^# Override git dependencies with local paths/,/^$/d' Cargo.toml +fi + + +# Add the patch section at the end of the file +cat >> Cargo.toml << 'EOF' + +# Override git dependencies with local paths to avoid version conflicts +# TODO: will need to replace to patch.crates-io as soons as they are available and updated on the sv2-apps repo +[patch."https://github.com/stratum-mining/stratum"] +stratum-core = {path = "../../../stratum-core"} +EOF + +echo "โœ… Updated Cargo.toml to use local dependencies" +echo "๐Ÿƒ Running integration tests..." + +# Run the integration tests +RUST_BACKTRACE=1 RUST_LOG=debug cargo nextest run --nocapture --verbose + +cd "$REPO_ROOT" +echo "โœ… Integration tests completed!" diff --git a/scripts/rust/clippy.sh b/scripts/rust/clippy.sh deleted file mode 100755 index f91ba2c1e4..0000000000 --- a/scripts/rust/clippy.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -workspaces=("stratum-core" "roles" "protocols" "utils" "test/integration-tests") - -# print current rust version -echo "Rust version: $(rustc --version)" - -for workspace in "${workspaces[@]}"; do - echo "Running clippy for workspace: $workspace" - cargo clippy --manifest-path="$workspace/Cargo.toml" -- -D warnings - if [[ $? -ne 0 ]]; then - echo "Clippy failed for workspace: $workspace" - exit 1 - else - echo "Clippy passed for workspace: $workspace" - fi -done - -echo "Clippy success!" -exit 0 diff --git a/stratum-core/Cargo.toml b/stratum-core/Cargo.toml index aeea186af3..52d4ce02c4 100644 --- a/stratum-core/Cargo.toml +++ b/stratum-core/Cargo.toml @@ -12,21 +12,21 @@ homepage = "https://stratumprotocol.org" keywords = ["stratum", "mining", "bitcoin", "protocol"] [dependencies] -buffer_sv2 = { path = "../utils/buffer", version = "^2.0.0" } +buffer_sv2 = { path = "../sv2/buffer-sv2", version = "^2.0.0" } bitcoin = "0.32.5" -binary_sv2 = { path = "../protocols/v2/binary-sv2", version = "^5.0.0" } -codec_sv2 = { path = "../protocols/v2/codec-sv2", version = "^4.0.0", features = ["noise_sv2"]} -framing_sv2 = { path = "../protocols/v2/framing-sv2", version = "^5.0.0" } -noise_sv2 = { path = "../protocols/v2/noise-sv2", version = "^1.0.0" } -parsers_sv2 = { path = "../protocols/v2/parsers-sv2", version = "^0.1.0" } -handlers_sv2 = { path = "../protocols/v2/handlers-sv2", version = "^0.2.0" } -channels_sv2 = { path = "../protocols/v2/channels-sv2", version = "^2.0.0" } -common_messages_sv2 = { path = "../protocols/v2/subprotocols/common-messages", version = "^6.0.0" } -mining_sv2 = { path = "../protocols/v2/subprotocols/mining", version = "^6.0.0" } -template_distribution_sv2 = { path = "../protocols/v2/subprotocols/template-distribution", version = "^4.0.0" } -job_declaration_sv2 = { path = "../protocols/v2/subprotocols/job-declaration", version = "^5.0.0" } -sv1_api = { path = "../protocols/v1", version = "^2.1.0", optional = true } -stratum_translation = { path = "../protocols/stratum-translation", version = "^0.1.0", optional = true } +binary_sv2 = { path = "../sv2/binary-sv2", version = "^5.0.0" } +codec_sv2 = { path = "../sv2/codec-sv2", version = "^4.0.0", features = ["noise_sv2"]} +framing_sv2 = { path = "../sv2/framing-sv2", version = "^5.0.0" } +noise_sv2 = { path = "../sv2/noise-sv2", version = "^1.0.0" } +parsers_sv2 = { path = "../sv2/parsers-sv2", version = "^0.1.0" } +handlers_sv2 = { path = "../sv2/handlers-sv2", version = "^0.2.0" } +channels_sv2 = { path = "../sv2/channels-sv2", version = "^2.0.0" } +common_messages_sv2 = { path = "../sv2/subprotocols/common-messages", version = "^6.0.0" } +mining_sv2 = { path = "../sv2/subprotocols/mining", version = "^6.0.0" } +template_distribution_sv2 = { path = "../sv2/subprotocols/template-distribution", version = "^4.0.0" } +job_declaration_sv2 = { path = "../sv2/subprotocols/job-declaration", version = "^5.0.0" } +sv1_api = { path = "../sv1", version = "^2.1.0", optional = true } +stratum_translation = { path = "stratum-translation", version = "^0.1.0", optional = true } [features] with_buffer_pool = [ diff --git a/protocols/stratum-translation/Cargo.toml b/stratum-core/stratum-translation/Cargo.toml similarity index 54% rename from protocols/stratum-translation/Cargo.toml rename to stratum-core/stratum-translation/Cargo.toml index 9454524dd5..b410f998e2 100644 --- a/protocols/stratum-translation/Cargo.toml +++ b/stratum-core/stratum-translation/Cargo.toml @@ -11,8 +11,8 @@ path = "src/lib.rs" [dependencies] bitcoin = { version = "0.32.5" } -binary_sv2 = { path = "../v2/binary-sv2", version = "^5.0.0" } -mining_sv2 = { path = "../v2/subprotocols/mining", version = "^6.0.0" } -channels_sv2 = { path = "../v2/channels-sv2", version = "^2.0.0" } -v1 = { path = "../v1", package = "sv1_api", version = "^2.0.0" } +binary_sv2 = { path = "../../sv2/binary-sv2", version = "^5.0.0" } +mining_sv2 = { path = "../../sv2/subprotocols/mining", version = "^6.0.0" } +channels_sv2 = { path = "../../sv2/channels-sv2", version = "^2.0.0" } +v1 = { path = "../../sv1", package = "sv1_api", version = "^2.0.0" } tracing = "0.1" diff --git a/protocols/stratum-translation/src/error.rs b/stratum-core/stratum-translation/src/error.rs similarity index 100% rename from protocols/stratum-translation/src/error.rs rename to stratum-core/stratum-translation/src/error.rs diff --git a/protocols/stratum-translation/src/lib.rs b/stratum-core/stratum-translation/src/lib.rs similarity index 100% rename from protocols/stratum-translation/src/lib.rs rename to stratum-core/stratum-translation/src/lib.rs diff --git a/protocols/stratum-translation/src/sv1_to_sv2.rs b/stratum-core/stratum-translation/src/sv1_to_sv2.rs similarity index 100% rename from protocols/stratum-translation/src/sv1_to_sv2.rs rename to stratum-core/stratum-translation/src/sv1_to_sv2.rs diff --git a/protocols/stratum-translation/src/sv2_to_sv1.rs b/stratum-core/stratum-translation/src/sv2_to_sv1.rs similarity index 100% rename from protocols/stratum-translation/src/sv2_to_sv1.rs rename to stratum-core/stratum-translation/src/sv2_to_sv1.rs diff --git a/protocols/v1/Cargo.toml b/sv1/Cargo.toml similarity index 92% rename from protocols/v1/Cargo.toml rename to sv1/Cargo.toml index 5ef1b8df26..6e19ed8946 100644 --- a/protocols/v1/Cargo.toml +++ b/sv1/Cargo.toml @@ -20,7 +20,7 @@ hex = "0.4.3" serde = { version = "1.0.89", default-features = false, features = ["derive", "alloc"] } serde_json = { version = "1.0.64", default-features = false, features = ["alloc"] } tracing = {version = "0.1"} -binary_sv2 = { path = "../v2/binary-sv2", version = "^5.0.0" } +binary_sv2 = { path = "../sv2/binary-sv2", version = "^5.0.0" } [dev-dependencies] quickcheck = "1" diff --git a/protocols/v1/README.md b/sv1/README.md similarity index 100% rename from protocols/v1/README.md rename to sv1/README.md diff --git a/protocols/v1/examples/client_and_server.rs b/sv1/examples/client_and_server.rs similarity index 100% rename from protocols/v1/examples/client_and_server.rs rename to sv1/examples/client_and_server.rs diff --git a/protocols/v1/src/error.rs b/sv1/src/error.rs similarity index 100% rename from protocols/v1/src/error.rs rename to sv1/src/error.rs diff --git a/protocols/v1/src/json_rpc.rs b/sv1/src/json_rpc.rs similarity index 100% rename from protocols/v1/src/json_rpc.rs rename to sv1/src/json_rpc.rs diff --git a/protocols/v1/src/lib.rs b/sv1/src/lib.rs similarity index 100% rename from protocols/v1/src/lib.rs rename to sv1/src/lib.rs diff --git a/protocols/v1/src/methods/client_to_server.rs b/sv1/src/methods/client_to_server.rs similarity index 100% rename from protocols/v1/src/methods/client_to_server.rs rename to sv1/src/methods/client_to_server.rs diff --git a/protocols/v1/src/methods/mod.rs b/sv1/src/methods/mod.rs similarity index 100% rename from protocols/v1/src/methods/mod.rs rename to sv1/src/methods/mod.rs diff --git a/protocols/v1/src/methods/server_to_client.rs b/sv1/src/methods/server_to_client.rs similarity index 100% rename from protocols/v1/src/methods/server_to_client.rs rename to sv1/src/methods/server_to_client.rs diff --git a/protocols/v1/src/utils.rs b/sv1/src/utils.rs similarity index 100% rename from protocols/v1/src/utils.rs rename to sv1/src/utils.rs diff --git a/protocols/v2/binary-sv2/Cargo.toml b/sv2/binary-sv2/Cargo.toml similarity index 90% rename from protocols/v2/binary-sv2/Cargo.toml rename to sv2/binary-sv2/Cargo.toml index f3b6eb32d9..9e7585962e 100644 --- a/protocols/v2/binary-sv2/Cargo.toml +++ b/sv2/binary-sv2/Cargo.toml @@ -16,7 +16,7 @@ keywords = ["stratum", "mining", "bitcoin", "protocol"] [dependencies] derive_codec_sv2 = { path = "derive_codec", version = "^1.0.0" } quickcheck = { version = "1.0.0", optional = true } -buffer_sv2 = { path = "../../../utils/buffer", optional=true, version = "^2.0.0" } +buffer_sv2 = { path = "../buffer-sv2", optional=true, version = "^2.0.0" } [features] no_std = [] diff --git a/protocols/v2/binary-sv2/README.md b/sv2/binary-sv2/README.md similarity index 100% rename from protocols/v2/binary-sv2/README.md rename to sv2/binary-sv2/README.md diff --git a/protocols/fuzz-tests/.gitignore b/sv2/binary-sv2/derive_codec/.gitignore similarity index 100% rename from protocols/fuzz-tests/.gitignore rename to sv2/binary-sv2/derive_codec/.gitignore diff --git a/protocols/v2/binary-sv2/derive_codec/Cargo.toml b/sv2/binary-sv2/derive_codec/Cargo.toml similarity index 100% rename from protocols/v2/binary-sv2/derive_codec/Cargo.toml rename to sv2/binary-sv2/derive_codec/Cargo.toml diff --git a/protocols/v2/binary-sv2/derive_codec/README.md b/sv2/binary-sv2/derive_codec/README.md similarity index 100% rename from protocols/v2/binary-sv2/derive_codec/README.md rename to sv2/binary-sv2/derive_codec/README.md diff --git a/protocols/v2/binary-sv2/derive_codec/src/lib.rs b/sv2/binary-sv2/derive_codec/src/lib.rs similarity index 100% rename from protocols/v2/binary-sv2/derive_codec/src/lib.rs rename to sv2/binary-sv2/derive_codec/src/lib.rs diff --git a/protocols/v2/binary-sv2/examples/encode_decode.rs b/sv2/binary-sv2/examples/encode_decode.rs similarity index 100% rename from protocols/v2/binary-sv2/examples/encode_decode.rs rename to sv2/binary-sv2/examples/encode_decode.rs diff --git a/protocols/v2/binary-sv2/src/codec/decodable.rs b/sv2/binary-sv2/src/codec/decodable.rs similarity index 100% rename from protocols/v2/binary-sv2/src/codec/decodable.rs rename to sv2/binary-sv2/src/codec/decodable.rs diff --git a/protocols/v2/binary-sv2/src/codec/encodable.rs b/sv2/binary-sv2/src/codec/encodable.rs similarity index 100% rename from protocols/v2/binary-sv2/src/codec/encodable.rs rename to sv2/binary-sv2/src/codec/encodable.rs diff --git a/protocols/v2/binary-sv2/src/codec/impls.rs b/sv2/binary-sv2/src/codec/impls.rs similarity index 100% rename from protocols/v2/binary-sv2/src/codec/impls.rs rename to sv2/binary-sv2/src/codec/impls.rs diff --git a/protocols/v2/binary-sv2/src/codec/mod.rs b/sv2/binary-sv2/src/codec/mod.rs similarity index 100% rename from protocols/v2/binary-sv2/src/codec/mod.rs rename to sv2/binary-sv2/src/codec/mod.rs diff --git a/protocols/v2/binary-sv2/src/datatypes/copy_data_types.rs b/sv2/binary-sv2/src/datatypes/copy_data_types.rs similarity index 100% rename from protocols/v2/binary-sv2/src/datatypes/copy_data_types.rs rename to sv2/binary-sv2/src/datatypes/copy_data_types.rs diff --git a/protocols/v2/binary-sv2/src/datatypes/mod.rs b/sv2/binary-sv2/src/datatypes/mod.rs similarity index 100% rename from protocols/v2/binary-sv2/src/datatypes/mod.rs rename to sv2/binary-sv2/src/datatypes/mod.rs diff --git a/protocols/v2/binary-sv2/src/datatypes/non_copy_data_types/inner.rs b/sv2/binary-sv2/src/datatypes/non_copy_data_types/inner.rs similarity index 100% rename from protocols/v2/binary-sv2/src/datatypes/non_copy_data_types/inner.rs rename to sv2/binary-sv2/src/datatypes/non_copy_data_types/inner.rs diff --git a/protocols/v2/binary-sv2/src/datatypes/non_copy_data_types/mod.rs b/sv2/binary-sv2/src/datatypes/non_copy_data_types/mod.rs similarity index 100% rename from protocols/v2/binary-sv2/src/datatypes/non_copy_data_types/mod.rs rename to sv2/binary-sv2/src/datatypes/non_copy_data_types/mod.rs diff --git a/protocols/v2/binary-sv2/src/datatypes/non_copy_data_types/seq_inner.rs b/sv2/binary-sv2/src/datatypes/non_copy_data_types/seq_inner.rs similarity index 100% rename from protocols/v2/binary-sv2/src/datatypes/non_copy_data_types/seq_inner.rs rename to sv2/binary-sv2/src/datatypes/non_copy_data_types/seq_inner.rs diff --git a/protocols/v2/binary-sv2/src/lib.rs b/sv2/binary-sv2/src/lib.rs similarity index 100% rename from protocols/v2/binary-sv2/src/lib.rs rename to sv2/binary-sv2/src/lib.rs diff --git a/protocols/v2/binary-sv2/tests/test.rs b/sv2/binary-sv2/tests/test.rs similarity index 100% rename from protocols/v2/binary-sv2/tests/test.rs rename to sv2/binary-sv2/tests/test.rs diff --git a/utils/buffer/BENCHES.md b/sv2/buffer-sv2/BENCHES.md similarity index 100% rename from utils/buffer/BENCHES.md rename to sv2/buffer-sv2/BENCHES.md diff --git a/utils/buffer/Cargo.toml b/sv2/buffer-sv2/Cargo.toml similarity index 100% rename from utils/buffer/Cargo.toml rename to sv2/buffer-sv2/Cargo.toml diff --git a/utils/buffer/README.md b/sv2/buffer-sv2/README.md similarity index 100% rename from utils/buffer/README.md rename to sv2/buffer-sv2/README.md diff --git a/utils/buffer/benches/control_struct.rs b/sv2/buffer-sv2/benches/control_struct.rs similarity index 100% rename from utils/buffer/benches/control_struct.rs rename to sv2/buffer-sv2/benches/control_struct.rs diff --git a/utils/buffer/benches/pool_benchmark.rs b/sv2/buffer-sv2/benches/pool_benchmark.rs similarity index 100% rename from utils/buffer/benches/pool_benchmark.rs rename to sv2/buffer-sv2/benches/pool_benchmark.rs diff --git a/utils/buffer/benches/pool_iai.rs b/sv2/buffer-sv2/benches/pool_iai.rs similarity index 100% rename from utils/buffer/benches/pool_iai.rs rename to sv2/buffer-sv2/benches/pool_iai.rs diff --git a/utils/buffer/examples/basic_buffer_pool.rs b/sv2/buffer-sv2/examples/basic_buffer_pool.rs similarity index 100% rename from utils/buffer/examples/basic_buffer_pool.rs rename to sv2/buffer-sv2/examples/basic_buffer_pool.rs diff --git a/utils/buffer/examples/buffer_pool_exhaustion.rs b/sv2/buffer-sv2/examples/buffer_pool_exhaustion.rs similarity index 100% rename from utils/buffer/examples/buffer_pool_exhaustion.rs rename to sv2/buffer-sv2/examples/buffer_pool_exhaustion.rs diff --git a/utils/buffer/examples/variable_sized_messages.rs b/sv2/buffer-sv2/examples/variable_sized_messages.rs similarity index 100% rename from utils/buffer/examples/variable_sized_messages.rs rename to sv2/buffer-sv2/examples/variable_sized_messages.rs diff --git a/utils/buffer/fuzz/.gitignore b/sv2/buffer-sv2/fuzz/.gitignore similarity index 100% rename from utils/buffer/fuzz/.gitignore rename to sv2/buffer-sv2/fuzz/.gitignore diff --git a/utils/buffer/fuzz/Cargo.toml b/sv2/buffer-sv2/fuzz/Cargo.toml similarity index 100% rename from utils/buffer/fuzz/Cargo.toml rename to sv2/buffer-sv2/fuzz/Cargo.toml diff --git a/utils/buffer/fuzz/fuzz_targets/faster.rs b/sv2/buffer-sv2/fuzz/fuzz_targets/faster.rs similarity index 100% rename from utils/buffer/fuzz/fuzz_targets/faster.rs rename to sv2/buffer-sv2/fuzz/fuzz_targets/faster.rs diff --git a/utils/buffer/fuzz/fuzz_targets/slower.rs b/sv2/buffer-sv2/fuzz/fuzz_targets/slower.rs similarity index 100% rename from utils/buffer/fuzz/fuzz_targets/slower.rs rename to sv2/buffer-sv2/fuzz/fuzz_targets/slower.rs diff --git a/utils/buffer/fuzz/random b/sv2/buffer-sv2/fuzz/random similarity index 100% rename from utils/buffer/fuzz/random rename to sv2/buffer-sv2/fuzz/random diff --git a/utils/buffer/fuzz/run.sh b/sv2/buffer-sv2/fuzz/run.sh similarity index 100% rename from utils/buffer/fuzz/run.sh rename to sv2/buffer-sv2/fuzz/run.sh diff --git a/utils/buffer/fuzz/rust-toolchain.toml b/sv2/buffer-sv2/fuzz/rust-toolchain.toml similarity index 100% rename from utils/buffer/fuzz/rust-toolchain.toml rename to sv2/buffer-sv2/fuzz/rust-toolchain.toml diff --git a/utils/buffer/src/buffer.rs b/sv2/buffer-sv2/src/buffer.rs similarity index 100% rename from utils/buffer/src/buffer.rs rename to sv2/buffer-sv2/src/buffer.rs diff --git a/utils/buffer/src/buffer_pool/mod.rs b/sv2/buffer-sv2/src/buffer_pool/mod.rs similarity index 100% rename from utils/buffer/src/buffer_pool/mod.rs rename to sv2/buffer-sv2/src/buffer_pool/mod.rs diff --git a/utils/buffer/src/buffer_pool/pool_back.rs b/sv2/buffer-sv2/src/buffer_pool/pool_back.rs similarity index 100% rename from utils/buffer/src/buffer_pool/pool_back.rs rename to sv2/buffer-sv2/src/buffer_pool/pool_back.rs diff --git a/utils/buffer/src/lib.rs b/sv2/buffer-sv2/src/lib.rs similarity index 100% rename from utils/buffer/src/lib.rs rename to sv2/buffer-sv2/src/lib.rs diff --git a/utils/buffer/src/slice.rs b/sv2/buffer-sv2/src/slice.rs similarity index 100% rename from utils/buffer/src/slice.rs rename to sv2/buffer-sv2/src/slice.rs diff --git a/utils/buffer/src/test.rs b/sv2/buffer-sv2/src/test.rs similarity index 100% rename from utils/buffer/src/test.rs rename to sv2/buffer-sv2/src/test.rs diff --git a/protocols/v2/channels-sv2/Cargo.toml b/sv2/channels-sv2/Cargo.toml similarity index 100% rename from protocols/v2/channels-sv2/Cargo.toml rename to sv2/channels-sv2/Cargo.toml diff --git a/protocols/v2/channels-sv2/README.md b/sv2/channels-sv2/README.md similarity index 100% rename from protocols/v2/channels-sv2/README.md rename to sv2/channels-sv2/README.md diff --git a/protocols/v2/channels-sv2/src/bip141.rs b/sv2/channels-sv2/src/bip141.rs similarity index 100% rename from protocols/v2/channels-sv2/src/bip141.rs rename to sv2/channels-sv2/src/bip141.rs diff --git a/protocols/v2/channels-sv2/src/chain_tip.rs b/sv2/channels-sv2/src/chain_tip.rs similarity index 100% rename from protocols/v2/channels-sv2/src/chain_tip.rs rename to sv2/channels-sv2/src/chain_tip.rs diff --git a/protocols/v2/channels-sv2/src/client/error.rs b/sv2/channels-sv2/src/client/error.rs similarity index 100% rename from protocols/v2/channels-sv2/src/client/error.rs rename to sv2/channels-sv2/src/client/error.rs diff --git a/protocols/v2/channels-sv2/src/client/extended.rs b/sv2/channels-sv2/src/client/extended.rs similarity index 100% rename from protocols/v2/channels-sv2/src/client/extended.rs rename to sv2/channels-sv2/src/client/extended.rs diff --git a/protocols/v2/channels-sv2/src/client/group.rs b/sv2/channels-sv2/src/client/group.rs similarity index 100% rename from protocols/v2/channels-sv2/src/client/group.rs rename to sv2/channels-sv2/src/client/group.rs diff --git a/protocols/v2/channels-sv2/src/client/mod.rs b/sv2/channels-sv2/src/client/mod.rs similarity index 100% rename from protocols/v2/channels-sv2/src/client/mod.rs rename to sv2/channels-sv2/src/client/mod.rs diff --git a/protocols/v2/channels-sv2/src/client/share_accounting.rs b/sv2/channels-sv2/src/client/share_accounting.rs similarity index 100% rename from protocols/v2/channels-sv2/src/client/share_accounting.rs rename to sv2/channels-sv2/src/client/share_accounting.rs diff --git a/protocols/v2/channels-sv2/src/client/standard.rs b/sv2/channels-sv2/src/client/standard.rs similarity index 100% rename from protocols/v2/channels-sv2/src/client/standard.rs rename to sv2/channels-sv2/src/client/standard.rs diff --git a/protocols/v2/channels-sv2/src/lib.rs b/sv2/channels-sv2/src/lib.rs similarity index 100% rename from protocols/v2/channels-sv2/src/lib.rs rename to sv2/channels-sv2/src/lib.rs diff --git a/protocols/v2/channels-sv2/src/merkle_root.rs b/sv2/channels-sv2/src/merkle_root.rs similarity index 100% rename from protocols/v2/channels-sv2/src/merkle_root.rs rename to sv2/channels-sv2/src/merkle_root.rs diff --git a/protocols/v2/channels-sv2/src/outputs.rs b/sv2/channels-sv2/src/outputs.rs similarity index 100% rename from protocols/v2/channels-sv2/src/outputs.rs rename to sv2/channels-sv2/src/outputs.rs diff --git a/protocols/v2/channels-sv2/src/server/error.rs b/sv2/channels-sv2/src/server/error.rs similarity index 100% rename from protocols/v2/channels-sv2/src/server/error.rs rename to sv2/channels-sv2/src/server/error.rs diff --git a/protocols/v2/channels-sv2/src/server/extended.rs b/sv2/channels-sv2/src/server/extended.rs similarity index 100% rename from protocols/v2/channels-sv2/src/server/extended.rs rename to sv2/channels-sv2/src/server/extended.rs diff --git a/protocols/v2/channels-sv2/src/server/group.rs b/sv2/channels-sv2/src/server/group.rs similarity index 100% rename from protocols/v2/channels-sv2/src/server/group.rs rename to sv2/channels-sv2/src/server/group.rs diff --git a/protocols/v2/channels-sv2/src/server/jobs/error.rs b/sv2/channels-sv2/src/server/jobs/error.rs similarity index 100% rename from protocols/v2/channels-sv2/src/server/jobs/error.rs rename to sv2/channels-sv2/src/server/jobs/error.rs diff --git a/protocols/v2/channels-sv2/src/server/jobs/extended.rs b/sv2/channels-sv2/src/server/jobs/extended.rs similarity index 100% rename from protocols/v2/channels-sv2/src/server/jobs/extended.rs rename to sv2/channels-sv2/src/server/jobs/extended.rs diff --git a/protocols/v2/channels-sv2/src/server/jobs/factory.rs b/sv2/channels-sv2/src/server/jobs/factory.rs similarity index 100% rename from protocols/v2/channels-sv2/src/server/jobs/factory.rs rename to sv2/channels-sv2/src/server/jobs/factory.rs diff --git a/protocols/v2/channels-sv2/src/server/jobs/job_store.rs b/sv2/channels-sv2/src/server/jobs/job_store.rs similarity index 100% rename from protocols/v2/channels-sv2/src/server/jobs/job_store.rs rename to sv2/channels-sv2/src/server/jobs/job_store.rs diff --git a/protocols/v2/channels-sv2/src/server/jobs/mod.rs b/sv2/channels-sv2/src/server/jobs/mod.rs similarity index 100% rename from protocols/v2/channels-sv2/src/server/jobs/mod.rs rename to sv2/channels-sv2/src/server/jobs/mod.rs diff --git a/protocols/v2/channels-sv2/src/server/jobs/standard.rs b/sv2/channels-sv2/src/server/jobs/standard.rs similarity index 100% rename from protocols/v2/channels-sv2/src/server/jobs/standard.rs rename to sv2/channels-sv2/src/server/jobs/standard.rs diff --git a/protocols/v2/channels-sv2/src/server/mod.rs b/sv2/channels-sv2/src/server/mod.rs similarity index 100% rename from protocols/v2/channels-sv2/src/server/mod.rs rename to sv2/channels-sv2/src/server/mod.rs diff --git a/protocols/v2/channels-sv2/src/server/share_accounting.rs b/sv2/channels-sv2/src/server/share_accounting.rs similarity index 100% rename from protocols/v2/channels-sv2/src/server/share_accounting.rs rename to sv2/channels-sv2/src/server/share_accounting.rs diff --git a/protocols/v2/channels-sv2/src/server/standard.rs b/sv2/channels-sv2/src/server/standard.rs similarity index 100% rename from protocols/v2/channels-sv2/src/server/standard.rs rename to sv2/channels-sv2/src/server/standard.rs diff --git a/protocols/v2/channels-sv2/src/target.rs b/sv2/channels-sv2/src/target.rs similarity index 100% rename from protocols/v2/channels-sv2/src/target.rs rename to sv2/channels-sv2/src/target.rs diff --git a/protocols/v2/channels-sv2/src/vardiff/classic.rs b/sv2/channels-sv2/src/vardiff/classic.rs similarity index 100% rename from protocols/v2/channels-sv2/src/vardiff/classic.rs rename to sv2/channels-sv2/src/vardiff/classic.rs diff --git a/protocols/v2/channels-sv2/src/vardiff/error.rs b/sv2/channels-sv2/src/vardiff/error.rs similarity index 100% rename from protocols/v2/channels-sv2/src/vardiff/error.rs rename to sv2/channels-sv2/src/vardiff/error.rs diff --git a/protocols/v2/channels-sv2/src/vardiff/mod.rs b/sv2/channels-sv2/src/vardiff/mod.rs similarity index 100% rename from protocols/v2/channels-sv2/src/vardiff/mod.rs rename to sv2/channels-sv2/src/vardiff/mod.rs diff --git a/protocols/v2/channels-sv2/src/vardiff/test/classic.rs b/sv2/channels-sv2/src/vardiff/test/classic.rs similarity index 100% rename from protocols/v2/channels-sv2/src/vardiff/test/classic.rs rename to sv2/channels-sv2/src/vardiff/test/classic.rs diff --git a/protocols/v2/channels-sv2/src/vardiff/test/mod.rs b/sv2/channels-sv2/src/vardiff/test/mod.rs similarity index 100% rename from protocols/v2/channels-sv2/src/vardiff/test/mod.rs rename to sv2/channels-sv2/src/vardiff/test/mod.rs diff --git a/protocols/v2/codec-sv2/Cargo.toml b/sv2/codec-sv2/Cargo.toml similarity index 70% rename from protocols/v2/codec-sv2/Cargo.toml rename to sv2/codec-sv2/Cargo.toml index 43a825111b..553df291ab 100644 --- a/protocols/v2/codec-sv2/Cargo.toml +++ b/sv2/codec-sv2/Cargo.toml @@ -12,10 +12,10 @@ homepage = "https://stratumprotocol.org" keywords = ["stratum", "mining", "bitcoin", "protocol"] [dependencies] -framing_sv2 = { path = "../../../protocols/v2/framing-sv2", version = "^5.0.0" } -noise_sv2 = { path = "../../../protocols/v2/noise-sv2", default-features = false, optional = true, version = "^1.0.0" } -binary_sv2 = { path = "../../../protocols/v2/binary-sv2", version = "^5.0.0" } -buffer_sv2 = { path = "../../../utils/buffer", version = "^2.0.0" } +framing_sv2 = { path = "../framing-sv2", version = "^5.0.0" } +noise_sv2 = { path = "../noise-sv2", default-features = false, optional = true, version = "^1.0.0" } +binary_sv2 = { path = "../binary-sv2", version = "^5.0.0" } +buffer_sv2 = { path = "../buffer-sv2", version = "^2.0.0" } rand = { version = "0.8.5", default-features = false } tracing = { version = "0.1", optional = true } diff --git a/protocols/v2/codec-sv2/README.md b/sv2/codec-sv2/README.md similarity index 100% rename from protocols/v2/codec-sv2/README.md rename to sv2/codec-sv2/README.md diff --git a/protocols/v2/codec-sv2/examples/encrypted.rs b/sv2/codec-sv2/examples/encrypted.rs similarity index 100% rename from protocols/v2/codec-sv2/examples/encrypted.rs rename to sv2/codec-sv2/examples/encrypted.rs diff --git a/protocols/v2/codec-sv2/examples/unencrypted.rs b/sv2/codec-sv2/examples/unencrypted.rs similarity index 100% rename from protocols/v2/codec-sv2/examples/unencrypted.rs rename to sv2/codec-sv2/examples/unencrypted.rs diff --git a/protocols/v2/codec-sv2/src/decoder.rs b/sv2/codec-sv2/src/decoder.rs similarity index 97% rename from protocols/v2/codec-sv2/src/decoder.rs rename to sv2/codec-sv2/src/decoder.rs index c27b48ffcc..7f527066e7 100644 --- a/protocols/v2/codec-sv2/src/decoder.rs +++ b/sv2/codec-sv2/src/decoder.rs @@ -228,9 +228,15 @@ impl<'a, T: Serialize + GetSize + Deserialize<'a>, B: IsBuffer + AeadBuffer> Wit let src = self.noise_buffer.get_data_owned().as_mut().to_vec(); // Since the frame length is already validated during the handshake process, this - // operation is infallible + // operation is infallible. + // Conditionally call `.into()` based on `with_buffer_pool` feature to handle differences + // between Clippy and test builds. See: https://github.com/stratum-mining/stratum/pull/1860#discussion_r2457908851 + #[cfg(feature = "with_buffer_pool")] let frame = HandShakeFrame::from_bytes_unchecked(src.into()); + #[cfg(not(feature = "with_buffer_pool"))] + let frame = HandShakeFrame::from_bytes_unchecked(src); + frame.into() } diff --git a/protocols/v2/codec-sv2/src/encoder.rs b/sv2/codec-sv2/src/encoder.rs similarity index 100% rename from protocols/v2/codec-sv2/src/encoder.rs rename to sv2/codec-sv2/src/encoder.rs diff --git a/protocols/v2/codec-sv2/src/error.rs b/sv2/codec-sv2/src/error.rs similarity index 100% rename from protocols/v2/codec-sv2/src/error.rs rename to sv2/codec-sv2/src/error.rs diff --git a/protocols/v2/codec-sv2/src/lib.rs b/sv2/codec-sv2/src/lib.rs similarity index 100% rename from protocols/v2/codec-sv2/src/lib.rs rename to sv2/codec-sv2/src/lib.rs diff --git a/protocols/v2/framing-sv2/Cargo.toml b/sv2/framing-sv2/Cargo.toml similarity index 70% rename from protocols/v2/framing-sv2/Cargo.toml rename to sv2/framing-sv2/Cargo.toml index fb3ebfd996..992bd1c9e5 100644 --- a/protocols/v2/framing-sv2/Cargo.toml +++ b/sv2/framing-sv2/Cargo.toml @@ -14,12 +14,12 @@ keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -binary_sv2 = { path = "../../../protocols/v2/binary-sv2", version = "^5.0.0" } -buffer_sv2 = { path = "../../../utils/buffer", optional=true, version = "^2.0.0" } -noise_sv2 = { path = "../../../protocols/v2/noise-sv2", version = "^1.0.0" } +binary_sv2 = { path = "../binary-sv2", version = "^5.0.0" } +buffer_sv2 = { path = "../buffer-sv2", optional=true, version = "^2.0.0" } +noise_sv2 = { path = "../noise-sv2", version = "^1.0.0" } [dev-dependencies] -noise_sv2 = { path = "../../../protocols/v2/noise-sv2", version = "^1.0.0" } +noise_sv2 = { path = "../noise-sv2", version = "^1.0.0" } rand = "0.8.3" secp256k1 = { version = "0.28.2", default-features = false, features =["alloc","rand","rand-std"] } diff --git a/protocols/v2/framing-sv2/README.md b/sv2/framing-sv2/README.md similarity index 100% rename from protocols/v2/framing-sv2/README.md rename to sv2/framing-sv2/README.md diff --git a/protocols/v2/framing-sv2/examples/sv2_frame.rs b/sv2/framing-sv2/examples/sv2_frame.rs similarity index 100% rename from protocols/v2/framing-sv2/examples/sv2_frame.rs rename to sv2/framing-sv2/examples/sv2_frame.rs diff --git a/protocols/v2/framing-sv2/src/error.rs b/sv2/framing-sv2/src/error.rs similarity index 100% rename from protocols/v2/framing-sv2/src/error.rs rename to sv2/framing-sv2/src/error.rs diff --git a/protocols/v2/framing-sv2/src/framing.rs b/sv2/framing-sv2/src/framing.rs similarity index 100% rename from protocols/v2/framing-sv2/src/framing.rs rename to sv2/framing-sv2/src/framing.rs diff --git a/protocols/v2/framing-sv2/src/header.rs b/sv2/framing-sv2/src/header.rs similarity index 100% rename from protocols/v2/framing-sv2/src/header.rs rename to sv2/framing-sv2/src/header.rs diff --git a/protocols/v2/framing-sv2/src/lib.rs b/sv2/framing-sv2/src/lib.rs similarity index 100% rename from protocols/v2/framing-sv2/src/lib.rs rename to sv2/framing-sv2/src/lib.rs diff --git a/protocols/v2/handlers-sv2/Cargo.toml b/sv2/handlers-sv2/Cargo.toml similarity index 100% rename from protocols/v2/handlers-sv2/Cargo.toml rename to sv2/handlers-sv2/Cargo.toml diff --git a/protocols/v2/handlers-sv2/README.md b/sv2/handlers-sv2/README.md similarity index 100% rename from protocols/v2/handlers-sv2/README.md rename to sv2/handlers-sv2/README.md diff --git a/protocols/v2/handlers-sv2/src/common.rs b/sv2/handlers-sv2/src/common.rs similarity index 100% rename from protocols/v2/handlers-sv2/src/common.rs rename to sv2/handlers-sv2/src/common.rs diff --git a/protocols/v2/handlers-sv2/src/error.rs b/sv2/handlers-sv2/src/error.rs similarity index 100% rename from protocols/v2/handlers-sv2/src/error.rs rename to sv2/handlers-sv2/src/error.rs diff --git a/protocols/v2/handlers-sv2/src/job_declaration.rs b/sv2/handlers-sv2/src/job_declaration.rs similarity index 100% rename from protocols/v2/handlers-sv2/src/job_declaration.rs rename to sv2/handlers-sv2/src/job_declaration.rs diff --git a/protocols/v2/handlers-sv2/src/lib.rs b/sv2/handlers-sv2/src/lib.rs similarity index 100% rename from protocols/v2/handlers-sv2/src/lib.rs rename to sv2/handlers-sv2/src/lib.rs diff --git a/protocols/v2/handlers-sv2/src/mining.rs b/sv2/handlers-sv2/src/mining.rs similarity index 100% rename from protocols/v2/handlers-sv2/src/mining.rs rename to sv2/handlers-sv2/src/mining.rs diff --git a/protocols/v2/handlers-sv2/src/template_distribution.rs b/sv2/handlers-sv2/src/template_distribution.rs similarity index 100% rename from protocols/v2/handlers-sv2/src/template_distribution.rs rename to sv2/handlers-sv2/src/template_distribution.rs diff --git a/protocols/v2/noise-sv2/Cargo.toml b/sv2/noise-sv2/Cargo.toml similarity index 100% rename from protocols/v2/noise-sv2/Cargo.toml rename to sv2/noise-sv2/Cargo.toml diff --git a/protocols/v2/noise-sv2/README.md b/sv2/noise-sv2/README.md similarity index 100% rename from protocols/v2/noise-sv2/README.md rename to sv2/noise-sv2/README.md diff --git a/protocols/v2/noise-sv2/examples/handshake.rs b/sv2/noise-sv2/examples/handshake.rs similarity index 100% rename from protocols/v2/noise-sv2/examples/handshake.rs rename to sv2/noise-sv2/examples/handshake.rs diff --git a/protocols/v2/noise-sv2/src/aed_cipher.rs b/sv2/noise-sv2/src/aed_cipher.rs similarity index 100% rename from protocols/v2/noise-sv2/src/aed_cipher.rs rename to sv2/noise-sv2/src/aed_cipher.rs diff --git a/protocols/v2/noise-sv2/src/cipher_state.rs b/sv2/noise-sv2/src/cipher_state.rs similarity index 100% rename from protocols/v2/noise-sv2/src/cipher_state.rs rename to sv2/noise-sv2/src/cipher_state.rs diff --git a/protocols/v2/noise-sv2/src/error.rs b/sv2/noise-sv2/src/error.rs similarity index 100% rename from protocols/v2/noise-sv2/src/error.rs rename to sv2/noise-sv2/src/error.rs diff --git a/protocols/v2/noise-sv2/src/handshake.rs b/sv2/noise-sv2/src/handshake.rs similarity index 100% rename from protocols/v2/noise-sv2/src/handshake.rs rename to sv2/noise-sv2/src/handshake.rs diff --git a/protocols/v2/noise-sv2/src/initiator.rs b/sv2/noise-sv2/src/initiator.rs similarity index 100% rename from protocols/v2/noise-sv2/src/initiator.rs rename to sv2/noise-sv2/src/initiator.rs diff --git a/protocols/v2/noise-sv2/src/lib.rs b/sv2/noise-sv2/src/lib.rs similarity index 100% rename from protocols/v2/noise-sv2/src/lib.rs rename to sv2/noise-sv2/src/lib.rs diff --git a/protocols/v2/noise-sv2/src/responder.rs b/sv2/noise-sv2/src/responder.rs similarity index 100% rename from protocols/v2/noise-sv2/src/responder.rs rename to sv2/noise-sv2/src/responder.rs diff --git a/protocols/v2/noise-sv2/src/signature_message.rs b/sv2/noise-sv2/src/signature_message.rs similarity index 100% rename from protocols/v2/noise-sv2/src/signature_message.rs rename to sv2/noise-sv2/src/signature_message.rs diff --git a/protocols/v2/noise-sv2/src/test.rs b/sv2/noise-sv2/src/test.rs similarity index 100% rename from protocols/v2/noise-sv2/src/test.rs rename to sv2/noise-sv2/src/test.rs diff --git a/protocols/v2/parsers-sv2/Cargo.toml b/sv2/parsers-sv2/Cargo.toml similarity index 100% rename from protocols/v2/parsers-sv2/Cargo.toml rename to sv2/parsers-sv2/Cargo.toml diff --git a/protocols/v2/parsers-sv2/README.md b/sv2/parsers-sv2/README.md similarity index 100% rename from protocols/v2/parsers-sv2/README.md rename to sv2/parsers-sv2/README.md diff --git a/protocols/v2/parsers-sv2/src/error.rs b/sv2/parsers-sv2/src/error.rs similarity index 100% rename from protocols/v2/parsers-sv2/src/error.rs rename to sv2/parsers-sv2/src/error.rs diff --git a/protocols/v2/parsers-sv2/src/lib.rs b/sv2/parsers-sv2/src/lib.rs similarity index 100% rename from protocols/v2/parsers-sv2/src/lib.rs rename to sv2/parsers-sv2/src/lib.rs diff --git a/protocols/v2/subprotocols/common-messages/Cargo.toml b/sv2/subprotocols/common-messages/Cargo.toml similarity index 100% rename from protocols/v2/subprotocols/common-messages/Cargo.toml rename to sv2/subprotocols/common-messages/Cargo.toml diff --git a/protocols/v2/subprotocols/common-messages/README.md b/sv2/subprotocols/common-messages/README.md similarity index 100% rename from protocols/v2/subprotocols/common-messages/README.md rename to sv2/subprotocols/common-messages/README.md diff --git a/protocols/v2/subprotocols/common-messages/src/channel_endpoint_changed.rs b/sv2/subprotocols/common-messages/src/channel_endpoint_changed.rs similarity index 100% rename from protocols/v2/subprotocols/common-messages/src/channel_endpoint_changed.rs rename to sv2/subprotocols/common-messages/src/channel_endpoint_changed.rs diff --git a/protocols/v2/subprotocols/common-messages/src/lib.rs b/sv2/subprotocols/common-messages/src/lib.rs similarity index 100% rename from protocols/v2/subprotocols/common-messages/src/lib.rs rename to sv2/subprotocols/common-messages/src/lib.rs diff --git a/protocols/v2/subprotocols/common-messages/src/reconnect.rs b/sv2/subprotocols/common-messages/src/reconnect.rs similarity index 100% rename from protocols/v2/subprotocols/common-messages/src/reconnect.rs rename to sv2/subprotocols/common-messages/src/reconnect.rs diff --git a/protocols/v2/subprotocols/common-messages/src/setup_connection.rs b/sv2/subprotocols/common-messages/src/setup_connection.rs similarity index 100% rename from protocols/v2/subprotocols/common-messages/src/setup_connection.rs rename to sv2/subprotocols/common-messages/src/setup_connection.rs diff --git a/protocols/v2/subprotocols/job-declaration/Cargo.toml b/sv2/subprotocols/job-declaration/Cargo.toml similarity index 100% rename from protocols/v2/subprotocols/job-declaration/Cargo.toml rename to sv2/subprotocols/job-declaration/Cargo.toml diff --git a/protocols/v2/subprotocols/job-declaration/README.md b/sv2/subprotocols/job-declaration/README.md similarity index 100% rename from protocols/v2/subprotocols/job-declaration/README.md rename to sv2/subprotocols/job-declaration/README.md diff --git a/protocols/v2/subprotocols/job-declaration/job-negotiation-flow.png b/sv2/subprotocols/job-declaration/job-negotiation-flow.png similarity index 100% rename from protocols/v2/subprotocols/job-declaration/job-negotiation-flow.png rename to sv2/subprotocols/job-declaration/job-negotiation-flow.png diff --git a/protocols/v2/subprotocols/job-declaration/src/allocate_mining_job_token.rs b/sv2/subprotocols/job-declaration/src/allocate_mining_job_token.rs similarity index 100% rename from protocols/v2/subprotocols/job-declaration/src/allocate_mining_job_token.rs rename to sv2/subprotocols/job-declaration/src/allocate_mining_job_token.rs diff --git a/protocols/v2/subprotocols/job-declaration/src/declare_mining_job.rs b/sv2/subprotocols/job-declaration/src/declare_mining_job.rs similarity index 100% rename from protocols/v2/subprotocols/job-declaration/src/declare_mining_job.rs rename to sv2/subprotocols/job-declaration/src/declare_mining_job.rs diff --git a/protocols/v2/subprotocols/job-declaration/src/lib.rs b/sv2/subprotocols/job-declaration/src/lib.rs similarity index 100% rename from protocols/v2/subprotocols/job-declaration/src/lib.rs rename to sv2/subprotocols/job-declaration/src/lib.rs diff --git a/protocols/v2/subprotocols/job-declaration/src/provide_missing_transactions.rs b/sv2/subprotocols/job-declaration/src/provide_missing_transactions.rs similarity index 100% rename from protocols/v2/subprotocols/job-declaration/src/provide_missing_transactions.rs rename to sv2/subprotocols/job-declaration/src/provide_missing_transactions.rs diff --git a/protocols/v2/subprotocols/job-declaration/src/push_solution.rs b/sv2/subprotocols/job-declaration/src/push_solution.rs similarity index 100% rename from protocols/v2/subprotocols/job-declaration/src/push_solution.rs rename to sv2/subprotocols/job-declaration/src/push_solution.rs diff --git a/protocols/v2/subprotocols/mining/Cargo.toml b/sv2/subprotocols/mining/Cargo.toml similarity index 100% rename from protocols/v2/subprotocols/mining/Cargo.toml rename to sv2/subprotocols/mining/Cargo.toml diff --git a/protocols/v2/subprotocols/mining/README.md b/sv2/subprotocols/mining/README.md similarity index 100% rename from protocols/v2/subprotocols/mining/README.md rename to sv2/subprotocols/mining/README.md diff --git a/protocols/v2/subprotocols/mining/src/close_channel.rs b/sv2/subprotocols/mining/src/close_channel.rs similarity index 100% rename from protocols/v2/subprotocols/mining/src/close_channel.rs rename to sv2/subprotocols/mining/src/close_channel.rs diff --git a/protocols/v2/subprotocols/mining/src/lib.rs b/sv2/subprotocols/mining/src/lib.rs similarity index 100% rename from protocols/v2/subprotocols/mining/src/lib.rs rename to sv2/subprotocols/mining/src/lib.rs diff --git a/protocols/v2/subprotocols/mining/src/new_mining_job.rs b/sv2/subprotocols/mining/src/new_mining_job.rs similarity index 100% rename from protocols/v2/subprotocols/mining/src/new_mining_job.rs rename to sv2/subprotocols/mining/src/new_mining_job.rs diff --git a/protocols/v2/subprotocols/mining/src/open_channel.rs b/sv2/subprotocols/mining/src/open_channel.rs similarity index 100% rename from protocols/v2/subprotocols/mining/src/open_channel.rs rename to sv2/subprotocols/mining/src/open_channel.rs diff --git a/protocols/v2/subprotocols/mining/src/set_custom_mining_job.rs b/sv2/subprotocols/mining/src/set_custom_mining_job.rs similarity index 100% rename from protocols/v2/subprotocols/mining/src/set_custom_mining_job.rs rename to sv2/subprotocols/mining/src/set_custom_mining_job.rs diff --git a/protocols/v2/subprotocols/mining/src/set_extranonce_prefix.rs b/sv2/subprotocols/mining/src/set_extranonce_prefix.rs similarity index 100% rename from protocols/v2/subprotocols/mining/src/set_extranonce_prefix.rs rename to sv2/subprotocols/mining/src/set_extranonce_prefix.rs diff --git a/protocols/v2/subprotocols/mining/src/set_group_channel.rs b/sv2/subprotocols/mining/src/set_group_channel.rs similarity index 100% rename from protocols/v2/subprotocols/mining/src/set_group_channel.rs rename to sv2/subprotocols/mining/src/set_group_channel.rs diff --git a/protocols/v2/subprotocols/mining/src/set_new_prev_hash.rs b/sv2/subprotocols/mining/src/set_new_prev_hash.rs similarity index 100% rename from protocols/v2/subprotocols/mining/src/set_new_prev_hash.rs rename to sv2/subprotocols/mining/src/set_new_prev_hash.rs diff --git a/protocols/v2/subprotocols/mining/src/set_target.rs b/sv2/subprotocols/mining/src/set_target.rs similarity index 100% rename from protocols/v2/subprotocols/mining/src/set_target.rs rename to sv2/subprotocols/mining/src/set_target.rs diff --git a/protocols/v2/subprotocols/mining/src/submit_shares.rs b/sv2/subprotocols/mining/src/submit_shares.rs similarity index 100% rename from protocols/v2/subprotocols/mining/src/submit_shares.rs rename to sv2/subprotocols/mining/src/submit_shares.rs diff --git a/protocols/v2/subprotocols/mining/src/update_channel.rs b/sv2/subprotocols/mining/src/update_channel.rs similarity index 100% rename from protocols/v2/subprotocols/mining/src/update_channel.rs rename to sv2/subprotocols/mining/src/update_channel.rs diff --git a/protocols/v2/subprotocols/template-distribution/Cargo.toml b/sv2/subprotocols/template-distribution/Cargo.toml similarity index 100% rename from protocols/v2/subprotocols/template-distribution/Cargo.toml rename to sv2/subprotocols/template-distribution/Cargo.toml diff --git a/protocols/v2/subprotocols/template-distribution/README.md b/sv2/subprotocols/template-distribution/README.md similarity index 100% rename from protocols/v2/subprotocols/template-distribution/README.md rename to sv2/subprotocols/template-distribution/README.md diff --git a/protocols/v2/subprotocols/template-distribution/src/coinbase_output_constraints.rs b/sv2/subprotocols/template-distribution/src/coinbase_output_constraints.rs similarity index 100% rename from protocols/v2/subprotocols/template-distribution/src/coinbase_output_constraints.rs rename to sv2/subprotocols/template-distribution/src/coinbase_output_constraints.rs diff --git a/protocols/v2/subprotocols/template-distribution/src/lib.rs b/sv2/subprotocols/template-distribution/src/lib.rs similarity index 100% rename from protocols/v2/subprotocols/template-distribution/src/lib.rs rename to sv2/subprotocols/template-distribution/src/lib.rs diff --git a/protocols/v2/subprotocols/template-distribution/src/new_template.rs b/sv2/subprotocols/template-distribution/src/new_template.rs similarity index 100% rename from protocols/v2/subprotocols/template-distribution/src/new_template.rs rename to sv2/subprotocols/template-distribution/src/new_template.rs diff --git a/protocols/v2/subprotocols/template-distribution/src/request_transaction_data.rs b/sv2/subprotocols/template-distribution/src/request_transaction_data.rs similarity index 100% rename from protocols/v2/subprotocols/template-distribution/src/request_transaction_data.rs rename to sv2/subprotocols/template-distribution/src/request_transaction_data.rs diff --git a/protocols/v2/subprotocols/template-distribution/src/set_new_prev_hash.rs b/sv2/subprotocols/template-distribution/src/set_new_prev_hash.rs similarity index 100% rename from protocols/v2/subprotocols/template-distribution/src/set_new_prev_hash.rs rename to sv2/subprotocols/template-distribution/src/set_new_prev_hash.rs diff --git a/protocols/v2/subprotocols/template-distribution/src/submit_solution.rs b/sv2/subprotocols/template-distribution/src/submit_solution.rs similarity index 100% rename from protocols/v2/subprotocols/template-distribution/src/submit_solution.rs rename to sv2/subprotocols/template-distribution/src/submit_solution.rs diff --git a/protocols/tarpaulin.toml b/tarpaulin.toml similarity index 100% rename from protocols/tarpaulin.toml rename to tarpaulin.toml diff --git a/test/integration-tests/.config/nextest.toml b/test/integration-tests/.config/nextest.toml deleted file mode 100644 index 5f5d26a09c..0000000000 --- a/test/integration-tests/.config/nextest.toml +++ /dev/null @@ -1,17 +0,0 @@ -[profile.default] - -# SRI has flaky integration tests, which we are ok to live with for now -# but if a test fails more than 3 times, it's safe to assume it's failing deterministically -# and that's a reliable indication that we shouldn't merge this PR -retries = { backoff = "fixed", count = 3, delay = "2s" } - -# only run one test at a time, which allows a human-friendly experience for inspecting logs -test-threads = 1 - -# label as slow if a test runs for more than 60s -# kill it after 120s -slow-timeout = { period = "60s", terminate-after = 2 } - -# display status for all levels (pass, fail, flaky, slow, etc) -status-level = "all" -final-status-level = "all" \ No newline at end of file diff --git a/test/integration-tests/Cargo.lock b/test/integration-tests/Cargo.lock deleted file mode 100644 index d9135ea85d..0000000000 --- a/test/integration-tests/Cargo.lock +++ /dev/null @@ -1,3249 +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 0.2.16", - "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 = "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 = "anyhow" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" - -[[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", - "futures-core", -] - -[[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-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 = "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.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[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", - "base64 0.21.7", - "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", - "serde", -] - -[[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" -dependencies = [ - "serde", -] - -[[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", - "serde", -] - -[[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", - "serde", -] - -[[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 = "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 = "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 = "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 = "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 0.8.5", - "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 0.8.5", - "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 0.2.16", - "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 = "corepc-client" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c6b8eaeccd3df6c1f264cd6ff8cf5df7d056029473ce9551b2a6257832d38e0" -dependencies = [ - "bitcoin", - "corepc-types", - "jsonrpc", - "log", - "serde", - "serde_json", -] - -[[package]] -name = "corepc-node" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2bcc6e09458f052024ec36e4728bd5619e248643da6175876eb3b10ca6d4d86" -dependencies = [ - "anyhow", - "corepc-client", - "log", - "serde_json", - "tempfile", - "which", -] - -[[package]] -name = "corepc-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4bc664fdaeeae1eaf459edb88650af3009b758010fc69b423c5b38142446cfb" -dependencies = [ - "bitcoin", - "serde", - "serde_json", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[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 0.6.4", - "typenum", -] - -[[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 = "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 = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "filetime" -version = "0.2.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.60.2", -] - -[[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 0.8.5", - "rustc-hex", - "static_assertions", -] - -[[package]] -name = "flate2" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[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-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 0.11.1+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.7+wasi-0.2.4", -] - -[[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 = "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 = "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 = "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", - "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 = "integration_tests_sv2" -version = "0.1.0" -dependencies = [ - "async-channel", - "corepc-node", - "flate2", - "jd_client_sv2", - "jd_server", - "mining_device", - "minreq", - "once_cell", - "pool_sv2", - "rand 0.9.2", - "stratum-apps", - "tar", - "tokio", - "tokio-util", - "tracing", - "tracing-subscriber", - "translator_sv2", -] - -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags", - "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_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[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", - "clap", - "config", - "serde", - "stratum-apps", - "tokio", - "tracing", -] - -[[package]] -name = "jd_server" -version = "0.1.3" -dependencies = [ - "async-channel", - "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 0.8.5", - "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 = "json5" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" -dependencies = [ - "pest", - "pest_derive", - "serde", -] - -[[package]] -name = "jsonrpc" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3662a38d341d77efecb73caf01420cfa5aa63c0253fd7bc05289ef9f6616e1bf" -dependencies = [ - "base64 0.13.1", - "minreq", - "serde", - "serde_json", -] - -[[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 = "libredox" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" -dependencies = [ - "bitflags", - "libc", - "redox_syscall", -] - -[[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" - -[[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", - "async-recursion", - "binary_sv2 4.0.0", - "clap", - "codec_sv2 3.0.1", - "common_messages_sv2 6.0.1", - "framing_sv2 5.0.1", - "futures", - "mining_sv2 5.0.1", - "network_helpers_sv2", - "noise_sv2 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-format", - "parsers_sv2 0.1.1", - "primitive-types", - "rand 0.8.5", - "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", - "simd-adler32", -] - -[[package]] -name = "minreq" -version = "2.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0c420feb01b9fb5061f8c8f452534361dd783756dcf38ec45191ce55e7a161" -dependencies = [ - "log", - "once_cell", - "rustls", - "rustls-webpki", - "serde", - "serde_json", - "webpki-roots", -] - -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "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", - "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 0.8.5", - "rand_chacha 0.3.1", - "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 0.8.5", - "rand_chacha 0.3.1", - "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 = "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 = "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_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 = "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", - "clap", - "config", - "rand 0.8.5", - "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 = "r-efi" -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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - -[[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 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.3", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[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 = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.16", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[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", - "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 = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki", - "sct", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - -[[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 = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - -[[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 0.8.5", - "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", - "serde", -] - -[[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 = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[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 = "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", - "bs58", - "clap", - "config", - "futures", - "generic-array", - "miniscript", - "rand 0.8.5", - "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", - "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 = "tar" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" -dependencies = [ - "filetime", - "libc", -] - -[[package]] -name = "tempfile" -version = "3.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" -dependencies = [ - "fastrand", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[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 = "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", - "tokio-macros", - "tracing", - "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", - "clap", - "config", - "serde", - "serde_json", - "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 = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[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 = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[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 = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - -[[package]] -name = "which" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" -dependencies = [ - "libc", -] - -[[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.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -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.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.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.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.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.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.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.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.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 = "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 = "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/test/integration-tests/Cargo.toml b/test/integration-tests/Cargo.toml deleted file mode 100644 index 884ec26136..0000000000 --- a/test/integration-tests/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "integration_tests_sv2" -version = "0.1.0" -authors = ["The Stratum V2 Developers"] -edition = "2021" -documentation = "https://github.com/stratum-mining/stratum" -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"] - -[dependencies] -stratum-apps = { path = "../../roles/stratum-apps", features = ["network", "config"] } -jd_client_sv2 = { path = "../../roles/jd-client" } -jd_server = { path = "../../roles/jd-server" } -mining_device = { path = "../../roles/test-utils/mining-device" } -pool_sv2 = { path = "../../roles/pool" } -translator_sv2 = { path = "../../roles/translator" } -async-channel = { version = "1.5.1", default-features = false } -corepc-node = { version = "0.7.0", default-features = false, features = ["28_0"] } -flate2 = { version = "1.1.0", default-features = false, features = ["rust_backend"] } -minreq = { version = "2.12.0", default-features = false, features = ["https"] } -once_cell = { version = "1.19.0", default-features = false } -rand = { version = "0.9.0", default-features = false, features = ["thread_rng"] } -tar = { version = "0.4.41", default-features = false } -tokio = { version="1.44.1", default-features = false, features = ["tracing"] } -tokio-util = { version = "0.7", default-features = false } -tracing = { version = "0.1.41", default-features = false } -tracing-subscriber = { version = "0.3.19", default-features = false } - -[lib] -path = "lib/mod.rs" diff --git a/test/integration-tests/README.md b/test/integration-tests/README.md deleted file mode 100644 index 6078038501..0000000000 --- a/test/integration-tests/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# SV2 Integration Tests - -This is a test crate and it can be used in order to test the behavior of different roles when -working together. Each role should have a `start_[role_name]` function under `common` folder that -can be called in order to run the role. In order to assert the behavior of the role or the messages -it exchanges with other roles, you can use the `Sniffer` helper in order to listen to the messages -exchanged between the roles, and assert those messages using the `assert_message_[message_type]` -function. For examples on how to use the `Sniffer` helper, you can check the -`sniffer_integration.rs` module or other tests in the `tests` folder. - -All of our tests run in regtest network. We download the Template Provider node from -https://github.com/Sjors/bitcoin/releases/download. This is a pre-built binary that we use to run an -Stratum V2 compatible bitcoin node. Note that this is the only external dependency(and Role) that we -have in our tests. - -## Running Instructions - -In order to run the integration tests, you can use the following command: - -```bash -$ git clone git@github.com:stratum-mining/stratum.git -$ cargo test --manifest-path=test/integration-tests/Cargo.toml --verbose --test '*' -- --nocapture -``` - -Note: during the execution of the tests, a new directory called `template-provider` is created. -This directory holds the executable for Template Provider node, as well as the different data -directories created for each execution. - -## License -MIT OR Apache-2.0 diff --git a/test/integration-tests/high_diff_chain/blocks/.lock b/test/integration-tests/high_diff_chain/blocks/.lock deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/integration-tests/high_diff_chain/blocks/blk00000.dat b/test/integration-tests/high_diff_chain/blocks/blk00000.dat deleted file mode 100644 index 63f072fe0e6be83b029cca2fb736d1406d064017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16777216 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

KB}UKM-m~W{FW;-H_3eE* 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 z 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

kp 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

kw-r8P8AQ+O?=m4C6aSAyg(K`C#6@MVW$=HB<4 zt5unSD@C(P@P-;BVnT*qIH}i3`~i8*I*OooRj#6Y7>!Sd>Q%F;b@S^w z0pH{H6-l!BZ;jL%K47mwuu3ff;x+O%M$px74j1pS7Mx|-nbgN-kk#?fluiS?r{B?e1P-JeDg|WAEi?Ul{+YJk^@GaeMh7P z5#lxZ;oHExMj1N12C|>Ym7n0%;9l3ZT=B{4h9cWJ=0y8+8t&=TxD(RyT9DT?2@7U4 zraPnSp~bMLe4ZDW8u00}T@H-mfWSeCf3Nfcv>aiw>HIo_z37qiOd=@cMyD z=jK4Ud5U=ym@mlN5dhx@WC9(0f%Ka-INISJE&bs+zNCw!ooIzg`)sBUH~34*oT0b- z35YLASfe+mWtI3FQQ@$OEF7zv={h0Uq2()xO>mxKKH7vMV{%>BKEtNLiGo39 z=l6Y6HnblwW#kJsAHjL^Q@o=HV0rUH=*XKPeLo|DUa3Xg@L zbyF{C|N63k_+k_tCu%^0$ULN5r@%6cq}C8tnc-w-x^1MlP$ewWP(}~tOJMB?4-mf8 zsO#VRzMpzxrwiGWFB{?Vs6Cje+1jc+<4H$@Sr4>=J&*lCUNclPT_})#1`c00biwfhkshRiDiuot7*RUmp90Bnfm!b&h>ibnG1TBs6Ss_x^r20QS+hI%U|IAF)#1-p(z>O zBh<@@V>dD{$J2r(liM@n2k$|A(Kwn)ZzgyjBc6n#6^i@pi+UO(7Q(xLKDza6yNLFm zq$^;)@ZgA-1LF%SbnyjY>ETK2?**?nzvEK9_^(wy!OrkSStLFh>X^Oe1o1@%QLnin zMtSt6)iEwZ_=)^$;TvzC=!o-P`v8LGtD?!{ph# z?a7p^#pCr+$t;0M*uCRqHk78Gj7E>Ued?}MmyZJ+KEwD2FP`zAb^S`s_OT2dlfm56 z>_tP(@O~K#&YRWI=mEXInH&KfI`U@7`DQjH8V}lw12K!7&g<5N7=e|m5ZvX54*q3m zC5oeM9owsQpa*#$#PGinq!nKNow_Kp4BJk#HiI!%vKal&fm2kwO}?ASwX zPe^L~abGbjEx)4GD;8%1d(CuXP&6>F*?}&vk?OLA6yKk4A{*CowF~Su6cW^Yz`TYP zI=lu_mv#K`l3mBpR>nVehYQ|A0}pxQ;mYgmhUxbx56T5VUURE7#y5WGqvtuF+DbG& z@pb04u$*a*CFvW)eFiV$pj=~!*IZal0P&g*bz|tN%gQ`Vxsgw^RavB?BamzC51d_g zY}TD5$i?z)7ODgB<&gK7JlZX~$eC{BfimJ(#8;?%Z4~_?1HvCdYT!B>Vd@}!A=jV) zR+mMCF22O+N6{c=td9)1e^S5w@ENI!Fx6+m>5IXK@=lCX5MN$>)0@Pc`@+DYgXUQl z&s_Q|oGY&9*sd2f zUsj-tFB{_V9yficBEnTQPnZ#VQqlV2Y#g~`uJ@&F^In4ZVm&dYD;p5ss_?!dBqBpG zFDh>Ja*K8#{H5EiOh6j!%ruxUlz(N?f$>EUI`{&q%a$tWqYxmCvl8ScGPHJ?m~Ll( zM>{arc)hUmERO)hm%#5l%Npwkdl@Et+E?j11NB&J%@+rKsp^p^N1GP!zPtzXC5Y!a z84$kIs9W9JycyDOPOjr>d~@j=bG4ZS4@2>cD8sdRl0~L;JZ&8QAPkf@FL|bqbbLs> zXsE7a>-d2s*;My2QL*{sOB$|QEDPQhmJx8?%<<>A7f9ZWUet2$`^^*%c%^ik2c2)z zBc3K(hBQ=<^DSr0o0MoGF6{_|;*Ox+^S8sL=FsXV^WW~;rVoq}E7ozxQ+o=oQB%+Q z%Um2FUUMZj1|;q})Zapv*DMki5c5XS8$7Eht$!+U%^6AQ>-{Puys>B-&gpKBPJS0b zqloHuhE#ahXp3xD`h6OF@h{=#uJOS3j??zfRQX`9QGKgV2Fz<OGJL+5;(_ka^jY04DyK!?{r^5(B9dNN*fQi1Cg@kqZZR_hk0n=7wNF5B!|Tzl{G zX3FG?Z|UQ^QVtHkc%mJVIlA%Ix_#@Z6bQspO_y2)+&?M)JKr3dWrzrjFV4`#m*>c$ zm{LRqEQ?@1qS{Jm{y z9RTqqs5tV;c+;9^{K46CC*|KPfv2e#Ga;ANG{Uk6S1(hBaLxbiXF`TYR075qk$cM* z$egx8=JEb24DGz~!$K_UEG2XAEajhrx2dLc4Y;jDAih-jBzk!!Uv)ZpxI0+CmZ)x6 zQ}$PVdGw_2P}Nh-Y)x+n%$G^OCoMquvO{roZ|Afj{Qv|NHa^ktpO=&&fTKiRr@ia~_G}JbS5iRca(Om@ze$5SZdIyjC zHEMLhd36z$CVkeDEEyGvkav;7{Gt~echg=k;eo_m3dId{cn#$J&ASdI1;*yc@Lg7?2i0O)%LvG#&SdgD|MtF!y#ho3D832&5mf#PoEPl!hjZuYYIP*$lhlF&KH!3R$XX(6=h zTl#utiw)(6;JBku3VRA9?ks|tq06U{db9RD6t~@k&FXXcj1*&hb~3+`JZ@eCowqHx zo8Jtsp!7YV+rpCT!>5+Br##S}XZ!Zj1VeZ~jz2`at0%4j?9)LR^tr%%+8w%lx@yup zXp5Q$V>6-6btGBtyKL}R7FFsNX@qNk$tI9bx0qHX4ae_wT$+d@UIntw;?B)x@&|MV z8PJZ-ND{RT8$x`VWxWTOPZL6yPn(E5#98eP)KgB?w*0!t7QQgiJ9d$P<`j+nbbA`) zH7{!(Ofawp>{TZtaf-oxdZ1bTA^?ZzgK9(JN(oPvVuCQlYgV6^1M`|!_ttA5K0W*s zY51WN2c@q^{0}{2+?11Ez887n4|F&}({k_LAAe;SLpP(L18bFO*fT53&g(7CG*hr% zv=;R$J^prD6lW9cHKSxwuYh>X4#nxc_h~ev#l*wM2`G*hW^xCQ(AhCTC05-^#FXDIdDL%6mBDd`rWI$8`N z1yTN1*2I~|1BFB#U)b>TB0t!tt)&7ef%!Dcz4vLxKg_+jk|*33O&3Fi?N^^sV(Smp zTgwTtT3Pb$=DL_IF~onSI*7lUds@yp?r1F%mAzR*Je;?p(ie)qQT4%vf<+FVrZ8d55!`+oJN z^Ku#YdhF6CVJ_a$8Lr^lUEiI8+}=CHLH>)(lo9LTz#h*I0qbMTZWv*-mUIlBO{|z1fsk5RtU^A z$Jx8qvfZGG{tW7&IjTD!#mwcS@%}KiFU{CYuR9rsC^y)uL3!NS?0_s9vPgt&_|@!_Yq1D}^8nP*ze8f2 zhxByM0(1f?!FimT4>>NdJZ>90@;HdskOgRrp*u8gvT5fFeoQo>a8}8@m2ArNBs{`) zzN=5;2d!1Sc}y>{&*<_wbg8uW)X^Cor6lih$992Mnkwk>n)otX zd0$orj2}nJ6R+wmsF*ELZQ}h03>d3|H0VKIv(fi?)0=Oe%-h+rbS+}SHJ`6T$X((a zIyT0prDbMY5+2xVEctFjfO!qwz4aPMecEQhxDIL2e2Q5w*+$VX_9d;sLvNP6qm(&z zEi`A4*LcBf(zlz0b8oO;dRC4}oJ}~>>rK_`$5Q`&O5wx$S1t=upN>!R0OB=|gBR~@ zeVUmavjT7PxzUZLHH=H!o$x3(eelU4{&byAMBm#DBVs{dsdK zq_VGLC#Fn*@{`m-<&zmIcds6IYU>U-?#xjX;DN*)JsS;l?5}}%4cthupn6Y>kSAw4 zd5tU2dyZMmefm!JONqlAs;?lgF&POpe~4V8Af=Mn*{PsjV;b&`ZtN~IqBDlU`gJ2J ztQG7vY{{M(z`RBey1eG?CC2;~q2%GZQX?kIxa`j`T%~}kfw^Nj3a7sgAg>9?F2KDt zNifeq@IDYxz+yfdQyTNO8Hqj9k;~s}X@#Q%drc|PX*DpfDY>^^1KGcP9Gj}Fr*nxD2rK8aA;VNI}UC|6zJ z@p3e)8|*cz@yg{uyyh*!>b>8~_sncIu2^4?H_u_;hmUNIUaS0<@kO?*{?&o4jyNdp zZtC*iYt6R&{ec*ZB2V&k6=7=n>afOhGjso&&{Nu;VSaGj6(qD=0g1a`(?!s=U*Jq_ zz4*Zh#b`?a-kQQ#UaRiuq@+v@|&)4E9Tij zX5-Npg&kD4YB4!T-@Q~f(0!>`%WK_N*pO54T z+qul4x|{%+?|cHT*F=nm@d2yX#NFF^4dk3eBaA5n%p--A=7*w3m}@WE zA9**IrIJ)&4s$Mug@e4Nt8;y z1Q9t-lpxXwzqX%XGPRamj1SdI6E4Bf(O7@M2I;%+1?B^byD#@P?jSzBG#2k0xf#<` zI3Dj+EHuzk7jJ~Dz>EAL_4xeH-TQ6Hw_K8Na|qOjJ_mN+co9bjn*1V}ZY&;hgYEf+ z(h|E7AK(( z@(RyIZV1q+DDXoSNk^zh4p2E5Kk2N{rssitT5Nv6*>9UTgoN*#!$*P0>h6}|OQ#1m zW+$$i1@jUeuRlZTgeiopKzy1U;R3qqgoA|-|5h3SOMdo*Hw1y(1_g04ED zel5OsbRjpn_-B0<*t7kv428ULebUyx-zW;@te|?$QO6&9^p*vKYo0>Qo`S3aQrUQg zpBKx1-4s{pzh=z}^TG8RE0iiPAoUt$Htu^{CxrMkTieJxBno}bqH-Q7f8?)~rxN(@ zNq;bfz3?XQvjq7x=IX28;^&km6HcwiN`XOxsaPo`CKJPCWWT5jUN-9}Y(VOSSGJhI ze0m7Fe0msd&J`(8ebCz}8fmb;{Lf5QzWnax-(#{ar70|s*El1!>2Bg;veA`an_~@J z9bxviyW%7Vuq>q^>|7YX-Bf_|KRTO^wuDWe3;l<#~|5BG-# ziTM5Lmj-L{L0%KB81@ydsnQ$~t|z%qwi*tW9kvc;XPrZq##Pi-eM|WS>^1BRC2PRE zMgcm!=3oNE3nVLCt=Ye~QOz_2rYvN`CH{l^yB$6QDdcr>N3WAU*@3))F3Y37@?^7E=2iz!ULkbvqne9AEM@8Hg<(Uger1%Yr zQ)OK;#f?^;fP$IX$E9)dU&t~F->vo#>%*TG|4{2H(fkhf8W_{Khrqn17`nVB&A{t9 zLBM1CR^0q0uPlqG%P(l7Q4(K0vC2>V7(n0m?TNa2#`R*P+*1DIV>JQ+J~zn(nq6lL zDHT1Z$gT40>poqLU~$C#)i*K*uEQ2>21Faw~c8E zc?I&C?A58;Lp8^ipC9{<_d6}J#8vSgj*PtXseH-qT2bMQ)dTh#>S^zNAYSu0_~hRA zKbcHV3e6cvOb*|mnV$4$jb_2Jd*s(OnvpQX#y$td9Y1m%KL6^~RYU_XR$!dON!-qX z+Tm(o8BquFQeXF2mM_Go)zAch_%uBmCUo>aA$3CW4lbN_|ko#8gmw`R#BHQm-i%+W_J< z=ta)=woV8+@7_^!xE2jhO#W(EhGhBW_xcDoqO!Tv`~#$w&Qm^+Pp_d@)qkt}W6{Fq zI~{{|n=hq~%ltZ&)b6~yzRu-gV7wuuKCLTQ48*59)U%=Me9pqL!foee%T@&1yDIUJ z1lg<6<^$TLw*=@Z6apEbdX4aihnZ_UI$^s}hh|3E@EeC3OD#J%Wv&-i*u`!#8Qa;A z{%6ik0I+&ZFLcyvAm@%Xiqf^|PU02_8I$H!3`aiHMmui1lTm14ldR5OfxISjTYTub z34kr_a|#v=F_I_fnL zzSJ9z_zD+W$i>6pgeEJzagmC7f%Hf8A~_&2T|P*1jL5gB4xI)hf^S0xZO*)G-)@f)$7yjQq6}57#KHdpBmfR%?y1O4AD^@a`++f_U6TxG(;N(P`&0NjYPUA@ef7` z9Z@r%KobhqlPK28-|i%36t_=D%*G`%NB_;M7x0(J0jt*pLRY;8A>bNCSjduaarH)2 zSo-(*7rsYa*NTBi#;gm?cY8>mxQMjPQ}8}#%`2vDmjf>jD&x>mE=Vd(^DZF94gJvKedYl)_+q*qJ z4>2WaF$`ES35q-Ad20KB=Pg|0hoACT9XtlFu+G@M zI%PkeAZpXMoZzwx-}u_5w!0?tG0Nz{+oZiCJ|b}3!99Sh2Nrk9_crbz zKCREe*+aoPbX#P8Azs&bI@nZNE*EmHp+Rhg6h{Y&JBo+IyAmHo;fu|su}r7-tgH|7 zbc{$lH*_8oznmv|yn6(WyRk;9bRcn;LO~B*J}v9Gp*7*9`OLdtTFRzxD}X&T%nknF z;q2wB=tr_3pT=UV!=2OVTG;nr88(yrE!MnsLI1WjK`2{%+EeS;7zQ)!-+n@>saiW= zKD`DVJ`I`cno@M5o&SdE+%JxNG*6V_`4bZsj=%btoM7ebnjhrT6PxvEw(siaBe;l} z_VIyhpVxtTQyt3*WUa7r_*ewb|mpPo3KmN09(pV z+fem>&6UY;f(SnC@+455Fev`(ieRLnAX&qk@5OQU*TiauJ58y`5=pgkCo&fOqNIiY z&VRB6bQc1t6S@?cL0A8C$_AaNDCc~ErF8Asn%e0pv7ON$%V#=xPm8s1UxIwvj7-11 zm*!OmhXKmAO~ykmtJpP`N6X|w9$DVl7X}C;sbHTjX+|^w=F?Ho<c0x*s4PSMcsrW9nOmwHC+e{4_TjznFRPbI zd5_j1KD}Th41_N`6ax3&YfS4{T7PKL(TMFAqrMzHKK&l_W>XpsK_Dkr1G5|CHI}Oy zu;Cf4nli&{tLygaFk7}Kha&tGcG^wiQO~BRa)rTOgWQZ+3(RZ$@4eR)w>Sl?t(Bt3 z;#DUqjHF9{pHIadX!|~?|9hkJuJ5kfgZVU|aX~jn>~=XLv9xMHPp7{aUFT0?WBhNj zay8FQaNI46F;oJHyA%o`=<*trBuwQbQt=-fj|%*pS*lN9&#N3u4uftV95@!cgS=)w zHp1?vK6K#&Ijv2t{*F_CQ-*bVdDWD6g<7uD9=Dn$*lUx(i%}?m?8c3aRzu3Lm z{PBqpVi$>Bj{~>y?60uuT)tt>sc853yZ1??o*jo7x-`-$>U!q%`?#=@%WIFq(!pxD zK1M>Ajv@a&4)L1VML>HkI@DXBt4_FMcIaL0tYJ0^|MiRChpNa-S(vXFRB`#R-+i_2 z_QcHxz<66HJASIvCn6|C4mImz-Hf%m{^b6Y*?nBC-+2-fTqjiYJ6-`+C!DdgA766Y$!<*B|qm%l|R`i<3+m94KS~XhYqiS)Eh*Sv~ zen`MI<2*-7{qtV9Z zfX{{>f;lGigEtd<1?CF{{&#O+eA$F9z9{#`HyvbkCK4p@SfzH~GBmrTxQDk~_XK!7VJZq-VQ?MNsCb`^at^!Df{uy0P=DXs~p2Vp#^pY zJ59yCN{e@W=J0@H(Uz{U&s;0p<~VgvcD#toHyDtze}+ao!6XHhB}Kj zC1}B0RbC}yo0)MOl)pG-_AMKPdCyVrjw_Xp+O7L_kgqeC6TM!clk0zV#9ZV7&R>uu ziv57(FY-2_&@sOW*&`xA&ZzQT)H$+U`RAi0gl@P!*VVrH@1>nZC8qLs=TaKrj$lHJTyLd)Z z+SB~rBvYguTp!~gR~G_SAH##L`WRzfI(-7Z(%S-#ct`Z=s=+~uH~lduoJv`#AAU%J zJo301{r$ul%n&y}LFgd8>pRCmhs))jHdYIUU93L?p9_gyg5>h+ueP2y4mc40`f>}v-kDbvvDK|x_+Wot+&k;6#Zyt zoVFjQd%XWf4)m`mgFSN6p`9OyNA6I_-rM>ZgfEiZg6$)xjlD9f{cQf9m;d_aQ0?WD zrk2$jzAL-y`;{Jrz|A!X3@)xmUTEAdEa6{IZl2kye7A|Oe(`Ts9BBtg3z}Ab|Zz{6h zT^NpmEIz(1>8alYnj4vafcausObc*Nl_hlW1u~~S)g~>g1^4BN!AANzXL=OA%f~kP zp&U_4`~(vsZxCOmm5Aot{oJ(h%epP!4AI~(<{1lb_m&611t+QGgFJ)z;`ns9l?zU8(K~D1W(Oi!Lbr@F3wYL*5zz zcENmLuGNGiQvEa6s;?&60xVHj;QYm#8%Gyd{?Y~=`3q!@j(QaHrzT+?efDaApj^6U ztSBRwV11WIAGgy!d@d+|c_)A6KgFe>(TeRD-KqtPAF2EPk^Fq$eBdvUy!>~^wP%q0 zMb>NrNdEE`A?x1P$7cQ-_oS&ng!lO`KFYeM5oN$KozEfqBgiba)M*Vll?OMECN72N9_ZqMyhh8WB|N^jL3p=#0h?8Z^Q}`hOaB4F`{r&8+nQ9H z?GZbO*C_970`Zy@3Nh%akB$B!i}5}W(Xp7>djBb-x>nnf(}e7iOK|TpU)tUIGsfCf zlfD@f)+i5F!gm$*=Z~VIR?cUT7V(+`TgB6!tpr2nO3J53fYrxRp{qXTHeh>lKD?TA z;C?p!W$?30%B;RXpBo0j(H2}c1BfpvNFz>H^Au0T30Ho4BrH*z=fu-1Vm8exkk`HV zvG8Jz63myd_^V)GeCdZSzW5H72t^B8ZCj9^$k5upvANOoNuT6@@Th1?oFN;;7hg(+ zoBVb}h4LCM?^Epf8)HUYQo@-OMHCi~VogdWmo+e7G@fha1LI5jz2!>+_+0%0{|c8? zEqz(^hgfWVW%w`E37XzGn>2r?hTrAf&6S{EnlqgHes-6mzC1#m9-@|S{Dl}_N~yj3 zfwIh+Qf`(Q%$JO}C(1zh5;?tlZ}$a3ye4q*C!>sB3?`wgWM|Bq^7A~^X2Yt63gqyn zC%IXmxZ`k@wrV>Qt-j4Qco@Jk%*HQZ(B+x5-{@QVLe@#qU)2}V_gl^e^nR$f2$}ab z?jZXd65_5EzvCXrO(|-@Z}_Y)B?UP~bz+?Cq{F^nlmf+Fa+PUMXA;A^saMPd+}~H} zVA4C8JxwLb^Ldy1TneI-~^z5s@xw z1VkF75$O;F>5vwXloSc+Jd4k@-hKAo<2z$KW1Q~?>(?{neHlE=buq8F?|IKnzjc}S zlInH1HO6v``a1--^Edkr#b0-kKY3u(XE42?^Cm;>=n1u}$0}FWKvIVL*1AJ0O9G!3`1!4-z^Fi8=zjrP`xfc@pvmjnB>x~e` zI8sY0Q5yGhb}v7hZ^z+>sqqnY3!f!xzKlmDkLbr)jwhJBRZT^a|)& z&tI0#siVk!FHJ!cOr?wfMBCMG>u6mj<{BB{|;W{dz?0a?c=ox)L7`h@SqWGj`|uw9!7d{QGJ?8oy-gNe>t{Xi-I_%JH!-JOgRQ-V%Q@xF|FQ z>6$C1Mc+jF?7Y2Bh9!vuv%!iTE+GPyrV1NQ?yrLn)-+4T{+@d*HADIaOxGMim#$&S zq5R%0yB3|;C4f%l@AF`F>SV01`V#SqWy_Qpq-!epmxtdLd}fbO@YvaS3I;iU=N@55AUPVoe`81Olu%9;XTx+eP0 z>l%f5_JmoAG(qYxy=6Vgbi7Ba+82&7cs1_$7>9x&U4z-gnU}dpJQSDnXjViYKeg*y zwjXuVuxn;(uf(dWQm+~B-#!Ox#FQCey2b;#bj?WE2sNel?hE*@THLc@C>mtW^#Uou zQOOIh?jPoY_(e`D$ypsk?=)=V(CCVlm1kn5n$ogW+b8C`XsWwsz91eyke~OP4TxMGRU7;8YPQ!;2awWgLKVLG-=}rGojOqit{TKb`F;l=b}MHU7?Xh$>>X0;X%e-+5i*pZ#&0c%`((Z7Ih_tUmAB zg1%LUJ42xMa35E)6ok7&gsyaA!4;n&SXAD0`vz=RL%VVe+ws9^D}G_=51BPMVBGQ7 zH>&`_olhwWbm$sLPMFqspuIoZe8K?RtC}jX|5BAx@^w$)<$krh#H0d9*EILP!fgE+ zBEOa~*ijhaBW4_luV%!jzU6|U*!+>1*ZMVB*OZz3i36f*;-)U|d`|e>-TRuo`1f`1 zJc0h~Jj~@AY#Q25Lp^Frav%Qppqy}oB%cU-@k!Wel-Eumse+)i$yYY7(8e!@FrmHv zE>f0$Fn{NWXfXM|0+vtL-1(gF=IAEw`ObFybcJDxixw5eObTs=WPYrX2*JLo9td|O z&zklf1C)imE){;&Mf7P{x0YS;pFdT9rV&pt%`@@n4H$Rj>1B36aQAbn1G@Bd7njB( z`O2eA>W7GZrw;2IAA>MtOr@H!+F^{DZgav`#TP7KX0PE>-Qj{ZLdfE=U^t5xPidiZtL7j!3lU+XjXo>+l= z_}4E%x<eh%iBk`7XMAp8g<7_^ABP_wu3182AQd6RxKJ}tr%X=9BoIt=Ba zqJMHNPoWH>H~C!W2MLR@F;5>vPxmOv1H)a{oyT3gDsFKSn_YviJI*^bTCFoP8iX33 zgbu=xfv0H&Alw}Xjm%6a{nVIK4M~ewcq_pXbeN5Nnbns$Y zp3Ll}Ma7baSBP`^SVrE{4lwRY302vE;jaJA;tp~SFTt}zx?xb_d}U~1w^{Au8p%@n z;5FPHDr%InTq6i~8reNRi8Bjux@>8e1Lql5U#>L~#PCV~{LU0Lo8vanLI=j3&bKvj zAh@GIcnuxr@E~*fre9{}%fFQRYT^)_snL9;^|9x1Q+05Nu%Tz((-Ba;c;ANmc_bn8 z2iY&)Vc%_&KBTNT{LZ45Nmy^l5u_nMig&gDJC_eR(xev1ISB#h={rA1q|SKKKTyDr z!WCoDp~#Rf?d)kyy&Rq%ox-dm;qAQPso2jB-wN6L6e{_4lQ@v>{r390dR#L`F7{x0 zTQ}urcLmtTeVg;u2j=6fpu@*O?uXiPlG=*8E_*RC5GUA70`uY*_Q4a};VYBIS(9(d zARm`44ZnXa@rJ~MZmv4$Nj%fn+KMNsPReb(M{`tYcG-LH!9I>bCU6sokF$$tfUZ7P zVRPhD@r%OT{zN?XI3}(J`Y#8Lr0?Zcc{YM31wi?9ugR(02&R^QIj)S(gnRVU>e1QU z8o?iP*YXJ17GLAVVK!no#Ef$lflYCv*S4%=!Wo1TGa^>Ok)s zWV1Jm&mGo1&Bw8QW-&D4vLg8)dV0#h6Ns+)IrR;?a>9Wd)Dp$upuoDV=|4YA-`6gu zMod(v@mTdpC6V3E8@_j#6_l0lVb|KJ_w6zNkg2_fq!!ug*8R46nrrp_S3>e&ewk2> ztOv#~&!CH6epRD;Y5Oh8OtYnaFHU0lrH0eO({`r&p7Oo7r5%W0uDe`=zZuGJQiir7 z^hj$Q1s9Q&J4T~n?oCfTQfY6gZv*qoLuFH3VEj@59sC06>q1Dico0dZieS zEXK_Ab>}rqYC7r^?hP@BUySgb;YtSxF$Jmk)Bm_)z}bqwsr>K?c{l$$xfkZ~{98gW zzl4+IWCP)sPdZ0;zW$lma5nskyu}(_ElSkjg%e3kFxn`g4gYx#Z|m0W{e$}O3}~q4 z)Wl)K-BVOM_oePhYomFoWOEID(K1yi#B~%8Qy){`za*) z`eyNx*B6EI=X-x93W>LhBH|!^iG`is_#EXK+|oX@5_PSG>0h48|6*lfuI&QR4hK*5 z5fYeR%7Z<`f$&S*)YP5z&5&~vx}U0gheVJa12ywi%OBfXAZ_DppNqEprJf$cM}u?? zxs})6WY%1zUPgvzSs$%$uf0gnmoT)qnLoR`r|o~_UO{p~p2T!ux+eY3>KaH+Xe7w4 zEAg@=Vqyf*k<|e);gmPahIOzv5xs5&^LDOULIwtRQQ3bvVgkYqu)gUuBQaNEwAw?cYy;iN>xh$?|ZT`^j}Y`v&G zH7|D&avzMl!kz$GV7QyTv$%uggv^qSeM2grEaR6@Y8}z?M0+k3?(xgK5X0AqHTeL- z-3_NCX9gnZ!U=fm%AWEx$cmIBN39&e`=re_+57(6o|=ZozA#t6lyWQEF?qYNGi0n+c09gG0X$9X`PkINY3 z2-l|OsLOC%0iA*UIo9-(m5*_;|6Bx*OF7cv=8&V2eiY!X9}Td=W$e{ zj|v4~1s;7GXWWPQxVZ8^zu&+rF(0Feqk86EM zKt9grU1#c^6ncZ)?+!%otIcNdu$^3Y#0K{dMc;*veR|%QKzv+34hJwFcY0@i9Au8q z*oV>kn))lgq=oXw*yv~PBXMR@HkrcU*6817y#e{SbP)!GgW)#}@`YjQZY0A!M~Jvu zd-lCcs(7s%`XhmFwjlk}-8WM}d>m#e)1B>~hRnGZUoS^RR)$^p>R24{xFPv9vZNBc zfhVo5Odf(W9td}O-^D9mBjN}J+>_e9USL>K)%0Rgu444 zvOs%)oFMtKD+4*z;LNbWRd&FVM(q8P0rO(ZD2|Kg)GQB+{_E#Ke(wK%o+B&sLUax1 zx6D{}Ce-|c%8g&w4@{n^Ehi+EUb0NDOdoEwf!>QX$lKcNSlEGQCYtHX%eQ+jlw?v$ zZ|ym9Kdlci9sB>Oh3IKF{6S#4#)+n!wgr3V_|Hf9Bb`NF7bT6pT-h~B_Xj%jM{tMM zbVP=8m6E?aq#r-|@9XHu1^zeW$qiaZZyls8zE`*N4{#jmrFoLHl9RI2vboGDg<@-} zjtQAOAnW4hD3=!NKWv{vqA3xGzEF4dt|sp5Yu#~Sn4b##b;u2FX8Y?$~)IM$X?%IE`k`Pc># zZ@5Zp=aCz+cz*$05d*)HY^Y zl#Br;OrKJu=wTR%@U>64Fnor^O1apRlZh z+NVEgqLWi2wusUh(;()f%r&Pik<47BdXa~(G2ZtUwH;myCD-d0f7k6F9Q>WT!Cek{ zPJq_&%$eJ01SK4_zO?0^d+k=fmuFi9vbYeI!#6(F^;IxBhpdZ&encQVRl{mJdwkv> zonpbRlBZsZvs4C%y!53_TZ_@5+o=+c2xZ7h|6@OaeE)xY&b_u9h4d5V)DgX&kM331 zK%H5~P7yrcPSSHf_I@+!=$V3h`?;TfPVXmX!UzhBr~d`JkY7uZ~)Rw7bAMK?2%-w zILNx(|FR!ee6=0ZRlTLDA910X&W{$fgzU~?P-J9?%U8WV?X^VZ_g_Ene|(Pr z;E_5EW*j~SQim!$zC>WV7Q9z^P^{7EG>7+moXuwBN=$`K!9gzo^j;VX@i#%UE{YXQ z7C}E9`G*)?bzuFGmk}#5-oJZtfy-$K_Ra6p-|hkN%|EB;-)VvNM?%m(c?Q}KhnwLx zIb2bVzh|rM(9be5R9G0AT8f7k7w-{%&0x9b9VhJn^`pmbs+Ish5;^-k)H zw)NRvN15N$221R3KO0@4VSKR~hOA3i8xpl=MbM~TFZo%>bUX&K(iO9?HnGdsu@C!$ zR@3#;nD`VV5lLJz|JQo`kDvcvpQ}cz5gue-*WMR3XrKH5+7CY}7V%n;=(NArJ?HdP!uz%g+BZE=HTsUsr(yrD+duDri@V!( zfjqT9>!?0O?W@jm%jY~{Vmr~V+z6|ru2CXgks3efPMVYpX+YNHk6h_Beb*1Ahaz>f zQ`JN9{!O|kE}}9A%`~vA@32B?Mb2A04-KA#9A*d7{;UR4y3ewt>* z{&nHXnnN4=_xva2H=UP2{0muxCa&WsufZV4EFGC?f1P>}pQV!FaDl?oBf6MkY{>5o zewKot|CV1k8}?*gZP1oVrR@@9hSn2AoqeN)tp9(EU$!sb>X5*BiEcc=$(bW^Ztzr6 zdU-~Wl3{h|D8mC<|D%BWB978BOmx`|LI__f!_s%|6HL|%P1@#HY-M!Q?3wZWJ^%Rx z{V63dekp)1e$la$eY;PWGS^gaxD&i=sK~>jQpfWXU8dW*fI1q4U)Lmr-Gc0AipMQK zh&XseiCng}GCJf41g#oPEIuNJo--H!J^#spBgX}dU*@2TU#d{ip0+6&qY#R2W((~osKK9{&= zzKr!1o-{Xob*2Gl$<zu!pe(z*bwCEVQyxD^h z6Bf)b52cLO`ssm1utb7k1bS3LU#j1b;M zSavN{PM72#Pjv8Z+0opJ#Er~HG@A`qjF}HSQYSI#RkMhvZqpry`+xBBKksAX-}nxa zQxqBFA>r$@ZAx#wMJ#OXEnWUH9cxp7zgWj8|BDgyUVmI#e;XaUyY1MpqkiiXw4;0+ zfn+;h&qs5zRI@#zh?Bz2Kl*bt zsa(4+%7J7~{aAJ4BTtF{(2rm?2k`yrHMLj3a*D^$l~b@4lC%5?2yR)CYu!b#eBAX~ zO;lxQY*JwyL-LFkv`=uSXKt8Tv}Si|KHO_AlCZ*A`kbzIWw~mbf+A3%9BC+d&C?T>G7J4J^8uU z*+dO`EY9l=3MsR4+_ZJ|)5*zE9axBt)YTOQrX#;VhmM5wmHfn~7fH_bY>P}9@jj%K z3tT)o7spoWlgK*YNt6QVNS{i@;rwB_p?$L&I@7J@-*#)rKWd4LoS)TFb@ZgLAEtqI zWX0g;BOp35Zi?fb*6K;KxKmjXjXkRR{E^s>hcl*Fce7H_r;NeBybSSmNY3DYL-O$d zm4i?5mT{ns;5g^puXYJ;iypW4jMaqj?td%?9}{R%5oY;ep81J>tH*xEMurOZYk?y9 zJzNj$v)g{AfIcn37~_*Tr(!vWALT4hxGtyy>)|Ky9_|x?pI=5RbyCj8HO;JUyyO^_kr+>fHQr=L_6N>=6G{VzRC`tr;>XAk-{du zGoo<}e?8+rI`J!ga-BzCQfdu?+MJKk2X7_Nu)BGcKYRuGpOR#^Trs%B!;$O%j zB<_4Z_SKB!{*r?Qx$sVtpUEO?2>Fu1HnrINQ%`o@fm>a3oXXXaXP^9;tApy*kytd9 zgHabjW0PAijpAS=87iL@8(7!a@8jhF(=`pyrEAz>*wiLa1k^?H?#YW(!)v9#dEbf+ zKi$Zq%Z8Ky+JC(5<&_pTYjJB_+b#Isl;K4hr+Nx5m?~a!@RK)W+jTty>l)7LE>d8+ zW)M1b4W$0EtX3&cLM=_%UuHwAFHS*24hv&-QO`n|Atg_Ho6GYuZf0Rjy+N95-l}~! zb29!bnIcjJy<*8y&kMisjaJ7BSl5(O5|jbaH9x0>pd%lH+yf`@o|(bPq{C&nBx@Fv zKIip&Q_tTWO%w6cKa@3aLHyE-EvGT@IHYw zA^Z{->I{Tmwkh98BE=L@JxWLR@Pg49D1h7Iq(jh{Z&6(HSUr9N;Z^XH6a4(Q^?d~u zgY?OlzMP`Y(p{!I>yal|K4%bq|Bu!8;V@DkAt$e*`MleUeoi^5uiv#O696B1JZEGv z)(z4%9YysRbvv-GP1k-^QI-kIj1q_R4=gF(k*An_x-qwo(1z%megqF7x<=WV0XlrM z-rvuYPR3N6J(QZpEzY;-!@4f556{mpO`l{?n-WPMy+jA`%O5Fm(Rp4VTwjT};j-)!$Smm^$z#FK9Tjcdb zFlaw&zjyRk4?=uK*zG+`w>bQ_b-(2k#b(`tES;1ot(70W@V`v!>=r=(|G#kf|Gn-9 z$z=np)waXKBV^UZzpS~~J$m*V)h1^^YOL7odB@J}`v#5$Af&xm4EFNTRk0zBzj*1y zU}evM*!&BbslV>Ap_m_7*U+98Vgk`Mb`kQ>mCKg)*Y!;0S6Q)3nf_v@dZePV4F5PN z#o+Nkw!e`O=yUhp>1O$ez$Jjub^Z0+I*_X*=e?%Y+U{=;g(Ne3cKqcnNG=-&_Z?U+ zO9WlHtg3H)Z*uy3QloK&c_&XCx2HmUT%@H9N>`$}jkon*#7}$GhAdh3wyjMPbV8xi ztu`iA^-Gc%A#^Ko9lht5{9xa#<4460%s0D3hi``TkG(hXrO}Uot+H>4SO|~J9d0^J z-d^YRunI}td^ro+SDq8%Bs~iq6cPHIgEFq+1m`vFNik5 z6~Oof4?6e-(m&?^=>2jLmdZ_r-JC@9u*R&60HaZy&x^q8Pun3Pp#AyYMN_D>An(b6 zHi4u-YIcQuE_(A@avFa(M$-oaZ87jj5Pp%Q3kSk4^qjHK3o0PU0;8xlzmn@!X2$Av`mh``g;-Mvu`Ome{XBcD6oz$3qyZ;R*d)^L?gZ z|H4byw++m{oIsa<(RYlL`xtsqc^~IBQPNzBvGV6;jThdfZqGH6LT~4tayGM;BsRbs zyofYlYosHKqZ?F#A4OCwVnco7)Aq%K;yc*C6zJw;0`o7h(BWSo{bR!>E;MCzjq>%j z>wbkfyN^lYKE&p1Ajz;l+nc-9YouZQ%CPI&dcSU74T6iO_BH|f3ugq?hewC zk%*sNjdzAajycl{dJ_jprb8T>XnlVoyk47PU_~XUHip#q8yz!%>By(hr6XGtP8(1+ zsb)H8=}1}hb|fV>j3nSR-fN;Dv&r1vPh+*k)-%c+Y;d@G^C3?6+RH11sNeNq5dAEv z$*Si{mkkx7BmGfYfayp|=+Kdn^W+5h_s6hWotbq+JCakh*eKr8{CQSxAtu9YGmDr5 z(vi%#fhaOcjpK?py59%o4@Op{yv_!x8d%Jyek7O)Hq1;ybfkRcOCUPZ9zl_&oWB9N zsdWYKM}zDZJsp-#OpD~jV;49)CDq`6{NUew;r5>LTc7oB`;#4q0|Kg;Iw^^F&8Y;W zP0~kWjmZDyBSGKy|GhsMQuhng2pLJVtJq;|PrVS?CGbs@JuB6FzL1iR)=cRSdS3y( z=Xk*;$pxWtZ+eA$>y+n=v)~_Zqc9r0B6f!lKDA{0r|zeU3WQ(crqJ$e-4BAh<5)er zMf~_#hHyKo_wL2c&Uxz8DP$e5gQb5D#(;2#e04}hBQeZhGI?J8sWH>dl0F^}ui^duMS6sRwYoJ}w$rd{sMD%{MM~VE;ZUi0j?Ts8Gr)B2BW} zxh*k6@h%Q{*u$L}nd2OKWW7|QD0wD4eDXs1KV7=TzZXzJ>H(yiTR?F4a|#Q(^fYg~ zIljs)KLY$$%~i53)a(231}F=^t3N@=xNAY zFexsJvyXI@J(^Nd{>x1^?f9by|@bllE_v^-E!T()XKUKrct!Oh%>*S}2<$bFgZ@=$9cHS>7fh8rB zBFrFOU%Uawz*-O&C6Los$!-+GiJdzR^xmI2+kUW6%g!ba^2q3^RpPu1;}2cmDZKho zrMf^OBK9g3oY!zO=BEP7YkopkUc;ii(?g}3)P>xa|5*`km@-vC>P>?JW_IHYCjIT_ zKgp_SoohYP)!HuY20z^uC-v|bzGo+A{iPbpjsu=FCLEYwN|r?Ufbk0+bnpwL-poQ( zOdA^Fhr<&^Qr@RB7|mfD$YY4NK5aVqB_mc&+p$Zfq@P^@^iq3tj0fJ;v{;xQv zFWKG%gZ9PCun3<-^)Jp>`nbd0C}Y?9;z5W~>F^~AD}#z<4&AnYApBCG0`NTfB6RT! z0p{^TBFFo1tXtX4sPp>Md~>Lk6<6xBPO%{kr=QUdMO8MNL`DQY@HpI%F>D z-V1|!_NyfvPApgWC8MdfwXyKcU zp?*fr1mc&n{U{u}pCe;k;m!3(Y5PiIXTsR*(x8i2B9Ip`(hoKW!2I%^qm2&;zhqIi zL5F{V^vmWe(3z*9Y#RO%rib6GKFiUfA~PgFF#WYgct-aWq+6FOkektInxmtC1YvP{ zUyRYy?H?`mz4MYr?@$u|?k;=`_AfM+!!$tr3t5EYo$Z%}^fNWfR-6bTrfC^gFg%Lg zNZ$VTUQqk}iNC&CR;DHlh+mMu$dOM5e_HK&P6Qi|$bTI*K3GbmOW;t;MwRgc?T=Lq zm|q+pybA=vFB+UFcfOyAF;6X}pJCd}z3^B8$(qNwFw07|)VkK=Y5xnPKoIWkD{?x2 zql-#I3uu#*zDMn@V+H^CzRDUI9{Xm);$odwJH$7C(}MwmJG%&H=iXwsY6ko>j3f1ojStMQHO%e!xA}{f0neG-?8_*p^xyAB)LC#V`3J*7KW)SoDaF~w`zedvK+L3#Lb%OJ z?cFqs$XowHP)r|!MA$YT!}!F#AWb@tCefFRr|Nhgn=%>Z`|vx}REU3RqcH};FZ7&o z(B)qygqJ!tKf~;{%u&QPa?l*Ug8!YVQBhXX(g+(|2GZSCqS}0wJs4O)n?Hr968BDN zl6H^MW!f%&+~>Xs(3Bh*2KyJDd%6R_^06)G@Gp>hbF$v?F-3v<{(P%xPIpmw^h#H3 zde)q-wQH@*J!lpYDSd(igI&zsuV?KYtLLL-cNq#Z-Qzy<%jG^I`t)ypjs$ z<4GX?C2q>%&e#10;HjsDVp%m*G2a9wDR#x0Iw@brKd^{BLYT>+sQ%Uugq7JTl~?!c4GzRBTsG+W zTid|6D-Vt{1cJMtQ)bZRo0t6HF;OcahuhRsAC+10hLmF-A z>F>^-@1<&6$b?_~ox?^wWnKWxzu-WZe?e}IM8nyI!I+h>YQ5)gORp1PQo1vq#d5SD zXUz`sFBhA0wW8*}Z(wNfe@LEsa7%<%yU*v$Sgl6sQ=c8?;yb=% zPs>vOEYD#iVrHMSB{0n&9UFVJ#LT=aPr&9hR^E!9K7Ud+}wt#i4q3pm(tD+X<+>x>nG zr+oe{{)(hVif%rGHKvQ}63j0Xo$S%T_@y1X_$6W5&zoScHuL9R_nz&wC{bjUm`-ZQR^ZSSB*bz&nm56m8*wRrT{6Y_d4UAvN zp^IOL$H^n4{m*4f)X|yxE@dP>qK2ddHCD}GmFXE%gZO0(UC|?v!_=>4!>`eOjwkb@ z6&8C&ps_yk!DLdOE*(lPm|vpEEEa(AixPD33nU*Kl^o#+rU}$Ds+;Rgyq>Q_Ly#|D zYDJKi|9}!60^*mv06`&L-mbG8mte+>#P_2+^rv|{FQwB1(=TGq=`sSW!2F^$nEL~W zf1yC2hpv3At?KQ!SK~_ujY~-xg7#ZU+*`0htA4YY^($-KhoF3nh^M&DRWeh$ujJx0 zoC0;X{i5X@Y!DB@$6_m#(7F&?ek<@Cwr&64!1A#`=*Y()^_TVI^d1IrcfBc7t7(+u z?xoO1I!m{OA}ly~-b+MKK8BK*u2h@6Kjn66C!Iu=o0Y}Dw7uQ?Vt;E__^aP8^A~-g zzw>;ZR=)25$;UJ}SMGfMB{d-p*GIJz^G%NBo=~Q7c8FN_xf9NrwbhE$tJ}WC!bi&R z8J6+>Sn}nB1K~2dCu2_uZ>n1*l}gk1CkLN!l7RVzzOkwV7{8D}7r(Tw@6$b-Fg84k ztM4hyeAY9KHEx4bV#T%(bH;Le4(Jh!oyQmR&$sF8& z;??>I%rBAo{$#-TMIO5N<+Z53y0i>;*8U&4<@3#leuF8a4%l&OlJ}n3=th9}<naOY8i*}Opy_$wOpXlM3WN9sM*El2 zPua+bv3tsGWDBxL*-1%>ArWq=)u)kf%s%GyF@gETIc0nr7{6TJd48F~L;IbnP+0do zo0EU#x5^xaU>JkO!1w43|BFIT5Wnn@&gSsY6N)lR${^vI;C76fr^!xr^T2#z`Z6uG zWqNK0<`=5R_YHvY3o>-^OFRBk*H+oj`{ppoE4zWdZ+7vx$0Fr1>odZp81X^;Vk&oE zI@wI4(UGwCVJv;*bX^M7Iag?|ZL|LkHe)hN=`@&M!ue$Y-gl(|UHmd~_RWY&*d3|f z{%uOc9Dbk?+DYV#vfaRC)hOxPInvVtm}9bCRO|=&e^xEF_0h!UN0!H3SuN44e$VO_ z<#ZT)oi1+{!gO5g*6`Dat?a6d7IWR2;R0!+eC zxI_I^0*N!CA^F%5b_lS1Yymp*F-YH{^J;JEQ+w%>cK zDTDYWwwO*Fei>KTe-lZZ$T#Ue96mezD+H2WSZ@=ADS@#SKQO=04Xrx>;TN(9j62`A zczI0!Sz^XbEzGN4xWD_XyQ$_^Yj3-(szUnc(8QT6N(Pe4#?RUS!yVS0 z#T{fm(=Rlc^M_SsBCA|g`VlK%i!v$~RbgHpxOY~?`5}RDXTG5#_?9DbXb_$pM{ViX zwX;%u*Mh@+%}xK~HZ=xsA5<{z?0M@Xf#8mw^9VZnnIQMa%2Y}d3r?x$(+s}6?5CN> z`8n|U6HN0$sQ1Cli`(-U7e<@wS%Sf;gm68^8sAHu3-+Yfu_L0EkiQGRh&3*9u^H zjUjaCX-K_!#JXR1imJ(vovE~4ZF<1Vt9&or#k=*i;MC6JBk1$zZdY{5Q>78^utw*# z$$jGYsGgWUAk8eTb}z6nM@oZ+1+1sFYcAdc(bM(_Tz9_SoS@O;@WSYWr0k5i@qW&$ z53bEdOausz&$r*N_T0{GNx;%LyTqkG=`!2qe*T-)`(F9*A9k}wCAfvmLCx$ZufBon z&3QY2?22hj_r<_)FLVk;jzEDKTjOK~5V$@S#0y z>L!pt%?xWBP2?SY)`U*Z)Etn}s4@_aZ4B1abMo1Z!1VM6bm(bFKa<~s^AH@GL^VbJ z*GA%uU9#42NnPy|{H)yZ1Yvfd{k-+;@QY&{z4S!`+p}NjSVHwb5*)=)(6rvd{&cND zzTaF0)-|*nC5=FIja>vUbmcYO1yZ^iI*tpMN7mI+=h8h?6h$a5Uqgk5_Snw7kxH_(DH`^BTlMSb+1(b)l;s zaC1<}ILkN5w_(CfQf^!Pd$&56flgxnbVd^)2aZ?E%iFaBs^Wu!K=@V18kep{xVO zFI>>UFOWGoC`u+wC7UT-u?>{e;y!Ys=@S>`N(5d~_f1T@7(n}?ZEVgg1&?l6%ED%f z!y{OHUCyVIF@1)1&ke(uYPlvnc)rQ!h(2wTboE5_bR84u*^?tr6Hj&SOM=&;jCK;-(bu>^UHat_dpB&y^gf z^GXrfX+a&H*n7|Rtf6RfX{>5oZTR*a5V0CM0qecG>r-0(ohQN7t=54bcapdU@jQ$; z5#Q>FA7+7djc%}jJ1|{?1Rc5tQir0~>|5)0@+G3cUT#Po3NP#rsh1XUuCOW*#MnFm z=^7q%VV8(Q)&?GOa=(CeR#onl%reTL2m3#oNoP2)PG))_eGXDuroie@dUv)C1)0y} zfbe>8nZF|{AgTz9=Q|w}yfV+DlYYF1jCi$4R3P6h?V9U+1-Ip{B5!cug?)*^-!iO1q60mO`kFtOV;+wZASMU6Mrgdvp17@Bsrq67mxm@T9(UfR^rZfq( zua$X&ZiYd)yY@uo9#D&KYF4%1nX&Y;hT0KdYYcI9lGiPvh#-Ody9!g=gNHwl#_lU z0&m_iX7w%_C2}Ns-`;bHp^gye;j(^y;c{#g^*Qyx%*Y)pZq0j(yf`QzdTB#u60B=D z`@gpU(KVQ*jd#8tV0oQ$x`g$cZ(OO?TJBZ0aDVkHY8sfiUb%Yzdz2tO?Z)s@@54ki zTp5phBVqf4{xXNNk_|)gM4XoEUZ3TM@)nT(ntg>QK=gE{ju&+0HGMC0QQr2Aw-#a) zbY%Ei)e58$St0K$TfWY7XSzLqHZAzeHhoTX`HD!L<(<0UKn$4xD}AF)z>&6sQyGZsW7WJuHBm;=AEs~fn&aNp zr~Z?CXESB7N&>bhBt_x!xw0gq>Z!K(ucCP_tq&pnHA(?W!1zTRI`{>0K4A3fTd@Xt zu2sO-GRqyeO{Oda*}S=Ii=XZxIWxEC15^mCE1Y?Da49X(r^|OY7Mw;fq8D(@*iD#s z@%@X8+tMI;O)Ew%5Ps3%oWJvV4P6XJJYOX<=`y*QlUQfC3%_;;Y;7!Unh7Tbk~t`^ z8Kc)^q0Mox;k$u#%puYtCa-iq4g09YP!zAD9&AKRGY-ycn4M5BfaNum(2>_b<}9DEbaIt-E^&p+ssKdCE4PI_1tJ%$o-kyIyrIn>KFMWcf%AbZHGp*MW5IRj_ltb zoqLdKIjz>eGI4|(|2^OQ@??t#7{9nf2fsk-%}OGzLsbuFwm+LbFYdURyfkv+qr{T2 zL~Zu#DGUYiOQ`l$id~Vx{h-Rt*cO&+gycm8o)S|TtS(Yr z&r5@Gr$a1B3JiDncOG~4-iJyN7|Z){qAgv;on=ggy?RPnu&E@GJP1}2w|zp|MA!IF zKU*|v(P^o1(2Zipk^QEM3x0G-{kvMBm`T40jJwh-BspNXGrY67gXDyjJYBQTaV1_p zb=}R6DRGIO(eqMqv9D=VR;M%ZR}uHB4nP8vb7) zTMoJ^ko}ILvj_<8C=dvsLr+6;LK*xoHgfQf?u9-c3oWcUQAgf>-zB>#@vQ|jjpp{e zZ9!Y{SYXxeu3fT-`L<=b3D$THy#UGOOGSSZ_~1iJ8FK5t=WUUNS1f?&X#r=sJDU?i z=In|Q(L`S*_45d9lkkePv+*kW*S1M2SCKGISl*`x{r#>8VmF4^cGPln4;ha&Ke0Yq zYxg5{@1>gdQ;OSqLy6tb{C8f^yx{N+keo1!@(?=uYao5Y*i^n<*5{Go+U#70Oy(sc zdmovVBSL>8vMeGMs(^H0S7n^3KHQ=_>QNzEfs68T&c=yMC)WJZQ%hK*+PEZI#31mz zpw)UgAbOfCg7MDw4MTh!|8M*i-ioswYf8IwERotVvXF0G-J3j78jllInLs}7QqNG% zb&&Ui=b$h%%*M+qPwK}mB`vAMRn?Z_i1p)5Qjh+g^LFf89suU!M((VSgW%5P`|7GP zej^qf;_k&NM=@a={4>kvTa-%O8_$cLfN%%vCBcT?r%ZXIbvl%n{}Z=PXDe3&EY6iXSlCvE5nRLHa$CuCHC< z;X@5+Sria{^!b{bF-Q!)i+Li*Tyq$9n;$HRQZ+e5>if>JC0sTfeb`0YFQX+4itc7O z8VfzQ$!EfX;7$qt5fI$jMNB|fejuzns@47o-!qFJ_D86&qqxGM@Bvm-z0`AdLn01P zeh?Hbd4k+l^-RC>iQ8OL*Mmu-&*=H@iq~2Ej>XI11ZeZY`9ai^??b@ygICazA3*f< z&O|4Q(!|0exQ+re0m9{a%=dxz=w=OjI1j$8-|A@;@4-)=S7!sXrAc2U2FnhpsPtC% zo_(B%m+#BDmXgbO0`YN4qP4*E^e%MiX%3UG=Zv*5&s63cMvSGDY~VJx8C=3#tay3? z!*B1!N?OzP&r6V;d(HC1<@Hzoa=299anE!sTs+y(ksp>WvD^?n4Z8IQn4Ye?vw9j* zcL&X)64zS$PHLD%1!7+JYcZttWm%(~^lwWWNh@Rf2#Jsb0cZiScY8C&fIB2nvNhku#*_oHaQOWUm z@IfPi2A^MB4+wWb2X=@3tRgh!_xnUYL{a_ei65jgBRt7wROr6KsYVPH1mg|~tzHTk z?rQHW?jSzSG^ifdoE6zAe|M-Pmdzo%GxHMrPt^~6BpF-2+xxhVE{cdZ7G{VjMClL* zx>v%{_Q@6W&X$dfo(brdVdX6+fpIs6cA5hOcUhF!(Br%_SQ4;Il+r%?S9f z&RiZHT09pxCagQZ)zjNly8UQy6IZb;*UXPsZm6YUUbFv+vOZw9n5~N^)IhQZ>zapg z4|aj*8nTGFJFBN5{T?txXmnEQuvkawN78SI_1lr#1*ixK9~hRsbC87t>1p-r(s!F& zLnM(fRaP8R0=jSIuDF8vH&1@nx2y$I{H1_DnL3#3rXpk z%mbc+UHN5$d*8O~U?h7rc2h#k3z$UJ^Ap-vpni{QSF>;vD-Y|@d2!jnzS^mkuHJr>7uMRRKW7- z;yas9L+T>%wN9TCW(9nNd@Zpar^(4V{nW}@cVLG}KJO_4`I0cFi9l6%!GZSN%ZgWQ zLn#f6dqhJzA>TNwFXK=-U!T{aK=SDlF~fgM?0GV#`#HlCtm#H%b10 zao75!tsV&Oeoj?D*ZEH!9g?sycBb(!3#>d}H#1+nJT&&JI-gQ!1?d`Z4&ul7 z89jn~j9<{AlFaQ9I0c$fbU4sN2x(1Th_WW{UXERq~>)DYty{0v9qfv5bf2##|65GYGRCEc>Yr<4^^MK_w$;k{Ct?g&ov$=aNy6rwtZF9Gq3l;QKK*Bg}_60n#Tu|vIsLa~II3~$Q& z45@jvyFL7wsKET9Emg+>gkLBSuAn2Yf%Fq928MQq!qDMgAoFWp_zU_iFxET7lZi?3s;}q6 zUR3?|>_6!@tni|`J%@+W1+zkH`Sy)8%gyoINB8v@Q@AcEGF#vsMz}J0-N_4;ApYfQ z4FQOM!7LTOv;FFj^PgAjPti;Kb+4W=B#2=QqdVa|5vwe|PN<(EiXq|!@e4matK}Dr zd-LS^a;~2d{_vI2(s+GrrU>elRsVC-7-?_<=9dUR4Lut77cxiqub*m|e8DI%H*A-9bN^j1QF% zggY<$k5n&(*L5PX(8L!};JGLGB8>QHO@mmdVcd{if+j3= zGYir+Y~dw7j5p3InF|?o;}Y9jpA*sIUVOJu?O%~XTK2|!@di?d$|27JrfW)|L)Sq1 zYr-#Y*asF+Bdtx6E^rJ8Pua{>-$=(IX=1Sair@w5nm>)EOFG=!jrR%VX&DBW8D$ZX zL*?UiEclGX$2pDs&CbEP#);{MABe8e;N-dUeGXn`zkhu|Smbccgo$2yNGm7#L(Kep z@!)E-y?Ue#$TxG>It*yVPQUf?^IlJt{lhSE&##t54qXvhIqCY-cTcrONS}j`6u^7n zhM~hZLv+pAyJ7==*3HHiS4Y*hvdP2_19*rm3x0GKSoYzFc1*0*t1%k@F)W7 z8eX%gw?K4_PpKSq)B_;7YUVisfSB>BSGlH#Z7bk7yi=YUJf4J4P{rX;+xeGZVk<|RzpNo67#8`48l6Iqmy5zNBld93jYzo8c@ zWd|T#BOPAKELylNem*e3{BSErI=#9jyvFdQmuRag)#Awu`y!C8xv$qIkF-eA2u#-` zK$or=w$J2rzb6+yOOHxN4g)v~d2K&zInpyIQRm)roh5~OMLPlxsbvX(u zl**0gH)>D*4|i`FRaGCYebXf%B}hp~gGiS_cS$$W-5?;{5|RQ+NJ%%+As`LX4N6G2 zGy;O}=D62+$2rgQyzf|JoaY1k(;D3OK=)o=dtbk6&iS8f>;vTUJ0aU;HtY=_uCj!?GIE4+a0dn)W4l{ZIaco(m z1(#cIQ|S8(A0_)lFYIv+{|`@zAzULtXCqZdyN+6bzDb4Bmmtzvq!h8sHX=))>GMm+ z=Y1(3glmq*mjuJu6G1-THV4LXb6&p>=&Lm2avYghgSp|IUxG}Z+@3jFj$F<5tC$)LtwD0LUD>?^wW?(kaJjh$wsLdQ zm?qoAQzPSg4I{*qmX98-kuzUkGo+I6mFpmMK>S5;`5Ot>elcn_-rEOoGB^*u*QK@4 zZFt1UnIVZ?W)7o%2K>b)GZvh`B*KQj0R4dUMLGBbLFa_lL5FG;+)ZvMc{31f8G+B-m>w`w6y}IYe zr)``bIXjD1%;spdOjMN!*((XjFH)@Rj+|e|DZu3yC)mm_bqrnQ=uOWbqcMovk=0;N zA4EuzVP4qTBIH&;c+iGZn9!ZHd^RHAj{EfqiNI@z>K|8^yJVqQC=RCbW&<-7_>lY( z@1?_9YN*@;F297sMt%Y2WxuTPOL+ZJzf|A~GC2r__w8y>8LF0E?QR+jn!1_O_QR-g z3??@4;G&`I%Msl-X_<7n3N>86)J5nCRS|ySWev$MU4%%L^A+2RVDd{EB^7MUl>q0@ zLP-6%vDS>#N~K~lRYVONSd2}jN|yDQS%{LOZk_`-9rH8Q;`v7en*9$JTwFu}4R{ut z%b$B_3S+27?l53B?*SZ%dwmFoBgw+AZ|%G+P?uFU<@Icc;G|Q;aILSTi)2RAV?}-c zKrCCqGWmp zYExB|N~7P4-N(5e&dns@e_#5m@eryzx`;$9aJu__Yjp>34Nbn;%4K*}(fB=yFZBV2 z+q>&x6NmYiA`z4har_Y7eQ^Ghk7z-p@b|O6Z+KLO)D{N!=mu|ud)&6E3;n|pqbfjm z=Do3CbVtvQ2wPm^kw>h2j7qSfkJ$b!;1z1Y@(VJ!uDExNUd7rjkmtVHuq-~E;_eb- z;KpT@e~h()vH~C9u>|+JHMOv&VnC|0dK1bu98-F&;JC&UHn;}ZUz0cHC9L^T{5}dx z;UQiIQMo2pUk|5^#Y4D+tZAyV#-w)`T zFKt59ne*YTX?rPy+M^2Rp2j$h==P$nl?Q$vQm=VBYf5^@g_#rGy>He+DE{-+U>j<_ zV;YXWYFwQN>*?wtv|fW~nJNyhUgHN_^_uTiI$e~D8pbi?S*+D{d4use=4BrtO0z5UxqZa&z~{k3!)1wA43j)%b`Y?wO`v zVX^#Fh>=wnMa1?HP_GG3Nd?C>2C&67_?9tD1`6zn_#f2|3L5)~&FcrflyJYu3*?jN zt3dqao(rq1teERij^|slfK$p>^XUqzYcgN4MFKsTFo(LgRG|J+xRD(I#$RHl;%`0I zEM`AgS>9>wsfr(dqKo_9lq#UYhSnBquM_g*xV} zZ|B-!lA%4vZfS|Lc#`&DoPf>AKpU#N7ucLH!03(wX#zI-P{2Nih#-bd(xe?jzU<4S zb5tzOeW!pwlz09lFL<8(?S*g+0ck5i>90v-b=K`DOb-_dKXNwbv{ELi74X&nfX(PfaCG)=A@%oTpuNF2O@%Bvvc!XNkkBy z&OEEP*9pdNHTAPlGxJT9ajYR;mS5leXw(u`Oe)yz*~;**K0P+zNd}ImId45r<1RlY z@cXF#w{zv`qmhKIfhm=N{nzsj10^DD=A#hZ3701#*W1Baa*91gS6x_LOe!jl;yfgI ze{G=~WZ=hNL<`j&%7dPCFuIesi-s+pe*a5FP6);1;r6>vO_4_iFl ze02XVC)cj@|r4(%RKY?@gTBU^QIgNf|IYK|C zEEvjC+B>~l*)01qG$fe@93XwD`vw`Z-zLZ0vI!h_0~Jr*+6tb@0r}*LEUby$E)OexzwOdu%2?Dl#6>|2Ib$f$&2P-1&fxTYEkL zIQMA84u@X4LM&-*o&F<1$?a%Y2CpKKszmyaNVCYz^PviR3P0sB*&zC~V|2U<*$&B> zkJGdBak5B`LYfwCo-A?){G|{H0gS(36%*e2bC1>*SmgA{@6Y#gtzJ%EXccez6+Nry z?w}f-jS{4V=x)&WT84&kpObl^hhd#zgKJLAqzsR?j-hQBqvA}8`%WiRccFqS6<~DN zuHFV4^P9lFAbkD!6Pw_@y!k-6mA^%50l#VV+x;u!GMLI=@Mc1CGcm7H2X7_*DFJ%7 zDtg7Q7pBWuAL&RvqzTbqJF}VW+mb?av#R2e8JOH`g;a2B>oow^NN!vzVvFEn`l_XM zZ+mDQSyjs`WoD6 z(@!YZ6tlh#2FEoku)#GzZkC-3-N^k)8HiYIkT4kouRRjYi<^fvoo-|HFduRsOYeN) z^L}@f@)udXoUg`aZPiSedCRkNcL|Ne)~N_>A1wsnn&ei0aJgCW*5+oQUW0VvU)RI_ z&dwFtfbI3WB{6lD+#Lq|U(H}?#9eXDU^W@_=KaZZ`D?m7DqOE^lqU2H@$t?=iYrgT!aBkx=W)JhAp0s zAF*-CM3j)Q)MQWA7dbm`R#SKzW*)41EC{Pp82KPXp&tL^!h~4ebM-RuwW;=pMCMHgv|T9IL{-IBgX5Y3 z*x(wVA3*0Uk*_fpQ3hYsSQzxqj3xdmM(?*m_rp zcnDexBY(9w>&@}m+9+Pm&f^FA0na7X!Eg-)QU+}F1Audnp_D;qJh==}O=S^}ds=37 zeZJ}}q>~);WSx?s&qDm=v%b8RcM3# znj0$xSC_qpt-35W+rk^;htdP`Z)$vh$CV0GWQv;Ld6*r)3iAGh2&v147~d5=+%f(o zT2XR=DO|u`5XW)!2r0A;%g#5voKdiZ7+ROLi`uRNSC=(_t-36wv3kL%P6NB9+krrh zrn#e&bOtW7fmM*j+;VCrBsZ&=Q?boO9KuF^0XPy-!f}|6^YX16 zUL^m5N&XKz+0D{7K}UVR;GXNkLpajxR{{qIQM`CjJW8!!<0C~|%QW&xEfOx`w0qzE zUcJpO1M-Wc6v%z2TCl~Da8vh>bCpq-y~*zIz|myb>mDX znRH5-FCxDhzF?0wB){dLf2G+ezApI!)yy@yDhtYy%x|k)!Es~=Y;j}}yD!7Lg+lc< zpJ*e$j`?W5)=23lX-QElYN=&L2uEVMwY*NecY2bA>`mdG-J0ETtQZgPaI`PVcXcMg zDLan<<;XC^f^cvgIRP6S3Dn1Y^04!e?st9k9TYh`3~-X#e(*?M&wMBIdq&L5JP1do zV=M6Kn##GxUCHipIIrU@ZTD)+Q0SJ%I!6y7g^d!-037MIVh4sJt&r+&eSPdW%XtOa zUCO;C+pp85tgrgBWxuk~=e6y-0WGf`ApTM`I^Y^I_~0YuMZ6b#4#Ncwb9diyf=*ZX z5MRPec%BPBpgtymjtS0R&S1k|fcn@&UV$CNYO0k|M>YTZO0|rA?&GhAiAx>zKEGkU zIrsSWx8L3pm+f}Rem_+(p{=*YLg+fNr{bMK5e02i^MfNrsJ~1FDv5)sQ*2Y}-`e^Z zkY89V6#_(G-N{3&FYu~>PqS7(Y5IBxd0B zrYkk<(!MM1TiYB>)IG-}XnsM6o(EMQleb%et^6W3hiyOhB%)Ge?Chjmd9H^4!B>sz zv*gac2Uv+uAo=CZtfJn7&C`hS%aD!{E!B|;pMWX1&F@_$Ti1~X8ns=$(EMUbVg3bN zesP73`~vj-tdQ#^rx@(Wfm=B@4f0sZDLHT%rlg~&ZcLRV{| z!b{RSR=@KN%F5mu6T{;$K)B{3>5+OX%MpLotjODp*g#ARmZ!2x&*b~I@G8YEdoCXG z0e!y=ias!0Q=|Ul*7lo$eGYFD_%7*Hj`gco7ZhugY*^iET)q)r;OrJW7G8M|(VgHc zqm4wv?E<+9%JT8H3zQ#I%EQv$h52z@pWHC{OGEvkx?}Iup#q~j_hM|=$j!i5gf+JaKqMOLNnyDb6zX61BTFg#6xQ~?`x zS)lL7qx+Fp@+#osNc0%%w<7^XIXSkcl~2na#wZ>huK?<@5sDaK>au)xySKLQ2k`VL`awBi#79aD43=;ARr%u? zN=0c#He3G0o}dskhK`qxPvFe&B8dE*~o!!0B%6*6R*l*GnvfT0qVsd3x4PUVa*j zvoT+KhB4$p{ow#IM0Zqz^8_|-#(^e`2^LPl_2`eMJ#)N?yz1}iAm{yAM88J_=&pIz z4xH{RZ@uomTC;xKGU}IJvQ-b8Qn3ouSESR_4L4qA33N8qP%kb zGZaxZ4^@+_P%@XtP2ZB=)DA~}h3XD@jSmf+?nZ8{?tuFA5Sgu#%QM}qd{1dR;o(jOKJ1PrEg49C8Z-Gz?R%YN z)=IwU*FOt)^%dFPS|_E6$kUF#boIX?cyk2I4NGI)0au?chpqaw7nR%HtsR^`sely? z??ueRPD=!_0>#VUcMIsyNFf|J@CRMzh5F^6s@Zx`c=Jojr$0=wI7FI9MRn*zBRMLT zbb$KwB&P+K9M`VC3>)=nV9p^%?pOa|eed%)Lc)BnB_f2AJ2jQ`)&ak}-bo$jKsZog zz=m|JjUMl8l3%G0`AoBrT}5d5*R52PSGM;nuc3>s`YW2dg6k1@%jLmXsX@ zvC*KX%h{nbP=A3p=kNvNFY2G^5>4T>x;tGJ*~!`QIz#A{0Kf5W+sKjox~f9+&AF6|s?Afz1x{JBeC@?rHm={8 zjTJp`B^j;vW87_*qtNR5fH^vW#b7Z0LKgnz*6&9XG+pK=NZ{X)9;SHQyS>9yB^nt` z$U1#;;u)8L0@0nD_+|LqJ7=k19M`(Uul8G>(|-6g{iy5r5W|XpvZH!=2%x*ip$K4f zN6#(>8}(_RPAKY0C{_$dQ}t*so>kJQ`glpEa5j6r>dvfb$w4}#PAF~NWAge<)Ju!V zOj7Yt-aX7np3f1%ztP4r&Sx~)s3)d@I^jwJJh(dHbJ(a80_Owf4jF0F&eyE5o@T+v zt~nbfA9n9!_?R9FCGJezoDV4Bkrk!!-M@=v6irtfC{(cQ>l+$Th~a{_>ZVxDmLebo ztrM1D%z@gEwmLNk8~slpzhoqxBVr*$FYxG)k6A=p>MtJ-ggP>+XJezXQ9OX;m-~Tv zJCzeeQQY@=kc!D!X?f-l?CN-AHs`Nd6ZhS;E9rpTd>~o_F29(iA!!2wr zuV=l|^sDm%k`1*Q+R zIyDAc{?ef5#ZKgs<8s+{D0DL3Qftz@Vv?o!Q?1=h$>|=%Uut~Bm$`^&;l8~w!3fJc z3X#uyHbOy7WR4;kyx%FazA(c0Z*R*)Mv4JAfAN3~e*yM8f0TVJGRK)1CfPE+tfb%{ zjj{O?F0yaEoksk}ngPULa@3vU%ncRZ!0b`p`(NB%_Jvve+SR=|A5dWN!R6Dt z=u^3$7(AiTitllrIXq5GWD6|}3O>*4-u3n>y_v63pk%d4YTfBYzZ}Bm!|BAluFZBvxKb?qlAmO3 zv6m%~^KWkp-tZ_Vxcov1Tloby{`A-WXf-VY4y81+m|2&odl#ofWx?=s)3@r55PvZs z9(cBynW{WHWaDL)LumD9eAPE5xG3T?(H+8Go_a+;sK4Z%`S61CmvPwgmp4TE#!X@$ zqU`z)Xym1j|5~vKtaU6uuM}G6(Yx8Rn^avn(o7s#+)t>^mN_V=*e$GmDBz^d4WzV{qn^jui^ZqTx?%@L;S_U zxZLO~qvOdl0lx{M&DohlJi&t(Rv1#1!!HIBo_}{HgZc~AcZxl5{!)Bv{RODYo}XCP z1$I43aGM(WeN6e|o&?_S>LX1@)F+5I95;2@*-u&1oG;Td*vX_Mv51d~wGBDm3>?1Q zxcKchEtGqAoB{BcwLijO{6(2vo=x<3HSoghKhdzEr zd0HkAP12$iQCn1Zx#&~J%YXZVqP%Cs!R6*F*vQR5->>)Bu%xolBUE{403pnFFXH2~ z4b?SaM1k*~YFZ7D+-y%EvficsAbwWI`Y``JB>sc@uYmBKD8av_hZgu=|>o465rB& zNf$k()phUkLw{LsJKUc`f3fzDR87j}(0!-1KDyxgeuJ>p_dC`$nM}I>`N_p&{e_Hf zC!ShIpIupt#@5egP0H{Pf6?yXL2Wh->WLJWOA^d*C)`G7YD1kF{7!k#hU=w{+R_uK zzu5Er$pGgsFK(^B0J$0c-R?nEfFNN}Pole0HqCNA2LUR}u4qBV%ObOzxe}?%rKj5h zlqJmf(gUVe6snQjqK0eZL=7+oY{CznV^+O@+^nN=4~)N%g%{m=uE_`~&MzT)>aoi* zklG!>FN;h!L$N1v zdc8SU{U87R{9V7&6t#yEi!J15u27++GubAM+%)y~Q>x4Jy@)$6Yb_Um-#?NnzNYV- zmwWiUmUim%V60c8#+kjK#98hC|BmE09{nUUEqB~ zuf@>o&HniRct1>YZbe8oUyJ@eg#7Hm*}z`EFA=msTP&9Jix}%o9pT&aQCi?WQ=$+d z9Y>hf>^opCApLn|U3RjA)#P}EW^dInB`5TBVYdpIKLv8Ij_$wyd;PEX?dJcxYCmZN zp2u}=D)gN&n_fUSFhoeE>9UZsp}JW3w;A!@IFl_}$m=@Up*SaDnt1q!9LH;OV0t|m zQTBYnHEMz_bmg<-yDl*_sO~InD9FI*j-I`EfZF|=|3o{xa&$hW9hP=fmBcl>#eIn% z<_aAA=p;$gyly^Md+U?7(%2ot!L=LjS_b}P0Io}f{68+v6MzPc_ z8siB6`F-)f*WZ8qdFEaX>A-m?DeTmRtFiA@wOsgox(PqE9(&s+`UYZ5w{S#k6G2|r zsRnLfeCi^;^3!qKW&~aRQ5?gBRE+|?g-qEeV=DG6M^K)Q5aNCVhNo-P5nMRs6iUo@ z!sJBT%7rdnOwjSWs+1O=S;&oWxd87QdYyt^|Bt%s!e=4atF4==H+pMz2kb|yeCRuJt{5fnuf+65vj3=>0tI6P~C-29fIt; zW4mzb%jB?UCvB%iWy)+mqr=jeE|>d5<$^90b)sMI;)Epptx6)FNS*(``QHuS-F*K6 zR~v|L%!R8STN2;!j0lSRw4%|qOm|<`GwHzc>kAGMH z{q;>?ZWysPEK0~7nXca+8hjvGL`<`}UtY05}@Bmi~91e%>w^+VS@<()Z49e=JvbSRxnw z{$mg8&7bo>&eK4B`uPvmPFi~()h%7skw+WWkrHs){VLHye)~(fv2>98PVn@7YRz&l z6ODYt^B)aHbtWZ0L$4l~Gnd6U49A-mkaR+M+UAc-0T`ZEX3x3xIc_KrY2?!`F^z-6 zgav}H=A-HNCy2Eb7u}(}4-ieCNxJyoWw8`ibW8i5(%r+2d$nhAWyTn$~ zR<#_B!Zpu0w=s%1@+$-!d;;C_ufeA=PhItR3g>|Of%oeRaJu_+Yjp?oMVR0@#Fdp0 z^EnWTBMk5u&+ko=nh$-!SCc~|tOi#eM3w?_3VI%5j_r~-UQE`Fds zRDtjfN##HN|EK zZ87Ow@_oxmyhSfCqS-s5MYbh8qM%dqPrixCF6#=a#VT#iG(wLA^Xt8@1b(1jVIE2#DU;(1~iWJvVns~-xkKxZNv zO?45(|HZkMA4zO9#n9WJ)H&t~Z)~uh-m538;?d^E|NE|E-uwc}(|pf`*}?F%dvT{g zD1-;YAzVlc;Xmc>6VV-NV+6_Xd6JhdbT|kQPP||@N5kI@cKfgVFmY42y3r+YrH0gD z-m#a`(`Vh(2kRzyjH@fk51tW+hE$4o=+a|k3@2j8{L?Rr&ApC;(1NS&sLsz@Dq7)} zD#^SU{y$Nj*|hU@b#Qq;uTQ%!S9eYSwZ;E$`~>{|f4RbqBSr!9>P)+o<>5Rw84J7G zmr-N9%esoR`+XbpRL83OK0XbTGU zt1^Gcr;YqPcsdpnRql%UnHsTu_gK-vfXQ^7I2Hi~Nq7QB)PLhA;P?N_Rk>LgAJ{K2 z`OND*zQoHH{Z3!Y`hQ}yyy|wnSg=I3-g}gN_h#NhVztl9qU`CtKgrmSx&1qR67O!A zZ{`@!C?~yQ8Xs~p!vf|B{|uslnfKVHOqN55%BK=YMs;z8Q}543*kY$cQl9%FyXLZT z8uwpbFNzI2^ymL0Ps=E1Cr<`=unXFWcfQk@zyZ{?;Cmz|Nqln z`9}gO7A3Cm&M$X-a-J8kVa@S~0QdiYTz9}ZiQsQ;lKl@{^JHVXHRc%w9V$J3>;9Z~ z%nuB+E5F$jH!JZw;Cv)cpnigr9;x&pDgt%b(g}^%i6F-pHOk3NGIT(9#I3rfw+l%3)NV}InPVBYjzpN3->xVf;;T@%GjjZFUt2G{)KW4&HgGP zIIa^540heNmy%jgsL5I zvsY?~4i86O(kdJS++A~$ z)06iOQBn6U5lNHljn>|TSB88a#iFU}WmMH){d9atb1*}Nf6s4oYgM(bR7dk2GWtc{LvbkAl=2U> zfZ>{$DS;T(m9r*c+md`5D|Dr~6QNxfJ1n8DCb|5Y)B)gqL$7ww>;JJ%IK^GUhB1s| zmv!&6ePB!ExYes@72y8=U#k-aCik8a3Gq=?luBXJ_y;M+1s}*T_&p&QXy)_}gXoSw z<~RnY%JxHH!YP0MOH7OycOG}Wtftxez1G;+*^T*nsO~g!E6>2_jsocwY}5$>t})Tr zFVWa&VQ5yP!)&l?O5e&CG4)tftMH&5v+jU!O-^pdg>6ZxRv>0m9`gu=j0sDACLG^v znJ@`)5vf}|%NdkwQ0U$gfTEzA+8vB{}@H$LIC&au#(PQN4$484HJEc!?Ub%?Z26HBQ;m`wIvxoc{9M`;qEv`xI z&Pv|ga)WPKHAnjVVK1S-va@}3I0}WNSA<#~^8JJVca?P8foN?%;&&1g>yg5*5ntj@ z`O`mDx*(1%(-t=Z(&N4dmy#0DUR(RH`F#d(_IlStisv}UZ(Fwr?$2Fv|!8Jf1 zYN>p*{kT$R+Cg(k`sD!;7V%EaW>pkDUK#?yvl@uMETN;8<<6CSOX3hU#W5L@TaD_O zphnl9tXcSo=xW58SPJ!*7w;MJ!1;?4Z23!NtDWe6lZN&1@A@E-$aVtwQ@hD`QeDrY z&x~byA^w7FZ{^zc_pKi@Ap&bfDw4%a4#Q;mndVEkp9GXK`rYk>Y|ba$4vJV(qv&)+|1wb?|g z+U!GQm7U4>B{p430}$Os)0lE3aD`IlDL$nwoHlUi<90?}gGeiHDy=t1-YSuQ^|9dDH)tCZ`#l z3wuw!XDtcerRXyS^RIpX6&kyks$Q^=WK{0m_8Ie`&-zUCj|NdWQ>K* zBUbt34i1yQiyrfBHB+4YZp8fjPSIuOI0lkaDNbqbM+okJ!U$Wj7)3vdNBOgvQMzgT zVSqw0AcmYLnL6v=`A?1>ktlHefK%A&2Vm~FkJ(FXYmy{IWZCVtGh+~wcYQs$lT^IM z;vfde&G*Fml|~~yl=-M-NRT~X>o+tw*H?K~i zz(zj+sLSF^qlL6*(q8Kqqa>6Rb=F{Wclfu@4Nn3FxINxGRvEk8Ori*z*=@DNjddlC(oo-M8qn7ShmDXko(MANP1eMc6y4Pdm!&!8gJ0Q z{Q~rf;wND83t70+t)Fu^K7an;-FFWYsn-_bnJ(ir3WD!GlwyuE^=&m`@j(3LVoI6u zL2%=5kGG^EZfmckQ@870B1yfSM&X-(;>+=v#2M-@cGd*aVEjdyJ?z%bIRLuj-(1h- zWqIV7j-vMIPuApF!-==)3aR5GMl#lFdx-9E$n7?Ru0|g6y}GMx|931Z-*GTLH#?#J z2lZc#cNeTiC{W!|$5j@9)1B9?)g8b!RErz|Lutck@fx!q9ub6VJjRJ+@1W*>fX&`V z!wAt`Wfiyoz@3InwI$y=ElF`lp@VNfJjl$XJ%;GcbFzu~qyVmoW1k13J9_pvu)#IJ zISIknJ&d^gc$P;d&+tt12p>>=94py)sje>lppffkFCS{z8Ry+`=Cd(kz1pgw$@bwF zsJ+F#DmmR>$E`JyXvn8eq{(>EEwvT4< z2UMq}wK?jAi|?2q^_nM@J9d43tddKjJ?bKTmpLPn|&%6&9gdL zj>W(8Fr@?xYGgPaQ|9rcdK!c>soD~(1ere|0z4gSy9G{n=eJgOK>zbF(~qW=U70$G zM{=~?Hb2Crg|FGEH*w(az?$N7i0+Q-bjbJpP}Ix)decKV${N@Iw$|ZOme1&(tqYN+*ezJT(`j&4oty9@$U$ z48niixip$0qJ9Rg*P!GPs(`82@Y#{w+BzYS<1!}u1Ea*LehUdz3|Uz4^d%u$jAw1XTw~K#?jM^4fEYiuMKF98%spk0+-|J zU@ONVJ{l$N=)(K)S_SvKvD@eIxyH2zSC`F*vm08vF@&d)B9}SEsqW8K*Z)tBPe~oRUY3zyuT=0(nx09SctkQodD_g3NC6yA zr@{tL19igZS_==yTGEx;(~VV5v$z$soij8H9bO7kye%aUfN)Jf*+!7hn~|cQK~I)r zP=;QW%*CF(BKGl%zskjYCX(x80OcBXk_U=lxW>JB4YulpoR%YdE`lC&sBTSz1H8+v z79Y5Z8q(dq==wxhYC`IS>^O)e&QAA{^WH?Ebf-}7znfhOVMFy4SotxvT7cid_ZwO# zEWG}42(C^@2wQc+y=;OJP7zB#jY{%L*$EcsSu5ZDJ#6?$;Z0RGf*XH9{TNeEMYEyP zwR{oh*0_vA`}p`v)EQ%Lq1MslP&{T7(1*(E8wBSsUtq&ufcZ_o8eEPjg*Js3Cq65j zd<*xraN**Au$XCLXe*<&2Yvxb$3d&e`#K+oTQrl=Sih8yXy&00LqvI*p7ymzm`kf3#v;S7;k^ojQ{hsR6=#9!9>wilk^j`QH?TEOMJnIK0B7>JVNd!ne@ zKyz%GX8!&K;4i^Rso?x&7dHF_nBOEBea)ZE{hZY8N-ARvDL2b8>4$!5p;s8?W$J(e z#9v4vTn*N%2n4Om;(HVcH*%amrLA3hq=&<05+L3aN&D3U^_SwU(j74V5;H}5>*qHK z)h^v)JkEr7^kt$~+c}@Oc^vvgnJR>^U6Y^Ortgv_pWqG! zAmF?j6w5uw)chp~&CN>YDo?=WW_8%e&A^<4-b{Zsi)F^jCoZCs4?Z@@O0upC0d~#} zYcm+%rXjicO7OXIXVu~FW%>5BDH6kCQ(i~aIHJ6dd)NsXiLafr+@QG`ad+7SOm3F9 zLxYVu2jIMI8zJV!-xS^K1o_M?8|Q}vRrA{v3*4{6Cb4MD86f$^7LH3yT_e7uz=60q z>h6$_RvsMvJM}0_7OaZrMsXCDq(C2P6KMoYeoPwsncuxcP?%F9?F5u)#IJd0V6r?l!h^fiSA&_TIlM(GCy7Lmym6Wqz=?)^X5{Ji=3DVrsI zwDI;D<~6-}p-f28goQj_Bac;n1tBCiOP$YGoem0^zokpzPD@YLYMW%Uk)I;d$flG~ z9}-?DQ-xwe)1f#n&%5Sh$uPKplt{puOwo^spD*4U2F?@DZnzGj4 zUfnsEd+Y+?8Ua`88lQ!nu%eI2cf*Kk194AZaYei*vlvnPlHFE+UR?m?8Y*IR5^(hz z9N4JW0R83&+In0AUx~rMdd#`8?+t7_Vo6FmnHZ{>r27SrAoZH&YEi_=pAw;wre`=g zq9=d*@Nm^s_^MJFkh+xSJKc#;fqKn?)&ns0ns#+j*y=auhJLR96B%)h>>8!Es2uE* zWA$8kMnvWdf=`l3IV3l$(|Ox8DepGtyYPR0E`>|MUH7)wp33VJn(3q^veD&RAdr>fcX#y*psuU^CJ=JvO# zAJQH{bF(?VAp^MFyaF4!8JMF(3`xqNq31LGvznx~@lt{uU(nEV?#+|h-!{SLH~oOf zr2vElw8iL0O42DtwJs7E@GP|swXJfocRPshd)tMlLvu4{73wya+>BM6cI)Tp=AH-! z#CU}7BYSXtFR~9OR9zoXunsa8jnD~O!h>+lff@4nBkqN;uV&mI$RE7texu;sJ#xKN z#IC@$z(P)EL<8lTaN}lXa9kq;8(ahI5xI(+C_2gR8aL_WDJ2hftX^VVQnIF?c~)UP zFWC&?8f$Nj9UT0X0jERqMBSXj}u(uSVJEZaNNF4zd4>Mo&Nqw48|2!?B{kf?6` z9+B3}u`#slQGyD3-@`rPNw)*fwXh}9PuOZr<9kAodJXbGzeixp>2(~5uCy1<;u%^~ zZMNi~L`}|LJBhq{(lQF5UK5{152judGi7sY`%r-H$PY*r5@TmN=(66;C0q)(Jnqm! zboX{bX7q4)Q47)Cu}U1O_cqtepnRoG!rB`3y9t9LTK03@`*sz~X~pR~t5DskX{^?Q z)7`^cuRGba03^bAqE~1nb#OruZ9fQ08|KsgG=BM1!)WpnqPz9v0yC@-FZiFvUg8-t zardn?vmXk~$Z^Izd5hy7u&PS})m=&IsTVliIo*2Qv2ujJX|9=@O4zyQJq7<@#k=2~ zH>dMBz2wJt|0;;?@{JoxNSyml&&X-xSGhCOAl;=(G7`dygtyiv?#W2lzku&MhZDv@O_uxE(}I@t5XiJQJ)6phUsy`uJiFO*7;LXV@+t6 zt@dlFPkG8v+`<;K?Z_ZG&aPO)T@WQs*G&qwgXh&yoo*YltA0a?g;sn9ew7qwxdo8p z-o8=-m*c*}MvepKx@w&@znQflnI|Dzdhrvw>cPjUqK)d9$Mf*;Bbh*Q9BF8U=UPAO zPX+CIB}zp4MN3kHhFWDE9F~AzdPv&dZ&HCAH>Y3&CdXNZ!@*XaP!NCmk$HqGOO;aI zpE(R?adzy8E$jd|lFiGn<7pMx;Avogjg7+fWwdD^k%0J%9!U`vLq8n(6&5#f)M$-auLy|0prLYg z`FfHMG!R=7ll+qA;Sg&xg(LJt3L{>Z7Ql-=7?)O=o-*oI1YjNMnDOYQK4of=*6}`ZjNi?TTs?j(@Xa>!rASss-f`%)D9&@4{$v}$$>3PA?y+EA@P zn33viLu@PHFCTLA!1)UnZ21dfKdYM4+Y6m}qTQ5Oa&xwlUy|Di_OIznAK(3R^PHZ@ z$Wgp6?LzQH_a;@_Mv;T16L>S-n$7M^opows>E%mXL;WR6^3Er4{-O(8{?a9*LN=me zBzHhXqw;k>jV7&+Isa|O2A5gS{WHjY42^Ce4RpzBV2LrudMnnfvWPIrHn=DaU8SDyxigM5#7&Vx{KbY^vXbeMl_e#^I)|$0hh4^+eJ|->?jnwQ zQDOGI&nBp#{xTUgOb*UpCSc27rVd(otR$=)X3LT8ywn+RNRh3Z?4;gy2`kikb8{|* z_*9w}HBK9jO3>x|qw~%1jIz%yS*pqM3=iLg42Z76%>r`{PN5*rrC`98zt{~MU!eqf zW+0_7ZwPae7srIID#@Dv{XB^g{`tmVLU9okykF%98ykmoD?YIHFA8fhC8jZG=5&DT zob?gydj<8E&=aFMaQ>nNTmC{bH!elxUmq?lU_IdeY;JCx6z*EB$E}*Iivg|?avwj$ z2Ak#X%+CE)x86E^$>I3JMGWc&kP z>eJ>uS-<;h_S!3yyNOJH1H*GlR2m$wA^svti0bU9wB9}O?F1=lxa2q1yw2q;DX#Z` zb5~|^`j>eW!+(1ak(q|B!1#;2oic2k4*>QB`L2C4Xsa0En)uDTmF0sn)Dh5D`|yuC z^7pb9J8nq7`IqFh;BvoHa7dil1>W&&oqjlCQFZl^IN!`jS@n-sMi$SZdl2^tHo*0p z`(dNs44gl6TR-})&C^)nY<*IkEKlV0K5_B4InCwBpu+@JpKL1f1+um{s`b}z1htvT%j!AD1O&#i{=J0Hgg zYAAzhz}*GK-MWZV2dNEufBZ?CdFJHAT_(nbISho1!;xFN#S$Qn#U{rdk~H! zvKPr7MU^qtuQ)*Z^)nIeX}>LB^_YL)q#ylRT(F5)n%2L)U=gO}hu}EU5Vkl{Gx8KO z>)SUOkv(LqH0K!R!||%fpt-K^?_GzbZtCCybbOzmp2*^cWfBObH~BhNqFy3Xjo0|g zJ!>{#jDt^Cxbts6liD)pLvS341X~*;3JJfZ>;8jcD6;2B5E6>^IA}IJ6fL74jZbHD z{}F^Ee?2*$F!@ALZ{3^lLH*gQIs39FiV5AvQUxW#_YqUtP&c3)IZoQG0gfYAVT&VI za~~3Y^F1YrxknhLmEeI|9z+g#Oh`szlHOtA&GUd52ZfgfAEt0Wx4CACKz^zwonH{z z5r_bP8pnI`yp&Fd9LkY2I=rBs8*Ujc09zbsMsklNG|obGUd}m*^`1`MxUrC)#K-ta zzva@8k0Bg6RfzMb>YDLaZKMchY$QHw5L#P_H6da2#m~ zTO66~%wbS+mov)PxL@iC2Kz3N{io*P!B?*>uD5j}Ask5~`B)&u29aX2zaKa6m*M^4 zSHUKNJ{n{(HGhIQ`fG5>pd6{1+QkfxBgtTkBMX|xE@Jmcg7rjAWmI@y9e$;>eHcKp zJa78p^zJOA9@!f>amQ;G;S7#XHBuz&i|{vma;$OyN1p7mg5yXL z*y70bXK}JFWfR#d$UMyh_ZeJYe!SZG>6VUs*SlN$W^V+ucb2Ny|HIu|MpYHQ?Y?wK zcSv_P0@B?eEnP}UmkJ2d-6=>(OShDiG*Xh%A|R!7Nu0&swca!Ke)m3S%rW-AI?2>7?spSlaNK#IBzRGB)iAkVI7>CL7({T#lSk!@wf}%{+9*l z(vd@EDTNeBii%UB)tp*9zFs3CIL%S!kx|wib@{;{9ckjmf5~4!!d+2S)tNgTwBmX$ zt2&@jb-;7vF5RlyOYj`5BZVUo!+`0?Lg>(uka<}66&Am)$Da#nroKGYc@#6fOX_Yc>EVY zbY$F&?!8?P4q2}^+lMiAbVUpsw{Rn#AhslPG?HP@sX2yEMyo(o1mcU=5Y}|~qdeaf zyPIvLr;TwvFjlHI9D7T`$Z^!Dt3O_gg88Dm`{e)_Ur3;fFGPQ1<_qer`dm(Z(M!HC zp49fTRfrD3@;OYP3m${`5>+i<&RHw*EAjb+=^vM8O%2;cY&Uh7f7Y+JY>M(;IK2S# z1!=e{1{hytp^Gm>ht5gm{tx@4X*LCiH*jWEqWKcUuQqmu54nd}KzxB;!u^e)tmE$5 z8BSiGh8LxKuz^Inx-VO${!k(#iE9)N%olr-el}n^#T<0;WpQ7morz8PdpxD6T<(ni zGTa5$`lm&AY@%yr%_IA&s(cM7S<&s5k-uwOgMx`2> z7|z&Fa@O=VIrO@g4LLtVtmH^9f3@Iq;{)+UNSt!8+@{H}Vt;DEk13zuFZfg1?{7rH zOD`N={)n10UW2Ts2z)vRj4u<=#TTsA_!cM0-yJht9$hA8EUH&OUTRS$sJt|FX)S*T z;tR{zKEK!CoC~)bTQ5@^eDkq)nFv|zXdWwaF6kvQQh7P_!{EMo$ z(Ms)mc-bOfg~eb?eOE_xf_!Y6QO@}=V zd8;8nd&-6;O#T?Mo?`IvB@n*E%^2N#|6(|nndBOI;2 z07VVRzjO!u^z+;B)=-T+*&=@J-Wo-xmeBmYk7NilRD*F4Zmt9DUnHI=dC{4tP!VXHyaS6Pl-zwnxT`_YJ>6g6kh zo6>BPntLl5u}pM*u^i%GPz(l#^aeM-u+Qn(pG5DV@DY zFvy|?@nzU17}kT4z4=I!4hdmqwU}rAEzM7p=Xt7?j+~OS(s>;a|57!q1B@@6(8ZT| zg1ydM*@shcgmm~Ss+vQ$J0BB&SzmtGe`odW?%d+kWc{$8t$QIbrABZmyoDaYqc7Ae z=lQbEX@A3g)ZMqt(+)B1il#^5lTUkZM* zc9AxP6o@Z_KYPqx&J2joCNJY+{Al2y)Ka5=n-HtNxtLhzC74zP1LjLvgkvBOzO2vW zLWh5W%vGiPA$e8B6jIgJVJU8YJ|v`Ga9)#kC=p(Ic{~CK@-LsqsnZ z1pR%LrUCIEbIWuVp8cJHN&Io}O2T^t%on(wN*iE&X@V}kP~BF-Qj~Vd^N59siRyF^ zIoVpZy0Z-*qZnQa(1Q5#uA8zX_nYJnk_(0ZdY>U}f9D!}F`p;?liM}M(74CmxnRCn zdCd9&;|o1>@uloTh)ksYY?Lv4eM2FNF6W4CZ|0g*TCLecU1=tWFNv^t&$@mXfBbtW zOWd{m)e>8KPj5@=i#pnVrA1g%Z2C5sFGA>22j@ukS4U!_E0o9$e&WtRB5llJ{E_1OMV&O{V$_owqg~2i} zV}Cbgbrop|aNU)YM&ClWm zymGXH9~t86dxxX95M9INPzFrbe1I-plNolXQPcFctXD%Z!UB54ZztL^plYRv=n2*RuR8r{x{6TrI0!tA9rFkK@89l8cG zw|k!G5h8JNIGLMAoNq%Y&!<%$iZ%+ezI}xLmlnAoT{C5Hc$~X*q+;i%adRETFZEEk zom5v;f%Qw>Wve|=cb;g@zkSqP+e*klbPYzC+`XUM{lzwd|2uE z@5abTwC#B8m$5t`DL+g}+j zg#ZU^U3>y`t&6Wf-$bvGeVIapoCJ3akzf=f}o>ZKrg+(EhGAGPKP~Tq2tfy5AagoCIi_@tc_K@u zvVZl#I^ja794P38Ntm`H`ys^_3gSyE+~BEC#VxO%=Q@@!r|;*thm{ZrWi28b`JYS>=sMQf3ZpV{oDC}b2`eLXu5&;m-U$z=*nf2 z$)3r^65DP#*~VFD{Xo1<_j-+0(_G%GO?6$12+Cz)ZL%3j*2@LcnvYH7Tb$@Xtf$9TS^BCuQ*_ul8S7pvNeR>TT!VosKWo!JPA-|}v?*W>66 zFCJPc-mNR?aohi-^VkgqYhWE?8-w7f>|#>8Mo6AsFG{#<%hw`#5inm6JY;==@ue8L z`0_JJTBETYH#Dr`X;>{g=>zz$?4zO<55AHm4=r$k_<~lt$IRayM9C%TcjktxBi@9x z{;b9?q2Skcx&N7V#cvHTUmW<_@qqD#2fFyez+KhRWP8-{OC?VuD}eqJLij0KgS2GZ zBzFIkyLIu3!FuL_^N1n@9W-5U`0#ZtefX0{-E)vk>oOFS$^;V7!F=J3d*%m>FAC7b zm!f|03z}?gp<|>*bWZA7fq8U|@>CST^mr+SS>snB*SG1&Nig`D*64;B7 z2kU;vm-<^F31JJXSFC$39gLgC_DOJ)UOU9I-_SP3tZ&Hs8qmXkGjc716!t zi_SYJR1I!Gw7qSrl@|{Kq7+(^hi!YMWZA_|XE#cZ5c-|{Te}?NubH_crUbbxz zUhW8h_=59CX}xoG6Ay{@4aR_M>^YyzCsa7mFk}H&3DiekkKvfXd@-{!oC3xdEa>73 zZ$FhoFxk_SwWYnp@647XZbfl60Vx+p?`2sQvq5~Z?*D#J&@O7p_x`IazUC6Ao3WXr zX!Wll6GJk$3{n(pGYDUPGK>M^%S-6s3#9+@bmOyej93RL#aOx4ukx2Rt-l^rzcoSi z*t5w}xbrXX1wCW@kKwrs`CAqQe@2@S*5X)9ANjD}R<@gW1!_-Rg85PjH_ZWrFL5*e z_rCvP`WP=3mT#0vry!Kb@sN)cemk{Lz+W%$W*YA5 z2mWf$=2; zy7=-3S?el6;!E&~F1h7z|5nH2UkI21SIWqi=wI?gL3~l&!XjO!M;^=asR}?IcxEg@ zPHisSE#|f zF23k|keTG%KrHUM6rOlcAb`PBqq!_6SMtWkVm<9{UHq)G0&kQRx7XpLFO4;wbwB5A z#$s}gkJ+_DXnJm6R`M@_`SNiy92FQ}BJMq3G8$ zl_Fy92KBMR!mNF4gfzEU1nL4x9ugUpDiQiP;o?>cX4b;?wc~0&V+Zp^8*Zow7+-pz zj4!WvD>aKeUtWa-;_+dvexJuzN8 zh)CD4Hu(6xU}De8lbwlF+Ig#PE%L+byai96I10p19)A-(i+F)A0C=VG?mSZJkh z_ubip>xAj%OZv0=svvyP5A6fSm)Fq67vh?qo9f>uIYV{v21}ly`$kJelc2?E2=(_I za^Lag!%qwEFEfQF6B9q!QzL>y=$9*~w!Dtmri@E{3NT|5s37@Rtu-?+zQo>pz6fh* zedxsA8)%^^VJ)~8jqdXMy>520*%8e$@y8#;7y932O6fz=s8KS06@l-O3ZEG|H!RlJ zp7rsOzQlJW5}$|g1v?(#zTPhA;0uofXbuETn0pLg=`?QZ4my!Bti zlbArBA3Xgaz7Uzq>_ojEU4eVLpVZK$A%x|`461hc$Tptj~Zc=YhHT$Ul>C!>MZ%9rF9lh+|5sR!N- zA^+y{Qz>_-0hw1~8?g>ub5*zPmI`N{tUjxd-T8A;D;6dAx5~*Tpk*mKWDJiT^m*s! z(pKvC5nbeHP$cI1Li8^<#C=v3?A)E}oJa z-Iz)eu7jq!L3dZ0o~9^UB$rRY%MnG^TRj-`+?>;Q6B*wzJ?o23|E`>&$6=HuBct`& zV4>|>eQ>V(G5ZOGFG(YUK>Ujx!t%Xc7Y|ui;+@97h_iBtu4ty`uxQL%YC?7r6rLoM ztn^H7QXiyiCbL5xBkT5|y4Q?fSm7T#s*}?kW!8VK!<9P3H1SA;ifqx@LVQ z1v>hEkaN&$_I%wA&()i9vtglQZ1%uPnGEMBJQt29m@kV$Mf1S;A_`r6F`YtURkOAHEzdAX;`^w{D(7!T ze2-n@zy(G%u?vVVaq?>Ga6Y~txq4O4@+H0DNzEqlmw)T-p!)qDIJ9sVhynA3hUlCN z7+-!s2VYw6`Y9Dta5PNwZqoSIs16?GWqphA&1j^T=_YO~4|&{^pD}~@^2ve8DI4?p zjEietN#KDP+ZEx=smuDr^GBBZa7B90x9GuqiA*-c1j3gF-D-DU6}58f-EbA@t}2PY z?$1#1dh0Y+L~T^Y`P@M|^*`?qe7(!v@4mjvNB;Bu#h0ZQj=V-=DB+;5=_@*W@!0Tx zH-4I_<>ghjo~3v2*1~*w_y4>5_e%N^e(=;yKhrtm^N(~(gnB-@cq-3R8zAyCmbGlJ zM2GIAN!cTmBddV_J>=*9m)C_5+i}Rc_+Bn;l+_ZcqBqu8eaxA45+e`gb57U955G2b zHr%aSl#%|k8p96bujiYYLs(BIpF!brWY=WDB3(Xyrj&X6Bn0f6xoF#kfcR#NGKYJ+ zE*?^MC*-tn{AoIE%f7SKCodY(^s~|pk3XSf4VnLf;Q{^Kk}h*-NFuqA3w{@=7X$@4-xSNR(I|9w(cP7W%W<=D*0J*l7V+uwugPOyoxFM-ax>Z2p>Y^JUc)#l3ilc z-`=NHN}<zbKzZCzlxh7US)4P?$|lEw8+%JDV(*w=Euyt7{hys9+GYOF`012-9Ym zc-`>r7A`s8%o6jP)Snpj6@v+L@_yOR82#Dr@8;yR_e=dDh!_l(AIZ}*eyoqCeKpWM$V@rJA5R`@*-3_4Ca za8LfDS0K8DCQKLn^*{Xu`u;`Yu*M9Hmj>Hie#aLk@#9RnRj)8hH+4pPZD|00*R;`g z`9C@a^f@|GD7#_oSyG`DYowp48Hx94(K~Yzmpg2wgJsXg45b&l{M@auGFW-?|GwTo zz4X8OT5z?~29no2B6Mi6`uWJxs7zR2phKu)+miToG|!;8Mysdb-F(?^GT*)~U-h9% z!YlSV+ECB5GW>KTWz#&x`6BkDV>-dI6QZXhZ+w8%oo&Ppbm-{;u%5n&2^46QW>#uP z*4!Nl!Ws7rsJHBWCN23OU(o079GvhqPxjh=n>S%4AK9BHw{?vk8gr?Uq>lG*eu~BO zk(9Rw>zbe$IX@t}rdzicI`SIGesa_K1!6{yBIm9z_8}q%d@(pv7p!EsViFtdw4n2z zX~JIaMlDwbqiIhTa%xk#I&X~pWu7b0Z{^5rz;US0LD($>=QVmBOAmqMHIxYD(9sWo z%wgkW5-2{yLhjU(`+cq1o%>02>2Ku$%?Gb{99#dHlOq#BU&n#adlY$GC)c_}F*`LH z>nG(@t+vx$q1aRWMPnb_4?t$ac?YB)pz3%4UHxWKssP0-mY_XMVtyK_&#ib&D(Q$v z*ka$yB=wuQL427o<5wO3T8D>RnpNdh>L6gBxvuqL6I2cPzX&x`xqfJ^!t4k87he6DXkh*Y2fF+Vt)pRZ0)LK3b>$Li*BgeTuJV4o z4`Svg%dEL>cjqZSnjgI_KkuGOtPq}?C2Sv{-QHA78sx<@ocJz^y#rUJ1@SMj6N|w7 z%O-UA7s#B?)&}@zU+9RF7X#a(ahD!x;#82AD1X1QPnSr_Aq43zbCkXS@B>?N6*S;78gI!A*Fh=19kQoFbNF(CcsvY;F`UNM{*4VoFT zH?o2W-1dy6KVQGuobk(ibvNfTN6@G%Tt_Y^k@Pni>c~d2r^4R^V*A!%W(N|{Cpoze z@Rs2Hl)czo~s&9gg^Yrj1&xY)22GeaqC1hu`VedT;QkDsQRN8i=mm&yMKvcUdtpXrqL1TB!d%c7Ei4&My%FAk`< z`N?{2!C!f&R?)>zBWDAWWzp+8NLnKL?43aVCER^l<3kFL0*5%twc%{G z_nQOcUjic{x_id|3L?JGS#IN*&>ctmC64i4<;R9Vwi2Ro5=)@rzj-BLrgrSW{L3(O z_!mgO*%h74hC3MRmWXFH-A{clZj#s5qG48~}4ep;!X9t?;*P8x(TwJ-0_gU;bI#`&s85Zhm3}&j^QZYYs2SH%mv( zlYd3Iju;=CE=-kvf!fIkV>o?K@NzLJ2;DSJ@7Pb|-<%vrjLQljzFCvo{NCrXNwLTa zf`;;jU3^q<1=7d!TxfA33kH5<{9pUU)IfFDWVKDGdzhYkb@nUDSQGWB_4-e^FsJ?~ zVRm$Uls8mUkHK|EoiCLNr0#4Zf}lg!K>E$QI*xEPN(_M?5)%K{$x`;T)4QevJEukp{setp*W>27~>Rm0zK)}N+@IT)Rr58DGJ zgQy&ZF(hSXMtyZ&Mql@I5as@x|Hs|N3~2t}t1@Kh%4=jlC`#k#X|!E__`N7Gy6N&F z|E(DM%apIxxVW0mAYCJfS|N+Ryh>X7&Oz;Eh^mKwum}EPa18j|cTkV*3*w8V=fz~>NAixT&DigyC3JsYw03TsdM+&w?nVUUzm>GW z2J?l=-FFQbU;LqiFOa@pb)Nz}qC=PY%^yCp9@g}uG?O<81N@BBSC8ew@Ay(@qx6*m zbq{8EXy@|m3ZBL^>66tU9UGa~_9y+EpfvB#V7^3f`ZNLIOSkSAbou7!)I@AQmnZ9S zMFE0LF(Wmr4~id--@5OGTlMGEfqe6-)+u5L^`f|J$^%=D7s;Ei{ibH$&#n$XAESs# zYzg6-GlITv$+e?g3fb8HcrQ8GZ7*53l5H*1D>YZH(PaeU z%gH-7#wbRV@=xkqZ>U|NV=Ll%M96V>-rR8;hlM?1qcfbtblYI% zM#KPTalwWgp2MXz_XEG_oU2T*u2~8<@d2W1cBr)OZ66A<&Vds#vliat=an+%5vJi~ zD)uz0aIMhZzRU=^mBQUzt#i1U7tq^m4vxX6)~gL74bVlG9-ye<3=Eao4;p0z+%|rB@C=3{QFHO#@7y+p>GTc_A%rAUa9(4-$P93>`D=SD8q#LkXM>}&Y+k%I2h!cF&eWqizP99LY`q>oGRpupmFg~a?`U+OA7f* zC$I3FYJ+_YDY)))LZ6?kTu}w;9pJx+dt^#0rdLCLb4K*ZqMtj|eS>`iTzApMO0R&`-R8Zm zJIKBi)tY)-{y);AUtT(;6@S9*+^Uo=^y5*8bNg*c?+>awAMB1%xAXZuInwV*B}kYK zpVw+t;TE`Sx#7ld)YFbk6~T4K^~WU!NZq|E6M(Lq@b8lf{D8-?=04Q4BU>Pq=q>GtD05ogBHDgheQ%r160!BO>Of_u_K0Tmp)s(J`|vCL zFAyKMKGO+ZIpLqlT13%b&tZ=P@%uqNxocw+A66^+ju%7$ES|Ef;qZk~f7&L!-v`D~kK3%AYV3Q_X*H5>}2<1vgUi+O^T;j;+U zkp8ED&@`}|uo}8@LUhbas&&Db2Zh0^JxC0G@X{)iIe*pmm{Hdrj0%DHQX1-B_<-G_ zYfWCNLe_f?$tT57@bN&i%{6*4eiVzj;Tf$*hUcLlm~LJR~k-JrB))z*TC z?lqLTLu3jkW4Ec_&~fXdyYJ?mTB(MH!85V?8BqtM7hTnoBCrO3*UuxzM|bjMWU&OYw#=vy^I2q6>|m!)<-ENb`=m` zN}82rtySO9sjrCD%Br`>a*$B?$E2v_bQymbdcJ+ZikkQA{GwerO?|DyH81^RqFG># z4vpo{yXBW)zD!ZqzXHY=bLirWPqb|2k3Vl^GB%2Gl*7-G7Tr#caPuunMX8?4DBtnL zC3oZX*5B*OLxNH<=gnXaA(6djcr(GPL9p{L5d38Q!F-`5tZWCym(S3}m(wQw=CT)> z8+dI29GCV*g`0h2#UCR7N;yt1I52?tqPbBJ1c>VG~2g7Xj?mW|>lt+Kw3uh?~XE&!G8laF}i&u9F zFc9DTQDj*9?M5Cq3F!w!8NdR|WtX5UmkoennGccFb)|SJC0$qM^{RJJRckU|Cr*LZ z7zPHE%krg1L@Kv`DnC0Vkn*%o%C3xiYLRLfr2I5QG6EAa>_kIsvyHI>n?D`PP0hST`*t5pPoko z;Y)+=>Ala#B$_+do^uk(Az3f2{ed}pG<)$!6hld98Ghn0~&#ZT`b$j;g4f5j>w*1+(o}!lVfcuouCq=R zh=1Xz&jI3J;%3I~{W^yXg&Ycw`WHv(`Jt>4d?=%DAAhB=db}c=Yb#sT4$?L4Dk<;} zdmIkEi%ZKWD@L@gQCe}EyiklaFs0eVvm$${z`91;uvi_KuE~ZDT?1L?@Lj%YsCMw} z>n9ZtCGrhm4FrYNC!^d(mlcq6q+$0*Sj~wy2 z*-V9sXx1RQMhUwKh^`TI?7O$?93cBr6rxV6UD^nO@M|l5Nt4iY2P{@m6I;5BM}EFP zKnMBeywPFj=hK$k+@<}BS#D4Ak+hj_dEHVq3UVBt9K!eU@j`OhcJ(u0zWEJw`Q|{l z5o@`nk*?l$Dwr-@&*8T?%Sm~Z?F_F!AJm(HeDmQ~#b%Tf?u)R5(3{?u`I%yoVcdwi z!`p?A9}G?1I%E!meKV`u#0W6o`~fk zKJA>J?{tmx;I+&ojs{Jtg%Sn9x0marDREZviu*`DqQL_W*3wii5Z~-|^b?q_Ik>mF z2GVa{A!FE>M64k|tR%GDleB#vdOcRo5=wA0SzDQV*Z1p7Dx?ng#webIXN7z0v!)xD zu|lVAJ)72jEN>U)+HN=q(KX)*0)Xfmj579ntEVCBhG7I5>u>A#>}Z{8c7FQ6;i`nJFY2t~`>gR&3Uvn|42&94r^dqwg6-7cttnwtAGq};5|vXt;F9{P*!?9&;683R``27{)0H6j zfaf`=rAh+RHU0Nq*Yu3s_zC>hVUR=AG5#?UlAou~bNEH**$z2RkH+16=VC(I$~0$8 z^KlI335h4aXo*6Z4X~>BlUO*lIA^PkWX5>@&2t!|SBVCuYuuqr*T`cUZYEH4XR8k~ zqB4px<>#v;rrd;26y;O;g<61g&2!?!xp^s>8FMCwt?3ZPcg?15l|pY~{5c}h_bJU_ z1ew#o=LdMI>;ls@CD5g7WO7T=rcdMdhAr3`WD@wYxURRF`+~98?Boyw1wnjC^%`!U zq4%+A(&Tt*tP;6D)VoiL-el%DdAQm91!*j77W>81 z1^z*KxdwFhKBmwA_C4}EtjhzUYhIPHKv!OK_2ePOXROG69m<<^RJLxXp!K*n=86s! zQPte*rl7ngC{?(S%;tk(zjDiK(=`O7RLsfuotpz{nclm(M??vnwLSmlIh5JyumHgVo3D1suVZ8{|b7JfOrOa{eM?S@b%w0I-u_lYp^Uv zXdl~>RD%AU6w<31{&KDdW7v)#2x^=H+oLD#JYuyVKVKn6(~FBN*x{=nME=8>iJbcD z+^E5Ij_;~wEWyWN^AgJ_uD@?+*l3IY-`9h@?ti~tBCGO4`cUnH(!$bygFOvp-Tgm&kE-MySfp2_5#89kgb_Rp_W>Z&=Sn3Sgz8`Cer zx<+Gs&>NVpabT&SZ^PO&gqdk1utRvK|H9&2PuHXO!hj)=o*zd6&auP9+>$TYXBz;tshluJ#J= zVtq#KK9Fij`JpX0UT4?F^@Zq7io&cb2{fj_w=WswujtQ*yZ_hU?Z4l@|9U_E`4_hn9^Qj236jqui3MOu-2?wRX2 ziJ)A}$~>&HwoKD5Q7s!-*T5bB*a4zzR2{h+rzVKLwZAW8eg$_WVvUG5TCKhJ*p)cXwYP*9y>mEJd*IEWM=#ecy|>S*%+;?91&f z!hFxrmF@a2TlC7?V#s}=LLx#qj(py*?~J~TaOLr$@?;mI)#Vz+$*y@uMM7kGuMSZt zqrydp;s4|Q{nz{PG!SlK2hZ6pS`O~n?Zwp$rLccM`TbSfa)Bc<1U`B_13#<=SK1}m!$bYJv zTQjzp$`QjLrcUmL+lE+fp-Q9J-OCv7d)t#eehU@no45b&+ke#oAoCvDTM2=fiz~z<kmu-7m+mSet1&C(z;mR?K zekU5FCIe3_;%VCTCgJ{5=l}Kh`mef!{GR_@t$muaA$d(3-PC&6mU|5x*5h0TBQu|m z`KC9CBRbC{CacM~LBB_HtQ<+`V~Krk#jIWV)~mw98gyNCEADXxZh^`q$ z$N{En;63i^xfKvygR6IXLfD<2)TeW97!ZjW>t#{Xm$^M}?+7a`7!9hs6{4brk9x<8 zS>r?2A{6B#$1}S#i6OtL+=b1Qk=c-(y})(H%~m}Dr0!moeG?ALf4381qEtZd?uww< zcOtz@V+kieUnKw6odFvzCRgc_+ZqLLFZkvER1bI0ySw^>Txme(eXBuAb=83=Nmxj&>=z@(2RUOYm~mj9+Y@{_2rIeXiRr1?*3ysotCcSBz<(9ljmx0e z$PnjI&E~AAtpncwPyPMp_rXlU$3Xf}M8Q=1`vdJ6L}Jx*KPhKyo1-1bJ1 z2gwRH)j;p-DUpfB%E!hR!8cD~Y?_1Pe$ZiT+q!l=C$Y6OB=i03w+7ZVmEWuYor@GV zLx1me&0EhOF*|5R`vP-Pyf~63UfihaO=#>f`*2@UXIep@f4|8gu8j|_swvmW39@4; zMCL>yy!cp5Mh_u*OBk+m&-{&Cf=7kUrEQj7-w^NIzuGi@00AiESaA?li}0wU=pg*=F`dJ7#4I#~an4(Sz-l zBHxb8<25+1p}{Yq1(Mg0MVM0BkMkRR<(g+8GaYKwD1KzAVmMl;wECDKrUVP}{{~<0 z{L+)> z=~Z`Y6xpE`-D?I*O1zj7Jdi)3&>gLY8$!_f_q>CAe*a5H|8Mnl=mZJs%EPoWi^;t= zs<4qkdILsNxD(PsgZQ1q0F3-MT0qydXWu4bp{K zApN%`%7CFaTcvVE?GBn+d1BH8(utBob#lc-xk8juuwo1#-Brjp@r2BKWzZG^V#N2pKqTcnPxKw^$ zJLDO#j-Cut1>9niw;%}{&{qHDFg!f0Ed7eWC=Av$+9(0ez;ulzbmKXa@_eoKM$;HrejXO1JN}*RPR)gVv4CB zrz5+2!srec!fkUiAZRYODQ~#{JWYV;Iq>z)r{8@ZA$j3{%L$d#4AQ3qytu_3WqVBb zHY3k4UtK_+qyN`(!k{KZmB&g%3q%ht1T3Y)qTULGR*2wMkm#ue?$3hW?@h&OE?OCZ zJn=IuLyFAoM^3?iGB|E`>QCP_v+4LVuzEx44kwx)NZqMAQbSixcp*x;sY{4@{<%$F z#_ohx%X!!5Q!hb`R;n|VA~WdkE|Qs}#$S$MQ@cumys`FUn{FY|kO1ND4|X zpk1)8agdVg0HSLcx!a&4CxqlRN#t1llMiLrj~;YUJa{S7>W0C_yiFMkbJvGDEzdnZp}UflHdP&zOIXT ze}%ykLoX)9brJG>179J28FCH#pM7)t=KLRfx!*JftDG5~M%NF}zdRd-JZJx}`Q`<~ z5d|@NzK30`!T!9mvAcB+%_}c1iuLd{Y*TK`Son$H(^KQ~ej~XqMTp2oN?0D>7K8l@L-IQgVE#oK zy8Mg9iyIXx?20D?Bx2C?;qRN1gxf*&&Q0h3h;hj?W-t=RDpw2<`>@7fs4IDPaD^ z13LT*q~EN7soNWl`PIFCzwRvV>VKz z%ur#G14Cz_UTV(NAaM?bK^k>7q~Bb9Sq@A`GDDY+M7{hfMzdy4mh2hP+!_^PPtle4 zGRiW*)`@Qe?QY-W`?k%VixCNa#4vS6=A^;;E&Fl=>ObP@I|bFw;%q3TM_?Uk&+*v{ zn2xN64jl>gty=|Un{NAkg#)&kLy7-a+Zw%-iN#}wrTJ^B~05!s)| z*QByc($n22ItR0jo0QPR^0E zoFJGlVY>7^!1(eIy7kW?VtS#_kt-9T)yX7*+E#h%cW-4M&pY z$7RAO|J)ROYa$p}m?Z7dc9RvDEz@JlB3EAr^F@k5_BSxTxI!0SP`{+L#j}tJFq2xs zN*g<1wdjZvz6#0{6p{0{xCQaWqB;<*#fsf*;8V6%;+E;$ zFqkj!KKeSq_`(KVe4&l>?e9^Ei9syU^l3aF=RH;^U`%E<>cl21Z@J?OX}nFqHrn%D zGQD_(Z9>y^wVp>W!j4v_J#h0wX1nIhkR9ntx%m{J1 zHH1|?dYLHK_WRF}^YXGle3@^T9`LlqKcTwltBGZMn4Vm^QweuaEWmYSa$4D2C-@W0 z7v30tcVK)uzW03b7s$=oQIkzD7U3S*){V{ZVz1Y=&LC{atlVv+0`UcgWnGwFbL}^? z-KzAYkxkA|;Uk3lN>O<&_Nep)aYfitV?+VG}$Wm8C~*p zHzsDEsC=sX;8qoFcz13%tU%EA#bWlwXQZQ-Ip1OIUk$A8)17BG45I7Wl=z(Y$AbBy z#f1rwFY3_6m->p|RL-;P-bfR>Pn?U~1-5@%5b_@QzmZ;?d~pTh3z~uOXgGgtXZjeb zy4VMTh85j+UVZLMS37OxZ0OYT{Oe%8z(&P^*jqcSBE5(VaXuJg$aW*__X^4N}fmiKchG1iG z4qjnxBmpc3M})2%yvW`_>iI4y%t5sG=vNjW<+P4*e`CU+GPWZuzq|7q#@?y@7>k1| z_YQ^IJqkLzqQcl^TQ|M-AEUM?!7_EC1>je zi)$8kgtFQ43Vl4h=$6wKl!N!OB^;{@u)y)KrU`bM;Y#9|Zq$6)pb3e*+ykIAmS?+lt9uo8@3BSsCVr6t!EXeRJa23X=tuANAt;cjq^^NBiGc zxyUQb-ZZ-`!!MOKEz_=C5;{sZF^qLDS?&>&?ru0{j2n)2{@ zE!fHKg9YAy`^jfE#z%nhr2xA45_a8y@;pvG3B%6aOzoxqI*d?wiL=79(JTC_)4P42 z!^3twA~mHq_I*70!%8UqH3I|;xA+KVzm0?wrA>_zqg+y5s3WAR z!s{Ysi2~&mG-;ImDfN`7DLy0Hb+jK%TM0h z2_LlaaYdY7x;Ovkl_2?ZT?5N0;Gip~2#am+v-DghL;9liu~a-@@CeQP z(SSZ2u{PUq=p#^00gtNt9hF?~o5pdl#M?#ZdJ`C=@Gd2vl!0_6LA0L<9~Ef+?d!E? z?fw8Pr+5!tIYl%gzP=~*;IeNW2a?96atV$z(~#{rOU*9n)ay);j;z6R%_`VvpWO?i zk)QT3nJOd5{&8t1-IlJ8Jt9xJi9!h0kvxOe?||vZ4d~F3ko}eHLpKk0XBU2v&bcNU zvNb&J*^%v%9*36%CY!LECrtDbyyx*8o2ICEa!`X)gAu5tvkrPlF2>9+1`vRl0#1c z^K&*uEL-`VGPy6y0_HbjO>aSecg~%eQoY^dg(EfGL{Z6?s*Le{e@A^j7u}?D#p&W% z$KrzPPFrSy3rO8jA{auKuEC_;-H=)@BHoI0xsl!t_>OurLJ!|HnA}#Y@%c{Ixb@-H z45cW+sRY{SPHd({v5WwHVbeA?eW~!(?8O%H*h^=TH6hb?E-jXTvtr2XZ>z&2QgLYHFOTAZZ9x z^~@{3^huxx>lz1`{y8AJMw2`B{_n3mH*&#kSN0Uo80j8IYT9(*+6}qh{M(~+l<4CP zdJcbivVRq?y4c`tJltgZ1IB=pvN}~ana7%pGMSl0AY0NF?3-yDI*oz(W=H7q&5c+N z_^7QX9U+p!)5ZOzHgyU`y}g&;vnV%H_3rjR<#zh}My`6PW#f1s3)XYFge283+4VO@ ziy4qPM4NTu^+0^{2nnD$Y~8w|(B+%w{zkfv9QC%f_3dW!ZM@|U`s6cz;*1o~?0Hjo zr)!8L=gJ9DXBrgvx?d-oAajkPrF}V=`=CWS&su-^?D;Y&gfF+t0M{o!fiAwd9hPx; zmgNWc{ScxTWsRb3FgJQBuQS5ne@(#H3*w8gcdZvk+b65pETs0YJRX%ih97-8Td)rL z-M^ibs&FalLVPpl>04lY!GsRJK;~fyKl18ZrJj@GH+o@Kx6YTa5W5~et9CpT{c*Sq z3B;FIk%@2F>YgR}vcerPpor7Wnd~nes*M*dly!(AxbVe^g85>%)@~1kFO1xA(B+#q z@oiXI&1j!pnNlaOIq>p!l8Ph}B`c%#Qw1|7f&9Yz<8Pr>XO>ENIs;8bS+HU;MZ2{0 z=i`D@@6W-4 zj0JS{Ul^qrHfIxGc^>9bWSQbkh;*JVi4^wQOzXeJX2AvdSdyk`Q&&22-hfqoX}(Cj z8XhL?f-b@WsSoV9Kb(@=bi%;>mnjO;GGP6e0qE+#tcsgClza$WHNKSZxBDLQaIY)+ z+n>!j>&(eAawm|E%n)JWy83#S5hvACw0)Rv@3V^b|8Vz~QC0Qr+BaPSN=bL4DBU3- zDcy~LbP7lcNGP2G(h|}LNOwso-6<&z(x~LK*w>o(e)he`e%>+1*zX6{r#ZaF@M1YR z*F1j5d7S@q9`E}HYmt~$*8Mi?aeocvNSdd7>fku?K5TI$&!k6GMclLvYq@vKy=DU5 z_kR>gX}<9MUFeJVpoVZH7vl~*hE$P_s+EoG@H*m}?$3oDDW7z1<3VrcTp^Lmr+2@fcz>y2~iQxJaOt8g~sU6;N!o93(C>;$)=^3Q` zw*2NZORsg8uZ~@@PaqsQ{j&>~@o3ka$61)0to;M_D=GJ!?Dkdb>_44P!sd96tf3r< zEHVdjUxF)aa3s*Dh()F4AJ5_<9Ls%9Dd1}QF|vwHf(GG8V>wEUwP7W} zvrJKW8+5s$10ALio7kmXNll3XhIHKka-dIfSPyDnf-3vQt?yGvH4JjstVuXCRQ6M~ zPBnRMV7P;i(PnNdW#2VzZ7~f*Uy{-@4dU?{ zPU;>6AI$837^=xiF*rz$^+EVU`P#bu$>IT2UqTopF2U)GB5ddjkiS%@p|2qhji?jn zO2`)1VMxH0P2OE#EYi-YUT^;d(HFb?Ir+0kO66z0n!lv|4%A8Jcvi|SxV5sDg&!=s zpK(2g>WfC^+BY!zVvR(9Yx5Vtzl{6Z86}VBRyiKl*^?`9uyR(d`t;Oc()+a6VtPXS z%UXS^WY#^2k5kfSy>UmUjfqA`M93TW+P+_BV&LJ-BMn3SODPiH2{`{^2^;SM=D$g67Bl4B(VDppo)9+0u?Q%~r zF+M5q_m`-X-+wr$4)~Xb>X6XT1N#%z zudA&YduiKuA7-}F7(1Ldpj=~qcYhCDe{2Od`eVTPeRG`}G^yP*402Svh#qbI1k;bG z2zui=X7nfT7Xl&uv7?ZhaQF(>J_OY=x(5VC18+^n1%Ge_DxL^*W-WSaUu6SxC2xf$ z!1Twkic@a=`ARx7(Y-x`vfs^eJ=^(#_KyRCPuag(onRTCFnHrZxCY0lswK}K9(!p% z^QT&>?&CQy_WwQW5kvV=*w?DMKJopXK!+A_geycyU(BA zo4I3qzq$iczCwiXJ-SIV{RXlojbgq>(;zp5Yp8`2#Rt%RdvY)~GxHRkm#`X*ae`SX zjXGL3tg@`~=3Sv&Gs@l_3x;dbsb*lSFYB#4A%zfpbdpRSRvxvVUGK=ejA&VZ=V)zZlkeyE<=H6hICd`E1l0dQUWV$6=AJM}-^_n& zeKRmG`#yQ@FVXM{Q`Co*qw8Al1iOUu>Oq#?p%N_W*FF&6ym>A$2mAm7T1g56yBIhs(Dxg3wahKd{iD^}I_`!0XNQuy~JTTz~!)|NElt&l^C$rtR7p z9M=fK7T55#)>>@1e7dJ=qArQZGs#t6QklN?-X#3pb&U4{gljS$Xb-jEzhhJAD=RqH zpjr*%Bbco!2{*JnVzT>!&J|Y+aE;TSW^i0%bL+Wg>tPuy+Dm&O`>|IqIF!p?{OoA; z&kOe&=fY9Qe*obc76c7}3uGe+dciWo8tHmBJ7#YhYXjl$dR8l*2N_;u_)xByFcY`} z$2G&S#WmH8RL`I44;-zw3vU^@>ScMxaJH5Cp2E$~N+&;saLvSW+#h&+|D7@+nci1_ zYd*f7aE@xiwI7K}OPOE2oAzxT$~6y8I%mOg4K{3Xjc*yFQWplC(L#}eFb2(e-p0?L zy}Qp)b(tE`=;$C^L(dR=$T>*jRV_l``2{6=`~xxR=?=OsV$To;+FgAW6m2NiMBek^ z1jjW7u*EebsO3o_+0T$R&G7aPs#n)#DKPDNno-0q3_gb4yzlC1)_HQPFM(o?tXy}R z*-ZPCc2v7$=le#Pa{^ivYMqX7sJ^HZ+Npuj7i%Q8Tgx>-9^kfIv0tB@`w%|8iDEk`Yw0qX;D=5aZ|A8X`f_IJys%({ zL~a^nG|O?Ms!K0ArTdfM&%i)mvUR;Gg(4b|2P9Dpg3*_4s)<{l2cUHN4F$2{DAGBn zT1Mg^?wtrHqAROnEgp#^)ZK865)N*t`&B2aYZ^WaZn2?=M_?g3IT^a|_-kKjOJNz5 zX(-nql_#)(;Ti>dTiD72`p^8Oe8mjhI{E(wdaLXl!wjCSQ8HuHOM-sd^q8U@V*Y#J~Pz~uo0u$2e!&&pE7?6G%d zkl1=C(rz;BZO94Kk6vomeXMM|+2@N>W|pQTz{q?0J)ygb#h^3huUT-StrSneGHct?w+-IXixM%=1FHM)uoT zSMQ68kLe{3%HT-ZHk9rvk2s(mJa~rRJ6t!1uVe=0n&7AVTHv_m1#EB)kO$z)L@60O z6dX>VHun=dEl|`aN$s@l`HV&@zMFJ2N7q3*T;poRG=G+O?<(MF$*$(afXS(l-<_Sn zWZOR~Zua$nZ#J~~4Tftdk?z8VZwB_opQ$&Vc%zjLDSUrpgmgU7<*Ko2-A0wK$4X)6 za`PTff-E5U|PNDjOY$0a_PG7WOLtlV7 zI<(y&&WVwP7Rl!_Xs+MO#Om`+?&PP|c{WN4*fBu#MU!44*_n(K*~?hiSIHYlRxO5+C=W4vW?#V?y=A1s?)@;LDZJ2uBzXC|S(nSrjQ z3XE?i3+1@=@A0&+c=Wn(%A_X@H6uz$PXzVC?nj7Nvsk3Xfq$qdr0!Oqm+fH+IP%Co`mR7A*`fCysJp#fVQ_W#?bg@bwGZjVFG59bJ)Y0ll-V9b zMf_?(vRvxvNGnlE;*h$NKesj_S?`(_ad?dzgq|eRBr_D=S0K*NBjl5L`12hLP4Ykc znW$ z%UjwT(yvL@wEWS}>_W!j{FCb=YiF{RgRiG*OCfwtyzi!o8pfSGb3o_+r)cV1@41*JKc0 z^m)yZ`%?%AdH;m)G|G704&RB^fjHIMjEJD~Jwyw+Nb8w5z8Wu1Pzf3SPUHDN_kwx8 zAOgqJhOoiYfW8!?1%EcPpQ@_Rc=N^O2=|f@Px^iD*RJ*Nj7I)9{TiYCnJBNS&#zGy zpZSEk?8Ssl#F${bYTDlYusGD89UJWl)fe+O%)Q|Br4_dHg`@ES`s903qc9gjQ|khC zPkDo}dRB*D6<_!BNTMP7k}kc4YpjsJUMXcIh0bl0P=vl092D+7R4B&8P@uHXiv#G3 z!VW4peW`~neL>OmEPITlRO6i^^x{QGz_!oxttX`)3VVt8-{RctXF?cPGBJ7OjR`O0 z;ZvovS#e~V9JQC}nK2$bOHN$&E;jzsnomvpMXu$4D|Ad8Uoaimk0^MSw8df#uf5a&N#_f+c@n#f1Pmk|GgLwBwF z%N4;X{C8s?;Uv+syK9{uZs*->+h-gv44nQLV*vgI*WM3|f3XT>fUUfFVz zCyGSjVRLWi*RYRL#y6TB|*t z>B}YBLsr!1_r8kX+nf{d_!83->6tcwXNbK6A9Dn`kK=YC+9Hn(^3ypMcTV?7?`C~z zKiZOblwoGB9M6_HZRk%5)fbuo@jP()LJM2^vi)?B;rGO|h>x37a#Fm5D;j~VwE`7X zbregC!6XoU`8z-^DZ_X|@|iTc#Qz?*&L`)nud~mO3}tId-grLwoLvspmtbKDC2;!U z0~`7R~9pqKNXG^b9 zIa6r~bdpCauuAC|?Kio7?(s=^MApiZI zEu$n?ym~@>Mp6ccF_mCc#g|h;78l^}JK4%4MZF%h&LUA0_r0@FbNR6%y8E5hm@wR` z9B)kmV#~blr$wFF5SRb_dBD&8?^nP5^-Uo6L;tqdzJB~hi&-yIhbY~iK2hv4FWVpG zk0ATF&Lre>Su5oVHWgpHXSt|NeX%I#T*Y$S=j*zXBcEh4e_!-$PypJeK>bcG0Bw^0L`)t&AyPZuB*|6Sxe-w9JA|D?sEG1v0ei9jqWL4L9y~prOH9%-Q;aw9i zZ-OzPFaAh>!RZSVZ0QSnl9Zi@$01rDUFYv12j`jhVoaE7*xK%_YBjZNkmnDgpM|tL z!cW&E`8i%oW3D|HAEoJRvwc;TQnlHMUt{%sP<;tCZqWg!FJ7>rFMxmXC?|IPBOyL1 z{xkJXc%@yj=|e{&`^vN_YxhNoV90ZPu|d&M%|_4QWKez4T*q$(qc7G-xVPTFP!fDHz>_BM)o?dE|3$j-;U2F|#wP}*TsR?!zF>1#@c33~<-M7No5t&sO75m;y^Do3 zS@H)n5ntqm;?x3EU&56UlfdXpwPxV0f4|?dMdMDJ$4`8%Kf*=}O>5(e?<*cg*OJpO z$DNKvK{$va{OaSh+tifD-@^I=%lkpJTANbB>#3Dh)|OFnX^6ha3uj0@(_4YJpILsE+h92_ zS~NZ}=9$pk{p2T3YT=~WAXHyI50>FUKUn5RJcfQ$> zujgJ^&-RF;+8Q&`vKo%3g=r)$o%Cj2mTxw@OC)Ts+py@H^oCxZc6SdW$K$jWIF#WM zI9)UvP6VJ|qugZ)u3y7=Yx_08oc8&5H|IH2Lo%(fvFz!`xP~VUUReUq+*COt;VpfdV0fA=bpF=Q%bKm} zj*0Cj(qU0<3XkSe&v;7y-jlQ()$Hn} z{1E}anhiOT_kC#HS$-RC08@92?DViP&jjqFyMLH8x@+|=|AE(j6mpwh!NVZBL!+Q> zhopt2H+~SFPTm`3tt`H570pD=h*SJl_?clc?1(#H%k%MA{`(iL-W5=u=9bTj0K?N* z#X7h4{2AcmRIT7`zt*0G!QB(c9bdtM8r>vMdT2(}S^M|Vrws3^@)WCu0!5>^uA6N3(_AeM8w>tS5w)%u3Zzrb3Xh<#1 z$5Y>DaGkSFoRz3^{?2C9xHx>y2;u4bCxp_Gd35bO!CW$k^$`i@6Keix9p9)& zZ-XdCxqKjXx8(QrPtM~tEEcN0NV}*73CF{*p;zCYVLyv~R>l5EO6(o9?&ywR1cRwN ztI%%P>Q6skxYBu*$tXa#cSOOkzJ1t_jz|$8ix^#69Q}j|!Zkm?RbU)eYLct6UDltM zeo-R87`s|9$U4hvGr5!0BBntHd>}#ZPW6Pk+0~GfT!1_FdlmjWCU5P;x7o2|@ER{^IbRz2BWGhqk+>g91`_154ja z{W_G{mf0&;WAqdo6A}S4-y8Egb4O_W8CIgAaq!I<< z&w?bn+`fBvXz5VCW@kinV4OV<%ea}JlJ`)nL5RX9bY^x?@}2SjI~Sn9l9D&tT$G<2 z*ja+U1N5iW&d|X0r>&9BZ*A@lNI%AF{Rd&PtE)Q@za9^c{E--1GK_4wtzxN-H~zD*0XrZa;u$Q%=8{pn1zJm&pzpN z>qn`dYJIINkh=@5JFRh^Ct&K166pjsauHyjaEBi)mP06?;t^`Mohi1g@^R{t^ii;0 zbH9)f!A&m0JF>^ORxC-U{TcJ`)wINYbs`p3v3M*zgg@Qc%ZARlwb1@FBBAyvxc+nj zZ1ksrbB{?GLY1mX+r;aA#jRtcijBKgbp_R5seQ{TJ?tU(5k+1e>B};FHJJ*&h{4n^ z8ZjsL9CPAN$9;Xg8DUC}1hn~3o_-M~tO$mu8QHmEt52xJrBpsTI_S?X+$h4=I+rU{ zI)K>mm6w@!(Ad}?!Zo7Cb|^#UkAH6wwghoB2!uVJo47yU3r`_)Xi^Y^Q&V#ZaE#`(_cQ*aO<3D=p{o6R471u1FXye4#FIYU=j-a}hm5QiY*7H$ z$SRG1)0aco(ihZC$JaKkA$;oasHLI@>Y8^C+J)ZY5Gv3sY{1>j(Z{wSxA*NiA3bs= z{%d{h)*5g8Asjctx=SMtx2ShW24x1wKY4x|gVUE{*wUA8rzktktEAh}sWW!P%jl~~ zPO~0K6X=rLPBJ=75PiW&VwC7FVAjWMKxR(d@me-&)M`N#GhBwdn2Sd~d7}9R=-2#w zhyzYvuwhGIp1nG{GnX7Ym3r0|&`!?QQE1ss6eS*)75C)W`{rEzehqQ#MsI{Fd&0Zr znZlEe@^W(-Z_;PplI`<4j#KEzJAl4e<(`1k7nfVFFP{7qfd(Cc#*u#%T9ChetQ|2n zPT>?DS*t*9l;MHs%e{QG+#^D@P*&83PE=obT}r9c^qnUh!zxPx zGZJml`v#I{^^=X2x?h`%ThatqL-j?a=?v6+D=3i$VMAYlxvpW1Oi>=1W5b6z3k@E1 zeexJI>Op9_yQo1XvCmr}`r_=OFf3pEu062({*TwBOz#YSOyjOm>*1YN^gidtgIC-G z^u@9L8jQa1*?+k8b6xOPlYfGPdp1w=S6DVO$(}P@QK$PiQPa|{UA$m`^vNWg6!2w> zy){bDSet(i`q$Z}E`9wJ%+%*U;r=K3VYi9szvj9ONWkP!+f=N#cCHJkJ9@LKxcf+7 zD7LYqrc~R;c3JdsOV2nkGa6qG#&tsKZq`oZTWQbAOBeeQTBqQ+r}!zf;Zz!^sV?74 zk_~0KpKb$ncZRP4uI}P*ZQTKTM9#0r)#D{d{+zsiw))8o)#z+hZs?=2`C>KovH=35 z?ymdguv^S*3~2*B{PDm&_} z%|C(M-Rb2k?3^zjY>SyCslPg@uPcaT_G+x@E@u@*TkA&H$ z@Hx3Y(hsIIA=KP(qabUNnrUd=F^-;DfvdZ#TU&QPpAaSF)9UJj1r%%FgLUq%Zn%Mz zGentU>6(JWcEp?a+fu7luu0Tu7-ULn7^r@HBzKD6_ll7741S-G^?9N_?vXXsKl?W! z?~4QZep_VN@NvNY&5GD8=N+0BOT?h1K#dFDE341ZX|3!1*dN2@cT*rfj>p*wMb|RF z7L)y#nWhNoM`Tnol$AWmXxo*jOqy>W%7mK!*}us#LT3!d$GI1Kz{Xq`kVAk@U&`{_RmJ6+`A?=s&z6`kp?T0@M%(L~a=}aXF{@I(PQG^iyu20wwTRx60 zOkh{iKjm{Lc5BnIfMtfQ38OugNrnvp%cR~3gs1;TCDxJ&(Yc`^c@p?~aDRCozi_ow z!thGcXWgZHP0x_P_n-Zn$Q0CV;CMRq*77uPKIfA}RIH0oh~8ZMw=`t-k34h9u9oVB zn)t|im4Y|>81Q537%GK-K1LkC(nOmsvDq}}wu+tN8o^W`ELbS|({(BmIEtAX}ZO#WF9BUr6PbNA_4JYj(JiV(Y z$&IO`*OE&|DcQFuG9sLok``a6gjV6@qoVbfl8!V5y3f8H{V^DxwhCQ`jd?;~uB+KB zuO|1-xARUl-u`(Pi+x`dCexk~nZD00XcSbC{xk;#x-jdex+w25)5p%@(L%@N?396} z$*2;Q-VblyKZvX}&hnRs$Q1Q;W_M+<@^$)^t91-U6Jpxgb4S*IFV{Fwu2FCP5Cn#6 ztdXW~?OYddPC`G1hs4{dN$XO7q)o(!6)81bVX$8Q2WP5g5m3{S64#=+J(iR^M?^zq>>R%0uaX!C~F zj~};{g}D|#xgUCV>fZDT3w#ZAel2MHW~*;%qOq)G#z6^^Jy>jGOhe&RvePi~OoMXG z5LLh)7_LdD5{0ckp=mbd(>JXH$xkfWS~F{6dTI@{uP1+9M_kSv`7QkI>#>EadT4^C(Zfq_)jW zSG1*mEd=EnTPooYa9l%pYqZ1oqcd+ghaw)RR zvfRA)^6hR5T;I3G1|oA^qAo3RvSpj#ak(@C{;RO5S$IRwU^8f+kf&-%3tXSj3O4$L z0N2ziY=>L#*jHRf5T-X&KCP6TDL!7i-{yI8;asEy;hLTi`ala&l6TV_Bb)5Z;X>41 z5o%({Ji8`^qcpT1;uQ}8u1WRm0mn5TVT)^G4s-0k`-b&Sk|h4lPw4LZ`TF$GPb0&~ zwkU(<=KN=UNvLM#IlX-+gFBLNc6NspY5mF5@e=+Vq2Y;v^bNOQDA$a!&AkW5H9WAv zHGpqUFkY@{&biNVnAMa^g4VwfsF0HFRq*6=sFl{m38F6;U1o;}9#7$W8Eb_Ci7(iH zSri1oiM2Xa8hy{7m?rk~2J{6t_cs`Qu?pRW4c`px7a++x_i`F%M!6c09j}Yh*ks`4 zqSrauPji{hh`X8Vs;*UtfV?4UVAIn9B~|5O_G@junPH5*^ZZ5vo#S*2?iSQHN95Tl zgYnJPnq9a49G=?{4dbVeL8*gNJoygPDltu~g|RMV=@Gd~2=6{a^yO9a(|k@;qAB%H zb#Ia0in1xa_*MD!vL)uJ-;||~C4IU9R9`sr%`U*`3sy1yt(_+X>h4n5O-@F$GC!!BHQjE7V5uXa42hi3|LdNavoNZi+@*kW)q$J+3L z=krWL-9sPF;>?qWtHYSZf$04M+;=9hwcV)dr0m3@bw|@qbqc2L+>7tS7S~`G+LdRd zf4+dv*|F<+NzeIezNaDaV-@wes3rBy`v+Crwi>MBMIX{W_NAhk)jl1O=Zb2cS~)H6 zHXF4^sgh!Wa!uxlgFHB{S%VF(0dlCa#{J7(4qNZ+iSrdM>XCX4l*HUut!l5=wAU-| zK)6P|*Jr6$BeojH>vhP?aCz<+Gnz3qos~;1`F0Y z<_XQ-u}HoDb084%C;Am>3!`=VvCwHs2y3vr_w?*BB!|L#GKWLciIVWw2jBleQi2~t z!_mBaRo4ZHbc0AwbQF9HG>1Ykq38#bL-E<4+}ivTm?xY_*tOEX>~tG2CN3K{O3$e% z@so0Ks^lh}7Ng~Y)E(WCh`g!zwIf|jXffMsE;2EtzGV7p(d7Bd)lI2!Z+tm`r}eGX zz|>tjl@e_6^u|$Rns&PB$vNEfmoN7}95Nm!AoLE$Jvwds*6s@7Y4!yNdH?ITsMBq0 zY|3cr2a_oepNjSTHj)v@3hUE;>Kp*@v6 zFx0_u&ET!&8ep$QTgOC5VNXNh3-yL%(bVZj(uf%Rncocs5gyt2+`NAxsO~fE_dgCF z{2g;^%QTp`wyMttDC4&HIhJF582DA*#X-4-{Rqw;4A)>4Q{H->?u6gXt9GUqA=Qsh zrNzv{RFGri6``IIt}j;#C56;oPw(r^V!U#cGLpBOq^q(l`6bPC!)2&}k-e%fYgrh_ z=m4HBNNWXGcm21v?f{-n+kSD5D!6YqR>L+4$Wd+V!a_fc7we87Mv>9<7}p&IK27b6Ljr;EiozJuZE=*hiXKTjyj?|sF}N&Vp4g3>3%G-jK{#h!)~q~(BUWvkCOdo7Ysr4A)sG~h44o6qz_)OzJ0+kQ{p^=5n0#0OlefhswjL z1(!qlz(x)Qa1HJe+_!*UGfAS$ue`HP=g8<1gFJ$5drRNWPWeh9`aJVzImTS z(&F?zdGm=^YPcbVcC$5}S65F7uqOIh?npZ|2_!ygO#$Y*R2l-o=}QZ2=?nR6PqFjs z*A4HFLtmUVauS{S3Z@>hpqSK^576B7Yc}_E=|s7g+u;#x$UxLv)7{7 zykVVnYRNptIo_@K`c!r3JwssrVLy+zp#N}Br=e2|pfBi;=E3QU7i{TE(aO-M5P9cu z31=6t+1m(ychBTaabA0i`ekeMn;eRR%0%^*8ab8-0#6CuAzIbQ^lMZhW=;d)y9cUp zyJhO@fPd+KcmPgc5O1x%06ElVVqS<$#*%!h9F-d%^{W?;$O1x>*c7p<1|Id8x@i-ARz&BgG?FOSS)tbY%);9xOgOna19e$x2JIY_j`g|s8E6VXc>5XqbKD_#<&_#~|pUfvdS}vr1T^npndMwyw z;~~m`Oy`!p3#~gr`qj_i>JIDH)*Zk#MJ*9h`UxaOqct+y-e(5^^ z_aJqbk@}UHnECkh3tyxu#{K(LE2>??aXNJ_iu4S>On3u3b%zUmFkTWjI)>ZNQuq)$i6BiyfXBs$)e)KZ0@%O1z^H7_Q;7 z-?;TTRCFKdAP;FO!N%R{*My~YzCl{`a5fio2k|cOsq1 zgiMNwRSL^0KeUJQlk~Gp4frj_%VrdaQ{=rpwv1>&>7erq?8s<<#y|RXTXOK5m z8!LVeerfeU_vv-> zxoG`A`vrK^tX_iQ8uwxh*vJEbxnT(g|HqjdMG+%tj8n5-^|(j(dA%O;(!{4eudo(} z^{to94y$7hmk_sohwd^%mZL38^mQCmy&BMFj`)CH${LTeZ|2c0<YKqA&G$F_}Ag?xIw5{(Q8}`ZMiz#8LI0Nz%Ks2;un! z6)&%$`ZCwkD*#Sk>|sk^o@+~^(r3{8dC@S|zll4@`|GK&U(f<#_=}CrGz*Bn^oICF zA^##}*DOFc6VCHaMcYu}PtB*^8o_KwG&!kcLnmXZPv8@q@kfn%|?ah0Yd$KtfOvq96_7$<6Qc@WG?#wO- zhmQ{{2-U>;KWO`o>kaKwu)jJ-1lOnFhmAf3Ft=Fd!(`6LD6RP1R7U9@Dg%OVaNb9& zk-m>M*oezg5Pw}dbvTKcxb#cgg5YC3$@cqv%l5eshXwddon+5K;ddi1fIh`fLJ=^1 zis;F~TR*pGi5K3i+NSJ)zErImiG`}#y#BH+1T|4-E@K)$38F8W^u3*wIPk86Cg|sh z9BTe>8-_2LS(VhoxCd=BN%bddphEGI-3eqUl25( zq=D0yIoQ&drrIy($F3rFToP#xX%DM*Uk<8O@5CKz?__+KMS|!{zluA>r?FTiJt@T` z^9N6xco`T&$(Lh4@(*=?(D^ko?D=xk^3D^1c6IBZvo;ope>u_Ap&4}Ah<~w+dIMYd}fAK@n*aYWaj$zBcWFsMYEF}|BlJl%{ zY0*!8d7RrPkLn>7lI$Ukmj}_8RPn=xw|Cn!<}*&ac^aos&{>vW#fZNpnv%_QpZpDP zF9OvUEuuJPaQgBQw)BO;by1|w;*t2YL() zF(OUK($rBneSOM7tYY=FI2;gt;hZdzrn*?WJD8+sO%cr^h@!zU|C;VCDM50J8HNJs z=q^-WtOjM8!Rd=0Z0HN%Ul^PSER$O*zbP@lP?s$HMaK11yoK#MjfdWLISj3>pCc5_FyhMqk{EX<^I1ym`EzJx^RN z&)TAoqx(@Y^kuu1pnYW30R1_(w+qC-jJ#5ioTw|?i#cAS&UgLZ>0wqFe;!GW$E{C( z#JUoyL=X6v_i!uV{LA>Q^)JA_Q{51f-{i!*=|+gTwI0`u?IMe1_tGl}$*s&sG}9*gV7gjq_|tZ@3eJe zYwJTivakOzH%^oj=Ha4*PDKdI;&)|+qM4h0r;;Ua&uK|XrH1_)7M-uYWpUceVeGDl zPbqcvKlMd5eMAGTyW$PEZZLJXIynnl`TjGKos${+0{#BWyl3}b+?o17UJ4H{Qqe0* zl4U>!$zL4A*myU3(!w-07ycfftDY(ssL+b89#Qh$<;)~B1e)4^_hqbAh<^hJOL(lQ)L*wbC!|eXt9eicYH$uV&ht zFg&*HDD|MI6Pt(l7j8r!3TE7@O1mAVm?qq#GH%yZUvInt@s-9L$-opwpF$vi8Iman z<6rpf`)_T&5A0`B{Xoz0gha_b75+nEZT#V%OX0tT8rb85!NZh$h!B0Dmu%~bQ%P+Y zZyRYI5OOfnc=~`%6-i>bVIFL9H)WotQ2s-VZ3nO&WJ( z^F0@?g4P{kjQuK@x>K;{tQ;R99BoN0W_Cxo5VAnV8~mcaE8s*d)|-ov&%`S)-IXW3 z$;R;5^c?aWf%qkezWsA8{kLlg%MZ6AB-^&4>zi}io}Bd}UqypEuCI}1J9{szR(Bpo zEWXJH{yr57A<}V}S@pgn<^s~iE1S}j9jwNemuL>w^^@{Xgcf!yk@-_fUTQG>=g$Lv z?tj0!>Iyah=gGhBsaV(~ml~aDbYURc{qb9u)-V>S53H_sd2ebxCcj{4WE zk08p^1LQr~Uef58vkV<9i<*#Tj879#-~0mSZz(w6TslbO-s?C1m0dL|pUNIfH?mUd zirtb(YOYKr-^J4aZ>C9km)k_%@#@Wgy^kd~eUY2{0$j%-_wlbir~VMy%|06XlD2PF zD>Ee-CcY0H2ul#_Uut>E>7N1jMNTKw7m>Q|XY%8iPe+;s{GEj_U7yWT>mqYA7B_4y zMg(mqirXT6MwS29@9n?ezkhw6xp!8>fNvJHZ1Nj=xTRTqvhMH79)8YUxF2|6Vp7S_ zEhe4{`CPvONFJHH6TB&`m<_4S4AiD5X&hkvjF{`BmUD^soEl>v@Xb;q-@*CjNxtAu zN!y`@ayj%aPDrZVClWg}rU=rrdD7=D4EGQsGv&_MEzt0G-@5&mdMp{gsk@u|0$gbz z_whY@2_s|H&3ndb$9YVvzLXsZ69>PmknGTB#K;&*z>Ws)3qi+0aKYtvWM}!7noj7s zS~4$&-x8`bn{K|o9u80W`jqQRRoB$D9p3-r{{8Foz>OhB0=XZHVFVl|T+V%inOqE2 z5oUL(>MN5Vw>ZKtZiF~Gkk9qtmBgpEDLt>|#!XGt}Rps>%lz%-og+?(bhZT>&MpMTXG@b~}A^$b;k8}Q8>o}oX~ zopPo|OVHWVVo6QNcVAqG?x(lH3HvFBKt9)DeR6@HVo|;{mUAK1XyZv@>ockY^|8cx z$o|uh8gQ%0KwlP{g%b?dSR+|7m+;o1HZ-r`&DTk9F*0ClMm9;D3ppU*$twl?m(N== zP6hq*|5$gngFZfASiVvb?-^6`Ng5^(N9mIT_xb;~x|3qHJY9~!qW%?*Y}#mGWiTt} zvz^|(v%A^(Sj+=bcML?uHs9`dzO?1k*W~_d9g1dkA$F;fuwH)keJss+jn)(B*KAU3 zf~z~mTU&R)9s`w&^D;Jd3obd=!x#AQdyif66XrD0^?t-r$Mw`g>h4*BA6;(GCk49L zP+Nu;;>fa3B#igSy=Z@ERPI~<9&hu5)}01ZK^U02qeQZ1E}?I_yL)<6iFl&9$n7An z`aN5Ejmnuq^T!Fou^9uA-dvf)j*Fy_`M*7fIG}$9uAGqXubU=HI#1w@uH3U_l;nz6 zPl(S*%HS}j5{#<&a!SbJ0(>8xY-N(7UJqJlk*JCL-dU)*{8$m){Z4C47;aULwouR#RD1pHebv{I>l&I6@BvR|eDk3T`oqzy zr`jAB%%auJd(}{`vA_0B1jjYGu*Egg{+d+tINhBh58ez$pm2UFe6;py?1wZRovAlf zAmsb7GaYXtzM0YM>m{0x%XW_)H!t|^qbS7Q9iy`!?pe8HyHKv-a&prL$2D`X!8O3S zE8-RB2gf98c@EJ;gV|r1>yGYHcX%n-I$JBKK9PkyCz!f0s?>vX+V^}@M#7@CiL1(* zHD|3$G7ahyWUEPZW@VxJQd*Y*YMyCzk||al@_ZzOJSYD`o`-{t2%Bt<=ms+x%DapV z@=6~e&rJqXEUm}{rys!c4SK!d=^NbzuE7vK>?7zCcP!7V5>uS)%fwZD=7eLEF{i7J2jKv;v=p?kFGp#Q~Gtt1vcB}r&J zN92ES)PMY(f8&Jz>$$1gtd9ry<}1X;4kG&BWUnLiM2&Ln#i}RMuB)T^ETccSBHipY zR>mnUYURR{9m{aOkT6n|enx4#LLTxtqqU|zqJFU(PYCLp$8u-t!TII@MU=>VYJp@_ z7gspVzFdSYb_OKXA5G8JTz;O$0{?F4l^uHhAN55}Q8#(Q-;-U`Ub53@cRlPkw)|7-xmU0E7F^+Oe|(9WhiJ~<)54%jGgoHv?4Y1ljQ?3uN$;}JBUghHqybfe#8!<7ddPGDzo^#^yL5gp8ng< z|Ignm-%^_;@SYGqqn5OY4CLP&_X%*lVs%HTnr7x=DMJPD1;yRGAfLmZjIxGzY{j>QVe61-z^Hf~M3YFlAAVL8apXG5Mv{_s6fAu;@> zY!XA$qBVx_KR^He^S)ZVuKx&}_v_;C{?_~Y4bDnLRvMC};SR#1qqX^GQq)~MEI*hb zpDV1HY4M?HVK&+@y3mE1uwVO(u2(Ywp~W40djssX`QPr)JRpqSGZ=4A406RQYxQNlmDOl_OH(m_+{W~`|rMaO3CC( zuKRnJuxvu;`j6>fHnuZ8=dHxhFQ(yjbIUUf}f5b!_lHH zmBS&wcds5B^fa==H%29NU+lYl*Zr5ul$7z4W8Qj*?C?tf3i^{3OGC2S88aDh3 zpf9yGN)sc=U)O}r)6K_c42d}O55(2JIwY7T#E0DE0d5>SXZ(U_0q=H)V;xtzzJKS^ zZmAD>`+9WYo{G+;C;ug&FF_hl!RZUlt<@KRYdC_c|KzVJw4U+)`J3X`bcs??9#bFZ zGKv0vrwKKMU`iMXt6Z?32TZ-GND0MqC68+Uq-zzX2Iyo zHdXSi{GC6ty>8fhs3kx=&JTXXL3 zV2}6UfAAhKkLGZV;b1M+4ZrKU?s#%{=0|mpev`~ZPDTyg@q&I2$J8+i4=euuoDOPK zz?{h(sy>6skFU@S8)=7kS*p%|{F45^Js@X?-Zo&qxgENEGr1TkWzR`aAg{vjh?+8v zACXpQFsQsVy+lk$m-l`OdCcP5jT?CoU8hUjqEuLKCf!$$=Sw0l`YyA2lrH;UQ+51z zuY}X#qct$!+zTDP8PX4!{$SywpUqwG8c^G&-XbRTho5)q71Pfr;_nGh?saW;QT3JS zv&jUB%?{YVXmYn#b=FT8iCHF9xYWLg)Uyz_^@8t}M2gq|%Vl{Vd@kFjhIf`n^QM%t zzm@g7C;|HFlZZgxaMdkgl=(S}-Y(X%Vadbz;sOjbm#ijhq<^&jpxC(oM@XisC=L-!t`} z6AsBLIr|C}u<_jV*_Z1Mn2)eL5SX~40&7_W- z-F(vc{jRFMgQX4`4%{X_FXPo}nWZmtN3lV=CVQv(H>L~u z-A6drZclv)rN#vQyOZ-(ka7!%uF0cJh7R8hdA}J^b%EBBW-Ax-g}qfnu@aV3x~;)V zk7_Uc3pfE)kk86^GdaNMj)@^t?Ad5S^Q9n!F~~i|6UIu8JKKTg0^wRL7knpYFCq|# zZzhdZBXyeMGy1~$ou2gBSc_&Ezm_;%RDDAk2cm1X z<|Uw`9{}MCwtCn^ijfZbV@XyAq7_l6?-4}Ge$#&i<&;#Z2tj=D)p^EF>Sisb(N;i> zawWjl&DRVcL@qww+~~fCo&EgP8JI7#8A>R?_>vA?e5rH}n-^1xJf3@uuq#)7ZJ&ER zll{wM^+VrliaQ$+UxY_>E94H~9oes}b;rM>TA*%*qTdm)G+X9#l#8f^x}!n((u?*3 z7+;hhJYSMz`oaeNduVo8ba``$=&^qAtMW}};#{%_Z%JZ;_|m4oM6znCR2Ct!n_~WJ zo5zF%6Gt%|fv{<9wA7))!axqp7dvTJT3~!hfDXPu_M7Xb29Yz$_`@uBX4_mAC^I8r zTv;BG8|Ytt?p{j;@kNk4HOQBptC&+)tEym4q;6!sQ=8-PFhJ+u78ThqQ9m*WUmE^y z0^tir)wy7FbMEF6ff4K(2oSZ*u}U4qjAG%qOs9{_`Er9)*ylZV7<; z0XAG-=&#wn5xJYt%WIF?bo6fV4Okf{cf%ag!^OvJjFw9McV9L^=}!W%egG|W^#h(< zDZE_%Hv5QopB2||VD7IK`ldHpYBTCfA8xjIkiKn?tY4LB%xL(V7(>bH&V}66HlFX! zE44N8uANMr)$Ffj@xB5ZIXdUcdYO&r30r4+M^9T<%mxat}!(BW+EPo;2 z^EjE7{7yWF?YUk#Cr>6KW%qB!<~9&tukTv%2PDzEXV`4fBAbH$LhA0j$!8#Sw>6IjUA`Gh zqrdG}pd_rID545x$v<97r9G4K$AgVm-=w4oK>8LX#*wFt-e5Mp5<&bkdWUT{Iiqtg z_9qtg@{E%%5AB5zh;QDId;-ij7ebeBPFt+JK#Bgo-F6rUHxm*3!X_8rJM(bO&x;k_ zAQJb|{Utms>y=z>F>ccA@q+kf&vkrYzB%~8`euXw z<|66O+tt3FId%l}^aYDrqszbb%~=y?dqQe%64j?k3(_@i{IOhj1h=9;oOyq581ubA ze%e!-xxbO(0asZbl$P1d2gzmYY=?m88ZE9@6Vwh9F=4N%F0_7dJ*U9^TnD?x=LK)n zd(n$?0r7d@Pd)JG|2RjtajD#?Uy`Tyc3x+5Wd!c$vljbRNG|@rHb>{hcvc~GiL>?B5!w(Q65@d1QIkmMEay*Xc#s z`^NZ)up!pr1CPawJzhhfz~4(J%htD!;r7_RTTP3n9RKGb42QW;J7(EYkoy zWly4~Dlf)G9g&Nlm*6prZdRTW+1|fr>gt#I#3I~<0W-q#Vy~vF`pgyXTscKM>6JQe zRC>m#iU35{RD>M^(KQtCGSK0hA#-%3({a|qtIkYu({?w^E><=|EFU+v*Q@_nLi)|Mj@dwbv!DyygY7p% z=IChN$NKd7X3U(=a0DBR9I{~Aa!`G25siKG(2E-~4w19*C|vplo}vb99ipqodMF`^@$oclJ6x@&ompiCtM$ z0bcKVjDn(B-!iD~T#Tmcv2UzQY0+Q3OR2JF-V-@%t6&{?A+UzHv^9~yo05bH78=!8BbHsZs`*MbS@Xk%IIwu2y1P0%mEhsX|D1+XFG>IL zL>pOi$Y&@SVa_k)SPe^#G`tgBcasGR9>D6Z1U*pP*UXmUP=V!Fv*Hc*;tpuBdf^EL7eV!+TyyD%A?lBK_by9Mjm%?-NF5I z`3|J+R9%puBPWFDX&crcsvouiTr(o(p|njhDqPt{c9DZ^xb8)-vO(WR#9Ph=%X1wD z@exydTw6D0wr$x8>fxNNlaoZgaxP4c1_H32c3t*N2d1a9AFQ5+%-1YS#)ah(tSX~~ zSx|*suCp$T-DsL2-5?e2OGG(<^z;`6T?IO!Tss_--IY>5pI2;7dqmf~G!)I36BN8r zqQCONdYW?AVhxC%CXKax@IDT88op4vC_9N6Ng4Lh@>I>mjIJ!(0XlVas~HRxsO}gt zc~bQKBI!h1L()gE?1o!0>ok3vhBs%JCd749L(&c)b=UF~8CczAKKQ!3Lk>qdt0Ur2 zUVKJ*Xl=vV_O(uZyR5%+sC{(X98`BIbDOW`yKTuh4a8&BO#abE>|Z zQo?j@K?6gxWxuyOED*GwuU06IzDhPo0QE&^Ulg0$r#m0;>b^v`Bv!F_Bhp`tp*ax&#iS6^hk_^Jw@I{HSkKHI6r*j=tu%x|aez0S+F^C3GE zP>z&Mv>|t5_($jmk51375ns|oZJdYpt6mCwi8A{D$s!?S$h)ScL31>F7zb;YrnoepBQ zxOOu=p!e)|PkCH!!TEt6UVJ)`{J;TTd)X+r zl|*})-}gmCqwQXzNfR^Me56%r^FH{u9;)5axg^bYMe*-emcZ^B7BWv*NkIan?zZMB zpsO#U@H1h3<7(&yccw%1<*!ncj-qG6DDE3J#6+tso67uyBr6RUzW>za7U7puzDvWPEI1pY_g7WDks$30*R6BBc z@do$69HRG_U)2VGyHoU+4w7Y<@1@RiL-Oeym!H7$X?f_%r+uDEidVFBvT)nY*YMRM z!!pLuhAp4NF{2pCpw@%<^7t#y6J=@Z?~k56=K0a=cz}qENcKwWy$!$m6RZogp_*MV zU(}IVUjX3?1w0#c<C0_Mwf@WCz+z8p}FJ=mNOl240%k;=2AntU-i|I5ehIcK%o))3hbYJ*V97}KNs zzQ{W%%K74fIlbd>RZ(`+V-Ph|qEYiR1Qdw}U09O%+D z9cMZI#RT<7St=Uv>hZ)cbC;c~-wV9oxXA0Q z%3(ac-^UD(gwwf7oJ_(fy`Zyh8lwNK&Fkp(52d$8@&d1PF_pXstZP)YC-Z>m8d~Vk zHIP1(e@+0QM~K5KN0gqBu_-bwHv|ulB9!{#iN_WTmmt18aqCygZT)r?*&ouJq7sb1 z`^rXjPU7&db=vXcun#JFsQ&-m5qT8MiUfo&sxJP}(T9TMvT=@h|MH$iFw45<#FGw6 z=CPPI>$)Mxwx;ytDKUU@S!IMn$*9BMjG{%tExUu5hEK!Ckm~u{3lJ4}l|4}gZG!3k zyCdR2i_;7&m;C` z7(d9wt1u&|Lhyv~pVGR~?1^<~4`g;D9)UKRsEy?!N-{$b>G6zEc!!tH+slRWg=-IYMB-)^^Lh1_R! z_VlUOc z{=1*ALH>${Co`BH#Cq4Mq%Z$eL{iKorciH?zPQggtlR5#<2f(*oTJ&)dM0{973)wOFam3@9&88^)>zT<4CjB?Q>R~6v6AV zbUNm)G8cMeU*+nTVpO0=2j&ZElO3Sq-6o=g&Fi)#ZGdZfb#i}(^3!S2t71M!)TQSaW3pM}ogn|BE%|m;tkPRb(;DrU zGv`yKXTCa77*Vmps3S$W;@im=R@ndDsnV%`e+0z8IKYcP_`V-8jmqy`{3k5A=?LSj z--3h!xBNwVoXs>(_N&>oL3LMJz2-4wpgUlX;XtAC!(UE3ZJh*PF+JRuie*ncuBG8E zxb6zisR)78-PXJtbmS_~CzdOr8Km)+CBXiYD^boi+}OG*v){^8sI1f*-ie~)nR zeChI0`|yreULpwhMMiMRM7@!;wzRhmvDus26o{@_b_fTiYnC3ou9;S6<3y2jV9;!@YqBcp3A*#ECJ{^*_e8O<-`Ires&s5v;0u0dZj zphe!(&@C_2D&h0hx6eu}cIoo9%y=gD=L!xhpi302Yh2j*S%B%9KhULXC_~K{1DQzG zvW5Tc(;6nfRclg=Q;t9w=i?hy$iMf^>y(^SADV++ykEj;|8e=J!D47&nZU~p({>_~ zr91T4L)SojGvCw!>9GvP6P{$RAp1;ICXdWbZZkQo#uM|iA}^;rd2a%qh$rece zg)|oP!OzP^RyFA)1(DZZ(+A7t1V8$jdJ^!e*WfBH@p(d}IEXLUG;ih&eTHN6w{y)L zQyG>R|4K4RIl!Zj#H_xgezrvc1LljZ>YV4(b z?}AOgdqZU0?FT19OwT3re=ryKcd87Yhoji?3?vxOe%r+W(s(2=;-@F=1O+)502gak#%8?tl<2@B^j}wnVqDt{ywJL9t*ke z`^_2G3SXe#z4;i~<3U4@5oRJIiv}}t7&Cg?zOlpqb3Fv?Ul9HMuz~db1YMjS>|6?-dV^0W$CiB(vEIHu{$=zQ(Ofe}Rh{BLTx`uq;VY~nRSx1^qyGW~s+X`R( zqo+}R=<7KPrrAw`a6Q@(U1J|u14P#xQ0_ih-wdg{w@gKcI^FT|Fw|UJsCq)!>CJ0C zdN+t)GNskM|AOl7_WXK5R6khgWb$(v?jEc<)8~Qxg+JBP^U9}^vci-XvEaIcm7}=@ zR(Dztw(cN$`oo~;m00Aj05#XAq|YY*__Ge7rTC=;b{5@a>D=cvqjaSmuRVS=?*9np zhgFex59#X=@Y|G{f5kCqFf_tMA_=a$`I+oEAa$2VxepzB8ZxJieVpNQEA?F>xo+`9 zX(0#evIW!OmMc^_si$OJ9i*p~YJV;(n&BKZ-1@Dj)uL?RA}M%T!V;p!!vqizwOVI( zfb}#jW>_Z>J#8N=0A2G;aVnu(?{xNR2phEeaciH~#8KD>R~8Ejvr2|1-On=xI6GLl zX{#rQJRVma4AL$uTTEo)=f9GDa`-)&y2{aq39M^kS}>mh(>0dRrE7>x!dRP`6>>YL zRS_D)^pD<<9!0jea(EM(bMi)kbPba~1L0(R^p{TFl0b`{SvUk2HS?jslJ?ygWeNVS za@!NIuF;#X-Up^@P@qHCK>E$cPYhUiHHN;C$@E%u6ISnW4-QAZkVFv_G5BqL|DJF* zQ#5s4(5)ev%6X1>+BohU?TgK&_$l_UMI3arVeK$AV7_4HA}j*oi>iw*bmg-9g9(1u z>bIEmH3slHUqW#N(x|_k!2cRp3qeQ|2Knas?tXGThMfRD!fCGn(N6(kN95zNn(_I# z;o&{*UiG4*rL8o$9@U z`}@xDyw1L|^968kjjtIPv1Znia+Ek-Poy?@mreElVdm&ZL40$eH9HXB>|50Z9l0!| z-@J)REPE;Cw_F4GB52-em&b#}2Wh5^s1`)%S*|(S)at&EIr_V_Q z)JL1VFxZ(@M#rAsH4=k!*{Z@c79hE7(!BD6?KeZ-drUT8Gg~4{x{dR4@r$N88OgF? z*hKXZi6+Khe{tV$rm+`~+O5l&%U|hIGp9sAn;RrodmFj7-_K6o%S@W;unXpk?t7P2 zV0=M@F1~#0OwjL=6(nshEvrn!^Xv`_DSX!199P@nVtLb$ctsH!s}+e0R?Py77RM&U(xl2w&dLHvr>H zKXmW~lFPE@puD-og?(XZj?CmaEdR$6^NnlrcU%qU?RM+?ccopv_bj7l>vv={z~I67 zB_%NBoi`bV1y8!Wa2t83#q#`u@a1=T6cD~(RJA_XT(gVHXFrqk4xfNp6)y%QehkpNE~v>yP?m+KJB+u zgXFUPd4@pzOS7JxH;;;1we8Os6`B4z$s5n-D7YU#Xs(IbsZ8;Df_{&Hcn13V|9#Gb zKW8ETeA%b+%7w>d68ZjJMY@{4kGQOOM=h=MjXXTcwhMI50os^8kiV~F7#j>n)%GKc zBf02IuUu%T!p&RxJCzXvA465g{#tz0L58#wd^M8F|Nr$MU-$q0T#B@xg4EsWzq}th zUgKwLl{!-yzFwX-A6^R{V0((=8SDxcg8sc&RZKk3H)#iu7BH({4O0H0^N0vdlUA2K1&>aP94)*a*>YmXXhN4UVx81@8xqlHbT`C159zUpvT4PpnU zo+P0E@6*;f@~H>R`mVR9+dr6}G%u!5!O^*^KDA@V-rSyhsVE7qyVzs02q1Mgpyv%; zzPX@K$4lrFq1|7NvlFQmloewt;xB0uqM4)%%TBW+qd}ff3 z3+QO%8F#z)&8<-ZKXHv1$eXh}YABe-*##U?&~Ee!%EJu@e>&<(d>8}k8hAwp8z8<} z&_(LO=dwbLavCkytEcIy(q!ioNgPO%d>MH1@WlbMTFb1U-@{uk<NKD|tx$IOfQy8#Z_6E9g*{JuqNA(v)6M3F9S!7-@c4lNX z)gd?@=6088qIMuYja6G8zeg|qao~n4a$cc2DUmuP_?}@~lvm4lTw879>;jz2Qso|~ z0Lx`5p(~e_IiArjLaAhvZF(-rta%cbcurDjeT#J)nr(hf2;xhvBbUF(3Ca3|cHH%a zJbE5AigD7J&bvL!SVjElU*hQ65M48+m;#J19?-#;|NCwQ>)%-k_^W=B(O%QkE=dE0 zC+9Se7s*!Qv|gIs-_Mwn#0nYD&?>;zlxrS##(Dbb#PKUf=3TE=mc*sl*LNn-V7_SF zgf0W&iv#?V2Ro+?$!p3m8)lg6yAWYNm08xSqMF=&Z^Rj$)xaK(M~>G3>04G5Y+Y;C zr!Gp$v8<}i6?&cT$~uk%`kI~SD~7dWZ1)u)`vG(z&cN~--3Oc3K$o!@r3U(0{<|SG}9kJ<~g4WtdIrKgHNV*txTFiVYP~Gh} zp;B+0_8%dbZHA7%Gx+_@-F#)}WAt!ghUUvi2K9MqNZrl5wgaiVt$AbUn%{iJ!(|Yj zf6JnYof^paEJB|BnEPK$YRny{X&z(t-P zI9gG0{1RIP(r?yG337?*%-u*O$xUwi&RlVL&Hd1g2{^pi9^M%n(6q zs(m8La5WpA@{KrT4sRjZ=Dl_FG6>37(Al|_G7oH8^W#+ zbnfqGD4Zn@tm_vtKRqW>RCn$k_-*_dK8tb|H&JGK^TZNSPyg|M`)B&fX@K_6DBvGM zhi``Tn}>O+Fiv^U&3DD=0wp?bYKX-o-rj_i{T$!8u(*Hk(Z3+%^6;3MN4aR2F^vH| z;^%c7{`}qU+Iyzzliga6m7U zyP`c+CiW{jtznt-Q1b&NC7&f_!*tWt(3RUNOD){Ur zVfXV)FzK0{8g72iW)<-j#d9m@u;*%CxXHHsw(jOV->rTvA_wM+UV)eg5dT5}e*_(T zf#kAJsB~Nle?{TdOK%ZiUlU`->3QLIBz&rUT~@Pod*HcB+Fwu zS6TZ;G(3+R5%1g9^2`PfgfBua!9e&T=u-FKb6GqN%OT6!;ClN8&6g`JY$hX);lI@u zZ0Ei;hukoO{L52B1O;qXyyv{@r|Yi#dW5K3ZA1(2mm0^mk1MAC&KLCZf$z3dyY>L{ zFX+(aUp~^)KEDiF7L;)M>q#?*b`Vw(D*PMAQzc@2z|jZ97tW-q=Wm@ih!K>};bi;P z#JK+{`)j3C^D~weObvYBxZnlzh2A-&0T^GVpo=e+kNyOk!~PxH6=)|A5OLmH9BwwW zioG55lq-O_@B4WbmfNYdgtus8#o6!2tdyhL?GeDCxs-hK^h_E-Y@$vD^CjLkJPa6L zLZFK;gjUo;^v03?wp>*HIeIFV>Zh0fI7FNIqJ+$@(ICD&)0CAWIYJ`N&)A-(Z9gs8 zPoEkQ$*UuKYHZAAR7qFh4d#mmF@85Nz8pXoU+f0;9fM8=2@B;5m$XKOBVWjL+BaCA z+en!qei#JtMNv+kzSoOobK8`QG0Ia2iDFGuG)*pc;O?_#$)hErNh&a3kYLV2f$^o| z!Sf~AbBqRQ=K1^3+}1*uTA8H~Pf8<10oT2Q))bTbeGIuG^rlBLCvv!*Z~OaB49>n5 zyWniH48C9d=nYHvb1UTz%$M0^nNwhVfq^c*gfqZc^oOZk&Lf%AkZ2T1l%KTwsCVjS zUX$_kScCYo`MsAt^#crPMA9f?xwMedZTi+ShN17RX-16X5+nQs7R0}3$_WDF%L;Vx z1v1Yx!;zBaBHOxyDdDETuPKcs`gk<)B6&kcEuQbK5Qs0!MzqfG&N6CQofp1T;ntDw zr24*x&vFsyy0Oo^|Ka($70j1tdVfIoWt;WlAN)L1C*A={xTP|D@zk2pbhA3b0l`A4 z?eWr>!=G<*m7sYhcFM2l0Y>tj{^K9JsVF8T>1;kEVq9l)2+L|SbKvqu!nXZ)caod) zvaT{tbj=2I=o&~aJK$Al7DmZ{P&647E|8clcmv~qfmh5)7{tCKJ_XV>@7QhndN^)+ z+njFFEyuULv<+h92;OqlU>LpadG-6oQ4FGMicUp<=o(d*PUy&GA$N_t_oH+?^~$_k zVENbma(^+oA5i^Xp5`124MIr1e{W7lzo3Cf2yN)Bsn_Q?;h0FsU8S+}?JX&H6ilOG zT78x#IG1%HaeM|Omu27*hpsv8X(RFh*;u}i3AG6Bw?n^KZUZK1;*KLLSV~Sj?(b)8 zu0M4%Qu!X2@HJ8^>HQz|!nzmlrphNywX3oEGTu{JxP$q^bAkH_7++)`JYVD{?gHu& zxpR8LS*>E^u~Ku86b)(6dwxE{tF|Ks@gLI+@%=X0%d6V~ z;tOfA#ZR@ZAuDC!7=y}AYSu!dpX)vSrirypt<*kEI}kKiLPYas+R9mY;jK;R01D~dhNyE< z@qBZm+yx2M*CpCElfC}`?rB&2^B@2+SCTZ}{NU%A7|#t))45sQzaRZeK`vh5c`}aX znF|}nHG6U<&;qKvL#pfRk@Q;hRHI_e&*OoLWfIMAPnls!#CK^-YhpHhNFa6hGA#~R z-CaG{x`WI!xtkvti##f1eqCQiRUBne)t1(FgybQnGEfx!@xJaXCM8;6#rO!w2tF+= zDhW!lQncUYG4j5@F(BZ2d=y79yxy?b+V+n4M_F)Ujqqpo**j%4>e5aYboI z+RSKOtM?_6@aLkX(~3C4Pad}6zuP|SR}t_B=QY&Hzf*zaHKehhAM8Ck$ehDrj;o?! z6HN38_1o@PZTS{X{v>*Q!!C8ss)RFSke>E5x(cgjBw89KHU2{1kvwgq<)Iy#aoOZ% zp)g44i8sjy*3(Wn9>GBLv=*1ZgPn7L)Sa?Kc?y~2wc@|?QSpz|W^uW#;W5EUe!5WZzb0S1u%4Tgi%c*$r;_1bB9OtE(U<>j`By5Jg~YOf3S52 z@p0FWDr{l&vICwwF|3C-+AE;b7*j?HEzrtkd*>s9>W*=S;kaPnDxG-5e7GOwJ#Of9 z&v~{2zPS3UkFo~&<8<7R`5KyrS3v5Hfr}41d>rJS11`4L_nXn2p{@7NSKL2Uei6(y z%XF$3tkqG#Re25aal(WIs_4$txgUsqMEahSCx+Ap$pk%r^rbGmWCZ4NiBKDo6JGZ| z2IAv<5&WRm!~z2F$|y)2~VPJohznb;A2}_U&Xry2b!MO(RI)^FlXT zaqLu6zRg&gy$(!%_$j@M7CK!wviE1OuBmk$Vg#aVlID9Jte%F_ptWHF(>LnG68N49rVmtkg! zgUt=r)6WLJY5>vG6!5>GBPWE+*DPmYu;c$Jf@PKDF|)g{@TDSC34CLKBy*z)yK(}` z3B~0^bpK$uBU;tHdoJu^?;zawrTx(4Fg!^;iDFmm=F|%^Uo*!#2_z>Jbg6pq{ZEc; zj(7ti^j3itf9Ab8IC6W9_G8ucVq;j$>| zb%)&v2I-pM;Nj~}yDPsdT)JAiJ}K7iM%x}QGisJAQGL_SLgw((hv?}67=ZoH3+U>9 zt`cW6O}f)n6r(%!wab4c9RQi$z`tR3kC2U7ggT?8IKRfLzt(qaAeEaWD zOy@xj4y1^!54^c_Q&jdiu%p;LkC}roSv;(L6URj zt;(xXOanF4*8!m4YxK-zE<0sJwTTP$-(F&HZ4Fdl(}l*P!Oj)_GCj>?ZXJ<5Z&2M;N?^!(m6o@LeeiGg9CQc^<}0%zVA59U-%K#@dNVBD z0I9pg`57Q}XCHeDUAo3h6*lwq^H8kA$z!#a<+@FzkIQi1wO=~O3$vf!>k>ZfCNxE! z;Q|Voy_E943w+x_aRpd}80_`&Ev4yJ0V!gzt_e-ElL4k{Y9G9=X+-^TKKhY!cY&>P zKXB)hsOqRvmhWtD*{>POR6{;mxJyM0U@9H+g*t z>2ye5<4K$eOxFZMm#*>4TP=Nu8P)=?)?c&B7;zIstS2XNrBhfTagnbG(lsxqPnkde zO!N?p#hupJXB>T!u!J+buJP^GB+`;0qdNx?tZS-tQX7Hkni%NPHIKIQ%H<1dYBQ+T z9}j&WR zD}unfW`28i4w$Z?g)Uu_aK2ac#l`HHVBea5cdBP_k?$PNf3tNY`nsw9e(!O_ut~8w zohkd7>iOUc4n9#y+B(?sd|4WcFtm;r)L-Z0A$<8Ez6*peq_I~IR@XrK0rET~3TGl= z$$v3;zEj7D)~*lr{o;UYkiAZaIlK3}VtmgJ&dm%|y}J#W%1B-|?NM{VRfp4kx;#64 zN;TkUx&+oWb|$gnKy(cQ*CXia2XwkKew;B}3VdJS9rr0}ZaWfFtG_I2S<~s#w50&V z7b7fT7*A)3pWDLP9$XQL^`;-Ze&3p8RO!E#Z5VG^Sc`=4C7c)!7+?IMi!TN{5%eS} zZ976{-_>#KuXMJ35iAi@6B={(`%~`w0VpS(qYhLUvK(S^d{bmLb&RDaUSv5Pu{$ti zPu`EFu@Hm#QW4M24U8`-(7_kT`!wO`zGAx1Z(?!COVu84tzgpk{Ape`N7=0F6w@IC z@#Qp#U+61A&@&@kTK-=~4h z4U_pjUO+#!{i5dGvN&m`Ih5%4M~>_HD&A|)8Pjpl??txqj=+nL4nLZVY-=d(x%9s^ zkw)IhSmO&P|lJ7{Ulh<(G9LJ$M^k4PRBLp~V#5W+mB8Pcr zoapnowprj-GrrQ0q<*uv=ogqTLjBok!1!VTU3}p?i2thmm^VxthF{p^8L!LTc?U~Y zlu}k)f^4G#h%ZkYOUAziy33Z3x$)qC7nQwp`si8A4T ze0N8=Y^m>u?sLzV-*HlO!8(RYC+d_N_)lMwYiH^5?NT*fGTFW#@7ov?ng;WQ=eMCV zFupiJ2VWrj0bji>noG-RhJCB0#tN_gqG=Q8zs9Ub31!vxBXk4tC1EzSxH}jlEZ9`Q z52kB~=|-kB&81tJH+iaa#xAY{O9jlA+DCYzK=_h0pYh=5napoDrHM}%N=w%yoT}?> z(@@+g7mS>0(%bcV9V0>g_jblb)qnn&7hbDAcggc32TkhVzWETQ3wg(Oa$@-&UpIi~ znKT09g@Mg8%|pjL6U4u$DvYkaIvF#oPrat=@-KN*m@;qtF3?W}Gm>3m0OVhg376c= zl~P6BzT!8@43%ISor(qiSQojtcUlO6qzqAVrwlxdBY~@4# zR90)=cWpDIpl>Pe*=4+;!D*k})R} z+vb{r`BI~=E&+ruTl2Zl1CLGXQV#pQq zmdM|=w%9z^n~c3Wvz{LROEW}eAZpH&1^E{zxk{CW0BeJm*K~o8FTJGUOZ8dYO|vJT zqEaOydP!~0Li~%jHZu_aVjr6Z9sPaCdq6bv+pwg`kCu^%DRKJL$2q^!Khh^hYjV>5eGwCMY~z5K_g`QePK z;FS^CR;1v(w$C6P$<8WL)yTQFvZt%LuVVXBeabD=MAipaiN`0OkzFds;SX3xA_N5k zylZ?3T{=?ka^Hchf=xUqNs`t!(|+@j%4<$-)vsjgUzzCGK|0cBo|LqUx6t%`bHs>8 zHA84?dm&qKvcba6It5h^!G@Fz%YS!`ZN-rUfayp_=+Kdn_vU)=N5vC$YBtzV48qM~ zk7>)OYw`v)P>aiI7{0lnEz5!VOa%`M82nH-Ds8?JYB$sq)`l$-nJnKX z=^Dc&$^W~Xuloe+6c}HMpo1@vy(`S03u`2F8^(x7@vrSCLRIHNK}XvI${1RLA)zq|QJ#~%Q_(=O<8{owbm z)>KzJvn}2z2mg9v9^>=p@a|0g3uTb)jKNG#`+a}k6yCUfuY22Xg?eAM-DQuF9QJ3- z{EbGch_3rqc7BD!gz$fNjpyW}c!BvBY3TAVp|Hjyx*x-pR2qvaPd9_ObF#qxn zy8MfhbxKNl`JP*olrtg>kV?Xw*YPrxDl+T$@6CA)pi<4hz2MVOG(zQ`W=fFu z5@xo{z^_`7eKwrjECE*z=1XjC!9Fm)j6w%rAorOL(6fJNBh#M>OUPf@;7cVbfX=`-XU>v9eQ4Q31F5Ge0G!SI6;R zo9S8zxaVKxI9qac(SrE0^oEs}iQ-%)ubY^ySPfU4&;DR)(dAB>n8TWL-*LXM3(S|e zm+Yp%_%aV2e1YU+vRb$wT^SU2*gIh@1)Du*bvGQ?-4rZ~>cyR}B0zkRT%m>`{^;8m zLQm+cNv~tHa=A=0IhN!HhX{+1gc{B20_KY;?OQD%e4&8vh7SJ%>9fMbEV89bS{6!b zt2umLFi-gU0wrN3O*EcPfvW5Ny=+$$`p^eYuA>Ym=cf$`^qW*Q0|gEU&YL#d@He}=k#p@H%-aSa%H z^tQJrZkJtff*tY9Vp@oH^yd*LJUc^;-|4C}Sit$1lc6g%uzbuPy7IA~g-;MI73B6# zX@n0xRk|&IwzT2nttYLX5123y0p(-$*wr2{)QB|SXrghh7jZq|HWtyfjeEvf=lWMz zo@E*?0GyBU#MOQWmXAGpu=yC|ZvI>?^#Yp`MtiI%0^6&PIx2#x9egJizIt1xl4z`eGdW^v={;0;ui+e3~Lt++q*4AD`F!3Bash%;k>H zo!BSkk5H8)b_}2ig4CTC?i8@PgMF}d2kHCyfAZ^oSzUtoLt5_2GL%$(pRFNlYmEeD zSYI#}4pes)?tD|&9_+7+$L@M&4N6jF{hWOI2Sd31d^u*lzxLdGgy@=PVPzn7H=wry z9l8eMn>CYkin(2I3cg-F>S!ut!?+W>)o5)he?DPB*V5l+!k5^c))m<0pYHTqH=Xh3w01ANni_05pE#qELVwjl+Fa)x6~yk}!u z*>=&WD7BR#g*W-H{=dCm1=se!3v0QExsvDUuRk7H`Yk+1uD7O*@Lh5p`TPQT`vUBn zYZ%Nbf%xX6`9BYSZn3y|UdT!HpAJ?^c|o_u@Gqs>+T$|#R+a&54zYGn-K}5J^e}EC zslogu530)4t%+?a6sOAS{EUT`?8BbDM}`8fI~{-bUqI@P0=@-0^faU&VBP6uod83D z;fB~>^JwbO?vdQBB4+C?!&7%5)e(@MR)LZBp!a8lO|F>zLs^OkXUT6BLC2;X6iT`# z&uQv&WB}IFh_@|zK=ibr%khKl2SE1yV(2& z_k+~ko#Kb)zqnth3*%L>t`dZGt*Saqv9U)mySRS)^@8dSme-l32c0a`@b$`QX5wsX z9lhYqZrT^p-$Yu2eCQh;PT;yjC{>FCR(FRFw(cN%K!hSZ?n}aB;h+AYM%7xouAILb92jI69*qmCJ6xt1pQdJs5qU-CB*h}%1_t+B%+5ne*-}}t*k2Zn`K92xoBuVt z2BhxtDD|N$C%n8(t)6pX>FgbedVJA&Ize;EX#o9gdd4a5*C&2Xd4s_{hDre!|>zyc`qp5GJXv8XFd5xKI`M*7uHP2Ak@81b=t1uz@hdn_% zmwc)hD)O@x6@lY`7WB5m#ov;|!9Q}fED&9@$=?V>*Eql@JXk#qnbYpG$e@^$|7dVG zJtpWt@~VUV+mt?=@SN~`3&F4%s1L;ft2C)@L!MX1yJJU|b;l?BL{%=q&yWMn;yuE) z`acg2u%51#MZ5u`r?=+&psNq{=D?4^J+ZNA(oOet1Z+kCk4M-pA-Do{9zBoY#Um*AYN|gdT zr(M_L{1_8C65AFz_s6b8Vs@mNEI4=Q@9+P4$bCX+rn^p%+i~$COzLsx31SFA3rTzD zi@Ml(6rqAy4CV`{6+HxmFQl>c4}Q;7vFa*va%N>!ws)(+f#jz1XFU>Ma+=!O=2BW5 zNFO4iX4W>xz4;aScb?`AKh1YV*BAW06S0+}`qfq+_#PR*-T>#*E`i9p!18GY=*Xub z_hm&tAB^Ffz2)Jcz8Kj3u$51mBP~a=N+$nvq@A)5loRrv#_l{?E*Td|+0Wkl=!uo7 z8e~;p)KPrMpfk_l)bgnioD*`EO(6rx34N=$p=%GQY@D!@ZqYUDaKyT&8f8BE&*<2u z@~5trJcS-_FA!hQwL_^VGT>g?dd82E1nxSr=k(K?RdT*z)LtwrXSdiAf#fxm?uNkl zG724hf%xX*;)R(aO}UVM71dAJb+h%ayUt6n}P z)Kj7Ts3m?=OR}F`YyK4JfE}NlKI;X6I1|hzr2pB3K?Z~`T3m7u{=O`nIecy5P;!Ps z!~Wn!59@ZAW-s;*+Ouqmy@FIrkgoZB>xqazsD|fp#x`}tQiAg?#4Q`aP1BJubz#d% zm1M6Q;+vg_wt?sx``A|K^35m-hSNW*jG~d~o(E1*WH7%GFj@*vuFrKd4&)FA`DV$? zlJ$(n2!TB2cE5M0gV@u#CKmqO-igm~0|@ns1*_yW1} z`ByMkeKF?+Eo^15vw+-kmW;=W@?=?aRfEE?uPBHwc;_)8Dt;})%O|;2wxbu%%H=+K;|5DwL;5a9yz_2+daU%lL=))Bej$! z#_f??+(0VM0r{7k{Q=norjMlhQ;i$f@f_)e=}p5g-u`(bqayR}QURtd6*A{QKo70$c@?80%KWnfDllK-6YDc#-OC7nulgLHSN(%o2yG)Q+z zcL+!$AYIZ(h|;`^%eAg|-2Z!A&v=e8p6dha(?0MVp02eskK?y(bIyJ4f;(v>k$$;p zBdVAwRYEd1B9lbDiUJt=Am2Pwu<>96F(8rhkW-{MofjS9^>b}QadW*a1VsjOF$R1r zux~CwIxqs}o4-ScZ-&(UP9Lr8Uks{Cpzr5>31uT}z##fP(lC%)69#N|+%-O{XC2wfJ+Cu8hooNz4d~-UPKXl|}A@eihc}cRr zb9C_h&Kb}*5oq^Ullqa8t{BY+UxO)pcc1Cj@itLh|Ic2cwn2uk`V$wNiS{&t4Fsd2 zb*MQKB}J>7VE;k}N1g=Czhpy~f8no~9A_6bmDYcvb&yH0QDm}1rK7G*;&M@8@!;2ydXvzdR>W3HBYOGH7#PQ?q#_RK}f{i9h?j4>ioo zetZe!U(^L!C{BFyV?7(~=~%C6muE6(!&X8|k0<{G`_Aat)Ad9Ai{i6kVE$zQy8KJV z(D3RlW~v~aePsJ5CY)5}TzNLTO;U8s+Q}svkbmJ?sV%7wvFvbf)mllT*51r67>wnP z+B-krejf^1>mrO5{;jCVGeNDGAPUlv@G5zy4@RS$u+GkY4JSEH zk8&@ID!^zboa1b*20nA=bOr0kf@oD9U^;RUx^yIg5@*Z{)p)a3a+E)Ai&jc*qlp7V z5}4bmKe0pcK{|3(Y;CpoBbG7Go=8v*eNJYpg^}xKh`OcfxZ{8xR*WD!SVvB!j)Vf! zk?zo?BlnSeG?G?iy0YhOcbJw_b)|mon;abtY0XP~YLW%%NEfE2E|_0Pb1o=KA`Ggo zob+uvFamJMC$M&}*OMykdHTRQlHsvV5HKBC1RXjOat=st1zw{gQ7r9AhqV-2$Eien z|5lZ@{S7x=yJtBcNJn1AFv<@imv8hvW3@{>uAH0?;RvqySce`vY7b32sRT$y{S0;JDAln1xi^z?+05HBx@kivQ>_!>N=25%3AgK18 zO6*aX!pY3#%UrtAJb{bLlD%NJe1y3l>Iu>dcYQ;5eH(Wx<$tUcp2g?M>}9mH*&3k# z{$+~Cw6?P1NC-b7tXisDpZ0O)=sUCo$lu5AK8h&3-i_<2+ELSqx>Wnf`#5mrp(~qi zfxg~To~q3mkG0y~nOl3z|Np-S`FH>C6=o7X4$?nn@NufOJrIEjcgwE22q``7^O7Os z;B@>$4Gg_i1<>bWO|#GKwVJSS|bXb3#Vp#2~b z7+)xp)&HE-;8M4spVQ#!jWok;vpFFd{K!<^r=^is$~e_rrZGj;ihJj>@PFDxi zQaJLg%lMW0AB()?a3a$vb?Pt6E<>cGpwG2Q%OCjK?80q|+9HEZYi6c-BG)i&?0s;m zGTG~Lc4g;@XaDBpOy2IS1L4aqS(FlDTmiYj$A@knFq%W3;da<*5L6dhz|V^%s6u zhrFAxE6y9`u})W~RQ{G{s4&jBuS*O&tOW1QH^Y*hlnq+9iHNA;@!I}|y-AkRNBFTv zFHbttgk7%DnvL%MZ$1+Y`a?5d{)Me}Y8-c>?NbT87u=Pg8z41Tn z-+%wPhwif`Nd1Lq;+ZlcwXt5q6yuLB&{*CCI*%U#8WG7lO^a-I=bKr}OgldvTDh6M z4r08yG}Fvf7{fT|l; zBYneeg(p2YT5g;*2E+VZzWR|Hd!8AHHGkS!IIa5=l{J+o&v%b)RIyDh`O}3 zc^qB1Gd@!^(YSh<0zpesy}l+mo8f%O_Xe*B;Pvjk_h0dl47wklEaEe~^xgBFu8t8= z@Vze+MI7Ne;_C-`)1_AKt>3wj{YO}JDpB|-!8`fJ*0W__Z72F^blwA_xk$OVh1aJl z1cqOgO%f^Ev?k&H{eJ#C-XMSf|5z>k8Z#j0OmPg(^bRGK#d7kTy!vIRJYQ!nXToCa z_aADIJ|P2ruDZ`p4kagd$Amr13rJ-{1LiucqP2N{e(zUtPtW96RQ;szZ~k8tZdW@H zUDKrrN`Tz4ia$Y=C;D+SKG|~t)Rme%CzP;xN zdt6I#J;#ktpdnP<&^Z=%T?P98tRbzIx-PRUFgg!^FLM-^bW}l~r%^@?h;Q04V_cqK zk|6yz|4&QfJui^@3n_vvT^WeW`yf8UgE-9XD6cw@BlDBYl|*yl6z;bU!=I+s=s@inUcw2O!h3ky7CV$gBxUD{@3{O$L*b1j?AP4{_04! zn*2qL$54Lw8=L8mvV|Xe`ap5~#Bj#^-P)f{tF~9{0?FXuWL<} zh{1e;bD?bl#+PT%!52vX*rvDOtevJ8TL+Sk-He{s&3Ouag1@LvP7xeG2_5KpAfIYB zrS6!?#&BUK9>LwmFEGY``0j|?6aUs@My~*MR35^Y#|NT7_@c_5KTKgW5*Zv#eyY08 zZbphZQ~|fl?T(<^e%g+43gIev)da71y86H36QmDSrYU?>nC_l$t_ac9zjDo5pVKwp zPphvjeudwAS#P}rc`i?$m)y|yEXq>~*Hced4JZ0FY5jE+kv?vwf@RJ?3#S&oXzhBw zJWzi6f4u+x<9z~oAM>qrSV8J99U?9DJQ%1011y1u#`HQD#-|3Y76Gt_=g;zf+}$6$ zTlNx^3G)`levIYugNg-`r9|Brk}0}GgMHYBzKaXA&l z=ME5uC05)lA4v^1XpCj3xh zGP&=s02>{`b@Z7YvM>K@e6dSZsvI>bBnW|nk!z?sTR;#hsTFZ)L^yCneaHm*egCYE zzrFa0vf|=KGB2jGW}z9y^Wvr97Z(HCA9UnXTU4cBzC@JbM*`yuDs=D#Qr~yiBD_HV z&7Fp7ZpJ1_#c(vg$=|$ss`ooL=yogw^gIkjlE*XA)5mDD(G5DH;BgmC?j-GJ@G|c= zxC^Zje9?9V^F^KgaW)XX*dQ3umGL$_Y-(M@Tx^ipp`}67jBAm&6m*2clvfIda233g zfmeus{=a;)-N>s~l?+{E_`i(F`J@a#j>YQ}L!QU~HQ)T?fuBGh{RJo4wFvLkafz8O;A*YZFeRQF2`RzhaQjtC1_< zHX+b#&(gYJcbCT;UkCX?Hzd`8Q{x#}2uJ>dEB_;3_G3d_a4RKm9z{DLHWq7rH!ORm z`oG2($hoWjYhw1!&v7_1aR+m9YHK-6Y1q{(zMX?X>`Zt0?WxwNK+|uRXz+`2;^O;S zu_*Y>@R>PpPbYCwVX_nm9Mn+2e4&utk^#aOq9{FLyD@IvZ)|fkL@x*HRSI}a<@H8B z%dQB}#1*1LzBhQitLxtBz5mfSvo{<{hip-oNT&S4M-Bgi6LUUJ4cY(yHQyX$(NnBT z_;knmxO3X&!OH=Ww?EP`elE4=VSD-rfIhFIDg7S_w%>;2q1caw56mO^Sa{`!dEOeI zhuuVnf5NrZ2kRQEQ}{R_x`vj$6*_z~Brl7fzq!KfNjekDCRDcyqw3%&Gye2t!fSPH zAx;#quFt#aH4)=+RE~=v}@J zxioz@ddg~h)P|U3KjOgCUy&|}7khA_8HpjKk?;#CAJ{iP+$cZ-=9_7u%Qrj!YEq~y zt@kgGz8P#j4}B`GtSqVG&Y@PO$*+It35&+e_s)w#U*&=Dyh3EjXO z^!{vAJjlq4zFcPM5SC8LW#emKh^N`q$KRE-F}@pSl5uDMA`z;HP%t$(B+$* z|MqGLg>?F(Dtn*@-c;0vDo#6+Uh~g-tjw=bf_yWY647hy=aiPUG06CTiG4>hwWzA7 z0}g*6O1zJwO7LIq1pDSl=WKUizWFJ1_-066)~(u~ek320-5c{4=2*lF_nJ^XQJdl> zY%y$l)LIZ|d zF21y~Ro3muTj9@6NoRHR#>;{N=-+WiIYbQ2F0b<2YGcmsw8dh^s*kUU)Rb>AbbgvX9B_( zTJ{>~@Gp?MpH;zW`LJr`@AyrEPX`8+16H1J)Rss5D>#AyIAI|FqF+Ju(juH>$3kD( z5l(fJXCum;fn)0|Uq#aIDuFp4K@#j=*q!RPfcO{GlHq$__tWTdt~dRpt6}Rp^2q7z z`*V@ete6X$;m0G%j{jBn!}Jdpllu8Oj(ehN%HYM3wpgr7VE(~Abu^(%=vovd>?xQp zu_#Rd>wW~##g~m4I-jA{qcX9FC1JmQp(1k&u#)U5l)UhbQ2!kT;)}$`K}k!P2Mtwr z$%dWup$5o5^;9H&#AeD5N9iSqS?NxL`J&y)s0@rR&d|XZ$h^geV>Zs8xhA_t)3exx z6RX9puVa6oyX*E_4^;gg0r7>@NXC-gyj?8Y-)*%P`_lHO&RKC<(e`ULaYxI=Q|F8K zV7?%peO>{=7d{8(+NpNTx$Vj3xID#u9(Q?_!Bg37T89S)alBvXA@xr18UYW`6l)=dbzuL(?Qup9%)hunmw(|@2_9UO)la!>v8GCN_>}X3 zC41WOW^Hlb2sEEP<6_FkMXDb~Ke*^0DtjvbB8yvxtOi42Q7wYrgg z*~Z#Qq$XY1PUN9Z#p^Rct^`ku?vnn4FRedAaO^E@Ep2y4fl$LmUR^s zE&{SfMl>JqVsA`^0Bs24()C>OuN%clD1#nh{KErDkgjptCYyzs|IBbj?5APU$5>U2Ib}KOap3RA zo;St(ZoG9HtZNv8>H>l38dvDjH917ame9ifn_Ud1kto-1?BbY!&(=V0l><=+HHgye#)8 zkE<81hD7;M5`_^+PM>I;SL2g!qF0`d8Sua|yME|;16&c2%2qr0`ng zzg&OMOLq_@_rOZ)$52~;>??J7Eo{fa3gSyS>%uJuKa)?xm8z~$sq|Z$>t{&BPUqr7 zayCosl6y#xz zMcxu6Ky6>#Zs>!1&??9ejbDGxbax zKXsWh@$y@4JWcSRd_*&ENIb9pQSd|)k4yl>7mtzVV{gH*)W+oBB;(%GJ5uHi4n|dH zzc-bfjtahJo?C&Ze~cBy)`P!LRRu)TyCKE z2&Av;Zk-1I{6C%p>cwQjI;j6LUCqfUXE{S{@2iI93Hp0?eiiZm^K(FuekPVw)QDq& z=dhuH#uANx-Azk=4<<=mGxg-2;F~3YKEGqjrKLu)kqdu(eX`DJHG*Jcgsoqqo7CPM z?%8ZHU(p!k9FS$k9*}x~SIH%G^)soV&8_}LN9OOU*n7>OM^&@m5QCwnRz}a?X_R4Jk0+jCyZl`mS zP(plj8zGRPr?e~G0@gLkwuQfe=o%XY{d=3Qft+tH<$d^GBsKbFai(~wd^Lw!bSH~K zU6m8DfyUu$MNr&%3kRWogL_ANPU3uA>Hs@55P$=RkSP5GiX<2pAm4 zuCI*PpjJzsE^Jp->b~PyjO(tz%dZ&itOWeN1CZ##{jFlK#P$m64 z2SkBt?*c^E5Jg$u`#x=JvcQp{w$L=4+r`pC`^=n;v|Bosa4MD65Z}uuARWN+6^#O~ z=H&cyh4gJlN;lu6!lde3<&h*us&7f_TYF-*^#9H`J48*a0qb-42pxS65MATp$BGb& zmAdsfGlqF~NHW1fRCF5kO8Ucil15$*NY}Jw{rdL7ZG1JZo}T_}Az0~6b$m!_nL(O8U`F z;DV7YHq#&djk$p#b!j5-`DWe7MPRx{@80X0nA)sd^+(BvnX)2%o1D9>(~ir2Yw38x zzuxw_8-U`jHQC{|?DD7iYXs^674PDb&yzDE&ryse`w@*_EimV>)`R0NH>K(jNZf5q z6F`@)8MvZYnXJEx2wY}A>^ptSykk6xxx^cI`D$EN2DFc5tm_yhdIOO{`w`H6{1{IjDse}$)1L5SUr`;s(=`sf-#T$ke)v5~3X7*Uw$48OgL+YfP#SotHVPv}2;< z+MD#&PgA?6aZhP};1GTodUuzvu}!fy;U9PrHW9 zN^Q;JBIGW-p;}bv;Y`X=69?s)Im`IG-aoRHB^!S^nN;TJ5#bzpa)~YHfErof;Fw7nt8V8CG&}md)XuCqw1F=%kYp)VZ~9-3<%deY}`U zJ=-CCxopS)#uw>(&lmXaI;@Z!eDSn66%$zh9l{*j8@hWCJ!Y*O!-7s|^XLa;0#$G+@DbS+koDBf#>qyU>-FMWUfeob=B4 z^WI`>t2Z*mKl1M~Z(7P) zL+w_Qg!mT=lTBcJNrn!-K>D-`#~9qKUuE-z=NgbusD~miANuc5b^A2Az7py50`Uc< zae?JSGyM8XwW56|={2r^3n|;>C{B;_805TMr?#FcFki|FwEBSXC1Kk9-sWW?eROWi zb+JORPq*Nuq^lWVMX^C0=Ybc${;WGcM zuy(1JIjYzp_&U;q^Rk-OUbR5-vZM%9(A7s5Wy|5v(UbR`94jId1-9FFuZs6nh^G zjysqFo<$&Wr{LfPUFYg4dJ8MPJn8-p|Be_$)k~6o%=E41io=e0g8nAzPS>>2NyG8i z9T+&g2wv!S_1r>dP!&EQ>KnqId(GgUhvK~g)-{f^-|K+snpNo1H4mL(SR;w#508r< zN$?d69Zpk;kj;j`I*%02uV;dM^8=OUjesxs$WbQWHT=4+L;{!dk&9QC*UV(os#Vrt)0!1 zm6yF*cUymek(k@B$HTy1GLHCydVMiULQiAfzT5TgeDjT4-MqF&lfD8B<$3lhK~L;v zv9btZbA#5G3G$G$`fXNlUN-uSVF_4XR_os9Wy?`?%qNoiR==iRvXx@Jo?}f;|HfkC z+lfBg(tXDlo1IXqCZ0iWL@HIvKYne8q@JOXhD6T|IIB!lnv~r}K0)|`u1N-rFX7O| zmr{CV7ZRtR(z`!4zxJv5=DZN!-7&wRBjm6o9>43;UJi0;D;i7*@|Zgi>OXzSDJj-* z8@Ygm%g}RS`oKcT0Uwf=z3|=##usPk;tS(6mZRn@%wNi&uU<1|;cxiW#5Sn~c5!V) z?d+*Qd=V_$bi~ejL~ZikV!rCdf`X_Qd|Ctfj|@qL)uHros;_onzARvBYyjiSJaq5{ zl9%1iC?9^eeA7(&e)@J+@o>pm=v`mSjC(;t4hyyih%e#1L+fbI{bI3TLmb}e=W_Nj z51iigZS^5WJUv=6UK&{j^M&U0YZ(x}5Jkb>+q^8~zJ_F~%`xiYW4chS9Gd~U&XEM# z_4+w(Mgvj4!6ZSDf2j&j(h0XLHQLOK(W~aT&8i!`tjgVXj38hNPvC{kjmn4g%bHt> z0r4-Y>}U6W?qp00KWB#DKv$E#lUVlDAQi@Lbk3>CnxV(iTWx zdXOpw_N@IRsU;J|(5Pxbs{ayGkaG-~-az8cItm^-bPeP_p!ZtY-icYh^-kTr zVaGd;EqcD04W-YLUmrS6J)#qjH1z&iV|p?&hkK z=dFH#`Aw_C(oOL(zD<&F8rOATekD73%y@Q&9|7+=9MUhFI7kH~FKdIqeQ*0^A@zWa z;Kw|_jRtS(Jj0o;<%gb&BGRt75r{wk^lo_R?jBvmeY~?5d~uWYhhOyaUKMV2U;Xq zCrQocfZ{G?Ee1`|5gsM#iKOK!?PJE+xA2;mfv7!loQ)mQ6kMv&;JDKmMa}~fcccis z&`}S7+;eyky>^b(`<|mMD(0J5ujU|=e9Yb{hU}S-Ox=sS^RHch_?=jM^*QSbmU$U7 zB2TGg{)+D9|Ah%A*)CqqL{{4b=VcKYQ%`{9WgVd_FZyvOGU4wxBv{nsnJ#4Q6+1_QR@!J*Gd^P{i=v~GQ?oy`1+^IOQuCecq z5CEcUXxUGoD=+&(2JK|cy;FYdi=6US8mpoNW~)@HjAMM85TkS;$Tw5D6I}dA-G7yH zgmVUG|Hsm@TqQHH&uH}^xzF*+_nEx2H&;;l7MR-zTWh10NBV!T z|EcVowdYcrT#Qxas;C6w%Y%A4A+x|CyzwViJhW4LFQWz@aGu+v<}B1UlJ!}@zj*=i z&6_?OK=`sT?GIhP8To->i)3Dg%GPkOtcf$@q9G3EY<{HApzMv7@l%j*X5LBMXnwJU zHnI)pgy^O;6Rq+R({RvxTlmOO7)GQjKoF9bJ)9>4=9{;m!#6|nva?b1YtH7=aTb>@ zsR6z(EKQdOK4$-nD9w{vZ z*&ORp9C~%(nSWBuO{Fg8fU}N1sbz{>2PB{0pS7BroUXW8Hz# zCQ^gQfY&2N*-ol1+oD4=HR5UFD3u@`X&r!!UB&`S8$Ly0X48g3i=5U~S`zwYV)(e@ zgKRVCLRqkmRLA5z0H!0+p+iSP<{Dq}_;dV_U^J~qWmF8ILan}LTlRd76*2D_9YA^l z(vc~Vey$owpIgszAi>S;T!e5FPpCWi1dL$>*STZ~ICh zbB#|&gR@*qy|nGAQHaM~{_bq!a|$4wxwhiz7n9!cg`z|QX@~i9%8OWGqkMSQUwvFH zasE*Yut!Xq&$vmfl3BrgnMP-=1ICwq=;Dj-2y>@$bENsBgmU>0D)Br9UnEaWXRfa> z=GS+8L40{J^k(bfyO$mB)9PfrVe!1eEE$c8zT%>W38hnw+>nYdf%!u5+&~H#UpSzP zFQQ&1XVrnvzfLKoKFfMoX7rt<7nxPwrLq4!t%(-I7dE8d)04AHYL_V&Q|P~lv=*7R zWEGmt^qCPxEE|c`P(2{|F-+}4V00BnEhbK5?gWyTreejAzQw8(eqek-fG)nA z!cb4pQfN#eKF3QqzP#oeep%Cz$TSq~)*?4Q2I7ky!$Cs&48TZwWac>e2KM9dam2F4fNd&?I{-=eI&@T&km5qteZCDD(Fy(2p5)@Q>q zkzxFon&c!Pz91}wZ0c0sdY8;&S!r3bXyE3^@_nuxB5Ed3`K0-oRdS5~-+A&W49ztl zeAy*iy|;af5M8r9tnmIs(m&Cb=Xdy-h4F;Oq4+f-VQ1u7)tm6U{Fs&t3favTp4Re> z;i6~_34(In z(^(A|U9SR$2TXE`+-)q7L2nL8P%tpQhD zR%T?S=Ae3Z0{fF35xM^y?_lgQe~J1TNY{9_#WhC2{Or}xI4=IyPTh$ut<$|>>)uS_ z#uZ%sJGiJ&2|ULjiDeRquBp?sy0`sIkb9^MpG>B1=Q%FVXfnF!!Bd}{g@;@y=E=|< z7lyhOf%38x6=$2eytXRY0&|z7LhiZk%5yyO(y=6#(^O`-k_H)1YyZt>;*cIn29lRW zEor>B_fSFN4xSz9QV6 z8%zi>4(Udw#OIWsY6|S8l{gps` z{kWOnNv%_{Dyr>jD=6-~<(jd4c+_u%{^lF?suCZH3n81>9yn>Ueo7334Ku9A2ge=f zlQ1M;ahH2<;||j2AZsm(h`&8AsYLMj@z=6G-?7AznMwV09v;O-;Q?}L&>1(x$KflH0MXM54l>YDZ-(^I9l<_C zp+u)gb6PfXYvKQnqX?TdrG`BCx4>`I3LaE%4t#(+VXB9ya5QoA*HIFUs7YasJZXVD#k7RB3E**wB_IZ?y#%dnGp zJWA?)t5ub%V?ny6U?hx{hWH`5RfFtY;!#tX@;=6I$=+&F~%)NNkRFV_~Vlt`Dzyz8@P_c_(~T$-762}$ro($bRMrC6cNFueuCr) z5nuWO%hw!3N4^GfK47A_z2b_v+b3&1NzHIoP7>|K#1B!s&q6+0B^?$ZzRaw}_J`Dl z_0zkfCAo=uqPevYP_`*sR;XcyVU@H=yIXZ4-;^?5Xo~G3biCwM*)(#!9?ec$2%L zcT54%HOsBcz;w;{z1KAl%l=Z7t}UYK?l8PLt-^>3OmyYsQlfmFd6pu3r)!wYj^8M& zA!!9WT^~tmJ`AReH;frkH(xUk8i=qmw0Q3Vjynp3L@FS0XB|ZkUAl(Kd2KGsKPu%m z7|GTRmfis_>2^F&v~$=>7Eq>QJ!&Rr9aGhJC_#jubWn%neMYhq6JJ5O^}|(x^=NfpT`}V%Hp#5-g8pB z|3jDW@hr`5n~F$66;^B15m--`{rqqVq;EK3I`iJ2OBr7oVfsjq97+&?YP>SihgVlP zIombB8Cek^diWWXCsZ{hV-TV#^a)5Tl6Mt*oS@3}JYA!;87? z@ryOfg>8er*Kp4U{LM?w-Cyhka+!kjgbxeL<$&c0>!B-82yZj5ALcU67d`O4|4Lai zjNE_S=apH5nFr;@6bwjDN2ZYn9zFP)Lg;wyGu;!Y(Z3X^{@qgS&cy zliS$?)6QKS%2z+@^~8r=5a;VTa?b_ej+2=0+6+4cB?^z`ace4WJ6TcJkj zUec_O__rm~o4eyXkB5$1X|{dYV6CJeKCT`&5}2MIhb}#xYoDuRH7prM2a7W$`nhix*J{IH24O9bWJ01jHtoqRC;WlOv zvI;JeLa7uR-DU^tWR)^V*K8BNFnt0uLrLHYLlgZhHRhuYeb>wV>=*|mM#_@;Wu9q> zu0d~R2c~O^@4c=GJm(-J_$@*7MMP)SWG3*EZwHZt)xOquO`QI^6~yBV!!WLhj<^b^`LY*dBhhm9&&97`~j@BziLf>DpyqP;EHYa55xIN<`NY~VF z4te}>%om=p_l3ZGvo>__1u_Tmq2%tMPS!5RkK!@m3tc(PC*3t%CjnTfv!5# z;+DCGWKqPds>Rz8<5m0*n3IjhbtP2N^O*4p2%tKYE#riv@!xZ(bif>pTiRhCSd-h z8#??8q>s)mPZTG(C;g);&h2^1Xt6SOk!J%}cALW$7Ct)*h)3nAd01_6>!>3>*R{)) zZg^4s!CI+@F(=-`;i)ppY3KA{{}PppQV+zxbZNeau0A^3i<9l|R~!LX@{Pv}e?-b` zW~KIp11BmSaH?(Z@?#8^0#rqYT;Y5fdY+44-$-0$a%{5XE@(TlySI~z{uVm|>qs?n zkwIWO@&-C|B;;Jm0m*#2Q(D)x{>&r1*vh5xZ*{F(y5aK1+x`AujzRhe`-rUVU0GJJ zyZWR9wcwKEr#UB_y$>l=tw??%zJ+>1!Wd?d#buhY0s#q z2mhyPvn+Yki%pYUjbYLhw7Wisft|cp^{&S<)jMy0y=8N(LV3+Bk4(qyEkP%-B*WMJ z&>Yg|AW6Uij4wpc#h2WvBuZI?UIQX_Z1#9X#J3Tl72e8H747E;Z~u6K_|lr1{$QP) zN1#qsy1<)$+c4#r>YY}#GTettm8a(x@#x8rdUNtG6kvSmfG)n&CUMc)M9P|6*|>zw zZ*%Lq%wprG2c){d^;6~E_0bu_JTLpx*-Ka*%p;0M5aeeU7mx1sjY%fz?bBiEV5>y| zFke8wfY-qIk^o(N@lJ~DX(vMI6mW)_ow&+A#+WTG_CTyJ>=YQy2?g;fsPpF?8iAcFC1+$s9PnIb#c9Vu`Zg8j5PvK2cB@+4a90qX*?F6!X5x z8b(GunKd%szC|}=D^Q*|*te?hE5V{45@q+U90TVm966o`1IttJLPwqg(pM6uXq-ME z>Ce)1@q~n+HBBEcDC)1GuC%Xe_b1A7P@dvPUvR+^9fA&1pGV7;#kVmQJEvU}CC`2g zJ)4{P=4a%`;5-HA#wR);c?#4LrF-93l2Pf!_S!$bcmIWir^7N^;F1_gfsA?LZ!+f0 zOllBc>`etin8{yVKdC)yL^<_jdtIH-^PjCR%U()Vv{RI+t zYEQRqqP5tmi?2g_Z@4>ke>ktJ+~?QR}j(b5+^;}j9w*ge@HzvPf|u~bGF zTVz&6g0cgj*u#dUBXHbtyh3{iEbd<1+qi@HX4sTOA%}1JgCB z(4lJ}b9P}2Jm#qT@w1ghDr_~mn0!LsWOHM=-~JKQ?d@m+>6!qON%-T?;>Lo4fWYT+ zJh;WBuHO1iM6%hT*pgK&Ir=j=lAvIEB^dIsOsCN-Sjq`QSC zI*oqicLa_h|P;_`!$%dTB{yFBNWlM&>6 zz%y2KV0qaV=*r9DIbR1Ay*H(`YQn8A2;M2_ZI$|b>J9TGkNtKS4dk0m+O873n#Sn5 zpV>yZ6!Qy_&eX*(^QE<9Xz+N(K5R9E1N-Lhs)QB5d@~(%_-4rY0K=9a!&%WY(aR_k z-s`=(H6|kS@+@Wk$D}I~mXaXf+>W7*nO7a%Qp|D_x__NfX@>V((ro&0pM9cp#6&vq z>G^;AX2ZWgd^1th>AgK40IB=IIu3@_COvb|>hSfOI$=oN+MZG2)|U#BY;kqIJ0C!s zRdiOlwrlQ$Ci3cWEo#SP?t#N`XE42i$MC+6QMO4vSWnx((+4zep!oKj%ACL6kRjq!r9mv=B; z@H4lmcuZ^f6C$EV635#KqY3GJmIWYPqcA13Bu(=I>M_G*+@K- zLd+K!uFYUwqk&uZ3Yf0>dT(_Nq)$6CIsPcWyYFW9SulCmQ~nYv8n*l-xxE|L&-8j~ zAYId24@c<#_GciLbuLA2h;g6|;&c)g{|(nR;wN2s&-LRX#;Q_Fa^>-8>28!VYYhxGfO0_Ub)JP0_v zw_nhHT!He0RhG$~2xmP7=0=%~SRsUy+zi7S^OS*K@ShN{^~(#gTtMnjB3}XC>zWE3 zc|yoM2d}YL*QPM2?c}~lSwR=)Nba{7;;+T!*jru`wlRWu+?}_pM!=&(szY%+EdQo-lYrh%giG7u%KH*kutIoAkivw9Ff#mZ?(2({KaU=~KZ4mq zC0}Lt)khUB;cHL*_+>syNzYeIb`~b|`M>#Sk$z&jz;sP1bm^Law^@9@wu>5ZbDJ8P zaN9V?N@tRyRDQ|ge@q&=t3#RaG%?#edB@lB)qY9D!o13^sZ_IE>zd4ABwM(@FeO5d z;NQF;jnwo}AbhbwXutQmX3u-@i3--!9;DY}h|kW;zwyk@tnRgX!SjBpO26yVHrk4% zu{$JUTYr^Wn0VAUhqqIu*7Eh=JcqIumiEAOP5iyrHM?8691jKe z*wT|~e+5=;cM_a zH83+z4T+yCDvckV?SJyGZ=&}2Y-uM`6flzG+7r}-{lw|tyx|A^-NnG_pP!&h*Bl2j zs1koGSB*X9$9ytk^#1c%S>Fk-$%jghY^FP1gFh7IRKETFELAwm2!n^B%)r9RG;v`o zfHsM9_~k?PKo8b`bE@ohiL`;~8j^djYeai3$OMTJ(YHR+bH9A|vc`*DVi1mUl@od3 zdA&PG*I-~!>5ju>49mde%tv)Rve|k}$C;DKkz0+zEg`R*H81k!-`rQ4$i6dRx@H5q zbWQDswN`sEAI=x0_|8w?Ud=9I23nUW2HG`B@+Q-ObPa8(IysL#CyBt~{8XENqwW$H z0hgz7Sq2fglJX64%6Ji2*F>9!2LaPHHPE4JAor!;2rU+)g1<9e&qf zP%eiS;PRm=48#|$^+niY*k>4mvL6l;7`0;12`1$ZqGs(Wrb9mj)be?{fcc^+P~Z%V zFH6wHmmAM^9JRko@Py9`_!)_bb7od43S5qVee5h5WmyFArC)$1W-FBXj}P@Db!q7a z6|7+g1;@H4J}M4>P1b+Xl(T~Q0>2!JL4XSHTZJd`UY0mhRk3(Iir#c+fm*i*kXtzI1XK_<+WvApJrnf=B|)7i%|* zT_Aj+WhaD=dH`g8hQ2&bU_&)=io?9e@2^@OL@eK=X;+i-Y`+p z`I9#(1XuAZnW8!kQRNF)-QGRbclWxENdKnVnW!u4H~p+&bP(cKfd|7_ju(m{-@x3Z zKAMsB9_(LA3p^cx_?M08x6n~awXPBKU`J*E#22$>R%e(dxS#u^;;Te8 z*=>G}ogo~@tj7t$45}myNi5`xqT z(p~cK*pXM=L>0^+zC>9}wEXs~)=yY7yZVR$EADq{stsFw`gF$q_+6U5Bq|k{FAi>D z&w=nol^yHe`WHx_gKW-mZDCnnLtOXf>oUJL*>!Hzea8+VX%k4zu!u!hPr$`44G#dJSOzOeJ4oUu%|5+XbZQoXwe zj_%c)@I8ruiBalP-u}8Qg!*#0u_>{?9`JL62QhL}f1M$5$N3xJeJR_}<(p@wK5Y{8 zVGm56(_M(**9b8Cv~+(A78moRX$!vV8-5T?dR6W8!zLU}ko72E7>>zmw8n=EPf!jI z6Gq@S2l)b|9zZOW49quwfiB-{XE4HqrGGm5{XlG2OrBlh>EN7x8}7V=Zbk{JKlnlHz&T$j2Wa| z;Bnqx>O#=|c+5YhxGwhhimVkODQX0yYhrA)u@SuEFl9cKAMpL5#ENG_=~FC^LPhBF zv!zOOaeoQcHHg-JM!9Bb_|O^I7A+Jn@z?fP((&$Ep`9#lyOevbS8Ds+wqqH72}QGoD;mfaCLd^6;H zz;L@NMbv)G=OfoWZMuY=y1w6p99N?^3;RyU0-r#>nR@Cp9BGSbtwp-sX^H07Hg5{q zgmyI^e*%9L;hIsZfdkk#b0U$90P)SJCAjy#kFGz#k$2rVr$47J`F_LT`zGyo2o@41=dAioWOz3Z5403-VA6n@g{3TCNDDDyi<V?KS!}1??ZrJdrH#^A1ma8f-#`28-*F5B7dSI`1RM%; z*{*z+(+c6kc#}{5f84!gR8>*9E=(f=0us_)(k&nzf`p`WNJ=*nQqqm2fOLa2NJ@!- zfOI!Xr_us~-{!d2z2lsF&o{;zmsFz@*f#a7V z*x(o7UVUK<&X4&rfuspCxd#mPMor0^<5(P}?N8R;Cvn}}d!+Lh#?o>Zi3sSf`~055 zfWk!t$vOOS`joNd7p@DditlCszr^RBgW(r`yVF~HuO7&25<;JEof0Zh9o*kqV3-a` zGQ-9D(WIx$KEzSiLIBBYPKR9%sm4_rborlOU20Th){mNbbR|Bf{gz^2t6i_lQ3K>P zHFW&o@|tJ2KCfAbV6b7mzuv;?XxHMaE%rVoed~jhPr7O!OVvyl1b1$if4#Zt?9mCl z2l@N-GF_uDRC1jBNJKUH}Gnigw4arE5G-_UJY%!Y>)P7{jIMDnC54T80MMtM@`|=X zp6AcY8CID^{WazB(Zt0UuTj&wQGcHht3c`jq+!uQ-;0_WW6A2&4751|qt8oR2z=)3 z-=M?0z`Z?Te+cw5WfZG}sRz8B+PwAsOp!y9yCLR;@l;Yr4_M%{y3cdFzUBMJ`=`+% zVq`;f%~M9X31a^32lJ+zE+U;Ak1x)p>ammYv!iOazH8LDkjw+R#_XpMI9*c+8@dLl z2ShmGnkU2n z+4pzadJkNxOan1k>&{uhd$lSc_Fw%gwyhKtb978PA-Gb@IE1u*x-l;>0l}_TJ@B9E~!EJ z5{f&sgPAKZxMScnf-PP1DVPh3{Ic>>QQ1|fH+$PfMc;vF#RxUdeTjbhAc(G6DXms2 zG}XF5>0&={QcyRW)hMg0zFH6=PIkata0oAqh3XoBy&ojog!*W# z;We{~Tcl2B*Aka$L|bxE`wk=_y2g!Z!HX&8T_*#p$+eDRSUc(JvA#_IW8ou?l;lkr z1XUD3*Cf&22d8U}ZoRH)ToOhkp6k~PD|9b1ddYZ@IQ>#H+$!b@cNAg&=KdMaaO_~y zviSyyP&((ueP(!rfxrE9DI5tn*I9T|>{TkQhr5L4=$^-pFgmz>rwh01L53 zPD3qJ*C4-Y*afF+mSIEJ0Q|y_TlQQ|rb(Q)m3U+$_>x{aA@>d{|JLG9l5dtb&k3RL ze5Fhj#g?~bQ>bC4qOg~gUN-8>ODCux#b(gZ_n~zb&^5$AWWe!@Cv5NwP!CA!>K4T8 z(~Toxv@{QxLJD1_#mVNvj)pUN7km&8;TQ6@>BWn9Zn-garau6D|c z;iHT$eCH8BJ-~GT4j6vHEQPzZ^#I`cXYSg+Sr?KiF>Npr!{gjiT!!YAl{g0)W22Vr z(+D8-0EFU?smLGls#uxa^XpdfGu6)O-}N=_tLJx6diA)P4FN|f++Qr z`&*SX6Z5Czn|t*qZYl1CQ;mx1{(|zt+H$Bb;{y7kiZt1N5a-|JV3)hVf$Eyl($DGO zbj`)B*ENYIrDoF(YZecgID61g`3PIOX2<{`v6xCi>-*2Ehm^gi*r!t1$>!=meG)0Fr*T4Y*7OqL4(GkEnWI2Gz@sM} zoUYk|EnU-y*t0=xcls<mvS5>8>5ec4)B}|J z1;OwOW+~dO)iuBzL?v5Cz4fF0jAH&6?e%%vj*nduI7j1EDw?y*VmuICb8?s)njSl# zg)ao>t((+1M8mZABm2(p!L5w`49a%_?`)vD#y>Ru9T;8HsWAr|eGb4J#MfDb4U;1> z@As3BNd3C^Kd#ccAhpuXNNa=^)rLU$g@Xd_Jd)_hH5coX!{9QV>+9osiB_`ujJ}uL z&o{(cQ}UqvqH(e40ES&M z=$gmmF5YeJ*cnC%uQv!CPbd7STROBkr~dHGcbq5(NDgZOc?~1IGB{mxb!&AEfV+ki zW8bb%l5w^f3oX@o+HZ?{6uxK56mAB2AUNOjZ%#c3K=M(mJ+LkvcoSH~_hKqe;Z2sO+&y24I|PTj$Xk!Qufcfb=7zsGJ`G79>pUlS5VvyZU8Y)(4GVbp z{wV}^ldt4NcOq1=P#w*wrkI9o25N+=bXL;59zXqagoYR;stUzj;d9(>aJcKd^|-r~ z3RvwUX18gvdN5oLNlKN6o~4_m??jof zcBVf5Sq(GIhfmTafzA;TN#h5HyXaesJ7DhXqw!#Y8Nc%~^E=Ydxn|(S1aa)s_~e-{ zIU?Z7#UZ%+HHxsa^tqRmguj!?-*G2jm1QT5iYX>&x?Wk=A{Z{G?d!ieB4uBNJHX&> zZK?yd?%@SLZDKu=Bw~d}pR4_o)rkz3q#Pd3A(uIqMA(4|>FZ(}w-M#3Msi)+{;AZ5 z;qxsZGmI}W%UXZ0kYjPTdiShT6nYQu`(XpPzAl?v-`AyMGp1_Ir&S=@MJFdLP0#%` zzRkR#Q6BrnQa$cX-94!aU5)n{FF^&K@2odj4!>qx!%IjOrQF}XBX2#N!AauyZ;l9J zGBpl3A2$gbJ`Sk6YtO451@^i~&S~+T4zNbY_z5BXb$yrh**8Rq=H@vHM9xYoLMXP1 z?&G{{O?B10%=~Xr1*3+BKVZy~7^5sMD z>u#RYqph)a(!86RLnVfcZ8y-w<#$LNM2=F287t#=#BIX5k@NK5e6(=9-Ewd~ZXY&$ z956=&^X##CpW8DuZSGxd#)rAO*c0{Dn*++cf5pAg!y&px#~O#@Lj^uU7i00yP58H@ z_HQaxa+o+97w^9~o0``2^ML9aRe{%eU~~-yQV?wAglUh4x5^E(p7x#xDPYZ7HLrNS zGPC8|C>ti#WxSaqqNeC%O=~B3WuW9;*4I@LfH{QEkM~}DW?%zvU(BiEk_*Z&DDwu~ z;P_<*Huwdoe`l$9yW>JFblo%Oxp;HW`yy4Pd4u|C%x+Mk z;)54%DP#8m(CnTjjS@&)D~A{+bo*q?k$K_S5KTU$3Fgq`j>J`33@R8T;%Cp_?rCp*m8!Qrr;?7bmumBA17&!G{OV_-s*=8A^)h8gvIKdhXxjR%i z!_DiRyF7U0*{GBZ(KX>@YAZWRhvyr#qV&fS;XIv76OQ`QUdurf-C~NGZM>#XUBj)2 z?+H%V(8HFl8G3;|jBrrYu=kab>rlDReJtDVM?mxm>IR1gw=YE3#N7$`Dvz3o(wI1v>33kptd999{MI>I;TmjkR)zWlpP?5)qF! z_J*PSQW{{p3Wi_SrsiOy?w4>gU(SX*x>cxaNHw;Se!Y=Q@tA!sk^6UpjfK`{O8DP~ zHnfnspSViLV#M;B1AM=tcb^Hr8eX90m+Vqwj_olXUiK~B$5e;b{YD7)m%!BhGANy3 z<9Uxj{e{%eBbJuLb?aImgGPQxceg;~>X2A>xs?@}S=bBWUmno(VamQuOzXm-xufO~ zT>SQu`BejXX7_T@=14`1uU8(_zpxBCsDtq@mf;n!QGWsM2hc7weNDj-N=Ei`pIN6* zYtyW?#9n1mxkjU>jmCxOA1N6V%UVx{sk06|1V7w>ci%rUJlwA@={*ysoMS4`+$9I< zFX@H7VCpY*8W*?regJSU#qSYe$irfbLsATaH?etm?e9jnF-LxUBCy=uFmHf--dSo- z*DRyB!il5vTpt^^C>cGCAuIP-tA6@xXYHHi6Tb?;zcgPz1j8?wrHr?}e^cMlch;+e zpFg=F_tWQrMSI&Iu|f{G(g#wKvk}4&+>Hn*BYM>_wz_fudKCok1GoC5W{TbDpi1xc zP8OG2?*lvlclb<5U~t!|@f)`4etTj4&8#|l+Xd37e|=(FX{u`%iwa^c-ZhSz?`bD zBQ4_cMjhEHWpQCfriX)j_aeH(2sz-m-l+>+LHx^7MNeJx-#33P(E?Kk5AYTg$TW*R z>CCPBSolKL2y*HQ0so?X0jjTr-;TR_q8)c`Yy4Am{+6S*x0J7oRnM-2~e zbi<7gg|6Yy^+vz`$Jalc$A2rG7>@Emh5WgbI;}DtkBUWwC%mf#-6vn9m;-ZxPXhS< z&^8om*NQhITD@fFLDR7qs0vri!q189d&WK-446(gNMqtr5QisnMgQ-g2mIXsb5(8C z!v*G4X+|RD=6w(|$1*js*QXz;H|ixZ!{7UCmZ+w4Ndb9Xq>T;YoTh$5e)h!}!k@qF z`Yn8*>@lY|yQ_Hj*YptIi33#EjHloqg3&cQlwqF>!YYA4-gxVI5;cF+AB z*k>mj*_0TsA*&o>H3?tzrTVMPnzuc{n&ZN7YjTh3lMve$b@G3w0t}ME#t5 z>wlt}6WL+sjHOuHn{=8rorAhw0mU84f_Nl2-^^J(G5TPvEv@vSJHj8Km&mw7RqA_! zPDJAU1qelqj}&CO3uU%A=-Ev!{|g>&`g3pg1z5`=`}hILrsKok8=dRP-6YZ}8v5n$ zU!=LN{y&y$yDU-5enr5(P@+6QI*u@_`{jVKg!ISNy5eLP^ONJ%9eb;$DFrd1rM+5Y zf%oN(&*}f+8`%H<`}#`faWha42+x+Iof;IFKJgQ{AVbpp9rJ!}JJ+BiR@(c8rwHVA z<)kMaFL<81Y@8c9bqbGhdPSzpe{(YVqgY@4!F6h8xErW9%Xf)`@y)87Wxdo^Ltz0C zR41ydoTe1G-zpK7d0mlo+E3c=p8)S0x@thz=fKw?pI?=y@(?T9m)hY`N;lZP(4i@OKu8IPb(AKbaJY` zckh4GfjiqG`Q7E^9qImNjVpQ+3f$;o+#*Ula*d%XgnlHo|AL2`^X}$-04oY)ADJja z3lN{&e7-Z(?}v;Mp)Hdnml}`nc`L$41o9jdJOuUy>4!t9Vyfh$@dRtPmOZuY2%Ly~ zc^3e;vCo04Sy~@V1)NPvM2>(C(_rG4xORuI(;9g2{Yk3L0Dse(Qf1+laAfJ@p zSItLro!BeXDCskJkk_U6W-}X+#@^uxil!g;W8T_ve5Mn^IA)oo`q&c}!VW!JsIG~S zNxBD4*C0A@8_4Bz=B4apL}$xw3DaY0PFE`Yp>jbJi#{=Eba6rw@2`DQ^gu)4-@XC( zL%G4-&AtGu4P+k|!qty0iEcjM5-9G|O2*f6J$>E6Pm{to7H0OXZ59@ReJK~A?!J%8 z-|izTK=#9dk&J3!W=Q8M%jdgl41UQsqhgatj!SQ97Mh~}`{x(-D z@zPmiEjYR}{uwVN|hoPPkqR_sl3N)tl)6%_n$vcB>8LEmvmCvy6RN>>rdPHaTi~%IRGIc$U)+_eG>?Tuo(<%{$U47U(K* zd?i0erGw1NP};n`92K&YEMbFGhN|#i&J*DK|I<1jY&in-Gr>L1f8+o9eL6qfiz1Qd zMq+5oMl9Vp7*|de+A%k;OTY&{MS%#4n)9(}Uc=e^;<8P{Lb2V8{gb3A!r~?7t z{M2CyjBj=?ofip(oR8sLdb&7#)f>~VRz7x5|Fy;aFXsvH{r_o&8%K-=a@ni9 zaQEy2Ej|U^&kELC;dK%$(%t)Vykfo@92R@?x(=NPpM*=&?7k#_UP-t9<;a+3&PC_* zSNCWAmVSJw9)bkWTs8}1<|(*bmN7vcay~wQoRjB}^Kj@B!WM@En*MB-@*V@df>I{r z+@v?b)QnDYS^&;B=nD82U=97R{-teW?x)R@eQKTW?C(4DuI^%dF&zT-<$uh-_-;_c zi_AMKy`UPXqHTW7M2CQk@7xBLx{pUxLJ7g~^nP{Ln}P6+gK*9z9F^Z!x>t!dIMiE3 zp=KUcw%z{ENTL2^N*7KBoPP9VSFs>5Pe5dCm3 zS$Pva^6;`G(6-S0rcI?3glwI zs{UW|vAw83rw3J1*LNZuP?|nyP8yUyR{f9s0`mX-KhMX2epyC40*1F|_QdRn8RN>~ zf3M4SJ}(+_oNn_3oZS?|Qp9JXpgU9`_g#=ax)=+1-_W%by8gGhENA1d zCxM%ErBW$Bi7-PN2qMo%>45$JAIoL?-C25zjyU4#6%2f)Fneo-V%>Ef>hwNR_*K)! z579NhT1eEZW?#&!J(}coBzYZ~-5AUEKwgjAruF+Bo}dRS$$)QmL4FEG*Qjzf-TGXX zS;Ea=g6+=8q8-*^Z60AfDz~HoGo1ZdZ^a`GMF{Q?6cl71NOP?W3R2I9VtHjAZkWx@ z*6H}eeJZbfkv5Ss1;rg@Ch-Co+*yW;!&WXE!!bilcA8FKD;k?G#GwAVc7JqSj8kS0oBEWbwPkZ*Hlkt(TxnOwc=0L^8iA~LSQ<+9kY zmCJUUt)G#)3Ods)tfLh=+NRU6(%^qeW8PlfkD9od7vz1t^QuqnmDWTNeX-Myd8$c~ zpO86QV3{G7<>%u(nx7wldNU4%Ah=xiHEiXw^2n0w!F{}3rGdzU*blpP=p0#+6bVKj z7)2AmJBH|*pqZzV7`@e@gm*19I;*7ju{V!V*?Mv}tkUK0HhWJ-pg8@TV}L{(dIyZI zQM6-*E#J%{mw`~d#@q5l&_aMD-%1RfwSpWj5KnDxvMA{09+2ao?hw3MOR&7F6UM$EFo0YZ{_S+|IIP*{vj6$hF=&s>tV|`?~-fzYT%>2_xWsYa5q-r zTaB}@)7C4AoztW_&LoI$-mdkePgt=QO8ZhSWYvfV|J1Y}bM(T%u}@2Ax$=ZDoVN1c zJyWg}tpRYpc?hmT@x~kNE((mS&M;2tIPEU9KXN=#(NkG?@7*AJi^ff*W0O$p z@mYz^0Brf@sk`O%TYpN`*fg_b z16mhHkKJeYRj2XmHYPPDZ*tkcnWQhpt)em>xN%3Ggv_C`v#*`<>Bd3FxV63+=qsr#!H&9~S5e}xs;@SR9ebm`II!r>USmb#g0FEi?=&@O zx%(h7Tj+DBf^OvE>WbkmE@9^b#O8P3`32N>QSykO{DQ!?e+-Ua*kFTSfWDGe&iebK zZ#v7jO-TN-_Y9n9ndEUij$D_%VyPgYhwuyPxXW1{?iCxo1jaGzV(a;AL_e31`ES1- zsmnF3uHZpgD8JbJlFS6dFRGlCx7NP^zIn;~U}oK1__??O;bYgopGs;7g9qCwACqxv zn-6+I{7Y1&J$F_BHyW?|Fx5{&0`d3ou4~6U-+yillgs8U5-A`h{hO0R3&$1##=nq; z%imhx4Ah$^Px+dE&<`=epRsb1_9qH5o=My~{^uqiEbL2$QK#wU?#=_4ZfqT&q` z4%U&1NXH7P4U62E3+C=f<3y<;fV=4kTrjv};EaYXUDHJA;atLkD3ZR0hGr|eq-!N+ z+ZugOINaZe`RO}|uIWBlxzisSur+!|e2o`*u9%3B{6rE#2H}-$-|EG3?*uBKYuITF z!0DP}*w8h=bNBpmN#Rg~u#^9OI-_9c^+s50V1I$#vbAvX=RKbfMAvxNc$)sb&f>GF zwwCd3-XnY)NMM-#M}Ld*1JQN3lk!3eplh1t?ZD`ow^MKl>Z@m;#BIxpXsysx7EZ+W zTm9=(V7<^#5SipYf8KFz2mGM!$P z;s`NFMFp3xQ`UOrsE{ zh3pY<6_f&i_YGYsq3eI!=U_AB<5R`dNlEn6h>Bm@Aax{0j||xV|FJ#?v0NW}5tU}i zBXhe-GbFNOBIkl{O~fsiM%kl#&XC`mak7LeXZUlu@yd?l#u=uRZ(>i>9JH1hlaz7- z_1uri_My1TVGmUUgS)jU6xh(yKt14^X_k?_%%OY;*~_S@%c+4yB5ZuelVB6;V1gA1 zf`5}qxah|ji~|~P74O=4aCE%+pn_+Z+EHAzhoUs?n6H!y=;@=_Z7_N|gR%^^>H$xf z#r4`{4=$@qhHUnn1zc8F#deeu&RFH2uRoE8d>-h2SxnNiB`(B%e7u*CfhIvD?r=Vf z)@w%S8erqi|F%y%v}m#?3qu0wDzrEGp~+G!@8)qHADR)whH z2i_RV3{6rU*M+grgxtTmlcAj^qhRU*bsFpr+zQXiUhak|NPeyqyL2%{!|kqBSr)cX z7~ydN-Zyleg0BB<|7JHX3*LTx!&D77xBSa#I@{N3_uPQ}{~zn$9BPnN9B&a0j8;(?I`bKg$?V>6esQ@zpQMQ+MAYDl=d0FCX|6q9+IamWANI zU&U)0lOPS>EphOPnbpNam~ zRUkijYO`CyAHNd05Z5hxZY4COt2VGj)_!yDM6bSG5zOrpezfYJt}EIi`%Zosv}^^<2~qB`^MlI?$zdxe z+?=aNu{v138{zydRp&B+>V5j}*T%cXp=%K;nm5mT{40vhdsaVSKG{S_yqu2}Rh5OZ zw@Yv!WBE%4SDz?u3LeO7^kr$l=o(c{zgwRZitas7!u_SW5mDT(Bxlr+Q)1KP(nGnf zBBIB_cQem{w3Gt*X>m@&K0S>mOD=(UZ%21Vg+TH8Y2-(Yt16bwGt1F~ooxmP?!2s2OjlM?gP0IjD^fld zW%%=)*#vl1b)L%m7S*U%W|Bj3XRD1U1`c-_w-$H6y~i`W#}?(%D%!>3(wm1x;cIx4 zboj-?m0Nd}^dCAxaF>wd>W7_Y8LDC}{A~@(XHz3-dq>WRPkvtl_2f;LH;E?T<6PC$ zz~Ig@+zht*YvlBP1tk$aK=roZ@2aO*9a!+?6%es}b{2m2hveoyoYOkthg~C9EYoRC z>k@(4D0rPG5*pdHBW1<($gdpf*?&RxbQErX1QSe?410gZzSuww^3H z7wGdFJJl{^$VU!SG$Z6fd=!rgd7xk`_7qvacgWEW72468(<<(ATBhj0~`0=f&?`;O7A2#m2|ZHn(V^$D$4KWT|r zGY?p|>v>esaVYP$<^8l)I`e+bCxsq>>2CD_$}jYM559oo7h~Asmu)T~l>0895dI)w z_EKX9chjgYE}r*n2P8iC3dM);%jckDDHLWF66Fl!a0GJRrElIGO7V|~JT;SvU1z*} zqfmjoM)+eRIDX-TEq?LY66;o)!3i&JbXey0oxKm4UfgsX^}TtD1M_YagkNMw^e0vA zejBoyVrgeO$KB&Jj&(H>|1lSFMU71srkI0W)q*|~aX?_)XxR|3Qi5`D!(rdk%{ z-U7bap@SYAzubilegXO%;_aLplD`IPZr@9+Oe%V(p}@aeGU(e*+rF)W&$ z2*NM-zMg~(2Cfi_C##-kq%m{Yt9a`R?&8~_6K8)=mo)l{3-}j;DjjhAA_yD&0`xi5 zj4|gQvSG`O-7z61?*3>PN_1X{SEz`|U5YJ|bK_rF;uw0yPLa3SX82b_#imERM?>Zu zIKI`T*;R9~GTlD`{7dBAI2eAhLQ=W)eGX{TzX@r~w6faBo@~<}?J7KKJ(V}ZvGZ4a zW{BhpsRtDJyAbjYdAu%<;@gDJ*K3jF%j_Df?zDj6_~-=)FT1L|*mJs=)!K=^k7QB{ASr)xOd45L@^+LfT9 z`XVk~bJI;7*X!^%nVZDtH)^{YYDCOQzb-3!{ml|4cbN5Iyhb9 z3>&%z$Yrw%45)sb@M{finJkcS$*-=%ta5YTcHOnmlYrp3&M=yB@2d7M*V^{my9`emWd)b^B81ZpX0EIIrw?q~ z8u|5SC-hWfNb?})VuD*}%d7i^KNdu=9xZn~#2Ct{=gZ|X_5UqIzV|NKAT|@|->mBM z0;g*jZ@sSZMdij7U8UzHik=@XeOZ<>5Y-rzlKv?;i+pAw*iwbKjKcXd{Ec5uhI6yQvFK*SNrjt^x9z-8kR+T)~gVD26loAFlY3 zm37`E&hV^6G;3*AAV72t`lR6|&*&$&?u$scN0Ku0vkT>tA42`gDzlL8GvgS3{|MDJ zc6=z(U~~-w=Q?cVHFa}GI(_Cp+3 zwxeyn3Nv+y7e4duzBWNMs3sC~usn@dX1Y;wIpCHvX-T9B$i3XcAi8Gm`3LlO zR(xvV)w<|>2pOLzes1Cli}91~O)^B}iF%$yKy{6t*%~((T|*X_1IA;rAf? za+e{~o&WebN~CDM8FEH}4Ur)VcHNP2u=*e6@0oD6WodwKCcEPehF?@US8lCu2JYLI zIlpEw@8eC^t$58&fpO6N@O#&N)=07vnpF(`|n^U4BWY0{3AIXv5xv9QvPR4&H z?rW>T(Eai{jv*!#XAJPosCq$Qd^2e{@vZHn19XjeI})jY;(HXD8f(1Q-{}pVhLW7J z4omSVG@sMo^lxr=P9Fa?j2C$ocb`F3r|TqxL*1B(woSsPXudx9ePK*86n7TuHTz(2 z$H4g=HgpXz-}#fG&+0g81_%97wn7=JKgXbW#SHWH0-1xw{g@Ppu6cEUEK0pkk@#9n zqEUzUBiKI_#2F_aW4&mjr#$>eOvZ2x0bb)RjmSr zVEFmhQu&%cGOvmm(_SxabNo_<_~zcdPYc`q(w~`(RcPbVrO?QEJe|9;18CG{EtxKg z4yaV1zPapZ-4cv%emmuS>+jprDL-3Ok!Kee(AXwsW&ME9n{vG7ZSG<6z#dM&5u$6X z5NQr_;+HMgge~pVJcP0rCA<%pA7+2m%uwNw%E^J;_xX3D?zMU+jAJ%AX2vb6Q$@O+|5xJ9EiEa91DOPYHiuE#aON7#glLXD(v(6T853 z-iY-;Gpj;3^iFZ#zxg%gOn*kf;BIZo5jOG~;J)ppkDxcHQo7`?c~dxjIeN>!9$5o# zigw0SLmNxTK9)ysAS$s&RSxB;=aj;~Q5h|q`yrM>erI|1s-j@Gv!Hoi_}~1R34cyb zFnLV|9;A^Ucbz;hTXz2Q-5t*2rVzJNuoRTPoOrDD;G143!;ADxUC=MMOaO zrF?!<_o*d=;l>mD!J%!Jt!a`*8j1bK&jb+la(~hEg=<6kMY+b*0UWynpGzY?%%Ano4LO}RQC_(0(|Y$^4ZnYuC#}mp9W}Vh}Tlf zh(m4)4M(BTyF{zj(q1zX10hiT1>@O&aj}e{o}zViU=${&2}%mWvr!Se~dA zy_qZi)giElGwU6XO?uG_a-qGS>+o-VACZg*hC4Es$f@d4^#lB(QG5>!zwA(s+}e5o zFjqVy3d^eYw~A`oCVcNbJ9qMq%H&PA-OX~0mb<^FA$bjENW!c8{(<-BNidpt;JBxr zvZ=awX|Y}HeDlQr71|0%P;Og^Am^lgY%Mcw1xPRaN6`Eg$^6z;th8n^U&1MCTY0JV*q!U zYzW|RH+JiBH#V0CtyQQ13BTovrUcy{DX=O6LK6Z^ z+@|GvGLOnQw=6H%cuZU73O5XdAUUDBer=HcBV|zrCgWNE9~fH_D%M1A(++W8f5A8M zQ|XYDX8AX7*oLc#8%$29%6V|>^XbQro(udWQLFihd7wydJNruBc7_0-D#IG{PDz;D zO<&jMqt|12Vxile?kRotZ6VWkkXFoTIobl_f;Osm2~W|XxTBm+eF_eDJhv8iKt5e; zBz~lK_4lMlMEhk&`w?EBZAk6YyQt07*vuEW5ZtA6cf3i9o?x}bV9_MG_PR_gz1mAZ z`GC2_uL@ss5p^U1#hq=z3?~@eF>nsSMm`P9P3rla_aP;GWDD6kAo*|3)0LC3Xn(|v z`qDkE=-LQKK7DyNH|)!iMd{YN&a+qVy$|Q@rM~R^IY^(jvDWrFX@84Y1)XzkJ!k?Z zpT;b$zqNIDU~bai7bPD*i@qBlF=&)HEc(cFqG%{@L}5A@(n^T%5RxBwkFQFE4PRk# zl8NPQ%`l8oAiKJ#o2PbUDC*!-j=2#v1Np(P1VJ$QL7j%#t)H9J-p1f{cn|B{E=JY_ zu?<4Od=8z$`mriAaYyP#Is|vEe5vJe!!eq*tEF>6GlU04*1r-R37$wb-6hAZr}5ZF zf#U9wFwZs^+_{%l!A4yKn42UiWWpBkh@|xqcamx3soyq^1T#x#`!=H?<`S6#BtMv? zc>cDki*%H%=uwDUZWS*{l5_T&It!mSTc{q1?Z8YTG(RZtegpEkWHPXk9{_sVbS8;k zYN!%@M4vZ>1O0Avgsc6)X&LoETypZs&D; zJ~J5x`~0kZ3)R!3ae|cK^z=S#>FK2wq+aL6jvrA%Xt*7RZ|r1Nc4ecb*U2Br?lMS2 zbj_yh<}ati)Ow4RjdanUO3%f3CC5DO&#=6ArgAlmWpc0q^fY1YG&o(ued~2iwq3#_ z`gFO0*0~Rfr`X(y2mX9+=t3cj&!)95Z|+G*Qp)Qt2E*gZ^~$B>whvEP+i*CwqD2u5 zvsNMhYELGo0rd1o16y#qrW-bN4N!Lv!OC;2$EvsGDJ|)zX80TwTx@(amolTQx)#sj z2jQ2lJ1e#HVoIKvW5G7@m0qk?EEm2-+PQKkADf(4FUK8Bp!~vlD%T2*Up~PWzhIVl z#LL#=UgjJp)U@zhX}4!m1q@-Hx~-AzyitPi%d30C0zE1J7{=1rbr1oypPr(ST{`RR18n;z= z?k6S)5Po^gs8s6}=DUj(n*Q*Kb#GMLX2xO(oR{}U)ou^uBoaD*D8CH*S_gsQmmSKL zTb~nlBF4dE?%7LkV)C=t|3(hN)D$sce3AIcID>W^QeRR(?JjwCz4lgd0gW$xpL8tg z%U_Ehe)Sy!2sll_pU2_CAz zjyAuDie&PKxDedA%GF+rZex54w=pxR+!nQ#%^SDjdX`Aa;X7RTkTmdJJrsA7^i`JN zaJP2ramR$I=#*=`oK!P8g^+fY>8fu?8e)h;lD~qs`E(6}yFCtf$Yi$=Wac@H6@D6Tn?CS8~ml^ z{5UdcDx`D}+}UPZ26~T0opeT>E;9Tw(eigBjyv2~&D(w`Xy^9F>scEVcjM+rao}*b zaqDp>i>~MMrYmkZ3E|N2MMuD1yJh)1n(#s}G8grSIDl$$)dKj@S*?Ok8^m|o*?dJK68i+?t&62~k6++Alr1BW~DTaUXfM!Oes`16Qe8;Vn^KksRNGTpXO-14Tt z3m5Ulf#9zA&HGLBKj)!Sln+7;wcju2+?ja#XyIGV@qj=Tx*Eshd;oVg1QOtIcX{h^ z_t{}&EnHEo6+=t}w?=QNuDp%L@}Fy6L@w_aizIG+AZ5{< zdC3}q6c$A2Ya`zsaHZKhXD{-4pX zcY;#|X%HQ6o;NV1i*3RfMKktOLY&V-?@O^|T>i6-ouXhhouoE|o{ZoEDDH+&H}}Bc zE`#z4HgYSVukwyE236DrE6EdHJ01ErVPe-Q{qAPuyv54d@mDwVZ&?Z09_K!4QyVO@ z{dQ05nzSAvrGkvWVMW)9SSh?LO-T!yTQQFntb@s|EW@c`tFQ9g#dDdv&z>VJr-Vhi z<2eg>xKw@HC<^!Em0yo$gVaTGJx>CgPVebzGRsdB^icmm5VH7Ey__YLu<}OH3h(k2 zCbTXR)=(w`rY_Q{;RqXjmB4+((8RkI6AuloJ*W-hYm>#Ki}=vWyXpL;7DfeBZ|2|r z8KJ58^!)4b9kIbgIH?Oqw)P)nY=jxlECScA{806#=7GNbI;$x#eU%hQc(8RJkzQ;` zFoEKnaL*+zDA$1Z?bFfzu&=MZ>^Tqp-MbXL|<^Nr{&3X9n~j0Hrz~s!5N$>$HcZ^gWo{iooNpnTs}Px zTlsW&lZ5fdpjvc~j)k0?Zt19dhKUXOxb_@9oYJw7d>Y;2FlEV|<$3Y9>d3XZGR@QG zo$l2^UrvnzHPb&VFZuI<`sdXK4!C@}9Jcal4lxu8#q(9It={O!PBPwCaQU`x!xKDX z`%xIqHE;6iu;QP6%8T=9il2gAzRwP5e`7)2Itj(#A6cHzh?kGz0`BJw)_8-EyxHzgweHvrak5b51VHw|{BZY468Dps7ErJixkrK~$l>>*@a^G>iTpQ@1Tn#rh z#Ab0D#15TcT65Hy_$>?g7bN|5FgkLF^5oX~7vTBQS4#=KRH(^<4qjVMjfV!IBzetz zdAiqzg&OToZ=P3`)kQ4L>l*T;kAGf*>70H|a@^Hdl|SZg-;&{=#MD69dnmskbe5ol z;}=EP;1{5u(DPGWV4THec$$9Nig5+~D=u4Gq3X=`78P!$Lo^7#c(=KA2zL=k_wPzP z<^9r2S^fyqylNz4x8=@7mGShSkRd3)*a;Vbx_7L~IdW_J2?75yC9!GeZsGcgK>b`F zMU-ImtR{knneYYC6DBKidx(Fbf_vE7%@rd<_G(|qVqmG=NHv()XjP-G)#EUEEDz2m z9O_>fWR~{8`Im9n@-I*B#3&yghMde_y(H8gcosFRHoS%(&&c?)8zBG};$J%KJm{It z{N)CnSps>K+ojcF236bJuT+00KUcN5px7DoWS(Q6;1sRei=tGdl=m0 z9ej;{R;PjJdz|V|b5%E7z-HxqRPp1YxxDZC!vnAd$=sc)JvsfT^3L7IMb|F)pq_MH#n*cu?C_KC+-lr zd<*TiXrm9Xb&r(P_gQ9;H~cu7oi5pN@V6!JS}_g{wVFFiO%klkM?#xi~Jiy?Nfs+w7d^6Cm&Z$EnAN;3Ava_p_-TTDBhcpmxY1`7w{Kc7nmbFB-5*xHnEHdV|HRO&S=bnjVadt!f!tuC8*CI01AaT{iZ;4nygvg$<-*8b5?OyEs7CUS^oFfjE=wFDl>0_L>`#@RWvD^&nwKwqOB!;w8-DX9 z^|xTE<6B0wo^7>EU*>GNrQO@8yfBC6HF=p8Pr&6hMX-_A0Cm5X6Mxw`v4ZaBb%wEv zMBD?`RAXCpxTn9LWxq4Nx&N8OwbWM|YpHsFQ68bc@Vrng@pGY8cT|N9)$p?4M;}jk zXkIg8{?ZOiUXwwI2wQc(J&IpXiDk_i?1o7>v^8Ud!@5rurwAz@J+ONDph|&Z;?ND9Am=CuMM%P$|Ps5gPcHDQTV0A5> zE4Mlyq0m?2cX7hmpEyq^nHoj1zN!25T09S`CaJAHr-8GZ!{3rI%{x~obb5{VMBg~F z$5B((9PrI;wcg--a|CShOAQUazo$j?f=(P?9TP$HyBZGEocvk_v!XOftr^NMs_`_f;P~aot>+h}dqcg7YfZWL zt5b@L+o#A95xu@g{rY6jEI=@#58;;{D_&IgcPP$naH^yB`mF^WXWULGp>)$R5XsAWs$kL~N892Vt!6E-Q??gl0LE@%g9ZS;j zGcECto#?Ie(D;-rF2BTlI`nOAwE&KAo5%f=%p(Z@=5>w4Jb4C=UkG80U+x>Wvm367 z?^!QUu%dl@aoA4FW;-yMXoNuiT^7PG*3{m43Nvh1_}CWh%umsOG5)QsuUEE$=ZEmi z-}f{+n#@~>_%>^Uf4e$HLL-{dXpA~$0(qs+5nqJ12fO^6*A1o)6oqvoHr1`2tmJARhA^hSZg3ojCBIcgi`j*4wsvZ6a#vmVC zz_^l;_`BEU*3EKK^w4>b_mS>D?vBFyDYdv6>w={bQl~!x*@scT_p*$574nJCED*jN6W_ZN0mmPMW5(vE z=v37yBdGa6;osaOR6%%%|yyTY%iEq zv6GZblFF=J4e|$j?n3;h7ODvR$Ik1U?(RMdt!&ep3Zi9wKQQ>CP~E;dK>S5m{UKvGF{u_Ygy;_l>_C8d()BT~ItQD{B9hcLU?8cef7+RmG$zb{o4SD92Y!f7^fq zHNHIhg<|L%Otd4D#r&m*lwf~BShnB<*7wVYE`KSpeSzB~ zMw$5Z2380jgGR=o2T=4NZ3EAHG}TP~DKIZ|snBKm!`i!|6@Y+RIyfcXp8 zz4w=b5@OCoXJlCZ;_pcL_`{g+dL=K2>mI0Bj9JjRg8U^b%%x8mlRPcqP*&c_E2!6o z_BrCLnzL5KhF!b?>?|z`*k9-sdH~HwvkV!6E`RYnbUHyBDVuqJh7;r8!!+Do%_#h6 zF!~Kb4w(iy$X~v6jJCa6T=Jv9l|L4U<466CBT2KHr7F6MW?+r*F1o%J>@Pu888pEB zB>}qp1(j^t>_YU^vfRG#>PNyWqz5#S9XKB9-Z`V}8_ghpu^oGa88oDW*6lP>EAlj6?Pp>JNA99Zn^6S`o3c@wX=1H@nGIXIxJzrS2COXX2xN>f=lWnPPz z+yjHTE|zy7`o-4I?qeP(Uw9CqU2xv7bI8vMzeb0mIwCgr&QHgRiQ+ieV`~JSG1%}R5LHb$#&qHEo*O-0Nd@46Z874C2FQh1sK>pH*$1TCq zzJL7p(cUxZkufy)lLPjQ$v}n`w%B^U&4})5h`+!J#{%=09O&{FX(Mj85ndN*vG}aF zecnD__6k(sPiI%Zr8|x*`h)!Cyh(=idA0AvuQ@oR*a@k)hGp_nk7>9a8a(|fZ1mbc z)?k0ppMCrun7{mh4u65n6>pT;#ML*VKqwL2oa$Rm6g(+MoYepA?>57u@bm!WFD2!Z z#Ar8xztD^fW)an`9^o5}R?}*2&tN{glT-NBg%@?WCYwhspaAyJ|Elw7Vr0B^e|AhB1#fn`3rS zG8M%>Wqjs)Ng}xaGWK(<3rPQEn}Ye?@5ji}Qr$nN{klrY;7K;1{=-Z&$<5$n>INGo zQM|$390QJM(p#N^>1Stac^;{jJ+95B2ROdiZew~lYPGD*re+#o-NC}QwgJ(dqHP>> zan0L_;oRG6YSOl!MpzF?_;Kac9i&Ewgf2ZFRpj2ycc!-tw-qZ4B-f#&QPxmi$6|c8 z%0#e(%d}{qzkXn?IK~U%n*MVyU|fR&U0n0NDz;2HT(oO2?)SA!4i0Xx$=b`l$`bLl z&J2_S5ZAQ(2(Or)DQzA!X*r5SKe5z9n+w%d(Mx`^rHR^m}@wh(-wenjpM!N z8WTc^cQ1ClSh`JadtE{#-4P@UG6VPw#CEda-q3@%CaI?CiwQ111Lu1JD~ni$Ce*Us zUzbkgswvbPBb}PNEt8PG-x49fbtT2n#WkFW{hxmwe{d3Htg*K>5u}Fe+%{IOVPDK4 ze6Vx3Zt+YYQNbt!|64!_NjAz$_>l)B(b*GRec^u!X7+K4mDjkzT%%$y5DAQHI-!ee zBt{V4QB1Q~e$9B_WX~$|vc;x;c=FTk*%OQv;%Sh-#11q>bBNQ29h#N~#8)P{_NI13 zbIR}d*1)4KZ4=k)7=ir-u?OA)h`;dJ#@~Cck>%%qr1IMPPpM?xievaECkB_-{0PXO zV)3RwNk0T}O-X50h$v&ml`^x=Jig>Li5wl199g+bcS%Z2{@-Q|2ceVQ+UizB(e6o=^k^ zL!xZoeDkuH8Jvu}A#As{9n8+^dg zE2xnv-hVgW**5KiU*p(n=M|r77!nOHS|QmyE>=^orc;SdJ|*0bXX#+wjgJQanv=6l zL3?j?2if0DqcK!HuvifJvmA_S&lWq?C;L8l=0#%%5s?)1zxBT|3MP zyx#{N<05iS?wAi|ahp7PtyTKg9;`cLZo>c|x>K}GhAy7YG17_~(u61eZCc1?OoO5P z9g3-BqIqb>{IHK4viu-UuksF*{8F6-=|U^^531ST=UtsNa}H$-E7uo z!T1c}V8o*fPA@X1`sf0R2F-BXDn&3)KNYDW2FBB7_nxQKy&s@GTB%l3JgUdzoAgyP zpVY7@^YnRtmdVJ71LEnGz726{ZoSvbe^+vv^WCS<5`z#fA3u3{<&i08S<7Sb0K(Ia zWZb}V!W!t}Y2?Km$4`oc+p#`twk~Y?8&s5-H_aA8GBykTh;<;I7UUsCaejd3*{L6Y z3P;|Bfy#*e`!$Z^Q|@xfM(Mwt3&W6g4(ko{z<7EdI(Qo5FL`9L*9HS*&4Eq(12xGt zuxWgF-8*7_4t4v=-hH6&MIDpO?r1&5!O5>)xibhA-uyR+u+8MM-nsm2c(Ai#8-5V3 zp>=8o<}cpR-se7ohdR`}D4k zwXhVtKN#nOZ8BF{t+VP_M>|+Bwz(FV>ln3$sgM6TKfOfKG8Wi=j2P(JkD(V_ev?!o zz_HWs@R0yizpPYzp;2dF#o`+1J2LCyArHMB;+AX9HHD)&iOm}$qUUwh4b;nBVZM_eZX<=Ud;DBA^ zF{Jk3b==I+pu2hp(jBct!RqOW#QU|!hy}|UvrWPd>+j+>j~6{oG9Oe~hY?jlbQiEe z4or6&_g;63B)6xX#{yseMk3_XaxI3lBd4@SevxiT+C4Ah0_m>b@xya&bDj2e0zt`_zR>JsknHHe2WrqOe0-09bbv zL5*cVbY~f|1zqcgKbFXx3mLl?3(+)g?arARUp~QBct@gbh#D11xvPhi$_CDBLKx52faNu{(3RI%%6n{Wc2X`*zkWOP z!LRFyU;;d{KOP~&ljZu(B2ZqlL&uzdy{7(+KJ8{efgFXG4HNj0!> z1IcRw@>zi8HHFZX*RcJe)IVZD`LHzk&PwX3F*^5eGBz$>o6>}g#`JbjUZbwH)YO~* zo8VIZ7U4W1C@G883CI5LU&h?lIQdcxl+7Y=UW4>UQ59HTa|j)I4TPs%3f{cw6;+kU zed{ZD8u&(PDe(EPA0HU62k=oH1VR3i@8Nw?CtHYrBQ(XUW}*HlQA8yNJ5cl#bm(In7(6Up=B=6TCJNC&jMqf#yzm0k+Q_I3=d zl+1Ptfc+(`(SIM9zqCP@zc5@g9(JAbsK#m=pmgjpSnA*LH=9e z{-b5PWGN(nc!JWAGPPog7&)OB8%h#)SCs>VZ#!UrQDco;1LiMz(B&`wk9AV}rc^UA zuxJ!f9WzS4?zRQe&(fziYqy@w4p7gt1MHQgKZ@ z!#7n6={Fw)IRNvQW9aY~$i9^6j4r=4HAG5HbzL{s?3Y6kug$eYUuWg{>WshU1o;aq zQ9ZpQ^*$?B#-G*j@oIH8!cVY_SCd~P{n!meT?T53AoYuwcQO!vq35uKj(z}S?qpI| zpo|9-d;AUNjaPC;Tp#&M$Gua>Z-Q)b*lu_GQeciS^bPC1);W971*_#%&gzs|RCvy5 z4q@BlBcIvQPo9JO0lao{dO-RC7=@(wzTb?^!=o_RCS-hjad9d;j3&bUizvyiSzcRcYYXmS9d#O$yZm`%9<=*%2^*8G;Uf zfy|v8ZA{j_eVS{nWUaunCT>4-pwQyy+4@6odD`|zFvwqS^z1`sp7mpSPRAjq8ST)> ze?J&ot)WIsu$goI$IB zBif`|E7|OU!)rhMKz_Era-cpGDfMl;LI?Y+C1cem2e5>*CpZ!XkY( z%v5TMQzck;+V<%`faxyn-s)}-tUG}-j#Iqr2c#3hiJT}L%nYiE`iPOY~nT4`cuZoHGBO`MaiBeHtvN6Zh?%0GJ%4jDg(=@m-`Vx^y4?P?~knMAr99^W_ zAf*iGdt|Eir>U4tRXApIA{+ADqpG-Anl#o(B6jMIfCKRrzxWYw-Ar9~Yz3rlCJ9Nr z_x*snI;MWzx6uahCSvlGzm^ii5;Y_II7huN4J4!4LEob(A?E`MBy}YIE2wg(c9IS&0N_*!OCNn$x8T47RY zWAL!kLtn+;f^@g#JV^5}lj?LkkF73#Yb(s?s-u`aa21;sVbW!jaFA>qqB~blfalZr zLswq2iqXKAaok=XiuBMV`?xSKS&Muxr=CcB(nfKd1;hbtZY7M2y}Wr3_6g0qSW?@? z+gfM$Wr^$U1qN=^e+)%6wLR&f+*R6f8!R{xsygymNCZ|@Vev0rn!jkm=%t6$$89N2W zHO=>yYal#**7Gv$ifl&4>oGj3>~m~B7m1jF%hqQ|S%-=+`Mz#Z%6Rtr=P6+8k`4{2O zlplQD(ji~>sPb|4@{h7}b62nWXzabgd&f0w_S>Z_@aqg#FDrk4Cg?e~CqVr^ieE0T zY??n-RP>!p9{Zp9n>MfWn1FGO`@QEH#$K+n_6jGKgbmKB;*V>4A2jNq z?)p#&DYxif65!rGfP11X_SG9(PT zxW?2Sadq%zEVJI&(mCy1Me~3ZXH_JE0-Ef@0|7h`*NhfQo}R&d>|B+1G>UQ z>XX#tX5Wt>f06NzB!Z0&aI3y}ky$lyIgij$$WcM^ zk=xBE78_D_JvNwMLo#>79bhEF@6O#zTO&1o-MkPbt5w>EN%Ni1p3cX{s*U2{oluBR zV6Gpg8klR$q#dS!a1A|&J#_RxA^V|RXm%%Jx$4cTd)dTN!%PE8OiY)t%26ipMW`Ko zK>dV1neU%$Ly*gId2yufYP*M&Ax zF&{sjCeZXo0Qrk(E(bP6dzPFGQpgdHSnnUA1vtTh@`GcER zFWVHB_jaBGWRA#L`C>}4al&xbXH}*Pp@}eG{%ks_x6;9_YKZyspt`v^g%MvVbKaVr z!^LN|jp^W7$Ou~ZOt;e^CH&LMn_8kmYCDHPVw#Wki}ML!m2)+FD2`C#@oaYW7?UI#;!DCP3+QzzTIet?Vb z&+2bM1ugV}oz$~BuiDeXy)-2>g;%IT&ucn#)TlR=^6AG z9^4#YK(%>afY|uhq-MFY;l!q1_p|$-VXxRqMIA8LJQXM62F5j<(7`p3egMwi!4?-X zOvHykibs3`spU$g#%MHzBIhDkl<%UOIRAXJB(XBNQUKGsFS=-nX?;u7dAsT)>OYM;8H?`&KwM+4eT>N^9f4_LwjQ-u z$%4F!**U0Z-eNm#Co!rN_q{P4%rzWKO5cES4IgxH4Wu8Cy#0~-l3HlamaW~*W1n5R zS-OEnGt4HIe@#g5j%&hJ)plmCtSJAIA}AA+TDOlQ=l+Dvm?ZK+%|WtCa)c8Eb4@{p z6Cx0)(b7`$jwAcJ8jq37XWeEZ$H=sC(fgU!Bx7(ODXZXW6@#adGsn`CF<^bqt*zn%UEMM9?1Ab(NY9U^-2PP1y~lSa&^iYd|zF5P^XRRJ|wJWDb( z#(<}dkh*!UR0oK^ScYUlN58p26ZE+)9*3LdY9$lUv`yYdsJ`G6oPyUc_)23BH-D6y z*g*&ScPeCSiRWZVo75A9pGZ;QY&0U@>HOTzPCSXWS|9lQEUbnJ(r+f^ehsAGT&;C# z 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�SUD-8_wi#<+{-dvpB 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Ð(x=QZ~nI> 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�ov!wlqa^nIaBA#bk+J4`LunKu()~h` z5UBg48a;YVKWVtyVu^;FeezgJq&_7?>FG}?l1q7KFz5Ez3+gG{m)nluJe37wo;uG& zLGo(0QG*1ZGi+*R@vSWwD(ExbL<|yL)#aJ~O3up9Es^LvDmAEut*^eXC zBS(`0&Xzxq{h^*Ztqf8F=P4-|^AuBCiEs#6$cK2X??sBwufr{nKJgau-tF_?72gKt zi4M9s-1$2*Z>3^9Zze9D!-8jTesT@d`(cBHuXMre! z>9R&-nAZ6X+ZUzxxEsj6kdBYPy93Ol_m!(EiOCsO73wO{VK&F|A;O=3S#cJ0xqhvj z=j?IVIlVU|pDL9v+XCk)2^jO#V8ZRwC1OIGO*>o0<9%k%E~%Rr=*)=^)(>At19cx| z*NId?&H+(MU`*iLSHWaFjez$hsSEdQb1Dq+q@VXi?LqP>eI}(>;5 z`j=Vs{)tyqB-#ikQ+`vJ`+u=O~s)2K7{!A`3e>PZ7YFr?QyJF&=lK zZHyJ~nYAu>6-WqVbLxj!`Mpe&K;ii9sR^X!K>ea#NfI>enh&nG!gJ*VP}&AR3dcWY zil`i}RZb6tc#1F@>kOQyl3~nKVgg~_Wx7$jiqubs?^fwv+-#8^%HJ{15HTYl2l6RG zWvxKf&W0c8T-*cIBMI9`(N9C(V_9h@ET0Fb^JFP2zlC@TN3~K2oTtiQ%u}XGt1MO( z4~x^9Nwtj(+PZIcbsXpybp;3E3K#=*UuDhyw4r#@Gm+zs4ck0^_Ku zKaHR%4; z8K<~wJ)3&WoMZwQ!E@SX(k&dV1mshCA@8eOyb`$v8cfnLHJ&5wGxoSJ?!Gls_a)#Q zRmQa*41(lS(QJcO;5?NDW1h0PZ!28s;dafO?KT{ruZE~g-sg<)wJh7d#1Tti9vvHz zn&V7}R5-M$++>_oNQrk1Woi2PhiC%~ArZVK@@C~0NIqrS+ZP1RQwcEUsqKhaw`Ws? z*yIt%E}oL6E9zx2QhcbJ1Mne!>fygVRfwUTjpsifv7h?bYg?f);?Qd>fq6nmHbVDV zv>~2XU#ur2pL){dArH<|jxgpa9IGV0R^)YbmZP{Its*oB`ZBhibD7a8hTPPZspGW4e*OKTYy^Q`8hRwF%ts`lQ8y3iT8*cFY<$PqD+8r^b;3k57~7 zs}JtG?`L0AL-~xJM7k?k-<5qf@B_%FCZF`Ve{~^EORt~3FIp6RQ1FiPmzNwZ2HFnF zEcWMbg(^@_-Oo?t0q3bQ81s~dJ*8L?X@^9XM9Vk5NZMv*vi(AmJVCl|mY&2w-Pij; z8V#MqSf9u={w`M>13Z4504A)i3E7=K~OZRWtWTA;Y*oEX%a@5*7;52MJY@7J282A(Ou1hSW3zFl^i)Eo-g!ola-cOb4#Z-wMj?pEaS;5?NU zJTAFNWpqtBeG@Vse~s7G^D<)`X}~y@jnBChpW1Niji3SRDgQIi%_Pfz%rcb_n&_EC z#27Thq89v|xy`Mp-Gx8@f<6g)vb zODOY#&=KjuX^V&a|I)!Fh@imOK?Din93_{(co=dVSri;Yhz#Tfy{g z;*y7O!|^V8#h2HU~w#cxo(5o+78zu>AQH zGTgQjvHuq4_g{NOLU!0$D9mb*=MxDqLvF9);pOG`?~7Z8-fmdJ|31v6y=0{{ATCUN zO^`4R>M6|95m$5JcWn#Sc%=|8 zJ*C*Pm@%`N&o!ydnc}XVr{I-m6Mo%vgwOX?*_{M~t`c{sr>v=I6To>&2$no$^9C_Y zC|k%-r<5t}^;#lBWOQl4y{*GEsTzY*)r?C|Nw>X(->02o$oZLJncJY0x1K(Zpi7AO zz$`PHWf(`@Q48v+MvcH+aGt9CM^9Zn=l2KXQ(2|Y5cWAtWfjuGWp?qYu`#8eztDUa z3YW3z#B~J82NirJ$mukV{3pJ(7LeXUt%#cVB0)c7P`#2@j2VSVVloc(l&sYZHaJfO z{iCNQD*hZ#?JHf#t&J^Y-ud3jPj}uS{pvl0mhVL@&yt=o~EX3CRRta-6 zaL-}ZBU9n?k5@Ty)~`>Rro1`xeyl=06<5ai8Jwp~{?SuF-FaEp{O9v2*1O>n18r}- zDD$PqU5B4~W82JE;*mDxP9DjBQL&7Z(dn-Auq`uz#IVDB)raiEPu8(}ddA*AEufyF@8{_kj@E zg-kQnk&*C1UDd>`G@JX=FW!sSL+ZYpHd^K2JQenDJ@waq2@ldC_un5-_o2w&s)^C) zU!st#FWs$Pa}7oeKRZ%IqM@@|jDggBIH-!$;&hg!efn+bEZ$to>vK3 zs$9F{;p7gf`}nm9$-#Nb7N+K>q(asnxZQrH9Vem=pCww163pAOQ_+vCB0PVNu@4XU zbkUQ2!0!;_IbK}MiNu{U@9oj22Xkc1BA>OBln8OLn+uO1^HZxdi4x#E#r&7;YZ*HEWMjDf)BB{8Wcpg)ul!HU8Ut3bL={f6Y_<0r?byvR!?-{(Em% zT63pJTs=Ypc zJ#uqD4S;&8(IyvUJ{9$EJv9QIe;KR&V>}hFE+0tpP87aQmK=+U`PH;M?_CppvaxmY z5n%++xXbgEFpV|F@F}ZdZ5^Vv>91$f#Cx}2liZYx<82?{()GNx73wMWk}X+qp0fP6 z`BWq{kGh(t3i;3HQ&x+Sg#LK5anjZBSb>D;Sz?y+^+t&gddDWDSHHzvp05RiT5dE` zvgxszUl5w3I?+Pp0;w-e(t-(Blib7&c_uBWr|wZ}2Z8ex|37=`4c(uTPhqdaRUF*^ zPC;?#sV!ryTW@BPcKE9x+J!HH@Y(?UrKf6-eU@ia*q6P|MH2jYCP%1ro+yr}%eMcr z*?1iHP_XhQ)Ki<9`5MEC^y?^b`!X{BLT@qp8QXg8mpOu8jtxb)`z zX8QEy^>j7jo-Yv32!h|fRZo?ad|$)sTj7grtkUO)@Z_sgA5?8C*ge32Mux%U1r9t%6dFocsv4>s2K#f!Lh4bkX~WI4 zi1xYQ=^9xZXtr?OsASe1t7^=HdaBH3j0l{kj{ezGcOmij5Af8OjZAG>JD<_}>mx=8 zIOuK>GJbo3UH4r0h#vWuQC(ioaR-NwO7}U}j-S%YZWx)4icii=h1+Iq+UDT};e;Ad zzJPj)yR=6ioTq-z(Epp?`~J$O+;9FVo{A8TIKNLLF)&ut+k<|cY2eqxWcA_m=EHs7 z#Lauym!3NGpv^J0kfZ3(*GL~B<`454qaoo?48oz+r&pL-p!c+cda9RJ5*wVSl>XUM z8h?zZ%7)+>yY4HE$J7z(@BM$&-DOmi+xtI$=@jYi2I+1Dq)R$P5G19$L{dpXx|Qyb zEWuQPML&wl?|_oL7F=&W-*YcaC#dEM`Q?Q8FSPvu*!gyIow&{?U#>7oDipybjYNEIW-L3 zsddJrVE`vCwVs{5%ed-drQ?Qe#+%{C=(F3gezZf^Dw<15*zP9V! z-gxGeNaPsTp<536oFu0=Yz2fwQ0N$TC{g}1A)AHxi3M^1q_^{e8X z1ejA)(47*58K?Gwqn2-xgS7_mY-o&YzVr<_hQz>y#RXM4PI4LLf}A=}7wqcB>uoyb zHa{UJAbyHQjQryRF9Y=}DfSKbBnFA65T`QfMi_xPB?H|lu;;LFo@cZ;yugs(R}!ynkFUZijHt^w+WFKz!V&YaSmZ`f%Dx zzFR|y|D4i`!vyA3KXj+WV8*GSEl)bbE40yEB0SuHvE_X+6Iq_N!Sdi*4`gaVB~YGP zE}U{e!*yq~I=tHu2N&{c@Z5nH_caXg4qiF8G(z?_nY z?ow>fgZm8I<~JFH0Hq$$RJ;k|QzF#oai0mvyG2Z1r(Tg^->o=Vc+ zc}SduLHg2*FH(1d*|bzOv9W%sL!1iGNv#Lw)HHOb++oHk+Aw$Rw~aqP$Ev3k)+M?M zji%(Hc9Xp%c)y!Ow<&3TY{kWuW9=nE73URw&1F^mE20<8AlHQ|rx*^M-Rf%{T_`w8$L z*H*seYhdB^X?HYpV)Ofk@;mMxuAs=QuVeSo7R0HVy2DXmPNDou>wR{-(RcQFa!Vg| zH)3ukqg^=r#F@4H#!Cv<iWBH1`bzTpAcTnl8wm9w?Myyv)4h|qrMLGOj1?JQybf@%ULGND}s85|dQ@}JsJByONuSI(y zX_=#ERfHB_so39(u=gMfye>k=mC1Tg?d_l2zV@s=%_TW1AwSV$wVTc^|vN%ao^-FFaMxPba*j>uqrF~Ii=)_Og3md z-(-zc=m(m9OMDPXv+QCSXo<4TdauK+=-9Ulr)?suaOMo$mk>@{$_C6S6X<#BgcbB$ z!@|B9syolMJlMShBY5PWzSv+$^pm~DctelgUFkWT5ALIga(bywJ-3u=k!*MK#&u=I z{@&MOmY=uR7$U+e%#}W1c5oV1l=hNm~pD(t21u@w^B@@kEvRrFW^;Q zdJulvktbwam@9RsW^N zr(WBa&;fI52f9=D<6y<9+D#P$SL`njI!?saRX;FIO=)7Sf6~|L)yAgM+6C_;>m5k{ z6#jc`}+-PvqQ!VksaPds69w_ z*3XYWwjaU=&HFwD6FFR={0coh2z#Y3K~q5L_Z*F_&{L1L@cQ9cSg`0I#3{Iw_w&G< zn*W#jJmL2|;LIN-EfLe1=dI`%RE#THy7p2JDBv_MywJf=|NH&>Ss~XuFJdI?rdY$D zubYxL5)kW=)pM7ZlQOOvx1tN)1mYB*L**M_P8~pZ$`EFp`ZMpdyKKK!I4w|heQt>H ztkvY{N58s`?%EUs)%p`qJ}xWrKxPa(tO?0gX}P@Jj{X2Yvrj8g6jHzNd(PZDw!#MD zlvB-E05GR8{;7GNQ}iwAfL}PlS(1~lV$D9)OtV2R#-_J7TThG~J;mQX&x6?bqeoMH zyn?9{H(NqypZxq%OQNUH#^E zS}px1#3>Oy27u?fY(mdd9wV?a@1qTJ7EXuET6 z-sgtEhe*3Qa!Fd!;pNU%7|9oMW#I@{t$$}Z7oo&a)!Yx_l!P5W5-_L6p*uAMGfqv~ zsNZFqHYs+M4BGm%B^BdBlDDv9zp6q)BY8^q6*Qj8_LWH84GVlj66QgK@DCh+DLru3 z@oWmW%IAsuPRn&u1#!x?QYjahQ-aX*)DsHOa}5J|s%$(`+K?Q5roTpzzJp+=nQ%-s z_tJ_0$1Uc{cqkf_r=~Xttc{!~5;jC5i?I#0WVm<-6TDc)ZEd!|5E4sSe<@FOw0rEA{ABx~^*OZh2PQ!T z!XW`e=l%D&Obzd`e8QIh=BbSw%Ps#^BHn5AiT!M~MXucOYh;8l-@Z?E-48Zu5-Kwx zPQ9}u5CP`YG<2t2<6&jqr!AxU?(1THjh~kS#{xYiF)5)$`BE8y_o2`+`}!=%scbA% zRDZ%ygMM1d>(MU>Azm+D$-F&@OAYi*j-MT`mx+fsrB@EW2+S#7=uTn4j8lK==ylnm zt2K(jidD?TDtuFTj8D(zHm+(#UU3V8*F{9o%B1u-Oka|D5q=DNvcH7SxU=lgyKVM- z#(kx$h>yb);?$(8j3qFq9RH;{Iu;{*IGJJ1eViD_X=UD~o9h>deH@|@HCvjxsNpwe zf6qsc$Q8+cz*ml-!?^rbIA9N5#Rr=L(K`mWH@7adN{%v?1L9PKj&B+;rw*X!DVAYa z$x~}e&a^y6Y9wL$GK8pK44;Ui{-6(@DybsxDv4|4BF)@fjFh+U_K1Ysd?y5)xwNZlXq3r>(ICHe|X*AEtaEx6ZJ^Ze{FS-9x> zd8DX-$|#q587*SL>Oc`5C?ET`uHaHHJ*Awt6{(+2_>5-cbDgnKV$1imuV-;~pam!LbvngA<#s`jqh_Rp7W zm(;IL2K$MssTW+Xua+-b9^!hpcwC=>-aGBTtw+e))3zSKC2~;GmMWu>;H6ZT@-OJJ zZ%43KeAL*4ICWi-I1bDyQRq%dlfulr&o(Nf&k~6-!SXC7mLy2$;Z5C(lhR+O`benN zBjEn|9IxZjn;$KkQq+@hS0N#fHv@Q#^g@-t`MHfm8%qc7APN{;| zhhSj6?@t|F5WXXLB`7c1`Th4@T!2YSzf5|E7HV=hwZp*#!MhDBKtM&)|7uL?% zy?GC*kKgzqST_81iqYC%d6YY)ivU%bOXTq6%czUFgTXjwQwLmWf8I~)f^~>fgbvv9 z!17cFbf^4a#wmOpl{u|!)tdTNB_!SRtk97uE}eTOau&;2vG?7<=ZVcDC$fKe)BV0F z9Vc2dN9Z(`j7`uni%v28Dq!Q4?xgV|mg(kx(3F+q1q1ZJF4F?gf>du@Qn@+Cz{ zA)VJ}(c#xQR+_;#fx1HHpUXh+omVZlc|U!{58eq%_4;geKSJBjwc@{E-0@kK*nca+ zawYMg_#wm`WJNyn+Z?zQoJ{!&vTgF-vz3O~ya_H#wib=uod&H3h> zHIMBJ0)kt8G5?`rlxA>W0+-zrdtgp=Lw9NqW}HgkQR|+OzY^hM+=;KQq?Z|uBf;5Y zNH#}~8~L6HUhlhBU~LRqc!b4{BYDg8a0~tCFT_^VlR;%RGRDj*I{j=~FK}N%dBtcy zFsJCCJ5>iWPDM?;5$)Wgs30${EM0Hbhz>)iV6xNbC;F7PmpBV{id)p@+70gNY7JrA z#G^ea^eG&=c&i(c#(Rqgn`l@_c%M(e{ZkCIlL)}_ls|N*0%69f;h;Ol!nkz^mCT}T z53_iD8f>amXH%-??<)BTrGWdV#+`X-HBs`7AhtYZXS~g!x`+~1QR7q4d#8bNR`k5MNP8h!yF7y_XeeYrYlVSq zYSHN%yIQEHy|EX>DT1=cV!)i@gzi))8LZ6v{?yT($?$(Iqo?c6=i(L+qI7Gf5-*-JM2;0btdWOnDG?1lDSb|etj~WeeYGS8T7noD7 z|56>jA_ne!m(As^`skWrpCk#6*2O(@1`7O2FH57{wP?z}{pfVfmP~2Y?3%nGnu#e$ z;h#Sd4@_3aBAZh-S4Y+~JIt;@oFdm&I05F=C3L3>2Vf>ojk=*2?!YH(lp(5mbsSg8 zjb+UgoKo(o0- zQQSl+BGIkX#P5gpu&BSwDyLJC_Yp08cihb^H*bYFg;RzK@SME+(4F!oh9RfOpA?y) zhVXOw_pVXiEs>Rr$hRYMk}as<`VG!go3CxglQZwk#P>a@b3Pe%t!T9f%KCE(ZwEuW zb>_Fk3RQ?x%gY}1z?|~@mz;{uMro5-ouwscdEbHma8$9ME%i%NXL|6eAy?4~hUnjO zfBWXS&kMDbzda?p_EHxh@%7&=rp%T#v0h_TBqIAJj42Fp%1}qf8<8z3#H#qiB2#$y8%hSRm+#;}PCdXKw<%YAIDYMZgm-b! zdVZ*flNN79q9b8zH@WlTsc8+wsbC5=HegQigiL9E<2FasTm+vl4mvj%X_p&PU@JE}V2rlijPSN}V za~UKS(0eC*<59$6GQx6;9@=a2kn6W8Z5MN+bJ{MZv2va72aC1zAx=HerFjd?DR}5k zDZq?VK|kG0+tM3J%G4Z=u~B~w(+r5;<3^%hJSJ$Ha00vfV*53ownfmh3jH2pNdg?Qw|N4;Zrg+Du6jlC~W^=;A{ zFqnln#asF5H87_ppgZMF0yBBaD>}kW>ig((++TCjA2pOJD!s&9pXl~Z^S(YL1?Q=5 zrM|Jivq$-n?TM(}SScFYB+nkBe?l{RCYfIElK-k%1mct`je`R)r(&QxMb8E+PW@T$ zQwj3j)Ell9KUh{nGQInd#)DJ}?+Yt+c&Z93XgqJ;ISjDf(Kog+BM$M~Tnx83{bu!U zw$bBavm~!!Q{br+#Hm3(EPr55Vaf3>HOcRYSy4R(|31La&!6wxfA=?l=Tm?75B%@< zA;a5vh#DT@ZEeVOW=7>AhpJkdppQkNe1Z>-II-So{F`fR^Gt4|sU~GV4I+Gv{a`q5 zz{Zl>*f1aGNHykEU%pw7)B>)fpW8d50CP$Nx>MRP;}qYNY89`hzG{dLrmTY5(M1&g zUh&5LL>KH;t+R$-Ag83f4&KZopp}|G+ot#DAZb{*OaEB)*@JwSTmx!r)YmKm-rzd= z&+>duU`{PVcWM}BoKoqHiVDTtkVKo9Zmbc>2>-;_fF2xhnaGCtJiryazYm=gVK!d` zW6relJ$i{Bzw;se?&&o!r-Gq7)elQf?LT*kGpgv0 zOvmI?zIyug?XZkvG+jB*Iui{p$PH5Zo4ZfSf+Gc92FQI>ATCa+JAY(q-eInz$@k%^ zfS@lk7+gmmAmD5R<`nwBh$@cVjxl#Q{%LbBkIgn&%8QG?Tm?bKN-ZSX?p=JU`|Oucj`0DI5kP3 zW^$_X?Pg$lF`)P7NZ+bGUx~b34N)%_;DZ@1>5(Ax5iUUkkaos55>MmlMcezBN3 z0{%UIEsx08=vibCr(`RJi-0+`0NtrPm~qN(HFB|eiy(5ckZ!Wus7VyjraCk_nPtoo zZS(9f6P%CHMKT}I9kJI@d7&7pe_+1m5W5+#dAQ!@XyCbb$uM#Yaq0_AumLcqLZLh5 zfCn>qDgZgS$}&xp%VWNtfAakl-B>Q$VzrB2*pt0A5%9j53KEH*WDjvmf;84bUl;9_ zRpf?_&e)alvuC>tbkppp7C@XL)fv16<`g0Hd0#d7{R;zmO8SGx_3fs4#bKnmcULO2 zi?TgMeEQAW&v~whJbiHAZgYfb(PP|R^6$Ja88I|Xtlr(q((0Y^M0{xf` zZ=K;`q%gLZ#ZgtAEmO^K2^Wegv%RjAUJ7VDvwviqg^IP2{-!ugq<=+V@P*9S*{Cm4 zyA(rtjWp9VJ94VdM~)Q;g7^`m@UacYi$$oIf@BNEN=Ke0xFukQ#fx zcEhi&<$uO?Xn%1js3CFWs|&{rXuymbB_abPgZw2q#BH(nsOOtSvnIs0Wgk z5T}mHzQzK}Q(vGv^#x{}idv1yw}+2T34kJl$$< z`Ijs|Qq>E5%ZU7x_S?eU?3+ZH9d}>geClJwdG;WPQ~NY40MDO#58bJ3m~kq46(xI& zW!X?fwvU-D%v10;dYb|3xSRksXAiLycsze0J{pb_q%x1(Noe^(JW_i$cd9HjWGBAn zpto=|9#Q=b;*^RWx)Ly_D4{!54DJhrfjrfL?s3w2F5`eF+MydMs}!O~>f!x0a})Q8 zb0GhrAjm0Yv$sQix2=xcJP~r4cjYhN%oz7Ycb>?aYK@4wy%)7G137feUkdfSX(NjiK;Q^>r!J%Sv3?bI2i7JdQ@x-@Q^6Sj{# z9@UlE=_xGnFN3@>K7sE;oh)SzMD9e})mmC6uVpK&3w%-&uku0sWpf7;t;oS$S7V4% zWYjj&z~+7N(48{Cg_(KZ6nvUn2GJw-jb{5ga(`Vu>_*}EH;#U0TlmJ0?t{j&H`Q$% z@9yVwAEfusx8h61G7Oh+gvvR&FZ#FBUNAn2LxwojYXV0H%qh};sXj$3>g;W^$?}?M zj!Rhne1{9M=Al^k^4Grf1GrrFt@^+9snK_0@HB_&%XqBhR?|t3_Rp#;pM-u>63qQh z8JE+}t`ZJ$D$hW)iGPr*izottM zd`?eim^6pBHv$@=?5%IxzUSU|xV#X10)Yv+bQW7;%kU`{1NcWMA;oQj@7EIs4w5K^=2`<*;j)}!&0lUbEVR2#*r=IGzZvbRdVw}kSmownU;IW7&Sd7D5l>38|2cjyE$F?|y4qicCDOJT-4aBH zHk5S3v@DUES5u;UZ5!%XM|ybJ2yrUP?ve(WQ@GHb8iyIDCQ%foeDG#pjrv=Yy{WQ8?vJiT(A4t+6MJ2SRImIe{9t*Lz1KQqPZqGo;_xNe+6-> zrHmTj{=PHlPHE7?N}d{IiTW~sRlNPaN|6cQ(X6p}`aHCT%4LK$+pJox0+f##SP9C! zcxZ)jJ{W5{T^y#89Es{`jnv8|yLPH?)qCpRfjISqMw<(mQ>D zk{u4OV|bK9)qcWT?0t8dX&{|4AV(=cr2~D=*pE7FLk=7vCgu02quWi~Xxk}px(t^B zYzG-MN%!C%8$+Cmw5at1<`nI}l&6Ajoca%>8d(^s6Z03X$isKxey}e^dayD-XWT`Y zKK`4hSO&DEbT-jz*)51XYkhwm;c2<24At~m$s5Ri-@^IAwE%I-$NnWIFsBHiJ4FfJ zX8{BCDeWlr_20M|GB4)f=|ArKHJd*BM&i17VyiFPPK(+pTn++G!1UCj+Kr z+vF`9ueLKp&BC_!@xkZx&^p?q@tAd_Y@!w%mwEB}lphPy1qi&Xy%SeX+>Wp(DhhFm zh2|^3^E@k|JB0w=w+aJ&o@$ebH{b8O%Z;i}XB~c2-=rS$+mhGNy2-X%+y9OYa_X%8 z1EFPS(lsRxCeGn^?_CL=4wM3@f}LP9f@b1MElVfS#wo^J7@(pP~)$ zRP*J>w$9!S{=S&4zha0V`lSb3Xi}4RXc2NBqpuj!vlTnppZ3?82_J~y9%@X{_ayRA zJW+EJTv`Hq$Dp)tg$(2-bX;kGLZcr(73tWPmw!iyB7% z98IIUR$~QRuLJ$OKkEy$;Cjy#E%;ogKl_~i_xB-3y^NTC+4@N|qEF0Y*(3G#MNWDP zqvNOJdV6ih@)ZfBKi@xA;uAB47IT(LhwuRjgkoM){u{ab5U23y z@;HFysa)tz)xnHY92Y2y0>_hyhSK_euSw)f$9ROpsXZ%4Z3F|A5#d2jr5vk&x!1Hy z>}*5D;c$P(1Lw~AGwJ-(<%?emx>;Gzq|>9o{Zk=&_N&116zf0bRG3>>ap&BcB*n_E ziVpF+ile0~n&6B+@4ds@$3h@a2ZkLF1q>H5KDWL25?lUB1O0tKYs}h%hut@{@{ukQ z1Gu%|K2L0<14>{{QT|K&X8xRy?%m#eCqhQTy;3K8GmnCk+c%jh@a<1?5wzoG%)k3) zT;Cwr!z0{-Ps&@4;TlH%A;%~!e(t@Clli<6e>{(Q(ANXp=Xn(H65x3WzyBqt?1E#2 z(-xlR_X*!Eat{>sz!{Hkeq^s+XKz7u8++g4@A|DR_6?VW^Fh{ph6-!cnM_BNrq%I} zq|xJA8RJUy0wx+<Dc3hVUH~u>AKvKwNVwQ%#c*oJ_FwY~9z|iNuM_M|!#tt{lDS|4W0^xbKl! zEbS+~N%+6d!Qc1C|KC5YUNpZi(fRZLsf8m5KYY_3&xwUNJNv?73~gT|yPU-kFBRdj zuH|3Pr)ZyZ-D2GLit=uEib1~mp;fa+y{1GR`RmX-)^vVq2Ay^m;#7p53&8u31)w{1 zAH2UF2G;v>n%pUaMAVNthi){uUuo4puvkT+K=AcDWb86L76JXf6R$ZbR6C4VL|E=+ zQ=Br}PdMyXDeSY^`ZyAyZXaLN>p`3nu-oSW<`g{iJQZ9D9`{Ah(hCP(qfykqxq+dg zz0YQSz9P%apUsh|0-@Pi&qvAV^1tUp$socXfu`wO8fR?YPu*hSt`cVt`8do{BzeyBdl{>qE$>b$eKSy%M5<6^d&9)CS^IYPmxiuspQ| zecoqC3;z56@ADKJM}+YS?yMMEZ)!`gsFBPq?Yt-Ql5ErulsrC^NYHq?;u=2xxb_oqJ9<}2r) z*k8EPd?+vd`QvchGgAaeS&|{ zRrO14&x;D;lv29w4X`}L^Dot>%m$rf9>pq(@Q@qvIiMka=DE8d#QSF3{^5zKPkdtj z-}k~U&RShn8l`mmBL@E%9Rtngq&qu>K_a`MkrpTGG?wBi5U1Ac$ZCK&g$&&(1Mq$@ z7^qLVA*$_k9(rx6_2~PqeKYzsgkXs(P>54sD(db5b7~#BQ#~-_R2j#cgY#*;#x^fI2lJUZ|Gk26 zuiTM_oyo-j!z*yk*et*D$#`QccKZ}_g`n7GtazTlkf(ZQ@r;3W6RF8Y&H>_-CyiPG zFsD92cd8p^oN@?9K*Vr-{GIo*n4~#6x>u~CG*`$}gkQBEGbtEcpE9wRY9)Ex7UoNz zk?T(<`3ScwykW%&A>=J0WyHG%#d@XxoA-qR%u^E3^OV;SI2Zo!_xG8Jy20bJRav#Y zmWoUtZWRx4p?oEs@b)A{-qBMY6_lsC?3S>K>kOF^+FdDb$l!(~h!;xxXS7`_zFC(V zL=KHlL7XzPCz1x{6gqULzQB%C_Z8vRi>Kjgq#eZ)M$nP8&dMtd`d_R++~%-p+5?TJ zvXNc(=X&b~S)ICEKV6P^1Jd-9Np-dBSHcYHHF*wCw;)bolymI?bLt0lr)0qELoiUE zvTKU!vuI7VBJkK4d&qoZGmLZ)@W%3jsfj=IflU@DPdUa)$X?Lbv+oOM7E9=(y(&^z(D#u_5g7Q?)13qd_JxyLrw;7A*WYPT&Q&ojaWr0_isG>~`5ARc%L!5f2 z({cqYPl-TxD&-JXoRUU!&Uu^j!f<_>Z=w)^68CMm{x%A6SjjeI;zL_Ti+ADR` zGpD;ks0y9;Eiq#@_`7To0}|KU)0s=0snovzSD$ix>kP~(Ea*--mcz=t?@xV7>~YEm z!UG0bV#$Vl{5+r5mWWQ5d@))fm|BrMsUr6U9hVtGIsGtp-9M7DTOH&wD0?%x;`>tm zt-I(Hu`nse_loFBRC@Xk>%3x%e1Q68{qc(^_$>4-a^Dg*h*J#Ia-_hVYJlz(czz5E zeF?+T$Q&E@2~EAezdUDt;{NDb)mbM$EY`icD3Cpx5$sg`s#FN=Bq?@Or!+S%#lCV~ zps=_z6BT3Zcaxr(o)<0n|e@GL{ME*9$^{rLzA|^);J6^T>EE6(d^ADF#LKZi<8+C;!2JCgT;QMC6yL}@P+_AR> zyqX`t3%ZQ?6xoL=p-$SV$(M;b{C>5=>j|!-o0MTb0_IdQ^gMM4GkHp-IAVXr5iyGC z1-FIDK9X1c{9QdvoW;lCqu(6_!TV-X3lU;BEtGM~P*K(oW~(bW87T`SR{5pl*4gNp z7xguke}n7jyENCiz?>R@?$k2Onc`J=e_!s_$_L zK~5nmND|`c)kenFCmHQL6sw?B_Fl)zd*y34k=tX28{_)w4S2mzMAsJ=m{Zcwox*{c zJY^>0^k8`8*LyC!c$o+$7p`l6N_B#^FU``&hE=`bb7%sVHnGaOC$7nks2aa>rJ@1O>{q`&`vCVJx*H$6B*vQ)Y^x!u}h*PGO zd)mO9iihr03CuXfRuD1XS2`DFVD)s&+j1aHC8;p=GhbHp)b1(GU@RyfN42tA1U4qL zp^UtJ??N&8Qq<>@oh_3b6<R^?vxAxtjzoV)X{${L?Pjc zZ6UlEp2RI~NOR%7Z~ct0mT=0F;V#IjF*Th!EnTLWgJdNWSxz)yLKsH=`2!fFjMQQ91;30&~g-x>Jte{$&`b zqt{j`PzKR99ZFKZx)k?P#8R+RM&o9tmQ5i)5KRTw(Z%B5!!fn46(oJQcbBH4ukFWL zkahBK`3Lu)rn2W$Gu~Jbr_jo0P=Pu19=cO};Cqx{pguK7A90j3i}K)zAyvZ|(OIs< zDr0o>Xkm5~m;K#DFvzJTc_v>}PpaZCjb$Hchve(}5opbVJaU*dGMAH4#IbLZAx_EA z-9!L$>I-zI$YI8*=xn%vSSydNQfbo|4+J#!s*Ax_hO|=sC&v^b(>NffmUYa_G7vB< zLOCAIR7%R7plMD1wn8p!Da#T`P(B?eAA&eFuZwa7%qb=4d1@3~&wzox1o#w>AUdnA z5=$A}k=J$`YvYnA#Z7TP);aIDue|-+R}!X8_xfP`bz#(cDyfZYLaZr{!U_JwzD5hB zp|NCjU*$TF6vU~Ic4aQWoO%h}sXCZ(YO+X8>_~%=k4U9mE%esHQgz6+kvQsx_OPD4 zrVqToPqBt?{^NRBbb!w*1>UQ)Cmn|Mj|%RO6Ul$%S}R4cw0r?^YO&H~2AEUn(4D%0 z8K>;_qY#BY^cTbls*b*f&*HV0X2Y8=!mjx>9+gWL0&*%LwW@=DvwaLRPyg)xu2>dz zpVMYyyrp&ArDHZHap0ZT5T|0OJMIH>st3AL3ozqUg+KBgr}@k`Zm}x8?Wa^Py}#;5 zeUmFooUb1sB?8x{m^S$K(l=?FQ8PHl;y?f3rB91aHi+8U)F%(oES{V{S%f%6uN}Y# z%qd;yPCbPkr?#9z%@Z`79~Trc&6t0Sk9xZOl&^5)wb%zMhFI|WZEWM+*Nj6Ve6>Gs zFXNnF=-;4Yk#jS2N)=G4B+059jTu6m>bENg*gs_k-Ki;Ut*W?db{g?#pm|@iaTFP;pqAZ-%3E1!G?sv0h90MeF7l(ft2XsJ#P}ro z5U0v3RvUmh6$;%ccN)<97Y6dwpZjLw&bHhhb3C#Xi&$1sTJB5TMNzf?U{lV7E%XZH zl&bd)b)aUy-rI&dkE#^05G|aQLpQh#vfp-IGE3=_d@hDKWyEIR49uy8m&oKM0}V>w znH<2s575v1`@Thl{5#5j|NVd8=UMUL4wFW*yP_d`Dz`{>pgkkFA!*vYaaVJYT?7?E$iLJV>0q0VFsFsC%3JJo#* zdahw0PX%##5Fs*{t8~3O)f<4*^5`U=*!<=)!Se>o7Qc)Il&9j@-9L9N#^vS-P9%Af z1`C+U)AR^Rj_EQs8ULuaYfy}bIOSlUybml-SweRzuM+&6{qOrc-QZQS^1Ws(nd|lD z{NSGSTL-)G$=Va&QS7Txk^B2SK(?F1;dD+q=~f%zKrlz6?oqF zmUdEsB14oQ8~@0OBT>Hr{@fHRcP?Iz9m71N66dw`UB})Y$4?ZiRW0B z0>UIC4%jI@`j;5G3jtjOXUu(N$630>T@L!9bXXetNh)boFA z-;6JngaUP#!yLW%m||%-QNVl7bL{yz^aKkRRrbG5HPCaE^tt8#+~0WP{A^>AZ!nOj%y>NtS=t|4TQmCW z)$-}tx{dhanbM5!D)}^-po8mEwg_n5op6L-Nkf@bG4i8Q6faBCEB9Or>Yicte$;WA zT!lErWjCS&%qa)xd8!567YGA&bZuOPxhJ^Byv~&I_%ctMNN&tNds9qx9>_Tib?JlG z`{uFbQ+~?9eO=vqN!m$B^Gp7_iF>Z*usSaL+O&21TYD}ya9=`9nd1*&PDMh`Q)?XH z`ON=*-;AAg1y|#edZ#bEq4f{ zM}zwk`X70J0p^tDzqH=Bw&oc^8Dh4+x#4ffaVR4@`E_n%s*ncF4VM|8*~<0rd}af) zFv?Xn*k4|k){3m^i};|+aIDNbjrCy!9NVbR;nf zPB#}m#?Bt*Q`Yr>k*i?i!`ZEKv8FKbssQ-B1h`r~ZN(O6>PJ0@`BE#|zdalm3CME{ z)r^L0sV1uSh?yZy?N)qC0_Ic$bf-#S#;G#(NafSBox^;MX`6)4-JNPn-&+q|)7pJ06-p^jKF;;(%9ZfO1?8|}(&FT^Pgx}8N}PVGW> z3U=yK4leNgwMBk3N!1mv;DOKS2}Wmid|LwNBFJ6N5m8i6 z8cbpPz1k`;Q;>3(?6*H z)1VhF{kzKhX>QaLUGTZXVont$7RcduYVroQ7VKMvmi3rMq&OPi#iZSC>=WzfaX_3B zu}e(?=9DgUrxdbbr9Op!N2SlZM(4#T%TH>&Ly+V z=rb=aUf;hjruPPY-e59M*xP>JvPY?Vh?d&};#7HMF%K}OYM?tc$_XoZDk?JCH(O?E zUN|ZCrLZnGo5cNHqhXRQFQGdM!$jad&)r&tELiXu#%^SyY7Tx5LRa)&-0@2C2|&hOFGYv z!~%_H`I9l~)O)r`!a5_Jf+|!C5{Wh?4GR8#IjMKqBh_+|mmyB!>RwO+bIJj_Q=Ty6 zR7adAQyqy!q7rJc>Zs_igiaFwC+K)JpDofU(PP2qCBTVo!yW4a|@TJ20mVp*ytzGfw^4HzQ!y5<2_rG!N@01bv0{-R6^s zq;~ma(`l*@a6h_;lxmc*zeYo@QNQ8PzXXtmM|es zz0e-<0p`?m=uV~G!b+a%IB*-0X&VR&m6kR1TBiOS)oL-ar)$u4y`o@{%?Wz%DBZm| zYFY@5)_Lhnm*G?>Q8fDnXETR0$e}TpmmF4^^A4QlVYB#NwiDSsHFdPD(dw9=(Db{HA{o>U~V_BwPfE}j?ex=r{Ol&oi)tCXBZJUP>r*v8$ECF+B z>tCv)V`-sG-^|={3q4K6@i*p_QqO#Sl~#XOQ`&Z(FrroZZ+-ZUs9WTm_T-}J$%886 z+P%J2>!8nGu1_81e=5;d<#*GQ|5u;Vr4j??lm~RD#9+oLT0iG;<4Q|%m2Bb89IyNY z$>1T=EZ4$8^~#u6c# z>;mS2IVBCmKw^jt(HJeb)HlGE;}tF z^`mEpCzJMw#4L9FCXVUT>XIS4Eb$p@IuhcPLFH3wU``c6cPbcWoMNMkR1CO$YwLU> z@Ii~_9mWW1!!ow7DjF@)GZBOYDfn!)j*)yAh*e-e63Dt1$ z{e97f1CEEiM8Ar+;Pk~;1rJYHShuRq-rFAZv%)7DF4_q~oYL3X-vj293v{Q9V8*GS z2xkTQpC*ohML!>&^fEfcHKB-k>6&t}(`RUgfzLhc-N?`Wl8&cF^iv$A!GM2@nw_NY z%MtpZe@Jy$?*uh+55y^YJD(w7PRT)c3YX2Y*_ozfYP?vU9w{o&5^H>1+n#C6<#%+9YLHr;^a94 z=2Y&#H1GS;B!UF9t>oW%9~>D*pK>?8@9fv0 zhR3>sm)liFN*aE(N;cL#hXo$>925|z2#P%%fH`&hFXbr*bNB{T{kE(sR?)><63pcY zf=^4_WjmkcYNe_^dGH~Eb-Qo0Pp zsRJFhC}2*#gznV#4fy*91Lsezdc0uPn30P+0CFoAM=EBOnFJVDJ zPVeJHvcHU_Z-l}M!`X@{rv3qA;K7MVM_CtWJgcs}l|o#Td+QbE=PE_+Zup()un`hV zUCh3UEa|m1Puzt#Wm0xT3e2fW=y__A9aiRjY}?VF;7U5Mv3?1E!bv zzR|NFb-8sqPjY=>SKipQ+j!(=i|z(-N@Z<|ADB}<|59JVpFU68@U;!y%#(ESXBWYD z=TS=nGf?)D_KGj{F@MwkU2p&3cr8KjY2L(Gbc`Om9s%b*w}RZR&_ebi*|W)BSu#Sb z25?_O*Y+D_U{0z1OMRaB_G)6D^Y*4<4q00xt*fuS$v-H}WR#|?*`3O#(i#2j^VBa` z{9^FJ2{UaeXR1g}2-STQwS-1d%PB79G*j*k{Tn)WaQ_rhg_j{Pr%ItarNasn^S)ErjjGd~o(d;C0K0B#mHim0tZ`^ zj~P!FYPG62Q|o_Bt5_Ik(-Axe-`{stI7ov-7_gkH(AY}#ndPxopxUZI-G^o=dEQ&X z={kITh*N^))&;zS*Scf<@Lq`R$F98F(Q@1eV6o(Dm z)?0M9q)}B`QX$2DBl2oQt76<3n%T}ecnR>iEK02O zNln3h2^_In8=IKiyVGjVHXplY%XJK8sv)EX(5G24IeMT+sw+X9ns$(82If>fbf+?4 z$Ehf__%~Nfn_LnC$UW>*)sDyGrtHvR5MVF^A?xs$*5h-6aQj$gNbt?S80 zd1{5n?GRGLeG-s3!2I;~Xnj=3@7LacPU*n40dp!Ex>E(`u#%@}Kf2*uCD))c#;yFQ z{IW55B9&}z!Sf^|<~ZaGivg6Um?||Ve7rla=nrFz>!Ri7^!e0mzGK3XV0X}*-Qnt4 zwud;C;81P@%&BJRPI2eMN}l>tNAGC$x~TQ`iJ_6r#IFtS*-+vP!1LsKjNCat2O7`% zAS^@y`uK2$O5YeMj+n1lREt@}?&~K5T~+@3RQqPW%4i8(N&_Od1ki@ zbl)NSyn1(bs<$ILr}HpuJ1h|MrZGQ=Fm?L=!QQTDhn>#EQ~5ldyI@*ICcEY+m) zyi$6*ae4Dr1>)2qZHXN)rx2j$sdh?OnfDEs!=V#yG(P@FwC+sh#$iNE)Gs{tv5riD z$qy|G4>XTwbj+Vv*b33W=@DlzR}iHYH>BLY95$pNA81}_@|rebf;gq78( zKIGrBMv399n>z+_3fsGKx-C7Nx%{U!&U@jN!3^{yI?Bq#z2O~)(-u6DjSz@ar4=GW zz?^yvJx>+hhn4!&U|NhIX(^f~Q{dy+u+OY5UP=3K`N-B)UZ=ywqVGZP9n9Uyx1JZ} z8F0O%J6P(6-A#F;k>7EuHGYYf+b_|*Rv?5pwMv&;2FxiG=uTNsz=~4`d2k(W)d{cp zoaI&oHX;zewl9pX86tSyoc4eA2cJW;`GN0fz+Vw%+YgQ`EsBDcfP4P(M~VRElst5&aF}7msVJlv?Iw>EoSGxoeGSDWimvF#b_BFV zZ0$6W3~GJ*NFJG;x;D4DQ~(Q3Sdr= zL3fG=cAPT5v#Y()s@JcT&TnW;IoD3QbdR&bQ}=-h3yTo={yr}v1Og>;rZS(I=S*y5 ziR47tg^_sGVa$2tG>-X<@x0a$r%rV43IKB|4!TofFyj=wk!MiU4qm5CA9r3dcbCiQ z^uT9i+2=mXIL8F*^dP6E65aJZ0 zJ@!vvP8C4UQy~Q~H19JCZW`Bh)>%C79K{x(H~IcHq#PsSmI|5P9-OB_h6c-Ct#dw} zBDhVG+s{zXv#fi5Svnfva2_d^_0iP77vj`U;xJ=iPRaaB^S&sqNVunQ8jRohc%)~g zN*BEAle%6b2`dWAaS2&=T>L%PC84S9yW4tPlo{`ply&|?96W8OLcxXaDxY~}jpF)S zp52EyMMkau0+>@&(47i{8K)dF;IP$5I^I84)Iq6>9GPD^bvag6Y1o60J)2dcH zgmdZ|FV?PC<{-!Y0Pagbx1&x6=2QuEr)Uac#i;}Y)r@+z=Cg;jNv>O29QUKX;(vv+ zx!1buYtIle0LsV5lGD3LD;cHyDZLc#uUGHE`@URFwDJkp3P?k1b8rl!cY)m7URJ9L z%qex~c}kWAX7bdJi0RyEho3&EVGoMfd|n~MGcbQ0(%ASd%zD=}6*Qh_8z$0icmwWBprGk%2j&zWbf-ATVP)P|;R<&m@xinF zQ!biFv@WvUn`aGr@2DGep3CoOJOJM_%3=cF#s6{0{P?s|6t}Pm{FaIFf;FK^yv54A&G2MUY8i$+vvNrq$GGh_8FyJ(a4@o z6f~YMPG=G|<47Zk^&MXk`|zk^QWg@Ckv_KdG^&N4a-o#xgE-Z1$5{Z(sS4;$>A;Lr zf9mK%LyQ6Mrd-dmPO{N3k9TPU9}3=)6x=_=%pC-cr^gy%Bu(cG>58;jn=py zjHegYH{CHl-RFn(>sSz{YB=ZkfjOo8FXgGd*oZDB_xCg^E+3+8YTV>S`BI!Ge=xzT zS=aq+8^`%ucX?{gS;sS*jnQ?zBtOa#a!=m%Sr6*!qA1?taWC(6#iJdFQ_0i}+`yb7 zg6`B$m~krF?+)jp{e9Dr5Mk15=b{x1)+wBQ6Aaucvm4Dv450VUtv4!)OA3R1HTpbH zyr8b^iw<%I;--jQG2PL+;g;|qdZTUOtTdNc!v8Gy%d~LY-0Z( zb$1z5)%X4npAP8;3F!`%ZX~2r8l?q6x&@>=B?JTn1*E$h>6DO?ZUiJGL_mZa&N<)z z%sum)IkO*qj*l31oH?Ae_iMkewXd}nuG3#}o0%wxQ&n_jD8QUzgYHxy%s9nq13zWO z&szH3XYlk|Z9V+&EL?AOffSy@lK&b#9LOo0)xq87QSYnDVKiS|7bhiunz8$R4J+rGh2mTiB4T| zOaTWzzfb2b@_Qd-wqf<*FGu!~dtFs2m~MQIivA;`?-P2m@U?6pPVLw}QU~T#5_G2s z>tUtts}@qhT5DG#Luc3;kq~r$QHX9NEkQ45%e0YSu`&)C&le^BSBK=>s#JY?GC`l- z9@DzL$oMi-i^3+nEbh6WvZe@eimbSI1ejCK(49izgO$3E-8ou zbZD*EfjPwm-Kjf>uu}J_z@eDo%PsVdsCMTGKJHE(zKUMG5PV!LBxN&5PX!v!&$NRC z??sy5oArJ|#C5S5X4(*3CD=-6_&W22hbAdf0vqC#ht5+rV0o$(x>I*x#wk;E*JH`v z=Wu&_a&W&Cr8ItgO49dK^O^Eg-d5@I;I+%$;`z;7P8l+fV^(`d zA4GPip}~?0&RmF7)wZ)Nz?@2j?$mFXaVil`jvmRklPY^fhhQAn*KS_LTM=VtsesHb9#SbL-`PK1k|6j-4Ur#_YFZ3A=47rIl` zFyqv2YRu_&q5e0Oad=ebTzeGVQJohWq*&+*DTz;x!RH6j{CFUYlI%0EoB5;lkVo*v zPJNU-++4d9UT>}XwbQtz8N{h<+JahOPSHVk>LmiKI5m8P;9NeoeU*kTBSSuJajqKA zFsbx5^w%Se%h6--xg1pRNm%y~r@aLt@5W?Wmz_vF>_Q_SD(po~X4`9}rYy9?b@W z=g|qHYx>GROH$cNtS!VT|LyY zI(wRi1r(Es&amg~AWkW2OMC|ARP8^u4mkrM-I_ig>yg-#0b%C%O3wH-{kiu)s%^}t z#~j_RL-vnjG7DQYf0CA&O_e3Y&mHt}w#DJ4O4{_?c6we))QhCOWOo!vQ+eyoUh_t z;84t2P^EPC>=c2XJHz%uRy-7Ac9_aCZhGz5RrjT&gp$9fy^FHc^8a{$LyZyQ)TCFf zH87`y|D|;^DtQP=9m<{5e_NIQNFx_!=Q!*?_^ULTgm-NfZ94q>cHU(w?MbhnShj0B z^Ao1K)Yy}+HgLk>A}v|M`w$C)@~Ss{AWoTU-2u3tXC?GJWd$>N3gNjcd8&^botq7& z!$9{0ZImGX0(L!yq;grA_aqM}9|s@Q|E&-6Ss~M>A@P4`in1dwyp>h#&Q7Ey zk9P%e>Z!ew2{5NZpy#QtFq5Zl-uD$Bz5XQWzDzC_Gr8SG=&DrdZHr9@KZxfu1YS48 zj7Aw}CNr^vFymJ7z&Y?dXuNBgE%-;pb7 zSy0j|Rht4Cj;80X!F>sn**mDfoD%<+oWjmTP4|{bSo9*wjd3#LJ@eAtqLJ*ja9MFJ ze13gj>vnzL+TJ6R8fIN~oM&#|q+CiT3Ffbju`0)DlY)&Z4)l{gr-Xz1rxrCUQ-I~E zR_IQJeTS90FTmX4F{7e|mHyn;?7cT1m=|ezvR?ze{3$T+@{SoS0;NEN?c~IPVw)ECkE*lz4%yKxzBIc_r1I@ zSaL_-RWBaRX)4vd>nSw(-E}Z8$pr4fj047BivDYeQ}1ap9s_fV8+zSG2oE#!Q^Kf5 zrj}Wl%=)aoOUu!JU9~P69(W=UPR}(S_0xgsz6eRgH=(1bxm%}O_I{-JNLp=aUlB$W zUhxG)1_?T%e#nD3C82dE446}m(4E498K)3b9NFHaT`V*duy`DCU2}9Rz6_=0=yraBX?9t)K5H)saZ1-FP#Kt0G0>e# zg&C(NI8`w%40|*QBAJw3DB4>yiVpQnc-TXJA|aj~^n;!|NG-bF1#JAM>zv>2`MaJN zH6d@udBoE=EUyvUZa)3hJ?!bn|2$v|BWlV7SW^An+!M4lKDhIp{`IWIh z!3kOZbw!N}LVtQj|)}!m@!(+J3SDmkgSI#fH~FkFU?OOoVZX&N{#W= z_%B9h4F7sPYi?frTfVD|SWKLPFu?70eyaO;yZG~jF!jFd=~#K0pT~~TJ2?6hIg{EN z6-0yRKp9(zQ>S)oo4}kpg6ZMeDeTL!3j_>YrUCPou{rN&2m2FHajpBRDGxnTV(6H^Z3*S75Q`5Bbqrmc%B6O!{;9wf&7rc-;(*yTAh@8VUOp^c<GeWRP5j9&i7Jlke;zBJ4xA-_81-(7yKye8~852-+0@P z?)T(wKPxr8is_|i&bMyEi-DAn*Q$uV!`(5PJlw2~?aC0RlI=D=0&`0BUz(q~$x|fv zG?6W0%ll;`>UT)q)=T(0Uj7ZzXy{VcN#wYlM<3u+5Hvg9L0kR){-{#=Ot|)4BjHwU z%TgDT7GCC;kHW+drv|ROG=Vua{4eFHRaA%bxoQS}M5lnpOT}i^T^5R0h0z}u*xpt2zj0s)k{0rISemr8~+=T3(UwzZ^?FX&cCbICW(2a1P9=3+PV4 zHN#4tx^c?1(@yOCbiUHtw;%&Gy-&(52qW>!8|mL=yoaFYPVMD~9>UYk`KJD-Yo<@N z{^(dou8~#mXnu}JFUi2EZ9j%M<>t(n1=j zhnBwi&W3*$lWlJ2(bc=9ZNCd*^{W?BbH>#=F?O@S2R6diSs z2r#Flpy#Q3sIZc!uoY0%bkcj!oQ2eVM1FJmgqp73eY5!0_M~J^gP9}0xNl{W7hH6+}DOR zlB?ZsW1@oZy;LgWE}QxHCzWcO48eV#o@p2A@4m{Oo67X1U@5V4v8MGo$TWS$FIN+v zmM~+@T!%Q7ZtHOd%&9BrPF2B-Q-oBi>-%z>AGJHrx%9F`Y3yG$|4e>Js}_qiz19L= zH?tq`#wS?IzgR$2Ia#=FGBIa14K1|0B%W`HrZsE1nsN%_lzXwSI54Nkq1Szv4`HS5 zV?U1Zuj^9Tq6k?$D19=itT{Q5vlJU8KN20%x?KQz?!X5vi@JF0N6`(h&1$}M&HL8I zsBI?bpZJcdbFQ~@1vJ}{?bp*uAPGfv&)sSnYnLRa~z)Mxo#6)c5X8FY~} zF8wddvGtd5K;zl}AT@sxx%5rE{5FE4ecbD*>X_eX6w7-F-851!=BCIkAWrqu>rDZ3 zYU*FgQvr8fe4B)D8Ii)3RVRMJj~E6A2}SuFd}hr_(cG)so4WBd&%(La+DuWSCM8qj zjUEBX%jGeHUD-eOZ-{n$xh*DIP9RRv+lMv+b7~d3Q)e*aRCR#7$H|j8JK>}z0=83h zm%KWx;m$ZJUcE12*oR%9=g#~7D4mT=1t}lZP8wSFm&2^urYG^koQrpD+2pfkj$OMU zPHmK$Hvw}B6}nTOV8$sM<5&yL_aED);72J~@-ZyzlZ=Vvu#*;loD{*Kg6F5Ovic5I zXupPqB-~?^HF)2a`$*`l8nF*Uop=Ce3%&DEL4kG9QJ zT%_fV-!=%00QV)tYtjPjNB;^vPbDwIN}dXkbflruo00viYd`RYCF7E!a`77_{nzGn z%9w<$+y0F(%>iens)BDRNh|l-BegNjSdHPfTD{Q@4<^_DIwp@Flj2r_`x5r;RK|fh zwF%uR=N4G0`v|#J98B1l@bQy}y6;@1mi*fAS~TgQXIh!*Q`VdXjpvtn5zO}_&zeFU z1TqYfP*K->n`cUk8R78f;(4v_uzlum0QXNN6qg19a|#i z);a`4wJKe>4q0#v^Jl#~I-iXLji*3N69(F4%@==f>z3)TI0vb>Gxy%UbCcu1qJb9} zA;7u11ouz1P)nWwbIJ(1Q=2g3)J>k+TddGynmK5$^C))A4)nAhJ-Dp38{z*{$pt>g zfleTER`-u47gL?_1R7VV=AJXj9hux0$Hjv=&t59*E;T@$!U!#U11wK1{!4kvRL^D1 zvjsu77iVnK&|OxpkFP>?AJy@|2~}Sn4*komQ^vibahR7ZDe#-7>P!B~IDc~=o6?c3 zR5liuYJ=%4wYkoO@TFY~yF+-c-8lXu~eI&@(ZuaNQejgCY)I-{&2knyqj6M@~;)z)Sr{ z^CWqFEgF3ac3}pA0VCY^iOCd*Q!;d48-Y3X1iDie$gq;9N-Geu&At#n;NfG9Dj=63 zD^2swqqKY|#YK4ZQxxn}wo_7wN7m^0#H)kqBH71ELv;LqhHy5_4|OnhtVeB|C;wlb zat#IM)GYKoRl5eh|Ni@PMmywP;TbG8(ftO|CLhZMujFdMm+iL2&bJ1$Nd0C7jVDJp zw%a(9qDC`i%@3_(X@Bz9a{Cxe@RC#JlvVIFNc?&br>Jbq0rsQsLw5?T6;|?8BE5XW z^I9xn3Poug1MK5E>>lGR8&mcXEWJDZzrp8>K2V>D#L5)yJe;s0Itj#QDs16F^xRk! zi0Yu?+7o8iQG+c}X@y)*gU!cc)pOrBhv zGl6_GXgo(r$qX4a(Yv3Y$L6Lam)_MT9O~fv{A5D?*s=cjJV>|=;#3}O5CJf!bf7zh z12awySE63<*T3G5F}%z6{i$)RGbD~X z$`-?d6b9`QEq%F8(Oo~`JxH%1P9rHpR8EP$)A5T5Va_J=Lwu>_S1jMOb zyR$#Q^3+%8PW^-#r>d2e{9g=lCD{_BA3XIGGVd?X;6~Ow{QY!1VYL{%ZsygtGWHig z+irRO*xC+{^k|ciB@Moh&z_D)ZhzUGQHo81IQ6$|a2l9X{Lr1Mf*q$QW6hTfB0I>W zIAkWDjq(P|cT1M>T)^XBZBP0XgPhVmLLb!dW|{3OPR%H&>_GXZhwWiNgIdjLL9jMJ zw{PhSaq0{0Gi_i_y@c*mBFs3o&j-I+(`d6yJKF{yYk*=l5M8L-75eT|!cT;)C328c zfjfhDh$JUbd#-v#w404hUvT|(^S5~Q)>aALQKU7~ff3@AwiZ(aFsD|bJ5{m@GkL1n zy*7-5LonHDy;a<~Xr`3YeeIoI8Ixs86NWB3$f=9_8vBFy6w(JD(xCGwacxF&Q~$s> zFENx8Qk$iSBK+arXeGJ z@!?#Wm=-uswS4h(Te&95;d)vlV5}0*=rmxYa3%MReTn5$-dxL`RtUr?^irAwU`}yE zcgpJltmLVotC+%L=G=*XSzH#Wxs)HQ0_NP-ud%Rp60AC2+^*N=F6Av%2>m>4i=ajj z%KmLzrJ@*V$Bp6INcV51=vLata4I{9Q;%o@;($4258WvpB+zw@k}#ysN*$Jq`1s+? zpEES^L%))tFO#Pfn@;S#R)&uZ;yKJ^`QN+)(yBwTa;g;nMB+>Lmc1uCk$91xkd5cU z6=N2Bf2rR!>{m05qixffKmecjd^=xq`+NT1KP|l*GYhqD|2+XgE~d6kro4fgFr!K* zztdv>9+5dWGCUtL(IW-I2sb?_bRHBSLs$u2j z_33fa-kYCO?&3K*hI>`DIOxwlA*_7{U6(4au-cg)4E(IU79cNtqEj;Cy8q(S@J_La zkh*8#mjkCwh*S4U8K{6cB@W%GUYK#}rq8pV$kJA=-=B%Ssb4VfORqqA|L3X4*G6cQ3Daa3w+OBy*(!S^jqdzecS=-$C~usN2)mp#T*c) zc!fOF1&pQlvNQTr3I)%%4o6Dbbz%j)A4E7d5@r(-pCN3kiv^HY-} z>{64xT?*AAV)A6q?HR{AYC4%^Et%B2w_|nG=(Zyw!F>s&Iz!yRoH~W>RKy-Q7ykG2 z=r>Lk`=|VA8u&n7t(5G$oMuR9%2a_*6Ms}^M+M$5Va45ib6}BinQl!DLc!E zc-265_B_3^9Jxu37_JK3m(Wc)!Vb)-j(^FilQQ`XGvop(-o|N;j>#Z<|FSeud}Uw5 z_xkePyxY{b=lA(ma39R&35!eXdj4J=psdozo$_{Gn62gPjJmFVhe|Hu2<}VBEX79w z<`ffjr!d99bw<&7!cg(#XtdAmz671L>22TV4s0Ip)9SBqrL{l(dk3@2d<1--+Hi-p zq0qXP`H$)LQ=SKZi^D`!`pYJ&MP}bSUUc}y{9C;Y7U8|S5pN&H{NMG<|G%!$XnjiX zo9@PI_PY^gfrafxb@yv8;0RahO=mDNKj6qd)6FB8Tm<(eNTvNi`tVw+;$>yKn=t7% zjr*al$w!XI3Ak>&B;q2?%b~IuzLu2JQ-1#-LG=k@3Ayci%uDfNn&z~a4-lvJ zHM^96IdufxDN>kmN>bW|^>XrF_|dZ>4DFc9Fv|y8W(#J z3;0WO5rbvwA=X@iHf1)4<_AY=fdOHAPC^i;Y;6Aq19Peedfm5E4yqF-7u<`^?YN(g zqTSAKFqF3UTdmEOelYcAw@ z35Z*OIYsv`t(#Hmik0#rNv=u8)|bUjb-6e;_bgbFxq7&j8E`W8Qt@_uUvvzo!-X-t zNTfuR*=H7?5U)>>EV;&5fi{I&jQbuDZV3>lLaB@BfjQ+1-Kn=Q<5bKpB40>{fLnw; z$x9;DU>z-+Y>{SJ^r=jlbL%tkex6<5J}5^-S=Z;wl`}Kk|B4)VhGe-z{eu-g-{QN8 zk6LdJ#3@$o#~r|&x`OUh(Jrjyses?E1hUU-zEoPtteBEn~(hO zoth9+U4)-F5lduHTzWQ`$|{K$IIJvqK{+{ex2fza=yOVTJSAw*Jo{;a^K7@2IIiSN z71vu&0XZ4_FoECt4KK7zAxhk=iVJ$4k{1U%{om)QsKiJNnz|Mg?`oM?!y3b^ zwiR0=(E!nE`+@at_;}EBN2yw^f63Tv4He@P23?$`I#Fg<{8tlV+O51q1k}-#N1+g> zmgvN!fH~z1-KocSV5RPhc?W0IOLZAS>1(!k`YzLKP;%{rl%Mm7PGNV9lal5}xy~PuFU8sbh@ti_d{-x>gHhsyxDnUxxDAJJhkF7c}o++uv z{4YO$-Sy`Xryl#>WDV-ud76jd{PDkh^MCwgy>W-tZunoS`)<}D2iraP`Sy8H71r76 zq7j}%&*okc`{M+A~>;v^9y+oZ^dQzh35d``B_rKFTCb2SWnLU3PY<8S? zq62a2=nk_nFsIo5r95TR7gZE$P8{};>Rm9Zjo;s?laWR-dCiWzblJ05Zq(b{Dk7d5 zz#E%P>%h64XPE3O^~ac)z^%$}PeE)lD;1eUw;AG89F3>|FsIz1JB0@`PGLVn-1M<~ z%~LD5)M3;uAA+vO?Spd=C1_M&Bj$7$bl=p+!7u(ak^V?gf7yU?k+oj$+-;yFzV59* z=9uo`I!Uz(aY{@Rp9PpxD6-uB&DsaN=FcGC$N#%e`5^NG55-`~sfo$pt(xrVe4D1| zsE0?v_6-4V=&|)EMSCN``{czX@0%5?YTxnlb)(3~3A$L5cp3DqBt0f&cqR1j2FFh; zh*NM2G?~DhdKf&ZvHrjuNn@eqf4K?r6MQZLcj@hZ4WN4Lzt2-QPO)gBiwKgtea)v+ z+}eIO8f*|_7Z_^TW0*ukF?#zsMby@-(n@o-m#DdZF$$M6&weYJYcta|BS={ovljl{ ztPF9goOKD{Jo+?x7`-*RdQY|bGWh%W-}gd$J1=%yzk=WQ-_)ltkf#cxD||3#JY%)E ze#Y=F@|vlej*`i4ygQpWCN~Lgxvl%k+!PBcT(NWM61-Q3j^5I-n3-l)_X(4~TxWiR zL$_hB0deX%&2TC(ry_k(F!X0DrFNb}^5y^gz7QezA!-!NICZmbCcJ04Cwta)j)GT^ zNM1^I6Nep6(WV@?4UY{I^m%QcHJDVq$@%(e*6Up!BS*66S4YJ})%7Yip@Khesw?B+ zAWq5C3v2>&3PF~8u|al6#PXg2#HIhugMiP&yU82>`}_ZX9vvavVaMM*N(GPXU`w)G zj6^pTPR?BFkw=^Y=l7i-@V8#j;SkRU$47fdi`yQpc}^B2mPoH&ii~^I-ivb(EJebN zfH);)zwri`Q+&abGAq<3Sn8P&xBvIOl#W8~!wG>(@OAdzJ4Lvol%_aINZ6d7yh5}3 zlrlEU&e79rJeeW|*PnO^^m)bRc=;WW1NiA@O;_z22`6|THwH*GWs-HD9JSGN2;9$y zI7L!My#*{!jiZNg+mmXJ)DcwtzxxjC6S%+o#wl+R@O}8-JEc?^lkrZ`o=${k z%R826AujN@c31jDxT6&KoY6ANTG@4RR)LZ7zM`zy=&85xi@Sc^g!~>7ci&lheKMDW zI2B2aUJuNvSm;jaqJyuq|Ned7um>EOoPET6Yf~ao{wsCK>ai>gk)Aso`hmN8wBUX6 zI%B3i*y}lXnXGV(2b|lH=6u|imk#IQLhAk7GXmf^h#^jCYCQ$G?=Uv>Jau{iz7PL< zr?QOQLQTgm+GW~8H^tF8;@fi2HNL}PiqqXJGg1QQzo+EClBhSSe&)n>m2juTg$YU2 zRs__dsNy;3zhm3x;4gtV#b_G@upeCrx>E))#>b<8?}0; zVX*|BvHBT!-{E-N2Av(>OoM!h^}qwlEP}QjNmPt0y?h;y)-%DoW{ANMr!Y#0;ea`{ z1l_4#m~kp9DE9rgVyWJ(UwKBZ+hVw17qe@KTHdTKPIa--gYz+dV0GPvr_Kwt`ZqLt zgsv$!Rv$5U2X7CoO?F6$ss_NtkhpQxKlxyh_6I#r=h$tIV}~ zpJz{kSiXN8LE^RBMPvXyca{;+Y?w&%g|E`|ZGsnBFGWOpkVCEry3L2_JD5eX&1E1? zHENqa0+y%lKzGU#W}KqE=c@At{xlZBtcU&toxF2KMB#1O!bJ{CL)byAFzC5sJewc6c38%$13#QbOMW56^Ubl-B6ofcsOQWC+%&Ac5PC28&N}l2* zMLn_i%a7Upg|VRiYMk)RND}f}93-io-?d{O!2MIwE7Q`^XV@P@s#^#S*)M;4H(83V zF9_hvH&Be9nms3Z4sl9DlOYS3Q^e4nO5KN*JasdVZoE#A^Sed*isY>9^fjGCv`3w- za-OIG zqW#C_r}mrD$39Bp*qb;Lh>P`C)Sq-#Tu2jquhGRJYq^~#BERNrah*{@X3m_dr(9uL zTq!+4OX$3QDtbl#h33=w(gPL9xmcfA?gMkG_g`|#CMANP{T|93Rojo9vVn9%baV0G zrzIm*#ycd>8mA9$oq7@R@P)ho(B0x=LS{vS-4*2nefE9KyhZWiPcdP%r34Ed;QlFl zI<`B&oJxc4)DM_(N<|m`zABuz3dcmy6KsyW)l(5&TGYHVQ;scf*%(?-p8Cs6ug?F6 z>u$E--3*I6_~dQ6*zd|5zP-p?{18_2^8r>!HMq|+SCgg$m{a)Bo#KHRr#cGVU!t3C z*^0q=Q*~T^l;XJh)cG3m;W%I7Ce|_|$f^4nFG}FN>POT>e|*pv7@PXrCTxYq*5`Z_ ze5^UgE~`ou1@7~lvU6Go<`g6JJe61lEAvyDXsSXiub=%@MSYxEyc`yg_+oif@28y# zfjF07&KT&qgQauf_o9G4N{-SkghHF``k35Zt1XwQsEiBc8X-@3j1%Hia&hJ`FsC}9 z*L`uKpz9h2=F!<{qWPJ}>!=OH7^Hac?|Am+wTqrAmF2DuYF$eufW|Z5A*arT{;pp3 z;accH;V-m}^8Mnz>b&rz%_)IByWFM$h*NlUWDkKkl?mOc5SVc)Mi-7|XJtKNhchSq z*H}SBC+GVXe5Wt;n6tL2CgAsdn@(j2W8*1}NAHDy7jR;De6*lU`OMJD%Q+)}I4V`7 z^$FsXfYx9aFsG=XJM{!+oLc?r#*Ho>eYIOW?a%4?)*H1#xHn46A#;fw4>=gT4tZA8 zYF=?np>U+0?QzMQA~k+RG1G?zela)e)-w76jg0sqP9fRx$^mnV6}nS=)iC4KN0qkS zj&@}1^Jct*FV!e9`S%d!-DxOD8N1MR+CfewZQJNm*wcPFi?G!naM5K_fp4O7=}`!E z`d%m}!)juM0dY#UbifywQzOuw@)m*>r*78wzCHSIH z7!B4bY%|$`zYp;Bb2C1-IqtS!_qIV3weBg z9ZLAV54k9>p0HFt^enIS9RN9{;MT6lvSr#U(pqEKw)(2m!Qt`O*315pBKGpdQwJ1I zV~A7S#hn1p86AY~)D-ME)fB6n@_7=T2jxiMwG>rvDS6lZ@dJV0Id?sU7Vy5qxx$XZ z%ez)FIp6U@4~d!73IjZE z1|PapJuu^xW{4b34MObJpD(|SW?pqfAADEymrXm)Ctgw5Q2?LcmqMIpLz*kHJNeBW zL8R#^FB6IiW8^HNLOyH0MO7_!xIDxuvSM8%V0r2{bf=nN$0_a@yqdv~@q>AOW&+Gs z11oLaUtAJM{5DpM4|-BT`6eUXA}K{&#Yn;^%v^k#crTSvG&+@=r=JsbON(>q_s;@| zQ@G>VO~9Ng`j_(5zAuUZVw$FZ@m6gLe$OC0uUfbgQu|k#^=YLtv>>Y6JmtFadATOS zV=iCCRu;Er9^SJ_dWWaFk>x8~+>f!!*NzAfr@AyX*?>7k3*9NO=Qc3l6m5t5&>c_1 zayT_yMtz5kx429A4P7dmtqH0DpoH^&w{SwC9VarZpPVLCbf z_4E|<+bOLK=vDt z@`37Ej9j&4wd7v&%VUsJ^Xv8*zlB=&*W&ovWYQutE7@bU*HCHf@J!myR8jmDQXx)R zmF=MebLtv;o;nu>&%6BheV#)kF+UOQxb&sN^XvIuaTtm{m_2u&rIJ0I#cDnQ_swq` z@bDz>PrnTaN65Gwz4o-8gr|hZ<2wuHGL6cg&mXpgIF(H8Iu0yPWkYu=A7-3V=|nhG zJ2ehM;l&L^p`;t}?oG=^!2Co~b|x54PXZdx+$1Xr8w4JkU6O!NMEFq5ZBR5r+KzG{~ifB2j$btqbI{u0Gbl#WW8;idW>I8QO&qY1CZ8Lh3m*Ai4K zxmP@4`PihJ^TSMvWbDW1X=Hh;5T_POaH4=Y^#{6BE&{OPl#ND&>9^N|lLCu6GHdet z92uuF-KUMnO7s}^i0JP@<7uBQBQkn*wnt(;fXB;&n)E()@Oi0v3%9BtmA9d?oIfVS zDMY#pfcvB8L3c_P3s&+Jb`s(|TW?Z{4%_j!&$~83r5yK?zIAj{CvMD}(16dyY9Q@h zS#5cu)i`#vOC9#2X(%;YA{E)rhkT-jKDXebkp{#mH7zA;U{3KtcPj1&tT=VkKjoK* zm?}8>aGRX!4Lwi8uZZ9eSvnN?a#>2M;QpzQ2>2}7?3gI8?pZeU0p#NxnJ+b2#C4VP09Sp$1go^DwM^vA z{TIh8SEZ&_^i27W1@4TKiqWNU4Q_}^X(Ymvkhk#gp(NiRPH}(#ege#?@BdQWH^dsf zRj6#p$r6$Cnh^P+5iZ5LwV_$T%UC%P0b5zE+xKk<+bPnb3|?lrL(f-=1YSuWP$s*0L55rxtZq^?^C{ z5V})rFymA}y320;gx5U(dX|?g;xl1tk=;}qAK7-rUzpS1!ShoKIl2P9gbTFPDlcXg zhm3h&6f0z#goz|9mJ*o@@XIBgLY(?$D~b&)PoY51Qx&k2r%;rVcM^yizuOLDdXzCf zJWy$QPjKR?w6yR@<`ekb_KnStH3=tANws*tACf+d*?BuXoKAUw!gIHtf2&{Oj#3B2 zDc8GJn!ub|`jxD-=n@tP1}u2~ zh%tcjaju{NQ_HKr<%kAK;4y_j!J{^E3nI)E($f znbyHd-FMQf8qJp{_>(ye(WXZ|%kSy-C62GI;>DRkrG?TsXgp8JUD)C)2p^X{M<-T1 z50+~#o0aT z8y|BFi?aMUBSg-^@pr=a$h@h+>!t~0l~d0SCZmfo+=v;-X=5lcJIY)U0<*(Lhed<~ z;Qp>boNA$KmILNg6?CVlFkz+cyXl{T&lbjV-Zq=)}dbYh7Z2zz> zMNNPsrnE}5y+Nh~af;ej(-)Xi*wCE{gBho8*7yAmrSB9-#qo%L5&XG%`R6+AqaxmK z9TDop3gGp9G(=;n%vqJI_;^Lp249#3yK!_i*XPt4m>$@QTx}3Mcn)!@dW!TnFsFX} zOL+?OdGz2XQ*th{>xL3Pk>1?5n?`$li_ZybN*)oapN!tteWNs%T5i4Peb0W9j+V%;;^z%WAuY%OxaofM_N#%Bbh|kQSDVOd5U0$w z-vZos_%ZZ6Rs0rK@>GYF8~?u2-w(N`Ghw-Jt6fQpTjz{Nm>qJiJ1k$*fSxpR)O4vCHZZ4>q30>j$FP#8Zu-$%lTwJrpHD_CR6b-04Bja6YHqNJxY%P5 zXh;E#XCV*X`RQz`QY*&;Ba@N#*_I(*3&$yOm3{orI0SKV%bF0Uv?>yDfjKq!FZEB^ z%_A8{Ra^)$J8R^bAo0B)#^@E693O>~rPhCK zR((MIjH-X_v%7zG_yY`xQ*X3z3xVY+N$5_k!;Dj_RgT8(?)gC+@$v!Ba_!N2xDJjZ zEJNVB%TLcR!1L%8^60(;J+D2bon&?+$xQnx@3p$*TcKeRB_oTh)-7rWLY$JYe>(}x zDL3d&NyCg>n@lD1rBmwj1R6%vA4xXO+bz^+mpq=dAd?%;*=$sYg;S>6etTtA}OZ z6Rf8XSO=_s>{!V14q)J$o7ww48)$(z^{XU56qr*9(4FFf8K;W%qNvQynfrNoikpor z_?=!K6p9x9*g)%vfzLWD068^=b2TcYXUk217lY%fi~Md`#?LxdCyns-KUa=5Sw1P7EYDkqs|HUfFych>)M>hkh!M4oTQHi^xTOo zcv)jf^~iT+b3U1~-Vbpc4WH3W?%IA;BTwqmSB12awqFgZR- z#j7ofv}$UUnL=#9`GGe;lJ@hxk7R4+V`9*FGCWla4j{KUoVQP>KNYDzx`);Wx6K;|=cELHf=VUpRF{`ukasl zj=!HjijRFgi=c`Haw_l&+hcdH%`H&Jp9@iWo+!F~6|?v-Eie^=+u=Er>gDW)&X`nVJp&YO9ywBDSZNna{2 zS#VtX=KNQ_zgo_jmJ9Q{`vIf#pwG#dFQKUU8$t5EV~x7=P#m*C6CJLeuy}GN`8F=@ z!39qU#HsBvcoJYvr9pQ}QWjR~zM;Ozw>;AZvX7qk8GBF934cufHkYEI5K-oE;L`wJ zH)E5}dVU0_tIAZGX2$cwK4ZM@kJtHJ+aOBKvP|oT)fj7tQyVn-&w)AB3*9LRCs@f- zoEeDYl~F|~_b+6p-6_4lJA6b-&84e#Fn`@?E6@K5T}rA zD!&4A$`ra&QvI+pKb2^%%x*6y>-iSJYPF}R3XNRugZX+YJq0(V{an~AXgpK2i;;8h znh;(Q4u9zODEs-)((#q0x42wh?&E~9K7QL3h*O6p()Pfd3We^J5bQW*7n@RLlS$gV zDoN?BB3f>7_~m*C#|vvJv$J7lKB z#AkQIL!5d|odB@Ua}K&w%P`~A@D4&%!k0|%{lCW6CFWG$`Z91g#F`5$oyitRzfggk zqC=-#y%z>&))MJ`PFC}T?R&NN6GFr%)Ydg@h;+~Mm$V^H`Dv!719M6Px>JHMKje)w1>&3r6VkMspw%30&V z*PaMxj3YCXH*hnfGZ3d_x{7OnIc4!L^?6qRko&8ryDx<#amf^?yQ1>=ZdW1u6!snW z0y+3O=AhgA@53T-Mxxoq%T1b!wXdI$N3lng9KZEJf5@=S)Z{#zTHv4OOjELWxJ1DEfr=Wbr!7nF|0ryWy+u~jWbIKZeo_aX| zD|t$jNls0GT&(xl6S-CvK_r8*#g-EJ%u&&2RPDUF3pAd;28zYQW=7(M2;N1@=!X6d z#ur!mqT%8A8p)Lntz}e=T?yPjm0XJX1DI2>(4C@%8K-XE_pxbyLqq3LN*$S*$)xI2 zmKb(#ZSl|`v&Q`62O7`q$em33kY55aF8%c?lig{G+b?4I+fSH&p|1W)4YDX+hd8CB zq{;)#squg5ecw(0loBTryENL?w`j*0Hay3I8k8mdAX%ov((k(Fk#>&n{pcm{a=yQvVbJg1c!pPe3mw37e2? z>*go3NUWhM#rKoptDEtK{8hL8QHIv`O%jd zUY3j%&#cGir+J?{5z4#7zi` zhu0$zKzYg~#{ol~&)Y{mh^52RB*7pukeNZlWRqcANp!td8ZEmA;#9S!z92BCETHEp zBbdok9Z?SI9Q*H(h>t@J`&R=qV+rSYJVF%$%uG zKa_(JIwY7RHB?pz)@xCbX*v+69BiwTfaNI-=uYts!b+Z+a8X5S^43!Q!|{m<=L4;a zdiz$D#HI=Rc_}Jg%?QY;zp?`XU(j3y>PBKMv9b(m7UEJ_a`VxYl%9Aewp9%Ou!T6K zTxv}X%&9u)PASU3O5KO48QK287p-~TlX zS)ZWH5!stj#B%Qo`PMo!su65meW$Pc+c-#c4;(*I&fF3}%V zIuQ4u`iK=YoJfUWpo8fp)W$-`wJPB|0>IRJC2>R;+d-<^n&UD8}(sw{Ch4QbfNCeQF| z9(ybLJ7R=~b7TphEPT4_s3K3?Ux|v6R-25}=fYKpV zKqjosY0LXbPuxYjZ!hx)9SwSrQwAa|v$7#_31@%&-7<5nF+bo&etBRsUi7+IOQA1L zb}km;6#NTP0bqGb|6iI%zj12pB~_Pi&0l1{&2#L!^<}l+>a^kJYK||Gu>#0%=h5XH z?1_GiZE%OYe80&3n%-?Y&xeMshmxzi*;tezrJT3`;uL?VQUfrj+WsY{l%ygbBpu%^ zr#Q9<*!Y^+d%E7y=v>wqpdZ~Yv)ZzAyH6fN%~DGA=Z0c7dCB1;QVMILmenKrPL*f& z_9@oMV@+)i5T}&sXaLTmuR(Xp%NBnMe1a!BaJH@PeenUVkj;^1m;u| z^tumO5?1OyCH@G+xc7edY%pUOgt~l3tUE2jNhH#M{cpMSIw|7jXQE(D^i9;K5xF9b3gKC_53)u_pn&78dSPOHR~3HVeK|Pu@6{OvUZbI_Gqi z`bqsRrxP{4LD6UC&tDEIqO}&W7Egm#h9FK!+WGbab4n6=-B&dPx~^fN&l5$-Vu7=~ zBCn&lA=d{LL)8-1i@`@#LRmP8Y53N26dSL!L0xi|7465_%kPEnAjt_~?YhF>sS;+m zHkUl;ZGW-~afdS6VQi|Bd^a7kGG*J}vB76~P8rD;} z;&P6)3k16h9o-b?X!MbJnTnfsmZL*|q)6Hzm7pqt_f@{FLvDZ1|NG~Bh}9Tg+0DOK z2PhFZla4bVxe+Z?_~tz0cyQB?A6-9VJMva&a{=`KZI>NFK7?&-dOUr#jB*g0@6dP< z`IzL`%BmO9We9fz;|Ij4<6=QQV0kJVdYUcP&SjJ%PAPMOHow67I-9;;p%8Tgw-KbDHyUN)7PFU-D*Uc#WQ+3}jIh<_IwYwaBsp;kLoBXSUm;FCFC+Q_%&9WyPAN-)z5Va^ zpStOv;?)-MS0?|kVN~(Q~y@ga;(JF6`R;ai}m@rI&DwfIZr+~Hh* zOnme(r!?#K`6-G#RHXfs*&1D)t1Zb*seG){`TwKtE~BFC+PLA<-Q8WHpp+mT(%nc% zmo(BX4HD8IoeENtf`D{LcO#*+l!Ao7qcd~g>s{}9o;BNemt|$l27e- zEZL8HCyjf=pA-C^QNI!V+#f`N#_CTTmr{d5Ihk?(-aR z$YBMRr&OTlDV1Sx&iUWxspLA9@i&fIk?)9yEE#>Y95k?r5VLg z$rBWxxX+HAK;AdAD;__@IJke`ceh^1+H>2w$NLVtJK~NW6Ah zK1drEcZNG6R6CH}c2%?mVTIck*@=rmoPx981~_-P2D(#1BQWC>zjE`d1a-{st$T0e zJ~ELUF#EQAdvZW{z3>f}4czBB;3PQ6&nA{?W%}6Gh8HDt3Q_mMiNyHa;;3otP>$fLM zY6&bobo2>s0U66v5T^pPRM~+!6%5^}ktA5jQwUEzkM0hSeiuBFrpF`fNB>%P8{VUwkEsH2>P|%!K&OnMI~5HxP7RI6 zgj)SBJh`4qmxOO6LEgxRyH9moW5M?;Wj; z`KdMr2|pI0g|IfnsSbu>Bw$V&Z?i9lR>v_!o_rF{FHRqOT|9NgRl890{i|RxC+Uv`mFVd*9#N*v0#C6w_OYRt+ z^(3G?WhX^iZDO%{*z?__d^G1(-?s$Iqz6ySMj0kGe(O3rBGN&e($S4n0p?URbf?Y} zVI@zQZFvS`ypZ`+Q!18HPa;;S-_ z{*u?mMH;u^@_WCKPC@H=nEHj+tMEB;>bn4C=ZrMjLV{PTL22nUkuvc%)p%wmvHz+2 zDzJut8wXxX+!E zI6+P^X}ca0XCy34twEqXGWnn~HVp}l zrY_{H1dXPm9G?YY2BtpjmbtOx<2;vnshES;Oxp)?YAp|LbG@pfW<^n2&iMDq*Y_^y zA2HdKB+AJ9%?jJR(Z?Z9JQPN0!d9)qTuuk~d3KpztiYkON-c|J zWgqK#?YhNHu1*D3IV=XDO62V(wpu})YS0o30OnL8bf76tGPwZr#yv&BoD7zAKkJ_eA<_@r0Kqa zIQ7_Z%@SCiDuM2lRXeQYDJ_5ba};;uQc=D_g~Jt$^Wyx_QHke6O|LVUe6&YE>v<5g z<1ybs&OCnYHxIuhn{oJ|+-2)?i~j~I?N$G}z|{!EsiumEWMEE7L3fH@1y=G@`DENl zF{kpoJgmGUfAh?z+_z3_UE4>9GCEl(&%yH&vTOy4+GQJ~{I~S@UOzF`i!PlMSz78U z>1wN)CB$AWS?&ct?~|unR|V!2Ep(>{VaF*rIMp)jh7axc8hFMZX?z#-NA{4FUnyuo zC_A75*L@ESq+i@6-dRMQ30!ads`D20?LDe36q0zF6&%T(*YCJ9s=?3u`gKl9fjN}} z-6pnbMS}YhXdDCafH_qL-Ki#+amsB}9wQ5pR8(rYUV+gL z2f3#}Ypp11tw1{A+gvEvDgFnIDPpUhk6vA6XE|S92Vuoig$C>~D^72T=18J=rrlQn z_a#(Sc^v?AN)@_OFJQ*0d(Ls)V~<7;MjB$LVh0?4XlA%=l?<$GKUP=YCt z^7Zd<7li5qm-gokY3xi~Ax_CxmE!_)N)ftK0jjW)rIZBGm&vPwwLly;da05rejAO%ajqA;`+KF8ffHY({_Y1XR#y4}aK+9ejv!fUiqKKV$jQ{BmOD)gZB)EPmJcz+ou z?p(S;Ebp|8^d{ZUzNh}dTK(t6ts;DK%}$6@q1t=!z?>?9?$jX6I3@YqGabA3HL}2W zV^Oq&6pGzwc~_>=BJRi8bRAycd7g;0x#u3vpJEqbm} zMxWal;?yg98cJYJB|^_r6&)~B_n|1Uuf|XzjSxJ0K=rUhra^J8ylI$ItnC@W=;vRc zeaD3J_uzDV6%liC_j{2@k_5J47Ln04cbV|~r*-V@D}(wFr?`Hs`vP;y^Iy8(ch4ve z-Ogrzc#HBuh3`_~yHh;m%~>bnSDx;pVs^rOf9I9l8I3}YXEc5llb%9P$nVp7Gq#er9yXV2xgp`a+mYESTSvOGcDw4b-xoy zoiFGSXKvE%EW=*A4ZerGQNdR&JKlk{dLmDdimdjm^7~FH$8-BnTxg|kogVP*^$@4r zE3j7sOe(rjSM@yW0@0mBXHs7tP<$(5`D)-E| ze7ZH1iX_qDceCA2iEq$IF&%H6$If0Kd3PL5KmE`76#YjtU{2kK?v%4PtmG-qbJXW% zW8!h84DyzreDY-O(!3TLR^fOORjDsVMGacdhz1*Og~d!ll0p)#`5R`ju^K{2ePQk? zudX;O$I+`bN{CZsI!#`{oT`NG)Ojw<)O~v{)A?_Mu_OX3n!eFK%n|EYAy6J%J`Yr9 zKZm;yTF<}_CNvzjxYvPc+vm&HbyZ6v1O}K%>KJLq7jN-gEdwebPHj36H3M_%HFT#Q zcfv}Zs{5f#!;+deXDZymQ4+NH`(v7mN2eLX+`#bXW_55s`s~etcNb$iLb2GJd*tbC z_L1>HGx(UvX9!E8DsM}M(z78>WtX!9oIe!^-Khzfaq3T>XL;#&YT@^Cj$!j}yb?#^ z-})9$((#xNj3J;UfYx)Z!V!)*NBs-2UF_zPoM>ujtF+lB{`ggiflV4h*Pt1 zn{L3IV*Ho-JaM{EO|kEPGIGsdmzZcfO7Gq~Xvn#0<-;oq|M2CR+~0om;Bai=-3lpJ zHj+fWw;Db5!~~vVWtRTKJTKHT=t_>lDnS}z0bdgR61J{)~+|dMW=Uv z^z(ntr`my@qv!lf=TkpXl^Wl~>Aq_V+t2ky(oF7561(>r7t1`Fqi1tUbLj86O94qX zhNOZ8&$|^3zsW*ntou6WeDv?Ozdb#>d!gP*gSr863cWIy3Rs@@VQR2b~!sY5sqdkcwCG#S71FGkgx=2_wv!Ep1s54v}= zt7t)aYMKIp@uZTcGFbU_NXql5)8Aw0@2e?O%DF{C{BT8z5e*E+Q z<5Y@U|7zOpPgxS11@E9u`Q@^m%Gd3C-86!eRCVBao}Ni$ikbrhlDlF3|TH2IjhG;i>qGKbVh@j#``3m$Hpn z4Q^gcRI{$k3Ys(8{S3;-kDmdr2tk32)(&oh&owk%z#?wz5A)xgnNy+|E zz~7KChZO^lk4(*4q}*k}G#R$XV-O+O017{sXy`i^8^P6Yf*@)O~T8h}RQ2 zcG+%;7BPJ|YJ(I^>rWPt5p2C>_zn5Nbsw6{wxpPGRIJg;wkCq-lnmT0xuD6l^c8xu z0P3?9U37DZQzP2A0Q;wEpgYx_11ojky1AR4ng-(Aa<_nf$921Q&E&?jgrnq5B$m4B z7vOmb;mnWU@iwM=VTJT0UR@{zT(2qnM$N=t5=^zT$}d-7T|=B=a~Pul=2SRzr=Gx$ zQz%M)Z?Gob>~LxX7{g1jeGB<&EY1_~EaJmCrx*BqDzAcJruwFJ$fLm8(}?3ZU1MX} zHKXb0TN83QlMPE9*&&EiHE>T`fjMRVFP)>?zlk*{YWyPAsHl6M=fm8#F@Pz`5Od{7 zGWO~9o!Gm7=c8|kq8sr2aQYBv8kg<5hxFyvH^pzF@y95AJlqi#;jY6>AGm+Yn@-jb zm{VfVojQRTr&Kgh97T6;b`+QK{W2fEz4u`yZ&%YI<%f}XsKQe;(7v-15>KweJE`DE z<3Og-fKrPtGB7@qdZ*fiOy0vvbLd(8dvO00fz}!&usqcU-6;x~aY|CeZT->ZKyW)l zDIMP-8;0E4+I_hM3n%l?-p>;xpna$FE~)9a@!f!!QZ=;;z9Wr_r5~fO$J9bZvpd-} zRD|!KMS=ULWE^t_fjJco-Kozo;}p@F>X~2QLovWE~RCC&YFJ1|QDh5;v8{9uec3Je@7PIc9BJ>RRgvis1gK`l|dxU`{zf&r>oA zu#%_#oKM9{UrY*!A&V{GdKEg_RbQ;2^1seb?Z#G7iU;MX^5xgkTe(|r&F148vlc9z z^)r=Z)?n61#N|h#m}8`|b1PjD25KKVbgckB6=4uRjcNN=>)a z1(;LK(4D%?fR#LD#^zbzBdqF(I3?cG{b2Wz%CZE}_^@Cu%CoNf4_QD?T|8C!=|?ES z>8O31hSXcW`_@iz?_PM?o?ej%+!5PqznQ{Sr4C4o6*2i>V$m~rZ!N*tbyR;>MW-8PY_dNHrSPCz$SqR;A7-mSP)8E8Gf zm^TTy=rEmy$la(Sy__RI7Gr5*ffs!c88RLa!Ly1{3UP{>5pNxsQ~c2LRAV5l)O{lx z@ZqlpP2jg0jV>s}Du{j|A2bPth!ps2f7NZG0PQ+bigR~Oxg z#xZB+`COp&97YqbIE-J-d&{(K^A*lI%RhiII*BO_mDKCimm7`poR<)%f=40&_IaNC zOXujx5q}1`*u%>_!FqqIzqB`ZV{3CbmMQ${PVad0QPI=C=jckc5>D8`kHlm~6mnfX z((Rj#<;OXy+P4$XuL6cD4l3V4oVr_~!V1hOQs_<*E5b^i!V-H)FYAF)M;kW z6!8pCof%yjJ-B*vzzE!r{_gVHJMR?{rPO|+R|g!Qj-L^l#E)K7lhxgU?s7N=!P91Zw6nbCt zFy~*o;d8gI#^TmAWbe*Lg(7Jbf?DCVI@y7WO&eKI4{vk z48a!$pm#|2U4ItNRFG6-$&mGCku-{4k5_K104 z`cwB2a-FUz+M=tOH4BA%A^YR#Z!H`DxnpEB%na^JAV1Tnwpn^UDqgC^gKkUbnDr)U zw2~N?3GO2K5n)q+E+NDzH9gf=z?@S3m+HQe9uy2&gBlZI&&jaDj{NaD5rlUYp2`cT z&qFecF>n9YeYjgaYy)(&JC*G7XO?u-Z1fgn_R6_Eg;~|~v@g_RyA2>tDd_xY2j-1PgQ&DxXxy}h$01^J6w>6o=!(5 zqHizsSX3*dPLT17ViWPW=cuV(8`^+7-*X;_Q-=)o62P32f}W>b{b40fIS!-J$?D;r z>Hk9S&JQ}FM0Pnx=I&G$iBORhcu5XgPvl`<%w^=&He;gff!fa5B;S>fhfch)jW!H* zNGP)by(SQ+FgMFq5Yu-9nUH+U4+yHmzg*PSA-~iYXEUd3n_8TgThL z?_pWJxA6GGdw&TZ`u!>WTSEy+kIAQpSLbWv!^aAgB?gHdn`?OM3 z`JamfcUTp$*g914$U61W$Sv)YwB)lI-?chLeEBE${ZQ>4z5Dxub22q&Ms-f#OiSY} z+X-q|A3SDg9d?FI?PrKnZz|2vfjLD9-6^Boi}plepnNP<9LE=3o3~^lX}y(V7`b)w+AWx}^#yUQ2rHi`S(o>CH~4wq4n59m zU{1+Gcd7+uoQlInnCRj**qMBGVCyy1+C_KU(m{>(l~#&*%(vrxakusdYMMv1Vf0RQDzX zVb{@$a^3_J8WJ{GJjg}Y<}arDq+`>l*|d@xv&ruM z1noQdqwNFV6VS;mLmp4wrTY1Xo{ysNR!8Xw&$8fbTiTo#Sq}WX&$A+{9hg(7(48`q zhm|~qbrPNDk;cqmFPNR)bgX*r+neNbEz0ME(Oh%vwL6`p*&)gtI@J#l@2J6U0-KBqZCikcM0xMJDA4{=Id zx48gVo|=H}RL5&r$y1%VE`9o#J$>ffd$^2i1A_|^3tuGsRu0(ZitiEVF-ZY0FjLT;EZ*{3TSsTlTX5fjNcvFZE9;(Z%8n@|n|?xN!TseplG`3~-WOSZN;Cex6!EP5%4u zdlJ`d44bbvo{B`gl}%WW5{X1z_(fz$pmBua75;*Iqo%G6;?z9jvol~$8AEq!5N4dh zi9yAed12{hHTJCp?{u)?wfqist;8cbGmzC z$@@wm0af^0j#J(T?@NeNf9ARXyx+G1-6=enaq7?WzC%tEPDi`=(j?9mDx8g=k2R@5 zKRrHa@n?&J@As9QDUNK2!=I5NvkCCb^^#5vE1%d7$b`l8`m>Hu zwE(BU?w1kQrG5J@zqKZMN>~%bsgsEXQ(#Wv{!329{JAn?wqx!@-i{x`;~Y4<%Xnqc zYaZTUAXe9!cWm&t&lA({#~eFxJ5!OW;G{hY#uo`Gx#ekr#Z!&1({^zA0i^~Ir!p9> z`hhv63Ee3(bC}6fjPS_dlReM>{yn4XT5h_f zlY|7sp`F+dm>NQD7~T5&bykxP@8u`t@q_-(ev?<|>(yf2@dUA@?*_tqOb%QS@J^^x z;M4W_jmZzjQXo$8I$;8wmv8_*Phro&N}l5Nlv~fSzE@F|8(kw;E&QQKQzr}`N9ULK zgpi+i7bs6v+Yj}KJJ~kShBn?Dx2iQp3~K3wb3ad*x=UuIiI`zk32~~bs&xvOQ<%`} zK0F=J_c{^=-ZT2^rOd};Q#X&nr}r=6)O}SRzE{*K6q4m5DU>r!1+8avql>AqV{m%; zj|Q2$uQk1Xnk)Gn-{xIjp!cwDSMwVyK%9C+CuarBDP8DJ3B!z2jx7j3$0qoZwJdOJ z#u+>KdTnnGESsB*6WI8O7Qs%PT=va>trO(Tdn>+Gu8Zr>r9n_c8F6|?a@y`u&jTA9 zw*NX+Q3b5-+l21aP#CP#eSh+lu&t=!?|cz)(l>+L87!%y1%}y+62bu5M*sj>{b+Z8CRA;~IZ(w<9;vdUXQ%FC9ogZMa zggs{06L>+j8Qw_nD6I2F%j~rv$KN@5B>l{uUxGS4kmw@@pCw)V;ME}e#ai8~VBaPp z7>g^?lLc|=G;lZ`m{a`!Ql7fU8+$~XJw|5!X8!nty+x@4TTFe6m*ux99rBZ68PUIW zUz!t9Z}pg{9W=PZv)x?|r=&_Kq0h)DB(Ib6`%{L3gSWW}J$9 z37wOPa5vE`;+Oz||$~z}2g92_*rqT1}*{AG_bMRaj{w*!VEV0`WUbR^G zrsvPCW-^RdWXZ%A*)El_92|wLry)+gs9XW)6c6+~<)RBKd8&Ll23tS+nP%X#tN8C7+XByHz=&a$CjV~SeNau#lMyxD$8-9MP>@Oz_ zaY}-2eH)llPSBmwHUrl)|NHl1;c&t~ka;asE$qQAI>3CCi|pYT5M5Uq1^a_r2$lHjR5sKW-PbIBzk#dqs~X=j^KO96x#c5U!Y?g`|bWX^z*orn4|-L z@|K)+v`t{K`T4l)+e?U377h)kz?@ou?vxVDI7QT|S`cxeQ%klzuJ#36r0Bcd1zaYA z?C!E=F>wd@y;ufWdgZ8wN4TzMrHBEo6hhc`)^ZM+a)h{n&hS}UavZ}Dr}ip;J_nYk z#GpGh0W(ewsm0{^C|3+#Yz-WZm>iAvp0G%Cx!#%5-%p`sO9JgXge_BN??+6B<{0!i zrsZ^v>ZKE0dt8jVvwS_NEU9$U#(Kbg3B`;U4Zxf-hwjubm~o1;8{x!451vzO&&oIj zlc?Ra^~ss;lBz7FPqSh!2FR(V0s=S!k@vl=y#60hkY80BgnrRCwPz!YA+`-8a8V`+ zsRj2Xx>IB@;}nCF$G{d!gZR;`zfIUpoO-&&;W+UynUsBS(C@e4d7fxo zLjvEAa~`}(oci;; z&s~gaY{AP@5_3~-;)$&EVBsl7%8s54%Ty@%9&$aFglZ4kx(yqJe>Vc!hS9x-I)TeB zyF^ig?^{_f5)fJB!F>rX)b$v^oD%w%>b@a~IMS-Otc9^DB4LkHrk)g|2}pCbycsC| z5!&mw-SxNb8|YGOdi(oh&28zEJkRP(cniY*A{3{wXERDP-+yPL4E95uGNNaU0p^r7 zbf?l_#wna)6#a9@r{8f3NWTOR$BEs(eyX0->+=_C^KAWr#c$4LWo>I}M5yAd#xr{+9<>gLElxS;x)<`FU$ zS`waJ#a9rs>Mmnn!Uw*GTol(Fc0@tKcKEY6s4iRn1oO5>X}Fc_UWZggJ0-gSD|O!=r{-w#CXVxc0!%{^4lyR9HJhB`Iu>th=?O)^c}mT)#PAdk z53S-;thl$Pl1}9>O0M6!nr}2nKkW{OXI+{=oH{`{4F~2F>%Zib{cYUZ`NGw{&e%vF z=4ij}RPQr<3EAG*sN#!m^`5@J=fjq3cSk4Yo<5Z;Y;>FUHkoqT@e{JMI#cmV+r+HR z`&}6famts`p9YvyzR;bzFol)6FRl{K$u(S8M7hH%h|z&bQu~0PUt>$q>usb<=MEAm zPbJ%sMx=bTKn@nKXnywfg|JD3_I`oUdt74e(Jc5QHk%!YQyw}f+rXSUf$kI@%s9oc z?qbftl4OS*9#&M1$KoyVytuCsLJkP@01<-UKI;- zKXxUVe7XqQcTT&~`da5gxg+uYJ`6_-%jaB_9Zu6ARR2yz3it4PdM*cXDx-4mJ}{>k zq1S!2TA=SW3_S1Kmy8ijZj66Ltq~D*$sS4AB%%~NqV$a`krH7i13cFyu}4Tykf4fX zRyeh%*Pmg-lbg62tM#f@l-^?!ZBkzIEyStsjQ(@LoC<{QR3^+gRXL4tdnWlQL|_1E zr_*Bix!bwPj4#vKOnj}@PYH1UloH*Gyp_!xW$KaZx?EF!Z3Q3J@HgF@V@%6yEl=d` z_@hCbLOlKE2+XM)S)P?9*LL_B6wETlyA8R1!3;56$NAuJZ zY0%+v`NaxnL!6Ry+5k8o{WEl@Oc!A#PyJ+6K5C6I{FTU@r9zfIw1kCy4;wzL`bqXw zv5Vw9XgzH&SDlqFzhV3L6otloxPDrl@pv>q`UQS&ZN&yPS&Bgw#3{z|hF8Fxx)0r{ zd2Lv6>d!r7mz5*a+lkv8#4QGa&4=Y7#9W*FIiis-rd={X>#4v$gFx{h+>1rtwq^7} zr?g5zwjkL4r$7OX!1? z>xBd`r!JxADXnl=aVpr=x%ut3A<5)D&!Lr$(SV#k~YGPD#q0k|)lw zzR8k7mof0jq{9#;(AZHfUt3(H3>*io=S(hR&A2+{Gq>YYtZrNjcT0?q@Ho_#qUHHY zEDE{UZ!0CtM&75iS>s41V^9QDL^3Ic@A!RO+; zuby)T>D@LC3TAeahJw%0BORQifH}n-JT1LOXNs+!yAS?6{`+~2L4Kcl3o}k7pDWYW zW}!d)DW8|A;+t#fju`BGVdeBzzBc`@f$Tc>cgNL(~b(I2CggC(^~x z`ht;Y^QO8kL$UDvqZY~^%!-+Nn4vY`zgLfR`sz;go^WBi!jSSgo`*$hGj>#(#y0+? z?dA;$Gykv8;OBi}jM4z__r3H&!PH-M#b<5X~|XUJvvV^x;ykFO50e7E%Fs5zfr)t2Ee-??8X z407Z4C3ZP_!1s;~DfY!X+S)yW+NTkD`oeAGIao~vNAAVA*`TR+Ex=K@Oly>vDI@T zIo6m%R6PEZ26#^1{S$hEP-BNiV!TWh2mK)InWIq^D#;altzuzmn@gsU9EelnjB4Y+ z&Zpv`J7r`A-Vguxc`9xP&PH}zVh>e7_IS|tviB;sc=wKo2=ms6#wO$6_pweh{9ydVDW&!_(M zqbsx|vV4<{RfH#(22X2z<+4E1SZ@7qJ^ClvkADSgya z=Hj8aNUx8~$6EezcQrO*FJ00esRC6AywCo9P7XOQ{J;EM3b!4{uln=<>&WF;@a|TH z!BJyMrKTY+hkneOo@9D7mz<)CTJTmb`p@4L$xU$*{zJMk(_Ch;q6Ue%V`vq>XKr9? zD>ke~PuH5r3F1_2Rjm&&r@laUY6fTgakUY_ag%teF$ zNn_`wSFpAq-P_V!piFErGjUsDvt^H>2XU&80q-j?ry`&`Wp4@IkN)?0>VOu3JKt;S z>G+TAX!(H~lpr|YVeDN}>a>C^ja_)qdTI#L?i4jw&AraUc#)~fvad2THM;SDijU^W zmhFq6bAK|3Q?GS);(<9u0^O-kA+VCC%mm$4m31FXlV&08H9L3?Syc}*=s2;+d|vpu zM+3g!*EC808^J+EJZXSL<>8WNO|f+7s`iY#*!2tLzR`RfokWOJG!B<0!0J8*=uTOz zz=~6Ksq#$XOJ`fe%X!%ntt(c7oRb~*r$hNZzL9q!>;bK3_(6L)4Kmgp`;*Mk`IBA; zd~M;NwtLPj`0th328g>~W^q{e)%i1| z#l5pT@&rK&fw95l;CnN#&%G1Pz8am}R?TA!eW%Q>Py6*Dk(nu0DH-!3OT56%65@F&r!u`zsEh|26?Rx1!Jo%}{MG{ZBSmU~@Av-6?f-kHI8P8uP-Ygf3|Yh(+mNK= z5w&kgOc#ij?zsyV9oRPi-FKKS82#dk@8mo($0X9er&~h$d_Q~Cm=|Zfg8n{REWU&& z#3?h~M*!b5N&wv{4w!MOV8D~%@jzFuA)V--DV>?Dzh?{suSmGpZQY*E(A)*>I}*X2 z&uWQrn!LVYPdFooIIF9_&5^z|`*g|7bLHT0kEjLWRKH_=3oxfxpgYwBJ5FuOGhzG* zo!g_C@7oXFTA6Ata$u7OjhQ!0qdc7hIkh_6@70f2l1E~ZpXT=6Q#Grfi$^MDKrmf| ztSFUvt>6UW6lF!J9x$i6pgR=;OHK)-ZGFAwoXVW<)eu#T*uQ?sWzZpaE;0XRE)(QN zaXs6?qPvTr(0wG1M*?}Z<%TF-ldBA|9IxFF2|_D`a3M}j3(CdaxpJ(!YZc{NJkt?W>2h^$bO!JQIJ4o7y+uYr>p5$UmABF6v_(i1t~xt` zCYMPm?B~-pC@;;r^HVZe_@EQw6k>TSzwSn* z#B@Q$z?@2gUiYzDfWFr-kf;9idBXAXnM{k}wd;Pktkht1)iGASJo@=zwnDl z8*LDLnH@}e@QN@D+?Rmih&m3;DNg84?ZJ#wQ}rqvzil2=-rYRcn%zSVkx%|*E{M!G z!!v(EtGNPlYFhVZ!&Jwfj_jcDBi75A^OHAmDfric;w3u9xHhjtZV+U_eF@SP&3VA` z)F5=HVqnLqis*o=LhjPGa zZLQ$G1VP&v;SrEr}OlzOyF8sb~kkMPN=5KzB-Q3|8{g zpL6sWQNbk|E<~zhgqv*s2O;~%fi6cW3^i0Ydf>S(WuxmUPn;!#sWG0`KFdxjR3AEe z*OO)CPW|0OXMoR`R|4XctDCwyFsFX~OXuiHv2j(O13&Airge92ikt{{p}g$t^<YA9nZTTS z2i>V*JDAB+A#e>$g9DbBFHNWOJ8~lq2ofX$4UvDQt*ns+P=cJIqUgoocj4m*;R>LN8=7A>F2j1G#B)8;JWpYLwwNC| z9R+3=q)CTxtU;}hL<;v&6WM;a`*D+}nRVntoSLm7;0NZ^59oQS=qaq!efAD7?NvE5 z=^IXzcSaxOUQHDCmE-j^wPowi1{lSG_MQ7ALMP9vdq{F~j4v@8N#}TwvniyulFtp2P}2loU5|PQ7-5y@Of5m%zSu%%8>RBqPE30Kz9vA53G^aZB(-`$NXg!sVai_zS zD)4jLaG%8OJ)2*V4{jMSrZ9U#F#}pxvHmHmD7IJa3r+qr>ApP1wNm8v08krF>}A%JJNHLl`z+Ug_7vln|iuyZ+Lmq zNGFDX7vhwh){`?}PO(FGN;n2q>OO?;?$;$;D_WlO2JYz(F?kRz`Hk>D##MYiY-u3? z&qsH%|5c@4upbz=z14c)=DfKn{BRgUu#=S&ly*YCb6rju)Cm|_NjZ(LyH zeKj?;S?YHnr=GntJ-@GE$*slF8*Rbx?oDKwY&=%z89rv7Z&es;X$m~VsRYLGU%;Gt z1Kp`|m~raQIl6vmc2q){HHnS-t!d%icYHepY1|KyCd<&c!TnQ}rMlS_6o*D{rPOeD z<5^>RRcnl^Ea_UTJrwdrA}NZrAx^0XpI88M>fyh1j?RGQxssk#cp-pxNPzaU|B}n; zYEhBerIcKmK-BodgTLqKZGpR=9f_VVwrB2Z`ivcoa=%yN`mmz*^X*zqLE!>@_#VV5 zDu;K3z??#W?iAQ_Sm;Opb3S#U&`V>LzS=?Y$Wn#%xfS9`GjikwJ?luLIrtns2d6N( zuzTLU@`pla_9I2R$32^QS)oQtS%&n+lk+3l|It~FQxX!i) zk()?Q&3Jt@$~FWiuI?>397CK!@iVXl<`l}mbZL3MSBq8oqv!WqY3J#Dtey6E>kE21AS?Ha(m^VqXy8zrnT>P;q=wCo_nDcLGN zQeaMPK+jV&hOkoi#ni?|%1H;%7_;#)T&P-p7pQ5?xQm5^@rCr`Ej74*ia`4wr}evc zl>N0YJYxj->ZgBC1$A%Z9>Ev4i@pk=T)78v%9Adt5tvh-pgYB93oGYS2Ul~2lzL|PThTw#Q`w~#1Tminf9pj&J>d*7O zsi@k;5D)Jcy)T8UL?cGsutfGR(UV0U#K@R|-rq-!Xt7`=bFFeHxURj5z@ShjaT2=B z()%@E^1UXLUC5)y;J$>$SXozKP96SBc`7C-`cv?u_3P@IS!wafjK*tK`jwgK!KAUf zS1)t+eE;UDQgKG1feyV`;(U0y!p?Xf4-Wf1I!&R|!sAnYWPFGH_I_|*!Z^bQEik8Q zp*vLzGfw@f`&1^+^y~8MzFE+IZMtemJ*bPI*kAsTHFs44&Qraq3gJ^2frr0^Zx%i+ zbTAJecp6GY&Bt8&*<71Zkf#^5f%_7oih@4^b4u`Es{72ox}%Tx8#X@nL~&Ve&nx1% zprU)c@N?Rr<1W3;*v8-I?H7s86o@VxF^@h`#uC@7$4zmTO*eke5EcnYS#aj8`o<4& zO2v_-3s|1QfnN8~PlC=fVBj9|pSn+%R?(e_f7U95+U1%qzl7Pfz^bEI)lNXy7<@i8 zOg9{M*yo~xNAp0iLE2vF<#%Gw&eW2yW<3NVpNVj8IEYi;A>uZ`oZ9=BoVvFXLmN`g zzkoJ%M&&YD^dWA1$xAuB>ux+PbI1Ze+TXqeUwOq&t56O4^<^){oD>R3!q(@cqmBsi)x8?*~5 zQ95te#D-h;|Fk+ykO3Rh7{(si<8voAk5J)Adwr$D@aO zKAZmF%Cju{0;=P2dh~URf4GNg!{3q24LLin>-*I5n}CK%V9mGw#JRjw8{*Vx)m}F+ zr_!L;eWymSQuqD2-`Ax(+a3FZ?w<5?vb9!oKxdiLr7f?kK&CHy1!&*t71Tv+|7{$C z{@qUe-N)iO?_F0bl^Ze|@#pHi%Qn*o9HR^3>qJbZ_RM0MYgHdnr0gp50`t zJ3T4#_UGT(t=|ozk9lqnN}2ub3u>;`6`VN`&1$Tro7s=|k<3>|n^fA0JJq4fURq;w z+t`FSMW7pW1uRcVLa+Pe;y~YP7|hMRD~`^=e;N#ND%?(f2$)kI|5DvY z6t3iahZ-yGX|GhmPo>9qmUO-}H@<=MU_lFS_+>)#cV0nTXM{PI0FANj#e^+yE$j62 z8UfZ#riy~u9*JY$Z6Wsth*S8L`T*;`B%)pu_Xy+E6p4ooNzS;JNnMq=`j=kd zcMK{k^krmkS0sX*dJ+6~PsB+28Sl3QqsqMsl10)W6#-1Fa1GXje9M{JWf_Q59gH0r zz?>R|o~MkRU?op+3c^>U%p57eTUhYu(}&z9*A^XftkBeo?bUlKuz=RH=6NGxix?)6 zZ7e?p>&@8MX>QlZ@Wrw42b6=Ay$g&4LWol=TGY9~oKk`A)Ls; z+%c)DI>5g2sgb&q=d;YT>*73kj=Q+Y^MfX}0Un%jddd(Cj=Kn%bM`^-d-bQQgo=DA z^oySa#%7@T@;1j=%A!ya}Q1#@>qp2IT zp4d(yqR32Nl1pFBHwwz87X*yY{}71G{IGw}r1NKFsb>PjsYm5_RKW67E_A2v!HiSo za52Q5o1FWm$J{Io7re~naBl1&bp!1xZqK!;>&6kwzi`1pR<0v zHnXfQ9tmU#Y{s6mggC`Vht>{O|Rvs7`)SmN6~FYfAFe*r(3Jm zLz=Njg9AAg;F+@AV7N2oLbNd|L{Xg13m?ueHGy;gjT#>ZxnSso5X7lCZNdd$PANcl ziVtR-DtPX;`psxL#wrq1WhnNk1qT5?Ys&&|ShMhtkaOQmD~6pbp1+BRB}aPZ@+ z{VozyC8Gif)x^~PvBEbP=kFm-#W_|2oR1y=-KjK~af+x+X#x4NY%B;a{L=Ut(s-_X z*j~tSb`#cb%LhT=^C`iKdR^az8Z@RI+6vY>B2Ud&+H6krbFOOE^S8fBj|0i7%aLc1Hb)bx-_CId&w$_afn#!bA3%YXy2jP80BdZ)4A!0 zO8qkD?%DrnL>b=9EUw7jk9SVS->!=g;?xdd*Pq7l8w!mm?{R z=EOLGAZ8iF0((R++?!mwPQ?su` zsq>yOe9#$(Yr(ioO#FgWISIe!DAM*@XY>9tgTVpBDH(3-H^B0g*}qix{W(WZ;z$iO z6vs5f+B#+V_HjF%`_H-XZZZ477Zp?GzkLbA;Q`3G!xu3ct73JQFUzD?^XgS+((3ZW zGT(<%{veQ;hdAZDRQ>{(QxX5tIl8@Iv`O>4p7rpdt*x|@7U4^GqTf-u4k%r5ouBxP zz5mXCvgagXP5*AgAk=l2?_~)8FhOAr(e1FC>DRjaFI=orv#b!Oh#Ao>fjPAZ-6<@X zaY}^*;juwvk-(f8R|!=e{G&%%=^L{`>BmCbQ5G@a_hRw!^>aLpB|KY?s)3)jukad^ z<-a2qDe}GI)$?=~c3xX&h*Mv+-t+-;N&|YHnu>&#x^I2R&D`9`0^#40x`KNw_7S z|8w3h9p7KZA&sTSXWIoK!yA`(gfcm#St#jZ9!i7z63EMY_klST0zFR+7=ZR)7I`2Mwpw{9Ys(796 z`O%f@|4?_AQBihnyuc}GkdRP1rKO}Bq(r(wx*KT`1qtbpkZzrfxn$$l>P8FwN{X$8+EXliyw0&~g$x>F{w;}p4T7is5be!m+rRiCjCzxB%C zUlc81IkrycfFJwdnX6 z0hm)Z(4G1XGfu^cK6*NzAY?6?Sj=!qGp%ir-q1W;z!%coJjxjez85P!@@yU#tMA+e z7vnUCQCSSdlJbf~dAPA-|EGQ^DuDY)QPfMMIsg+A)$&WLf>y<9%<8T*8v1FsLP^%(0j% z?+3&w4rX871@;en>r0;#qb?`4Fdiv_=8mK_Gs^ZavH;VXY*`jHz>>2yvkpy#O&Q}iLk!hB>1ubETXLqSoBq!&+i z3hRhnK1AxBUe~kx5U1Mgc13_WdL<>nL(YiF$yb0eNxXEhF&>Vx@B`OJ`dbcA#KbaaSQhNZpI!17cW zbf-kMVJ1&;MdFfYVAlSWiW2OTJkhQ|VQ~1U;EnW_%)e8+5cE6=Tj8kf(Ic_$Q6L-N zTJT5Ko{N>2czs9Y0mWf&V@^|TD#WR&jb?RxAXh-^EX+R_oXB$B0Y5ZgoMN zT2!Y1crTU(bf-9A#;NQ%x5oO(-ob_YC_-Xz&%;XLV@JCWCcYjubqvI^f}BF-AL~v=}*-X*)MN} zum_BCq4{Hf|HuZPL52|K^DT{?0(@T$U)W&v3$X#woN?oxfLfc8$~hq#;TeofnkVi^ z&vA1(RUuCC33nX=bL#zd>Q99;hjw$Nv#nXOp55ngTqehwp!*SmIEzb_c027Ei`UgW z`rRSqUP(fQPn@oSwym-_?k@}I*IU&dqpP;&maL{f^e==sHAcfZ1k9s$>|d>g+VyA)O-`)0&+v<*5wd`YUm5xmU( zkX$(~ALneBLmr;}{rweR&yN@or&cvr*n!o3CVzG6J~(gMacaSgQ-9{C81hA|qdi6> zIkyiwyO5&33B8+ELD4N;{>TIFPw|s)`c`u^k=>e1No4*KPEqyVCYYVzL{;oL^O=Or zouy!iQ{9W00O!#)uUQ{GgV03h?6-JdXH+}#0%FRU)6_gc6$%d4S>(moRUh5Kv<+Mqb3a=>5FD(-a9c9>G&V<`?Ax<5?_W}66ulhPUb#;#eK9L+otk+Pn zOlg`I+egIAJBj^m_8tgd;umDD?!_@I;lfD9VOD)f5*y~pGQkt{;@CCMgOv1R$_2d? zXMe3b#Ho8bxp#p%wR@fN)Svf#6G}4hS?3=Lvk&IV#$=a%=c2a-@ZHYLp?mDjaW&^@ zFq?YxQAC-jxP-{d2+yrLTYcp7O`jo2nWw6aDTo0WId$Ov)a!xa7r>lyxK8i;R--M* zgo(6yR>_dk6qT9mvatkQu;-}1r#rdt&8t^k&7%kP{ zvwj5nSOj`rz2)>;67l2Ah=H_`5e3@e{;Y~kanpuza34K~N~Z#tQ)_?CQ&&0eD)*xB z*@5%IzrSzhk5i0Xyo`)t*u2LZb6c-gPd?Z7dA2Q#FKjkAPlMLmEK`!7%9Bb+DwTpF zj8PgYP3F+$IDBVkv-&Zr>HAl)r@3IKWU&&LfH`FY)hY0~PJjNc{QG-GCH6d~;H@fs zs%ThADW;4lO_Dl?moj{5>5`nXuO z{KP;`v1K=rKDNlOoJO9=PZ1et!a$^U-;^+`e}oFaAmECz8(u!JxUm{X0=ow^4z zPT8$QMg$ba)}?e)7a^*s&fY&=s-b*`i>od$y2^_QdY*Ef!pdJUYpi-P2F&M zJG*^2?9W*_iINc?`xKH1af*aSDGiuY8_=EFfElN-_2Be3-<2NA4N-`rY6flD1@w<2 z`%C#K1W5?)%7dKBq<{0$S+d$-f+?He1=jH+shgXhQFJ-PK5#7hD17W--GewaqUkRH z%qjQ3)_ve}e39)q`9eY8YZ!1Ud&haNb&T^FW&7dfZ7lVp-?9<%+t1WCEq;uJo4o@4 z-Ffot?I%euzTP^WOvfl$B7>URS4@ock-7)Vjmoj_ao)dxIK}3Gtpm&{3FuDw!i-aK z0}5;hy|b$ken^Ik9j8A75bK)M(JGxlvB<0hm1=En|tD1QFuoaQp8 zUNN2V;+_Nju2C^2Ifzp~OZP{CIh7CHDX{0%Fff-eIrnGtYO9}lV~@}Dtf*RRgDBa z()yI);h*RiZ;NwVJVT%Mx`6NR+hIQZa@MBk=%^DAnJj@-5|D^P`EmH<=9|5OH!Qc4 zT|FUAN!y#O0dqNfwmkg5}HO}PUS#%>K)AFsXzBP zyse!HiR7ZOCvLL#7H}`0u^{zJ__=PqyX;_q0?weahjz@1GE{}z9M5eRXL&(UTWm{ao5ouc0a=Y)TMZ+lIT9K!q^FmWF; zwg<}Y>OusYZ+_9q)OS`othTHVVgt#xd;FhIJc>cEsMWVOU# zN)JeREN>X0o}APE2pi&5lcu#PFsGhEcPiu^tkiw0&aMh7$8RcS%w$xJd(Ti;$er|b zH?|LK(OBsuG(b*``#a6zXgg8sTfU|08n0u>9!q^UE!H95y6uD8s{i~l65^DI!x#}T zrxc+(bqi*kx<#z`Rlv?+(PqLU>DP*>)Ol}Zv7jhFMF~?!PB{YTc`CZiTKna@@2+Os z5VeyL87cj!VpjgynxCEZh{UJQkGme?lymv<2VhPWLwAb60{r_cK8qbHnHr6FeziZP zlRC9^V}938`a8Am%Dt3!{R?!=F0&8+{hbVMMW%Ku_Z!phCp!zAjlo1wxL_96xb4_G zQM^BFHe2@q>lVeG(0I;B@Z1Hs4}JB%?*I4SsxA8XCp3S4|0PCIAq55S+h1(r%x$82 z8aeIXSL%O7F<`bHQSABzaw@$?Ha#h2JGgdNnf_K^mhp^*gr}zs+ya(ntll95d94n_ zsmGKF)WDoNf$o&B8hHKxzK{N$7RA;b4|S=f$UGkL{9`rfO zJ!5;WI8BIwUY1_c8jw@B8Q- z`L24|+6?p$4Q-Zo;Hxt6c6Uaiem4zOa{N$BegIl$VdHngQKtIcTD{NRoTYFWF6d!4 zM((oC`6E>BIvX}$Lm*B?+nbvLbE*ouQ((_w;e7NON7+Ft>AvcWiy(~4X`UaWj0wXZ z3t2U!wkeX1NkQxU6uwfEr?n0@qH_{a_xri9Tyn&4w8$6+-iq)kMZ|<^4#cVMvaj^O zoYI2s6zt?F7P+vy31$)Vy~!gL6$aFdd1Et-1HtcwwT|6QMZtaaj=qdv!#q8Y?lB>2 zs=DbmiSNg_ki|^B)HwP<*2c7v^#J0OIOPF6FsCq}JN2Kl|K2IKU^s7&z2LPdkDKy8 zzH)D}AUsiEGTwKyJY=K1Xa}v6W-2xEP)VlX!Ktq2))So50bExt<|Zq*{aty7Z9O=m z+YqP1HGTs;AN}REatg~G-zuMZUj5;RXC%k_Bc6!ifn(o09KDP$L_Z}*MOYLRr2&uQ5M_e^FB>ZXd*K|e$Smb z)!)&u_V$I=R-Ho(1;i#Hp7E{7S%_ zYJfWD3Eq$Lr_T8I{i#U;S$OnNyYN)|O_iC01d3SQm^l5XKW9Ezu=}|BKD#<6uYx_} zUjKG^gVDI$qyuDEjzXF zfSf`%Nj*Pb!y+$!oA3FiGP`qAirw3F2?_2-QH+$qH1-@7#Hk|~^n|7Ap8$au_6m5#|6#Ga>L=n5RD?XPF zamu)iA|IGjdeEJ6vjlywVPG!7jxap=XL8_yMn{*Rx!Xy4HV!FX*N0jb%7f}&!a&gT zOecKyVAwSDGe&hD97)xX2F7cJK{QmA^4eLvTDbl8y>);v|S(Ror&2)@6MIBJ`2^v|sh`Ni+I=LMze zk9Rwhcar+kpT)o{{fr5HDi3k$mpa)TFsBm!>J&Tp^RWY;lli9~`R|?jbDpQ=H1U_g zHATeS``!2WMJQb->SRNFhy1bl`-(y9y;oFB@c};wEp*4{ zObNaa({_kcPwmqUfH{@@SEsJ#;II0%laPO5z^Tav#a3_WrQBVI?y7r%Oxu<4QhQ_5 zrM(jCuMK>iKy^pRuKT)>e#0>}&Yh1e&K2t?#Hk-;oKJx{ zCG%IO!oZGGhX}%qQzdobCZDNGXe;@tkEEDvrvoD2bz-))OTSxcZl~e~t+&TqN{~!K zT5K?XrNPPoL!4~`uXja{Q%;If`Q$J|*QGq!{@>i$8_agk6N5_E~r~b^N z%UQJyqN)p-O|Ig&JJuZIK1jkqdtryTmoy80p89AC!*z@W(RhV{-vc*%Hx^Vp&=JYz zESC}{S247$=pOvnpNb6!c+cqT>okx4r|!Go#HvZV_#8nI-(>=|SU#>SFN!`1&Rstb zYlz^=iA|)~E}K<>wJgyl4rlAGMP&{|o)_=!Z74C#f>?<}e=b3s8h&^y5|~qOuT$Ok zXCJa;zFJJlaGliSv-%TL9Z8e^qqYp>K9hzHQFys%x7;d^n*|M2bPoC$9&8~Y)T zimWZ>m_#B=FP_Ee;u+|3< z+U@<;D^)YQr1z8KyLu1VT`wkP88Irh;4k}=Ds4{2Nn5ssbANwK9%pkx1kR9iass?> zCLo5L9+*=UQ1|yCfO97-+%x*esS80#q4hVCVTs};yF+2KJLgNn9&ZpA9`5OrAYaXe zdt)nV|1#WP8J3}v#+h;(SQ_xaKUnj6!dw?B;F#Wco}CBYH>1$v*$OOArC&3r3`Mk$ zNm4wEQx6Dpmp6q5nBw;z(b~r`&<`!i(SEmjqGC zGJd$*Ul?r_`Zxw5g!z3{jS2ZT|Cl>16NM9Q(I#ijne* zbvs_|v%49~yF_nEm#y!f7D5-n$2?KK84)L6E&77MJ5%*BXM>(0cz<7G=?W4srFxEH+Tu2OsORHxQYjofLA zYuiUIAWrdVkY@vPD*3N<-~aZ_Si?+zDx1kIO0wE)yr(RpmoKtdxhlj5EB^aKw)^fV z7$V^F9qNmn75(1b_}b;lK`HKok&>8zIdoedp8EWdV%p6ZCB+5el!L?mEnrTS{k87< z-#+9$nCVa5`mSV-Er1}qtAv}gAkkb`Ks2#H;z!JFo|awJ%v z-J9Ok-3?jK?%J-dn!tqX06Eq8%o&;0#@D#Z*`3oEgQ|fXYmH0d<6=7x3z<~Tm}ycr z#3>c3uufo35kYtA73??_iNc-Pu77}?NBOp_fZtB`oP~swFrkXvG&FbD2ISP>Ojz?I z_AhsHmPx`X9kn_(_nBqk1nM6ogKpzOqXZfI5T~Rx+t-0P^%lBQnlR(kYQ9IjM+};3 z6Iwaj{R%qrL%Th-HW9yPxS91z#+9J{6mdr1;Ze}mugy*Qe%JVVf>s;9ZF;ZMobb6M zwZes$mOc=t-0g$lfI0OVx>KOvKTN#uyG5ta|FC8B9&-r^{A1^*uX||WC+HWv(037% zlr^P5>&!6=wy*X^)GYf%cghpDIJ=U0s5w~lTaZ0+h(_hvO$1qpQ*Ncow7{J5fbNu@ z8MvPL_j8^@cca*i;qI#xe+-dmR@-NOQFS}WVqPWkQ|NYBh7`ytD-Ap}RvfQwjE67m z2JZ0Wzk9I5+Vy(l=S_Y$iNf7fghGf@WYiC3fjPwt-6`0q`>^8?$3D%`Fi)AKju0h( zD2lSz-95_ZAe1w&f9SdnT4$zXe!A-R=S#P6yzN80>XXMckKc~Odc{9Lk*FZ^D)Bvn zIF+qM01wQmZ0Jsfy$ApP{(T=Ep~~ZVb`^ic!y_i^h;AWj)fjHlr<%lU4zuq+RonnM zg{ixS{{W4Tjg3M5d*E94ufxU3G9+nz4|;MO_717mL2rmtP7VTgz?@2e?iAiOxSsj< za|!D33bUFC*o#{(6C7}(VYs0uRNLYh1HBBY)aQ-3AWrp`h_M56$_2Vp;Liyb_92(RM^cxkXP>jD_M=^fWCpiA zjT?_vS<+=q#=nO<3|i-r%M*+cwy9SvKg-x+0&Jh*)>iLIT8(kilGy3j#0Z!aL!3gR zl6?fsse90!Vp0KpuVLVQA6qk`*2;(b_jQXY6|e6PDp7ZA3haoc;z)t57xcn;R$yDU;boYDJ_VB>rMa7%w8?z`q=nS zfn_Mk8aqhJ0T8F2*!80Vb1E6SQ{ph=lxv34jUENLh1FfT-P@bPNr-0=O&VKlhPI*j=r)b**-(ZNk`E8y2epSg!sW{JwLqPCYJt?f}fG7toy& zgqb|`O*VY2g8HdG32}FYY+_D4|A{J_!-My_B{pjxEeJtQxggb!GW3MT#)w%HHf1VQ zX)~|Xx^VIB)B3orpY*#^s6m|KrJ?u$%qbe^P7%S3Q)N{M&r-R1cj&|dMjYT%2 zB`UUa@+>nOcG28HPJOn|eHF$wG*o@?ss=0O$s)B86VewtFD=(MyRnZp%O!3?oD$G_ zx(&>!BIr&r!i-a^sjf=%Cq!e5TU49}A>1A3sh(Wkp-N|QA12Htu|ZA=oRhuwdT5m6 z?JtC+5_mF#e5-2W!0F3Gj3mOF8m2O zT0z?auHfmBs_Wr0H)5rSijNP-sfyf1lGoae)Y4Kqbvl)LvYp>0>u-z&$fvnA7V+$z zUo=3R@+o8T0_K!Abf=DB#wk10D60?otG88#SZQp+k@U;deWN&a{r1S+44utLK~4$Z z)#l!?Q*-xaE`D#_e2FE&5}8m+J?USWhxRI&6kW&w;*=hBs4Fn1?m~B}17@7Uo=0+C zwNT~{d2&zTwaczXe*mI8QT<&wpU}e)2Jkt_0$s5d*r}cZgZAHBpI-)vSQP%CB)T7I zas~I%PwdUh-6+P~)YzZY_VAWY zzYc5bXU;+EjQWCZ`L+n@EX=0e85Qo(C~L#g6@g!jD8ojzsFnGkBMjoyYsY}sz?^ys z-Kj4y(=YLhg;-&splxo$9RrpOIFt)^rL`KS5L;H=iF2t#Q zs&IZ_P6N+`v5b5?>$C|nHSM=Qt zjRK@Sgd5A++#$NZH-BO;cbtHn!YQkf8{T40TqKU>#f=r)9^3XlOj~txjn#cDc-`YtN z#Yg&)qg*Akc(;x^G z2)O4o^fh1>S(Vp5R2<^eJL;?wU`~lbcMAMD!NUAhxD<=H{&M2}sQlx@dx{UEUCZZq{J1j{$tIJh3El-)`$k4XoU*WwECJ?JD0HW8d=}5DXqScw zIGDM_(*{vEMyv+8%RkhkE^~wOl*^+)i&waPh{*gLp`S)=)9xizP2;**-d(|!(M9zQ z`cwpQN~R((4De?8K*FF!%YI6o_bO~Z>dZwnK8q?kFKOdW~wAJDOTN90eYT0 zwmZH>?uuLM@f#7eH5XcN=}F9Uar{queIh#D;(p{SL7bYSd2FO%#DQkq()yAuYb0$Na!n8{Z1?E&F zbf;Wl#;Hjf#g^QXZJNkugC^%@5vTU=59O)KGa9TOG?B5q06CR7ajT=->~$=Bv5_ga zMp~`ZB9UhFGG6l@;zJ&#ib>rvh*N1Le8Rw-vW4yxFU&ag=e`;IhjZdOT7KM=I#C>c zCVQTp-=OP z&v>DhZ+2&vx?9F4$`i8XDR>AyVn#g7=6Q$5#e9^Jx=`C@;?ue*O~szL%Y zQr~A{*eE`5Q0Eof*^17OtMkxQ$ZdJUFuzQez0#-(+i&^G$ADy?xI9i-bfYOBF-Bay z0^-!Py*>&srw;z=6b$a~Q+HJ;4NqmSlP;HlZRbGZeiyRx_I|MPAM{# z?|W5~rF9Bb$<697X%X$TU`*cr{k|8SPT1>C)l3M)sjuaX-oTt9`m0m_+uzp@J5D_h z$4mU^9$RKJp1QbNHhuLd%`{f z2vg!Vzv2$0%8OQz6tX_>g4QXnE7%k3X~yMVZ4(LaS2lRNRI7VF^A&~Z+97>9p5Y@d zh*Jpav6H}@>iBD(`rkfevRcsh8V2?uci_3PHm%>Wz2vs6zafJdEj(4eY#YfU7yWR$ zO1%{HJZsS(n+uay6KA+E`a~lbC5u0L1vfi1)- zIm+w~U`{DPcd7wq>b}U22;GA5O{rCD{;hY|bA&h0DP5Z+TfB$NCCFkZKu$4*%wch} z=^wwNBOd13=48~);vyOO_e|DDnqpmG4#3?IHvY)`58iDSV9LzX{AmiagXjGG& z!M;|JiDVk|jk9l&`m&d3>05%=Ghh!^_u>Cj)v&UqgB@( zpu)Dn)eUhf-7yQ``RM1+of?H1r~d3ic7>lqjM?Hb5MN6v-V8lXf4$L;S>aq0AMGA9glwD^>ocR+7`68D5~~y6M(aN{^%TUc zN^qE&@96k3JPO{2Jft3I4a_OYYgYFOE)2*J6PaW8kG8g;I$x67-4L*5eSnjI?tgxl zptzn`oP3BKKmBD4>|OshbAzm2Ch}z z7wr1;ouQhOA4W`y`OKrapx8*6j?B&~70Y*Q?LjG5`|Oq{1tK4Ot)Nk&=u=#r!7(Vh zfmv%FUE?>D{jE<#*h5V@9lQ@2WsmGJu)6Q^I_*RL;}r67u7yd{(Boj#mh6th#CHkL zRpkYSl+3!is3@-LucYKGZ3Na|+ER`SvbWUsf1B%l+ZTY2{e&T{#uo#ksK2R!5yK z&|0gQNB0rYMp5No}3ToQ(&jEs5t=6qs#m?PyJW#{r4{n%%hJe!rwh# z?dPD-?#Hz&WXV~wK1pvZFVP=gF#mCa2g*~Ae@Ll(&E?dL>T^X9Icjv|T(@f_qZzcv z;JJ)Bs$nLo$N@Wrq=~f#%&F>tj>;l&!zf$ zMCY?N{JQW99IrunO4zYDB{bxCN4%R^ZYOM6qkXAU=>BYN79`BVC9;CcoI-uE%e<^P~f8c8pI;&sc1)3!>CI3jJ;Az=+U)z;Af zv|a>t(k@0-9X@w`o6V;LX~z18cwK@B!QWz1Fp!Rz(9zL!z)ta(Q~=zEjPlpI@9G@U ztA0z!68PMI7??{4uMS-hmJT~=DD{KBiq{Oh$NMqY~!?A(dy1C-Lch&~wGFxOXE139jCRP@#BLKSW|r`5c>LeBy)d z$5v0y8@cQ6Ax=?i@_hy7)Hrmfz@HN=%z1X4dq`%d5=M1{d0?Si~(4C5b8K;KAqGq!fHROme^=uEN8|-RXvH_Uv_qLi2BBdf6xr>vvn+C-sQI}YI--4Vn%AOoWpU_~S zXPHbl&?QZb`&Nrzj}{i^=XE@Zl}V`94sj|~W1(cH{9Suco7B-OBHR#Ykv-Ls!r#&4KSx}LaqCJ zA-M_$^33~}m#4ihghr_`YKr?5f4|3A;`-@os}wt}lppiFjo zpo7QcrIeL|ge{$%+rS?y7VzftlkV=T{#3AWkV-tCdxsBmcO>TRNralt4gpQ|#0#Bg zZ^PHe^_OWOPIYJr&;WDlBUGnA|NpD+)xSS4p`+UMC#LXi%>iahMX{wB+Y9nGxhKA* zd)SI9oM${K zCRw!^w}xeR6UE@mj|*z=H@Lt>Cthrku!caKk}Dlk1LhP4)I1dmJ5CMRg*HCkoQ{7a zw%4H>Y?Dm^&sOJbP?abVPLO>X30kM0k{_0ViH`QD1&Wc4fM1^h$>!8Idt(wLTCt=W z;-dveh*SHNIBUS1(t?_&kYLEEbcE*=pV>&eUrov_h$+i#8x($V=oM>#%NC!XmI19( zm4>3Qjx9Rh)4!r9bNQff8;DR_+roqw%%&9NXom#I4=Y)Un z)So<6hin>*=qsTi(T&ePlW<3Xx!QlRQ>HwR#Gn=A6vkZUxzy$I%=Hks?UW)`F*d`jM5T^uR-kJdB6z?_5 zQ`V&Xmgf z2XO@pG54!}Hg=+#3kA`dPIyjSgRr22=KFi5(zR8*Y|2{Sl~2RUPwPamr-$$7arM3r9xLQidquD5 z57vfnW#(^0!bay4X1s-8DC6+?Do41>p~Qjv=>EUBp96Dh`kFa~jWg&a6HoD#DN!fR zIRW>ED+2xr{{?-#D&Gd@Ri3F)V96>HVZ%R=ha<$Pz3W(v#-H%E1Fq?{)Nr%Jd`g(G zCb*C8I^sDG%qgyG=F|vAppV6q>E8qG+%J(<*QLC-eveAf%^VgOzs0`FGnnp14X>l+ z>D-J)oE49<-S+rnQ~cfaOuKL=#y^Ui&Hwlae&45!%8LigDaUK(R7)AFr8HtWH^9+C{tD>E(ic zPHR>8lS#ahtihczH?P84z3NXLJBgbu{6EsK4KDj; z5AndA#V(POj`U@GpuEacD9Q8%rImVu_bzUvFI?KlMa)EVt5ZJs+<_N7<6f4P;|g(V zZBfz^m{Z%=%&C|c+l%5>A{CnowCh%H5NW+cS{OD@TEA36iM!@Bh z*QbXbEi?XWA6U8Wie5Qo>Aysyd^ft253elapq_e@5m(&ov6xIP79o; zY7t@{19M97nmHvK@vd`L5OK@4nr6iFrr-E2JzgXcA?AjuajVHIr(A10LgQNz;BgDc z)}PF;1U!;+H(x1~^6T)@l1dp68hShr&QsLHE0MsQvb<(a9s8Y8t+u7HJnm|IOMkn< zx=eCXto;7DL41eO(3MkYZ;LTCqSc|8QeHmma+xfq*6%D{Q5 zpKK=(m{Z5s%&A1)5!A(v^&2h+>T$0Ys6?L*?FqK`v)}E!hkpNRj%kbNkzZ%?XPh9J z*yIU{4|1JR$|8zSr}18%*Rh@$8$Lux06TRkrqvD1sg7&r)Z%+f=h4D`qq`1Lj15Mr zgDJTWbZ%a7v!Jjt-@0`Hz3Bp{EtKOPBSVKxrTfKIf68;0SZ{*6_!2YW zH=)t-@G>GFXMEt>Z!^^T7&Y@wmj_O>V5cT*(@%jp<$c|pg7@}|`90mY>P4b~q=7}8 zMF<~8-m37R4@t!IYA()?T;S_Lnn*@QxnsfEp(sc3nfo9=dLA31XJw2-3QDk&)seJ zGUlAj42UV>lI^{ow9}uy+BkjK3~_24m-rl*Q$5$rDg5{Fcjq_tdTJ|+Z`sZ=i83)r zJM()#KoEXrdvY~z$rJn~01YXTmBOu5ApMd8Ma$mPb>nv^XDN>1)8>9 z0CS4wnmP5EOn2~rs^E)hT+${^Sa%qY+NVg_?M8jxsr9p~JVnLr$+g)0`0O2#WG7SU zY1N&g9Bc2R=K%xgx5iD7ZmZSKft?z?Z_f_QsTbGGDe65uIHG^yQ+ga~PpVOLJ^V78zZ{Tg3O9nAdVMRysB3xCQdR{WiiWiZHz1~oM7tQ?%DWX%-- z-iLg7&73Oz{7^`>lzspmzsC;~i%@h56<^iSI`CwkF2Cuj?$gYBDm{|9C_K+eUMg_T zfP38}a&&k|@KSHj*v-qw z`b3sb@VC0T)UMw5#T=AAam*$woMj$Oy7dM}?zN#Q!3(MWN#D$HSNG@nJi!{^JauMI zA_UB-d)LgV6J8TarsPlibA}U2B`MfnHz%UbQF*PXZ@mx7y>g2DIK#4m^l83qPQNli z+&EhDmlcPs8*~G*Pd}%5Khyb*3~}nl+88x3r(CX+Q;Hg-=o-CqxHrQLs*2g;Wtr7n z3Pnks6Nxxze;E3@UiH!KkQHu>-uKudigI^)1{W7cl-T$FR%crdX8D%ts-F1Hn*xYa zS^~263kt>ipEC6wJ&| zxyH&(edhaivVQ&;JIl{kk0PIL>to;tiL;(yRHVnC=c%)nfPzr|vyNE0^44UVufI6< z4!;HO%))$&{Y6;A_e)Y$uv0HeMgX4c!VGnODufPv-y00fd6q;*i0myrF^oK3_=q{L zsb@X5Y%7I39Y5oz$sbq`DlVHF{pyQUJrDg(lT!_GYEnJqCNQUv#ku;M zGSsv|fQW(f-F1J73o`{e*v*7sMso*erK^osK{|mJ4 zVIGy+^RLAuPpr3=zu+8`Jl9njW%3GNQrG%@h`BR+!Qx8@sjj>cR zzJcBT-{`NDKW z)7(17EE(?8@3`@dINMq;5;#-@ir8xNY~|V~5T};P?_dDSQ!_V%xg3erKfK4Uy!!k0 zzt8Eqw#&rEdmJ9j(UX^V?&wNQaqp6u2xEfQ+x7c$xk}ls zY#ENYoa+Y3{N&o$R63%%cyOrS^t4DaRy4#ZTxvH4U`~a3A!F!%tdiU|7yW-er(pH} z_8DJY{2aw=|@y_7I!78>fYol9)B>It>Q*!ouyu zAx_CUjsx7^#{=D|E|_uZR;^N9h6kDBM!*IkqQZQ!GS0zABsXM{T z#*N*eZ1UDh!6=pXt9_RGDC|5~-lXzbfzeKcIKo-_N6Gzi|CZ5iXUcC06?yu6?rExbw>nuKx*d@w7&SQ)fccU)xDpT4&VhYg|68?aVk@RuA zuhInKR93kT8Zf7ppgZ-#7Mvgcy;CK6VR3>PNP&I1QjPmKC}&O^avOZ4-*~K-YI_oG zKu!gp*7*B)QBP9J4MoS$4Cmgc?OzvuNF?1JPQ0K#c}5xqaY}_!iwc-i{?MJGHvs3+ ze_!|gnMapIU-X^mGP3cW%oRW`Yk8vhtK|p37GtUY0RrfG64nZ?Al;1<3Lm7&a_$h0 z^{-*8{5m3VHlQc?gC+I_@ifFKF1J(}U{0Znb1l}(YztXZ=>1>${Hh=I-@ZQBnn(8x zY&X4E`B;#?A-T!$c-l%+nVBp!KBCnmfb`*2{q_6SrdwliwZZe;!O!_$nzG{Df?aDK zFTY`)61#n=)dBw*;uMPIU0q;KiTF>ce&#SkQvKBYpGS~?uln~_^$l$8L$C}1zF+_C>w~R*$RCalgzuLw+1z(u zGciXh*Ak4qy>^eF{^M3CUjEf{U7-0&MQG5*93gH<{|Z|t**|Qnsxj0aQO4AT4bM34 z_xKXHKb7)JRtlI?ue^{+4F})LZ{2kOyZyh<>#Bc$^|`^&KIA`6nHEJ2pOKw23E2kZ z?!FqYI}tRvp-64dtUkv)dgTRis=v5)HN~AnWh5tdsnMe6`0EXs&D9swC%1mNWEvY~ z7J~cedSN_|fH_5Y&77*DD#Llnxf;4V#;<59F&a=6SRy`*TSI;8PV5EPPbng&vlmamfAC zS`FMsx2PI-2bQOLubET7@xJ{E+zvi2p3qF}lp!6(!WpUXO4_2dpU^nI^5QmgKGOV@ zfpPJvfEg0Tv@qqQasr26QX^7xx4{y(Lc#hbZc9f)oYQ3F6t~~3zmRZk#8*OS=;s6KY zlwAP6A26rzu9;KKxE`n*EaP$CMheHz&}p+9Pv*nU>JIy2uM6Je8Ebq$X$`sX_E)5qKRCr?@0U z8h|;~d7YdZlJ%YRLzwSZyz@Ilmt>TC|B3PJir^_X1@B1DwlepXQ}Q`-C!B-t;=>LI z^h%kSUKbUuQ1SV``<7-i_D#S-xZyU#CRY&&d-Yw)j&@*9Wk9d{Ah`+#`sjc1RQ^Y9 z`ni|s%eWoM`0rab)2nLCE)oi~u(&siLF%ET3y^6HTtpsA(mr&NzdZ`x-9wdE7lqwSF&+ zFgY=&_qM>CVuI>a+n@hG=>68e?@#?Xe`=_yp-n#&c_h-#B1j5p;HFDpUZ}ZcD3W0F zujZ?|kIh#bw}wnrC=0!dOBpNvgs==_yz+3Lk%Y8DaQp{D&W*fx4`^v_IMZ7YJOJj@ z2dGY^fj=i$xX0no`BOI}(dpaG5tw9fafi!R^(74Z1Qy_{2m4fPRhqBfx5f7EvS1Xy zS@QJ&#`F0t^DK%p6AYe|hT{EW!Zo0+pBiFj< z*E27^6VKC1Sf?78qhNQ7?5)zca>`(Tm~yP%g+tlQ89R*S_x)d!1mDaiMd3cP7d_(d zU!`4#IQ4{m31EL}@R~V=6#wr2!EkqnjK(qnL4nf5ln2eh`R${zvzs2cS58qOcQLdu zRhI-|?O4WQ$%MRBbHgjAexuAjQhaNl z1gD7CD-yD?*MQJbdl=ivJH%4b()WDt4T%vrPkq&pxd7%A-gR>7*1l}F-0D)|*=}O&C_;ma26AYuZJ!{aunY53~?rl%A0zDCxr0d#j{Yb+$`_0)R)Cz`(p4}xv zZK=)%=PC0Nt$bj4>ICZhzECYt?u3Or_02Y1c#>!)Ynx+pgw1b$W^|`I%Q(Ez>di(i z*FqsE_fZrFh6UQc4VttmwEn7j!9acU4k=bYzdc0+Q()S&k|&yM;5@ZL1=j@3sbZ*g z-+%pd7^wRkB#>u0*6#$UqY9w5y=U=TOX)NsnZt??4bHH(rUm6b{^l<~4Uq?G+%$w0 zbQiT>i=(OT&?#KpL9;YE8WSk7!qf)msb%%B6<|(LL!C?Dcz5;thl#omq1S0>Sv=xG zPUDp2kuSt4Qis7PU``Q2olBU(fStN;@R2zZOH@ftIPtx*m)QFk!$h3Uk!$_ViPT3o zJ3-I$sknlzu=M!~;=CB8b%n4LN@u^EwgIv$ca~{UdYg)4Bg840GK~UYP8~vb$_{3n zV)2QL<6O;{!XL)=62gIhAW)x@6rh8D**rsjOf&*=N_>lvf+fr8-tn24Qt)#s>J0ba z-=x?*kc+dhT;NTgryD|?dPm*d56r1@=uR<1=4AimbW|Po8l!?R3NANKJ_CTGv?O&-DM*RtP#_2F&1Y{O-YDe zlz7Zv7_&`B3UNw6!%PI2Q%umEqJCERb@$4oG}C!K3l8=id3J#4K(1n z51)gavV72q?ShU;ej$S%I(w^75V@v(if}|loLjFIE77!OG8*C(xkGy`FsI0&JJkv^ zPW|bl8=OlNB$!_`-6p1vkz+Ta!gIkg^Cu+<62M&dH~sW$hAN<7km znR*}__d;Z)ihq0$Z+7)1_0dtDyn)RprvTw^-2jWEYaITbC@Rp^}aEsXQeidl> zvsI?@>V2Oqv;CTp^lKD2thuUF6T6~wEG!Q-IjRrrud09g`{}2#8iV^&_<7wTz??$3 zPW`DrPRaRnZdf*#_p5td7D$+iTClA$roP5~#BhZ295)$+@@ zRu&t064}~cbyvBsj*`A0iEDVowno-m%&KRVSiJJHPgZ7RbYq(O#Jvjht1BRz0^ zs-^Ob2Rcvjz?i2r_o5jTa)}f%3n%N`l8i0<=#=seX7W1eD@%o(d_D>gem#ppcA+8CRA zx@|r6%{*#4W!8)fG@e zh69D&2JieaBKew<%*&_V1MeGtK|Iy-D~b_1Phr5Ar%)av8L0fKA5}f<5Wf@rZ|82BFvfKJS9AL zD;~N$wF_gO!aD9U9eC@me5+`ElI?CtYYz`1%I%NOxym=4#{o}$YPv;_HQy-2cQ5{| zX11)iZaA3izQ%R)m_hikCp%T)IU6`nX$i_edA)=x81oe3*vE>5bxLP6k95954-;(y8Qx{dLkvL_~|vZ4Qn$@@u?RjvqhigYy(t zS}K(LkcDB)Q+G=CZ(U=i>IAUSsw#(n%%Fdtmy516Tx8czlL>grb3JGG^d)pDBVUH*}uDfiX|f7y6jJIus9asC+Xvr=c*w z;8x&K@zax%xn-FL@Ko&O`StzMg!*>_>rNC@w?1U^;ID@GC_>hb4TB&3dv`gM@TN0Pqw>MAT_E$;hzo!*T!f zP9sT1HAB-N^K~_=t?AEXKHAN+5Kqw){2YZYPu0Shrx4b+9-3jIX&=xQh388incr4% z;ir3vM#$uRcypaRA!3!pVa3EsNxH22%5KOR(O-Ajhs%#=-SFZ^O72nluNi!50` zBJ!bQ(jpU6@80)(VoFYuK9gSNU{B$UKVyZ?Q_e8vsaJ_{Cq|3yNbMYQWr^aPt!0@} zujj)9<-CpsHB?2{3FbdDM82v-{gyrSGV7Q-mU7goLV8m>T#yCMQ~7y} zp3r$J8OA*Ii7HUBEUNUdB4nBc9Q(0$^$uf(etpl zc>Mjnt6No-$>v=p9Ob5Eo*9pCv4iszaMzBtn`mB~a4Ea>|1sCPty z{s4ugr;u+1`8Oeur%(i(@$HuxOvbku$0FEz{9G6_Xum{OYN&5v#*uW9Ha&xQ>cxW0 zTj)F`31gnR+)+twE_*DH=95c)@l~YrgBeq@R4x68OJf)s@Vz8Az1dfPiE2rOUJfx= z#@lX`4w}tG4Y~?sxNw0Ih0^i}K44F^yWku_=P7p>^OV+c^6$muD~+eldjYrhhP~)S znrFPaq?!D?64rq{WzLabQgoo9W``PsvPcgh( zGKJ1l2{7iV&yVd&6hjdnaPBmz>@Sfkhcm~$**0nZ%1J-3hz#;n=q9SL_0PsQKXPMt zQPy8XWV9ZlPhJF+yN4R}CqA%cT66;f? z>NG?xluIfFG{K7`3guMt{DR7=j z&r^ zFHg0=kGZdbCK$Ye;ip}0hCbQ4UkULPeUfS-be{5tF;AVv_l;dA-})lYZ^qncc^2-I zNDZF*XC9(s9}OE_A-lEkoZ@R|T8Fo~kDe z_yC=!GGNS85+OvMG6733wBBVu|H&1^Wr$X2;!!sKIA`bVKH#Y``)tx_qPQLY6u(-2 z1;y4{muZSwQ~}Ej?>sEE#4OxCUa+U+lWoMI^Hdv*dFpaWNx%bFAl{A1oE^ElO{*i2 zvSjw~4GN81%TK^la!h?!s6TzP$#NR;+{foXQNEJhe5^{S6pZIU=ocpPeO?;usc)zW zjL>;X0meL)Kh6^?kK=O zR}1)dBB`A*GR^bwCEH{%rTZg2UvQpMC}xs`&QpFc=BYsO2s(U~eeEpoa~|~2Hw)Qy zo+~5rxs#1s9oxXVubjbna0I6#y^6?^g6(tWSeme;i+EUH=#T&&Vh#(VZ1!LHCF!eIg*E@;+*go%`CEC zzT&mwsropaO(x0mgaX4 zI!|fCn5S@iNNrtIMC4_E2Jc~!U3+iIA7h6u;N8}Z?+yq05|UpSeU@uaca|Sb4=b=u z8z7KF%#fvv4Flb4+rZu3nH5_5u57#&{&I8WUz+9ia} zQ=u^CDH+}_gm<>JiQlY$ek$WK^>C(rRzm!Gi|@6nvoMgS=FU;=hSeN>b}0)DQALU( z(f!~n;tqwQ7AWQq^a7LDHn_lfDlsTW3p!6t!o>(+&O`FM8D_aneLe>zsUGeRtG@7hykwSk zjBdfH9)JVX(LW4H_)TJeCz7SJw^Y7EeU^q1&h#p(B)%oLTa2I6?ot%user@e9q2q| z1Y@3Z4wSe%uVU!8%iy2K?M=_Fi6&S10!xQE3H>M=Soh7|xpR#*Dpql%lMy}HZkixj zzUtq3ZZCYD}z^dNjragu=2q2Q;!(nFN6>a!N0FQH(ROyH64s&zCmnZf1Ci}RZA zm_)@to9^u_O32kFFVMf?2Inb0Aq7k5JT(ksp5h$|$7^e+bJVIJ(@#9R>rdQV8|EQe zFdeq#{|m@dOL-gQ7Vyo@uUPQ2Hx?(WWw1<#GSUm5oOd(+M*8$Nib58Wr>1CML+2@d z81vLjE>)n(57S!-eXe(=Emx-zS|n}>UAMtcK645O>gdZ;-QRl)qskT$b#PeW6J#FN zJmmmmo|<{9^?n@`hlaHz)TtIISRDMz^>o|@@>0Ofr~$6?GyuwH9T^q}9 zGj_5Dxs5;kc5Chn9u~DH*i#YZ+&`i7)FT-4)L1R;j zo$uCm3xPaUd0gTYrQlnec&B*%19t7$sGq@kimsWJ13FLD z!kDKT?}Sol;W5;y+<$m~^VySUr-{Okax9+X@eN+E0G=A&#iTVZ-g>EytbtHAKFtxf zDDs1}F==ptrlzvUI?yae0Gy}XW5}WGN8f@mPoYwbSQydSUiQlC>s)Y7B1W>O2S-S_ zYxNS+$N+Wpm#o`5Wvw>vL))XTZM@i+Dt`()(`3E8oh5|K)E0-)LoW`_Q{t^ylF)g| z48}Y~6Ivy}VaS27aH+lc4&Aj)FeaBh$Mb|)JJg&R$W!M^+w9&t{w9J_DDrR=gpai` z(czI?UcUN#f44#q5s9-N;wemgCp73hr43`AdcB4%?&4u&|3;%_!bQ8Her#sv*(zJj z87_Yh0&rc2jb9|Rlv|V8$)~-cjhQWC9+zCNBwT$qB<7>akyR@4cOjnQW$lKtAH533 zJT;t=cy#Xv{uA_IQ}X8%S+sE)VEVxehqM=c-8yp z+u$M8oM=R2C(kEpg~yq0-drC&h^bHwx^>^~|L7^ej{whqj{kt`wfqsry)@}KrnFO(dES%7t4=iAZYJDdJL+@I4SzyFSZ_5Bz6$F3wCoNxF? zKi_)D{=g-%xVb+1*Rpi*>X1hSSvKYToQlHFKd?i7++tEnV`63g?yTR5=#%MnET{U- z_0jVXm7%H{KQ-ikqa>Y{Om^jyP2N{-*X)^rPdX+C2cRK)+ra=d6+W(nrd6u#wncl z$qF9WQzLvqywK$-8yNGHW?1r-c{7^yh0EkW{&=Jc9JWb8SJT`j|chCAp4u8PB!SLT^)TkC2L_9!Kdu-F9)?oYv*9RvVI!7Lj%?jhkn>8u0P5(vw8yrO zQxBTZZOmny{N2QBn-{mNr=&1~UU!;rQ%LZmK|Dnm=N$!|r`BQ2Q)490cTK-^+uO>F zY6TsMq7K{QpYJL#V`4v?4g~Vd_;z>S%&QtC%=Be#Ci3P8eqt{bQ5zqO`KYCD(6KLjC{9w+z)@%kmg@}wrvRW5I z?kewgTgG~LeDHU2cP?#0xpy6wK3_3fc_~Pk{!B`9H;xf zO)4|O-jzdbd!rZ;lc&yW{u4>kV&*SXt-yI|cA}dRI!|4|n5RzG2OBoskw`Q&oOE|& zUK}X9RpQ1AWD)4T?XU#u=ubs<*pf2r*jVR+&1Ygle;T<9&%TWjH4mz_Fn*7ThQ4tN zoTqRcT%hcqa)vQaSu*w#;h6^1yt_BY`3%eY4exDVfp9BUmw_=FEnuD6?8ftTG56Vz z9NQ5Ccik|YK_#{5-_=A@X}A-Qa`Tylp0a@R)axlxUFbX|17n^tod|l~V$Xqs>+5$} zUc$pLC!x%v*j;(QIz%`VSa&%%4SlsCirhd}iE0?>z!NUP@A|ev9-B5AjiIVNsrW2^ z3!JB_tbPhW=cybR^VG!hF?-vi77^o4qnU1UrxxLO?@}a@yX4twb{)XFj~v@;TO!Zb zWjas##|Gipzz3;~+s%nCJzBGm^Xk=J@=8KHr9)nR4V|a1Va!wfcM)C{hgRBdHnm;8 z`tXQCII2$dvEJeB@duu~K%V+q%xI+3E*Ja3dtFTE;){wfnaY)7l8f&i+lwiulFI}_ zh^L~w_m82=Q+_b!sip>2(S2FpS!}o6k!j*%R?L(XW6xpQI-3I1$3ULil5;-BUS0s* ztH(UZpU{u@$|+UB=z;#bay}BhFtQ(>IS@~cO6+}x&Ql67=BcPbGtclvN{63uzZR^1 zQm_}iuXjzuF3jBVjG6-KY0GkDX2g5dtHOp67s2G|`)7Nc>Lp66mlu(2_!JopcYi@V zP zO6xXS2h>f|eagz!M^TQgLq$guu%m!@$|#=k3OY|6!kDL+o8H(8$=nZjvda}wNte23 zqImQ~$@Q!HzKsPLP@ke=f>+=P(tpyJl&ad!(3(VrU%1))3!`3X)}DQg!W!2e;wc&V z!y)KAOW!Lw&!}G6IL9OXpj>QF=mo#S<1ISZ4T~cuVmaY3WL1&^O zQ!lOPcooV5CwNRo1@(Rm+OLfx-38|<$&a^^q4SgqjCqPwUhNy|I)=)Nw5K_ev+eB? zX`Ju(ufO8;a=uLm@)QC?$+Lu=LaHF@O^d_Fj(77=aUX;q4{nNgXo-|=PbQ1~0OzS| z&Myz3^Hd3pd5UfFHPNt@bZLS^g!_<@GX|C?a@E7%?PqW)Om_fJO~qXt1m_8MGhSCP zwX1IDrBD>2H1a8blr2np`<`S=^}qtW?h|B8s)o)}r!eNJ`sBm?r@vs0eR{dV(jx++F?Wtf;1d0Z(a@Q=9)hKT6N8+F{=QDpe2MweDJz2 zKGr?~I!}ebn5XJ6?+0o}N2UKJ`h4-~s>=C6`s?#2x5fsTzLRtUo^oN^U*xE%OvcW8 zE$TEEiC~a1K8o>5V)v6|{?xN2L+-Z_Pj&H5u0ZFhN*MFh2VIKnYL|QB=c}Y6-^tsn z%?Al1&=W($>iHWo08d4WZo10{J*5wPJ&oRrCs~qr(F>RI&b}n@Zg%T-*~VxY#8dY) z3#Xy;ln#t}${0?Mrk8QW8?K&VZD?CgOiku-TgRoGTg9&c0bt!n5@b$Hu6T~!L;YmA zeyL*n<2@YKnwNIGEm2_*7A=YIB4Ue3S;2V*jfSPwpGt z=~!sFj_eCUcOdxU~(l&5YH~k+X%dhwi-PyK}6dC=cz26 zp>F6rr2!+JI%5E>f3b$bLH8#bW5AkWV=*f+x}%}E>q@S`N;l+9Gx7GBKF~+OqT6Kw zy2+EqcUffDj*@ChN7ggOIPFujg1OD&6njQ33tw=al3Wvk>V7jzw76a<4`gRPYF`O4 zlEcS@IJUTIBO0~>9E1BeZjPmp$E6*RpWg-by&&1Olw1Sn@6G%9e%phv_Umh;>5gxg z)~i3+q89=R|NK2I3JKCtIa@6HZGW_YF7}TIN2>RXq)Ly`p3){$>U4lPkAZNRQMYdo98$Lz$jDd50<*X~Cr2 zyo{k^;pz!%*Gm#Uu;&!Qq zJf^fFu~i>VNbb7+;nUep?oF)EfK4{JhNM5{eC<_6Ra{qy&WIS?s)Ar`5@Pe5e(r+a zbhzsu9{=4jaBm%Sb4bwVmHovz$K}BXc*pUjcNRNw{GUjV2DCqOuI9;VD3x1u0{#5n z+4j7ynilYGCKPEp=XPRVy2KBe>KWRCR!hF?n9F(>V1YgFZVij|E*Y&z zW!jE4Kf7w(_l|j~yngR5=dBHSto<4|U(osd0`WCY@2$=TJmw1%-;RFaO!7hMH(I14 z%I6ZDF{5&kM}NlGpnM@Ttyri>Ll1@K%nxbM9HEgN-%lQL!S>W}Ivp}BjAJM;;w?+-Wc{bS1 zq(#2;WnUz2%WGQmef$9K=S}eEMPe!5?-CGSrK5wu4 zSL@+!9b3*sH@spP<9^#_AYL5rCkdV>Wvd?uL6hf~Xpy0sC$Au!{r&S~VG`%%ke=K+ zX8w3-u~LfbXF5E5zPnPWQrM>AMBvX0HKSN#RlF?~;hjM*{bYse-5wOxB^@p)6ikJU z;+g^OdB{Aey6tra-8@MP)jVkh_SoOsr=6z-@`K76+~?AT*S0e{?^^_D?Gi>kBI9Sw z-kPs|D)u`A`w;b3i3R_C-C!0m-9{ei*GCQx)Q;xqa{&&=vgSRFFAKyV^W+M0%_TJR zWV3d-1n*Ur+6Ju)Ji){|!ZeDil6lK#mB09p7V?-j4KhAKxg>$RoQWwLIR8(h0_N2p z-tUW%g@sf}w;M8HWDX|b#6jL)JEHdeu%fH2n2yR#4ZVm9jZ{I5z(rISF8xA7gIfZX z>l5zF)twX9_5^>%^S?X7jUdJZ1M_sq5xIi>gD=>b#3Y|vPV}VD+#Yq~RMb&YkSx<* zg2%)Dfmy;Q6YJ7e!tn1*{(i6Oh3KMHW(S503MM(93ij#}SBvkSIX&ZOcvF;hb6=oQ z24G(KbKd{gBjBYs)Cu7WdIR zvJG}fq$h|)g2B9CAvX)T;-LA2EJ(hOsAQmxyE#`2+0|K6B$*| z<%~|Kat8lDDQD2sj%qvN)twjVGnQnTKB?a*@~%MMFRwiNqF zW#AvkEVQI|hj=EPTBMRp1Nld~YT)EFYmB5GKvdn3YK^yqeSUc)ttALD=G2d^jFUsm=*Gf(DfC&0*h z@-}#V<|^BG!1bwej7(HKzs}z}NN2y7yVWmUUCs8ZdN45laGmtH_ysRCUgh)2;u)e9 zRZ|3?=e%X;+>%D!A3VxOSH%XeCwnuvo1pRhU5C8?SU2p4 zi}lD%vyJ+96IPvNUsId?BJ8UAa8t};ZVkXwPtu(lC)A2;1=~G*2Mo5AavTyD)Kqf( zoZ%G4C{!?lD<{C7>NP=EfyPt$+8GkOQ}5L;y)4il_ci_*XOQE+>Jhj92|0lTsGE); zDzPYzq5E`=bgOOzCSjS$_FF!g=fXh{(Y>!^dvo6Je*_Y9ib~kZJn3X@PZ@5I9j*CM z%SAR@i+P*uq|ImV11)&`1&-J`LNorTN>y>gXw7M4gKK5i1Reg&bCBb|>Z#8l>wtg6 z{8xTY=_z4<9_E?aT!r}jb7kHl+PZW-4|KV)Ebw_d8Yf8e_#Au;*&0Q{_|e0*r3Mg( zRi+m8{P;QR{obFi%$X`$cV7XR|3n!X6<;@Ty4;>&qbl{!4Fluc0 z>qo-MZ-ba9R;!5$!0Xc1x-w+8E_^tJ*6$wX9TI-6! zkZW)vE16?61^6Zm3LPBTlq|5OTw?zJW%}NP;84U z`0S8;xCpbj^${Y3D-NzqDd@P$|KD(R=O4q>5uH*wr_HgWy?o2EQ50NdD@|a+qdP`l z>V#XO?EG)Ia{D}YKS8VXJDqCPl+X4_VcW&VeC@B)M?P@TI*#bx@l6n}2ucP!q2tQ+ zf5VmaKZdKA5rk6Z=&pWc*Q5cV&wLJ#kvb^D)ZL%7;K zN56!QEBF5mSGNBguG$eO8v@D?I0OyH8iLB1rIHXkE|5`ZK6O#3o*CV6g_l{O9QuPJ zk@#JDyXl7}`s2Nbn&JZL)PuL>&1xQ4O%xEWsCB~zpyMk0f5X*3VtsQ9+!vy>|NX9G zXdjL}o6GGX?QmJW))!WEM+RwwDe8nbT=hNGpvJ%dWM*$`-Gm?4qgC{$|FdVOXXw%q zWUB5}vj_PQu7-&>E}-FxrquC2))!j;7;uN zjaoOCMhM8upKqIEc*@E$7F&=9F^X+gzpf3LX)Hn$EU6}fvvc~u3J1xdV?5Z;q06Bh zu#`g`S`o@PvsLxtf5>igvkJz{``13!Z(GX9pz*EoS=<3~sBg88a-t6&NocV(l0k@` z&c)B`y$E``g!nS17~X~DMh(dLyF~DW>iTo}+FAc*`~iI`z~?k__n(j#{*1qoa%*JQ zjo;gwp1i$y9rFus;R&C-CaYjvf4qHjz9w(X1l$gB<78g{R!#Gfz(1)B*VKHF!O?87 zi=_cSDm@1of4mFHwa|?}Ul zLLt?j&BEQ}eu`@V;upft%@NqI4m{==gZ%t&tuOqkuUAN}YYWt7j2!k3e6vwtl8Oyt zsh4>vZXHBeb#uNMwdMO8&%`9WZ0)}nxqZZ%d*qhE!A#wzg0&i&E&^9O~P5A*^?a8 zLrR|b;_;yjT!%4y)@%&T`1?hB8%E~8;+y*VD1vF&ut(q1tCGBQBiF|PpNse93Xc8L zF196je(?jouz7{BuSSsTo_M6mcgrts`EDn->w-N}QiH3GenZV$hU;l?9kg^+&=Z>c znX6p_BX!U}o?@T1C#3aRE$7(2*M?(-`s}Lh%Q2{T6|v zzEW+_dCD8cJT;_El`45lAOMwwr~QRBjcs5xs`pUTg+8_%!wQfSn2R)2T#Rcm#e1&^ zQB2%fvWN(O2*@FqA($Ya+_j&y6M}eZC3O)UI#1n$F;5{q-H}vkTGDk_y8T1is&Fz)Tlzc_vIUc#>j0h_dY{vv&-%PVON;T< za#=u+K~MaFol~b~6|>m$WVW~kk4uQBSkKI%^Hd;=d5S1&c*>2D8PBsPfhdpo(LuLr zRiYO6#E*AJ_{l(?+6h5#gWJ9=K_jMaxI>VC4 z-i6LnmN4e2ywbjG0X!qe11Bs&pNP2EPdcf7SKzU-m$V4X0^jeJZFE_0V3d9^NarZP zKkDXx9ud?fF{XF9CFg4x{u3`px)PkHjH-9}p!1X{jCqRSDE6V8QOxqJ&&&GGJG1%O z!fk|B)taW7J{2KAJz)%K+a2>mz+0UFk-1TZrVG!^?{@DT3V$c9=!p(+Jn`a$c&bIP zk_&)>SwL$!={8N}DJdtcWRuuLO{z6V|}PKr?H?Cph0 z&&PySW8r=jhrNOBrdnQ&?|5FxO)PPoes1Zz*^l`&KC1+peJ0}EBxm$z_snq&2J#jD z@_vH*dCNx@{`~xJ-6s(R_z1<0K)8miQBOqb(UrW(+M~2n*W@7K9w`-eMUr{3GGJfd zNfc&cK_n;oXIrr?ALOsqabNalOc|Iv)2<0E+!2RXQvMkAUhe*RVP zh4!TbSAZvSGRN8v_|B)32;hU)AKSAjuCQI%h@zpqJbHqn6VlK->@9e+pK$m4W+pU! z{x}i4tacAu--|EsyCMC>7Z5+8mjC(r-|8tZh^M~F6fFta7*5U0%ASNOf0|+Q-;*8- zUJJCejTQwy-x5}dQ}k;x%a0;>3}gkPFSfirJUKTtl?)!>;kt*LI2s>=JymTcjS7vY z%#jAE%R{u!`JKs(Vsh{bQ8n)n{Qj)G@)ytk>9hLRex85C_ghu|lq~pZ;`G0l88j^! z8v1btL)Wjy>?H46iuwcN;P~M6t=IhAkqGd$?-MW?CTsGaE$Tb3XXO|X`~B=UGV34O zy18%3_)9t{ueZkkpYo_y`=5E~Z|$?X*$)Tm-{UEEORoFnT`8r1x~gMtlary_z#01_ zmQ=v!G6~3|!I*`zjd(xo1D{sTp7!$Zt5q4i_P)O%L3Ht{WQ#uhUU9*boBNiq{A_E1 zCXY62e}ZN|9K`pd^#2IYCo;(J$uY%jmCF3^q>jE8Q_&xw6<^agM6X~81$;|jziK<- zA239_exCRW4zEDrskS)YMpGcl6Nd>UW0#T?MzH7kYCdU0=lQoV=K1d>NsB5^Yqn0; zJ^8$Y#YGj@wq}2u3j83^zoG?v>#e9Qw4OLqIr^g!hxSV})%eFYv7m27k_CyQ%V`Z$ zv5L>Zo<|GNy@1a1i7?{%KY4WDq$7=SSRM0Igkf@{l}?1Y)R%z!L8T5|J_R9w=ZB)+ zs$%YSt_*angs-~{2fO!vFrZ*N3k^1UQ76~gmc9$|Jieam5;UIoL@I-^JbI0F#q%i9 zt!FyfPDhXM>TQ2i@FP>8G zkELI2<$eL@QBKXsQt0xi9gO7Bn|-^WJh~?lF){dS#)VNQ(u25AF`q}US*j|Q{P7H# zIy>O0v;3!54i#;N(gD=GY?LkXr%XR$-Qs)X1RHVs;9c(CHHCOerH2&CeY=a$?A!g< zy6JEIzWL_*AfWqG+4dCMDsZr`SK~SVAS7qdtNLUHXE`;hBXT8O3iMqh7B$JddzXBz zPsl++`xE|4GiGO7R?yf_+==>mKebG?@eH+_`%~rb73V=yci?t7g=XI_B-j7V*9ZBN z=kXb5qGnS??(b8b^b%r5r@QVjb4M>WklQ>|{{pQ4--kMoVk25=#~W$C(OmkjT$H001v{Y~ey85yzjOF=`Zp;aX+M4M7&?4J+*!JnV>-LAd zep0M&@A}^c@;uk0PNyC^yx)65AB|G(VM_ImQ1>S%>%$3=6f0&Z-oic31n2puRNK+3+s#5E(^1`h)X(PFGj%}oB+8PO{Meejj{y$c zmRj0?sF;4bS?Vu%!fVov3@rO3QFc0To`0rQRt-&_e>1)Xd-LR$)o zQ4emJ{*Joy(nPr+KnQz`L3atLhb~GiIcgV=3LME?D^=5U+aj{bewUfVRVnb7NK40< z{Ti17o+n2rGmxQ~C+|A^`cL)w3X+?89BAJ8NAzWF?0Fh2Wg5!oC1CNSzw1i6OqWd3zAXy>r^sPsGj>=2EZ;(!=@i6C5TK4_J^}+S|dhG|C z(A4K?N~!mj)Q6KZkq&2haJjHl!>BMMruZgy#aeF z`0$%6be_6`F;AtSjlAS6h$Cm#6o-?Z`$a*$wllGUnD8r`CzAsBJO_~jh|)~otqQdk zb<%n=WB6Z^v{G^n<=5=&CoN;)_P7~U)K395#lLkJU(COJcR;do*D~?(aI5+Yi8J~4tS%(qI+BS z^j-c|p^Bo6N2@+mgP3p4vYI0}bLSss+?k z=saZzW1f;kF+iHZlWAF@?U6BB+}lTO*9gVCc=tV$BW4r$J~&n7AvaF+iZMM4bNYMX zMel?AHP^EbED&|0gub%*b?I6`JXMV{Rs&t08ip}X$-w%abEt9r3EJy(i)65ZPa0 zjNJ-UzRm3r9v|2^gB_pWKcJ!u&Qr_Uwq4M93IWDEB}wAa5!S$e+wNB&UV^OX8q zll#zlN)5(5HFH|6Smo={UAa|5;)%+cVBFy?_qEKuLGaC?}MH&fuG613iL z1NVI{GU($9It%q$IDr)F?RJ%=4(FD)y*0r!udT53!uLTmHVUj7wyl|2^YGU zQ)7dh*?~RNNoqn30Yt6gYY4GdT@X*5oL8+t=P4W*^VB#W&D5@WYE*GqR&4`OO!wJX zN#t@=yCT2HQ5~@Eqi8ktHUIF_Pcp&rs?qSI}%lA!rt3cJa-|UnqbHJ z2A!ugVa!u8%PYO)HD(7^`a}#$fg|m%ZG1L^F-Q-GH6{W8Prb-#PcVwaw;<9ne@AjB zS?E!FaBg~8llv!xVKZZ^(D+w5U{6JAW&1+ssc9JV)a~D2(yZ4~w$Tx=FFj9AY{%u7 z7seuMS5=U6Hg4)uRo4<)VO(Mk@q4_&FT;x_ zGPL3l_2gqT*>4t2X328+XJm?c;5@b8#9jcMr!Zi|Q|sV9xfo5Wno&v-&nH+;KA-w? zJ!4uu6pX1&q<#`r-or))c`7`jGtkFT-G-`77{%Xf6(g=!PYr3XkNWx5Ob)q#H8Hu{ z&Q1U0H$8Jb)SJH8@PG9i0C@~j-~BH~ps#p?_Nf=j)5~8~(ox^~Bur_ym9b3JS5yRe-4D=l>5Ur$E6tsdj%+JQ2@QCb ziu-R1Pe#!Q-SIEAc$tW0a?=kP()RNa&rLr+`2G8L=K=H?f7D)=;4SX<-r+?M&am3$ zXhJZ-{LB07_NQ-|;A?^Ms_PTfhoJihhy6dluZ}RdZ`xJ=yCaK%xt08z#^Kt@FE3K@HB)?X;MgNJ^lb|PURLQtuw_fF zXIVmsIit^WU*FYw4v#Z9toEU!aVlzlQ)Yut>!xo8p~ilE;o()Ck?SBY(v+~L-;~y~c+x4I z8FkXtd-rM&fcL>MCl`$~l!Xu7a`!ey$ec>seI!$k>ZX(lH0;-g2z0?rp5SpiC^u3E z&A3gVwFQmamLt%(m12m6%XR{B_?P$HLPpR3H@D={#BfRm)0|9^w@|~K8V|VX7PU&L(>hqI7Z<$CLS3yU} zsu6CDggt?%I)V$#A%2Ye33MEegE&M&194~`WY+-G9Nzm7$6)=#GxZ7aO81;|9!bX< zULSoIzv=q#2j@2&zH(}KXU^nCqjYU0wf$mnTF4T|vENZi)6gr`tKpVKlP{RVuf2jt z&~a!F;!t-V#35d-9|}x!*zzV^D*ub|Izi^>g-iJR!=YzPeGg|g$UbIC-dta2azO;? zio4XvLM4yVL8dBJnGL0w)%i|;StthFG@31+Cl;7PR0m8J=r|k&ad^T9;xK*xc_U17 zsA8%pmX|tA2zOan%1ld_Pv=F8V^GZD$58spEJOk=;M zTvqcHw(I>He{2)QJ_K_(S=Sf}9f!|A99j>6IE;^7f^81_aJf);i`qtte+AYjwRw^2 zvTLSM86UA}V{$$Od}u?I#jY^>(Qsg$wjEWLi2!3`XFcl3#<5Nxhx#I<(Rz=IGcA#-+d< zPOX0xhK@sX5Qp4fK^%^yC&R-uhfZ3{=xWY24+|!r2Xq~BUOEv3z)vbBDW3cej|MoL zz$oNw9ld48h3RP;iDydaK2ntTd)h^fm4bHOrfZ?)2m{O^rSFhAbR5ouIP~TLakyzk z2HWeDV3awTPcwo9w@ZsE7enXt&9=HF*Jn`g%jb@J0URP3GsHWXI~Y8P)#UQmcjwY^ z#@@m{J~(IOr}|vBCt;Q>4d!sVjq40L4xfNHbm|6ixP6-*w$~}6bQFHsoOxup@yP6X zZyvZL#6@n3rL@W451up!IMm;LK8N#Y+bjr+aH(~MruU;B!TyIn4c6LWWXycsC}uij zFo#r%pAVqpa2CX2g&2rK`L!Rg&7q&Mm8Qej1O6q^(4Qh|=5m2+UTn6dSeXTEsFMJP zO*(-yG8DJi7bIVdDwO$OpxT!?&!y}qw{OuF9)UKMr@M{=74%0T1R7|9vw%bR4#UIGhy%ak!&30^955Li{+*O6vDoR>Rnv za#85Vhvc{|^xHFJlvmsw0EbxD_L-E8R}(_6Z^}(nGx)^0&b1c0P*M&b?Gc@Xt&+wo zgE<^a(?o-gLoX1A9xWga)e>o7d!2IkZJqB6=EZ4(UCC!k#S)WU4HeG8$Xl2#Mf`aH zhh^=$X%pJ(VNbBPGnleJW;&~kvPsgE;%}3lygoc*O&y{Db9l??M?Z8NwtzS^W&m-R z4!7A1)A`VpN9WGoLLX{ACi$Mt{rRDohX}Hv=>8Nn(Pt3=hdI+(QG^ypc$qfu6tZ7j zt$%fQJJf&Cf0CsT680PKg>kt7n8S(F!}ri}_#DKc+&GBCa6>Ijn9heCliF?erAFIK zEaG`aWXNtQnpPptNvR*GEzKAL97YreC)Z)})AQW6YYSNabZH#YsmlJ{5aDwIQ{?LR zb|DjDFo!7bH8-K-unWZD4hM+CSXATpFwLP#8@^n5GcEb6z^jiD9-U9&=YA8)HZRAZ zYbYH896r!3IH{5-4Z6m9>-72F3#m+gdBg`!+5sx?oE8;qndRcfU=D}PJ;|Ws&=<`@_Ea#D5*1#d&5m4dM0%Yn7|fo=V8=SDMt!>S10Okw@>?-v-R z4QbzB)}mkD6**xQ?+JHZ|NZlkhtE!yPb(G&EL4&~f+; z#9`kUh{Mce{YseT@RL|pd^XFIvQsDH4dum!-`2@Iua{hKV=){8$^i~-2Tx;2$fh{a z5O$6=0>7tR>#T+`4hiy!XM#DLjCQ*X9fwYU57|H*QdKRY!?X`aDEKWa zVkO^>rb^Il#jAPoivJK@Ew;`d|KLp|sKb*P>ra7E8K7G9SVqW@Z0w)U>ua$Ln*lg(6 zuPJW=oZ^%L4m(AVRq}~tzRH#u$@eHJirUDmdpjgN>NwP-uX{eR+k;dL=I~4FcnWkJ zx_~%bVgzv*oYKw<(;WUp&+huT8Lyq})A)Uyt-Ar<+ktc((c$UAj=mni;f1h|GYict z;<@Lu;_VBK?D1C*nf6fihSMadY4VTot(R6I9Bw{(1s#V2AP((jK^$&5j)lQAhspPQ za)>mxH`*-v9rW;UK5Ycuk1OmUrbUrpL;*NdB<2)yBz@~L7N9Dh(@F6G2`}b|tBkH} znAe3SR&JSgz8uV97Xe)%bR4>YI257>aafjdiwCATM5-{cfiJ!P!fzR=FM_3S#^b93 z22HPAJBx^{H^5QNY3t$fMD=DF@ z+w_4rM4A9`*yqv|4$~agpU4a$vpP=D5cLbh+Ds0e7KWY2Gr$?JJ%(cjI2_$)n0WA| zEK8JMfAAZd!#Y85d1XM~t9!y&4@klf@x;!{z#L9trHDhvp$CXVN;(jSv$aCpFwJ3% zTkA?quyc{kG$~uhL4mcIO(&0D2Y@1 z{w}LOmzATBV@ml7%ppov{V8-lycVD zA)UHp=d$wJG1np);P7{|2z#1&*qs7S_MEM6Z8<27Upx;8JV;~0ch#$9OF8_jz#Ptb z=|S1|74D7l^vwehhZ$W@xnP>Zw!sv-EVFX-H^HnNC{ME(XZw}I%ZV-nKHykPoM#40Q zZc0y6dyDkq>W?$H`<~cbs(X{25x-?qvdqkA0XV#s5pq5n-W<{16im7FW;^M{TV|>^ z#F2=Yd)}`z#E@nTYQP*0gg;n>&WDj84jY9*97@aP!ZwHTyCT)yU|p~Z=vWW-yC{yKZSmvnn6Q31^bZX z^8cgmuA-_6w>AJ10@Bjm-Q5j>G*TiVWdKrwAYIZZodVJ+DUBcvN{4hKB1j`h$G^7Q zGsb_;-MU;?T!DMIpYhIb#hjBU5S&91z+u=hkcSfc8j+B8_(dOMT(@J^=+>n)&JuF& zxUvPyO(qgUng#lc6qrLUZ42_zBbf)u89L=unH%RQy62SHb_Tap6iRfZ%}_W$Ho_fF zI}Hnia~KIYG!OzDek5NJhIAgLd>@RKKxQM%Z29u2TgdF$JsMKWyyxkq#Ew6MU=H;x zsS?++WylNZkZ73h-+0}#<=9ej7Mc3<3qkr|B~>DFAMOy@0MQ+sLm|K+@>jrN;MO^` z9j$AkLpXzrsJqL!iT@C-3wz7-;NSVs z+SL-ALkYm)NH5@!&6fB*r1P*Q$MN(j`5J2QRURp8!gVTz#}B`8L^eX*`CHd8hfALx zev0Kfc%P4Gs@j3$$m(>6U=o|`o?6aoB)DnyDXHK!+~HT;@rU3X1^^CaaR7(eG*!@c z_%$H;vbc=^388pRVpl3K4n1{yRr6|UEwqwW2YkIX7J-B$!Ndp`-c3+>l% zln6N!^FvgJ5@y=*0k}gXnh}uaLovXi(GlRVzG^NC(hgNf(Mj7dr|a-dqNbg2#b`TL z0|~NawZxT@v0Y&fg)$I{s`4M!+l>i>^QL@uihW-a8PB6 z4xGb4z@au4;4l&mM-b9^Xc*|#uZaDU*T2|_V8|$urX-4?Z^UIkb|z+d1?F&EE;BWE zQ8XSCm&uwYY;5`UlZl0)($3cLgH;b(QZ~-CLs@Sc_;%oe0B^tq|nWchO|TK z7+l|&%-V2n{8}8Q>44H_Od6l_(2!VE+d7`X9O7~aI&%w(7=Gey#FDYvGeq{#J#@_4wpBr8pX>mrzwv#0{V9J_dg(t z3~A=P=@c>=>xVhKON~SM@`LsB!x~(MuAv;x=86YDuu_*7#GK-EpL^?;qjbU@#wGG6 zgL5baIDB&fIJA=uOMI9M;eNflVK&nQW9Xd`CgCYRjC;r^zN8B;_dR(h{24tL=5YS; z_LXJ(G10BJfsdXd|1#7GX;zU&C@kT^ZuhGvW$xIBggflWdhoOK&97MojD+!DJ{BnCBjh&2n*FtRaAcUf+$O3cv$g9Md{O|QL(WGprrkE+7qg_8%OG`De z{lXW%lbgzSwuKc~@;Szmi1ToVs*F62;2bgl4x@hn4s~<~pzTolZg=Oa&*(9CitR1G z7V9q~Sk2dnTa|C%NQE~#@VfWN;cW@5V0EbSbfWw$Q3HF?q5YEF<*Q%*^2$&J> zmh}_@6V!^;_|3hmx8(%1d)>>l%hSf@il)am;vqL+{p4%DFzw?>}Vz zTV$7J8oomhRqqKi@x%SI8IEU{6hxjCb z!@_Q5Hb^`4zKEe9W;NxXr0z!eBTG+O=UBy*;K%ZSs%wZR2LQcvrTr4zp!m(co zBpYt8vf`=ZhRctZv2R2v*!;-H;0~#G-f@6)$OJe{*aRGs=^DpFIuAVy*Au^d2tl-Y zC26lOV|m?dP;0DgxlJXdw?_wa=>DQgwybEUdHQtbUa9l?tA`s0v%0Z(S0sFzUmoMN zsVuL+9nMqp`~v4N6L5%X4>-h?&*g!%!x)JZ_n6?=h=NygT56Xs*c(O(C9*=J8kW?L zRACN1+IbzsEPinn;(tBz_sHn*;z*}BR{cy!iG7lzBS*QkQUG^|O{SFx&LIcj&@lsW zSjYJ%3DOSN?mETP6n^~~u7zLS8vbz<4R5!MUZ_dfD)SEy%pv>Z5L%vb*_eb4i^Uwo zV7uDZl?H?D>H-6=sZ&KXwI#(}xWl>Nwsdd~V*!V`mVm<_?}6(SDA(ocN0=vv&eNGu zW`wBUd;`c)t9&|kydIAQp5D5KIds*}?K7(AY7pTkvwOn(yG4o-pXd8Ruaw)Qv&NFh z#HB?x+#v-|77I9sY=FbPcYwnQU9)6JJ8W+D%yN6yg~dUhn?#9Qd~~#qac2oVn(!&I z9S+Q)a;w^1)}bWaRF8%B#0uB62w~c(?UCE|&p&IEI|W;LzO{ zaM%#aO#o?!+45*gg@esCc;m&LI!rFx($-_?nQc7}5^+Ivs9)Naf;E zo(u1fk5q70dQ8v>BJ&ij>w3t*9LBtOO*pp3kLfb1z9FysQ_%pmt>&t+1e9)QE!k{)=Fb{Lr0m#Ki$8KY0k_!;>_tJ?4q!`kD*-*Wj zuqfx2;iQ%@^CQ~XTXT}-1sNx~p28h2v_?&UbC?V`jCTPX>XB|}G~lqcBC7<_4jq{BlzNDfd}r*J<~dTe<+M1S6rr3*^u5h* zV}?1La2B!^4bZfHMf|D$VQ*-9SpB%vo(dI$Tl7OUyu6*{gLb&XLCo1Ya1M(Bha?(+ z!`T)Ka!5P0y|`yi7$cE5q}0WV_pbT2P;ZHjjVI8j65z`UbLer%ESs@C!*5$NR#Akm z*C#QTw>H0bI89@x!yi#C>A&^{?htFH8wH$0JiwuCHQ+EnSs@3~4#Qto^E~Mu;cdSC z#f{B#AjO2k_bDyI5qi-ts(F~hso&bV+U)k}gU7S^>ZElK%K|#Qn3lIwtB-E$38Jl$ zUiZQs&g8IDfpb^_I3!gB9R9*ICWW*^bq73SHnVSz_JWb&jahvRjPr{Qtw#J@mYF;| zFo)qjlJ}koE7G(RzIfzCxT)0>nIM%mW&E4E`9);Zn|5cT(0}K{75RN|4oLuqCl=S+-igY}xq2G7Rz`f!NzG@tep zwbFmu$E53jv$fw=$uJ!Aa3%@v@J?1CKRAaJfJ1|Fz+semIkX)bi!}Z*V77NJl=fO) zR%VItVX!31;=~<^`-)w6ujxW@Y4-TOlM+cMa8__Q}O z|IUX$Tb_V(m<>22(+3>d&mBYC;pC&hFIh$EC41?r_h{XB{YCGc_|cZddu@vpQNkSF zdDN55e@_NWKSfF%eePqH@8rQ}td(7+l{vbm_b90f=gI%-^2JCQIEQ3_LzfD`p@YIH zv>l#E+cDeq7Vt+0o-{t#xYR2uNb4^7q95hmF&qkWSWeygv<`jymiUrRmZhU1!JU=All zn*#LaPOmC)WXRu$s#vd|`2xkO2ApJb`;P$KB^=7h0hy`9y&5h>ZiJO#b&=!7Z}E;AGuT@ zhdFGw{X>7XJ2TW=+1hDJ6kPH7TDMezzLxck$;He5m^U-;tlHXe-l0DV&$mH{FaI^tm*@En01Hm1-}EyZhjCCivIX5 zj-Z$yt0#k#rbXV3>qzM7kyI++hwYE+ue-Lud!hv{X$KPUcOT7p9_bkOzp=mi$Jm$?hyZ7NT z%;8x&hb5Qivx!1;M!S#kMKZD!;n$(KUm9-vha|K!Yq|Td!X1tyrn-Q0*aSFC%L5#8 zF32)N+F^%rPLFj{b?*U6x^mPcmX)Un+FYN=svBB0=O36u8zkAD-fT>!Q434t)tF+v z$Fs7H+_%`gda|6*#hl)Vr<%hZV%MX;0p}16a7g$Na2PFwn*wQvd-=Edhz$uo&b_)C zN*ZJfyDcMy`8BU1X}S6)4(9L!sp|Qy?l+^a)l5{Z4?6B2pBi$;2izTa{3BiTL@0>3 zod@o4>Xkk}IESABhb*~(L$(My21q*;L)bjLw}JGr!J6VMxyO&HY3wYx!Wpse4q6&J z%pt#7!~+e3I`xGz35KvC98<+IOmjy3WYH-#TM813$QL=5aEEugwOYYBL;)Of8UYTq z+G3&Yum{C|j-lfux#zL}2jhLf#%AnTLA8G)I|g29-mggAww(RaA|{?9?}lCr1~ANrQYOO2D4*X zAnE-|E$z*}Pe{)}xh75zbI3BJKAT+{HHi`aEBK=lO2a59WIBX0VWPR}&CtUf za%u$_XW_-LOWh^Dxv(5XYwgq+UapRQml8;UD6`2Gl`jH!*kOFT3S1t31RN?f0S?(w zXy_pAaPej*p?=MA)?I0om)HTCQ-1NEP(v;2fd@4%;;VhePJNX^?gpTg#4%>Xo$6se$ZF zXsJ}Wj%bopZh9~96U*7(y=Hb6!@uvSD?~iypxg9%`zYvH`P$C6u99Q=fQJtn9)Gf~ z5QRG&oax>H=dc=Zs8Rzsq^bNy3u%W1BV@8(#7v{9!m0JXr+0pyai=dbSdJ@K=1c#b zD?)oqqa#oN`LUNAr9dUOVK(vQjqu6~aR*{O{k5+e31^ir;SNcPv_alK#04Bms{sz# ziDlkH+Mxnb;Ll*JciG==^%ig>`LCIZd#ok#s*yjZeH{qP!^sk)-oav0Bj7N=OAgxCDHZP?KS~gGt=9jz zsEy9}9{)Gu{GyRVLZojb!{7ZW&u`p`$YZ>piPb8WM`e@eADjvN>iwmt_JFSQ(8|qk z`ceb#5XBTt8(bb@0}h>C0f%!z@1X7Q7Psog65{XY6U;k9S#;}U+@*t&7Zes|>2Gwd zU=AZzh=xd8-&WdZ*-p;TOK_8x+Zl#U(j)0Ohy5OzJe@y5fIFNOt?2;gupV%T_ZDy% zIa2{`hhH{Q@V|H3q!}Qwwu-RzD5GiN|9Q{0ANg$9;|f-n>$*|MsT}$>B6n_`jtKq4 zD@?pJE8R03nJ6(gPq~)0`=ktaNOP|T;^e-m^I*AMI_y(LWmy&R&GCvhXNF4Bg(4!( zpurtZq4%4CbJzv49o5%7m(x^O)`Gb*5?a$a}mKPrsTpIq} zTkJTF%%@y&HQhm28C*+^nJ*q4H;jQ(?8vClc|75u>6NMqcZg6p5Cbj`odAb~&VWN{ zHeP7=>$Sf1n)I?TjC?yi|8Bl$<2(B^j!34*lZWw&my$4tCVs^lx9$mNa3DPr39TE6 zF?dS-xPH0CD7NO~AN!`Qj|nJnhr=`*2;dy{0}h?T0f&Z3x~z~sA6nmuaIAbN@6cAp zEk%4Z5i+3U@T^K&|9T~8(Gix1+8pIlUk`9u4SMzang!pH>UoMiqO*8@UynNf$J5t@ zc^C3_t)y7k*S3%WkD%1kw!wjsxN zb^UedftlE4@vQ?9H)@YM=WD1E6X)>^9Wy^}lU=77daa)L9k|00mrZhT4hI2;^XY)Y z(mpIMNIQI2@!g2Gn3?)JNvsNc{EG1_LE0fKEeE;k_eOvB1w@gC*G@+Y=U(I0J49aN^%60|>@E!$ z-0jvhF5$n7)&F(*g4)FU;_n_GBCP;TY+jMhC6j zHFCJaF=m7ea1Mt7hh<5CLvH5~X!nhplZHxbR*YFp9=Q7iJrdiW*1|30Hrqt3N9$sM z<>BQ(#mYu;^p=~;=GlqLFuRZ&q3y`1VV7*%fS_()Z6(12xI+}$+wtlnx`Dw8l)W-;fSch#^wbn0bqe5BF7a-rnuC98qX z!FIiJQmnn5i{`82bPL1$@l#T`!|8BLkaf8oa2S{XI1I3&fp*_0PUH2s@guz@yETP+ zsgaY%-PSeMsVC*G{&-7!Fo%0*&ozne?x{Z&A`~_`Y~j?t>f)ZZmqxGu5yLIP*RI8F z0Cz|sd`JM!p$FhF%Mox$G4c`G4t2-^M2dMjaO3ua#khfO|y8x7a| za#wXU7G+laI_aE^5$14#Z*2(s<(eb+hpcmaE(3DgwGu(C01ltQ;-9yj2p6VhF}x(IeoMH+p&XHUa1Or!4!L##hvUPapzSadefO>?%3PW+k*j1kBirvt zoWye5K;q|mjd%av$M&&8M{T`0yF|N6dVDD`Q+6$pF-XBDw&+hzL9G#=*jB{}cZgm_ zqz2C6W5A&#A>fd-V;els|3Be;r+i(&8eHqZu6b5zNU|h)>d;B`sZ+#R`D}9hcke~Z!m`nxUSHivm~A3 z^BD7J!M4P~>5%lI%Mg3cm^-8Xeyp&{?sr%oiYyGeziM+_HlVM@Ojb<@J1Y*}V-w~} zs{Q&~Qs@sZ#tZI$b-DcOEI5Z2fWvEIz@h$UZD`MP=)Z&WsQ>42?3hm|1=Ghn&FU#O zk@xOsJsUIXc857U6}>OmAj*l_R*%e)KN=*NEHWB2k@bYN8f7fHPdtO#+#H^VOE=%; zz&V@)9ENNI4xiN`ia@$9%k=yhG^wQ}A#>R?kJiT9)3ddSX0H%->@dwhf;nu}5isGy zFUfYOJIcV=RHI6mx-LwnYZg;_zA7KE5hyzU&mj%t(=c!jtpJBQFo$jri=gdr*!spl zC#yB@px+pqU~ce9k>+M-QlH_=s0_JNm_wF=I1%}A%4^s7El%t^jQN>|h)Ol(nMOX5 zM_lcW8Ko^2aEAjT(-`0!E&vW6?f?$aJ=&l>-v;;MYyaVB1TGJ40f(IUfWu!8tD!yL#!Kh%toRe;#Lvl76uX|W-dXu-X&qcc zvz{2Y%3uyvLpXZ3b#T_VdvWQkslWHq46G(>3x2vx)J2vVZ_B?Vdjfa(&9*`joWoJT z;mro%&|v!%+H;m{LOjuJf-&95Uz)s^vwpdlL{4njK=nia3{C6^<}g3|bBnV7lUjpo z8j>NkILav%QN>^9WIpk>HzS-u&sh-}|K(vd`3N|NHh{xuO2A>iPaCuy#+hygN#+ub z_B|~sdU{`wae(k+5h=3LLds*gzjNAq__(jHkPawAh8~6c5w%~QznxJ2!(`qTMqF3G z;SshT{v7VGD`=1xoWpN`L&u+h!;n8I&~{kzzS2cQIVBB=^XKm8URx2$6Nz0HJ-+A+ z8lyv)!*+~Svb9b|Eb?zOkF@9x&J4B#*0dlU0Bj8Yp z3UC-p=2i;n=P9wY^!RtFO%|pX%IaMQ^fRJ0k)5+J53|41R&BxZaGk)E5IsOOK`CxP zogKL;bkY6W{7%B@u=eLk#9pmPeo%d%&RqDd4bcMkfT)4wd@c2q&(k<~ztr$|85L>r*e0ui9@k zcMK^SKf@f38KKJrSDQVd`l6^Y+i>=ySefKWGLA+*Ge<*nutga5xg*?RkBtf$IEPb! zL&{aa;Sl+r6r>&c5YmQ<7qH(FPm??-qf>i_9H(Pu!J(M!|A9IXmWOQ8(np+0%pBjs zJBylLpWxpT#L|C6$zf-d_&p6VXkhdaJ=`JIP1PrG4s`*C-^c-nNKq%ycBo1eeCKx6 zX~Oa^6ZJuBU`V@Hz^aC)i3?AG%-=cfMyv9HhJ-#7y9F}w?%nEG-X8Dwgl!9u6&206 z)f{l54`0F^PCY4H2Iue>;E?DBaJbdH2JQZ3%(rC1tMV0vBdOdJ1}fgNM_4E0I&Zxz z`8dh`zJHijqOm9}_smt8b>I38RXaCjUC`tl#`xHICKLYMX&dK2DY(O1p{o|)9BKm& zu~7hrbhiSa-M^ft;Qj}vlJ_m<_ljNfUOb|2GpcD!-YGmO^xYehZBW=3#l5=onJ7b6C1-0?R-V!0l0hrQ2@n!!2T0URp+ z0UYKUazVRauTNmMRf9!@2RU`VP4|P-<}B`Zs?ZVfuIt)C70lsU^Te#~_bT2;nVqGT zw!xocWFHxExn7kYO`D3XSE8(}@xmSAdigGabEpqEw7m^D3`TmL3hDFVHb;<<2lhl; z{=Ew^+I*RJ^pa@U`Ytj}iP5YJFo&1Ht~~MTp;lXWo(r~5%kt}wWfB+nr<@LxUe}22 z;8sj{!yV4hSjT{KxCb~C*asYvorOZ%VOT12neJUDIjZ0fs;XTkpJeva*n|*p=(QGE zC1DP4s~;Hg<|Y>Aw9RD%7%Psc>td}IGt$|oqg$8R`tdVY@xvV=YaJ_sb7%}Wl*9lW zX0QlByKgifaTkVgn`h`xFXZHKRwqRyxo<}!GW&Xpt)jmDUY8Pn8PmxoV{-x|Rj zu2_ZZhVwrBQ!=%@*;=ixx?mbPW>5Fk=Z{L3_2!0sdbjMq^WprrVsH*u0f!6&fI|YZ z_t18@eISO>g1o<}^vbUYVQ_vstS1FuP)zh##i#}WmWQMEwt^IQyf{MAEfB9q@+_9Q zpAd>Fs98Ny#WUX*o6xzB19wOu{*@4%Lo>kPqA=jFYn&w=(sem(v$+)cqz<`V>*RfS zbxBCoaxVo(KoX% zoS87HQ;i9nBLC$ez7=a1Cz0bg@0mY#pE9HeT|R8gMi?FL5aYwEyWku?0vtB*0S>9; zGobA-_^iC;S(p!VK+v+`!96j8Ijn7wr!Mv?L^Y}8usn>dOdXQy+I;NZm6J-_l-DlG zOnlta^Soc|Z5Qinu^TkLd;iXdA$K&uIa~)EPW1y0Il=;v&~pasB^tZy zA9W6uTQTA)_Stt&In-bdn+Vi=on6SAZEkh0#1nLc?OJa7Yz(mM@S{e=N$Ur^Ai?-o zmnmLIfODt-IDE$sIOIVrh4ynjLCa^#ne2!^dq{>Snz8%oEd`g!u_ZW)TRyz{yI)WJ z(_p*X@A@q^d zgIyNh;2d564i&oqha`J$Opvb2`q*dh1cq+y_I*>iZSm1`hsMbrY3VDKZh>8WILzTi z$B!ExlASRL5qk4Aon0K6B3r!Zcyw=@(v8o@R-T(5QUB#3QOFHAhxY)7GSYxUJWr|! zNS_bc@JJ_E@+7(kkALfG$qz@P#PVJI?A}3SW!P4SIV>m1!i+2 z9=meb&J-4-T51#Y;sE8J!=YE*W8fTK0S;GT4ln&pq3v+{iObCfU>T>YXWPN8I`T zmRRn+Jdco{>}A!ch;WCudr8>AIaC82hKT_VpB=A3+u_~I3dFf%a|hbNPib~&lpo${ zAsktA@z-n&^;5#)dqQlFV0Ag|$GcZ1 zC>pMQNEF#<=M(}+EXLh9YZse#Gs*i&$?ga-{~Y3V@POPmsscEqhB>T@>xH&MqyFwf zZM?FH`EB1!5kfuww@&gO&Z9q7Rv6oj!5nV39H(uY4EcC#PfQRZK0Kk`?ym8~yEuC! z!c;rwaw|~!pTmhbCueXD4*`eMFo!&I_Kc7|AGV~TuKMQ;=hbB=Rn>a#l5agRi}pgu zA{AV1t^DgSb=*=icfvv?hBv)0rG(hV&UBsD%um@k^42O7|1R|@(!cW|62%K}d8h?A zd?gMz)X%cbgmhi5At*9V=oQ_3eX9QLO!>uyWvNbuKU&j!50U6-m_xFPRDLURs{o5Y zI;jbvkkj2!)i2UwD?y)}Yv>#bWjJ4H!1Hi!2rCzy!#{w-n0CM+$}$bK9eTO`jucmy z{q^L-iAYFqWQ_eX_EhRq-3+Fa7ojkR^mEB=ND&m{aY|~XxRviFZ*DcEul&ZF`?=(Y zM=Ha~8ASzmh*ss33(lbi;1GccaA;T43vGu|Cnt)_l&q3$NqAQ;58CVELq3jo%WGGiWo@8a-#@9INsxI?p)`yli11aR20 z05}{M`-YL@?71rd`}bhq+rOV~uMi##d{(%SWX6zdZ$P?`vOsz;d#ZiOY(=Wnd8)m` z`~qE0n|;CU0cwVD&^YjWckEb7q?XA_bQAIyPd0b65>HWMlyxYKA9dKg<@?@BN?89htAWBkX`J+wb326vc<9vcA8;YYyXBpu+;^8;2Dq#c&W@cL;t9>s^REo$~u z37#8KlNMlgRaU-Ur5uGh6r~E9zU&YEmV|4+hHoJoec<#y_9d@7W;@=al+}WpG`W8c zO`HQ4!8ybP92UYHZhJP6LfT=i{7N?klIhdVln-HblVe)U&n*0pju)GE2Ff^K4nJ!( zuA*R*=tk1?;k)Siu{K)yb?t>Elv*DgUDst%3b*`om|%?{49;Oa;E)&Qa5LBz+7A0K z@B{maN(pq3R`cEpNS+gAm&$)<2)xTvu^t6;7*;@TLLyLwTdzL0{cN%(r8GH8u8Xbe z{0Ej0D$*q73W^>)4;SQve8D-y1{|(V0}h*hucJfSq3qLT(dlU92TksFu~_`#gA5A! z$*U-)=7@2x&0r4kKezN25zkxebA9%Wp_l(2bk$cF8Lo=l8}&5xh?Sj~nF5}N5%ePD z;2hQh4vQE7hiGCg&~|7a*w&(NV36y@uNG(iCZC&zsAr!%=i%+fb~!_s!xzZBw~sya zqyC@<8|FLxm`t@wtp4;M&F;06bf@zfUTBg&+~K2>{XgIw;sOp|%m5BqL=%W0?Qm4# z^i@nNzu;tGEm_a5oORUD7464n5<>A6<1Cm%t>p+>;f`ImDi{00gZ{z+89Rl6<3i4t zk0wre$tsV8rvEvN9)1FH{$Uf~@Bz%>-HQ!qJG?RwJl8R-+6(3|A*u-@r{Q~Ak6oXn42M!}^7F4)&+BV@EpAv4->(bN`x?O=4hEe@f^&!n zI82!Y9NPU!hISqzx!k!OPp$9s6NRK&jqyk=-p;r}o7?L7hkHX`VGcW1si$_|JiOYY zv`JaXWXV$Str1VlwQbHj2u^c|!VM4|IZE$tD z6>vDr4mi{ZSPz4=!zB^s!b0NsCn-C$9UFTDWkGo-ubE4OTR8-t*TWoA*wsi4y2n~N zc{e@XS#$c@Q1Dird_)e@GP{OAsneQ6-URMYHqsO1eH#?OVcrPf@P&Mn0Ho_OwkwhU zi&uwX4aBp{IDGsKL8f>oY6`UEzVe*RFo$pMjtVPCpFYOPVyXS`oNmQ2C(}Rd>g%l6 zG4~(bfi0W&1aOD%51G-yIs6PbB;WuXo;wJIL)u|ox(zDnF&=JWN5?l?x4Usq8)Emd zmqKFg>em)w4o5G}qTO8=^PuAyiu$_i3&Z>hdM0Fl$H;{k^&U)^LyvRDFU$(jrl|ov?tL7=J`U&WevX(*0yf*p z7+ob_cv5eqGf;#!q!s(-+ z);CyIy+s2_Z#$iSYWs9fG?uQN(fo60`if-;oI?u0;Tg=~RAVKy9qwpL6F!flBTf%5 zrin(BR8S}plEh#8vg`T{#S-T5d87CeXZZX*4?}{d3h5Rb+Qdp`88%g)DjyAuBnT0x z?f-Ka6)@os&S4?o&==89r0en?@z3|V4cNE7U*CHm?pagotIt4vr=`5!{MIW! zm_un6e3n{%hn$!G*R zBP10*NY~|=%>%tYabFFi*n3RlRv$maVYZQQ4tLUYoiUNX9IiD}EO>t#GM>!9_!Dxr z^s9-6v+dH?#5VL=`B>Y=MMmKlJP(uF%I&~8%m*9}jsgxPyG$Y=?eOBc6?V|`1QoK! zBm-Q|c7dM=Op|sIU52?-2>-s1?T7G2?yM^Q`VN2BN)zrdic6{2@Q)xp68W}I5y__v zJ-LN&hlbf<3g8^l01g#70EgBca#E0XSTprCNWpY6`Ei3PwYF09+@i#|*5vOYJliZD zVVJ`$42?ynppy9LhYdz7qZ9j&+xvB75iYLA8l)+5U$g7uZNeSKtn(U!b65g6%p3z8 zKH7|iw!^CJKfSIm8ouz^*hUH$OYCvxWi%%u`3{_RewT&i;c-pm@>x!w#Lk8J3gxLe z$9Z|K%{%v??&XcDVx;@klKOdYhZAMxAoo1t0S>EK0f!UM3!t5cI@pL}>Ge*@YTo2} zQFkmJiz%L7cz#J;XnIqX0CV_*yb3?=dYC%J`(|tcIZ4G%p4K|9Rkpw!O8}RL1b{eISrW=U%poSkP}aj8mkE!=RM3o)%>--M@XK?Ua`kbtenmC0g?6Y5B6u zyNMsW{8Oo2T?C)rPu}4b#aaJXmo581_R&`W4*iz^hqY#%(02HVy~^s{kT2I%uj9y~ zT{LBB`Ie{4GX>S5XR3dnFUN4Y*s##nY^EOG#BWb!ZawtzUq_ksY>*9kLR@b(gHurs zcX(go!VR25BEX>vJ>XEh@4g769rmUcC(hNg$P+LJiJcVH?Q^sljbq=fRl=#j`1`(1 zKnvEf$Sj2^^Q#9kSy8$NLmlN0%X~Qpm#v#x`BWnui_9FhPI*%$zaUFx@??T~5t+eVjC(_yJXLT~8K zv$GW=Y6w)HwGXtup)btWY)1zwbH3 ziT!3q_hd|%lwzYPQeCv@WyBqDmg)-;zgZ#LjLcx>|L1VHp<57~LpH$SILsl7;SIDM zGMK6en7HjM7Z%X|s_*2{?c6kGtn=e@Qyq{VggNZGo`0F)Dji4_bi6wmpitQCe#mjD zFDFbd!CvRHuJtiz748txOkWzD!&tx}=N#bh!`}Q`NIQI`eGp?qjnvyf?>*928M|Vw z;c-rv#v#D!PyKgqg;5fjJ)OL-E@^UJaT|Hq-S1TFc0=b$sl&cV>cUN^N(5!_JT%P- zSOVve1913<4siIJmWdM5b$MbNqjNnr_!)na)z;CK`2jJIN1|c9pEE;10W3h1S41Z#9m@HyYT3RGshTaJ8?J;qoOe4?0q2khc0QB@9F7*A3q#r=XE;ez z*Ftlt&8>x%a=bfjygp^9lv)chHhCR?_opQE6SH+-J}k`MLi;4C%s~(&dHB)x3H|=} zbI05H$Yop9r|>+?OmamA=P(U$7}Ewg{DUSK1?fDjbZMwD^>H>EKxDJwIsZ=SFcO}z zX?d3)C&XL^<}mcd%U`(P(92$$Z3J>5zEB_^y{p7UK*EDfhX4E3SLP3U^>BxC=Qv{E z9MS;}(Io(fL!pAu&O;>UxRZ(R*C*t@UcBqkV!!roO?6 z@Zqw#$36V-bG;1a`&n4RUf(9}oNIO#phdjjq3AbP8~uKMFPD4%)mQ(PjTqME?R3M+H+3DpD$q!9X|MN$%VZ) z^scPWulrUYZS=ll4`J|0M~Q}k>r}mCGV4Ewox>?0_nI*Q4u@b4adfT_A?;Al*AGLf zc)jR#(w&oAp=nVmd<+s(jIb z!!KNPZRW9#V(e7xy1n-^@5SM3n#k`Hq`W6^hnU>opMrB32smsR1{{7>vVgWj=K!|z zWb=iPPD#qH!Cn9Q-lb2?qS}iz8mEGP=Y1WD809*y3Ek4WHA`k&ikm_bnqcDfE{g#UbAM|btB6iovD zjiK%`%Qu+ACkc#+PoA5rvJ!pOD=5EuvBxJJeTsQsm5F2xeXmI+Q0)NjFtL{-1f0VF zz+rbU;PA|39oh~ry*d`=dHn2Di)$Fz6W%V}?<3{uRY}q%=Ioz@btJ$Js|?TSLgR=C4^=Y3J&97+HV*98EFG+g1(cKAc&51zt` zHcP$#uh&veEXGa?_I9M5MGI@#s^l<-E!M~hsnsjBUv3y2w>L7RkuU?1uHFt^Bp5rY zxo$MBJvjX5u#u<_oWoGS;dDRXaGS=m9MW|e5w)%$Y1=HaSB<8`e#s;QF|V4AOaC*< z@U6zbbs44mO-bi_{Okm@Yiy@o{rfxK2}a6mvu2bz51wH7B>uAf{I4#5sL}!FPzrDu z$p<(LRpx_s-zevcx8n<@^9&x=QI7@-G|UEO^uF_JzaRC*3^IZ_Bp=M<;;MR*TE=S2 zJ3xG!^N794@bcZb8MeZ($a&MMoCd` z!Q=D;IFN3gvaC+Z+}otZE~glP=V4sj+$uPSZvcmh zM}R{^6p|Q7J1iRhjhJ}QE4jk`J8k4coPWubNsaW_09$fk!B3b&5vP~|+eME{o^$ro z0%`6{>$-t{T!fXsKyQE@PVaSA=zA3#@asdOV167S`=qR(ean{!W5$t?AoUY z%uoM0ymjGI2hL$Q;BX7((4X`NwEF^V1Z2&vN$9T|9{aZzCma3ooPQrqu$bblIbr;F z&XQKzvFAx?Q3i8gIw%%d72TPf-FfJ1-y-!F|5fsQv#U7KsnS$gnh-8KBgJ>H(2f0 zOc&dx1oe21r9GRe)dS0-ormFh7{o=;0?uJH;PCPUa2TCRSpn%he6fReOfB?IxJ1S7 zBaKQ*P}-MgFPa@(h*iGxHNf(4*(3jxbt;!ht^cCsIr44q;|jv~UjyWIc{=@+AHErl zW`w{U%D(#n@_Z-+ICRGV9AfDp6GA!GRR)=;*r#Mz-vtltz3d?VoZ*b*9h|GFsw z8Rk%S)!OalS62)9n$Gy52kIS8QY*}CqH!#O#2^ig0U1>wMQ^rZ0 zy?$eyDF-h6b`c4R=(2c>?;ZqqIHF8B0WJ?k0EZd30f&?u&nY17@RzhVhL2R%*&lU0 z!ss_b8^c$KoZClx80N>Sf9KnbaVC-`nTAy%*t$PFi_uoWeO7Rd3XRgq`3qfYWbQ%+hHInU!cRJwrbX3L82vo zV~kKabE~?hitZzGohDcwUY%BAzBlm}atZRQ@1>Agtx__0Gono8n6=bdOzs{|uNnq- zs8ARw0?wft;IQ--;4qse4cZRXYVY+6|0%A2V~34X#Bq*G+2D5n;qB$Olw^gXFo#$? z(`KI~3QBqX#SbR7sj;hbI^(Le=T<3XDmqYc^X&t_z#U@AJ}?63@C0y(eFHe0vn7D` zJXjeQ&abOZA56+67D*&;!gK|0ihVe*TcRb!-ArH(WB)LIM$IwRva2lc zEZKnZ?eYV@j*#(*cKK+y!#37gA8-ye0Ec`$Do>@fcz>de z(>y%%P!cr#zGAM5Z3=UUR7rN&qHc2ur2$tIK-Ft5KMB2?`;0}$?m7~Eq)B+raPy!AWJp!S9 z--eGqawC*J(eYZ9m3xUjZ7|Y;o;NfkyRlW|>KDwRbl9?9T6RR?R}IZ<~Dv zuLbFTINAR=e`CuY46@6pxS&`OuC!=gMYz66_OsF#2x)|ee&w?Gy;qfi& zn>c%Yn=Fe7(ZLWg1wAzL4ke-l$q5x9{j7zWHpv;dLmKjs5^#BV1~{x*0UU;mmP6Yi zN>_&Emr8=BYCMh~aT$?sd9B(Cwluk4Ty2#7y-sn`=N=ZE#M{+6lg5;+5bx3cNVO{y z&L%i{l2}U9w!yxi0C%YQ>Fq0Ud8hz5BqjwMe$_96wnJQhZnaCtmDOa2WU6DscYgc9 zHO$={e%X2l9(gc_`g`yDqi~a`&i$gjZr*cYdA;M?M==WZ-C9k1sp!1+#%>PoFdX-J z6F7%AfWz;rfWt#(fjmg(A=0HtSg*~~US;p23yp=$$VyIr$=2<}vBxci+ORxyE3m6f zIHpMv>phlrva>qyT6fQkQT56FJ!d!-Fy)fTp9puT?_}ly&Y>dUaGVTq*lr?61L^Z& zo;JeH_zkYJt(4HO9@RZubW*ZsGz*pzfxO8nFoyxp0$wjPl`OX$d<@w>bzSslAaX9r zuBtPsFMs|i%^mS_{GY?@hzW2GuK2 z$R3zdNN2*L|Lbs1>olc;eO)y)umvcE~+jqylp|*hd|$sNFU~ zIC{~U!Q-WzJbVy4|cXxM+2%>Z&DJ9+ANJzJUfP{o} zDvflDNFyO3C0$bT?s~l6dG33jd1lU^=dUy4%yM@3eEeRqd;KnV)Jx6B8IU}j@NB6y1q`Z zB=l)n$l~(`9Trk4z2>2Q`*46Yy6nLpafgDo*aWAdrq{+LDH%UCII_pSLL9zoM%RVT z;RfJPdK+-4TGIyG4v7l11BrQ)Ii7#zd;Ls^p||`;Ta{Idp|k#dL2b}s*>p`Wy!jJM zIC0Kelef|(5+9d|3UPDC_sfDw;Rec>*;XMARq{<_pmS&pI2?5V9FnV3!FD}ZiA>1= zFW-RjbTYvQgg#47_I%BOe)&pwphF7HO;joW$EYYP9^1@qoqm{K*NrnV6gyUq zV-7al86TEaKpetxy@0ZRxC=OBNCh00j)%Z@UE*i&C20IUB3)ED#+jWhdBojp?3`%2 z1;lcTUM7PM`{Ug`k7rl(4(3z$iz_uJnoTZv2xP)a`H>`2tSPnXaaaD0Q|2azp>wDP zI6Sul9P%7T!nQ*rH|_>=c{_QUf(_-(NAuJ?p-6f@FZZ4o?NR)FzDzl}D$y(cJ2z%J zqvWHqOvm==&;frO*}jsS-}4Ip%iZ6V5QnLFRNBzx;WpsVBMo#IC>aaW{X^*$wFRcB z?-%s0wTzS!VVxD!YrVcm5^waSLXbg+Yh+czyl^}pH>pO0{~)dy>6vGWNXU69geBgo z5WFQ*>Dz=j)W2b^hR&e|;E>uDa445R0NZsqeTs_U;^8|Rwe|f>Hl*UCcsFQY7t^mG zBD9E%gLycO@olI*ydZOvi~6c4me}K-wN#U*#vdGI&IY+20ZZlWQi#Kdb?s^B93B7; ztr7u;Pkuzgc0E}0b!7+5cY$8kd0)eRH>)k9vh0nX(>*)W8SMW1JVlMckX{M_t1#Gx+vGL-Y!T7W}IYrtVC*&=K^>{1I!Drx#f z^c4@F@zph${OTpwhRnJ%4CQ>~_vr#*rAlbj0|z_F1jPl2UT6G2>u> z;O^f#?b#7l--BmO&SELx1S}=Gx9H!B9=I@17-{8bwwspBt5*MWXw0Kt30)rQ01mZ5 zhsF~#uw5Vfqc?JudG;g@dy79?$*I+jmDkrP>{a%_r#7s(phNPg`Z6J73l0t4Z}&!* zErQVDKEW-eJaB6j?ONxEwD?^4&mpk}MJjX-zX1-ZL5IkJ*RY+3+zSH5wQGBs9zh+S zCbmzatI`5zFy8H>iw^A!fbDWQA&cK`o8U(FjMA<$%lqcf56wS`-L|OHRy$vg#k|l# z_~&q1f#p7Q4s8I3>!3rV=1>Hfp0`nnR%4fo8v2M8tgmKw(^Pej_wz4OisKJ+S%rVs z1!U4xot)efmM6rv!>2@Eo8&ta%Y3#)myzyTwm%Y50TmqVqcY_Y6gp^+hH%)j>&)yu}q6Eob&n9^J}i{e$Lh-cgb6Z`Fklsvm12cxKiR{1ksu zMHE6DBCMKOLg#P>aESgEa42W(mkHD36l_acg41uj@w`g2e-_-MY#Wf-a4oz}e>Y4A zj)D$z@Y`2alGq>Rj7h4Vm5Uty*uGVy&v1MTc^BgF%Y6eC=p5Pu4w5Cv%O;`qz_I^Z+0p2W z*-!FV1XQH2Q+eb5=RE#%{C|C_w;SSb)BQc)xWn18S5L$D?YUnnc6+L_i;3=U_}%L- zGgy0dfel_y*B7JYSHYdpiB9v5_DgpTx1_1X`zfJO=!PWzCmW~3?fVdihP6Xbj+Y$) zhq`8fLp4Ea*se<)vyI4hGErJNq{qNrer0-pMCwkAjg1^TuS&~DeD65sl zr=S`9FoPHKT=*}>Mg?AsRBwIwCF%V%`yIp~j-nk2ba}V{IK+PgIII+BZ-;4z&3Wn% z5f^Q}5zM=M>X~T3q@-Qwa zZ^LGM_XbLzy0UaJn^TM`6r0Wk$0Yf}V*KcIk@e$1*I%nb8==)vr zC7Q70f)2}ul9;ELZGY_FdQTI%|M=xK^nggBkHyVD1ehn;Ee2G-VFjWsx4mFy7+f}*;$wPToDlzCBngb41OaX`K(^jxu5B3q|>hvVL z<`DUwN!68J-r|P6=>)m}`nQa^d-b40h1;$GOv9>@CmKJ>*0<0Y-rD&S>7!3o&ulm6 zOvWa&Z?!@kCe+{^?7H5Qp9MEe6mzv;Z6m z`vDGPeY;`Xp$f6c!DML--kooCnQ@Z=tM%dUtIZwPKQpJKXQ0DB?CSdTkGA&jBUUIL zEsmh&w-x2HCzJ;~6%Q@LK^0?@ZHG8a>}y#r@Cr-nXkOwuI~D$2 zlHU3vjvu4pt8aLRarQQ?1w`N?3Q3$xlp@vTK*v#hpPMYA$PLK$nNUfJ19snM|-uBm`a??xWRI-|KZ8V`!b!T~*wOPR%feuZ0(v5Kpq<8fIIU8gX}sco2XUx4^)nwjhk<~@WLLo9cj`4Un9jpM!)9#f1T1;QF)Eb` z@si(G@F+-+6YZo%t!DrB4^y=TY8%h)zR8k^y`mLaSa#ow{UZGO2THA!XJK1aT>ecB z#9_kIBXsB-4g(Hdq5y|$MZvILcdk-P*;w;U2Aj0P{^+0J$dV2!(%`sIeuM%)*E`Um zV@Y;>ksmhV`&4VrQK_UZX4VXJw<7nPnFfx4MNM0VgIkD0UAZe4=o~r&4$++fhq~UO zu>xBp8eYi$oRF`<#v?6&2lqpoAW3i!Xz4_-z|Ew-yDWaRYMOmLg=ilKKM z+%82EgHyJNaOk&cm6n)||M>Y?SZw2u#=( z=_0xK5EV8eW$hI^=fz6Gd-P9e9>5;}*jfWzm?fJ2_TE>W1y!@PdKYsb^5t$G!2D+E*&`W072?!y3h$L0n`3b20| zS)52vdig8xX>?eLm6OBBKznmKesgxVs{`}={b>G`%{YidLSZ;J=p23l9NIJj4llK~ zVcVf4>B;w=ILY9w7S)k56Da~K<%Sw@?cl=x&vwV4!w*v$Tv&5rbo1!##`JIpt=1n2 zO~sV+MSaZepFW{y`-IsKaoBrlc?F$AcfjG!6TqQrAf5y+{3t@2KSZJ)R%u=QDLnMUYbI+L;!yg; zUKBcqQNAeF{tAFYZ+|Ian9jqXc82p#%g+Ok55r5d9`PR??HL^CK9L@;V93x0^ANd0 z{B4Ce&D`$CZ^Bs-z2fT?HL z-Y>vmeGbpR4V84+09D^pVow`MWGDb3b?)UB#cj<|JK zER;glRjw=X_6LsN%keRYL)A{2H0T^g0}cgc0f$#5sj%&EknQ2*$oW*;p=~JAh5R+T zuQq26PTIc8eHLC>(4m4Q1`VSm({!V~U&&cGlATEHqo*9d@GigED|6i>C1ueELmZ~{ z96`BWPY7`Mp$TwUWoH%!)9te1N+P)?XXxwSD-ncyFOxfwuX&2{u5y!t10!ldht-@_ zwR!zZcR#7IOrtokFvEQOc--7iG=5eZXCc!!hG>3)I9$^14S~*KEa0$K4sb~M{TQ~_ z1(1gBdRG7@@Aj#GB9nt{G86h(Gf}TL=1FTp|Q8}{-ykz6h5-*l7VM@n^Moj z3Tc1S-SG$tO4u6O7yNS=*0J>yx^`IraL55V^h*)TfoX?l;SNjL_#KIEZO#5xCmmUh=tqQ!6zys{tK8YhjF_T;J}Tw?Y2>@^RYFZc48ocY~Ka z&E2W3oM($;o}-XF>~B|t@;!%Oz+s{c;4t)ltQ1T;Od6E)95%su1vgUY7E79L>$%LN z-X=wSQ1zxd5p=jI_fXd8^+QbgI2zhF0XXT3YiJhg>aJP|oZ`yW6PV%X+5g5V$C^;? zuO|XHjCc zY!!NSZa0Khz^KEwePoMVN?}5PW>#s$czON};!roKf)Tnr3gV+2AJ<<1k%(caWX}6cQ*-}@g>;8^Y&|V7o)e$>pxNo#$2*gS@wkZ=H+>+qO z&IxmBlk&Ga>*YWk;%~!0hb|AL0EaV0fWyV}YuK*SemeH7%3J8eG;L#^N(?8BdaZLf zH+6VMR+9FgCtx0G*(Qa<*%*1;GwKib`$c(#ENVi5*G*0zP#dNYd7n~h;48%8SZq2T zbPitu4mqC#4zFu0VY@yybGo_j5gu`iqvLOF>w=PL4Q!T?HMA%-K}&=oFb`F>Bg^IR z-u|3IePUHFl$N@9)rK^pOmO{s&&zrlOYOON?!R%0OKmoE4kZAGd&Pi5MNE6xZkIo< zRGrQ4z26lHvycg%=J2(?x3PXNJ84!p|D^%w@O4tD3=*OIGYX{`&7kR9+IyGIo73TZ znlWQ{1E=CHuBy`zhthNM6VN$~030^z0}gMEuwdJv)brRZ^~-SklzVx^<9mJ)*ZogA zYfhsZdoRD{y{!$?-#HfGwLJ8hBl**t=Dg9zez-5Cp^8C*!x`^tGW<$xQBK1?YimK zCe`9a#joV-garqZ(;2hY(b^(L{`+~8W`{Enhu_sMilK9u0XQTw030$nt;4p%axQ{# zf1UR&-034nUr@^*Ek;tFE zg@cZ+6JqIeCEoZI9tGh^RMIs<$m&+ophG6|E&WPS6H!**bwi1u*gB41NDQf6GAOtW z(*k5j>!Q-K3rcZK=!hcG(9MEVN)Tb;% zH-}`4zk?2OY7Tj)NV5FxfGTqgolyyd#AQkV^Y^1S+=k77VD@NVm${P&j0v1GmlNdU93RiYgG5eaeN{2swNt}vb1Dp z!ry!FdBQ)3@%|Ar(6!5KfI~{q;WHh7*mn4Ew3roT*@`ts5l$qXO?m%7sBEB4;(u4X6s>r`TRY=Wdz>{g8Wv3BwH{{C%_HH67P4NA5`2CShBy@S02soSq z9p0T$!uGsPeM^J8ZHG?;>Z|qdTyY~@E3Ys{o{6a;qH7_Sf({j1<#;N*^}NPXI)i*N z`rJdXFALdD2Xy6Cwzigh59(|aA$j-)-9;Wchb(}@jZ(m&iE1NkJ5;7rEbd8piJ}JI za(9g6T-B;Gx4ds@9;niLX#(b<{U-I%6d^a?pu)S|Z)~~I3L@mwDXpZ&S z?v4fT3nVHz=Gg$O3hnRO`TH?YA!;ANaz0YVA3W# zgF$btXvIutEotZ+asUp6G#xO6T>GS9yZ#}8T|uB9bJj)NUCgZg+!{vK<8C5}KLKt} zxNI3ghYJtntFg{!0*T~JrKtCNo#Ldk5S8xIUGQ&I;jh-#PQFsF@O>cTuG1^>K!(m? zI^a;KyiGoasi_OL9e!BVSxVk4`jDYJ#hlxl{iyj?zMrc?@2g}Yb}g8Ph;il5uQAnP zbyD!xl!R=e|bAHh&mDAlGo$gj4?(n7$lmFfcgh3?Mkv`5w@N~! zb=a3k1hygm>gv*9)hpSBsrcb?JD1}yqajvcrCyyJ-o^*jDnbQSpmmIuFZCVn0P? zEaH93V=ZCOZeB|mwC{PyqY!mdMo9=dtg)^?ctpsjRZW=tB#pGFpfppD`z!x&jO&8& zPF-BXBR5Wn!-rTRP}bqB0vsmg0}iQKNKj!q4;>%*J@NlReGxZLPVh)UgtW)e?RG@= z9U?y46Kv4onCwBzKe&~Z9qa@eV6k33|fH?UHcc6Upyxv}+GBmdSvjFz0f8O8fjE&Jw! z$R9FP&-G{8tga!1<;93@vig3~r#M^?hX!?vQPAaK9^i1h0CbplLk`mpA5+y%IWu6K z*Qp95Vw-4CKIf$}Tqt~9Ai#ip7C3HYy^O|FHyxcJ zChzWH2Fb$|8b&DBr#t{0{?-Q^{%KBu?L1_8`*8a40@}p0TWsH)8Oe0b#=5C-A?&fU z1&&8x9(wG5|MbS?jI#L6!A8_KJ>4}s)c8dM8Fl#SO++MlQU_dih{MsdlXK`C761;< z-vJK2qTa){L+n*a*U+5V06{sVtRf=*2P0C2o;H*6m;s4$MWDlu%c7Nm@PZ_cma2ua z=qm5~$AqPeajl5buQ1#hT^x7t%^?oaSuuH`%R?%_VYUI_P-6NPwjGi@B$ElYHV*tO z{mx%-OxF0K3diw~#&I>sM*Hvk*!0u_`s>Io^Inti4;uP;&Jr2+BvP|<^rMe+e5FVt zx)V75waYIvsGxKB9&qT92RQ6_orVI_c~~IeQ8T69jMWo1mh{{DVjC{5)*$C3`Bji6 z@mtWLtP5uJyER`obde?}g9rpVe{`jNEMAgut(mRH(cvTf#D5Nnrn!8fb4UR=qyil_ znJvM#LxD?zc(kv!IJ1YCpUO6(!hbpXY-`-(+eT3g`a53kCg)=s9X$=NszCa9%In0A zuqc&wEW!S@j!dP|#`^i(CNIRH&cp9ew#&tUL-=;U;dJ{mM3{Ei>d3lA8Y~}v-KMNQ zF7@j}aIKGxyXUk_Zse2Xzx~6&-KT65*prD~FA$8_1rgXsKfSnz-_vXn!x%x%zIAmF z0LjCw?+^T-bBG8yG<^g(w6VYW1k>Y`<2d3>{xHU@q#wbk>1fB9&EC5~Xid>le(#OK z!FDz4_Vv$3!@RUef+&kVe|zcU8Qf+pzj^^a%tH1TKK)slmeh1V64 zw*@!cCVf)MH&UN20{EIWnY|zm6MdteK<5x1a9AV_IHYSsZh~ot9En7%Zg04htGGnl zIZDpnt)jmpg~lQO($7LZHBYhqFJ z5aO^?{;CYRcDV&`c-ss(EXbgP?R|U@FAo_{iU&y-zPh>fyA9pc4AzkT!kLTWoX+3^ z9a^cbX!$ATVk7FkU+xq^J6ezbJ&KxZh9ULA=pIq@B+{5S#34RGoH=w3Q2~e2GJwN? zs!G^)n36elW?_-M>~-gOAz>qyB!4R*)x0}IxA5@UtDu8$dQp+soS zIYv--GWb_-6QBAE`Ye@Teu%?y+B+!Mr?deMtv&$`l}5T@+hNF)_2RSMJt25HcAZ03yoz)TX#);r^JeJx#H9+alJegW?x9RX0}9p~Cf&8-AE%^@pbu$JnyMVP4%F zbFMn7r<+{pvJ>aJUv~aEB&Kn@hRz`d;P3_LkPkT*w%17^xH%_dV4i|K~^@) zkxenX`)mA74qe;db)#{joJ_onE|#R^J8pQ5fpIR!7mc~_1$H0qGGDLT`^0VwLmVz2 z{u+YLVGZCAryg(^<@6o49frwOkQzVtjy8TwxijeeT($4ds9I>0L1IcRX%gr#Dt+5- z$&HaIckof^=dY^W6vo-|59tx^zYt6)gd2I26W{^KL-^?sALtyS0}cJ9+?67iB3SSZa{Sx&bgZQgFjQgaz_&4aM;M489Ik`fJ4k$z###F0vt@|Ap?fMBSu2R-wm1_51!Gw zY!%l3F-7LBPd$G9DHL?*{adfUHET0S*-%u3EN-TJ%lTj;)KXly(x|d!`(sDvPc)IpT$RqJBoAo$GJ!)5_%Y&tVS{Ih1wU8vuuIL5GS4;jsPQ zm*H^F_HxCoq3%(CP`0^sHeaQWSq^5+FXC&gJ<#E}RYfcw=T(4}&dqUHdszw#Q^If; zhhYc?z4R6w8SXF*7eDy6z_h~|8a5mrjRBtHg&#FqtDbPf zyP8seY#!Y^p161lI=tyI%u=B7lE>B78GgorHlF=f{U+dqzo9R6U5djk-Yi=Rl7|l? zzwtwthaUllzIA{@+z}+$&O^qQK+2HT6ZXwip5iMD-RGZB+#hf%niS5jEBk{EIcXQp zW8p6>rO3JV5a;1$?E)?v<=d#=e50yZeoB{b=>s@qwv1MccuE=ookLf^;VI}4`Oi=- zOgnVnJjE&({aX6CM2=Kl@lczFu4XAywbPRbEWfRpnS($1?MiNtO~so5Iy}~u z-iy=85jyTmCwZ|=>ACgewPOw&U5YZ&s|!y*4kO)O^0+K$7dE2 zFIXcYQ|vg=xQpA8wmpM5%n|COgwCNS;E>M)a9CyjwiTxHkP@~0>1f}^M-?QGKeA~S z54da=KFJ_s3m%$B{cV?b=Y!V~P1Pu!kxsr@2z+oX)c!b;hhm6=_Mkmb4{ram3?AZ8 zE5wipI)|fx!>6%;!_@CNuETRitkOB_mct)LNf@P{7DR5#kV+ zok-TE%@L+aeqQpj6aZcy+wjJ)Xoy#g3jS*z@bkh;PB}42y8oq zuQpxyOmJV=aDz|AXN3F3$v4h~3*|Q{DeX6fphLC8-1Qm3bHpSmh8aKUEQj5#F<0Mr z%3n3;()PCYv|^W^KpaN(sd7Q*&>L{5<_b6@f5iyf`+)DK_(|n5ci_vVd$;i`T>e5B zPi&@CWskMUsBeWWu5LoBbJi+INTk_L2m@UuRX z&)UHv%)iTaTR3K<=xqyD@fW52E;I7{*fp)lPp zzoj8%Qcmf;GwrUd3p~slZj=n%utS?r7s!^~1|3$0n0f9NW}KectfmymeR)^8!|+1; zcrZMX&B2lJLWY9{1LAOWiFq74hrNKqFA;#l!x>W8cK8RUZ1u6vnr%M~X2W7;*+WZJ z9odBnAu|zP;aSk3!s_}l37@_9pT065x28~Pd2Lp?$JeBEN>*GZ4{0_-cOF3;zA6xc zvfh_J;P6EN;INV;xg4e)9@{%ghKVHhaJ(ucJVdroeSo*7@nV7x8(MT&!4 z&~DD&#b%2CmXcl;;xGYm!2r5EGzT1h@Btk5#3aG?K95K-r;X z^iL>Mq)g?}6T`ab)r}1m>>8{kdEt|o!a!jm2Rcj(C8DqBz&__?(RRW!z@yG*V#}I) z$W!QmQJ3TS`(2tFKE$EX$bk`b4i^B2Nx6W-q(OYxcGyOLZFDF4SxOG>Cb0q~W+Ptd zbfHpi<(c$d$=~NG_^7E%NB$dAcnP`hTM@LpLMiBYk9JNYJK6Ab9yJHCT^d0gMtgBW zxqpfk;Ly_&aA@m%0ox8G=kD1$J^vgOP#Nkw0<`d`~E&>h_vH^!j8kw-&KO}JE zHZ&9sNT*ngro+Be$lgSVy>~KXT8APkxdOJ!i}`9)-{U%ruZ;*>Do!R%rE7D8KA9x! zd9(|LA4PYGdg?+P5+b#N6ifSZjoG`JTR*n=XA22gD_9zCR`zC(`j96i5kMU31a0V z&;>%+pl-e{)nn$FZwGE7Q}auUvX+f|$V_hf5Qix0IkeC@v;`bqnE?(9?Sf#tf5^G~ zWi31kJG&bG)kD(Y{=!plCp_VAG5ykg^?&yZ(7uYxL;3N?deAxi3OLNq0332%gu}K&%!@W}`3*_#d)TfR8y9zyi$?0` zOQx0&>|Q7lgAU_0f~Cjmczed`tOQy)W%5#8=Eo>tDNUUSAEJ zLmR;1AA7*zlMm8KFr9~;0SIu|7ISzBUNP)lKi{RNXcTYTr#ig8VXj>R^KdwIk;P80 z{pOj>+lEs_mQ|k{mvG0`jIf#PLwYBQG-XdRh{G>IR2a}X{02BoOa~lxOANzyyFAU} z{y>1|2||2`(^2{C9arJWr}GAFg@p}HgGSI{ln((X>Z1>DMf^O<(cX+_q{r*`%@vAy z?7CCqu_d=gN9#Zw!mDhJLg&yCaER;(I843MkAP{1sxJD68{a)+c$6hR-XzS^!wFSZ zq-KTJ#Oucs|Dk?t-^(%LQ@9!P-OyK(>YQ9Bl?VXMCCNL%70mPxK*2+3` z4yOQzZ&Cq=x|dh5UGM8`IA_Q0XHl0535&|>((&}!&=BoIntFFH@#m7D!_ZGm<(LuV z6%051{?+w%)<6Oc1ulS zwotuv+cON&c?=I0YV^GW7iU9fDcw7pzw?KwM%2QN-S*oBf}hw&R0pE5sF_Mt;xl(b z!WT?su51}~C?O6t<&B}7x0wMP)+7NA`?h>xdtWpEIzqdV+UC^Eva(9gSKo(s+dT%f zte(gF3eI$a4%PQ@jbFg0(5#iuah`CkN731+VyrLRv(g?yNdETib7j;&hryGBdC)o3 z034!%4x3QECBt+cMwg3QcH4^NVki#pMQ=+SzaS+ZPD!HCS>$b50rRlw@(cC~;w67^ z&!f;>SLk!E7{O3^N=!Y|OdAJQYoCO^+bv=h|hwKd&KXS=p znzOO{b2(Es-WeuDiw+?OApXKKN(CLx783Rmv7cKE*FM@X7{oh_c}U+v}tj60Kb%_D;mw7`eKYTMF2e`ZPY7N1RS= zn>zP{4izb?=OWN@w*n=n&xG73efZ!KCRKNR>zb|9er5sD{Zvm>yU2)3+s@D33clZYcJv#iO3pzvE?i_^QozYsXnf~uMB|7$8Yk3o?->LUASEsgk%?lxK_~5I&^PmJ(#MMF1 z7C3T{zS3ki+RojS%Bc%o>v?VR5c zfO$w8y{MxkU1d30J;^{DZ}G-*R5p%&&ZSMU*7Msl^G0D*c8Eh9^D`*djcNf7qX+