From dbdfd51a56ac486e8f8cc1c131e67421c27537aa Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Mon, 29 Sep 2025 13:03:27 -0400 Subject: [PATCH 01/16] refactor: checkout v1.5.0 before build --- sv2-roles.dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sv2-roles.dockerfile b/sv2-roles.dockerfile index aa92089..c304104 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 1.5.0 # Build the project in release mode WORKDIR /usr/src/stratum/roles/ From 1fc7bc8d889622ae132c74be2ab571df8968b8bf Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Thu, 2 Oct 2025 12:14:02 -0400 Subject: [PATCH 02/16] use v1.5.0 --- sv2-roles.dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sv2-roles.dockerfile b/sv2-roles.dockerfile index c304104..85a3769 100644 --- a/sv2-roles.dockerfile +++ b/sv2-roles.dockerfile @@ -7,7 +7,7 @@ WORKDIR /usr/src/stratum/ RUN apk add --no-cache git musl-dev pkgconfig libressl-dev # Clone the repository and checkout tag 1.5.0 -RUN git clone https://github.com/stratum-mining/stratum.git . && git checkout 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/ From a0d6401d139673f56ad111fdbb6c557c7677406b Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Thu, 2 Oct 2025 12:17:43 -0400 Subject: [PATCH 03/16] feat(template-provider): Enhance SV2 Template Provider configuration and startup - Add new `start-sv2-tp.sh` script for improved bitcoind and sv2-tp startup - Update Bitcoin configuration files to enable IPC binding - Modify docker-compose files to use new startup script and environment variables - Update template-provider Dockerfile to use Sjors v1.0.2 of sv2-tp release - Simplify entrypoint commands in docker-compose configurations - Separate SV2 port and interval configuration using environment variables --- containers-scripts/start-sv2-tp.sh | 10 ++++++++++ custom-configs/sri-roles/bitcoin-tp-miner.conf | 1 + custom-configs/sri-roles/bitcoin-tp-pool.conf | 1 + docker-compose-config-a.yaml | 12 +++++++++--- docker-compose-config-c.yaml | 7 +++++-- template-provider.dockerfile | 6 +++--- 6 files changed, 29 insertions(+), 8 deletions(-) create mode 100755 containers-scripts/start-sv2-tp.sh diff --git a/containers-scripts/start-sv2-tp.sh b/containers-scripts/start-sv2-tp.sh new file mode 100755 index 0000000..6e71d2d --- /dev/null +++ b/containers-scripts/start-sv2-tp.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Start bitcoind in the background with IPC enabled +/bitcoin/bin/bitcoin -m -ipcbind=unix "$@" & + +# Wait a moment for bitcoind to start +sleep 5 + +# Start sv2-tp in the foreground +exec /bitcoin/bin/sv2-tp -sv2 -sv2port="$SV2_PORT" -sv2interval="$SV2_INTERVAL" -sv2feedelta=0 -debug=sv2 -loglevel=sv2:debug -sv2bind=0.0.0.0 "$@" \ 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/docker-compose-config-a.yaml b/docker-compose-config-a.yaml index ad134a7..3035b65 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", "-${NETWORK}"] + environment: + - SV2_PORT=8442 + - SV2_INTERVAL=${SV2_INTERVAL} ports: - "8442:8442" - "18333:48333" @@ -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", "-${NETWORK}"] + environment: + - SV2_PORT=8443 + - SV2_INTERVAL=${SV2_INTERVAL} ports: - "8443:8443" - "28333:18333" @@ -118,7 +124,7 @@ 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}"] ports: - "38333:18333" - "28332:18332" 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/template-provider.dockerfile b/template-provider.dockerfile index f89c2e3..ac48515 100644 --- a/template-provider.dockerfile +++ b/template-provider.dockerfile @@ -7,7 +7,7 @@ RUN apt-get update && apt-get upgrade -y 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 +ENV BITCOIN_VERSION=v1.0.2 ENV BITCOIN_DIR=/bitcoin # Create the directory where Bitcoin Core will be installed @@ -15,9 +15,9 @@ RUN mkdir -p $BITCOIN_DIR 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; \ + BITCOIN_URL=https://github.com/Sjors/sv2-tp/releases/download/$BITCOIN_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; \ + BITCOIN_URL=https://github.com/Sjors/sv2-tp/releases/download/$BITCOIN_VERSION/sv2-tp-1.0.2-aarch64-linux-gnu.tar.gz; \ else \ echo "Unsupported architecture"; exit 1; \ fi && \ From a9c35682c5425511065a625b98d93d8541df121d Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Thu, 2 Oct 2025 16:40:54 -0400 Subject: [PATCH 04/16] chore(config): Refactor SV2 configuration and startup scripts - Update CI workflow to include rustfmt component for code formatting - Modify template provider startup script to use bitcoin-node with specific testnet4 configuration - Update docker-compose configuration for template providers and bitcoin nodes - Adjust sv2-roles dockerfile to use correct binary names - Improve network and RPC configuration for bitcoin nodes - Remove network-specific flags from entrypoint scripts - Standardize logging and configuration across services --- .github/workflows/ci.yaml | 1 + containers-scripts/start-sv2-tp.sh | 10 ++--- docker-compose-config-a.yaml | 66 +++++++++++++++++------------- sv2-roles.dockerfile | 2 +- template-provider.dockerfile | 36 +++++++++++----- 5 files changed, 71 insertions(+), 44 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d7a36b5..061e21c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -45,6 +45,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 index 6e71d2d..1ced463 100755 --- a/containers-scripts/start-sv2-tp.sh +++ b/containers-scripts/start-sv2-tp.sh @@ -1,10 +1,10 @@ #!/bin/bash -# Start bitcoind in the background with IPC enabled -/bitcoin/bin/bitcoin -m -ipcbind=unix "$@" & +# 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 bitcoind to start +# Wait a moment for bitcoin to start sleep 5 -# Start sv2-tp in the foreground -exec /bitcoin/bin/sv2-tp -sv2 -sv2port="$SV2_PORT" -sv2interval="$SV2_INTERVAL" -sv2feedelta=0 -debug=sv2 -loglevel=sv2:debug -sv2bind=0.0.0.0 "$@" \ No newline at end of file +# 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/docker-compose-config-a.yaml b/docker-compose-config-a.yaml index 3035b65..afcccf1 100644 --- a/docker-compose-config-a.yaml +++ b/docker-compose-config-a.yaml @@ -62,7 +62,7 @@ services: labels: logging: "config-a" image: template-provider-builder-image - entrypoint: ["./scripts/start-sv2-tp.sh", "-${NETWORK}"] + entrypoint: ["./scripts/start-sv2-tp.sh"] environment: - SV2_PORT=8442 - SV2_INTERVAL=${SV2_INTERVAL} @@ -76,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: @@ -94,7 +94,7 @@ services: labels: logging: "config-a" image: template-provider-builder-image - entrypoint: ["./scripts/start-sv2-tp.sh", "-${NETWORK}"] + entrypoint: ["./scripts/start-sv2-tp.sh"] environment: - SV2_PORT=8443 - SV2_INTERVAL=${SV2_INTERVAL} @@ -106,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 @@ -124,7 +124,13 @@ services: labels: logging: "config-a" image: template-provider-builder-image - entrypoint: ["/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" @@ -134,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 @@ -154,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: @@ -191,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: @@ -298,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: @@ -404,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 @@ -421,7 +432,7 @@ services: platform: linux/amd64 environment: - "NTM_INTERFACE=any" - - "NTM_FILTERS=" + - "NTM_FILTERS=" prometheus: image: prom/prometheus:v2.36.2 @@ -564,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 @@ -595,7 +606,6 @@ services: aliases: - promtail - log-server: image: log-server-builder-image command: ["./log-server"] @@ -612,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/sv2-roles.dockerfile b/sv2-roles.dockerfile index 85a3769..b737831 100644 --- a/sv2-roles.dockerfile +++ b/sv2-roles.dockerfile @@ -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 ac48515..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=v1.0.2 +# 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/sv2-tp/releases/download/$BITCOIN_VERSION/sv2-tp-1.0.2-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/sv2-tp/releases/download/$BITCOIN_VERSION/sv2-tp-1.0.2-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 From 2952250efe90190c5f280e22255e7b939add24bf Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Thu, 2 Oct 2025 16:43:49 -0400 Subject: [PATCH 05/16] feat(config): Update SV2 configuration files for v1.5.0 compatibility - Refactor JDC, JDS, and Pool configuration files to align with v1.5.0 standards - Update coinbase reward script to use descriptor-based configuration - Modify network addresses and port configurations - Add full template mode and version support settings - Simplify and standardize configuration parameters across roles - Remove deprecated configuration options - Enhance configuration flexibility and readability --- .../config-a/jdc-config-a-docker-example.toml | 52 +++++++++---------- .../config-a/jds-config-a-docker-example.toml | 28 ++++++---- .../pool-config-a-docker-example.toml | 31 ++++++----- .../tproxy-config-a-docker-example.toml | 29 ++++++----- 4 files changed, 78 insertions(+), 62 deletions(-) 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..97fc3cd 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,19 @@ 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 = 10 + +# JDC supports two modes: +# "FULLTEMPLATE" - full template mining +# "COINBASEONLY" - coinbase-only mining +mode = "FULLTEMPLATE" # Template Provider config # Local TP (this is pointing to localhost so you must run a TP locally for this configuration to work) @@ -22,37 +33,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(tb1q2zc2k3uh6ketqd6yswzdy8evrcvd8yxn4sw0f5)" # 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..5b7b9fb 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(tb1q2zc2k3uh6ketqd6yswzdy8evrcvd8yxn4sw0f5)" # 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..6dd0157 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,17 +6,18 @@ 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(tb1q2zc2k3uh6ketqd6yswzdy8evrcvd8yxn4sw0f5)" + +# 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" @@ -25,4 +26,10 @@ pool_signature = "Stratum V2 SRI Pool" # 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 \ No newline at end of file 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..53e44cf 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,11 +3,6 @@ # 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_port = 34256 @@ -16,11 +11,18 @@ downstream_port = 34256 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" From a75c0d94b40ed7a778882c383dded61bafaa809d Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Fri, 3 Oct 2025 10:28:18 -0400 Subject: [PATCH 06/16] feat(config): Update SV2 configuration files for benchmarking - Update coinbase reward script address in all config files to a new address - Modify pool signature to "average-benchmark" in pool configurations - Update config-c pool configuration for v1.5.0 compatibility - Add timeout configuration in config-c pool settings - Remove channel nominal hashrate from tproxy configuration - Update run-benchmarking-tool.sh script to clarify Bitcoin descriptor usage - Standardize configuration files across different roles and configs This change prepares the configuration files for consistent benchmarking across different SV2 roles and ensures compatibility with v1.5.0 configuration requirements. --- .../config-a/jdc-config-a-docker-example.toml | 2 +- .../config-a/jds-config-a-docker-example.toml | 2 +- .../pool-config-a-docker-example.toml | 6 +- .../pool-config-c-docker-example.toml | 35 +++--- .../tproxy-config-c-docker-example.toml | 2 - run-benchmarking-tool.sh | 107 ++++++++++++------ 6 files changed, 97 insertions(+), 57 deletions(-) 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 97fc3cd..28e36aa 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 @@ -43,7 +43,7 @@ jdc_signature = "Sv2MinerSignature" # 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(tb1q2zc2k3uh6ketqd6yswzdy8evrcvd8yxn4sw0f5)" +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 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 5b7b9fb..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 @@ -14,7 +14,7 @@ min_supported_version = 2 # 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(tb1q2zc2k3uh6ketqd6yswzdy8evrcvd8yxn4sw0f5)" +coinbase_reward_script = "addr(tb1q3z6v6ga372pqwej23r0akpx3v6n6xdffglhd72)" # SRI Pool JD config listen_jd_address = "10.5.0.5:34264" 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 6dd0157..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 @@ -14,13 +14,13 @@ min_supported_version = 2 # 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(tb1q2zc2k3uh6ketqd6yswzdy8evrcvd8yxn4sw0f5)" +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) @@ -32,4 +32,4 @@ share_batch_size = 10 # Additional v1.5.0 compliance settings [timeout] unit = "secs" -value = 30 \ No newline at end of file +value = 30 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..a44cca0 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 @@ -32,5 +32,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/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" From dcb6242e9ea3d16d3e9271d800ceac7f2d0a13a5 Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Fri, 3 Oct 2025 11:47:21 -0400 Subject: [PATCH 07/16] ci(workflow): Add Clippy component to GitHub Actions Rust setup - Install Clippy component during Rust toolchain setup - Ensure Clippy is available for static code analysis in CI pipeline --- .github/workflows/ci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 061e21c..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 From ee0c04a5041788289b89ce7c4e9935356bf9fe9e Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Fri, 3 Oct 2025 15:34:11 -0400 Subject: [PATCH 08/16] feat(config): Add min_extranonce_size parameter to JDC configuration - Add new configuration parameter `min_extranonce_size` to JDC config - Set default value to 4 with valid range between 2 and 16 --- .../sri-roles/config-a/jdc-config-a-docker-example.toml | 5 +++++ 1 file changed, 5 insertions(+) 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 28e36aa..1b63f38 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 @@ -22,6 +22,11 @@ shares_per_minute = 6.0 # Share batch size share_batch_size = 10 +# 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 From d3e23a48c1a3a6531ec53c7bb718514ea00d7b00 Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Fri, 3 Oct 2025 17:05:55 -0400 Subject: [PATCH 09/16] feat(proxy): Migrate SV2 custom proxy to v1.5.0 crates - Update dependencies to use Stratum v2 crates from v1.5.0 tag - Refactor imports and message handling to match new SV2 library structure - Add new proxy module with helper functions for message handling based on `demand-easy-sv2` - Replace `demand-easy-sv2` with individual SV2 crates from the main repository - Modify message parsing and channel handling to align with v1.5.0 changes - Remove unused dependencies and simplify Cargo.toml configuration --- sv2-custom-proxy/Cargo.toml | 13 +- sv2-custom-proxy/src/main.rs | 49 ++- sv2-custom-proxy/src/proxy/into_static.rs | 107 ++++++ sv2-custom-proxy/src/proxy/message_channel.rs | 85 +++++ sv2-custom-proxy/src/proxy/mod.rs | 13 + sv2-custom-proxy/src/proxy/proxy_builder.rs | 312 ++++++++++++++++++ 6 files changed, 550 insertions(+), 29 deletions(-) create mode 100644 sv2-custom-proxy/src/proxy/into_static.rs create mode 100644 sv2-custom-proxy/src/proxy/message_channel.rs create mode 100644 sv2-custom-proxy/src/proxy/mod.rs create mode 100644 sv2-custom-proxy/src/proxy/proxy_builder.rs diff --git a/sv2-custom-proxy/Cargo.toml b/sv2-custom-proxy/Cargo.toml index 7dcbb35..d5dc190 100644 --- a/sv2-custom-proxy/Cargo.toml +++ b/sv2-custom-proxy/Cargo.toml @@ -4,14 +4,19 @@ version = "0.1.0" edition = "2021" [dependencies] -demand-easy-sv2 = { version = "=0.6.0" } +# Stratum v2 crates from v1.5.0 tag +mining_sv2 = { git = "https://github.com/stratum-mining/stratum.git", tag = "v1.5.0" } +template_distribution_sv2 = { git = "https://github.com/stratum-mining/stratum.git", tag = "v1.5.0" } +parsers_sv2 = { git = "https://github.com/stratum-mining/stratum.git", tag = "v1.5.0" } +codec_sv2 = { git = "https://github.com/stratum-mining/stratum.git", tag = "v1.5.0", features = ["noise_sv2", "with_buffer_pool"] } +network_helpers_sv2 = { git = "https://github.com/stratum-mining/stratum.git", tag = "v1.5.0", features = ["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 } diff --git a/sv2-custom-proxy/src/main.rs b/sv2-custom-proxy/src/main.rs index 23e057e..cfb947c 100644 --- a/sv2-custom-proxy/src/main.rs +++ b/sv2-custom-proxy/src/main.rs @@ -1,20 +1,24 @@ -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 mining_sv2::{ + MESSAGE_TYPE_SUBMIT_SHARES_ERROR, MESSAGE_TYPE_SUBMIT_SHARES_EXTENDED, + MESSAGE_TYPE_SUBMIT_SHARES_SUCCESS, }; -use demand_easy_sv2::roles_logic_sv2::parsers::{Mining, PoolMessages, TemplateDistribution}; -use demand_easy_sv2::{ProxyBuilder, Remote}; +use parsers_sv2::{AnyMessage, Mining, TemplateDistribution}; 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; use std::fmt::Write; use std::net::ToSocketAddrs; use std::time::SystemTime; +use template_distribution_sv2::{ + MESSAGE_TYPE_NEW_TEMPLATE, MESSAGE_TYPE_SET_NEW_PREV_HASH, MESSAGE_TYPE_SUBMIT_SOLUTION, +}; use tokio::net::TcpStream; use tokio::time::{sleep, Duration}; use warp::Filter; @@ -431,14 +435,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(); @@ -499,9 +499,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 +531,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,18 +557,18 @@ 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 { + while let Ok(AnyMessage::Mining(Mining::SubmitSharesError(m))) = r.recv().await { log::error!("SubmitSharesError received --> {:?}", m); stale_shares.inc(); } @@ -580,13 +580,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) 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..a13c7a3 --- /dev/null +++ b/sv2-custom-proxy/src/proxy/into_static.rs @@ -0,0 +1,107 @@ +use 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..a5e7306 --- /dev/null +++ b/sv2-custom-proxy/src/proxy/message_channel.rs @@ -0,0 +1,85 @@ +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 parsers_sv2::AnyMessage; + +pub type MessageType = u8; + +#[derive(PartialEq)] +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 { + let (mt, message) = self.message_from_frame(frame); + 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; + match frame { + EitherFrame::Sv2(frame) => { + if let Some(header) = frame.get_header() { + let mt = header.msg_type(); + let mut payload = frame.payload().to_vec(); + let maybe_message: Result, _> = + (mt, payload.as_mut_slice()).try_into(); + + match maybe_message { + Ok(message) => (mt, into_static(message)), + _ => { + eprintln!("Received frame with invalid payload or message type: {frame:?}, from: {expect_from}"); + 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..9ee840c --- /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 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..f0c1f52 --- /dev/null +++ b/sv2-custom-proxy/src/proxy/proxy_builder.rs @@ -0,0 +1,312 @@ +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 network_helpers_sv2::noise_connection::Connection; +use parsers_sv2::AnyMessage; +use tokio::{net::TcpStream, select}; + +#[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![]; + for handler in self.handlers { + match handler.expect_from { + Remote::Client => client_handlers.push(handler), + Remote::Server => server_handlers.push(handler), + } + } + 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 { + let mut send_original_frame_upstream = true; + for handler in handlers.iter_mut() { + if let Some(frame) = handler.on_message(&mut frame).await { + send_original_frame_upstream = false; + if send.send(frame).await.is_err() { + return Err(ProxyError::UpstreamClosed); + }; + } + } + if send_original_frame_upstream && 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 { + let mut send_original_frame_upstream = true; + for handler in handlers.iter_mut() { + if let Some(frame) = handler.on_message(&mut frame).await { + send_original_frame_upstream = false; + if send.send(frame).await.is_err() { + return Err(ProxyError::DownstreamClosed); + }; + } + } + if send_original_frame_upstream && 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) + } +} From 876c0b231efc46ecf4d8037dec87ab6379c2c388 Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Mon, 6 Oct 2025 10:50:04 -0400 Subject: [PATCH 10/16] feat(proxy): Upgrade SV2 custom proxy to use v1.5.0 crates and tracing - Replace log and env_logger with tracing and tracing-subscriber - Update dependencies to use new stratum-common crate structure - Refactor import paths for SV2 message types and parsers - Add connection retry mechanism for server connection - Improve error handling and logging with tracing - Update main function to use tracing initialization - Add more detailed logging and error messages - Simplify dependency management by consolidating crate imports Motivation: Align with latest Stratum V2 library version and improve observability and error handling in the custom proxy implementation. --- sv2-custom-proxy/Cargo.toml | 9 +-- sv2-custom-proxy/src/main.rs | 63 +++++++++++++------ sv2-custom-proxy/src/proxy/into_static.rs | 4 +- sv2-custom-proxy/src/proxy/message_channel.rs | 20 ++++-- sv2-custom-proxy/src/proxy/mod.rs | 2 +- sv2-custom-proxy/src/proxy/proxy_builder.rs | 29 +++++++-- 6 files changed, 90 insertions(+), 37 deletions(-) diff --git a/sv2-custom-proxy/Cargo.toml b/sv2-custom-proxy/Cargo.toml index d5dc190..49268b2 100644 --- a/sv2-custom-proxy/Cargo.toml +++ b/sv2-custom-proxy/Cargo.toml @@ -5,11 +5,8 @@ edition = "2021" [dependencies] # Stratum v2 crates from v1.5.0 tag -mining_sv2 = { git = "https://github.com/stratum-mining/stratum.git", tag = "v1.5.0" } -template_distribution_sv2 = { git = "https://github.com/stratum-mining/stratum.git", tag = "v1.5.0" } -parsers_sv2 = { git = "https://github.com/stratum-mining/stratum.git", tag = "v1.5.0" } +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"] } -network_helpers_sv2 = { git = "https://github.com/stratum-mining/stratum.git", tag = "v1.5.0", features = ["with_buffer_pool"] } key-utils = { git = "https://github.com/stratum-mining/stratum.git", tag = "v1.5.0" } async-channel = "1.8.0" @@ -18,5 +15,5 @@ warp = "0.3" tokio = { version = "1.36.0", features = ["full", "tracing"] } reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"] } serde_json = "1.0.120" -log = "0.4.22" -env_logger = "0.11.6" +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 cfb947c..06170d0 100644 --- a/sv2-custom-proxy/src/main.rs +++ b/sv2-custom-proxy/src/main.rs @@ -1,10 +1,15 @@ mod proxy; -use mining_sv2::{ - MESSAGE_TYPE_SUBMIT_SHARES_ERROR, MESSAGE_TYPE_SUBMIT_SHARES_EXTENDED, - MESSAGE_TYPE_SUBMIT_SHARES_SUCCESS, +use stratum_common::roles_logic_sv2::{ + mining_sv2::{ + MESSAGE_TYPE_SUBMIT_SHARES_ERROR, MESSAGE_TYPE_SUBMIT_SHARES_EXTENDED, + MESSAGE_TYPE_SUBMIT_SHARES_SUCCESS, + }, + parsers_sv2::{AnyMessage, Mining, TemplateDistribution}, + template_distribution_sv2::{ + MESSAGE_TYPE_NEW_TEMPLATE, MESSAGE_TYPE_SET_NEW_PREV_HASH, MESSAGE_TYPE_SUBMIT_SOLUTION, + }, }; -use parsers_sv2::{AnyMessage, Mining, TemplateDistribution}; use prometheus::{ register_counter, register_gauge, register_gauge_vec, Counter, Encoder, Gauge, GaugeVec, TextEncoder, @@ -16,22 +21,22 @@ use std::env; use std::fmt::Write; use std::net::ToSocketAddrs; use std::time::SystemTime; -use template_distribution_sv2::{ - MESSAGE_TYPE_NEW_TEMPLATE, MESSAGE_TYPE_SET_NEW_PREV_HASH, MESSAGE_TYPE_SUBMIT_SOLUTION, -}; 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"); @@ -304,7 +309,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 { @@ -392,7 +419,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; } @@ -486,7 +513,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"); } } }); @@ -569,7 +596,7 @@ async fn intercept_submit_share_error(builder: &mut ProxyBuilder, stale_shares: let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_SUBMIT_SHARES_ERROR); tokio::spawn(async move { while let Ok(AnyMessage::Mining(Mining::SubmitSharesError(m))) = r.recv().await { - log::error!("SubmitSharesError received --> {:?}", m); + error!("SubmitSharesError received --> {:?}", m); stale_shares.inc(); } }); diff --git a/sv2-custom-proxy/src/proxy/into_static.rs b/sv2-custom-proxy/src/proxy/into_static.rs index a13c7a3..aff9523 100644 --- a/sv2-custom-proxy/src/proxy/into_static.rs +++ b/sv2-custom-proxy/src/proxy/into_static.rs @@ -1,4 +1,6 @@ -use parsers_sv2::{AnyMessage, CommonMessages, JobDeclaration, Mining, TemplateDistribution}; +use stratum_common::roles_logic_sv2::parsers_sv2::{ + AnyMessage, CommonMessages, JobDeclaration, Mining, TemplateDistribution, +}; pub fn into_static(m: AnyMessage<'_>) -> AnyMessage<'static> { match m { diff --git a/sv2-custom-proxy/src/proxy/message_channel.rs b/sv2-custom-proxy/src/proxy/message_channel.rs index a5e7306..ecfd787 100644 --- a/sv2-custom-proxy/src/proxy/message_channel.rs +++ b/sv2-custom-proxy/src/proxy/message_channel.rs @@ -2,11 +2,12 @@ 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 parsers_sv2::AnyMessage; +use stratum_common::roles_logic_sv2::parsers_sv2::AnyMessage; +use tracing::{debug, error, trace}; pub type MessageType = u8; -#[derive(PartialEq)] +#[derive(PartialEq, Debug)] pub enum Remote { Client, Server, @@ -21,7 +22,9 @@ pub struct MessageChannel { 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}"); @@ -47,18 +50,25 @@ impl MessageChannel { 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) => (mt, into_static(message)), - _ => { - eprintln!("Received frame with invalid payload or message type: {frame:?}, from: {expect_from}"); + 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); } } diff --git a/sv2-custom-proxy/src/proxy/mod.rs b/sv2-custom-proxy/src/proxy/mod.rs index 9ee840c..0e66c32 100644 --- a/sv2-custom-proxy/src/proxy/mod.rs +++ b/sv2-custom-proxy/src/proxy/mod.rs @@ -7,7 +7,7 @@ pub use message_channel::{MessageChannel, Remote}; pub use proxy_builder::ProxyBuilder; use codec_sv2::{StandardEitherFrame, StandardSv2Frame}; -use parsers_sv2::AnyMessage; +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 index f0c1f52..b40e950 100644 --- a/sv2-custom-proxy/src/proxy/proxy_builder.rs +++ b/sv2-custom-proxy/src/proxy/proxy_builder.rs @@ -2,9 +2,11 @@ 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 network_helpers_sv2::noise_connection::Connection; -use parsers_sv2::AnyMessage; +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 { @@ -24,12 +26,17 @@ 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, @@ -42,17 +49,22 @@ impl Proxy { 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 && 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) @@ -64,17 +76,22 @@ impl Proxy { 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 && 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) From f478a9f78aa291c39d4750aa02289d19dd8ad804 Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Mon, 6 Oct 2025 11:36:11 -0400 Subject: [PATCH 11/16] feat(metrics): Add comprehensive metrics for SV2 proxy components - Introduce new metrics for Job Declaration (JDC) workflow - Add metrics for mining job distribution and tracking - Implement connection health and channel management metrics - Create counters and gauges for tracking job tokens, declarations, and allocations - Add latency tracking for job token allocation and job declarations - Enhance observability for standard shares, mining jobs, and channel operations - Prepare metrics infrastructure for different proxy types (tp-jdc, tp-pool, translators) Improves system monitoring and provides deeper insights into SV2 proxy performance and behavior. --- sv2-custom-proxy/src/main.rs | 523 ++++++++++++++++++++++++++++++++++- 1 file changed, 519 insertions(+), 4 deletions(-) diff --git a/sv2-custom-proxy/src/main.rs b/sv2-custom-proxy/src/main.rs index 06170d0..ae75a85 100644 --- a/sv2-custom-proxy/src/main.rs +++ b/sv2-custom-proxy/src/main.rs @@ -1,11 +1,23 @@ 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_SUCCESS, + MESSAGE_TYPE_SUBMIT_SHARES_STANDARD, MESSAGE_TYPE_SUBMIT_SHARES_SUCCESS, + MESSAGE_TYPE_UPDATE_CHANNEL, }, - parsers_sv2::{AnyMessage, Mining, TemplateDistribution}, + parsers_sv2::{AnyMessage, CommonMessages, JobDeclaration, Mining, TemplateDistribution}, template_distribution_sv2::{ MESSAGE_TYPE_NEW_TEMPLATE, MESSAGE_TYPE_SET_NEW_PREV_HASH, MESSAGE_TYPE_SUBMIT_SOLUTION, }, @@ -58,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" => { @@ -107,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( @@ -156,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( @@ -179,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"), } @@ -227,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 ( @@ -257,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 ( @@ -287,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"); @@ -586,8 +790,8 @@ async fn intercept_submit_share_extended( async fn intercept_submit_share_success(builder: &mut ProxyBuilder, valid_shares: Counter) { let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_SUBMIT_SHARES_SUCCESS); tokio::spawn(async move { - while let Ok(AnyMessage::Mining(Mining::SubmitSharesSuccess(_m))) = r.recv().await { - valid_shares.inc(); + while let Ok(AnyMessage::Mining(Mining::SubmitSharesSuccess(m))) = r.recv().await { + valid_shares.inc_by(m.new_submits_accepted_count as f64); } }); } @@ -642,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(); + } + }); +} From 3326c3219d1d223e520b19c54c27a07a7080f68e Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Mon, 6 Oct 2025 11:48:29 -0400 Subject: [PATCH 12/16] feat(grafana): Add JDC performance metrics to Prometheus monitoring dashboard - Extend Docker Prometheus Monitoring dashboard with new JDC performance metrics section - Add timeseries graphs for job declaration performance metrics - Include metrics for: * Total jobs declared * Successful job declarations * Job declaration errors * Job declaration success rate percentage - Add job declaration latency tracking graph - Enhance dashboard with detailed performance insights for JDC components --- ...r Prometheus Monitoring-1571332751387.json | 1562 ++++++++++++++++- ...r Prometheus Monitoring-1571332751387.json | 1562 ++++++++++++++++- 2 files changed, 3122 insertions(+), 2 deletions(-) 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 +} From 10a1269c9886142d83ec6a546a04b94fcf9f6c6f Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Mon, 6 Oct 2025 13:55:10 -0400 Subject: [PATCH 13/16] feat(proxy): Enhance SV2 custom proxy block submission and metrics tracking - Modify JDC configuration to reduce share batch size from 10 to 1 - Update tproxy configuration to add `aggregate_channels` option - Refactor block submission handling in SV1 custom proxy - Add detailed logging and metrics tracking for successful block submissions - Implement block propagation time calculation using Prometheus metrics - Add authentication header to RPC requests if not already present - Improve error handling and logging for block submission process Enhances monitoring and configuration flexibility for SV2 proxy components, providing more granular control over mining operations and better tracking of block submission performance. --- .../config-a/jdc-config-a-docker-example.toml | 2 +- .../tproxy-config-c-docker-example.toml | 3 + sv1-custom-proxy/src/main.rs | 103 ++++++++++-------- 3 files changed, 64 insertions(+), 44 deletions(-) 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 1b63f38..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 @@ -20,7 +20,7 @@ user_identity = "jdc_user" shares_per_minute = 6.0 # Share batch size -share_batch_size = 10 +share_batch_size = 1 # Minimum extranonce size for downstream connections # This controls the minimum size of the extranonce field 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 a44cca0..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) 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") { From 23d249b74b9abb2cccf59f913fcaaaeb9aa95f9f Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Mon, 6 Oct 2025 14:04:05 -0400 Subject: [PATCH 14/16] feat(config): Update Prometheus and tproxy configuration for network deployment - Modify tproxy configuration to use '0.0.0.0' for downstream address - Update Prometheus scrape targets for SV2 components - Change service names in Prometheus configuration to match current deployment - Improve network configuration flexibility for distributed setup --- .../config-a/tproxy-config-a-docker-example.toml | 2 +- prometheus/prometheus.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) 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 53e44cf..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 @@ -4,7 +4,7 @@ # upstream_port = 3336 # Local Mining Device Downstream Connection -downstream_address = "10.5.0.7" +downstream_address = "0.0.0.0" downstream_port = 34256 # Version support 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' From 7ce7372d8075355476a5a03acb774ec19bf1e293 Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Mon, 6 Oct 2025 14:13:48 -0400 Subject: [PATCH 15/16] fix(proxy): Update share counting logic in submit share success handler - Modify `intercept_submit_share_success` to increment valid shares counter by 1 - Remove dependency on `new_submits_accepted_count` field - Simplify share tracking mechanism for more consistent metrics reporting - Ensure each successful share submission increments the counter exactly once --- sv2-custom-proxy/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sv2-custom-proxy/src/main.rs b/sv2-custom-proxy/src/main.rs index ae75a85..59ed43e 100644 --- a/sv2-custom-proxy/src/main.rs +++ b/sv2-custom-proxy/src/main.rs @@ -790,8 +790,8 @@ async fn intercept_submit_share_extended( async fn intercept_submit_share_success(builder: &mut ProxyBuilder, valid_shares: Counter) { let r = builder.add_handler(Remote::Server, MESSAGE_TYPE_SUBMIT_SHARES_SUCCESS); tokio::spawn(async move { - while let Ok(AnyMessage::Mining(Mining::SubmitSharesSuccess(m))) = r.recv().await { - valid_shares.inc_by(m.new_submits_accepted_count as f64); + while let Ok(AnyMessage::Mining(Mining::SubmitSharesSuccess(_m))) = r.recv().await { + valid_shares.inc(); } }); } From 3db631b1915b55e87aeacf3024c3c0bbc77cbf7b Mon Sep 17 00:00:00 2001 From: Gary Krause Date: Wed, 8 Oct 2025 10:02:15 -0400 Subject: [PATCH 16/16] chore(dockerfile): Update public pool repository source - Switch repository source from benjamin-wilson to average-gary fork - Modify comment to indicate reason for fork (timestamp validation fix) - Maintain existing Dockerfile structure and installation process --- sv1-public-pool.dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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