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)