Skip to main content

Network Monitoring

Guide to monitoring BTX network health, difficulty tracking, and fork forensics using getdifficultyhealth and the monitor CLI.

This guide covers how to monitor BTX network health, track difficulty adjustments, detect anomalies, and investigate forks. BTX uses ASERT (aserti3-2d) per-block difficulty adjustment targeting 90-second block intervals, so continuous monitoring is essential to confirm the chain is converging correctly.


The getdifficultyhealth RPC

The primary monitoring RPC is getdifficultyhealth, which returns a comprehensive health snapshot of the network:

btx-cli getdifficultyhealth

Key fields in the response:

FieldDescription
target_spacing_sTarget block time (90 seconds)
actual_spacing_mean_sObserved mean block interval over the window
actual_spacing_median_sObserved median block interval
difficultyCurrent network difficulty
asert_half_life_sASERT half-life (14400s before height 55000, 3600s after)
tip_heightCurrent chain tip height
tip_age_sAge of the canonical tip in seconds
reorg_protectionDeep-reorg protection configuration and rejection counters
reward_concentrationMining reward distribution across addresses

Key Metrics to Watch

Block Intervals

The most important signal is whether actual block intervals are converging toward the 90-second target:

  • Healthy: mean and median within 60-120s range
  • Warning: sustained deviation beyond 2x target (180s+ mean)
  • Critical: tip_age_s > 3 * target_spacing_s (270s) while miners are active — treat as network stall

Reward Distribution

Monitor reward_concentration to detect if mining rewards are concentrating in too few addresses. High concentration may indicate a single entity controlling disproportionate hash power.

Reorg Protection

The reorg_protection section reports:

  • Whether deep-reorg protection is enabled
  • The maximum allowed reorg depth
  • Count of bad-reorg-depth rejections

Any non-zero rejection count warrants immediate investigation.

Peer Agreement

If a node reports We do not appear to fully agree with our peers, treat it as a first-class operator signal even if sync lag has recovered. It usually indicates peer topology or upgrade drift.


Monitor CLI Tool

The btx_difficulty_health.py tool generates comprehensive health reports across multiple nodes and time windows.

Standard 24-Hour Report

python3 btx_difficulty_health.py \
  --time-window 1h=3600 \
  --time-window 6h=21600 \
  --time-window 24h=86400

Extended Report with 7-Day Window

python3 btx_difficulty_health.py \
  --node-config /path/to/node-config.json \
  --time-window 1h=3600 \
  --time-window 6h=21600 \
  --time-window 24h=86400 \
  --time-window 7d=604800

All-Managed-Miners View

python3 btx_difficulty_health.py \
  --node-config mainnet-managed-miners-node-config.json \
  --time-window 1h=3600 \
  --time-window 6h=21600 \
  --time-window 24h=86400

Notes:

  • On BTX mainnet the tool auto-sets analysis_min_height=50000 to exclude the fast-mine bootstrap phase
  • JSON output is the source of truth; Markdown is for operator review
  • The default fleet treats only local as an expected miner; remote archival nodes need explicit --node-config with expected_to_mine=true

Setting Up Alerts

Tip Staleness

Poll getdifficultyhealth on a cron interval and alert when tip_age_s exceeds a threshold:

# Alert if tip is older than 5 minutes (300s)
TIP_AGE=$(btx-cli getdifficultyhealth | jq '.tip_age_s')
if [ "$TIP_AGE" -gt 300 ]; then
  echo "ALERT: Chain tip is ${TIP_AGE}s old" | notify
fi

Block Interval Drift

Alert when the mean block interval deviates significantly from the 90-second target:

MEAN=$(btx-cli getdifficultyhealth | jq '.actual_spacing_mean_s')
if (( $(echo "$MEAN > 180" | bc -l) )); then
  echo "ALERT: Mean block interval is ${MEAN}s (target 90s)" | notify
fi

Peer Disagreement

Monitor for peer disagreement warnings across your fleet. Any node reporting disagreement should be investigated even if it appears to have recovered.

Deep Reorg Rejections

Alert on any bad-reorg-depth rejection — these indicate an attempted deep reorganization was blocked by the node's protection policy.


Fork Forensics

Use the fork forensics tool to investigate stale branches and chain splits:

python3 btx_fork_forensics.py \
  --min-branchlen 2 \
  --min-tip-height 50000

This reports:

  • All stale tips with branch length 2 or greater
  • Fork points and divergence heights
  • Deep Reorg Guard section when bad-reorg-depth rejections are detected

ASERT History Replay

Replay the ASERT difficulty adjustment history to verify correct convergence:

python3 asert_history_replay.py \
  --time-window 24h=86400 \
  --time-window all=-1

Diagnosing a Stalled Chain

If tip_age_s > 3 * target_spacing_s while active miners are running:

  1. Inspect getchaintips on all nodes — look for height=tip+1 tips with status=invalid
  2. Check debug.log for bad-shielded-anchor entries — these indicate miners are templating transactions with stale shielded merkle anchors
  3. Deploy the patched binary, which scrubs stale-anchor shielded entries before CreateNewBlock()
  4. A daemon restart clears stale entries because mempool.dat reload revalidates against the current active chain

Invalid-Tip Recovery

After deploying a validator bugfix, recover stranded nodes that cached a now-valid block as invalid:

python3 invalid_tip_recovery.py \
  --node-config mainnet-managed-miners-node-config.json \
  --leader local

The tool reconsiders the leader's first missing block on each lagging follower and waits for automatic catch-up.


Mining Runtime Signals

The health report includes per-node mining runtime status:

SignalMeaning
process_active=trueMiner process or loop exists
work_signal_active=trueNode has entered the MatMul solve path and accumulated runtime counters
in_flight_unsolved_search=trueMiner is actively scanning inside a long-running generatetoaddress call — normal for CPU miners

Do not confuse in_flight_unsolved_search with inactivity — it simply means the miner has not yet completed a solve cycle since process start.