mirror of
https://github.com/monero-project/research-lab.git
synced 2024-12-22 19:49:35 +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,536 +36,6 @@ def newOffset(params):
|
||||||
totalOffset = 60.0*60.0*float(x[0][0] - x[1][0]) + float((x[0][1] - x[1][1]))
|
totalOffset = 60.0*60.0*float(x[0][0] - x[1][0]) + float((x[0][1] - x[1][1]))
|
||||||
return totalOffset
|
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):
|
class FishGraph(StochasticProcess):
|
||||||
'''
|
'''
|
||||||
|
@ -703,7 +173,7 @@ class FishGraph(StochasticProcess):
|
||||||
mIdent = e.getNeighbor(n.ident)
|
mIdent = e.getNeighbor(n.ident)
|
||||||
m = self.state.nodes[mIdent]
|
m = self.state.nodes[mIdent]
|
||||||
mdata = m.data["blockchain"]
|
mdata = m.data["blockchain"]
|
||||||
n.data["blockchain"].update(mdata)
|
n.updateBlockchain(mdata)
|
||||||
y = len(self.state.nodes)
|
y = len(self.state.nodes)
|
||||||
assert y == x + 1
|
assert y == x + 1
|
||||||
shout += str(y) + "\n"
|
shout += str(y) + "\n"
|
||||||
|
@ -712,27 +182,67 @@ class FishGraph(StochasticProcess):
|
||||||
elif len(eventTag)==2:
|
elif len(eventTag)==2:
|
||||||
# Block is discovered and plunked into each edge's pendingBlock list.
|
# Block is discovered and plunked into each edge's pendingBlock list.
|
||||||
|
|
||||||
shout += "DISCOVERY"
|
shout += "DISCOVERY\n"
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(shout)
|
print(shout)
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print("Checking formation of eventTag = [\"discovery\", nodeIdent]")
|
||||||
assert eventTag[0]=="discovery"
|
assert eventTag[0]=="discovery"
|
||||||
assert eventTag[1] in self.state.nodes
|
assert eventTag[1] in self.state.nodes
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print("Retrieving discoverer's identity")
|
||||||
nIdent = eventTag[1] # get founding node's identity
|
nIdent = eventTag[1] # get founding node's identity
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print("Retrieving discoverer")
|
||||||
n = self.state.nodes[nIdent] # get founding node
|
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
|
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
|
newBlockIdent = newIdent(len(n.data["blockchain"].blocks)) # generate new identity
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print("Setting timestamps")
|
||||||
disco = s
|
disco = s
|
||||||
arriv = s
|
arriv = s
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print("Retrieving parent")
|
||||||
parent = n.data["blockchain"].miningIdent
|
parent = n.data["blockchain"].miningIdent
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print("getting difficulty")
|
||||||
diff = copy.deepcopy(n.diff)
|
diff = copy.deepcopy(n.diff)
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print("setting verbosity")
|
||||||
verbosity = self.verbose
|
verbosity = self.verbose
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print("Initializing a new block")
|
||||||
newBlock = Block([newBlockIdent, disco, arriv, parent, diff, verbosity])
|
newBlock = Block([newBlockIdent, disco, arriv, parent, diff, verbosity])
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print("Updating discovering node's blockchain")
|
||||||
n.updateBlockchain({newBlockIdent:newBlock})
|
n.updateBlockchain({newBlockIdent:newBlock})
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print("Computing discoverer's new difficulty")
|
||||||
n.updateDifficulty(mode, targetRate)
|
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:
|
elif len(eventTag)==3:
|
||||||
#eventTag = ("arrival", e.ident, pendingIdent)
|
#eventTag = ("arrival", e.ident, pendingIdent)
|
||||||
|
@ -755,9 +265,9 @@ class FishGraph(StochasticProcess):
|
||||||
arriv = self.t + u + receiver.data["offset"]
|
arriv = self.t + u + receiver.data["offset"]
|
||||||
newBlock = arrivalInfo["block"]
|
newBlock = arrivalInfo["block"]
|
||||||
newBlock.arrivTimestamp = copy.deepcopy(arriv)
|
newBlock.arrivTimestamp = copy.deepcopy(arriv)
|
||||||
receiver.data["blockchain"].updateBlockchain({newBlock.ident:newBlock})
|
receiver.updateBlockchain({newBlock.ident:newBlock})
|
||||||
receiver.updateDifficulty(mode, targetRate)
|
receiver.updateDifficulty(mode, targetRate)
|
||||||
receiver.propagate(newBlock.ident)
|
receiver.propagate(self.t, newBlock.ident)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("Error: eventTag was not a string, or not an array length 2 or 3. In fact, we have eventTag = ", eventTag)
|
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):
|
class Test_FishGraph(unittest.TestCase):
|
||||||
def test_fishGraph(self):
|
def test_fishGraph(self):
|
||||||
for i in range(10):
|
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 = FishGraph(params, verbosity=True)
|
||||||
greg.go()
|
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