diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d7a36b5..127495c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -25,6 +25,7 @@ jobs: toolchain: stable profile: minimal override: true + components: clippy - name: Run Clippy run: cargo clippy --workspace --all-targets -- -D warnings @@ -45,6 +46,7 @@ jobs: toolchain: stable profile: minimal override: true + components: rustfmt - name: Run rustfmt run: cargo fmt -- --check diff --git a/containers-scripts/start-sv2-tp.sh b/containers-scripts/start-sv2-tp.sh new file mode 100755 index 0000000..1ced463 --- /dev/null +++ b/containers-scripts/start-sv2-tp.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Start bitcoin-node in the background with IPC binding +/bitcoin/bin/bitcoin-node -chain=testnet4 -ipcbind=unix -server=1 -rpcuser=username -rpcpassword=password -rpcbind=0.0.0.0:18332 -rpcallowip=0.0.0.0/0 & + +# Wait a moment for bitcoin to start +sleep 5 + +# Start sv2-tp in the foreground, connecting to the bitcoin node via IPC +exec /bitcoin/bin/sv2-tp -sv2port="$SV2_PORT" -sv2interval="$SV2_INTERVAL" -sv2feedelta=0 -debug=sv2 -loglevel=sv2:debug -sv2bind=0.0.0.0 -ipcconnect=unix:/root/.bitcoin/testnet4/node.sock \ No newline at end of file diff --git a/custom-configs/sri-roles/bitcoin-tp-miner.conf b/custom-configs/sri-roles/bitcoin-tp-miner.conf index 5f6a591..b7815a4 100644 --- a/custom-configs/sri-roles/bitcoin-tp-miner.conf +++ b/custom-configs/sri-roles/bitcoin-tp-miner.conf @@ -11,6 +11,7 @@ rpcuser=username rpcpassword=password rpcbind=0.0.0.0:18332 rpcallowip=0.0.0.0/0 +ipcbind=unix [testnet4] rpcbind=0.0.0.0:18332 diff --git a/custom-configs/sri-roles/bitcoin-tp-pool.conf b/custom-configs/sri-roles/bitcoin-tp-pool.conf index 5f6a591..b7815a4 100644 --- a/custom-configs/sri-roles/bitcoin-tp-pool.conf +++ b/custom-configs/sri-roles/bitcoin-tp-pool.conf @@ -11,6 +11,7 @@ rpcuser=username rpcpassword=password rpcbind=0.0.0.0:18332 rpcallowip=0.0.0.0/0 +ipcbind=unix [testnet4] rpcbind=0.0.0.0:18332 diff --git a/custom-configs/sri-roles/config-a/jdc-config-a-docker-example.toml b/custom-configs/sri-roles/config-a/jdc-config-a-docker-example.toml index 61f3a89..2eb9934 100644 --- a/custom-configs/sri-roles/config-a/jdc-config-a-docker-example.toml +++ b/custom-configs/sri-roles/config-a/jdc-config-a-docker-example.toml @@ -13,8 +13,24 @@ authority_public_key = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" authority_secret_key = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n" cert_validity_sec = 3600 -# How many time the JDC try to reinitialize itself after a failure -retry = 10 +# User identity/username for pool connection +user_identity = "jdc_user" + +# target number of shares per minute applied to every downstream channel +shares_per_minute = 6.0 + +# Share batch size +share_batch_size = 1 + +# Minimum extranonce size for downstream connections +# This controls the minimum size of the extranonce field +# Min value: 2, Max value: 16 +min_extranonce_size = 4 + +# 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) @@ -22,37 +38,26 @@ tp_address = "10.5.0.20:8440" # Hosted testnet TP # tp_address = "75.119.150.111:8442" -# string to be added into `extranonce_prefix` -# note: these bytes are fixed and they effectively reduce the search space available for the extranonce -# the bigger this field, the smaller the search space available for downstream -jdc_signature = "JDC" +# string to be added into the Coinbase scriptSig +jdc_signature = "Sv2MinerSignature" # Solo Mining config -# List of coinbase outputs used to build the coinbase tx in case of Solo Mining (as last-resort solution of the pools fallback system) -# ! Put your Extended Public Key or Script as output_script_value ! -# ! Right now only one output is supported, so comment all the ones you don't need ! -# For P2PK, P2PKH, P2WPKH, P2TR a public key is needed. For P2SH and P2WSH, a redeem script is needed. -coinbase_outputs = [ - #{ output_script_type = "P2PK", output_script_value = "0372c47307e5b75ce365daf835f226d246c5a7a92fe24395018d5552123354f086" }, - #{ output_script_type = "P2PKH", output_script_value = "0372c47307e5b75ce365daf835f226d246c5a7a92fe24395018d5552123354f086" }, - #{ output_script_type = "P2SH", output_script_value = "00142ef89234bc95136eb9e6fee9d32722ebd8c1f0ab" }, - #{ output_script_type = "P2WSH", output_script_value = "00142ef89234bc95136eb9e6fee9d32722ebd8c1f0ab" }, - { output_script_type = "P2WPKH", output_script_value = "036adc3bdf21e6f9a0f0fb0066bf517e5b7909ed1563d6958a10993849a7554075" }, - #{ output_script_type = "P2TR", output_script_value = "036adc3bdf21e6f9a0f0fb0066bf517e5b7909ed1563d6958a10993849a7554075" }, -] - -[timeout] -unit = "secs" -value = 1 +# 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(tb1q3z6v6ga372pqwej23r0akpx3v6n6xdffglhd72)" # 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 = "10.5.0.4:34254" -jd_address = "10.5.0.5:34264" -# Pool signature (string to be included in coinbase tx) -pool_signature = "Stratum V2 SRI Pool" +pool_address = "10.5.0.4" +pool_port = 34254 +jds_address = "10.5.0.5" +jds_port = 34264 # [[upstreams]] # authority_pubkey = "2di19GHYQnAZJmEpoUeP7C3Eg9TCcksHr23rZCC83dvUiZgiDL" diff --git a/custom-configs/sri-roles/config-a/jds-config-a-docker-example.toml b/custom-configs/sri-roles/config-a/jds-config-a-docker-example.toml index 080d3f7..35509bf 100644 --- a/custom-configs/sri-roles/config-a/jds-config-a-docker-example.toml +++ b/custom-configs/sri-roles/config-a/jds-config-a-docker-example.toml @@ -1,19 +1,20 @@ +# 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 -# List of coinbase outputs used to build the coinbase tx -# ! Right now only one output is supported, so comment all the ones you don't need ! -# For P2PK, P2PKH, P2WPKH, P2TR a public key is needed. For P2SH and P2WSH, a redeem script is needed. -coinbase_outputs = [ - #{ output_script_type = "P2PK", output_script_value = "0372c47307e5b75ce365daf835f226d246c5a7a92fe24395018d5552123354f086" }, - #{ output_script_type = "P2PKH", output_script_value = "0372c47307e5b75ce365daf835f226d246c5a7a92fe24395018d5552123354f086" }, - #{ output_script_type = "P2SH", output_script_value = "00142ef89234bc95136eb9e6fee9d32722ebd8c1f0ab" }, - #{ output_script_type = "P2WSH", output_script_value = "00142ef89234bc95136eb9e6fee9d32722ebd8c1f0ab" }, - { output_script_type = "P2WPKH", output_script_value = "036adc3bdf21e6f9a0f0fb0066bf517e5b7909ed1563d6958a10993849a7554075" }, - #{ output_script_type = "P2TR", output_script_value = "036adc3bdf21e6f9a0f0fb0066bf517e5b7909ed1563d6958a10993849a7554075" }, -] +# Version support +max_supported_version = 2 +min_supported_version = 2 + +# 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(tb1q3z6v6ga372pqwej23r0akpx3v6n6xdffglhd72)" # SRI Pool JD config listen_jd_address = "10.5.0.5:34264" @@ -26,3 +27,8 @@ core_rpc_pass = "password" [mempool_update_interval] unit = "secs" value = 1 + +# Additional v1.5.0 compliance settings +[timeout] +unit = "secs" +value = 30 diff --git a/custom-configs/sri-roles/config-a/pool-config-a-docker-example.toml b/custom-configs/sri-roles/config-a/pool-config-a-docker-example.toml index 9e58eb2..7cdd847 100644 --- a/custom-configs/sri-roles/config-a/pool-config-a-docker-example.toml +++ b/custom-configs/sri-roles/config-a/pool-config-a-docker-example.toml @@ -6,23 +6,30 @@ cert_validity_sec = 3600 test_only_listen_adress_plain = "0.0.0.0:34250" listen_address = "10.5.0.4:34254" -# List of coinbase outputs used to build the coinbase tx -# ! Right now only one output is supported, so comment all the ones you don't need ! -# For P2PK, P2PKH, P2WPKH, P2TR a public key is needed. For P2SH and P2WSH, a redeem script is needed. -coinbase_outputs = [ - #{ output_script_type = "P2PK", output_script_value = "0372c47307e5b75ce365daf835f226d246c5a7a92fe24395018d5552123354f086" }, - #{ output_script_type = "P2PKH", output_script_value = "0372c47307e5b75ce365daf835f226d246c5a7a92fe24395018d5552123354f086" }, - #{ output_script_type = "P2SH", output_script_value = "00142ef89234bc95136eb9e6fee9d32722ebd8c1f0ab" }, - #{ output_script_type = "P2WSH", output_script_value = "00142ef89234bc95136eb9e6fee9d32722ebd8c1f0ab" }, - { output_script_type = "P2WPKH", output_script_value = "036adc3bdf21e6f9a0f0fb0066bf517e5b7909ed1563d6958a10993849a7554075" }, - #{ output_script_type = "P2TR", output_script_value = "036adc3bdf21e6f9a0f0fb0066bf517e5b7909ed1563d6958a10993849a7554075" }, -] +# Version support +max_supported_version = 2 +min_supported_version = 2 + +# 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(tb1q3z6v6ga372pqwej23r0akpx3v6n6xdffglhd72)" + +# 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" +pool_signature = "average-benchmark" # Template Provider config # Local TP (this is pointing to localhost so you must run a TP locally for this configuration to work) tp_address = "10.5.0.2:8442" -shares_per_minute = 1.0 \ No newline at end of file +shares_per_minute = 1.0 +share_batch_size = 10 + +# Additional v1.5.0 compliance settings +[timeout] +unit = "secs" +value = 30 diff --git a/custom-configs/sri-roles/config-a/tproxy-config-a-docker-example.toml b/custom-configs/sri-roles/config-a/tproxy-config-a-docker-example.toml index 410e0d2..3292e21 100644 --- a/custom-configs/sri-roles/config-a/tproxy-config-a-docker-example.toml +++ b/custom-configs/sri-roles/config-a/tproxy-config-a-docker-example.toml @@ -3,24 +3,26 @@ # upstream_address = "18.196.32.109" # upstream_port = 3336 -# Local SRI JDC Upstream Connection -upstream_address = "10.5.0.17" -upstream_port = 34251 -upstream_authority_pubkey = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" - # Local Mining Device Downstream Connection -downstream_address = "10.5.0.7" +downstream_address = "0.0.0.0" downstream_port = 34256 # Version support max_supported_version = 2 min_supported_version = 2 -# Minimum extranonce2 size for downstream -# Max value: 16 (leaves 0 bytes for search space splitting of downstreams) +# Extranonce2 size for downstream connections +# This controls the rollable part of the extranonce for downstream miners # Max value for CGminer: 8 # Min value: 2 -min_extranonce2_size = 4 +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 = "translator_user" + +# Aggregate channels: if true, all miners share one upstream channel; if false, each miner gets its own channel +aggregate_channels = false # Difficulty params [downstream_difficulty_config] @@ -28,9 +30,10 @@ min_extranonce2_size = 4 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 -[upstream_difficulty_config] -# interval in seconds to elapse before updating channel hashrate with the pool -channel_diff_update_interval = 60 -# estimated accumulated hashrate of all downstream miners (e.g.: 10 Th/s = 10_000_000_000_000.0) -channel_nominal_hashrate = 10_000_000_000_000.0 +[[upstreams]] +address = "10.5.0.17" +port = 34251 +authority_pubkey = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" diff --git a/custom-configs/sri-roles/config-c/pool-config-c-docker-example.toml b/custom-configs/sri-roles/config-c/pool-config-c-docker-example.toml index 19a8598..2fa3141 100644 --- a/custom-configs/sri-roles/config-c/pool-config-c-docker-example.toml +++ b/custom-configs/sri-roles/config-c/pool-config-c-docker-example.toml @@ -3,26 +3,33 @@ authority_public_key = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" authority_secret_key = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n" #authority_secret_key = "7qbpUjScc865jyX2kiB4NVJANoC7GA7TAJupdzXWkc62" cert_validity_sec = 3600 -test_only_listen_adress_plain = "0.0.0.0:34250" +test_only_listen_adress_plain = "0.0.0.0:34250" listen_address = "10.5.0.4:34254" -# List of coinbase outputs used to build the coinbase tx -# ! Right now only one output is supported, so comment all the ones you don't need ! -# For P2PK, P2PKH, P2WPKH, P2TR a public key is needed. For P2SH and P2WSH, a redeem script is needed. -coinbase_outputs = [ - #{ output_script_type = "P2PK", output_script_value = "0372c47307e5b75ce365daf835f226d246c5a7a92fe24395018d5552123354f086" }, - #{ output_script_type = "P2PKH", output_script_value = "0372c47307e5b75ce365daf835f226d246c5a7a92fe24395018d5552123354f086" }, - #{ output_script_type = "P2SH", output_script_value = "00142ef89234bc95136eb9e6fee9d32722ebd8c1f0ab" }, - #{ output_script_type = "P2WSH", output_script_value = "00142ef89234bc95136eb9e6fee9d32722ebd8c1f0ab" }, - { output_script_type = "P2WPKH", output_script_value = "036adc3bdf21e6f9a0f0fb0066bf517e5b7909ed1563d6958a10993849a7554075" }, - #{ output_script_type = "P2TR", output_script_value = "036adc3bdf21e6f9a0f0fb0066bf517e5b7909ed1563d6958a10993849a7554075" }, -] +# Version support +max_supported_version = 2 +min_supported_version = 2 + +# 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(tb1q3z6v6ga372pqwej23r0akpx3v6n6xdffglhd72)" + +# 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" +pool_signature = "average-benchmark" # Template Provider config # Local TP (this is pointing to localhost so you must run a TP locally for this configuration to work) tp_address = "10.5.0.20:8441" -shares_per_minute = 1.0 \ No newline at end of file +shares_per_minute = 1.0 +share_batch_size = 10 + +# Additional v1.5.0 compliance settings +[timeout] +unit = "secs" +value = 30 \ No newline at end of file diff --git a/custom-configs/sri-roles/config-c/tproxy-config-c-docker-example.toml b/custom-configs/sri-roles/config-c/tproxy-config-c-docker-example.toml index 516d5fd..1eb2428 100644 --- a/custom-configs/sri-roles/config-c/tproxy-config-c-docker-example.toml +++ b/custom-configs/sri-roles/config-c/tproxy-config-c-docker-example.toml @@ -22,6 +22,9 @@ min_supported_version = 2 # Min value: 2 min_extranonce2_size = 4 +# Aggregate channels: if true, all miners share one upstream channel; if false, each miner gets its own channel +aggregate_channels = false + # 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) @@ -32,5 +35,3 @@ shares_per_minute = 6.0 [upstream_difficulty_config] # interval in seconds to elapse before updating channel hashrate with the pool channel_diff_update_interval = 60 -# estimated accumulated hashrate of all downstream miners (e.g.: 10 Th/s = 10_000_000_000_000.0) -channel_nominal_hashrate = 10_000_000_000_000.0 diff --git a/docker-compose-config-a.yaml b/docker-compose-config-a.yaml index ad134a7..afcccf1 100644 --- a/docker-compose-config-a.yaml +++ b/docker-compose-config-a.yaml @@ -62,7 +62,10 @@ services: labels: logging: "config-a" image: template-provider-builder-image - entrypoint: ["/bin/sh", "-c", "./scripts/update-mainnet-chainstate.sh ${NETWORK} && /bitcoin/bin/bitcoind -sv2 -sv2port=8442 -sv2interval=${SV2_INTERVAL} -sv2feedelta=0 -debug=sv2 -loglevel=sv2:debug -sv2bind=0.0.0.0 -${NETWORK}"] + entrypoint: ["./scripts/start-sv2-tp.sh"] + environment: + - SV2_PORT=8442 + - SV2_INTERVAL=${SV2_INTERVAL} ports: - "8442:8442" - "18333:48333" @@ -73,9 +76,9 @@ services: - common-template-provider-builder volumes: - bitcoin_pool_side_data:/root/.bitcoin - - shared-mainnet-snapshot-volume:/shared_volume # Shared volume for mainnet snapshot + - shared-mainnet-snapshot-volume:/shared_volume # Shared volume for mainnet snapshot - ./custom-configs/sri-roles/bitcoin-tp-pool.conf:/root/.bitcoin/bitcoin.conf - - ./containers-scripts:/scripts + - ./containers-scripts:/scripts restart: unless-stopped networks: sv2-net: @@ -91,7 +94,10 @@ services: labels: logging: "config-a" image: template-provider-builder-image - entrypoint: ["/bin/sh", "-c", "./scripts/update-mainnet-chainstate.sh ${NETWORK} && /bitcoin/bin/bitcoind -sv2 -sv2port=8443 -sv2interval=${SV2_INTERVAL} -sv2feedelta=0 -debug=sv2 -loglevel=sv2:debug -sv2bind=0.0.0.0 -${NETWORK}"] + entrypoint: ["./scripts/start-sv2-tp.sh"] + environment: + - SV2_PORT=8443 + - SV2_INTERVAL=${SV2_INTERVAL} ports: - "8443:8443" - "28333:18333" @@ -100,7 +106,7 @@ services: - common-template-provider-builder volumes: - bitcoin_miner_side_data:/root/.bitcoin - - shared-mainnet-snapshot-volume:/shared_volume # Shared volume for mainnet snapshot + - shared-mainnet-snapshot-volume:/shared_volume # Shared volume for mainnet snapshot - ./custom-configs/sri-roles/bitcoin-tp-miner.conf:/root/.bitcoin/bitcoin.conf - ./containers-scripts:/scripts restart: unless-stopped @@ -118,7 +124,13 @@ services: labels: logging: "config-a" image: template-provider-builder-image - entrypoint: ["/bin/sh", "-c", "./scripts/update-mainnet-chainstate.sh ${NETWORK} && /bitcoin/bin/bitcoind -${NETWORK}"] + entrypoint: + [ + "/bitcoin/bin/bitcoind", + "-${NETWORK}", + "-rpcbind=0.0.0.0", + "-rpcallowip=0.0.0.0/0", + ] ports: - "38333:18333" - "28332:18332" @@ -128,7 +140,7 @@ services: - common-template-provider-builder volumes: - bitcoin_sv1_pool_side_data:/root/.bitcoin - - shared-mainnet-snapshot-volume:/shared_volume # Shared volume for mainnet snapshot + - shared-mainnet-snapshot-volume:/shared_volume # Shared volume for mainnet snapshot - ./custom-configs/sri-roles/bitcoin-sv1-node-pool.conf:/root/.bitcoin/bitcoin.conf - ./containers-scripts:/scripts restart: unless-stopped @@ -148,14 +160,19 @@ services: image: sv2-roles-builder-image labels: logging: "config-a" - command: ["/bin/sh", "-c", "./monitor_and_apply_latency.sh 10.5.0.6 2 & exec ./pool_sv2 -c pool/config-examples/pool-config-a-docker-example.toml"] + command: + [ + "/bin/sh", + "-c", + "./monitor_and_apply_latency.sh 10.5.0.6 2 & exec ./pool_sv2 -c pool/config-examples/pool-config-a-docker-example.toml", + ] ports: - "34254:34254" environment: - RUST_LOG=${LOG_LEVEL} container_name: sv2-pool depends_on: - template-provider-pool-side: + template-provider-pool-side: condition: service_healthy restart: true sv2-roles-builder: @@ -185,7 +202,7 @@ services: - RUST_LOG=${LOG_LEVEL} container_name: sv2-jds depends_on: - template-provider-pool-side: + template-provider-pool-side: condition: service_healthy restart: true sv2-roles-builder: @@ -292,7 +309,7 @@ services: - RUST_LOG=${LOG_LEVEL} container_name: sv2-tp-jdc-proxy depends_on: - template-provider-miner-side: + template-provider-miner-side: condition: service_healthy restart: true sv2-custom-proxy-builder: @@ -398,7 +415,7 @@ services: - RUST_LOG=${LOG_LEVEL} container_name: sv1-node-pool-proxy depends_on: - sv1-custom-proxy-builder: + sv1-custom-proxy-builder: condition: service_started sv1-node-pool-side: condition: service_healthy @@ -415,7 +432,7 @@ services: platform: linux/amd64 environment: - "NTM_INTERFACE=any" - - "NTM_FILTERS=" + - "NTM_FILTERS=" prometheus: image: prom/prometheus:v2.36.2 @@ -558,19 +575,19 @@ services: ipv4_address: 10.5.0.18 loki: - image: grafana/loki:2.9.8 - container_name: loki - ports: - - "3100:3100" - restart: unless-stopped - networks: - sv2-net: - ipv4_address: 10.5.0.30 - aliases: - - loki - volumes: - - ./loki-config.yaml:/etc/loki/loki-config.yaml - command: -config.file=/etc/loki/loki-config.yaml + image: grafana/loki:2.9.8 + container_name: loki + ports: + - "3100:3100" + restart: unless-stopped + networks: + sv2-net: + ipv4_address: 10.5.0.30 + aliases: + - loki + volumes: + - ./loki-config.yaml:/etc/loki/loki-config.yaml + command: -config.file=/etc/loki/loki-config.yaml promtail: image: grafana/promtail @@ -589,7 +606,6 @@ services: aliases: - promtail - log-server: image: log-server-builder-image command: ["./log-server"] @@ -606,4 +622,4 @@ services: restart: unless-stopped networks: sv2-net: - ipv4_address: 10.5.0.32 \ No newline at end of file + ipv4_address: 10.5.0.32 diff --git a/docker-compose-config-c.yaml b/docker-compose-config-c.yaml index 059b113..da5c28d 100644 --- a/docker-compose-config-c.yaml +++ b/docker-compose-config-c.yaml @@ -61,7 +61,10 @@ services: labels: logging: "config-c" image: template-provider-builder-image - entrypoint: ["/bin/sh", "-c", "./scripts/update-mainnet-chainstate.sh ${NETWORK} && /bitcoin/bin/bitcoind -sv2 -sv2port=8442 -sv2interval=${SV2_INTERVAL} -sv2feedelta=0 -debug=sv2 -loglevel=sv2:debug -sv2bind=0.0.0.0 -${NETWORK}"] + entrypoint: ["./scripts/start-sv2-tp.sh", "-${NETWORK}"] + environment: + - SV2_PORT=8442 + - SV2_INTERVAL=${SV2_INTERVAL} ports: - "8442:8442" - "18333:48333" @@ -90,7 +93,7 @@ services: labels: logging: "config-c" image: template-provider-builder-image - entrypoint: ["/bin/sh", "-c", "./scripts/update-mainnet-chainstate.sh ${NETWORK} && /bitcoin/bin/bitcoind -${NETWORK}"] + entrypoint: ["/bitcoin/bin/bitcoind", "-${NETWORK}"] ports: - "38333:18333" - "28332:18332" diff --git a/grafana/provisioning/dashboards/config-a/Docker Prometheus Monitoring-1571332751387.json b/grafana/provisioning/dashboards/config-a/Docker Prometheus Monitoring-1571332751387.json index dad87d8..cc221ca 100644 --- a/grafana/provisioning/dashboards/config-a/Docker Prometheus Monitoring-1571332751387.json +++ b/grafana/provisioning/dashboards/config-a/Docker Prometheus Monitoring-1571332751387.json @@ -6194,6 +6194,1566 @@ ], "title": "Node Mermory", "type": "timeseries" + }, + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 217 + }, + "id": 100, + "title": "JDC Performance Metrics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Success rate %" + }, + "properties": [ + { + "id": "unit", + "value": "percent" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 218 + }, + "id": 101, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "jdc_jobs_declared", + "legendFormat": "Jobs declared", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "jdc_jobs_declared_success", + "legendFormat": "Jobs declared successfully", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "jdc_jobs_declared_error", + "legendFormat": "Job declaration errors", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "(jdc_jobs_declared_success * 100) / jdc_jobs_declared", + "legendFormat": "Success rate %", + "range": true, + "refId": "D" + } + ], + "title": "JDC Job Declaration Performance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 218 + }, + "id": 102, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "jdc_job_declaration_latency", + "legendFormat": "Job declaration latency (job {{request_id}})", + "range": true, + "refId": "A" + } + ], + "title": "JDC Job Declaration Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 226 + }, + "id": 103, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "jdc_job_tokens_requested", + "legendFormat": "Tokens requested", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "jdc_job_tokens_allocated", + "legendFormat": "Tokens allocated", + "range": true, + "refId": "B" + } + ], + "title": "JDC Token Allocation", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 226 + }, + "id": 104, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "jdc_missing_transactions_provided", + "legendFormat": "Missing transactions provided", + "range": true, + "refId": "A" + } + ], + "title": "JDC Missing Transactions (Full Template Mode)", + "type": "timeseries" + }, + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 234 + }, + "id": 105, + "title": "Mining Job Distribution", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 235 + }, + "id": 106, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(mining_jobs_received[1m])", + "legendFormat": "Standard jobs per second", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(extended_mining_jobs_received[1m])", + "legendFormat": "Extended jobs per second", + "range": true, + "refId": "B" + } + ], + "title": "Job Arrival Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 235 + }, + "id": 107, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "mining_job_latency", + "legendFormat": "Job latency (job {{job_id}})", + "range": true, + "refId": "A" + } + ], + "title": "Mining Job Distribution Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 243 + }, + "id": 108, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(sv2_submitted_shares[5m])", + "legendFormat": "Extended shares/sec", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(sv2_standard_shares_submitted[5m])", + "legendFormat": "Standard shares/sec", + "range": true, + "refId": "B" + } + ], + "title": "Share Type Distribution", + "type": "timeseries" + }, + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 251 + }, + "id": 109, + "title": "Channel Management", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Channel closure rate" + }, + "properties": [ + { + "id": "unit", + "value": "cps" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 252 + }, + "id": 110, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "channels_opened", + "legendFormat": "Channels opened", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "channels_closed", + "legendFormat": "Channels closed", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(channels_closed[5m])", + "legendFormat": "Channel closure rate", + "range": true, + "refId": "C" + } + ], + "title": "Channel Lifecycle", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 252 + }, + "id": 111, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "channel_open_latency", + "legendFormat": "Channel open latency (channel {{channel_id}})", + "range": true, + "refId": "A" + } + ], + "title": "Channel Open Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 260 + }, + "id": 112, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "channel_updates", + "legendFormat": "Channel updates", + "range": true, + "refId": "A" + } + ], + "title": "Channel Updates (Hashrate Changes)", + "type": "timeseries" + }, + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 268 + }, + "id": 113, + "title": "Difficulty Management", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 269 + }, + "id": 114, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "min", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "current_target", + "legendFormat": "Current difficulty target", + "range": true, + "refId": "A" + } + ], + "title": "Difficulty Target History", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 269 + }, + "id": 115, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "target_adjustments", + "legendFormat": "Target adjustments", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(target_adjustments[10m])", + "legendFormat": "Adjustment rate (per 10m)", + "range": true, + "refId": "B" + } + ], + "title": "Target Adjustment Frequency", + "type": "timeseries" + }, + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 277 + }, + "id": 116, + "title": "Connection Health", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 278 + }, + "id": 117, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "connection_errors", + "legendFormat": "Connection errors", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(connection_errors[5m])", + "legendFormat": "Error rate (per 5m)", + "range": true, + "refId": "B" + } + ], + "title": "Connection Errors", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 278 + }, + "id": 118, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "reconnects", + "legendFormat": "Reconnection events", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(reconnects[5m])", + "legendFormat": "Reconnection rate (per 5m)", + "range": true, + "refId": "B" + } + ], + "title": "Reconnection Events", + "type": "timeseries" } ], "refresh": "5s", @@ -6241,4 +7801,4 @@ "uid": "64nrElFmk", "version": 1, "weekStart": "" -} \ No newline at end of file +} diff --git a/grafana/provisioning/dashboards/config-c/Docker Prometheus Monitoring-1571332751387.json b/grafana/provisioning/dashboards/config-c/Docker Prometheus Monitoring-1571332751387.json index 2abab52..9e9dd46 100644 --- a/grafana/provisioning/dashboards/config-c/Docker Prometheus Monitoring-1571332751387.json +++ b/grafana/provisioning/dashboards/config-c/Docker Prometheus Monitoring-1571332751387.json @@ -6141,6 +6141,1566 @@ ], "title": "Node Mermory", "type": "timeseries" + }, + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 217 + }, + "id": 100, + "title": "JDC Performance Metrics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Success rate %" + }, + "properties": [ + { + "id": "unit", + "value": "percent" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 218 + }, + "id": 101, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "jdc_jobs_declared", + "legendFormat": "Jobs declared", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "jdc_jobs_declared_success", + "legendFormat": "Jobs declared successfully", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "jdc_jobs_declared_error", + "legendFormat": "Job declaration errors", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "(jdc_jobs_declared_success * 100) / jdc_jobs_declared", + "legendFormat": "Success rate %", + "range": true, + "refId": "D" + } + ], + "title": "JDC Job Declaration Performance", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 218 + }, + "id": 102, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "jdc_job_declaration_latency", + "legendFormat": "Job declaration latency (job {{request_id}})", + "range": true, + "refId": "A" + } + ], + "title": "JDC Job Declaration Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 226 + }, + "id": 103, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "jdc_job_tokens_requested", + "legendFormat": "Tokens requested", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "jdc_job_tokens_allocated", + "legendFormat": "Tokens allocated", + "range": true, + "refId": "B" + } + ], + "title": "JDC Token Allocation", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 226 + }, + "id": 104, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "jdc_missing_transactions_provided", + "legendFormat": "Missing transactions provided", + "range": true, + "refId": "A" + } + ], + "title": "JDC Missing Transactions (Full Template Mode)", + "type": "timeseries" + }, + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 234 + }, + "id": 105, + "title": "Mining Job Distribution", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 235 + }, + "id": 106, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(mining_jobs_received[1m])", + "legendFormat": "Standard jobs per second", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(extended_mining_jobs_received[1m])", + "legendFormat": "Extended jobs per second", + "range": true, + "refId": "B" + } + ], + "title": "Job Arrival Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 235 + }, + "id": 107, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "mining_job_latency", + "legendFormat": "Job latency (job {{job_id}})", + "range": true, + "refId": "A" + } + ], + "title": "Mining Job Distribution Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 243 + }, + "id": 108, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(sv2_submitted_shares[5m])", + "legendFormat": "Extended shares/sec", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(sv2_standard_shares_submitted[5m])", + "legendFormat": "Standard shares/sec", + "range": true, + "refId": "B" + } + ], + "title": "Share Type Distribution", + "type": "timeseries" + }, + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 251 + }, + "id": 109, + "title": "Channel Management", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Channel closure rate" + }, + "properties": [ + { + "id": "unit", + "value": "cps" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 252 + }, + "id": 110, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "channels_opened", + "legendFormat": "Channels opened", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "channels_closed", + "legendFormat": "Channels closed", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(channels_closed[5m])", + "legendFormat": "Channel closure rate", + "range": true, + "refId": "C" + } + ], + "title": "Channel Lifecycle", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 252 + }, + "id": 111, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "channel_open_latency", + "legendFormat": "Channel open latency (channel {{channel_id}})", + "range": true, + "refId": "A" + } + ], + "title": "Channel Open Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 260 + }, + "id": 112, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "channel_updates", + "legendFormat": "Channel updates", + "range": true, + "refId": "A" + } + ], + "title": "Channel Updates (Hashrate Changes)", + "type": "timeseries" + }, + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 268 + }, + "id": 113, + "title": "Difficulty Management", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 269 + }, + "id": 114, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "min", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "current_target", + "legendFormat": "Current difficulty target", + "range": true, + "refId": "A" + } + ], + "title": "Difficulty Target History", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 269 + }, + "id": 115, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "target_adjustments", + "legendFormat": "Target adjustments", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(target_adjustments[10m])", + "legendFormat": "Adjustment rate (per 10m)", + "range": true, + "refId": "B" + } + ], + "title": "Target Adjustment Frequency", + "type": "timeseries" + }, + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 277 + }, + "id": 116, + "title": "Connection Health", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 278 + }, + "id": 117, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "connection_errors", + "legendFormat": "Connection errors", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(connection_errors[5m])", + "legendFormat": "Error rate (per 5m)", + "range": true, + "refId": "B" + } + ], + "title": "Connection Errors", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 278 + }, + "id": 118, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "reconnects", + "legendFormat": "Reconnection events", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "rate(reconnects[5m])", + "legendFormat": "Reconnection rate (per 5m)", + "range": true, + "refId": "B" + } + ], + "title": "Reconnection Events", + "type": "timeseries" } ], "refresh": "5s", @@ -6188,4 +7748,4 @@ "uid": "64nrElFmk", "version": 1, "weekStart": "" -} \ No newline at end of file +} diff --git a/prometheus/prometheus.yml b/prometheus/prometheus.yml index a0375b8..5b5e444 100644 --- a/prometheus/prometheus.yml +++ b/prometheus/prometheus.yml @@ -87,21 +87,21 @@ scrape_configs: - targets: ['sv2-tp-jdc-proxy:5678'] # The Network Traffic Metrics IP/port - job_name: 'sv2-pool-translator-proxy' - + # Override the global default and scrape targets from this job every 5 seconds. scrape_interval: 5s static_configs: - - targets: ['sv2-pool-translator-proxy:3456'] # The Network Traffic Metrics IP/port + - targets: ['sv2-translator:3456'] # The Network Traffic Metrics IP/port - job_name: 'sv2-tp-pool-proxy' - + # Override the global default and scrape targets from this job every 5 seconds. scrape_interval: 5s static_configs: - - targets: ['sv2-tp-pool-proxy:5678'] # The Network Traffic Metrics IP/port + - targets: ['sv2-tp-pool-side:5678'] # The Network Traffic Metrics IP/port - job_name: 'sv2-translator-miner-proxy' diff --git a/run-benchmarking-tool.sh b/run-benchmarking-tool.sh index 5e66771..5fe6ceb 100755 --- a/run-benchmarking-tool.sh +++ b/run-benchmarking-tool.sh @@ -79,9 +79,10 @@ fi # Prompt user to check if they want to configure the custom public key echo "" -echo -e "🚨 To customize the coinbase transaction output, a custom public key (or redeem script) is required." +echo -e "🚨 To customize the coinbase transaction output, a Bitcoin address or descriptor is required." +echo -e " In SV2 v1.5.0, coinbase outputs use Bitcoin descriptors format." echo "" -read -p "Do you want to configure your custom public key for the coinbase transaction? (yes/no, default is 'no'): " CONFIGURE_KEY +read -p "Do you want to configure your custom address for the coinbase transaction? (yes/no, default is 'no'): " CONFIGURE_KEY CONFIGURE_KEY=${CONFIGURE_KEY:-"no"} # Validate the CONFIGURE_KEY input @@ -93,18 +94,35 @@ fi # If the user wants to configure the key, prompt for public key and script type if [[ "$CONFIGURE_KEY" == "yes" ]]; then echo "" - echo -e "If you still don't have a public key, setup a new wallet and extract the extended public key it provides. At this point, you can derive the child public key using this script: https://github.com/stratum-mining/stratum/tree/dev/utils/bip32-key-derivation" + echo -e "You can provide either:" + echo -e " 1. A Bitcoin address (e.g., tb1qa0sm0hxzj0x25rh8gw5xlzwlsfvvyz8u96w3p8)" + echo -e " 2. A Bitcoin descriptor (e.g., wpkh(xpub...))" + echo -e " 3. A public key (will be converted to descriptor format)" echo "" - read -p "Now enter the public key (or redeem script) to use for generating the address in the coinbase transaction: " PUBLIC_KEY - echo "" - read -p "Enter the script type (P2PK, P2PKH, P2SH, P2WSH, P2WPKH, P2TR, default is 'P2WPKH'): " SCRIPT_TYPE - SCRIPT_TYPE=${SCRIPT_TYPE:-$DEFAULT_SCRIPT_TYPE} - - # Validate the script type - VALID_SCRIPT_TYPES=("P2PK" "P2PKH" "P2SH" "P2WSH" "P2WPKH" "P2TR") - if [[ ! " ${VALID_SCRIPT_TYPES[@]} " =~ " ${SCRIPT_TYPE} " ]]; then - echo "Invalid script type. Please enter one of the following: P2PK, P2PKH, P2SH, P2WSH, P2WPKH, P2TR." - exit 1 + read -p "Enter your Bitcoin address, descriptor, or public key: " PUBLIC_KEY + + # Check if it's already a descriptor or address + if [[ "$PUBLIC_KEY" =~ ^(addr|wpkh|sh|tr|pk)\( ]]; then + DESCRIPTOR="$PUBLIC_KEY" + elif [[ "$PUBLIC_KEY" =~ ^(tb1|bc1|[13]) ]]; then + # It's an address, wrap it in addr() descriptor + DESCRIPTOR="addr($PUBLIC_KEY)" + else + # It's a public key, ask for script type + echo "" + read -p "Enter the script type for your public key (P2PK, P2PKH, P2SH, P2WSH, P2WPKH, P2TR, default is 'P2WPKH'): " SCRIPT_TYPE + SCRIPT_TYPE=${SCRIPT_TYPE:-$DEFAULT_SCRIPT_TYPE} + + # Convert to descriptor + case "$SCRIPT_TYPE" in + "P2PK") DESCRIPTOR="pk($PUBLIC_KEY)" ;; + "P2PKH") DESCRIPTOR="pkh($PUBLIC_KEY)" ;; + "P2WPKH") DESCRIPTOR="wpkh($PUBLIC_KEY)" ;; + "P2SH") DESCRIPTOR="sh($PUBLIC_KEY)" ;; + "P2WSH") DESCRIPTOR="wsh($PUBLIC_KEY)" ;; + "P2TR") DESCRIPTOR="tr($PUBLIC_KEY)" ;; + *) echo "Invalid script type. Using P2WPKH as default."; DESCRIPTOR="wpkh($PUBLIC_KEY)" ;; + esac fi fi @@ -167,37 +185,37 @@ HASHRATE_CONFIG_FILES=( for config_file in "${HASHRATE_CONFIG_FILES[@]}"; do if [[ "$OSTYPE" == "darwin"* ]]; then # macOS uses -i '' for in-place editing - sed -i '' "s/min_individual_miner_hashrate = [0-9_]*\.0/min_individual_miner_hashrate = $hashrate/" "$config_file" - sed -i '' "s/channel_nominal_hashrate = [0-9_]*\.0/channel_nominal_hashrate = $hashrate/" "$config_file" + sed -i '' "s/min_individual_miner_hashrate[[:space:]]*=[[:space:]]*[0-9_]*\.0/min_individual_miner_hashrate = $hashrate/" "$config_file" + # Remove deprecated channel_nominal_hashrate field (removed in v1.5.0) + sed -i '' "/^[[:space:]]*channel_nominal_hashrate[[:space:]]*=/d" "$config_file" + sed -i '' "/^[[:space:]]*#.*channel_nominal_hashrate/d" "$config_file" else # Linux uses -i for in-place editing - sed -i "s/min_individual_miner_hashrate = [0-9_]*\.0/min_individual_miner_hashrate = $hashrate/" "$config_file" - sed -i "s/channel_nominal_hashrate = [0-9_]*\.0/channel_nominal_hashrate = $hashrate/" "$config_file" + sed -i "s/min_individual_miner_hashrate[[:space:]]*=[[:space:]]*[0-9_]*\.0/min_individual_miner_hashrate = $hashrate/" "$config_file" + # Remove deprecated channel_nominal_hashrate field (removed in v1.5.0) + sed -i "/^[[:space:]]*channel_nominal_hashrate[[:space:]]*=/d" "$config_file" + sed -i "/^[[:space:]]*#.*channel_nominal_hashrate/d" "$config_file" fi done -# Update JDC and Pool configs for custom public key and script type +# Update JDC and Pool configs for custom address using new v1.5.0 descriptor format if [[ "$CONFIGURE_KEY" == "yes" ]]; then for config_file in "${CONFIG_FILES[@]}"; do - awk -v script_type="$SCRIPT_TYPE" -v new_value="$PUBLIC_KEY" ' - BEGIN { in_coinbase_outputs = 0 } - /coinbase_outputs = \[/ { in_coinbase_outputs = 1 } - in_coinbase_outputs && /\{ output_script_type =/ { - if ($0 ~ "output_script_type = \"" script_type "\"") { - print " { output_script_type = \"" script_type "\", output_script_value = \"" new_value "\" }," - } else { - print "#" $0 - } - next - } - /]/ { in_coinbase_outputs = 0 } - { print } - ' "$config_file" > temp_config && mv temp_config "$config_file" + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "s|coinbase_reward_script = \"[^\"]*\"|coinbase_reward_script = \"$DESCRIPTOR\"|" "$config_file" + else + sed -i "s|coinbase_reward_script = \"[^\"]*\"|coinbase_reward_script = \"$DESCRIPTOR\"|" "$config_file" + fi done fi -# Update pool signature -for config_file in "${CONFIG_FILES[@]}"; do +# Update pool signature (only for pool configs, not JDC/JDS in v1.5.0) +POOL_CONFIG_FILES=( + "custom-configs/sri-roles/config-a/pool-config-a-docker-example.toml" + "custom-configs/sri-roles/config-c/pool-config-c-docker-example.toml" +) + +for config_file in "${POOL_CONFIG_FILES[@]}"; do if [[ "$OSTYPE" == "darwin"* ]]; then # macOS uses -i '' for in-place editing sed -i '' "s/pool_signature = \"[^\"]*\"/pool_signature = \"$POOL_SIGNATURE\"/" "$config_file" @@ -239,8 +257,25 @@ docker compose -f "docker-compose-config-${CONFIG_LOWER}.yaml" up -d # Display final messages echo "" -echo "${underline}Now point your miner(s) to the SV1 setup:${reset} stratum+tcp://:3333 ⛏️" -echo "${underline}And point your miner(s) to the SV2 setup:${reset} stratum+tcp://:34255 ⛏️" +echo "🔗 ${bold}Available Mining Connection Options:${reset}" +echo "" +echo "1️⃣ ${underline}SV1 Public Pool:${reset} stratum+tcp://:3333 ⛏️" +echo " 📋 Traditional Stratum v1 protocol for compatibility testing" +echo "" + +if [[ "$CONFIG" == "A" ]]; then + echo "2️⃣ ${underline}SV2 Translator Proxy:${reset} stratum+tcp://:34255 ⛏️" + echo " 📋 SV2 Translator for backward compatibility with SV1 miners" + echo "" + echo "3️⃣ ${underline}SV2 Job Declaration Client (JDC):${reset} stratum2+tcp://:34265 ⛏️" + echo " 📋 Native SV2 protocol with Job Declaration for custom transaction selection" +else + echo "2️⃣ ${underline}SV2 Translator Proxy:${reset} stratum+tcp://:34255 ⛏️" + echo " 📋 SV2 Translator for pool template mining (Config C)" + echo "" + echo "3️⃣ ${underline}SV2 Pool Direct:${reset} stratum2+tcp://:34254 ⛏️" + echo " 📋 Native SV2 protocol direct to pool (Config C - no JDC)" +fi echo "" echo "🚨 For SV1, you should use the address format [address].[nickname] as the username in your miner setup." echo "💡 For example, to configure a CPU miner, you can use: ./minerd -a sha256d -o stratum+tcp://127.0.0.1:3333 -q -D -P -u tb1qa0sm0hxzj0x25rh8gw5xlzwlsfvvyz8u96w3p8.sv2-gitgab19" diff --git a/sv1-custom-proxy/src/main.rs b/sv1-custom-proxy/src/main.rs index 1fc7bf0..b66d05b 100644 --- a/sv1-custom-proxy/src/main.rs +++ b/sv1-custom-proxy/src/main.rs @@ -195,21 +195,69 @@ async fn handle_rpc_request( let body_bytes = hyper::body::to_bytes(req.into_body()).await?; let body_str = String::from_utf8_lossy(&body_bytes); let mut is_get_block_template: bool = false; + let mut is_submitblock: bool = false; + let mut submitblock_timestamp: f64 = 0.0; if let Ok(json) = serde_json::from_slice::(&body_bytes) { if let Some(method) = json.get("method") { if method == "submitblock" { + is_submitblock = true; log::info!("Detected submitblock method."); - let current_timestamp = std::time::SystemTime::now() + submitblock_timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .expect("Time went backwards") .as_millis() as f64; + } else if method == "getblocktemplate" { + is_get_block_template = true; + } + } + } + + let client = Client::new(); + let mut new_req = Request::builder() + .method(method) + .uri(forward_uri) + .body(Body::from(body_bytes.clone())) + .expect("request builder"); + + *new_req.headers_mut() = headers.clone(); + + // Add authentication header if not already present + if !new_req.headers().contains_key("authorization") { + let auth_value = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="; + new_req + .headers_mut() + .insert("authorization", auth_value.parse().unwrap()); + } + + let res = match client.request(new_req).await { + Ok(res) => res, + Err(err) => { + log::error!("Error forwarding request: {}", err); + return Err(err); + } + }; + + let status = res.status(); + let body_bytes = hyper::body::to_bytes(res.into_body()).await?; + + if let Ok(json) = serde_json::from_slice::(&body_bytes) { + // Check if this is a submitblock response and if it was successful + if is_submitblock { + // A successful submitblock returns {"result":null,"error":null,"id":...} + // Any error or non-null result means the block was rejected + let is_success = json.get("result").map(|r| r.is_null()).unwrap_or(false) + && json.get("error").map(|e| e.is_null()).unwrap_or(true); + + if is_success { + log::info!("submitblock succeeded - counting as mined block"); + // Now fetch the share timestamp to calculate propagation time let prometheus_url = "http://10.5.0.19:2345/metrics"; - let client = reqwest::Client::new(); - if let Ok(response) = client.get(prometheus_url).send().await { - if let Ok(body) = response.text().await { + let prom_client = reqwest::Client::new(); + if let Ok(response) = prom_client.get(prometheus_url).send().await { + if let Ok(prom_body) = response.text().await { let mut nonce_found = false; - for line in body.lines() { + for line in prom_body.lines() { if let Some(start_index) = line.find("nonce=\"\\\"") { let start = start_index + "nonce=\"\\\"".len(); let end = match line[start..].find("\\\"") { @@ -219,11 +267,10 @@ async fn handle_rpc_request( "Failed to find end quote for nonce in line: {}", line ); - continue; // Skip to the next line if end quote for nonce is not found + continue; } }; let nonce_value = &line[start..end]; - // Decode the nonce hex string into bytes let nonce_bytes_result = hex::decode(nonce_value); let nonce_bytes = match nonce_bytes_result { Ok(bytes) => bytes, @@ -232,19 +279,17 @@ async fn handle_rpc_request( continue; } }; - // Perform bytes swap with the nonce bytes let swapped_bytes: Vec = nonce_bytes.iter().rev().cloned().collect(); let swapped_nonce = hex::encode_upper(&swapped_bytes).to_lowercase(); - // Check if the body contains the swapped nonce if body_str.contains(&swapped_nonce) { let parts: Vec<&str> = line.split_whitespace().collect(); if let Some(timestamp_str) = parts.get(1) { if let Ok(previous_timestamp) = timestamp_str.parse::() { - let latency = current_timestamp - previous_timestamp; + let latency = submitblock_timestamp - previous_timestamp; block_propagation_time.set(latency); mined_blocks.inc(); } @@ -255,45 +300,17 @@ async fn handle_rpc_request( } } if !nonce_found { - log::warn!("Nonce not found in Prometheus metrics"); + log::warn!("Nonce not found in Prometheus metrics for successful block"); + // Still increment the counter since the block was accepted + mined_blocks.inc(); } } } - } else if method == "getblocktemplate" { - is_get_block_template = true; + } else { + log::warn!("submitblock failed - not counting as mined block. Response: {:?}", json); } } - } - - let client = Client::new(); - let mut new_req = Request::builder() - .method(method) - .uri(forward_uri) - .body(Body::from(body_bytes.clone())) - .expect("request builder"); - - *new_req.headers_mut() = headers.clone(); - - // Add authentication header if not already present - if !new_req.headers().contains_key("authorization") { - let auth_value = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="; - new_req - .headers_mut() - .insert("authorization", auth_value.parse().unwrap()); - } - - let res = match client.request(new_req).await { - Ok(res) => res, - Err(err) => { - log::error!("Error forwarding request: {}", err); - return Err(err); - } - }; - let status = res.status(); - let body_bytes = hyper::body::to_bytes(res.into_body()).await?; - - if let Ok(json) = serde_json::from_slice::(&body_bytes) { if is_get_block_template { if let Some(result) = json.get("result") { if let Some(previousblockhash) = result.get("previousblockhash") { diff --git a/sv1-public-pool.dockerfile b/sv1-public-pool.dockerfile index 4fb470c..dde3dff 100644 --- a/sv1-public-pool.dockerfile +++ b/sv1-public-pool.dockerfile @@ -17,8 +17,8 @@ RUN apt-get update && \ curl \ && apt clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -# Clone the repository -RUN git clone https://github.com/benjamin-wilson/public-pool.git +# Clone the repository (using average-gary fork with timestamp validation fix) +RUN git clone https://github.com/average-gary/public-pool.git WORKDIR /public-pool diff --git a/sv2-custom-proxy/Cargo.toml b/sv2-custom-proxy/Cargo.toml index 7dcbb35..49268b2 100644 --- a/sv2-custom-proxy/Cargo.toml +++ b/sv2-custom-proxy/Cargo.toml @@ -4,14 +4,16 @@ version = "0.1.0" edition = "2021" [dependencies] -demand-easy-sv2 = { version = "=0.6.0" } +# Stratum v2 crates from v1.5.0 tag +stratum-common = { git = "https://github.com/stratum-mining/stratum.git", tag = "v1.5.0", features = ["with_network_helpers"] } +codec_sv2 = { git = "https://github.com/stratum-mining/stratum.git", tag = "v1.5.0", features = ["noise_sv2", "with_buffer_pool"] } +key-utils = { git = "https://github.com/stratum-mining/stratum.git", tag = "v1.5.0" } +async-channel = "1.8.0" + prometheus = "0.13" warp = "0.3" tokio = { version = "1.36.0", features = ["full", "tracing"] } -dotenv = "0.15.0" reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"] } serde_json = "1.0.120" -hex = "0.4.3" -log = "0.4.22" -env_logger = "0.11.6" -#serde = { version = "1.0.89", features = ["derive", "alloc"], default-features = false } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/sv2-custom-proxy/src/main.rs b/sv2-custom-proxy/src/main.rs index 23e057e..59ed43e 100644 --- a/sv2-custom-proxy/src/main.rs +++ b/sv2-custom-proxy/src/main.rs @@ -1,14 +1,32 @@ -use demand_easy_sv2::const_sv2::{ - MESSAGE_TYPE_NEW_TEMPLATE, MESSAGE_TYPE_SET_NEW_PREV_HASH, MESSAGE_TYPE_SUBMIT_SHARES_ERROR, - MESSAGE_TYPE_SUBMIT_SHARES_EXTENDED, MESSAGE_TYPE_SUBMIT_SHARES_SUCCESS, - MESSAGE_TYPE_SUBMIT_SOLUTION, +mod proxy; + +use stratum_common::roles_logic_sv2::{ + common_messages_sv2::{ + MESSAGE_TYPE_RECONNECT, MESSAGE_TYPE_SETUP_CONNECTION_ERROR, + }, + 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_SUCCESS, + }, + mining_sv2::{ + MESSAGE_TYPE_CLOSE_CHANNEL, MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB, + MESSAGE_TYPE_NEW_MINING_JOB, MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL_SUCCESS, + MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL_SUCCESS, 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, + }, + parsers_sv2::{AnyMessage, CommonMessages, JobDeclaration, Mining, TemplateDistribution}, + template_distribution_sv2::{ + MESSAGE_TYPE_NEW_TEMPLATE, MESSAGE_TYPE_SET_NEW_PREV_HASH, MESSAGE_TYPE_SUBMIT_SOLUTION, + }, }; -use demand_easy_sv2::roles_logic_sv2::parsers::{Mining, PoolMessages, TemplateDistribution}; -use demand_easy_sv2::{ProxyBuilder, Remote}; use prometheus::{ register_counter, register_gauge, register_gauge_vec, Counter, Encoder, Gauge, GaugeVec, TextEncoder, }; +use proxy::{ProxyBuilder, Remote}; use reqwest::Client; use serde_json::Value; use std::env; @@ -17,17 +35,20 @@ use std::net::ToSocketAddrs; use std::time::SystemTime; use tokio::net::TcpStream; use tokio::time::{sleep, Duration}; +use tracing::{error, info, warn}; use warp::Filter; #[tokio::main] async fn main() { - env_logger::Builder::from_env( - env_logger::Env::default() - .default_filter_or("coinswap=info") - .default_write_style_or("always"), - ) - .is_test(true) - .init(); + // Initialize tracing subscriber + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")) + ) + .init(); + + info!("SV2 Custom Proxy starting..."); let client_address = env::var("CLIENT").expect("CLIENT environment variable not set"); let server_address = env::var("SERVER").expect("SERVER environment variable not set"); let proxy_type = env::var("PROXY_TYPE").expect("PROXY_TYPE environment variable not set"); @@ -49,6 +70,36 @@ async fn main() { let mut block_propagation_time_through_sv2_pool: Option = None; let mut mined_blocks: Option = None; + // JDC-specific metrics + let mut jdc_job_tokens_requested: Option = None; + let mut jdc_job_tokens_allocated: Option = None; + let mut jdc_job_token_allocation_latency: Option = None; + let mut jdc_jobs_declared: Option = None; + let mut jdc_jobs_declared_success: Option = None; + let mut jdc_jobs_declared_error: Option = None; + let mut jdc_job_declaration_latency: Option = None; + let mut jdc_custom_job_set_success: Option = None; + let mut jdc_custom_job_set_error: Option = None; + let mut jdc_missing_transactions_provided: Option = None; + + // Mining job distribution metrics + let mut mining_jobs_received: Option = None; + let mut extended_mining_jobs_received: Option = None; + let mut mining_job_latency: Option = None; + let mut standard_shares_submitted: Option = None; + + // Channel management metrics + let mut channels_opened: Option = None; + let mut channel_open_latency: Option = None; + let mut channels_closed: Option = None; + let mut channel_updates: Option = None; + let mut target_adjustments: Option = None; + let mut current_target: Option = None; + + // Connection health metrics + let mut connection_errors: Option = None; + let mut reconnects: Option = None; + // Initialize metrics based on proxy_type match proxy_type.as_str() { "tp-jdc" => { @@ -98,6 +149,52 @@ async fn main() { ) .unwrap(), ); + // JDC-specific metrics + jdc_job_tokens_requested = Some( + register_counter!("jdc_job_tokens_requested", "Total JDC job tokens requested").unwrap(), + ); + jdc_job_tokens_allocated = Some( + register_counter!("jdc_job_tokens_allocated", "Total JDC job tokens allocated").unwrap(), + ); + jdc_job_token_allocation_latency = Some( + register_gauge_vec!( + "jdc_job_token_allocation_latency", + "Latency for JDC job token allocation in milliseconds", + &["token_id"] + ).unwrap(), + ); + jdc_jobs_declared = Some( + register_counter!("jdc_jobs_declared", "Total JDC jobs declared").unwrap(), + ); + jdc_jobs_declared_success = Some( + register_counter!("jdc_jobs_declared_success", "Total JDC jobs declared successfully").unwrap(), + ); + jdc_jobs_declared_error = Some( + register_counter!("jdc_jobs_declared_error", "Total JDC job declaration errors").unwrap(), + ); + jdc_job_declaration_latency = Some( + register_gauge_vec!( + "jdc_job_declaration_latency", + "Latency for JDC job declaration in milliseconds", + &["request_id"] + ).unwrap(), + ); + jdc_custom_job_set_success = Some( + register_counter!("jdc_custom_job_set_success", "Total custom jobs set successfully").unwrap(), + ); + jdc_custom_job_set_error = Some( + register_counter!("jdc_custom_job_set_error", "Total custom job set errors").unwrap(), + ); + jdc_missing_transactions_provided = Some( + register_counter!("jdc_missing_transactions_provided", "Total missing transactions provided").unwrap(), + ); + // Connection health metrics + connection_errors = Some( + register_counter!("connection_errors", "Total connection errors").unwrap(), + ); + reconnects = Some( + register_counter!("reconnects", "Total reconnection attempts").unwrap(), + ); } "tp-pool" => { mined_blocks = Some( @@ -147,6 +244,13 @@ async fn main() { ) .unwrap(), ); + // Connection health metrics + connection_errors = Some( + register_counter!("connection_errors", "Total connection errors").unwrap(), + ); + reconnects = Some( + register_counter!("reconnects", "Total reconnection attempts").unwrap(), + ); } "pool-translator" | "jdc-translator" => { submitted_shares = Some( @@ -170,6 +274,53 @@ async fn main() { ) .unwrap(), ); + standard_shares_submitted = Some( + register_counter!("sv2_standard_shares_submitted", "Total standard shares submitted").unwrap(), + ); + // Mining job distribution metrics + mining_jobs_received = Some( + register_counter!("mining_jobs_received", "Total mining jobs received").unwrap(), + ); + extended_mining_jobs_received = Some( + register_counter!("extended_mining_jobs_received", "Total extended mining jobs received").unwrap(), + ); + mining_job_latency = Some( + register_gauge_vec!( + "mining_job_latency", + "Latency for mining job distribution in milliseconds", + &["job_id"] + ).unwrap(), + ); + // Channel management metrics + channels_opened = Some( + register_counter!("channels_opened", "Total channels opened").unwrap(), + ); + channel_open_latency = Some( + register_gauge_vec!( + "channel_open_latency", + "Latency for channel opening in milliseconds", + &["channel_id"] + ).unwrap(), + ); + channels_closed = Some( + register_counter!("channels_closed", "Total channels closed").unwrap(), + ); + channel_updates = Some( + register_counter!("channel_updates", "Total channel updates").unwrap(), + ); + target_adjustments = Some( + register_counter!("target_adjustments", "Total difficulty target adjustments").unwrap(), + ); + current_target = Some( + register_gauge!("current_target", "Current mining difficulty target").unwrap(), + ); + // Connection health metrics + connection_errors = Some( + register_counter!("connection_errors", "Total connection errors").unwrap(), + ); + reconnects = Some( + register_counter!("reconnects", "Total reconnection attempts").unwrap(), + ); } _ => panic!("Invalid PROXY_TYPE"), } @@ -218,6 +369,35 @@ async fn main() { intercept_submit_share_success(&mut proxy_builder, valid.clone()).await; intercept_submit_share_error(&mut proxy_builder, stale.clone()).await; } + // Mining job distribution + if let (Some(jobs), Some(latency)) = (mining_jobs_received, mining_job_latency) { + intercept_new_mining_job(&mut proxy_builder, jobs.clone(), latency.clone()).await; + intercept_new_extended_mining_job(&mut proxy_builder, extended_mining_jobs_received.clone().unwrap(), latency.clone()).await; + } + if let Some(standard) = standard_shares_submitted { + intercept_submit_shares_standard(&mut proxy_builder, standard).await; + } + // Channel management + if let (Some(opened), Some(latency)) = (channels_opened, channel_open_latency) { + intercept_open_channel_success(&mut proxy_builder, opened.clone(), latency.clone(), MESSAGE_TYPE_OPEN_STANDARD_MINING_CHANNEL_SUCCESS).await; + intercept_open_channel_success(&mut proxy_builder, opened.clone(), latency.clone(), MESSAGE_TYPE_OPEN_EXTENDED_MINING_CHANNEL_SUCCESS).await; + } + if let Some(closed) = channels_closed { + intercept_close_channel(&mut proxy_builder, closed).await; + } + if let Some(updates) = channel_updates { + intercept_update_channel(&mut proxy_builder, updates).await; + } + if let (Some(adjustments), Some(target)) = (target_adjustments, current_target) { + intercept_set_target(&mut proxy_builder, adjustments, target).await; + } + // Connection health + if let Some(errors) = connection_errors { + intercept_setup_connection_error(&mut proxy_builder, errors).await; + } + if let Some(reconnect_counter) = reconnects { + intercept_reconnect(&mut proxy_builder, reconnect_counter).await; + } } "tp-pool" => { if let ( @@ -248,6 +428,13 @@ async fn main() { intercept_new_template(&mut proxy_builder, new_job_pool, sv2_block_template_value) .await; } + // Connection health + if let Some(errors) = connection_errors { + intercept_setup_connection_error(&mut proxy_builder, errors).await; + } + if let Some(reconnect_counter) = reconnects { + intercept_reconnect(&mut proxy_builder, reconnect_counter).await; + } } "tp-jdc" => { if let ( @@ -278,6 +465,32 @@ async fn main() { intercept_new_template(&mut proxy_builder, new_job_jdc, sv2_block_template_value) .await; } + // JDC-specific interceptors + if let (Some(tokens_req), Some(latency)) = (jdc_job_tokens_requested, jdc_job_token_allocation_latency) { + intercept_allocate_mining_job_token(&mut proxy_builder, tokens_req.clone(), latency.clone()).await; + if let Some(tokens_alloc) = jdc_job_tokens_allocated { + intercept_allocate_mining_job_token_success(&mut proxy_builder, tokens_alloc, latency.clone()).await; + } + } + if let (Some(declared), Some(latency)) = (jdc_jobs_declared, jdc_job_declaration_latency) { + intercept_declare_mining_job(&mut proxy_builder, declared.clone(), latency.clone()).await; + if let Some(success) = jdc_jobs_declared_success { + intercept_declare_mining_job_success(&mut proxy_builder, success, latency.clone()).await; + } + } + if let Some(error) = jdc_jobs_declared_error { + intercept_declare_mining_job_error(&mut proxy_builder, error).await; + } + if let Some(missing_tx) = jdc_missing_transactions_provided { + intercept_provide_missing_transactions_success(&mut proxy_builder, missing_tx).await; + } + // Connection health + if let Some(errors) = connection_errors { + intercept_setup_connection_error(&mut proxy_builder, errors).await; + } + if let Some(reconnect_counter) = reconnects { + intercept_reconnect(&mut proxy_builder, reconnect_counter).await; + } } _ => { panic!("Invalid PROXY_TYPE"); @@ -300,7 +513,29 @@ async fn listen_for_client(client_address: &str) -> TcpStream { async fn connect_to_server(server_address: &str) -> TcpStream { let address = server_address.to_socket_addrs().unwrap().next().unwrap(); - TcpStream::connect(address).await.unwrap() + + let max_retries = 10; + let mut retry_count = 0; + + loop { + match TcpStream::connect(address).await { + Ok(stream) => { + info!("Successfully connected to server at {}", server_address); + return stream; + } + Err(e) => { + retry_count += 1; + if retry_count >= max_retries { + error!("Failed to connect to server at {} after {} attempts: {}", + server_address, max_retries, e); + panic!("Unable to connect to server: {}", e); + } + warn!("Failed to connect to server at {} (attempt {}/{}): {}. Retrying in 2s...", + server_address, retry_count, max_retries, e); + sleep(Duration::from_secs(2)).await; + } + } + } } pub fn encode_hex(bytes: &[u8]) -> String { @@ -388,7 +623,7 @@ async fn fetch_last_block_reward_with_retries( match fetch_block_reward(hash).await { Ok(reward) => return Ok(reward), Err(e) => { - log::error!("Attempt {} failed: {}", attempt + 1, e); + error!("Attempt {} failed: {}", attempt + 1, e); attempt += 1; sleep(delay).await; } @@ -431,14 +666,10 @@ async fn intercept_prev_hash( last_block_mined_value: Gauge, last_sv2_block_template_value: Gauge, ) { - let mut r = builder.add_handler( - demand_easy_sv2::Remote::Server, - MESSAGE_TYPE_SET_NEW_PREV_HASH, - ); + let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_SET_NEW_PREV_HASH); tokio::spawn(async move { - while let Some(PoolMessages::TemplateDistribution(TemplateDistribution::SetNewPrevHash( - m, - ))) = r.recv().await + while let Ok(AnyMessage::TemplateDistribution(TemplateDistribution::SetNewPrevHash(m))) = + r.recv().await { let mut id = m.prev_hash; let d = id.inner_as_mut(); @@ -486,7 +717,7 @@ async fn intercept_prev_hash( if let Ok(value) = fetch_metric_result { last_sv2_block_template_value_clone.set(value); } else { - log::error!("Error fetching metric"); + error!("Error fetching metric"); } } }); @@ -499,9 +730,9 @@ async fn intercept_new_template( new_job_timestamp: GaugeVec, sv2_block_template_value: Gauge, ) { - let mut r = builder.add_handler(demand_easy_sv2::Remote::Server, MESSAGE_TYPE_NEW_TEMPLATE); + let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_NEW_TEMPLATE); tokio::spawn(async move { - while let Some(PoolMessages::TemplateDistribution(TemplateDistribution::NewTemplate(m))) = + while let Ok(AnyMessage::TemplateDistribution(TemplateDistribution::NewTemplate(m))) = r.recv().await { let id = m.template_id; @@ -531,9 +762,9 @@ async fn intercept_submit_share_extended( submitted_shares: Counter, gauge: GaugeVec, ) { - let mut r = builder.add_handler(Remote::Client, MESSAGE_TYPE_SUBMIT_SHARES_EXTENDED); + let r = builder.add_handler(Remote::Client, MESSAGE_TYPE_SUBMIT_SHARES_EXTENDED); tokio::spawn(async move { - while let Some(PoolMessages::Mining(Mining::SubmitSharesExtended(m))) = r.recv().await { + while let Ok(AnyMessage::Mining(Mining::SubmitSharesExtended(m))) = r.recv().await { submitted_shares.inc(); let id = m.nonce; @@ -557,19 +788,19 @@ async fn intercept_submit_share_extended( } async fn intercept_submit_share_success(builder: &mut ProxyBuilder, valid_shares: Counter) { - let mut r = builder.add_handler(Remote::Server, MESSAGE_TYPE_SUBMIT_SHARES_SUCCESS); + let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_SUBMIT_SHARES_SUCCESS); tokio::spawn(async move { - while let Some(PoolMessages::Mining(Mining::SubmitSharesSuccess(_m))) = r.recv().await { + while let Ok(AnyMessage::Mining(Mining::SubmitSharesSuccess(_m))) = r.recv().await { valid_shares.inc(); } }); } async fn intercept_submit_share_error(builder: &mut ProxyBuilder, stale_shares: Counter) { - let mut r = builder.add_handler(Remote::Server, MESSAGE_TYPE_SUBMIT_SHARES_ERROR); + let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_SUBMIT_SHARES_ERROR); tokio::spawn(async move { - while let Some(PoolMessages::Mining(Mining::SubmitSharesError(m))) = r.recv().await { - log::error!("SubmitSharesError received --> {:?}", m); + while let Ok(AnyMessage::Mining(Mining::SubmitSharesError(m))) = r.recv().await { + error!("SubmitSharesError received --> {:?}", m); stale_shares.inc(); } }); @@ -580,13 +811,12 @@ async fn intercept_submit_solution( block_propagation_time: Gauge, mined_blocks: Counter, ) { - let mut r = builder.add_handler(Remote::Client, MESSAGE_TYPE_SUBMIT_SOLUTION); + let r = builder.add_handler(Remote::Client, MESSAGE_TYPE_SUBMIT_SOLUTION); let client = Client::new(); tokio::spawn(async move { - while let Some(PoolMessages::TemplateDistribution(TemplateDistribution::SubmitSolution( - m, - ))) = r.recv().await + while let Ok(AnyMessage::TemplateDistribution(TemplateDistribution::SubmitSolution(m))) = + r.recv().await { let current_timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) @@ -616,3 +846,314 @@ async fn intercept_submit_solution( } }); } + +// JDC job declaration interceptors +async fn intercept_allocate_mining_job_token( + builder: &mut ProxyBuilder, + tokens_requested: Counter, + latency_gauge: GaugeVec, +) { + let r = builder.add_handler(Remote::Client, MESSAGE_TYPE_ALLOCATE_MINING_JOB_TOKEN); + tokio::spawn(async move { + while let Ok(AnyMessage::JobDeclaration(JobDeclaration::AllocateMiningJobToken(m))) = + r.recv().await + { + tokens_requested.inc(); + let current_time = SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Time went backwards") + .as_millis() as f64; + let token_id = String::from_utf8_lossy(m.user_identifier.inner_as_ref()).to_string(); + latency_gauge.with_label_values(&[&token_id]).set(current_time); + + let gauge_clone = latency_gauge.clone(); + tokio::spawn(async move { + sleep(Duration::from_secs(10)).await; + let _ = gauge_clone.remove_label_values(&[&token_id]); + }); + } + }); +} + +async fn intercept_allocate_mining_job_token_success( + builder: &mut ProxyBuilder, + tokens_allocated: Counter, + latency_gauge: GaugeVec, +) { + let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_ALLOCATE_MINING_JOB_TOKEN_SUCCESS); + tokio::spawn(async move { + while let Ok(AnyMessage::JobDeclaration(JobDeclaration::AllocateMiningJobTokenSuccess(m))) = + r.recv().await + { + tokens_allocated.inc(); + let current_time = SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Time went backwards") + .as_millis() as f64; + + // Calculate latency from request + let token_id = m.mining_job_token.to_vec().iter().map(|b| format!("{:02x}", b)).collect::(); + let request_time = latency_gauge.with_label_values(&[&token_id]).get(); + let latency = current_time - request_time; + if latency > 0.0 { + info!("JDC job token allocated in {} ms", latency); + } + } + }); +} + +async fn intercept_declare_mining_job( + builder: &mut ProxyBuilder, + jobs_declared: Counter, + latency_gauge: GaugeVec, +) { + let r = builder.add_handler(Remote::Client, MESSAGE_TYPE_DECLARE_MINING_JOB); + tokio::spawn(async move { + while let Ok(AnyMessage::JobDeclaration(JobDeclaration::DeclareMiningJob(m))) = + r.recv().await + { + jobs_declared.inc(); + let current_time = SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Time went backwards") + .as_millis() as f64; + let request_id = format!("{}", m.request_id); + latency_gauge.with_label_values(&[&request_id]).set(current_time); + + let gauge_clone = latency_gauge.clone(); + tokio::spawn(async move { + sleep(Duration::from_secs(10)).await; + let _ = gauge_clone.remove_label_values(&[&request_id]); + }); + } + }); +} + +async fn intercept_declare_mining_job_success( + builder: &mut ProxyBuilder, + jobs_success: Counter, + latency_gauge: GaugeVec, +) { + let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_DECLARE_MINING_JOB_SUCCESS); + tokio::spawn(async move { + while let Ok(AnyMessage::JobDeclaration(JobDeclaration::DeclareMiningJobSuccess(m))) = + r.recv().await + { + jobs_success.inc(); + let current_time = SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Time went backwards") + .as_millis() as f64; + + let request_id = format!("{}", m.request_id); + let request_time = latency_gauge.with_label_values(&[&request_id]).get(); + let latency = current_time - request_time; + if latency > 0.0 { + info!("JDC job declared successfully in {} ms", latency); + } + } + }); +} + +async fn intercept_declare_mining_job_error( + builder: &mut ProxyBuilder, + jobs_error: Counter, +) { + let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_DECLARE_MINING_JOB_ERROR); + tokio::spawn(async move { + while let Ok(AnyMessage::JobDeclaration(JobDeclaration::DeclareMiningJobError(m))) = + r.recv().await + { + error!("JDC job declaration error: {:?}", m.error_code.as_ref()); + jobs_error.inc(); + } + }); +} + +async fn intercept_provide_missing_transactions_success( + builder: &mut ProxyBuilder, + counter: Counter, +) { + let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_PROVIDE_MISSING_TRANSACTIONS_SUCCESS); + tokio::spawn(async move { + while let Ok(AnyMessage::JobDeclaration(JobDeclaration::ProvideMissingTransactionsSuccess(_m))) = + r.recv().await + { + counter.inc(); + } + }); +} + +// Mining job distribution interceptors +async fn intercept_new_mining_job( + builder: &mut ProxyBuilder, + jobs_received: Counter, + latency_gauge: GaugeVec, +) { + let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_NEW_MINING_JOB); + tokio::spawn(async move { + while let Ok(AnyMessage::Mining(Mining::NewMiningJob(m))) = r.recv().await { + jobs_received.inc(); + let current_time = SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Time went backwards") + .as_millis() as f64; + let job_id = format!("{}", m.job_id); + latency_gauge.with_label_values(&[&job_id]).set(current_time); + + let gauge_clone = latency_gauge.clone(); + tokio::spawn(async move { + sleep(Duration::from_secs(5)).await; + let _ = gauge_clone.remove_label_values(&[&job_id]); + }); + } + }); +} + +async fn intercept_new_extended_mining_job( + builder: &mut ProxyBuilder, + jobs_received: Counter, + latency_gauge: GaugeVec, +) { + let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_NEW_EXTENDED_MINING_JOB); + tokio::spawn(async move { + while let Ok(AnyMessage::Mining(Mining::NewExtendedMiningJob(m))) = r.recv().await { + jobs_received.inc(); + let current_time = SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Time went backwards") + .as_millis() as f64; + let job_id = format!("{}", m.job_id); + latency_gauge.with_label_values(&[&job_id]).set(current_time); + + let gauge_clone = latency_gauge.clone(); + tokio::spawn(async move { + sleep(Duration::from_secs(5)).await; + let _ = gauge_clone.remove_label_values(&[&job_id]); + }); + } + }); +} + +async fn intercept_submit_shares_standard( + builder: &mut ProxyBuilder, + standard_shares: Counter, +) { + let r = builder.add_handler(Remote::Client, MESSAGE_TYPE_SUBMIT_SHARES_STANDARD); + tokio::spawn(async move { + while let Ok(AnyMessage::Mining(Mining::SubmitSharesStandard(_m))) = r.recv().await { + standard_shares.inc(); + } + }); +} + +// Channel management interceptors +async fn intercept_open_channel_success( + builder: &mut ProxyBuilder, + channels_opened: Counter, + latency_gauge: GaugeVec, + message_type: u8, +) { + let r = builder.add_handler(Remote::Server, message_type); + tokio::spawn(async move { + loop { + let msg = r.recv().await; + match msg { + Ok(AnyMessage::Mining(Mining::OpenStandardMiningChannelSuccess(m))) => { + channels_opened.inc(); + let current_time = SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Time went backwards") + .as_millis() as f64; + let channel_id = format!("{}", m.channel_id); + latency_gauge.with_label_values(&[&channel_id]).set(current_time); + } + Ok(AnyMessage::Mining(Mining::OpenExtendedMiningChannelSuccess(m))) => { + channels_opened.inc(); + let current_time = SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Time went backwards") + .as_millis() as f64; + let channel_id = format!("{}", m.channel_id); + latency_gauge.with_label_values(&[&channel_id]).set(current_time); + } + _ => {} + } + } + }); +} + +async fn intercept_close_channel( + builder: &mut ProxyBuilder, + channels_closed: Counter, +) { + let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_CLOSE_CHANNEL); + tokio::spawn(async move { + while let Ok(AnyMessage::Mining(Mining::CloseChannel(m))) = r.recv().await { + info!("Channel {} closed: {}", m.channel_id, String::from_utf8_lossy(m.reason_code.as_ref())); + channels_closed.inc(); + } + }); +} + +async fn intercept_update_channel( + builder: &mut ProxyBuilder, + channel_updates: Counter, +) { + let r = builder.add_handler(Remote::Client, MESSAGE_TYPE_UPDATE_CHANNEL); + tokio::spawn(async move { + while let Ok(AnyMessage::Mining(Mining::UpdateChannel(_m))) = r.recv().await { + channel_updates.inc(); + } + }); +} + +async fn intercept_set_target( + builder: &mut ProxyBuilder, + target_adjustments: Counter, + current_target: Gauge, +) { + let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_SET_TARGET); + tokio::spawn(async move { + while let Ok(AnyMessage::Mining(Mining::SetTarget(m))) = r.recv().await { + target_adjustments.inc(); + // Convert target bytes to a numeric representation + let target_bytes = m.maximum_target.inner_as_ref(); + let target_value = u32::from_le_bytes([ + target_bytes[0], + target_bytes[1], + target_bytes[2], + target_bytes[3], + ]) as f64; + current_target.set(target_value); + info!("Difficulty target adjusted to {}", target_value); + } + }); +} + +// Connection health interceptors +async fn intercept_setup_connection_error( + builder: &mut ProxyBuilder, + connection_errors: Counter, +) { + let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_SETUP_CONNECTION_ERROR); + tokio::spawn(async move { + while let Ok(AnyMessage::Common(CommonMessages::SetupConnectionError(m))) = r.recv().await { + error!("Setup connection error: {}", String::from_utf8_lossy(m.error_code.as_ref())); + connection_errors.inc(); + } + }); +} + +async fn intercept_reconnect( + builder: &mut ProxyBuilder, + reconnects: Counter, +) { + let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_RECONNECT); + tokio::spawn(async move { + while let Ok(AnyMessage::Common(CommonMessages::Reconnect(_m))) = r.recv().await { + warn!("Reconnect requested by server"); + reconnects.inc(); + } + }); +} diff --git a/sv2-custom-proxy/src/proxy/into_static.rs b/sv2-custom-proxy/src/proxy/into_static.rs new file mode 100644 index 0000000..aff9523 --- /dev/null +++ b/sv2-custom-proxy/src/proxy/into_static.rs @@ -0,0 +1,109 @@ +use stratum_common::roles_logic_sv2::parsers_sv2::{ + AnyMessage, CommonMessages, JobDeclaration, Mining, TemplateDistribution, +}; + +pub fn into_static(m: AnyMessage<'_>) -> AnyMessage<'static> { + match m { + AnyMessage::Mining(m) => AnyMessage::Mining(into_static_mining(m)), + AnyMessage::Common(m) => AnyMessage::Common(into_static_common(m)), + AnyMessage::JobDeclaration(m) => AnyMessage::JobDeclaration(into_static_job_declaration(m)), + AnyMessage::TemplateDistribution(m) => { + AnyMessage::TemplateDistribution(into_static_template_distribution(m)) + } + } +} + +fn into_static_common(m: CommonMessages<'_>) -> CommonMessages<'static> { + match m { + CommonMessages::ChannelEndpointChanged(m) => { + CommonMessages::ChannelEndpointChanged(m.into_static()) + } + CommonMessages::SetupConnection(m) => CommonMessages::SetupConnection(m.into_static()), + CommonMessages::SetupConnectionError(m) => { + CommonMessages::SetupConnectionError(m.into_static()) + } + CommonMessages::SetupConnectionSuccess(m) => { + CommonMessages::SetupConnectionSuccess(m.into_static()) + } + CommonMessages::Reconnect(m) => CommonMessages::Reconnect(m.into_static()), + } +} + +fn into_static_mining(m: Mining<'_>) -> Mining<'static> { + match m { + Mining::CloseChannel(m) => Mining::CloseChannel(m.into_static()), + Mining::NewExtendedMiningJob(m) => Mining::NewExtendedMiningJob(m.into_static()), + Mining::NewMiningJob(m) => Mining::NewMiningJob(m.into_static()), + Mining::OpenExtendedMiningChannel(m) => Mining::OpenExtendedMiningChannel(m.into_static()), + Mining::OpenExtendedMiningChannelSuccess(m) => { + Mining::OpenExtendedMiningChannelSuccess(m.into_static()) + } + Mining::OpenMiningChannelError(m) => Mining::OpenMiningChannelError(m.into_static()), + Mining::OpenStandardMiningChannel(m) => Mining::OpenStandardMiningChannel(m.into_static()), + Mining::OpenStandardMiningChannelSuccess(m) => { + Mining::OpenStandardMiningChannelSuccess(m.into_static()) + } + Mining::SetCustomMiningJob(m) => Mining::SetCustomMiningJob(m.into_static()), + Mining::SetCustomMiningJobError(m) => Mining::SetCustomMiningJobError(m.into_static()), + Mining::SetCustomMiningJobSuccess(m) => Mining::SetCustomMiningJobSuccess(m), + Mining::SetExtranoncePrefix(m) => Mining::SetExtranoncePrefix(m.into_static()), + Mining::SetGroupChannel(m) => Mining::SetGroupChannel(m.into_static()), + Mining::SetNewPrevHash(m) => Mining::SetNewPrevHash(m.into_static()), + Mining::SetTarget(m) => Mining::SetTarget(m.into_static()), + Mining::SubmitSharesError(m) => Mining::SubmitSharesError(m.into_static()), + Mining::SubmitSharesExtended(m) => Mining::SubmitSharesExtended(m.into_static()), + Mining::SubmitSharesStandard(m) => Mining::SubmitSharesStandard(m), + Mining::SubmitSharesSuccess(m) => Mining::SubmitSharesSuccess(m), + Mining::UpdateChannel(m) => Mining::UpdateChannel(m.into_static()), + Mining::UpdateChannelError(m) => Mining::UpdateChannelError(m.into_static()), + } +} + +fn into_static_job_declaration(m: JobDeclaration<'_>) -> JobDeclaration<'static> { + match m { + JobDeclaration::AllocateMiningJobToken(m) => { + JobDeclaration::AllocateMiningJobToken(m.into_static()) + } + JobDeclaration::AllocateMiningJobTokenSuccess(m) => { + JobDeclaration::AllocateMiningJobTokenSuccess(m.into_static()) + } + JobDeclaration::DeclareMiningJob(m) => JobDeclaration::DeclareMiningJob(m.into_static()), + JobDeclaration::DeclareMiningJobError(m) => { + JobDeclaration::DeclareMiningJobError(m.into_static()) + } + JobDeclaration::DeclareMiningJobSuccess(m) => { + JobDeclaration::DeclareMiningJobSuccess(m.into_static()) + } + JobDeclaration::ProvideMissingTransactions(m) => { + JobDeclaration::ProvideMissingTransactions(m.into_static()) + } + JobDeclaration::ProvideMissingTransactionsSuccess(m) => { + JobDeclaration::ProvideMissingTransactionsSuccess(m.into_static()) + } + JobDeclaration::PushSolution(m) => JobDeclaration::PushSolution(m.into_static()), + } +} + +fn into_static_template_distribution(m: TemplateDistribution<'_>) -> TemplateDistribution<'static> { + match m { + TemplateDistribution::CoinbaseOutputConstraints(m) => { + TemplateDistribution::CoinbaseOutputConstraints(m.into_static()) + } + TemplateDistribution::NewTemplate(m) => TemplateDistribution::NewTemplate(m.into_static()), + TemplateDistribution::RequestTransactionData(m) => { + TemplateDistribution::RequestTransactionData(m.into_static()) + } + TemplateDistribution::RequestTransactionDataError(m) => { + TemplateDistribution::RequestTransactionDataError(m.into_static()) + } + TemplateDistribution::RequestTransactionDataSuccess(m) => { + TemplateDistribution::RequestTransactionDataSuccess(m.into_static()) + } + TemplateDistribution::SetNewPrevHash(m) => { + TemplateDistribution::SetNewPrevHash(m.into_static()) + } + TemplateDistribution::SubmitSolution(m) => { + TemplateDistribution::SubmitSolution(m.into_static()) + } + } +} diff --git a/sv2-custom-proxy/src/proxy/message_channel.rs b/sv2-custom-proxy/src/proxy/message_channel.rs new file mode 100644 index 0000000..ecfd787 --- /dev/null +++ b/sv2-custom-proxy/src/proxy/message_channel.rs @@ -0,0 +1,95 @@ +use crate::proxy::into_static; +use crate::proxy::{Frame_, StdFrame}; +use async_channel::{Receiver, Sender}; +use codec_sv2::framing_sv2::framing::Frame as EitherFrame; +use stratum_common::roles_logic_sv2::parsers_sv2::AnyMessage; +use tracing::{debug, error, trace}; + +pub type MessageType = u8; + +#[derive(PartialEq, Debug)] +pub enum Remote { + Client, + Server, +} + +pub struct MessageChannel { + pub message_type: MessageType, + pub expect_from: Remote, + pub receiver: Option>>, + pub sender: Sender>, +} + +impl MessageChannel { + pub async fn on_message(&mut self, frame: &mut Frame_) -> Option { + debug!("Handler checking message from {:?}", self.expect_from); + let (mt, message) = self.message_from_frame(frame); + debug!("Parsed message type {} from {:?}, handler expects type {}", mt, self.expect_from, self.message_type); + if mt == self.message_type { + if self.sender.send(message).await.is_err() { + eprintln!("Impossible to send message to message handler, for: {mt}"); + std::process::exit(1); + }; + if let Some(receiver) = &mut self.receiver { + if let Ok(message) = receiver.recv().await { + let frame: StdFrame = message + .try_into() + .expect("A message can always be converted in a frame"); + Some(frame.into()) + } else { + eprintln!("Impossible to receive message from message handler, for: {mt}"); + std::process::exit(1); + } + } else { + None + } + } else { + None + } + } + + fn message_from_frame(&self, frame: &mut Frame_) -> (u8, AnyMessage<'static>) { + let expect_from = &self.expect_from; + trace!("message_from_frame called for frame from {:?}", expect_from); + match frame { + EitherFrame::Sv2(frame) => { + if let Some(header) = frame.get_header() { + let mt = header.msg_type(); + trace!("Frame has message type: {}, getting payload...", mt); + let mut payload = frame.payload().to_vec(); + trace!("Payload length: {} bytes, attempting to parse as AnyMessage", payload.len()); + let maybe_message: Result, _> = + (mt, payload.as_mut_slice()).try_into(); + + match maybe_message { + Ok(message) => { + trace!("Successfully parsed message type {}", mt); + (mt, into_static(message)) + }, + Err(e) => { + error!("Failed to parse message type {} (0x{:02x}): {:?}, from: {}", mt, mt, e, expect_from); + eprintln!("Received frame with invalid payload or message type: {frame:?}, from: {expect_from}, error: {e:?}"); + std::process::exit(1); + } + } + } else { + eprintln!("Received frame with invalid header: {frame:?}, from: {expect_from}"); + std::process::exit(1); + } + } + EitherFrame::HandShake(f) => { + eprintln!("Received unexpected handshake frame: {f:?}, from: {expect_from}"); + std::process::exit(1); + } + } + } +} + +impl std::fmt::Display for Remote { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Client => write!(f, "client"), + Self::Server => write!(f, "server"), + } + } +} diff --git a/sv2-custom-proxy/src/proxy/mod.rs b/sv2-custom-proxy/src/proxy/mod.rs new file mode 100644 index 0000000..0e66c32 --- /dev/null +++ b/sv2-custom-proxy/src/proxy/mod.rs @@ -0,0 +1,13 @@ +mod into_static; +mod message_channel; +mod proxy_builder; + +pub use into_static::into_static; +pub use message_channel::{MessageChannel, Remote}; +pub use proxy_builder::ProxyBuilder; + +use codec_sv2::{StandardEitherFrame, StandardSv2Frame}; +use stratum_common::roles_logic_sv2::parsers_sv2::AnyMessage; + +pub type Frame_ = StandardEitherFrame>; +pub type StdFrame = StandardSv2Frame>; diff --git a/sv2-custom-proxy/src/proxy/proxy_builder.rs b/sv2-custom-proxy/src/proxy/proxy_builder.rs new file mode 100644 index 0000000..b40e950 --- /dev/null +++ b/sv2-custom-proxy/src/proxy/proxy_builder.rs @@ -0,0 +1,329 @@ +use crate::proxy::{Frame_, MessageChannel, Remote}; +use async_channel::{bounded, Receiver, Sender}; +use codec_sv2::{HandshakeRole, Initiator, Responder}; +use key_utils::{Error as KeyUtilsError, Secp256k1PublicKey, Secp256k1SecretKey}; +use stratum_common::{ + network_helpers_sv2::noise_connection::Connection, roles_logic_sv2::parsers_sv2::AnyMessage, +}; +use tokio::{net::TcpStream, select}; +use tracing::{debug, info, trace}; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ProxyError { + DownstreamClosed, + UpstreamClosed, +} + +pub struct Proxy { + from_client: Receiver, + to_client: Sender, + from_server: Receiver, + to_server: Sender, + handlers: Vec, +} + +impl Proxy { + pub async fn start(self) -> Result<(), ProxyError> { + let mut client_handlers = vec![]; + let mut server_handlers = vec![]; + info!("Proxy starting with {} message handlers", self.handlers.len()); + for handler in self.handlers { + info!(" Handler: message_type={} (0x{:02x}), expect_from={:?}", + handler.message_type, handler.message_type, handler.expect_from); + match handler.expect_from { + Remote::Client => client_handlers.push(handler), + Remote::Server => server_handlers.push(handler), + } + } + info!("Proxy handlers initialized: {} from client, {} from server", + client_handlers.len(), server_handlers.len()); + select! { + r = Self::recv_from_down_send_to_up(self.from_client, self.to_server, client_handlers) => r, + r = Self::recv_from_up_send_to_down(self.from_server, self.to_client, server_handlers) => r, + } + } + + async fn recv_from_down_send_to_up( + recv: Receiver, + send: Sender, + mut handlers: Vec, + ) -> Result<(), ProxyError> { + while let Ok(mut frame) = recv.recv().await { + trace!("recv_from_down_send_to_up: Received frame from client"); + let mut send_original_frame_upstream = true; + for handler in handlers.iter_mut() { + if let Some(frame) = handler.on_message(&mut frame).await { + debug!("Handler intercepted and replaced frame"); + send_original_frame_upstream = false; + if send.send(frame).await.is_err() { + return Err(ProxyError::UpstreamClosed); + }; + } + } + if send_original_frame_upstream { + trace!("Forwarding original frame upstream"); + if send.send(frame).await.is_err() { + return Err(ProxyError::UpstreamClosed); + } + }; + } + Err(ProxyError::DownstreamClosed) + } + + async fn recv_from_up_send_to_down( + recv: Receiver, + send: Sender, + mut handlers: Vec, + ) -> Result<(), ProxyError> { + while let Ok(mut frame) = recv.recv().await { + trace!("recv_from_up_send_to_down: Received frame from server"); + let mut send_original_frame_upstream = true; + for handler in handlers.iter_mut() { + if let Some(frame) = handler.on_message(&mut frame).await { + debug!("Handler intercepted and replaced frame"); + send_original_frame_upstream = false; + if send.send(frame).await.is_err() { + return Err(ProxyError::DownstreamClosed); + }; + } + } + if send_original_frame_upstream { + trace!("Forwarding original frame downstream"); + if send.send(frame).await.is_err() { + return Err(ProxyError::DownstreamClosed); + } + }; + } + Err(ProxyError::UpstreamClosed) + } +} + +pub struct ProxyBuilder { + from_client: Option>, + to_client: Option>, + from_server: Option>, + to_server: Option>, + cert_validity: u64, + proxy_pub_key: Secp256k1PublicKey, + proxy_sec_key: Secp256k1SecretKey, + server_auth_key: Option, + handlers: Vec, +} + +#[derive(Debug)] +#[allow(dead_code)] +pub enum ProxyBuilderError { + KeyError(KeyUtilsError), + ImpossibleToCompleteHandShakeWithDownstream, + ImpossibleToCompleteHandShakeWithUpstream, + IncompleteBuilder, + CanNotHaveMoreThan1Client, + CanNotHaveMoreThan1Server, +} + +impl Default for ProxyBuilder { + fn default() -> Self { + Self::new() + } +} + +impl ProxyBuilder { + #[allow(dead_code)] + pub fn new() -> Self { + Self { + from_client: None, + to_client: None, + from_server: None, + to_server: None, + cert_validity: 10000, + proxy_pub_key: "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" + .to_string() + .parse() + .expect("Invalid default pub key"), + proxy_sec_key: "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n" + .to_string() + .parse() + .expect("Invalid default sec key"), + server_auth_key: None, + handlers: vec![], + } + } + + #[allow(dead_code)] + pub fn try_with_client( + &mut self, + from_client: Receiver, + to_client: Sender, + ) -> Result<&mut Self, ProxyBuilderError> { + if self.from_client.is_none() && self.to_client.is_none() { + self.from_client = Some(from_client); + self.to_client = Some(to_client); + Ok(self) + } else { + Err(ProxyBuilderError::CanNotHaveMoreThan1Client) + } + } + + pub async fn try_add_client( + &mut self, + stream: TcpStream, + ) -> Result<&mut Self, ProxyBuilderError> { + if self.from_client.is_none() && self.to_client.is_none() { + let auth_pub_k_as_bytes = self.proxy_pub_key.into_bytes(); + let auth_prv_k_as_bytes = self.proxy_sec_key.into_bytes(); + let responder = Responder::from_authority_kp( + &auth_pub_k_as_bytes, + &auth_prv_k_as_bytes, + std::time::Duration::from_secs(self.cert_validity), + ) + .expect("invalid key pair"); + + if let Ok((receiver_from_client, send_to_client)) = + Connection::new::>(stream, HandshakeRole::Responder(responder)) + .await + { + self.from_client = Some(receiver_from_client); + self.to_client = Some(send_to_client); + Ok(self) + } else { + Err(ProxyBuilderError::ImpossibleToCompleteHandShakeWithDownstream) + } + } else { + Err(ProxyBuilderError::CanNotHaveMoreThan1Client) + } + } + + #[allow(dead_code)] + pub fn try_with_server( + &mut self, + from_server: Receiver, + to_server: Sender, + ) -> Result<&mut Self, ProxyBuilderError> { + if self.from_server.is_none() && self.to_server.is_none() { + self.from_server = Some(from_server); + self.to_server = Some(to_server); + Ok(self) + } else { + Err(ProxyBuilderError::CanNotHaveMoreThan1Server) + } + } + + pub async fn try_add_server( + &mut self, + stream: TcpStream, + ) -> Result<&mut Self, ProxyBuilderError> { + if self.from_server.is_none() && self.to_server.is_none() { + let initiator = match self.server_auth_key { + Some(key) => Initiator::from_raw_k(key.into_bytes()) + .expect("Pub key is already checked for validity"), + None => Initiator::without_pk().expect("This fn call can not fail"), + }; + + if let Ok((receiver_from_server, send_to_server)) = + Connection::new::>(stream, HandshakeRole::Initiator(initiator)) + .await + { + self.from_server = Some(receiver_from_server); + self.to_server = Some(send_to_server); + Ok(self) + } else { + Err(ProxyBuilderError::ImpossibleToCompleteHandShakeWithUpstream) + } + } else { + Err(ProxyBuilderError::CanNotHaveMoreThan1Server) + } + } + + #[allow(dead_code)] + pub fn override_cert_validity(&mut self, cert_validity: u64) -> &mut Self { + self.cert_validity = cert_validity; + self + } + + #[allow(dead_code)] + pub fn override_proxy_pub_key( + &mut self, + pub_key: String, + ) -> Result<&mut Self, ProxyBuilderError> { + self.proxy_pub_key = pub_key.parse()?; + Ok(self) + } + + #[allow(dead_code)] + pub fn override_proxy_sec_key( + &mut self, + sec_key: String, + ) -> Result<&mut Self, ProxyBuilderError> { + self.proxy_sec_key = sec_key.parse()?; + Ok(self) + } + + #[allow(dead_code)] + pub fn with_server_auth_key( + &mut self, + auth_key: String, + ) -> Result<&mut Self, ProxyBuilderError> { + let auth_pub_k: Secp256k1PublicKey = auth_key.parse()?; + self.server_auth_key = Some(auth_pub_k); + Ok(self) + } + + pub fn add_handler( + &mut self, + expect_from: Remote, + message_type: u8, + ) -> Receiver> { + let (s, r) = bounded(3); + let channel = MessageChannel { + message_type, + expect_from, + receiver: None, + sender: s, + }; + self.handlers.push(channel); + r + } + + #[allow(dead_code)] + pub fn add_handler_with_sender( + &mut self, + expect_from: Remote, + message_type: u8, + ) -> (Receiver>, Sender>) { + let (s, r) = bounded(3); + let (s1, r1) = bounded(3); + let channel = MessageChannel { + message_type, + expect_from, + receiver: Some(r1), + sender: s, + }; + self.handlers.push(channel); + (r, s1) + } + + pub fn try_build(self) -> Result { + if let (Some(from_client), Some(to_client), Some(from_server), Some(to_server)) = ( + self.from_client, + self.to_client, + self.from_server, + self.to_server, + ) { + Ok(Proxy { + from_client, + to_client, + from_server, + to_server, + handlers: self.handlers, + }) + } else { + Err(ProxyBuilderError::IncompleteBuilder) + } + } +} + +impl From for ProxyBuilderError { + fn from(value: KeyUtilsError) -> Self { + Self::KeyError(value) + } +} diff --git a/sv2-roles.dockerfile b/sv2-roles.dockerfile index aa92089..b737831 100644 --- a/sv2-roles.dockerfile +++ b/sv2-roles.dockerfile @@ -6,8 +6,8 @@ WORKDIR /usr/src/stratum/ # Install git and necessary build dependencies RUN apk add --no-cache git musl-dev pkgconfig libressl-dev -# Clone the repository and checkout the main branch -RUN git clone https://github.com/stratum-mining/stratum.git . +# Clone the repository and checkout tag 1.5.0 +RUN git clone https://github.com/stratum-mining/stratum.git . && git checkout v1.5.0 # Build the project in release mode WORKDIR /usr/src/stratum/roles/ @@ -26,7 +26,7 @@ RUN apk update && apk add --no-cache \ # Copy only the compiled binaries from the builder stage COPY --from=builder /usr/src/stratum/roles/target/release/pool_sv2 /usr/local/bin/pool_sv2 COPY --from=builder /usr/src/stratum/roles/target/release/jd_server /usr/local/bin/jd_server -COPY --from=builder /usr/src/stratum/roles/target/release/jd_client /usr/local/bin/jd_client +COPY --from=builder /usr/src/stratum/roles/target/release/jd_client_sv2 /usr/local/bin/jd_client COPY --from=builder /usr/src/stratum/roles/target/release/translator_sv2 /usr/local/bin/translator_sv2 # Set the working directory diff --git a/template-provider.dockerfile b/template-provider.dockerfile index f89c2e3..647f006 100644 --- a/template-provider.dockerfile +++ b/template-provider.dockerfile @@ -6,28 +6,44 @@ RUN apt-get update && apt-get upgrade -y # Install necessary tools RUN apt-get install -y wget tar curl jq -# Set environment variables for Bitcoin Core version and installation directory -ENV BITCOIN_VERSION=sv2-tp-0.1.9 +# Set environment variables for versions and installation directory +ENV SV2_TP_VERSION=v1.0.2 +ENV BITCOIN_VERSION=30.0rc1 ENV BITCOIN_DIR=/bitcoin # Create the directory where Bitcoin Core will be installed RUN mkdir -p $BITCOIN_DIR +# Download and install sv2-tp RUN ARCH=$(dpkg --print-architecture) && \ if [ "$ARCH" = "amd64" ]; then \ - BITCOIN_URL=https://github.com/Sjors/bitcoin/releases/download/$BITCOIN_VERSION/bitcoin-$BITCOIN_VERSION-x86_64-linux-gnu.tar.gz; \ + SV2_TP_URL=https://github.com/Sjors/sv2-tp/releases/download/$SV2_TP_VERSION/sv2-tp-1.0.2-x86_64-linux-gnu.tar.gz; \ elif [ "$ARCH" = "arm64" ]; then \ - BITCOIN_URL=https://github.com/Sjors/bitcoin/releases/download/$BITCOIN_VERSION/bitcoin-$BITCOIN_VERSION-aarch64-linux-gnu.tar.gz; \ + SV2_TP_URL=https://github.com/Sjors/sv2-tp/releases/download/$SV2_TP_VERSION/sv2-tp-1.0.2-aarch64-linux-gnu.tar.gz; \ else \ echo "Unsupported architecture"; exit 1; \ fi && \ - wget $BITCOIN_URL -O /tmp/bitcoin.tar.gz + wget $SV2_TP_URL -O /tmp/sv2-tp.tar.gz && \ + tar -xzvf /tmp/sv2-tp.tar.gz -C /tmp && \ + mkdir -p $BITCOIN_DIR/bin && \ + cp /tmp/sv2-tp-*/bin/* $BITCOIN_DIR/bin/ 2>/dev/null || cp /tmp/sv2-tp-*/sv2-tp $BITCOIN_DIR/bin/ && \ + rm -rf /tmp/sv2-tp.tar.gz /tmp/sv2-tp* -# Extract the downloaded tarball -RUN tar -xzvf /tmp/bitcoin.tar.gz -C $BITCOIN_DIR --strip-components=1 - -# Cleanup -RUN rm /tmp/bitcoin.tar.gz +# Download and install Bitcoin Core v30.0rc1 +RUN ARCH=$(dpkg --print-architecture) && \ + if [ "$ARCH" = "amd64" ]; then \ + BITCOIN_CORE_URL=https://bitcoincore.org/bin/bitcoin-core-30.0/test.rc1/bitcoin-30.0rc1-x86_64-linux-gnu.tar.gz; \ + elif [ "$ARCH" = "arm64" ]; then \ + BITCOIN_CORE_URL=https://bitcoincore.org/bin/bitcoin-core-30.0/test.rc1/bitcoin-30.0rc1-aarch64-linux-gnu.tar.gz; \ + else \ + echo "Unsupported architecture"; exit 1; \ + fi && \ + wget $BITCOIN_CORE_URL -O /tmp/bitcoin-core.tar.gz && \ + tar -xzvf /tmp/bitcoin-core.tar.gz -C /tmp && \ + mkdir -p $BITCOIN_DIR/bin && \ + cp /tmp/bitcoin-$BITCOIN_VERSION/bin/* $BITCOIN_DIR/bin/ && \ + cp /tmp/bitcoin-$BITCOIN_VERSION/libexec/* $BITCOIN_DIR/bin/ && \ + rm -rf /tmp/bitcoin-core.tar.gz /tmp/bitcoin-$BITCOIN_VERSION # Create a volume for blockchain data and configuration files VOLUME ["/root/.bitcoin"] \ No newline at end of file