research-lab/source-code/Poisson-Graphs/Node.py
2018-02-01 17:36:30 -07:00

271 lines
11 KiB
Python

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)