Last active
November 5, 2022 15:57
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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