Skip to content

Instantly share code, notes, and snippets.

@endorxmr
Last active November 5, 2022 15:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save endorxmr/07364dc54f277abf487574d455d67341 to your computer and use it in GitHub Desktop.
Save endorxmr/07364dc54f277abf487574d455d67341 to your computer and use it in GitHub Desktop.
Estimate a Monero miner's income and profitability, plus an upper bound for the total energy consumption of the mining network and the number of cpus involved
import texttable as tt
import datetime
import requests
import json
import math
node_jsonrpc = "http://node.supportxmr.com:18081/json_rpc"
time_format = "%Y-%m-%d %H:%M:%S"
# Get network diff
data = {"jsonrpc": "2.0", "id": "0", "method": "get_info"}
pool_stats = requests.post(node_jsonrpc, data=json.dumps(data)).json()
# Network hashrate
target_block_time = 120
difficulty = int(pool_stats["result"]["difficulty"])
nethash = difficulty / target_block_time
print(f"Difficulty: {difficulty}\nNethash: {nethash} H/s")
# Last block reward
data = {"jsonrpc": "2.0", "id": "0", "method": "get_last_block_header"}
last_block_stats = requests.post(node_jsonrpc, data=json.dumps(data)).json()
block_reward = int(last_block_stats["result"]["block_header"]["reward"]) / 1e12
print(f"Last reward: {block_reward:.12} XMR")
# Time to find a block
miner_hashrate = 6500
average_block_time = difficulty / miner_hashrate # Seconds
print(f"Average blocktime @{miner_hashrate} H/s: {datetime.timedelta(seconds=round(average_block_time, 0))}")
# Price (from Kraken)
# https://docs.kraken.com/rest/#operation/getTickerInformation
pairs = {"eur": "XXMRZEUR", "usd": "XXMRZUSD"}
pair = "usd"
data = {"pair": pairs[pair]}
xmr_ticker = requests.post("https://api.kraken.com/0/public/Ticker", data=data).json()
xmr_price = float(xmr_ticker["result"][pairs[pair]]["c"][0])
print(f"Price: {xmr_price:.2f} - {pairs[pair]}")
# Hashrate unit cost, expected unit profit, profitability
# Assumption: fixed price, electricity cost, difficulty, and block reward
# In order to be profitable, the expected profit should be greater than the mining cost (price/hashrate)
pool_fee = 0 / 100 # Percentage
electricity_cost = 0.10 # fiat/kWh - "fiat" assumed to be of the same unit as "pair"
power_consumption = 65 # Watts
rig_cost = 750 # fiat
mining_cost_s = power_consumption * electricity_cost / 1000 / 3600 if electricity_cost > 0 else 0.0 # W * fiat/(kW*h) / 1000 W/kW / 3600 s/h = fiat/s
expected_crypto_income_s = block_reward * miner_hashrate / difficulty * (1 - pool_fee)
expected_fiat_income_s = xmr_price * expected_crypto_income_s
roi_time = rig_cost / (expected_fiat_income_s - mining_cost_s) / 3600 / 24
profitability = (expected_fiat_income_s - mining_cost_s) * 100 / mining_cost_s if mining_cost_s > 0 else math.inf
miner_efficiency = miner_hashrate / power_consumption if power_consumption else math.inf
breakeven_efficiency = (difficulty * electricity_cost) / (xmr_price * block_reward * 1000 * 3600 * (1 - pool_fee))
network_power_consumption = nethash / breakeven_efficiency if breakeven_efficiency > 0 else 0.0 # Ignore the pool fee when calculating the network's power consumption
# network_power_consumption = xmr_price * block_reward / target_block_time / (electricity_cost / 1000 / 3600)
print(f"Mining cost/s: {mining_cost_s:.4}")
print(f"Expected crypto income/s: {expected_crypto_income_s:.4}")
print(f"Expected fiat income/s: {expected_fiat_income_s:.4}")
print(f"Profitability: {profitability:3.1f}%")
print(f"Miner efficiency: {miner_efficiency:.1f} H/s/W")
print(f"ROI time: {roi_time:.1f} days to recover {rig_cost} {pair.upper()}")
print(f"Breakeven efficiency @{electricity_cost}/kWh: {breakeven_efficiency:.1f} H/s/W")
print(f"Network power consumption @{electricity_cost}/kWh: {network_power_consumption:.0f} W")
# Daily/weekly/monthly/yearly rewards and profits
times = {
"Day": 3600 * 24,
"Week": 3600 * 24 * 7,
"Month": 3600 * 24 * 30,
"Year": 3600 * 24 * 365
}
rewards = {}
for timeframe in times.keys():
rewards[timeframe] = {
"ci": float(f"{expected_crypto_income_s * times[timeframe]:.4g}"), # Crypto income for the given timeframe
"fi": float(f"{expected_fiat_income_s * times[timeframe]:.4g}"), # Fiat income for the given timeframe
"fp": float(f"{(expected_fiat_income_s - mining_cost_s) * times[timeframe]:.4g}"), # Fiat profit for the given timeframe
"pc": float(f"{mining_cost_s * times[timeframe]:.4g}") # Fiat power cost for the given timeframe
}
# Draw table
tab = tt.Texttable()
tab.header(["Period", "Profit", "Mined", "Power"])
# tab.set_cols_width([max([len(x) for x in names]), max([len(str(int(x))) for x in vol24]), 7, 8])
tab.set_cols_dtype(['t', 'f', 'f', 'f'])
tab.set_cols_align(['l', 'r', 'r', 'r'])
tab.set_deco(tt.Texttable.BORDER | tt.Texttable.HEADER | tt.Texttable.VLINES)
for tf in rewards.keys():
tab.add_row([tf, rewards[tf]["fp"], rewards[tf]["ci"], rewards[tf]["pc"]])
print(tab.draw())
# Cost of a 51% attack, by providing additional hardware
# Each cpu is represented by speed, power (estimated at the wall), and efficiency (computed)
# Note: The number cpus marked as "(dual)" must be multiplied by two!
# Todo: cpu + ram + mobo + psu + cooling cost for each setup?
cpus = {
"AMD EPYC 7763 (dual)": {"speed": 100000, "power": 750},
"AMD EPYC 7742 (dual)": {"speed": 90000, "power": 742}, # entr0py sxmr
"AMD EPYC 7702 (dual)": {"speed": 84000, "power": 720},
"AMD Ryzen Threadripper 3990X": {"speed": 65000, "power": 600},
"AMD Ryzen Threadripper 3970X": {"speed": 40000, "power": 375},
"AMD Ryzen 5950X": { "speed": 22000, "power": 225 },
"AMD Ryzen 5900X": { "speed": 16000, "power": 160 }, # More in line with the 3900X
"AMD Ryzen 5800X": { "speed": 11500, "power": 125 },
"AMD Ryzen 5600X": { "speed": 8000, "power": 75 },
"AMD Ryzen 3950X": { "speed": 22000, "power": 225 },
"AMD Ryzen 3900X": { "speed": 14600, "power": 140 }, # nioc matrix monero-pow
"AMD Ryzen 3800X": { "speed": 11000, "power": 125 },
"AMD Ryzen 3700X": { "speed": 11000, "power": 125 },
"AMD Ryzen 3600X": { "speed": 7500, "power": 70 },
"Intel Xeon Silver 4216 (dual)": {"speed": 16157, "power": 312}, # leonarth irc monero-pools
"Intel Core i7-10900K": {"speed": 10000, "power": 180},
"Intel Core i7-9900K": {"speed": 8100, "power": 150},
"Intel Core i7-8700K": {"speed": 5894, "power": 120},
"Intel Core i7-6700K": {"speed": 3365, "power": 85},
"Intel Core i7-4720HQ": {"speed": 1768, "power": 45},
}
for cpu in cpus.keys():
cpus[cpu]["eff"] = cpus[cpu]["speed"] / cpus[cpu]["power"]
cpus[cpu]["amount"] = int(math.ceil(network_power_consumption / cpus[cpu]["power"]))
cputab = tt.Texttable()
cputab.header(["CPU", "Speed", "Power", "Efficiency", "Amount"])
cputab.set_cols_dtype(['t', 'i', 'i', 'f', 'i'])
cputab.set_cols_align(['l', 'r', 'r', 'r', 'r'])
cputab.set_deco(tt.Texttable.BORDER | tt.Texttable.HEADER | tt.Texttable.VLINES)
for cpu in cpus.keys():
cputab.add_row([cpu, cpus[cpu]["speed"], cpus[cpu]["power"], cpus[cpu]["eff"], cpus[cpu]["amount"]])
print(cputab.draw())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment