mirror of
https://github.com/monero-project/research-lab.git
synced 2024-12-22 19:49:35 +00:00
commit
235a2e556a
5 changed files with 1539 additions and 0 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)
|
1143
source-code/Poisson-Graphs/Blockchain.py
Normal file
1143
source-code/Poisson-Graphs/Blockchain.py
Normal file
File diff suppressed because it is too large
Load diff
40
source-code/Poisson-Graphs/Edge.py
Normal file
40
source-code/Poisson-Graphs/Edge.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
from Node import *
|
||||||
|
|
||||||
|
class Edge(object):
|
||||||
|
'''
|
||||||
|
Edge object. Has an identity, some data, and a dict of nodes.
|
||||||
|
'''
|
||||||
|
def __init__(self, params):
|
||||||
|
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):
|
||||||
|
nelly = Node(params)
|
||||||
|
milly = Node(params)
|
||||||
|
ed = Edge(params)
|
||||||
|
ed.nodes.update({nelly.ident:nelly, milly.ident:milly})
|
||||||
|
self.assertEqual(len(self.nodes),2)
|
||||||
|
|
||||||
|
|
||||||
|
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Edge)
|
||||||
|
unittest.TextTestRunner(verbosity=1).run(suite)
|
187
source-code/Poisson-Graphs/Graph.py
Normal file
187
source-code/Poisson-Graphs/Graph.py
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
from Blockchain import *
|
||||||
|
from Node import *
|
||||||
|
from Edge import *
|
||||||
|
from copy import *
|
||||||
|
|
||||||
|
def newIntensity(params):
|
||||||
|
x = random.random()
|
||||||
|
return x
|
||||||
|
|
||||||
|
def newOffset(params):
|
||||||
|
x = 2.0*random.random() - 1.0
|
||||||
|
return x
|
||||||
|
|
||||||
|
class Graph(object):
|
||||||
|
'''
|
||||||
|
Explanation
|
||||||
|
'''
|
||||||
|
def __init__(self, params):
|
||||||
|
self.nodes = {}
|
||||||
|
self.edges = {}
|
||||||
|
self.mode = params[0]
|
||||||
|
self.targetRate = params[1]
|
||||||
|
self.numInitNodes = params[2]
|
||||||
|
self.maxNeighbors = params[3]
|
||||||
|
self.probEdge = params[4]
|
||||||
|
self.verbosity = params[5]
|
||||||
|
self.startTime = deepcopy(time.time())
|
||||||
|
self.runTime = params[6]
|
||||||
|
self.globalTime = deepcopy(self.startTime)
|
||||||
|
self.birthRate = params[7]
|
||||||
|
self.deathRate = params[8]
|
||||||
|
self.data = params[9]
|
||||||
|
|
||||||
|
self.blankBlockchain = Blockchain()
|
||||||
|
self.blankBlockchain.targetRate = self.data["targetRate"]
|
||||||
|
self.blankBlockchain.mode = self.data["mode"]
|
||||||
|
|
||||||
|
self._createInit()
|
||||||
|
|
||||||
|
|
||||||
|
def _createInit(self):
|
||||||
|
# For simplicity, all nodes will have a genesis block with t=0.0 and no offset
|
||||||
|
for i in range(self.numInitNodes):
|
||||||
|
offset = newOffset()
|
||||||
|
intens = newIntensity()
|
||||||
|
name = newIdent(len(self.nodes))
|
||||||
|
dat = {"offset":offset, "intensity":intens, "blockchain":deepcopy(self.blankBlockchain)}
|
||||||
|
params = {"ident":name, "data":dat, "verbose":self.verbosity, "mode":self.mode, "targetRate":self.targetRate}
|
||||||
|
nelly = Node(params)
|
||||||
|
self.nodes.update({nelly.ident:nelly})
|
||||||
|
t = self.startTime
|
||||||
|
self.nodes[nelly.ident].generateBlock(t, i)
|
||||||
|
|
||||||
|
touched = {}
|
||||||
|
for xNode in self.nodes:
|
||||||
|
for yNode in self.nodes:
|
||||||
|
notSameNode = (xNode != yNode)
|
||||||
|
xNodeHasRoom = (len(self.nodes[xNode].edges) < self.maxNeighbors)
|
||||||
|
yNodeHasRoom = (len(self.ndoes[yNode].edges) < self.maxNeighbors)
|
||||||
|
xyNotTouched = ((xNode, yNode) not in touched)
|
||||||
|
yxNotTouched = ((yNode, xNode) not in touched)
|
||||||
|
if notSameNode and xNodeHasRoom and yNodeHasRoom and xyNotTouched and yxNotTouched:
|
||||||
|
touched.update({(xNode,yNode):True, (yNode,xNode):True})
|
||||||
|
if random.random() < self.probEdge:
|
||||||
|
params = [newIdent(len(self.edges)), {}, self.verbosity]
|
||||||
|
ed = Edge(params)
|
||||||
|
ed.nodes.update({xNode:self.nodes[xNode], yNode:self.nodes[yNode]})
|
||||||
|
self.edges.update({ed.ident:ed})
|
||||||
|
self.nodes[xNode].edges.update({ed.ident:ed})
|
||||||
|
self.nodes[yNode].edges.update({ed.ident:ed})
|
||||||
|
|
||||||
|
def eventNodeJoins(self, t):
|
||||||
|
# timestamp,nodeJoins,numberNeighbors,neighbor1.ident,edge1.ident,neighbor2.ident,edge2.ident,...,
|
||||||
|
out = ""
|
||||||
|
neighbors = []
|
||||||
|
for xNode in self.nodes:
|
||||||
|
xNodeHasRoom = (len(self.nodes[xNode].edges) < self.maxNeighbors)
|
||||||
|
iStillHasRoom = (len(neighbors) < self.maxNeighbors)
|
||||||
|
if xNodeHasRoom and and iStillHasRoom and random.random() < self.probEdge:
|
||||||
|
neighbors.append(xNode)
|
||||||
|
|
||||||
|
|
||||||
|
newNodeName = newIdent(len(self.nodes))
|
||||||
|
offset = newOffset()
|
||||||
|
intens = newIntensity()
|
||||||
|
dat = {"offset":offset, "intensity":intens, "blockchain":deepcopy(self.blankBlockchain)}
|
||||||
|
params = {"ident":newNodeName, "data":dat, "verbose":self.verbosity, "mode":self.mode, "targetRate":self.targetRate}
|
||||||
|
newNode = Node(params)
|
||||||
|
self.nodes.update({newNode.ident:newNode})
|
||||||
|
self.nodes[newNode.ident].generateBlock(self.startTime, 0)
|
||||||
|
|
||||||
|
out = str(t) + ",nodeJoins," + str(newNode.ident) + "," + str(len(neighbors)) + ","
|
||||||
|
for neighbor in neighbors:
|
||||||
|
out += neighbor + ","
|
||||||
|
params = [newIdent(len(self.edges)), {}, self.verbosity]
|
||||||
|
ed = Edge(params)
|
||||||
|
ed.nodes.update({neighbor:self.nodes[neighbor], newNode.ident:self.nodes[newNode.ident]})
|
||||||
|
out += ed.ident + ","
|
||||||
|
self.edges.update({ed.ident:ed})
|
||||||
|
self.nodes[neighbor].edges.update({ed.ident:ed})
|
||||||
|
self.nodes[newNode.ident].edges.update({ed.ident:ed})
|
||||||
|
return out
|
||||||
|
|
||||||
|
def eventNodeLeaves(self, t):
|
||||||
|
out = str(t) + ",nodeLeaves,"
|
||||||
|
leaverIdent = random.choice(list(self.nodes.keys()))
|
||||||
|
out += str(leaverIdent) + ","
|
||||||
|
leaver = self.nodes[leaverIdent]
|
||||||
|
neighbors = []
|
||||||
|
for ed in leaver.edges:
|
||||||
|
neighbors.append((ed.Ident, ed.getNeighbor(leaverIdent)))
|
||||||
|
for neighbor in neighbors:
|
||||||
|
edIdent = neighbor[0]
|
||||||
|
neiIdent = neighbor[1]
|
||||||
|
del self.nodes[neiIdent].edges[edIdent]
|
||||||
|
del self.edges[edIdent]
|
||||||
|
del self.nodes[leaverIdent]
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def eventBlockDiscovery(self, discoIdent, t):
|
||||||
|
out = str(t) + ",blockDisco," + str(discoIdent) + ","
|
||||||
|
blockIdent = self.nodes[discoIdent].generateBlock(t)
|
||||||
|
out += str(blockIdent)
|
||||||
|
self.nodes[discoIdent].propagate(t, blockIdent)
|
||||||
|
return out
|
||||||
|
|
||||||
|
def eventBlockArrival(self, destNodeIdent, edgeIdent, blockIdent, t):
|
||||||
|
out = str(t) + ",blockArriv," + str(destNodeIdent) + "," + str(edgeIdent) + "," + str(blockIdent) + ","
|
||||||
|
destNode = self.nodes[destNodeIdent]
|
||||||
|
edge = self.edges[edgeIdent]
|
||||||
|
block = deepcopy(edge.data["pendingBlocks"][blockIdent])
|
||||||
|
block.arrivTimestamp = t + self.nodes[destNodeIdent].data["offset"]
|
||||||
|
self.nodes[destNodeIdent].updateBlockchain({blockIdent:block})
|
||||||
|
return out
|
||||||
|
|
||||||
|
def go(self):
|
||||||
|
with open(self.filename,"w") as writeFile:
|
||||||
|
writeFile.write("timestamp,eventId,eventData\n")
|
||||||
|
|
||||||
|
while self.globalTime - self.startTime< self.runTime:
|
||||||
|
u = -1.0*math.log(1.0-random.random())/self.birthRate
|
||||||
|
eventType = ("nodeJoins", None)
|
||||||
|
|
||||||
|
v = -1.0*math.log(1.0-random.random())/self.deathRate
|
||||||
|
if v < u:
|
||||||
|
eventType = ("nodeLeaves", None)
|
||||||
|
u = v
|
||||||
|
|
||||||
|
for nodeIdent in self.nodes:
|
||||||
|
localBlockDiscoRate = self.nodes[nodeIdent].data["intensity"]/self.nodes[nodeIdent].data["blockchain"].diff
|
||||||
|
v = -1.0*math.log(1.0-random.random())/localBlockDiscoRate
|
||||||
|
if v < u:
|
||||||
|
eventType = ("blockDisco", nodeIdent)
|
||||||
|
u = v
|
||||||
|
|
||||||
|
for edgeIdent in self.edges:
|
||||||
|
edge = self.edges[edgeIdent]
|
||||||
|
pB = edge.data["pendingBlocks"]
|
||||||
|
for pendingIdent in pB:
|
||||||
|
pendingData = pB[pendingIdent] # pendingDat = {"timeOfArrival":timeOfArrival, "destIdent":otherIdent, "block":blockToProp}
|
||||||
|
if pendingData["timeOfArrival"] - self.globalTime < u:
|
||||||
|
eventTime = ("blockArriv", (pendingData["destIdent"], edgeIdent, pendingData["block"]))
|
||||||
|
u = v
|
||||||
|
|
||||||
|
self.globalTime += u
|
||||||
|
out = ""
|
||||||
|
if eventTime[0] == "nodeJoins":
|
||||||
|
out = self.eventNodeJoins(self.globalTime)
|
||||||
|
elif eventTime[0] == "nodeLeaves":
|
||||||
|
out = self.eventNodeLeaves(self.globalTime)
|
||||||
|
elif eventTime[0] == "blockDisco":
|
||||||
|
out = self.eventBlockDiscovery(eventTime[1], self.globalTime)
|
||||||
|
elif eventTime[0] == "blockArriv":
|
||||||
|
out = self.eventBlockArrival(eventTime[1], eventTime[2], eventTime[3], self.globalTime)
|
||||||
|
else:
|
||||||
|
print("WHAAAA")
|
||||||
|
|
||||||
|
with open(self.filename, "a") as writeFile:
|
||||||
|
writeFile.write(out + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
123
source-code/Poisson-Graphs/Node.py
Normal file
123
source-code/Poisson-Graphs/Node.py
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
from Blockchain import *
|
||||||
|
from copy import *
|
||||||
|
|
||||||
|
class Node(object):
|
||||||
|
'''
|
||||||
|
Node object. params [identity, blockchain (data), verbosity, difficulty]
|
||||||
|
'''
|
||||||
|
def __init__(self, params={}):
|
||||||
|
self.ident = None
|
||||||
|
self.data = {}
|
||||||
|
self.verbose = None
|
||||||
|
self.edges = {}
|
||||||
|
self.mode = None
|
||||||
|
self.targetRate = None
|
||||||
|
try:
|
||||||
|
assert len(params)==5
|
||||||
|
except AssertionError:
|
||||||
|
print("Error, Tried to create malformed node.")
|
||||||
|
else:
|
||||||
|
self.ident = params["ident"]
|
||||||
|
self.data = params["data"]
|
||||||
|
self.verbose = params["verbose"]
|
||||||
|
self.edges = {}
|
||||||
|
self.mode = params["mode"]
|
||||||
|
self.targetRate = params["targetRate"]
|
||||||
|
|
||||||
|
def generateBlock(self, discoTime):
|
||||||
|
newName = newIdent(len(self.data["blockchain"].blocks))
|
||||||
|
t = discoTime
|
||||||
|
s = t+self.data["offset"]
|
||||||
|
diff = self.data["blockchain"].diff
|
||||||
|
params = {"ident":name, "disco":t, "arriv":s, "parent":None, "diff":diff}
|
||||||
|
newBlock = Block(params)
|
||||||
|
self.data["blockchain"].addBlock(newBlock, mode, tr)
|
||||||
|
return newName
|
||||||
|
|
||||||
|
def updateBlockchain(self, incBlocks):
|
||||||
|
# incBlocks 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.
|
||||||
|
|
||||||
|
tempData = deepcopy(incBlocks)
|
||||||
|
for key in incBlocks:
|
||||||
|
if key in self.data["blockchain"].blocks:
|
||||||
|
del tempData[key]
|
||||||
|
elif incBlocks[key].parent in self.data["blockchain"].blocks or incBlocks[key].parent is None:
|
||||||
|
self.data["blockchain"].addBlock(incBlocks[key], self.mode, self.targetRate)
|
||||||
|
del tempData[key]
|
||||||
|
incBlocks = deepcopy(tempData)
|
||||||
|
while len(incBlocks)>0:
|
||||||
|
for key in incBlocks:
|
||||||
|
if key in self.data["blockchain"].blocks:
|
||||||
|
del tempData[key]
|
||||||
|
elif incBlocks[key].parent in self.data["blockchain"].blocks:
|
||||||
|
self.data["blockchain"].addBlock(incBlocks[key], self.mode, self.targetRate)
|
||||||
|
del tempData[key]
|
||||||
|
incBlocks = deepcopy(tempData)
|
||||||
|
|
||||||
|
def propagate(self, timeOfProp, blockIdent):
|
||||||
|
for edgeIdent in self.edges:
|
||||||
|
edge = self.edges[edgeIdent]
|
||||||
|
length = e.data["length"]
|
||||||
|
timeOfArrival = timeOfProp + length
|
||||||
|
otherIdent = e.getNeighbor(self.ident)
|
||||||
|
other = e.nodes[otherIdent]
|
||||||
|
bc = other.data["blockchain"]
|
||||||
|
if blockIdent not in bc.blocks:
|
||||||
|
pB = e.data["pendingBlocks"]
|
||||||
|
pendingIdent = newIdent(len(pB))
|
||||||
|
mybc = self.data["blockchain"]
|
||||||
|
blockToProp = mybc.blocks[blockIdent]
|
||||||
|
pendingDat = {"timeOfArrival":timeOfArrival, "destIdent":otherIdent, "block":blockToProp}
|
||||||
|
pB.update({pendingIdent:pendingDat})
|
||||||
|
|
||||||
|
|
||||||
|
class Test_Node(unittest.TestCase):
|
||||||
|
# TODO test each method separately
|
||||||
|
def test_all(self):
|
||||||
|
bill = Blockchain([], verbosity=True)
|
||||||
|
mode="Nakamoto"
|
||||||
|
tr = 1.0/600000.0
|
||||||
|
deltaT = 600000.0
|
||||||
|
bill.targetRate = tr
|
||||||
|
|
||||||
|
name = newIdent(0)
|
||||||
|
t = 0.0
|
||||||
|
s = t
|
||||||
|
diff = 1.0
|
||||||
|
params = {"ident":name, "disco":t, "arriv":s, "parent":None, "diff":diff}
|
||||||
|
genesis = Block(params)
|
||||||
|
bill.addBlock(genesis, mode, tr)
|
||||||
|
|
||||||
|
parent = genesis.ident
|
||||||
|
|
||||||
|
nellyname = newIdent(time.time())
|
||||||
|
mode = "Nakamoto"
|
||||||
|
targetRate = 1.0/600000.0
|
||||||
|
params = {"ident":nellyname, "data":{"offset":0.0, "intensity":1.0, "blockchain":bill}, "verbose":True, "mode":mode, "targetRate":targetRate}
|
||||||
|
nelly = Node(params)
|
||||||
|
|
||||||
|
while len(nelly.data["blockchain"].blocks) < 2015:
|
||||||
|
name = newIdent(len(nelly.data["blockchain"].blocks))
|
||||||
|
diff = nelly.data["blockchain"].diff
|
||||||
|
t += deltaT*diff*(2.0*random.random()-1.0)
|
||||||
|
s = t
|
||||||
|
params = {"ident":name, "disco":t, "arriv":s, "parent":parent, "diff":diff}
|
||||||
|
newBlock = Block(params)
|
||||||
|
nelly.updateBlockchain({newBlock.ident:newBlock})
|
||||||
|
parent = name
|
||||||
|
|
||||||
|
|
||||||
|
while len(nelly.data["blockchain"].blocks) < 5000:
|
||||||
|
name = newIdent(len(nelly.data["blockchain"].blocks))
|
||||||
|
diff = nelly.data["blockchain"].diff
|
||||||
|
t += deltaT*diff
|
||||||
|
s = t
|
||||||
|
params = {"ident":name, "disco":t, "arriv":s, "parent":parent, "diff":diff}
|
||||||
|
newBlock = Block(params)
|
||||||
|
nelly.updateBlockchain({newBlock.ident:newBlock})
|
||||||
|
parent = name
|
||||||
|
|
||||||
|
#suite = unittest.TestLoader().loadTestsFromTestCase(Test_Node)
|
||||||
|
#unittest.TextTestRunner(verbosity=1).run(suite)
|
||||||
|
|
Loading…
Reference in a new issue