aucoin/tools/simulator.py

167 lines
5.9 KiB
Python
Raw Normal View History

2018-07-15 23:30:55 +02:00
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)