mirror of
https://github.com/monero-project/research-lab.git
synced 2024-12-22 11:39:32 +00:00
Separating into files for easier bugfindin
This commit is contained in:
parent
4593f2b8ea
commit
7c8aa16609
7 changed files with 770 additions and 536 deletions
46
source-code/Poisson-Graphs/Block.py
Normal file
46
source-code/Poisson-Graphs/Block.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
import unittest, random, time
|
||||
|
||||
def newIdent(params):
|
||||
nonce = params
|
||||
# Generate new random identity.
|
||||
return hash(str(nonce) + str(random.random()))
|
||||
#### #### #### #### #### #### #### #### #### #### #### #### #### #### #### ####
|
||||
class Block(object):
|
||||
'''
|
||||
Each block has: an identity, a timestamp of discovery (possibly false),
|
||||
has a timestamp of arrival at the local node (possibly unnecessary), a
|
||||
parent block's identity, and a difficulty score.
|
||||
'''
|
||||
def __init__(self, params={}):
|
||||
self.ident = None
|
||||
self.discoTimestamp = None
|
||||
self.arrivTimestamp = None
|
||||
self.parent = None
|
||||
self.diff = None
|
||||
try:
|
||||
assert len(params)==5
|
||||
except AssertionError:
|
||||
print("Error in Block(): Tried to add a malformed block. We received params = " + str(params) + ", but should have had something of the form {\"ident\":ident, \"disco\":disco, \"arriv\":arriv, \"parent\":parent, \"diff\":diff}.")
|
||||
self.ident = params["ident"]
|
||||
self.discoTimestamp = params["disco"]
|
||||
self.arrivTimestamp = params["arriv"]
|
||||
self.parent = params["parent"]
|
||||
self.diff = params["diff"]
|
||||
|
||||
class Test_Block(unittest.TestCase):
|
||||
def test_b(self):
|
||||
#bill = Block()
|
||||
name = newIdent(0)
|
||||
t = time.time()
|
||||
s = t+1
|
||||
diff = 1.0
|
||||
params = {"ident":name, "disco":t, "arriv":s, "parent":None, "diff":diff}
|
||||
bill = Block(params)
|
||||
self.assertEqual(bill.ident,name)
|
||||
self.assertEqual(bill.discoTimestamp,t)
|
||||
self.assertEqual(bill.arrivTimestamp,t+1)
|
||||
self.assertTrue(bill.parent is None)
|
||||
self.assertEqual(bill.diff,diff)
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Block)
|
||||
unittest.TextTestRunner(verbosity=1).run(suite)
|
158
source-code/Poisson-Graphs/Blockchain.py
Normal file
158
source-code/Poisson-Graphs/Blockchain.py
Normal file
|
@ -0,0 +1,158 @@
|
|||
from Block import *
|
||||
|
||||
class Blockchain(object):
|
||||
'''
|
||||
Not a true blockchain, of course, but tracks block objects (timestamps) as above.
|
||||
Each node should be responsible for finding the chain with most cumulative work.
|
||||
Right now we assume Nakamoto consensus (konsensnakamoto).
|
||||
'''
|
||||
def __init__(self, params=[], verbosity=True):
|
||||
self.blocks = {}
|
||||
self.leaves = {}
|
||||
self.miningIdents = None
|
||||
self.verbose = verbosity
|
||||
|
||||
def addBlock(self, blockToAdd):
|
||||
# In our model we assume difficulty scores of blocks are correct (otherwise they would
|
||||
# be rejected in the real life network, and we aren't trying to model spam attacks).
|
||||
try:
|
||||
assert blockToAdd.ident not in self.blocks
|
||||
except AssertionError:
|
||||
print("Error, tried to add block that already exists in blockchain.")
|
||||
else:
|
||||
self.blocks.update({blockToAdd.ident:blockToAdd})
|
||||
self.leaves.update({blockToAdd.ident:blockToAdd})
|
||||
if blockToAdd.parent in self.leaves:
|
||||
del self.leaves[blockToAdd.parent]
|
||||
self.whichLeaf()
|
||||
|
||||
def whichLeaf(self):
|
||||
# Determine which leaf shall be the parent leaf.
|
||||
# If the chain has forked *ever* this will not be the case.
|
||||
maxCumDiff = 0.0
|
||||
self.miningIdents = []
|
||||
for ident in self.leaves:
|
||||
tempCumDiff = 0.0
|
||||
thisBlockIdent = ident
|
||||
tempCumDiff += self.blocks[thisBlockIdent].diff
|
||||
while self.blocks[thisBlockIdent].parent is not None:
|
||||
thisBlockIdent = self.blocks[thisBlockIdent].parent
|
||||
tempCumDiff += self.blocks[thisBlockIdent].diff
|
||||
if tempCumDiff > maxCumDiff:
|
||||
# If more than one leaf ties for maxCumDiff, each node in the
|
||||
# network should pick one of these two arbitrarily. Since we
|
||||
# are storing each blockchain in a hash table (unordered!), for
|
||||
# each node in the network that observes a tie, each possible leaf
|
||||
# is equally likely to have been the first one found! So
|
||||
# we don't need to do anything for the node to select which chain
|
||||
# to work off of.
|
||||
self.miningIdents = [ident]
|
||||
maxCumDiff = tempCumDiff
|
||||
elif tempCumDiff == maxCumDiff:
|
||||
self.miningIdents.append(ident)
|
||||
#print("leaf ident = ", str(ident), ", and tempCumDiff = ", str(tempCumDiff), " and maxCumDiff = ", str(maxCumDiff))
|
||||
|
||||
|
||||
class Test_Blockchain(unittest.TestCase):
|
||||
def test_bc(self):
|
||||
bill = Blockchain([], verbosity=True)
|
||||
|
||||
name = newIdent(0)
|
||||
t = time.time()
|
||||
s = t+1
|
||||
diff = 1.0
|
||||
params = {"ident":name, "disco":t, "arriv":s, "parent":None, "diff":diff}
|
||||
genesis = Block(params)
|
||||
|
||||
self.assertEqual(genesis.ident,name)
|
||||
self.assertEqual(genesis.discoTimestamp,t)
|
||||
self.assertEqual(genesis.arrivTimestamp,t+1)
|
||||
self.assertTrue(genesis.parent is None)
|
||||
self.assertEqual(genesis.diff,diff)
|
||||
|
||||
bill.addBlock(genesis)
|
||||
|
||||
self.assertTrue(genesis.ident in bill.blocks)
|
||||
self.assertTrue(genesis.ident in bill.leaves)
|
||||
self.assertEqual(len(bill.miningIdents),1)
|
||||
self.assertEqual(genesis.ident, bill.miningIdents[0])
|
||||
|
||||
name = newIdent(1)
|
||||
t = time.time()
|
||||
s = t+1
|
||||
diff = 2.0
|
||||
params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff}
|
||||
blockA = Block(params)
|
||||
bill.addBlock(blockA)
|
||||
|
||||
bill.whichLeaf()
|
||||
|
||||
self.assertTrue(blockA.ident in bill.blocks)
|
||||
self.assertTrue(blockA.ident in bill.leaves)
|
||||
self.assertFalse(genesis.ident in bill.leaves)
|
||||
self.assertTrue(genesis.ident in bill.blocks)
|
||||
self.assertEqual(len(bill.miningIdents),1)
|
||||
self.assertEqual(blockA.ident, bill.miningIdents[0])
|
||||
|
||||
name = newIdent(1)
|
||||
t = time.time()
|
||||
s = t+1
|
||||
diff = 2.0
|
||||
params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff}
|
||||
blockB = Block(params)
|
||||
bill.addBlock(blockB)
|
||||
|
||||
self.assertTrue(blockB.ident in bill.blocks)
|
||||
self.assertTrue(blockB.ident in bill.leaves)
|
||||
self.assertEqual(bill.blocks[blockB.ident].parent, genesis.ident)
|
||||
|
||||
self.assertTrue(blockA.ident in bill.blocks)
|
||||
self.assertTrue(blockA.ident in bill.leaves)
|
||||
self.assertEqual(bill.blocks[blockA.ident].parent, genesis.ident)
|
||||
|
||||
self.assertTrue(genesis.ident in bill.blocks)
|
||||
self.assertFalse(genesis.ident in bill.leaves)
|
||||
self.assertTrue(bill.blocks[genesis.ident].parent is None)
|
||||
|
||||
bill.whichLeaf()
|
||||
print(bill.miningIdents)
|
||||
|
||||
self.assertEqual(type(bill.miningIdents), type([]))
|
||||
self.assertTrue(len(bill.miningIdents), 2)
|
||||
|
||||
name = newIdent(2)
|
||||
t = time.time()
|
||||
diff = 3.14159
|
||||
params = {"ident":name, "disco":t, "arriv":s, "parent":blockB.ident, "diff":diff}
|
||||
blockC = Block(params)
|
||||
bill.addBlock(blockC)
|
||||
|
||||
self.assertTrue(blockC.ident in bill.blocks)
|
||||
self.assertTrue(blockC.ident in bill.leaves)
|
||||
|
||||
self.assertTrue(blockB.ident in bill.blocks)
|
||||
self.assertFalse(blockB.ident in bill.leaves)
|
||||
|
||||
self.assertTrue(blockA.ident in bill.blocks)
|
||||
self.assertTrue(blockA.ident in bill.leaves)
|
||||
|
||||
self.assertTrue(genesis.ident in bill.blocks)
|
||||
self.assertFalse(genesis.ident in bill.leaves)
|
||||
|
||||
bill.whichLeaf()
|
||||
|
||||
#for blockIdent in bill.blocks:
|
||||
# ident = bill.blocks[blockIdent].ident
|
||||
# disco = bill.blocks[blockIdent].discoTimestamp
|
||||
# arriv = bill.blocks[blockIdent].arrivTimestamp
|
||||
# parent = bill.blocks[blockIdent].parent
|
||||
# diff = bill.blocks[blockIdent].diff
|
||||
# print(str(ident) + ", " + str(disco) + ", " + str(arriv) + ", " + str(parent) + ", " + str(diff) + ", " + str() + "\n")
|
||||
#print(bill.miningIdents)
|
||||
self.assertEqual(len(bill.miningIdents), 1)
|
||||
self.assertEqual(bill.miningIdents[0], blockC.ident)
|
||||
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Blockchain)
|
||||
unittest.TextTestRunner(verbosity=1).run(suite)
|
95
source-code/Poisson-Graphs/Edge.py
Normal file
95
source-code/Poisson-Graphs/Edge.py
Normal file
|
@ -0,0 +1,95 @@
|
|||
from Node import *
|
||||
|
||||
class Edge(object):
|
||||
'''
|
||||
Edge object. Has an identity, some data, and a dict of nodes.
|
||||
'''
|
||||
def __init__(self, params=["", {}, True]):
|
||||
try:
|
||||
assert len(params)==3
|
||||
except AssertionError:
|
||||
print("Error, tried to create mal-formed edge.")
|
||||
else:
|
||||
self.ident = params[0]
|
||||
self.data = params[1]
|
||||
self.verbose = params[2]
|
||||
self.nodes = {}
|
||||
|
||||
def getNeighbor(self, nodeIdent):
|
||||
# Given one node identity, check that the node
|
||||
# identity is in the edge's node list and
|
||||
# return the identity of the other adjacent node.
|
||||
result = (nodeIdent in self.nodes)
|
||||
if result:
|
||||
for otherIdent in self.nodes:
|
||||
if otherIdent != nodeIdent:
|
||||
result = otherIdent
|
||||
assert result in self.nodes
|
||||
return result
|
||||
|
||||
class Test_Edge(unittest.TestCase):
|
||||
def test_e(self):
|
||||
nellyIdent = newIdent(0)
|
||||
bill = Blockchain([], verbosity=True)
|
||||
|
||||
name = newIdent(0)
|
||||
t = time.time()
|
||||
diff = 1.0
|
||||
params = [name, t, t+1, None, diff, bill.verbose] # Genesis block has no parent, so parent = None
|
||||
genesis = Block(params)
|
||||
bill.addBlock(genesis)
|
||||
|
||||
time.sleep(10)
|
||||
|
||||
name = newIdent(1)
|
||||
t = time.time()
|
||||
diff = 1.0
|
||||
params = [name, t, t+1, genesis.ident, diff, bill.verbose]
|
||||
blockA = Block(params)
|
||||
bill.addBlock(blockA)
|
||||
|
||||
# Nodes need an identity and a blockchain object and verbosity and difficulty
|
||||
nelly = Node([nellyIdent, copy.deepcopy(bill), bill.verbosity, diff])
|
||||
nelly.updateDifficulty(mode="Nakamoto")
|
||||
|
||||
time.sleep(9)
|
||||
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
params = [name, t, t+1, blockA.ident, nelly.diff, nelly.verbose]
|
||||
blockB = Block(params)
|
||||
nelly.updateBlockchain({blockB.ident:blockB})
|
||||
|
||||
time.sleep(8)
|
||||
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
params = [name, t, t+1, blockB.ident, nelly.diff, nelly.verbose]
|
||||
blockC = Block(params)
|
||||
nelly.updateBlockchain({blockC.ident:blockC})
|
||||
|
||||
time.sleep(1)
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
params = [name, t, t+1, blockB.ident, nelly.diff, nelly.verbose] # Fork off
|
||||
blockD = Block(params)
|
||||
nelly.updateBlockchain({blockD.ident:blockD})
|
||||
|
||||
time.sleep(7)
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
params = [name, t, t+1, blockD.ident, nelly.diff, nelly.verbose]
|
||||
blockE = Block(params)
|
||||
nelly.updateBlockchain({blockE.ident:blockE})
|
||||
|
||||
|
||||
time.sleep(6)
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
params = [name, t, t+1, blockE.ident, nelly.diff, nelly.verbose]
|
||||
blockF = Block(params)
|
||||
nelly.updateBlockchain({blockF.ident:blockF})
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Edge)
|
||||
unittest.TextTestRunner(verbosity=1).run(suite)
|
|
@ -36,537 +36,7 @@ def newOffset(params):
|
|||
totalOffset = 60.0*60.0*float(x[0][0] - x[1][0]) + float((x[0][1] - x[1][1]))
|
||||
return totalOffset
|
||||
|
||||
class StochasticProcess(object):
|
||||
'''
|
||||
Stochastic processes have a clock and a state.
|
||||
The clock moves forward, and then the state updates.
|
||||
More detail requires knowledge of the underlying stochProc.
|
||||
'''
|
||||
def __init__(self, params=None):
|
||||
# initialize with initial data
|
||||
self.data = params
|
||||
self.t = 0.0 # should always start at t=0.0
|
||||
self.state = 0.0 # magic number
|
||||
self.maxTime = 1000.0 # magic number
|
||||
self.verbose = True
|
||||
|
||||
def go(self):
|
||||
# Executes stochastic process.
|
||||
assert self.maxTime > 0.0 # Check loop will eventually terminate.
|
||||
t = self.t
|
||||
while t <= self.maxTime:
|
||||
deltaT = self.getNextTime() # Pick the next "time until event" and a description of the event.
|
||||
self.updateState(t, deltaT) # Update state with deltaT input
|
||||
t = self.t
|
||||
if self.verbose:
|
||||
print("Recording...")
|
||||
|
||||
def getNextTime(self):
|
||||
return 1 # Magic number right now
|
||||
|
||||
def updateState(self, t, deltaT):
|
||||
# Update the state of the system. In this case,
|
||||
# we are doing a random walk on the integers.
|
||||
self.state += random.randrange(-1,2,1) # [-1, 0, 1]
|
||||
self.t += deltaT
|
||||
|
||||
class Test_StochasticProcess(unittest.TestCase):
|
||||
def test_sp(self):
|
||||
sally = StochasticProcess()
|
||||
sally.go()
|
||||
|
||||
#suite = unittest.TestLoader().loadTestsFromTestCase(Test_StochasticProcess)
|
||||
#unittest.TextTestRunner(verbosity=1).run(suite)
|
||||
|
||||
class Block(object):
|
||||
'''
|
||||
Each block has: an identity, a timestamp of discovery (possibly false),
|
||||
has a timestamp of arrival at the local node (possibly unnecessary), a pointer to a parent
|
||||
block's identity, and a difficulty score.
|
||||
'''
|
||||
def __init__(self, params=[]):
|
||||
try:
|
||||
assert len(params)==6
|
||||
except AssertionError:
|
||||
print("Error in Block(): Tried to add a malformed block. We received params = " + str(params) + ", but should have had something of the form [ident, disco, arriv, parent, diff, verbose].")
|
||||
else:
|
||||
self.ident = params[0]
|
||||
self.discoTimestamp = params[1]
|
||||
self.arrivTimestamp = params[2]
|
||||
self.parent = params[3]
|
||||
self.diff = params[4]
|
||||
self.verbose = params[5]
|
||||
|
||||
class Test_Block(unittest.TestCase):
|
||||
def test_b(self):
|
||||
#bill = Block()
|
||||
name = newIdent(0)
|
||||
t = time.time()
|
||||
diff = 1.0
|
||||
params = [name, t, t+1, None, diff, False]
|
||||
bill = Block(params)
|
||||
self.assertEqual(bill.ident,name)
|
||||
self.assertEqual(bill.discoTimestamp,t)
|
||||
self.assertEqual(bill.arrivTimestamp,t+1)
|
||||
self.assertTrue(bill.parent is None)
|
||||
self.assertEqual(bill.diff,diff)
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Block)
|
||||
unittest.TextTestRunner(verbosity=1).run(suite)
|
||||
|
||||
class Blockchain(object):
|
||||
'''
|
||||
Not a true blockchain, of course, but tracks block objects (timestamps) as above.
|
||||
Each node should be responsible for finding the chain with most cumulative work.
|
||||
Right now we assume Nakamoto consensus (konsensnakamoto).
|
||||
'''
|
||||
def __init__(self, params=[], verbosity=True):
|
||||
self.blocks = {}
|
||||
self.leaves = {}
|
||||
self.miningIdent = None
|
||||
self.verbose = verbosity
|
||||
|
||||
def addBlock(self, blockToAdd):
|
||||
# In our model we assume difficulty scores of blocks are correct (otherwise they would
|
||||
# be rejected in the real life network, and we aren't trying to model spam attacks).
|
||||
try:
|
||||
assert blockToAdd.ident not in self.blocks
|
||||
except AssertionError:
|
||||
print("Error, tried to add block that already exists in blockchain.")
|
||||
else:
|
||||
self.blocks.update({blockToAdd.ident:blockToAdd})
|
||||
self.leaves.update({blockToAdd.ident:blockToAdd})
|
||||
if blockToAdd.parent in self.leaves:
|
||||
del self.leaves[blockToAdd.parent]
|
||||
self.whichLeaf()
|
||||
|
||||
def whichLeaf(self):
|
||||
# Determine which leaf shall be the parent leaf.
|
||||
if len(self.leaves) == 1:
|
||||
# If the chain has never forked, we have no decision to make:
|
||||
for ident in self.leaves:
|
||||
self.miningIdent = ident
|
||||
elif len(self.leaves) > 1:
|
||||
# If the chain has forked *ever* this will not be the case.
|
||||
maxCumDiff = 0.0
|
||||
for ident in self.leaves:
|
||||
tempCumDiff = 0.0
|
||||
tempCumDiff += self.blocks[ident].diff
|
||||
nextIdent = self.blocks[ident].parent
|
||||
if nextIdent is not None and nextIdent in self.blocks:
|
||||
while self.blocks[nextIdent].parent is not None:
|
||||
tempCumDiff += self.blocks[nextIdent].diff
|
||||
nextIdent = self.blocks[nextIdent].parent
|
||||
if tempCumDiff > maxCumDiff:
|
||||
# If more than one leaf ties for maxCumDiff, each node in the
|
||||
# network should pick one of these two arbitrarily. Since we
|
||||
# are storing each blockchain in a hash table (unordered!), for
|
||||
# each node in the network that observes a tie, each possible leaf
|
||||
# is equally likely to have been the first one found! So
|
||||
# we don't need to do anything for the node to select which chain
|
||||
# to work off of.
|
||||
self.miningIdent = ident
|
||||
else:
|
||||
print("Error, tried to assess an empty blockchain.")
|
||||
|
||||
class Test_Blockchain(unittest.TestCase):
|
||||
def test_bc(self):
|
||||
bill = Blockchain([], verbosity=True)
|
||||
|
||||
name = newIdent(0)
|
||||
t = time.time()
|
||||
diff = 1.0
|
||||
params = [name, t, t+1, None, diff, bill.verbose]
|
||||
genesis = Block(params)
|
||||
|
||||
self.assertEqual(genesis.ident,name)
|
||||
self.assertEqual(genesis.discoTimestamp,t)
|
||||
self.assertEqual(genesis.arrivTimestamp,t+1)
|
||||
self.assertTrue(genesis.parent is None)
|
||||
self.assertEqual(genesis.diff,diff)
|
||||
|
||||
bill.addBlock(genesis)
|
||||
|
||||
self.assertTrue(genesis.ident in bill.blocks)
|
||||
self.assertTrue(genesis.ident in bill.leaves)
|
||||
self.assertEqual(genesis.ident, bill.miningIdent)
|
||||
|
||||
name = newIdent(1)
|
||||
t = time.time()
|
||||
diff = 2.0
|
||||
params = [name, t, t+1, genesis.ident, diff, bill.verbose]
|
||||
blockA = Block(params)
|
||||
bill.addBlock(blockA)
|
||||
|
||||
self.assertTrue(blockA.ident in bill.blocks)
|
||||
self.assertTrue(blockA.ident in bill.leaves)
|
||||
self.assertFalse(genesis.ident in bill.leaves)
|
||||
self.assertTrue(genesis.ident in bill.blocks)
|
||||
self.assertEqual(blockA.ident, bill.miningIdent)
|
||||
|
||||
name = newIdent(1)
|
||||
t = time.time()
|
||||
diff = 2.5
|
||||
params = [name, t, t+1, None, diff, bill.verbose]
|
||||
blockB = Block(params)
|
||||
bill.addBlock(blockB)
|
||||
|
||||
self.assertTrue(blockB.ident in bill.blocks)
|
||||
self.assertTrue(blockB.ident in bill.leaves)
|
||||
self.assertFalse(genesis.ident in bill.leaves)
|
||||
self.assertTrue(genesis.ident in bill.blocks)
|
||||
self.assertTrue(blockA.ident in bill.leaves)
|
||||
self.assertTrue(blockB.ident in bill.leaves)
|
||||
self.assertEqual(blockB.ident, bill.miningIdent)
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Blockchain)
|
||||
unittest.TextTestRunner(verbosity=1).run(suite)
|
||||
|
||||
class Node(object):
|
||||
'''
|
||||
Node object. params [identity, blockchain (data), verbosity, difficulty]
|
||||
'''
|
||||
def __init__(self, params=["", {}, True]):
|
||||
try:
|
||||
assert len(params)==4
|
||||
except AssertionError:
|
||||
print("Error, Tried to create malformed node.")
|
||||
else:
|
||||
self.ident = params[0]
|
||||
self.data = params[1] #Blockchain object
|
||||
self.verbose = params[2]
|
||||
self.diff = params[3]
|
||||
self.edges = {}
|
||||
|
||||
def updateBlockchain(self, incBlocks, diffUpdateRate=1, mode="Nakamoto", targetRate=1.0/1209600.0):
|
||||
# dataToUpdate shall be a dictionary of block identities (as keys) and their associated blocks (as values)
|
||||
# to be added to the local data. We assume difficulty scores have been reported honestly for now.
|
||||
|
||||
# Stash a copy of incoming blocks so removing keys won't shrink the size of the dictionary over which
|
||||
# we are looping.
|
||||
tempData = copy.deepcopy(incBlocks)
|
||||
for key in incBlocks:
|
||||
if incBlocks[key].parent in self.data["blockchain"].blocks:
|
||||
self.data["blockchain"].addBlock(incBlocks[key])
|
||||
#if len(self.data["blockchain"]) % diffUpdateRate == 0:
|
||||
# self.updateDifficulty(mode, targetRate)
|
||||
del tempData[key]
|
||||
incBlocks = copy.deepcopy(tempData)
|
||||
while len(incBlocks)>0:
|
||||
for key in incBlocks:
|
||||
if incBlocks[key].parent in self.data["blockchain"].blocks:
|
||||
self.data["blockchain"].addBlock(incBlocks[key])
|
||||
#if len(self.data["blockchain"]) % diffUpdateRate == 0:
|
||||
# self.updateDifficulty(mode, targetRate)
|
||||
del tempData[key]
|
||||
incBlocks = copy.deepcopy(tempData)
|
||||
|
||||
def updateDifficulty(self, mode="Nakamoto", targetRate=1.0/1209600.0):
|
||||
# Compute the difficulty of the next block
|
||||
# Note for default, targetRate = two weeks/period, seven days/week, 24 hours/day, 60 minutes/hour, 60 seconds/minute) = 1209600 seconds/period
|
||||
if mode=="Nakamoto":
|
||||
# Use MLE estimate of poisson process, compare to targetRate, update by multiplying by resulting ratio.
|
||||
count = 2016
|
||||
ident = self.data.miningIdent
|
||||
topTime = copy.deepcopy(int(round(self.data.blocks[ident].discoTimestamp)))
|
||||
parent = self.data.blocks[ident].parent
|
||||
count = count - 1
|
||||
while count > 0 and parent is not None:
|
||||
ident = copy.deepcopy(parent)
|
||||
parent = self.data.blocks[ident].parent
|
||||
count = count - 1
|
||||
botTime = copy.deepcopy(int(round(self.data.blocks[ident].discoTimestamp)))
|
||||
|
||||
# Algebra is okay:
|
||||
assert 0 <= 2016 - count and 2016 - count < 2017
|
||||
assert topTime > botTime
|
||||
|
||||
# MLE estimate of arrivals per second:
|
||||
mleDiscoRate = float(2016 - count)/float(topTime - botTime)
|
||||
|
||||
# How much should difficulty change?
|
||||
self.diff = self.diff*(mleDiscoRate/targetRate)
|
||||
|
||||
elif mode=="vanSaberhagen":
|
||||
# Similar to above, except use 1200 blocks, discard top 120 and bottom 120 after sorting.
|
||||
# 4 minute blocks in the original cryptonote, I believe... targetRate = 1.0/
|
||||
# 4 minutes/period, 60 seconds/minute ~ 240 seconds/period
|
||||
assert targetRate==1.0/240.0
|
||||
count = 1200
|
||||
ident = self.data.miningIdent
|
||||
bl = []
|
||||
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
|
||||
parent = self.data.blocks[ident].parent
|
||||
count = count - 1
|
||||
while count > 0 and parent is not NOne:
|
||||
ident = copy.deepcopy(parent)
|
||||
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
|
||||
parent = self.data.blocks[ident].parent
|
||||
count = count-1
|
||||
# sort
|
||||
bl = sorted(bl)
|
||||
|
||||
# remove outliers
|
||||
bl = bl[120:-120]
|
||||
|
||||
# get topTime and botTime
|
||||
topTime = bl[-1]
|
||||
botTime = bl[0]
|
||||
|
||||
# Assert algebra will work
|
||||
assert 0 <= 960 - count and 960 - count < 961
|
||||
assert topTime > botTime
|
||||
|
||||
# Sort of the MLE: # blocks/difference in reported times
|
||||
# But not the MLE, since the reported times may not be
|
||||
# the actual times, the "difference in reported times" !=
|
||||
# "ground truth difference in block discoery times" in general
|
||||
naiveDiscoRate = (960 - count)/(topTime - botTime)
|
||||
|
||||
# How much should difficulty change?
|
||||
self.diff = self.diff*(naiveDiscoRate/targetRate)
|
||||
|
||||
elif mode=="MOM:expModGauss":
|
||||
# Similar to "vanSaberhagen" except with 2-minute blocks and
|
||||
# we attempt to take into account that "difference in timestamps"
|
||||
# can be negative by:
|
||||
# 1) insisting that the ordering induced by the blockchain and
|
||||
# 2) modeling timestamps as exponentially modified gaussian.
|
||||
# If timestamps are T = X + Z where X is exponentially dist-
|
||||
# ributed with parameter lambda and Z is some Gaussian
|
||||
# noise with average mu and variance sigma2, then we can est-
|
||||
# imate sigma2, mu, and lambda:
|
||||
# mu ~ mean - stdev*(skewness/2)**(1.0/3.0)
|
||||
# sigma2 ~ variance*(1-(skewness/2)**(2.0/3.0))
|
||||
# lambda ~ (1.0/(stdev))*(2/skewness)**(1.0/3.0)
|
||||
assert targetRate==1.0/120.0
|
||||
count = 1200
|
||||
ident = self.data.miningIdent
|
||||
bl = []
|
||||
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
|
||||
parent = self.data.blocks[ident].parent
|
||||
count = count - 1
|
||||
while count > 0 and parent is not NOne:
|
||||
ident = copy.deepcopy(parent)
|
||||
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
|
||||
parent = self.data.blocks[ident].parent
|
||||
count = count-1
|
||||
sk = skew(bl)
|
||||
va = var(bl)
|
||||
stdv = sqrt(va)
|
||||
lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0)
|
||||
self.diff = self.diff*(lam/targetRate)
|
||||
else:
|
||||
print("Error, invalid difficulty mode entered.")
|
||||
|
||||
def propagate(self, blockIdent):
|
||||
for edgeIdent in self.edges:
|
||||
e = self.edges[edgeIdent]
|
||||
l = e.data["length"]
|
||||
toa = self.t + l
|
||||
mIdent = e.getNeighbor(n.ident)
|
||||
m = e.nodes[mIdent]
|
||||
if blockIdent not in m.data["blockchain"]:
|
||||
pB = e.data["pendingBlocks"]
|
||||
pendingIdent = newIdent(len(pB))
|
||||
pendingDat = {"timeOfArrival":toa, "destIdent":mIdent, "block":self.blocks[blockIdent]}
|
||||
pB.update({pendingIdent:pendingDat})
|
||||
|
||||
|
||||
class Test_Node(unittest.TestCase):
|
||||
def test_node(self):
|
||||
nellyIdent = newIdent(0)
|
||||
bill = Blockchain([], verbosity=True)
|
||||
|
||||
name = newIdent(0)
|
||||
t = time.time()
|
||||
diff = 1.0
|
||||
params = [name, t, t+1, None, diff, bill.verbose] # Genesis block has no parent, so parent = None
|
||||
genesis = Block(params)
|
||||
bill.addBlock(genesis)
|
||||
|
||||
time.sleep(10)
|
||||
|
||||
name = newIdent(1)
|
||||
t = time.time()
|
||||
diff = 1.0
|
||||
params = [name, t, t+1, genesis.ident, diff, bill.verbose]
|
||||
blockA = Block(params)
|
||||
bill.addBlock(blockA)
|
||||
|
||||
# Nodes need an identity and a blockchain object and verbosity and difficulty
|
||||
nelly = Node([nellyIdent, copy.deepcopy(bill), bill.verbosity, diff])
|
||||
nelly.updateDifficulty(mode="Nakamoto")
|
||||
|
||||
time.sleep(9)
|
||||
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
params = [name, t, t+1, blockA.ident, nelly.diff, nelly.verbose]
|
||||
blockB = Block(params)
|
||||
nelly.updateBlockchain({blockB.ident:blockB})
|
||||
|
||||
time.sleep(8)
|
||||
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
params = [name, t, t+1, blockB.ident, nelly.diff, nelly.verbose]
|
||||
blockC = Block(params)
|
||||
nelly.updateBlockchain({blockC.ident:blockC})
|
||||
|
||||
time.sleep(1)
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
params = [name, t, t+1, blockB.ident, nelly.diff, nelly.verbose] # Fork off
|
||||
blockD = Block(params)
|
||||
nelly.updateBlockchain({blockD.ident:blockD})
|
||||
|
||||
time.sleep(7)
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
params = [name, t, t+1, blockD.ident, nelly.diff, nelly.verbose]
|
||||
blockE = Block(params)
|
||||
nelly.updateBlockchain({blockE.ident:blockE})
|
||||
|
||||
|
||||
time.sleep(6)
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
params = [name, t, t+1, blockE.ident, nelly.diff, nelly.verbose]
|
||||
blockF = Block(params)
|
||||
nelly.updateBlockchain({blockF.ident:blockF})
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Blockchain)
|
||||
unittest.TextTestRunner(verbosity=1).run(suite)
|
||||
|
||||
|
||||
class Edge(object):
|
||||
'''
|
||||
Edge object. Has an identity, some data, and a dict of nodes.
|
||||
'''
|
||||
def __init__(self, params=["", {}, True]):
|
||||
try:
|
||||
assert len(params)==3
|
||||
except AssertionError:
|
||||
print("Error, tried to create mal-formed edge.")
|
||||
else:
|
||||
self.ident = params[0]
|
||||
self.data = params[1]
|
||||
self.verbose = params[2]
|
||||
self.nodes = {}
|
||||
|
||||
def getNeighbor(self, nodeIdent):
|
||||
# Given one node identity, check that the node
|
||||
# identity is in the edge's node list and
|
||||
# return the identity of the other adjacent node.
|
||||
result = (nodeIdent in self.nodes)
|
||||
if result:
|
||||
for otherIdent in self.nodes:
|
||||
if otherIdent != nodeIdent:
|
||||
result = otherIdent
|
||||
assert result in self.nodes
|
||||
return result
|
||||
|
||||
class Graph(object):
|
||||
'''
|
||||
Graph object. Contains some data, a dict of nodes, and a dict of edges.
|
||||
'''
|
||||
def __init__(self, params={}, verbosity=True):
|
||||
self.data=params
|
||||
self.verbose = verbosity
|
||||
self.nodes = {}
|
||||
self.edges = {}
|
||||
|
||||
def createGraph(self, numNodes, probEdge, maxNeighbors):
|
||||
# Create a new random graph with numNodes nodes, a
|
||||
# likelihood any unordered pair of vertices has an edge
|
||||
# probEdge, and maximum number of neighbors per node
|
||||
# maxNeighbors.
|
||||
|
||||
# First, include inputted information into self.data
|
||||
self.data.update({"probEdge":probEdge, "maxNeighbors":maxNeighbors})
|
||||
|
||||
# Next, for each node to be added, create the node and name it.
|
||||
for i in range(numNodes):
|
||||
nIdent = newIdent(i)
|
||||
bl = Blockchain([], verbosity=True)
|
||||
dat = {"blockchain":bl, "intensity":newIntensity(["uniform"]), "offset":newOffset("sumOfSkellams")}
|
||||
# A node needs an ident, a data object, a verbosity, and a difficulty
|
||||
n = Node([nIdent, dat, self.verbose, 1.0])
|
||||
self.nodes.update({n.ident:n})
|
||||
|
||||
# Next, for each possible node pair, decide if an edge exists.
|
||||
touched = {} # Dummy list of node pairs we have already considered.
|
||||
for nIdent in self.nodes:
|
||||
n = self.nodes[nIdent] # Pick a node
|
||||
for mIdent in self.nodes:
|
||||
m = self.nodes[mIdent] # Pick a pair element
|
||||
notSameNode = (nIdent != mIdent) # Ensure we aren't dealing with (x,x)
|
||||
nOpenSlots = (len(n.edges) < self.data["maxNeighbors"]) # ensure both nodes have open slots available for new edges
|
||||
mOpenSlots = (len(m.edges) < self.data["maxNeighbors"])
|
||||
untouched = ((nIdent, mIdent) not in touched) # make sure the pair and its transposition have not been touched
|
||||
dehcuotnu = ((mIdent, nIdent) not in touched)
|
||||
if notSameNode and nOpenSlots and mOpenSlots and untouched and dehcuotnu:
|
||||
# Mark pair as touhed
|
||||
touched.update({(nIdent,mIdent):True, (mIdent,nIdent):True})
|
||||
if random.random() < self.data["probEdge"]:
|
||||
# Determine if edge should exist and if so, add it.
|
||||
nonce = len(self.edges)
|
||||
e = Edge([newIdent(nonce),{"length":random.random(), "pendingBlocks":[]},self.verbose])
|
||||
e.nodes.update({n.ident:n, m.ident:m})
|
||||
self.nodes[nIdent].edges.update({e.ident:e})
|
||||
self.nodes[mIdent].edges.update({e.ident:e})
|
||||
self.edges.update({e.ident:e})
|
||||
|
||||
def addNode(self):
|
||||
# Add new node
|
||||
n = Node([self.newIdent(len(self.nodes)), {}, self.verbose, 1.0])
|
||||
self.nodes.update({n.ident:n})
|
||||
for mIdent in self.nodes:
|
||||
# For every other node, check if an edge should exist and if so add it.
|
||||
m = self.nodes[mIdent]
|
||||
notSameNode = (n.ident != mIdent)
|
||||
nOpenSlots = (len(n.edges) < self.data["maxNeighbors"])
|
||||
mOpenSlots = (len(m.edges) < self.data["maxNeighbors"])
|
||||
if notSameNode and nOpenSlots and mOpenSlots and random.random() < self.data["probEdge"]:
|
||||
nonce = len(self.edges)
|
||||
e = Edge([self.newIdent(nonce), {"length":random.random(), "pendingBlocks":[]}, self.verbose])
|
||||
e.nodes.update({n.ident:n, m.ident:m})
|
||||
n.edges.update({e.ident:e})
|
||||
self.nodes[mIdent].edges.update({e.ident:e})
|
||||
self.edges.update({e.ident:e})
|
||||
return n.ident
|
||||
|
||||
def delNode(self, ident):
|
||||
# Remove a node and wipe all memory of its edges from history.
|
||||
edgesToDelete = self.nodes[ident].edges
|
||||
for edgeIdent in edgesToDelete:
|
||||
e = edgesToDelete[edgeIdent]
|
||||
otherIdent = e.getNeighbor(ident)
|
||||
del self.edges[edgeIdent]
|
||||
del self.nodes[otherIdent].edges[edgeIdent]
|
||||
del self.nodes[ident]
|
||||
|
||||
class Test_Graph(unittest.TestCase):
|
||||
def test_graph(self):
|
||||
greg = Graph()
|
||||
greg.createGraph(3, 0.5, 10)
|
||||
self.assertEqual(len(greg.nodes),3)
|
||||
greg.addNode()
|
||||
self.assertEqual(len(greg.nodes),4)
|
||||
for edge in greg.edges:
|
||||
self.assertEqual(len(greg.edges[edge].nodes),2)
|
||||
nodeToKill = random.choice(list(greg.nodes.keys()))
|
||||
greg.delNode(nodeToKill)
|
||||
for edge in greg.edges:
|
||||
self.assertEqual(len(greg.edges[edge].nodes),2)
|
||||
for nodeIdent in greg.edges[edge].nodes:
|
||||
self.assertTrue(nodeIdent in greg.nodes)
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Graph)
|
||||
unittest.TextTestRunner(verbosity=1).run(suite)
|
||||
|
||||
class FishGraph(StochasticProcess):
|
||||
'''
|
||||
Stochastic process on a graph
|
||||
|
@ -703,7 +173,7 @@ class FishGraph(StochasticProcess):
|
|||
mIdent = e.getNeighbor(n.ident)
|
||||
m = self.state.nodes[mIdent]
|
||||
mdata = m.data["blockchain"]
|
||||
n.data["blockchain"].update(mdata)
|
||||
n.updateBlockchain(mdata)
|
||||
y = len(self.state.nodes)
|
||||
assert y == x + 1
|
||||
shout += str(y) + "\n"
|
||||
|
@ -712,27 +182,67 @@ class FishGraph(StochasticProcess):
|
|||
elif len(eventTag)==2:
|
||||
# Block is discovered and plunked into each edge's pendingBlock list.
|
||||
|
||||
shout += "DISCOVERY"
|
||||
shout += "DISCOVERY\n"
|
||||
if self.verbose:
|
||||
print(shout)
|
||||
|
||||
if self.verbose:
|
||||
print("Checking formation of eventTag = [\"discovery\", nodeIdent]")
|
||||
assert eventTag[0]=="discovery"
|
||||
assert eventTag[1] in self.state.nodes
|
||||
|
||||
if self.verbose:
|
||||
print("Retrieving discoverer's identity")
|
||||
nIdent = eventTag[1] # get founding node's identity
|
||||
|
||||
if self.verbose:
|
||||
print("Retrieving discoverer")
|
||||
n = self.state.nodes[nIdent] # get founding node
|
||||
|
||||
if self.verbose:
|
||||
print("Computing discoverer's wall clock")
|
||||
s = self.t + n.data["offset"] # get founding node's wall clock
|
||||
|
||||
|
||||
if self.verbose:
|
||||
print("Generating new block identity")
|
||||
newBlockIdent = newIdent(len(n.data["blockchain"].blocks)) # generate new identity
|
||||
|
||||
if self.verbose:
|
||||
print("Setting timestamps")
|
||||
disco = s
|
||||
arriv = s
|
||||
|
||||
if self.verbose:
|
||||
print("Retrieving parent")
|
||||
parent = n.data["blockchain"].miningIdent
|
||||
|
||||
if self.verbose:
|
||||
print("getting difficulty")
|
||||
diff = copy.deepcopy(n.diff)
|
||||
|
||||
if self.verbose:
|
||||
print("setting verbosity")
|
||||
verbosity = self.verbose
|
||||
|
||||
if self.verbose:
|
||||
print("Initializing a new block")
|
||||
newBlock = Block([newBlockIdent, disco, arriv, parent, diff, verbosity])
|
||||
|
||||
if self.verbose:
|
||||
print("Updating discovering node's blockchain")
|
||||
n.updateBlockchain({newBlockIdent:newBlock})
|
||||
|
||||
if self.verbose:
|
||||
print("Computing discoverer's new difficulty")
|
||||
n.updateDifficulty(mode, targetRate)
|
||||
n.propagate(newBlockIdent)
|
||||
|
||||
if self.verbose:
|
||||
print("propagating new block.")
|
||||
n.propagate(self.t, newBlockIdent)
|
||||
|
||||
if self.verbose:
|
||||
print("discovery complete")
|
||||
|
||||
elif len(eventTag)==3:
|
||||
#eventTag = ("arrival", e.ident, pendingIdent)
|
||||
|
@ -755,9 +265,9 @@ class FishGraph(StochasticProcess):
|
|||
arriv = self.t + u + receiver.data["offset"]
|
||||
newBlock = arrivalInfo["block"]
|
||||
newBlock.arrivTimestamp = copy.deepcopy(arriv)
|
||||
receiver.data["blockchain"].updateBlockchain({newBlock.ident:newBlock})
|
||||
receiver.updateBlockchain({newBlock.ident:newBlock})
|
||||
receiver.updateDifficulty(mode, targetRate)
|
||||
receiver.propagate(newBlock.ident)
|
||||
receiver.propagate(self.t, newBlock.ident)
|
||||
|
||||
else:
|
||||
print("Error: eventTag was not a string, or not an array length 2 or 3. In fact, we have eventTag = ", eventTag)
|
||||
|
@ -786,7 +296,7 @@ class FishGraph(StochasticProcess):
|
|||
class Test_FishGraph(unittest.TestCase):
|
||||
def test_fishGraph(self):
|
||||
for i in range(10):
|
||||
params = {"numNodes":10, "probEdge":0.5, "maxNeighbors":10, "maxTime":10.0, "birthRate":0.001, "deathRate":0.001}
|
||||
params = {"numNodes":10, "probEdge":0.5, "maxNeighbors":10, "maxTime":10.0, "birthRate":0.1, "deathRate":0.1}
|
||||
greg = FishGraph(params, verbosity=True)
|
||||
greg.go()
|
||||
|
||||
|
|
101
source-code/Poisson-Graphs/Graph.py
Normal file
101
source-code/Poisson-Graphs/Graph.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
from Edge import *
|
||||
|
||||
class Graph(object):
|
||||
'''
|
||||
Graph object. Contains some data, a dict of nodes, and a dict of edges.
|
||||
'''
|
||||
def __init__(self, params={}, verbosity=True):
|
||||
self.data=params
|
||||
self.verbose = verbosity
|
||||
self.nodes = {}
|
||||
self.edges = {}
|
||||
|
||||
def createGraph(self, numNodes, probEdge, maxNeighbors):
|
||||
# Create a new random graph with numNodes nodes, a
|
||||
# likelihood any unordered pair of vertices has an edge
|
||||
# probEdge, and maximum number of neighbors per node
|
||||
# maxNeighbors.
|
||||
|
||||
# First, include inputted information into self.data
|
||||
self.data.update({"probEdge":probEdge, "maxNeighbors":maxNeighbors})
|
||||
|
||||
# Next, for each node to be added, create the node and name it.
|
||||
for i in range(numNodes):
|
||||
nIdent = newIdent(i)
|
||||
bl = Blockchain([], verbosity=True)
|
||||
dat = {"blockchain":bl, "intensity":newIntensity(["uniform"]), "offset":newOffset("sumOfSkellams")}
|
||||
# A node needs an ident, a data object, a verbosity, and a difficulty
|
||||
n = Node([nIdent, dat, self.verbose, 1.0])
|
||||
self.nodes.update({n.ident:n})
|
||||
|
||||
# Next, for each possible node pair, decide if an edge exists.
|
||||
touched = {} # Dummy list of node pairs we have already considered.
|
||||
for nIdent in self.nodes:
|
||||
n = self.nodes[nIdent] # Pick a node
|
||||
for mIdent in self.nodes:
|
||||
m = self.nodes[mIdent] # Pick a pair element
|
||||
notSameNode = (nIdent != mIdent) # Ensure we aren't dealing with (x,x)
|
||||
nOpenSlots = (len(n.edges) < self.data["maxNeighbors"]) # ensure both nodes have open slots available for new edges
|
||||
mOpenSlots = (len(m.edges) < self.data["maxNeighbors"])
|
||||
untouched = ((nIdent, mIdent) not in touched) # make sure the pair and its transposition have not been touched
|
||||
dehcuotnu = ((mIdent, nIdent) not in touched)
|
||||
if notSameNode and nOpenSlots and mOpenSlots and untouched and dehcuotnu:
|
||||
# Mark pair as touhed
|
||||
touched.update({(nIdent,mIdent):True, (mIdent,nIdent):True})
|
||||
if random.random() < self.data["probEdge"]:
|
||||
# Determine if edge should exist and if so, add it.
|
||||
nonce = len(self.edges)
|
||||
e = Edge([newIdent(nonce),{"length":random.random(), "pendingBlocks":[]},self.verbose])
|
||||
e.nodes.update({n.ident:n, m.ident:m})
|
||||
self.nodes[nIdent].edges.update({e.ident:e})
|
||||
self.nodes[mIdent].edges.update({e.ident:e})
|
||||
self.edges.update({e.ident:e})
|
||||
|
||||
def addNode(self):
|
||||
# Add new node
|
||||
n = Node([newIdent(len(self.nodes)), {}, self.verbose, 1.0])
|
||||
self.nodes.update({n.ident:n})
|
||||
for mIdent in self.nodes:
|
||||
# For every other node, check if an edge should exist and if so add it.
|
||||
m = self.nodes[mIdent]
|
||||
notSameNode = (n.ident != mIdent)
|
||||
nOpenSlots = (len(n.edges) < self.data["maxNeighbors"])
|
||||
mOpenSlots = (len(m.edges) < self.data["maxNeighbors"])
|
||||
if notSameNode and nOpenSlots and mOpenSlots and random.random() < self.data["probEdge"]:
|
||||
nonce = len(self.edges)
|
||||
e = Edge([newIdent(nonce), {"length":random.random(), "pendingBlocks":[]}, self.verbose])
|
||||
e.nodes.update({n.ident:n, m.ident:m})
|
||||
n.edges.update({e.ident:e})
|
||||
self.nodes[mIdent].edges.update({e.ident:e})
|
||||
self.edges.update({e.ident:e})
|
||||
return n.ident
|
||||
|
||||
def delNode(self, ident):
|
||||
# Remove a node and wipe all memory of its edges from history.
|
||||
edgesToDelete = self.nodes[ident].edges
|
||||
for edgeIdent in edgesToDelete:
|
||||
e = edgesToDelete[edgeIdent]
|
||||
otherIdent = e.getNeighbor(ident)
|
||||
del self.edges[edgeIdent]
|
||||
del self.nodes[otherIdent].edges[edgeIdent]
|
||||
del self.nodes[ident]
|
||||
|
||||
class Test_Graph(unittest.TestCase):
|
||||
def test_graph(self):
|
||||
greg = Graph()
|
||||
greg.createGraph(3, 0.5, 10)
|
||||
self.assertEqual(len(greg.nodes),3)
|
||||
greg.addNode()
|
||||
self.assertEqual(len(greg.nodes),4)
|
||||
for edge in greg.edges:
|
||||
self.assertEqual(len(greg.edges[edge].nodes),2)
|
||||
nodeToKill = random.choice(list(greg.nodes.keys()))
|
||||
greg.delNode(nodeToKill)
|
||||
for edge in greg.edges:
|
||||
self.assertEqual(len(greg.edges[edge].nodes),2)
|
||||
for nodeIdent in greg.edges[edge].nodes:
|
||||
self.assertTrue(nodeIdent in greg.nodes)
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Graph)
|
||||
unittest.TextTestRunner(verbosity=1).run(suite)
|
||||
|
271
source-code/Poisson-Graphs/Node.py
Normal file
271
source-code/Poisson-Graphs/Node.py
Normal file
|
@ -0,0 +1,271 @@
|
|||
from Blockchain import *
|
||||
|
||||
class Node(object):
|
||||
'''
|
||||
Node object. params [identity, blockchain (data), verbosity, difficulty]
|
||||
'''
|
||||
def __init__(self, params={}):
|
||||
try:
|
||||
assert len(params)==4
|
||||
except AssertionError:
|
||||
print("Error, Tried to create malformed node.")
|
||||
else:
|
||||
self.ident = params["ident"]
|
||||
self.data = params["data"]
|
||||
self.diff = params["diff"]
|
||||
self.verbose = params["verbose"]
|
||||
self.edges = {}
|
||||
|
||||
def updateBlockchain(self, incBlocks, diffUpdateRate=1, mode="Nakamoto", targetRate=1.0/1209600.0):
|
||||
# dataToUpdate shall be a dictionary of block identities (as keys) and their associated blocks (as values)
|
||||
# to be added to the local data. We assume difficulty scores have been reported honestly for now.
|
||||
|
||||
# Stash a copy of incoming blocks so removing keys won't shrink the size of the dictionary over which
|
||||
# we are looping.
|
||||
|
||||
if self.verbose:
|
||||
print("\t\t Updating blockchain.")
|
||||
|
||||
if self.verbose:
|
||||
print("\t\tAdding incoming blocks in order of parentage")
|
||||
|
||||
if self.verbose:
|
||||
print("\t\tFirst step. incBlocks has " + str(len(incBlocks)) + " entries.")
|
||||
tempData = copy.deepcopy(incBlocks)
|
||||
|
||||
if self.verbose:
|
||||
print("\t\t Now tempData has " + str(len(tempData)) + " entries.")
|
||||
|
||||
for key in incBlocks.blocks:
|
||||
if key in self.data["blockchain"].blocks:
|
||||
del tempData[key]
|
||||
elif incBlocks.blocks[key].parent in self.data["blockchain"].blocks or incBlocks[key].parent is None:
|
||||
self.data["blockchain"].addBlock(incBlocks.blocks[key])
|
||||
#if len(self.data["blockchain"]) % diffUpdateRate == 0:
|
||||
# self.updateDifficulty(mode, targetRate)
|
||||
del tempData[key]
|
||||
incBlocks = copy.deepcopy(tempData)
|
||||
|
||||
if self.verbose:
|
||||
print("\t\t Now incBlocks has " + str(len(incBlocks.blocks)) + " entries.")
|
||||
|
||||
if self.verbose:
|
||||
print("\t\tRemaining steps (while loop)")
|
||||
|
||||
while len(incBlocks)>0:
|
||||
if self.verbose:
|
||||
print("\t\t Now tempData has " + str(len(tempData.blocks)) + " entries.")
|
||||
for key in incBlocks:
|
||||
if key in self.data["blockchain"].blocks:
|
||||
del tempData[key]
|
||||
elif incBlocks.blocks[key].parent in self.data["blockchain"].blocks:
|
||||
self.data["blockchain"].addBlock(incBlocks.blocks[key])
|
||||
del tempData[key]
|
||||
incBlocks = copy.deepcopy(tempData)
|
||||
if self.verbose:
|
||||
print("\t\t Now incBlocks has " + str(len(incBlocks)) + " entries.")
|
||||
|
||||
|
||||
def updateDifficulty(self, mode="Nakamoto", targetRate=1.0/1209600.0):
|
||||
# Compute the difficulty of the next block
|
||||
# Note for default, targetRate = two weeks/period, seven days/week, 24 hours/day, 60 minutes/hour, 60 seconds/minute) = 1209600 seconds/period
|
||||
if mode=="Nakamoto":
|
||||
# Use MLE estimate of poisson process, compare to targetRate, update by multiplying by resulting ratio.
|
||||
count = 2016
|
||||
bc = self.data["blockchain"]
|
||||
ident = bc.miningIdent
|
||||
topTime = copy.deepcopy(bc.blocks[ident].discoTimestamp)
|
||||
parent = bc.blocks[ident].parent
|
||||
count = count - 1
|
||||
touched = False
|
||||
while count > 0 and parent is not None:
|
||||
ident = copy.deepcopy(parent)
|
||||
parent = bc.blocks[ident].parent
|
||||
count = count - 1
|
||||
touched = True
|
||||
if not touched:
|
||||
mleDiscoRate = targetRate
|
||||
else:
|
||||
botTime = copy.deepcopy(bc.blocks[ident].discoTimestamp)
|
||||
|
||||
# Algebra is okay:
|
||||
assert 0 <= 2016 - count and 2016 - count < 2017
|
||||
assert topTime != botTime
|
||||
# MLE estimate of arrivals per second:
|
||||
mleDiscoRate = float(2016 - count)/float(topTime - botTime)
|
||||
mleDiscoRate = abs(mleDiscoRate)
|
||||
# Rate must be positive... so the MLE for block arrival rate
|
||||
# assuming a Poisson process _is not even well-defined_ as
|
||||
# an estimate for block arrival rate assuming timestamps are
|
||||
# inaccurately reported!
|
||||
|
||||
# We use it nonetheless.
|
||||
|
||||
# How much should difficulty change?
|
||||
self.diff = self.diff*(mleDiscoRate/targetRate)
|
||||
|
||||
elif mode=="vanSaberhagen":
|
||||
# Similar to above, except use 1200 blocks, discard top 120 and bottom 120 after sorting.
|
||||
# 4 minute blocks in the original cryptonote, I believe... targetRate = 1.0/
|
||||
# 4 minutes/period, 60 seconds/minute ~ 240 seconds/period
|
||||
assert targetRate==1.0/240.0
|
||||
count = 1200
|
||||
ident = self.data.miningIdent
|
||||
bl = []
|
||||
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
|
||||
parent = self.data.blocks[ident].parent
|
||||
count = count - 1
|
||||
while count > 0 and parent is not NOne:
|
||||
ident = copy.deepcopy(parent)
|
||||
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
|
||||
parent = self.data.blocks[ident].parent
|
||||
count = count-1
|
||||
# sort
|
||||
bl = sorted(bl)
|
||||
|
||||
# remove outliers
|
||||
bl = bl[120:-120]
|
||||
|
||||
# get topTime and botTime
|
||||
topTime = bl[-1]
|
||||
botTime = bl[0]
|
||||
|
||||
# Assert algebra will work
|
||||
assert 0 <= 960 - count and 960 - count < 961
|
||||
assert topTime > botTime
|
||||
|
||||
# Sort of the MLE: # blocks/difference in reported times
|
||||
# But not the MLE, since the reported times may not be
|
||||
# the actual times, the "difference in reported times" !=
|
||||
# "ground truth difference in block discoery times" in general
|
||||
naiveDiscoRate = (960 - count)/(topTime - botTime)
|
||||
|
||||
# How much should difficulty change?
|
||||
self.diff = self.diff*(naiveDiscoRate/targetRate)
|
||||
|
||||
elif mode=="MOM:expModGauss":
|
||||
# Similar to "vanSaberhagen" except with 2-minute blocks and
|
||||
# we attempt to take into account that "difference in timestamps"
|
||||
# can be negative by:
|
||||
# 1) insisting that the ordering induced by the blockchain and
|
||||
# 2) modeling timestamps as exponentially modified gaussian.
|
||||
# If timestamps are T = X + Z where X is exponentially dist-
|
||||
# ributed with parameter lambda and Z is some Gaussian
|
||||
# noise with average mu and variance sigma2, then we can est-
|
||||
# imate sigma2, mu, and lambda:
|
||||
# mu ~ mean - stdev*(skewness/2)**(1.0/3.0)
|
||||
# sigma2 ~ variance*(1-(skewness/2)**(2.0/3.0))
|
||||
# lambda ~ (1.0/(stdev))*(2/skewness)**(1.0/3.0)
|
||||
assert targetRate==1.0/120.0
|
||||
count = 1200
|
||||
ident = self.data.miningIdent
|
||||
bl = []
|
||||
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
|
||||
parent = self.data.blocks[ident].parent
|
||||
count = count - 1
|
||||
while count > 0 and parent is not NOne:
|
||||
ident = copy.deepcopy(parent)
|
||||
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
|
||||
parent = self.data.blocks[ident].parent
|
||||
count = count-1
|
||||
sk = skew(bl)
|
||||
va = var(bl)
|
||||
stdv = sqrt(va)
|
||||
lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0)
|
||||
self.diff = self.diff*(lam/targetRate)
|
||||
else:
|
||||
print("Error, invalid difficulty mode entered.")
|
||||
|
||||
def propagate(self, t, blockIdent):
|
||||
for edgeIdent in self.edges:
|
||||
e = self.edges[edgeIdent]
|
||||
l = e.data["length"]
|
||||
toa = t + l
|
||||
mIdent = e.getNeighbor(self.ident)
|
||||
m = e.nodes[mIdent]
|
||||
bc = m.data["blockchain"]
|
||||
if blockIdent not in bc.blocks:
|
||||
pB = e.data["pendingBlocks"]
|
||||
pendingIdent = newIdent(len(pB))
|
||||
mybc = self.data["blockchain"]
|
||||
pendingDat = {"timeOfArrival":toa, "destIdent":mIdent, "block":mybc.blocks[blockIdent]}
|
||||
pB.update({pendingIdent:pendingDat})
|
||||
|
||||
|
||||
class Test_Node(unittest.TestCase):
|
||||
def test_node(self):
|
||||
verbose = True
|
||||
nellyIdent = newIdent(0)
|
||||
bill = Blockchain([], verbosity=verbose)
|
||||
|
||||
name = newIdent(0)
|
||||
t = time.time()
|
||||
s = t+1
|
||||
diff = 1.0
|
||||
params = {"ident":name, "disco":t, "arriv":s, "parent":None, "diff":diff}
|
||||
genesis = Block(params)
|
||||
bill.addBlock(genesis)
|
||||
|
||||
time.sleep(10)
|
||||
|
||||
name = newIdent(1)
|
||||
t = time.time()
|
||||
s = t+1
|
||||
diff = 1.0
|
||||
params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff}
|
||||
blockA = Block(params)
|
||||
bill.addBlock(blockA)
|
||||
|
||||
# Nodes need an identity and a blockchain object and verbosity and difficulty
|
||||
nodeData = {"blockchain":bill, "intensity":random.random(), "offset":random.random()}
|
||||
params = {"ident":name, "data":nodeData, "diff":diff, "verbose":verbose}
|
||||
nelly = Node(params)
|
||||
nelly.updateDifficulty(mode="Nakamoto")
|
||||
|
||||
time.sleep(9)
|
||||
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
s = t + 1
|
||||
params = {"ident":name, "disco":t, "arriv":s, "parent":blockA.ident, "diff":diff}
|
||||
blockB = Block(params)
|
||||
nelly.updateBlockchain({blockB.ident:blockB})
|
||||
|
||||
time.sleep(8)
|
||||
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
s = t + 1
|
||||
params = {"ident":name, "disco":t, "arriv":s, "parent":blockA.ident, "diff":diff}
|
||||
blockC = Block(params)
|
||||
nelly.updateBlockchain({blockC.ident:blockC})
|
||||
|
||||
time.sleep(1)
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
s = t + 1
|
||||
params = {"ident":name, "disco":t, "arriv":s, "parent":blockC.ident, "diff":diff}
|
||||
blockD = Block(params)
|
||||
nelly.updateBlockchain({blockD.ident:blockD})
|
||||
|
||||
time.sleep(7)
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
s = t + 1
|
||||
params = {"ident":name, "disco":t, "arriv":s, "parent":blockD.ident, "diff":diff}
|
||||
blockE = Block(params)
|
||||
nelly.updateBlockchain({blockE.ident:blockE})
|
||||
|
||||
|
||||
time.sleep(6)
|
||||
name = newIdent(len(nelly.data))
|
||||
t = time.time()
|
||||
s = t + 1
|
||||
params = {"ident":name, "disco":t, "arriv":s, "parent":blockE.ident, "diff":diff}
|
||||
blockF = Block(params)
|
||||
nelly.updateBlockchain({blockF.ident:blockF})
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Node)
|
||||
unittest.TextTestRunner(verbosity=1).run(suite)
|
||||
|
53
source-code/Poisson-Graphs/StochasticProcess.py
Normal file
53
source-code/Poisson-Graphs/StochasticProcess.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
import unittest, random, time
|
||||
|
||||
class StochasticProcess(object):
|
||||
'''
|
||||
Stochastic processes have a clock and a state.
|
||||
The clock moves forward, and then the state updates.
|
||||
More detail requires knowledge of the underlying stochProc.
|
||||
'''
|
||||
def __init__(self, params=None):
|
||||
# initialize with initial data
|
||||
self.data = params
|
||||
self.t = 0.0 # should always start at t=0.0
|
||||
self.state = 0.0 # magic number
|
||||
self.maxTime = 1000.0 # magic number
|
||||
self.saveFile = "output.csv"
|
||||
self.verbose = True
|
||||
|
||||
def go(self):
|
||||
# Executes stochastic process.
|
||||
assert self.maxTime > 0.0 # Check loop will eventually terminate.
|
||||
t = self.t
|
||||
while t <= self.maxTime:
|
||||
deltaT = self.getNextTime() # Pick the next "time until event" and a description of the event.
|
||||
self.updateState(t, deltaT) # Update state with deltaT input
|
||||
t = self.t
|
||||
if self.verbose:
|
||||
print("Recording...")
|
||||
self.record()
|
||||
|
||||
def getNextTime(self):
|
||||
return 1 # Magic number right now
|
||||
|
||||
def updateState(self, t, deltaT):
|
||||
# Update the state of the system. In this case,
|
||||
# we are doing a random walk on the integers.
|
||||
self.state += random.randrange(-1,2,1) # [-1, 0, 1]
|
||||
self.t += deltaT
|
||||
|
||||
def record(self):
|
||||
with open(self.saveFile,"w") as recordKeeper:
|
||||
line = str(self.t) + ",\t" + str(self.state) + "\n"
|
||||
recordKeeper.write(line)
|
||||
|
||||
class Test_StochasticProcess(unittest.TestCase):
|
||||
def test_sp(self):
|
||||
sally = StochasticProcess()
|
||||
sally.verbose = False
|
||||
sally.go()
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(Test_StochasticProcess)
|
||||
unittest.TextTestRunner(verbosity=1).run(suite)
|
||||
|
||||
|
Loading…
Reference in a new issue