167 lines
5.9 KiB
Python
167 lines
5.9 KiB
Python
|
import logging.config
|
||
|
import shutil
|
||
|
import math
|
||
|
from collections import deque
|
||
|
from collections import namedtuple
|
||
|
from random import random
|
||
|
from statistics import mean
|
||
|
|
||
|
import matplotlib.pyplot as plt
|
||
|
import matplotlib.mlab as mlab
|
||
|
import numpy
|
||
|
|
||
|
from aucoin import config
|
||
|
from aucoin import consensus
|
||
|
from aucoin import util
|
||
|
from aucoin.block import Block
|
||
|
from aucoin.blockchain import Blockchain
|
||
|
from aucoin.database import session_scope
|
||
|
from aucoin.transactions import CoinbaseTransaction
|
||
|
|
||
|
logging.config.dictConfig(config.logging(console_level="DEBUG"))
|
||
|
logger = logging.getLogger(__name__)
|
||
|
|
||
|
|
||
|
def simulate_mining(iterations=10_000, hashrate=100_000):
|
||
|
shutil.rmtree(config.data_dir, ignore_errors=True)
|
||
|
util.make_data_dirs("logs/")
|
||
|
|
||
|
time = 0
|
||
|
blockchain = Blockchain()
|
||
|
|
||
|
timestamps = []
|
||
|
data = []
|
||
|
dq = deque(maxlen=100)
|
||
|
Stat = namedtuple("Stat", ("difficulty", "timespan", "average_timespan_total", "average_timespan_dq", "hashrate"))
|
||
|
|
||
|
with session_scope() as session:
|
||
|
blockchain.genesis_block(session).timestamp = 0
|
||
|
time += 1
|
||
|
hash_rates = generate_hashrates(n=iterations, hashrate=hashrate)
|
||
|
for i in range(iterations):
|
||
|
print("Iteration:", i)
|
||
|
hashrate = hash_rates[i]
|
||
|
prev_block = blockchain.header(session)
|
||
|
block = Block(
|
||
|
hash_prev_block=prev_block._hash,
|
||
|
timestamp=time,
|
||
|
transactions=[CoinbaseTransaction(
|
||
|
address=b"",
|
||
|
value=0,
|
||
|
block_height=prev_block.height + 1
|
||
|
)]
|
||
|
)
|
||
|
block.target = consensus.required_target(block, blockchain, session, 2, 0, 0)
|
||
|
|
||
|
# block.difficulty is the expected/average number of attempts that were necessary to mine it.
|
||
|
# https://bitcoin.stackexchange.com/questions/25293/probablity-distribution-of-mining
|
||
|
# https://bitcoin.stackexchange.com/questions/4690/what-is-the-standard-deviation-of-block-generation-times
|
||
|
# http://r6.ca/blog/20180225T160548Z.html
|
||
|
time += numpy.random.poisson(block.difficulty // hashrate)
|
||
|
|
||
|
blockchain.add(block, session)
|
||
|
blockchain.set_header(block, session)
|
||
|
|
||
|
# save data
|
||
|
timestamps.append(time)
|
||
|
timespan = block.timestamp - prev_block.timestamp
|
||
|
dq.append(timespan)
|
||
|
data.append(Stat(
|
||
|
block.difficulty,
|
||
|
timespan,
|
||
|
blockchain.average_block_timespan(session),
|
||
|
mean(dq),
|
||
|
hashrate
|
||
|
))
|
||
|
"""
|
||
|
# plot
|
||
|
fig, ax1 = plt.subplots()
|
||
|
ax1.plot(timestamps, [d.average_timespan_total for d in data], 'c--')
|
||
|
ax1.set_xlabel('time (s)')
|
||
|
# Make the y-axis label, ticks and tick labels match the line color.
|
||
|
ax1.set_ylabel('average block timespan since genesis', color='c')
|
||
|
ax1.tick_params('y', colors='c')
|
||
|
|
||
|
ax2 = ax1.twinx()
|
||
|
ax2.plot(timestamps, [d.difficulty for d in data], 'r-')
|
||
|
ax2.set_ylabel('difficulty', color='r', labelpad=20)
|
||
|
ax2.tick_params('y', colors='r')
|
||
|
|
||
|
#ax3 = ax1.twinx()
|
||
|
#ax3.plot(timestamps, [d.timespan for d in data], 'g', marker=",", linestyle="")
|
||
|
#ax3.set_ylabel('timespan last block', color='g')
|
||
|
#ax3.tick_params('y', colors='g')
|
||
|
|
||
|
ax4 = ax2.twinx()
|
||
|
ax4.plot(timestamps, [d.hashrate for d in data], 'y-')
|
||
|
ax4.set_ylabel('hashrate', color='y')
|
||
|
ax4.tick_params('y', colors='y')
|
||
|
|
||
|
ax5 = ax1.twiny()
|
||
|
ax2.yaxis.set_visible(True)
|
||
|
ax5.plot(timestamps, [d.average_timespan_dq for d in data], 'b-')
|
||
|
ax5.set_ylabel('average block timespan last 100', color='b')
|
||
|
ax5.tick_params('y', colors='b')
|
||
|
|
||
|
ax1.plot(timestamps, [60 for t in timestamps], '-k')
|
||
|
|
||
|
fig.tight_layout()
|
||
|
"""
|
||
|
|
||
|
plt.subplot(211)
|
||
|
|
||
|
genesis, = plt.plot(timestamps, [d.average_timespan_total for d in data], 'c--', label='average block timespan since genesis')
|
||
|
|
||
|
avg, = plt.plot(timestamps, [d.average_timespan_dq for d in data], 'b-', label='average block timespan last 100')
|
||
|
target, = plt.plot(timestamps, [60 for t in timestamps], '-k', label='target block time')
|
||
|
plt.gca().set_ylabel('time (s)')
|
||
|
plt.gca().set_xlabel('time (s)')
|
||
|
|
||
|
ax1 = plt.gca()
|
||
|
|
||
|
ax2 = ax1.twinx()
|
||
|
|
||
|
ax2.plot(timestamps, [d.difficulty for d in data], 'r-')
|
||
|
ax2.set_ylabel('difficulty', color='r')
|
||
|
ax2.tick_params('y', colors='r')
|
||
|
|
||
|
ax4 = ax1.twinx()
|
||
|
ax4.plot(timestamps, [d.hashrate for d in data], 'y-')
|
||
|
ax4.set_ylabel('hashrate', color='y')
|
||
|
ax4.tick_params('y', colors='y', pad=50)
|
||
|
|
||
|
plt.legend(handles=[genesis, avg, target],
|
||
|
bbox_to_anchor=(0., 1.02, 1., .102), loc=3,
|
||
|
borderaxespad=0.)
|
||
|
|
||
|
plt.subplot(212)
|
||
|
data = [d.timespan for d in data]
|
||
|
n, bins, _ = plt.hist(data, bins=len(numpy.unique(data)), density=True, stacked=True)
|
||
|
|
||
|
mu = 60
|
||
|
y = [(math.exp(-mu) * mu**i / math.factorial(i)) for i in range(len(bins))]
|
||
|
plt.plot(bins, y, 'y--')
|
||
|
plt.gca().set_xlabel('block time (s)')
|
||
|
plt.gca().set_ylabel('percentage of mined blocks')
|
||
|
|
||
|
plt.gcf().tight_layout()
|
||
|
plt.show()
|
||
|
|
||
|
def generate_hashrates(hashrate=100_000, n=10_000, ret=5.42123/365/60/24, vol=0.14789/60):
|
||
|
# Monte Carlo simulation based on http://www.pythonforfinance.net/2016/11/28/monte-carlo-simulation-in-python/
|
||
|
# The return and volatility of bitcoin hash rate between may 2017 and may 2018
|
||
|
hash_rates = [hashrate]
|
||
|
# one year days * 60 * 24
|
||
|
changes = numpy.random.normal(ret, vol, n) + 1
|
||
|
|
||
|
for x in changes:
|
||
|
hash_rates.append(x * hash_rates[-1])
|
||
|
|
||
|
#plt.plot(hash_rates)
|
||
|
#plt.show()
|
||
|
return hash_rates
|
||
|
|
||
|
numpy.random.seed(6)
|
||
|
#generate_hashrates()
|
||
|
simulate_mining(3000)
|