Edit: I spent a few hours writing some code that simulates block propagation time and uncles. You can find it at my GitHub repo here: https://github.com/lungj/ethtools. The program in question is
mine_sim.py. I figured it would be a bit weird to post such a long program into StackExchange.
The rabbit hole can get pretty deep, depending on how deeply you want to model things. In the actual world of mining, miners compute hashes of particular values. When the hashes satisfy some conditions, the data that is hashed can be propagated onto the blockchain. When the blockchain is in equilibrium for difficulty and hash rate and block time is b, for every timestep ts in your simulation, there is a (hashrate / total hashrate) * b / ts) chance that a block is mined by a particular miner.
Here is a simulation that ignores block propagation times. You can, of course, add that. How you do that depends on the properties of the network you want to simulate. You can create a crude uncle calculation by changing TIMESTEP to your block propagation time and, whenever more than one miner finds a block at the same simulation timestep, you can count that as an uncle. If you want a more involved model, for each node, you can store the chain state from the perspective of that node (with the presumption that some nodes are also miners). You can model propagation delays between individual nodes to simulate block propagations more realistically. You would then want to choose a realistic fan-in/fan-out number for each node (or simulate connections being formed and broken) and perhaps model geographic distribution of various nodes. And, of course, you could go much more deeply than that, even.
def __init__(self, name, hashrate):
'''Initialize a new miner named name with hashrate measured in hashes per second.'''
self.name = name
self.hashrate = hashrate
MINERS = [
TIMESTEP = 0.01 # Timestep of simulation
BLOCKTIME = 15 # Average number of seconds per block
TOTAL_HASHPOWER = sum([miner.hashrate for miner in MINERS])
time = 0
for miner in MINERS:
if (random.random() * (TOTAL_HASHPOWER / miner.hashrate)) < (TIMESTEP / BLOCKTIME):
print("t=%0.3f: %s mined a block" % (time, miner.name))
time += TIMESTEP