Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
167 lines (152 sloc) 6.59 KB
# The purpose of this script is to create an evolutionary
# model to study the equilibrium effects of Bitcoin Unlimited-style
# "emergent consensus". Note that the model is not yet quite
# complete as it does not take into account the benefits of
# mining "sister blocks" that steal transaction fees, though it
# does give a rough idea of what equilibrium behavior
# among the various miner policy dimensions (block accept size,
# override depth, block creation size) looks like
import random
# Block reward
REWARD = 1000
# Call this function to get a tx with the right fee
TX_FEE_DISTRIBUTION = lambda: (10000 // random.randrange(5, 250)) * 0.01
# TX_FEE_DISTRIBUTION = lambda: 20
# Propagation time
PROPTIME_FACTOR = 1
# List of tuples:
# (default limit, n-block limit, acceptance depth, creation limit)
strategies = []
for i in range(4):
for j in range(4):
strategies.append([2 + i * 2, 100, 3, 10 + j * 4])
class Block():
def __init__(self, parent, size, fees, miner):
self.hash = random.randrange(10**20)
self.parent = parent
self.score = 1 if self.parent is None else parent.score + 1
self.miner = miner
self.size = size
self.fees = fees
class Miner():
def __init__(self, strategy, id):
self.limit, self.big_limit, self.accept_depth, self.creation_limit = strategy
self.chain = {}
self.big_chain = {}
self.head = None
self.big_head = None
self.id = id
self.future = {}
self.children = {}
self.created = 0
def process_history(self, time):
deletes = []
for t in self.future:
if t <= time:
for b in self.future[t]:
self.process_block(b)
deletes.append(t)
for t in deletes:
del self.future[t]
def add_block(self, block, time):
self.process_history(time)
if time + int(block.size * PROPTIME_FACTOR) not in self.future:
self.future[time + int(block.size * PROPTIME_FACTOR)] = [block]
else:
self.future[time + int(block.size * PROPTIME_FACTOR)].append(block)
def process_block(self, block):
if block.size <= self.limit and (block.parent is None or block.parent.hash in self.chain):
self.chain[block.hash] = block
if block.score > (self.head.score if self.head else 0):
self.head = block
if block.size <= self.big_limit and (block.parent is None or block.parent.hash in self.big_chain):
self.big_chain[block.hash] = block
if block.score > (self.big_head.score if self.big_head else 0):
self.big_head = block
if block.score > (self.head.score if self.head else 0) + self.accept_depth:
self.head = block
self.chain[block.hash] = block
if block.parent and block.parent.hash not in self.chain and block.parent.hash not in self.big_chain:
if block.parent.hash not in self.children:
self.children[block.parent.hash] = [block]
else:
self.children[block.parent.hash].append(block)
if block.hash in self.children:
for c in self.children[block.hash]:
self.process_block(c)
del self.children[block.hash]
def create_block(self, backlog, time):
self.process_history(time)
fees = sum(backlog[:self.creation_limit])
# print 'Creating block of size %d (fees %d, seq %d)' % (self.creation_limit, fees, self.head.score + 1 if self.head else 1)
self.created += 1
return Block(self.head, self.creation_limit, fees, self.id)
def simulate(strats):
miners = [Miner(strat, i) for i, strat in enumerate(strats)]
backlog = []
for i in range(100000):
if i % 10000 == 0:
print 'Progress %d' % i
backlog.append(TX_FEE_DISTRIBUTION())
if random.random() < 0.01:
backlog = sorted(backlog)[::-1]
miner = random.choice(miners)
b = miner.create_block(backlog, i)
backlog = backlog[b.size:]
for m in miners:
m.add_block(b, i)
rewards = [0] * len(miners)
blocks = [0] * len(miners)
h = miners[0].head
sz = 0
while h is not None:
rewards[h.miner] += REWARD + h.fees
blocks[h.miner] += 1
h = h.parent
sz += 1
return rewards, blocks, [m.created for m in miners]
for r in range(200):
tests = []
for s in strategies:
tests.append(s)
tests.append((s[0] - 2, s[1], s[2], s[3]))
tests.append((s[0] + 2, s[1], s[2], s[3]))
tests.append((s[0], s[1], s[2] - 1, s[3]))
tests.append((s[0], s[1], s[2] + 1, s[3]))
tests.append((s[0], s[1], s[2], s[3] - 2))
tests.append((s[0], s[1], s[2], s[3] + 2))
NUM_TESTS = 7
print 'Starting simulation'
results, blks, created = simulate(tests)
for i, s in enumerate(strategies):
base = results[i * NUM_TESTS]
if results[i * NUM_TESTS + 1] < base < results[i * NUM_TESTS + 2]:
print 'Increasing base accept size beneficial at %r' % s
s[0] += 2
if results[i * NUM_TESTS + 1] > base > results[i * NUM_TESTS + 2] and s[0] > 2:
print 'Decreasing base accept size beneficial at %r' % s
s[0] -= 2
if results[i * NUM_TESTS + 3] < base < results[i * NUM_TESTS + 4]:
print 'Increasing override depth beneficial at %r' % s
s[2] += 1
if results[i * NUM_TESTS + 3] > base > results[i * NUM_TESTS + 4] and s[2] > 1:
print 'Decreasing override depth beneficial at %r' % s
s[2] -= 1
if results[i * NUM_TESTS + 5] < base < results[i * NUM_TESTS + 6]:
print 'Increasing creation size beneficial at %r' % s
s[3] += 2
if results[i * NUM_TESTS + 5] > base > results[i * NUM_TESTS + 6] and s[3] > 2:
print 'Decreasing creation size beneficial at %r' % s
s[3] -= 2
for s in strategies:
print s
print 'Chain quality (per miner):', [(b * 100 / c) if c else 0 for b, c in zip(blks, created)]
print 'Chain quality (total, non-perturbed miners only):', sum(blks[::NUM_TESTS]) * 1.0 / sum(created[::NUM_TESTS])
if r % 20 == 0:
print 'Control round'
results, blks, created = simulate(strategies)
print 'Chain quality (per miner):', [(b * 100 / c) if c else 0 for b, c in zip(blks, created)]
print 'Chain quality (total):', sum(blks) * 1.0 / sum(created)
# results = simulate(strategies)
# for s, r in zip(strategies, results):
# print s[0], s[3], r