164 lines
5.3 KiB
Python
Executable File
164 lines
5.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
DragonX RandomX Block Time Calculator
|
|
|
|
Estimates how long it will take to find a block given your hashrate
|
|
and the current network difficulty.
|
|
|
|
Usage:
|
|
python3 block_time_calculator.py <hashrate_h/s> [--difficulty <diff>]
|
|
|
|
Examples:
|
|
python3 block_time_calculator.py 1000 # 1000 H/s, auto-fetch difficulty
|
|
python3 block_time_calculator.py 5K # 5 KH/s
|
|
python3 block_time_calculator.py 1.2M # 1.2 MH/s
|
|
python3 block_time_calculator.py 500 --difficulty 1234.56
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
|
|
# DragonX chain constants
|
|
BLOCK_TIME = 36 # seconds
|
|
# powLimit = 0x0f0f0f0f... (32 bytes of 0x0f) = (2^256 - 1) / 17
|
|
# The multiplier 2^256 / powLimit ≈ 17
|
|
POW_LIMIT_HEX = "0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"
|
|
POW_LIMIT = int(POW_LIMIT_HEX, 16)
|
|
TWO_256 = 2 ** 256
|
|
|
|
|
|
def parse_hashrate(value):
|
|
"""Parse hashrate string with optional K/M/G/T suffix."""
|
|
suffixes = {"K": 1e3, "M": 1e6, "G": 1e9, "T": 1e12}
|
|
value = value.strip().upper()
|
|
if value and value[-1] in suffixes:
|
|
return float(value[:-1]) * suffixes[value[-1]]
|
|
return float(value)
|
|
|
|
|
|
def get_difficulty_from_node():
|
|
"""Try to fetch current difficulty from a running DragonX node."""
|
|
try:
|
|
result = subprocess.run(
|
|
["dragonx-cli", "getmininginfo"],
|
|
capture_output=True, text=True, timeout=10
|
|
)
|
|
if result.returncode == 0:
|
|
info = json.loads(result.stdout)
|
|
return float(info["difficulty"])
|
|
except FileNotFoundError:
|
|
pass
|
|
except (subprocess.TimeoutExpired, json.JSONDecodeError, KeyError):
|
|
pass
|
|
|
|
# Try with src/ path relative to script location
|
|
try:
|
|
import os
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
cli_path = os.path.join(script_dir, "src", "dragonx-cli")
|
|
result = subprocess.run(
|
|
[cli_path, "getmininginfo"],
|
|
capture_output=True, text=True, timeout=10
|
|
)
|
|
if result.returncode == 0:
|
|
info = json.loads(result.stdout)
|
|
return float(info["difficulty"])
|
|
except (FileNotFoundError, subprocess.TimeoutExpired, json.JSONDecodeError, KeyError):
|
|
pass
|
|
|
|
return None
|
|
|
|
|
|
def format_duration(seconds):
|
|
"""Format seconds into a human-readable duration string."""
|
|
days = seconds / 86400
|
|
if days >= 365:
|
|
years = days / 365.25
|
|
return f"{years:.2f} years ({days:.1f} days)"
|
|
if days >= 1:
|
|
hours = (seconds % 86400) / 3600
|
|
return f"{days:.2f} days ({days * 24:.1f} hours)"
|
|
hours = seconds / 3600
|
|
if hours >= 1:
|
|
return f"{hours:.2f} hours"
|
|
minutes = seconds / 60
|
|
return f"{minutes:.1f} minutes"
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="DragonX RandomX Block Time Calculator"
|
|
)
|
|
parser.add_argument(
|
|
"hashrate",
|
|
help="Your hashrate in H/s (supports K/M/G/T suffixes, e.g. 5K, 1.2M)"
|
|
)
|
|
parser.add_argument(
|
|
"--difficulty", "-d", type=float, default=None,
|
|
help="Network difficulty (auto-fetched from local node if omitted)"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
hashrate = parse_hashrate(args.hashrate)
|
|
except ValueError:
|
|
print(f"Error: Invalid hashrate '{args.hashrate}'", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
if hashrate <= 0:
|
|
print("Error: Hashrate must be positive", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
difficulty = args.difficulty
|
|
if difficulty is None:
|
|
print("Querying local DragonX node for current difficulty...")
|
|
difficulty = get_difficulty_from_node()
|
|
if difficulty is None:
|
|
print(
|
|
"Error: Could not connect to DragonX node.\n"
|
|
"Make sure dragonxd is running, or pass --difficulty manually.",
|
|
file=sys.stderr
|
|
)
|
|
sys.exit(1)
|
|
|
|
if difficulty <= 0:
|
|
print("Error: Difficulty must be positive", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Expected hashes to find a block = 2^256 / current_target
|
|
# Since difficulty = powLimit / current_target:
|
|
# current_target = powLimit / difficulty
|
|
# expected_hashes = 2^256 / (powLimit / difficulty) = difficulty * 2^256 / powLimit
|
|
expected_hashes = difficulty * TWO_256 / POW_LIMIT
|
|
time_seconds = expected_hashes / hashrate
|
|
time_days = time_seconds / 86400
|
|
|
|
# Estimate network hashrate from difficulty and block time
|
|
network_hashrate = expected_hashes / BLOCK_TIME
|
|
|
|
print()
|
|
print("=" * 50)
|
|
print(" DragonX Block Time Estimator (RandomX)")
|
|
print("=" * 50)
|
|
print(f" Network difficulty : {difficulty:,.4f}")
|
|
print(f" Your hashrate : {hashrate:,.0f} H/s")
|
|
print(f" Est. network hash : {network_hashrate:,.0f} H/s")
|
|
print(f" Block time target : {BLOCK_TIME}s")
|
|
print(f" Block reward : 3 DRGX")
|
|
print("-" * 50)
|
|
print(f" Expected time to find a block:")
|
|
print(f" {format_duration(time_seconds)}")
|
|
print(f" ({time_days:.4f} days)")
|
|
print("-" * 50)
|
|
print(f" Est. blocks/day : {86400 / time_seconds:.6f}")
|
|
print(f" Est. DRGX/day : {86400 / time_seconds * 3:.6f}")
|
|
print("=" * 50)
|
|
print()
|
|
print("Note: This is a statistical estimate. Actual time varies due to randomness.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|