From 939b597edb6cf0b09f6929d24b3973b68dad99bc Mon Sep 17 00:00:00 2001 From: Brandon Goodell Date: Mon, 15 Jan 2018 22:21:35 +0100 Subject: [PATCH 01/13] Poisson process on graph for blockchain sims COMPLETELY untested code. Does the following: Create a random graph with a specified number of vertices. Vertices are nodes on a cryptocurrency network, edges are their p2p connections. Node additions occur in a nonhomogeneous Poisson process with a (piecewise) constant intensity function. Node deletions occur in a homogeneous Poisson process. Block discovery occurs at each node in a nonhomogeneous Poisson process with a (piecewise) constant intensity function. Blocks, upon discovery, are propagated to neighbors along edges with a random (unique for each edge) delay. Blocks arrive at the end of edges in a deterministic way. Currently, I'm measuring difficulty in the most simple way: use the MLE estimate for a homogeneous Poisson process's intensity function. TODO: 1) Plot block arrivals, true network hash rate, and the difficulty score of some node (or maybe the average difficulty score) over time. 2) Gather statistics on the above. 3) Different difficulty metrics 4) Different birthrate functions 5) Oh, I should compile this at some point? I bet it doesn't even work yet. --- source-code/Simulator/Simulator.py | 275 +++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 source-code/Simulator/Simulator.py diff --git a/source-code/Simulator/Simulator.py b/source-code/Simulator/Simulator.py new file mode 100644 index 0000000..8978cd1 --- /dev/null +++ b/source-code/Simulator/Simulator.py @@ -0,0 +1,275 @@ +import copy, hashlib, time + +class Event(object): + ''' Generalized event object ''' + def __init__(self,params): + self.data = {} + self.timeOfEvent = None + self.eventType = None + + +class Block(object): + ''' Block object, very simple... has an identity and a timestamp''' + def __init__(self, params): + self.ident = params[0] + self.timestamp = params[1] + +class Node(object): + ''' + Node object, represents a computer on the network. + Has an identity, a dict (?) of edges, a time offset on [-1,1] + representing how inaccurate the node's wall clock appears to be, + an intensity representing the node's hash rate, a blockchain + which is merely a list of block objects (ordered by their arrival + at the node, for simplicity), and a difficulty score (which is + a function of the blockchain) + ''' + def __init__(self, params): + self.ident = params[0] # string + self.edges = params[1] # dict of edges + self.timeOffset = params[2] # float + self.intensity = params[3] # float (positive) + self.difficulty = params[4] + self.blockchain = [] + + def makeBlock(self, t): + ts = t + self.timeOffset + newBlock = Block() + salt = random.random() + n = len(self.blockchain) + x = hash(str(n) + str(salt)) + newBlock.ident = x + newBlock.timestamp = ts + self.blockchain.append(newBlock) + self.computeDifficulty() + return newBlock + + # node object needs receiveBlock(block) method + def receiveBlock(self, blockToReceive): + self.blockchain.append(blockToReceive) + + def computeDifficulty(self, target, sampleSize): + N = min(sampleSize,len(self.blockchain)) + tempSum = 0.0 + for i in range(N): + tempSum += abs(self.blockchain[-i].timestamp - self.blockchain[-i-1].timestamp) + tempSum = float(tempSum)/float(N) # Average absolute time difference of last N blocks + lambdaMLE = 1.0/tempSum + self.difficulty = float(lambdaMLE/target)*self.difficulty + +class Edge(object): + ''' + Edge object representing the connection between one node and another. + Has two node objects, a and b, a length l, and a dictionary of pending blocks. + ''' + def __init__(self, params): + self.ident = params[0] # edge ident + self.a = params[1] # node ident of one incident node + self.b = params[2] # node ident of the other incident node (may be None when creating new blocks?) + self.l = params[3] # length of edge as measured in propagation time. + self.pendingBlocks = {} # blockIdent:(block, destination node ident, deterministic time of arrival) + + def addBlock(self, blockToAdd, blockFinderIdent, curTime): + # Include new block to self.pendingBlocks + timeOfArrival = curTime + self.l + if blockFinderIdent == self.a.ident: + self.pendingBlocks.update({blockToAdd.ident:(blockToAdd, self.b.ident, timeOfArrival)}) + elif blockFinderIdent == self.b.ident: + self.pendingBlocks.update({blockToAdd.ident:(blockToAdd, self.a.ident, timeOfArrival)}) + else: + print("fish sticks.") + + +class Network(object): + ''' + Network object consisting of a number of vertices, a probability that any pair of vertices + has an edge between them, a death rate of vertices, a dictionary of vertices, a dictionary + of edges, and a clock t. + ''' + def __init__(self, params): + self.numVertices = params[0] # integer + self.probOfEdge = params[1] # float (between 0.0 and 1.0) + self.deathRate = params[2] # float 1.0/(avg vertex lifespan) + self.vertices = {} # Dict with keys=node idents and values=nodes + self.edges = {} # Dict with keys=edge idents and values=edges + self.t = 0.0 + self.defaultNodeLength = 30.0 # milliseconds + self.initialize() + + def initialize(self): + # Generate self.numVertices new nodes with probability self.probOfEdge + # that any pair are incident. Discard any disconnected nodes (unless + # there is only one node) + try: + assert self.numVertices > 1 + except AssertionError: + print("Fish sticks... AGAIN! Come ON, fellas!") + + count = self.numVertices - 1 + + e = Event() + e.eventType = "node birth" + e.timeOfEvent = 0.0 + e.data = {"neighbors":[]} + self.birthNode(e) + + while count > 0: + count -= 1 + e.eventType = "node birth" + e.timeOfEvent = 0.0 + e.data = {"neighbors":[]} + for x in self.vertices: + u = random.random() + if u < self.probOfEdge: + e.data["neighbors"].append(x) + self.birthNode(e) + + def run(self, maxTime, birthrate=lambda x:math.exp(-(x-10.0)**2.0)): + # Run the simulation for maxTime and birthrate function (of time) + while self.t < maxTime: + if type(birthrate) is float: # We may pass in a constant birthrate + birthrate = lambda x:birthrate # but we want it treated as a function + e = self.nextEvent(birthrate) # Generate next event. + try: + assert e is not None + except AssertionError: + print("Got null event in run, bailing...") + break + self.t = e.timeOfEvent # Get time until next event. + self.execute(e) # Run the execute method + + def nextEvent(self, birthrate, t): + # The whole network experiences stochastic birth and death as a Poisson + # process, each node experiences stochastic block discovery as a (non- + # homogeneous) Poisson process. Betwixt these arrivals, other + # deterministic events occur as blocks are propagated along edges, + # changing the (local) block discovery rates. + + # Birth of node? + u = random.random() + u = math.ln(1.0-u)/birthrate(t) + e = Event() + e.eventType = "node birth" + e.timeOfEvent = self.t + u + e.data = {"neighbors":[]} + for x in self.vertices: + u = random.random() + if u < self.probOfEdge: + e.data["neighbors"].append(x) + + for x in self.vertices: + u = random.random() + u = math.ln(1.0 - u)/self.deathRate + tempTime = self.t + u + if tempTime < e.timeOfEvent: + e.eventType = "node death" + e.timeOfEvent = tempTime + e.data = {"identToKill":x} + + u = random.random() + localIntensity = self.vertices[x].intensity/self.vertices[x].difficulty + u = math.ln(1.0 - u)/localIntensity + tempTime = self.t + u + if tempTime < e.timeOfEvent: + e.eventType = "block found" + e.timeOfEvent = tempTime + e.data = {"blockFinderIdent":x} + + for edgeIdent in self.vertices[x].edges: + for pendingBlockIdent in self.edges[edgeIdent]: + timeOfBlockArrival = self.edges[edgeIdent].pendingBlocks[pendingBlockIdent][2] + if timeOfBlockArrival < e.timeOfEvent: + e.eventType = "block propagated" + e.timeOfEvent = timeOfBlockArrival + e.data = {"propEdgeIdent":edgeIdent, "pendingBlockIdent":blockIdent} + return e + + def execute(self, e): + # Take an event e as input. Depending on eventType of e, execute + # the correspondsing method. + if e.eventType == "node birth": + self.birthNode(e) + elif e.eventType == "node death": + self.killNode(e) + elif e.eventType == "block found": + self.foundBlock(e) + elif e.eventType == "block propagated": + self.propBlock(e) + + def birthNode(self, e): + # In this event, a new node is added to the network and edges are randomly decided upon. + # I will probably limit the number of peers for a new node eventually: a fixed probability + # of even 1% of edges per pair of nodes, in a graph with, say 1000 nodes, will see 10 peers + # per node... + # Also, I was kind of thinking this code should probably run with less than 50 nodes at a time, in general, + # otherwise the simulation needs to be highly optimized to make for reasonable simulation times. + + newNodeIdent = hash(str(len(self.vertices)) + str(random.random())) # Pick a new random node ident + newOffset = 2.0*random.random() - 1.0 # New time offset for the new node + newIntensity = random.random() # New node hash rate + newDifficulty = 1.0 # Dummy variable, will be replaced + + newbc = [] # This will be the union of the blockchains of all neighbors in e.data["neighbors"] + count = 0 # This will be a nonce to be combined with a salt for a unique identifier + newEdges = {} + for neighborIdent in e.data["neighbors"]: + newbc += [z for z in self.vertices[neighborIdent].blockchain if z not in newbc] + newEdgeIdent = hash(str(count) + str(random.random())) + count += 1 + newLength = random.random()*self.defaultNodeLength + otherSide = random.choice(self.vertices.keys()) + newEdge = Edge([newEdgeIdent, newNodeIdent, otherSide, newLength]) + newEdges.update({newEdgeIdent:newEdge}) + + params = [newNodeIdent, newEdges, newOffset, newIntensity, newDifficulty] + newNode = Node(params) # Create new node + newNode.blockchain = newbc # Store new blockchain + newNode.computeDifficulty() # Compute new node's difficulty + + self.vertices.update({newIdent:newNode}) # Add new node to self.vertices + self.edges.update(newEdges) # Add all new edges to self.edges + + def killNode(self, e): + # Remove node and all incident edges + nodeIdentToKill = e.data["identToKill"] + #edgesToKill = e.data["edgesToKill"] + for edgeIdentToKill in self.vertices[nodeIdentToKill].edges: + del self.edges[edgeIdentToKill] + del self.vertices[nodeIdentToKill] + + def foundBlock(self, e): + # In this instance, a node found a new block. + blockFinderIdent = e.data["blockFinderIdent"] # get identity of node that found the block. + blockFound = self.vertices[blockFinderIdent].makeBlock() # Nodes need a makeBlock method + for nextEdge in self.vertices[blockFinderIdent].edges: # propagate to edges + self.edges[nextEdge].addBlock(blockFound, blockFinderIdent, self.t) # Edges need an addBlock method + + def propBlock(self, e): + # In this instance, a block on an edge is plunked onto its destination node and then + # propagated to the resulting edges. + propEdgeIdent = e.data["propEdgeIdent"] # get the identity of the edge along which the block was propagating + blockToPropIdent = e.data["blockIdent"] # get the identity of the block beign propagated + # Get the block being propagated and the node identity of the receiver. + (blockToAdd, destIdent) = self.edges[propEdgeIdent].pendingBlocks[blockToPropIdent] + self.vertices[destIdent].receiveBlock(blockToAdd) # Call receiveBlock from the destination node. + del self.edges[propEdgeIdent].pendingBlocks[blockToPropIdent] # Now that the block has been received + for nextEdge in self.vertices[destIdent].edges: + if nextEdge != propEdgeIdent: + if self.edges[nextEdge].a.ident == destIdent: + otherSideIdent = self.edges[nextEdge].b.ident + elif self.edges[nextEdge].b.ident == destIdent: + otherSideIdent = self.edges[nextEdge].a.ident + else: + print("awww fish sticks, fellas") + + if blockToAdd.ident not in self.vertices[otherSideIdent].blockchain: + self.edges[nextEdge].addBlock(blockToAdd, destIdent, self.t) + +class Simulator(object): + def __init__(self, params): + #todo lol cryptonote style + pass + + def go(self): + nelly = Network([43, 0.25, 1.0/150.0]) + self.run(maxTime, birthrate=lambda x:math.exp((-(x-500.0)**2.0))/(2.0*150.0)) From 868ac2680f955f8616b975b0b61a86751a614fcb Mon Sep 17 00:00:00 2001 From: Brandon Goodell Date: Mon, 22 Jan 2018 22:59:06 -0700 Subject: [PATCH 02/13] Added FishGraph.py --- source-code/Poisson-Graphs/FishGraph.py | 265 ++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 source-code/Poisson-Graphs/FishGraph.py diff --git a/source-code/Poisson-Graphs/FishGraph.py b/source-code/Poisson-Graphs/FishGraph.py new file mode 100644 index 0000000..2e15b49 --- /dev/null +++ b/source-code/Poisson-Graphs/FishGraph.py @@ -0,0 +1,265 @@ +import unittest, copy, random, math + +class stochasticProcess(object): + def __init__(self, params=None): + self.data = params + self.t = 0.0 + self.state = 0.0 + self.maxTime = 1000.0 + + def go(self): + assert self.maxTime > 0.0 + while self.t <= self.maxTime: + deltaT = self.getNextTime() + self.updateState(deltaT) + #print(str(self.t) + ", " + str(self.state)) + + def getNextTime(self): + return 1 + + def updateState(self, deltaT): + 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 Node(object): + def __init__(self, params=["", {}]): + self.ident = params[0] + self.data = params[1] + self.edges = {} + +class Edge(object): + def __init__(self, params=["", {}]): + self.ident = params[0] + self.data = params[1] + self.nodes = {} + + def getNeighbor(self, nodeIdent): + result = nodeIdent in self.nodes + if result: + for otherIdent in self.nodes: + if otherIdent != nodeIdent: + result = otherIdent + return result + +class Graph(object): + def __init__(self, params={}): + self.data=params + self.nodes = {} + self.edges = {} + def newIdent(self, nonce): + return hash(str(nonce) + str(random.random())) + def createGraph(self, numNodes, probEdge, maxNeighbors): + self.data.update({"numNodes":numNodes, "probEdge":probEdge, "maxNeighbors":maxNeighbors}) + for i in range(numNodes): + nIdent = self.newIdent(i) + n = Node([nIdent,{}]) + self.nodes.update({n.ident:n}) + touched = {} + for nIdent in self.nodes: + n = self.nodes[nIdent] + for mIdent in self.nodes: + m = self.nodes[mIdent] + notSameNode = (nIdent != mIdent) + nOpenSlots = (len(n.edges) < self.data["maxNeighbors"]) + mOpenSlots = (len(m.edges) < self.data["maxNeighbors"]) + untouched = ((nIdent, mIdent) not in touched) + dehcuotnu = ((mIdent, nIdent) not in touched) + if notSameNode and nOpenSlots and mOpenSlots and untouched and dehcuotnu: + touched.update({(nIdent,mIdent):True, (mIdent,nIdent):True}) + if random.random() < self.data["probEdge"]: + nonce = len(self.edges) + e = Edge([self.newIdent(nonce),{"length":random.random(), "pendingBlocks":[]}]) + 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): + n = Node([self.newIdent(len(self.nodes)), {}]) + self.nodes.update({n.ident:n}) + for mIdent in self.nodes: + 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":[]}]) + 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): + edgesToDelete = self.nodes[ident].edges + for edgeIdent in edgesToDelete: + if edgeIdent in self.edges: + del self.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) + for edge in greg.edges: + print(greg.edges[edge].ident, "\t", [greg.edges[edge].nodes[n].ident for n in greg.edges[edge].nodes], "\n") + 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): + def __init__(self, params=None): + self.data = params + self.t = 0.0 + self.state = Graph() + self.state.createGraph(self.data["numNodes"], self.data["probEdge"], self.data["maxNeighbors"]) + for nIdent in self.state.nodes: + n = self.state.nodes[nIdent] + difficulty = 10000.0 + intensity = random.random()/difficulty + offset = 2.0*random.random() - 1.0 + n.data.update({"intensity":intensity, "offset":offset, "blockchain":{}}) + for eIdent in self.state.edges: + e = self.state.edges[eIdent] + e.data.update({"pendingBlocks":[]}) + self.maxTime = self.data["maxTime"] + + def go(self): + assert self.maxTime > 0.0 + while self.t <= self.maxTime and len(self.state.nodes) > 0: + deltaT = self.getNextTime() + self.updateState(deltaT) + #print(str(self.t) + ", " + str(self.state)) + + def getNextTime(self): + eventTag = None + + u = copy.deepcopy(random.random()) + u = -1.0*math.log(copy.deepcopy(u))/self.data["birthRate"] # Time until next stochastic birth + eventTag = "birth" + + v = copy.deepcopy(random.random()) + v = -1.0*math.log(copy.deepcopy(v))/self.data["deathRate"] # Time until next stochastic death + if v < u: + u = copy.deepcopy(v) + eventTag = "death" + + for nIdent in self.state.nodes: + n = self.state.nodes[nIdent] # n.ident = nIdent + v = copy.deepcopy(random.random()) + v = -1.0*math.log(copy.deepcopy(v))/n.data["intensity"] + if v < u: + u = copy.deepcopy(v) + eventTag = ["discovery", n.ident] + + for eIdent in self.state.edges: + e = self.state.edges[eIdent] # e.ident = eIdent + bufferedBlocks = e.data["pendingBlocks"] + if len(bufferedBlocks) > 0: + for pendingIdent in bufferedBlocks: + pB = bufferedBlocks[pendingIdent] + v = pB["timeOfArrival"] + if v < u: + u = copy.deepcopy(v) + eventTag = ["arrival", e.ident, pendingIdent] + + deltaT = (u, eventTag) + # eventTag = ["arrival", e.ident, pendingIdent] + # eventTag = ["discovery", n.ident] + # eventTag = "death" + # eventTag = "birth" + return deltaT + + def updateState(self, deltaT): + u = deltaT[0] + eventTag = deltaT[1] + + if type(eventTag)==type("birthordeath"): + if eventTag == "death": + # Picks random nodeIdent and kills it + toDie = random.choice(list(self.state.nodes.keys())) + print("DEATH EVENT:") + print("Pre-death population = ", len(self.state.nodes)) + self.state.delNode(toDie) + print("Post-death population = ", len(self.state.nodes)) + print("Done. \n") + + elif eventTag == "birth": + # Adds node with some randomly determined edges + print("BIRTH EVENT:") + print("Pre-birth population = ", len(self.state.nodes)) + nIdent = self.state.addNode() + print("Post-death population = ", len(self.state.nodes)) + n = self.state.nodes[nIdent] + intensity = random.random()/10000.0 + offset = 2.0*random.random() - 1.0 + n.data.update({"intensity":intensity, "offset":offset, "blockchain":{}}) + # Auto syncs new node. + print("Auto syncing new node...") + for eIdent in n.edges: + e = n.edges[eIdent] + e.data.update({"pendingBlocks":[]}) + mIdent = e.getNeighbor(n.ident) + m = self.state.nodes[mIdent] + mdata = m.data["blockchain"] + n.data["blockchain"].update(mdata) + print("Done. \n") + else: + print("Error: eventTag had length 1 but was neighter a birth or a death. We had ", eventTag) + elif len(eventTag)==2: + assert eventTag[0]=="discovery" + nIdent = eventTag[1] + n = self.state.nodes[n] + s = self.t + n.data["offset"] + n.data["blockchain"].append(s) + for edgeIdent in n.edges: + e = n.edges[edgeIdent] + l = e.data["length"] + toa = self.t + l + mIdent = e.getNeighbor(n.ident) + e.data["pendingBlocks"].append({"timestamp":s, "timeOfArrival":toa, "destIdent":mIdent}) + print("Block Discovery event. To be coded. For now, blah blah.") + elif len(eventTag)==3: + assert eventTag[0]=="arrival" + print("Block Arrival event. To be coded. For now, blah blah.") + else: + print("Error: eventTag was not a string, or not an array length 2 or 3. In fact, we have eventTag = ", eventTag) + + + +class Test_FishGraph(unittest.TestCase): + def test_fishGraph(self): + params = {"numNodes":10, "probEdge":0.5, "maxNeighbors":10, "maxTime":100.0, "birthRate":1.1, "deathRate":0.2} + greg = FishGraph(params) + greg.go() + + +suite = unittest.TestLoader().loadTestsFromTestCase(Test_FishGraph) +unittest.TextTestRunner(verbosity=1).run(suite) + + From 4593f2b8ea951501a72fd5f5dbe864791ac12e54 Mon Sep 17 00:00:00 2001 From: Brandon Goodell Date: Sun, 28 Jan 2018 12:16:13 -0700 Subject: [PATCH 03/13] Failing loudly and often. --- source-code/Poisson-Graphs/FishGraph.py | 746 ++++++++++++++++++++---- 1 file changed, 639 insertions(+), 107 deletions(-) diff --git a/source-code/Poisson-Graphs/FishGraph.py b/source-code/Poisson-Graphs/FishGraph.py index 2e15b49..0d1e4f5 100644 --- a/source-code/Poisson-Graphs/FishGraph.py +++ b/source-code/Poisson-Graphs/FishGraph.py @@ -1,98 +1,537 @@ -import unittest, copy, random, math +import unittest, copy, random, math, time +from scipy.stats import skew +from numpy import var +from numpy import random as nprandom -class stochasticProcess(object): +#TODO: Node.data["blockchain"] != node.data + +def newIdent(params): + nonce = params + # Generate new random identity. + return hash(str(nonce) + str(random.random())) + +def newIntensity(params): + mode = params + if mode=="uniform": + return random.random() + +def newOffset(params): + mode = params + if mode=="unifDST": + r = 2.0*random.random() - 1.0 # hours + r = 60.0*60.0*r #60 min/hr, 60 sec/min + return r + if mode=="sumOfSkellams": + # This mode uses a skellam distribution, which is + # the difference of two poisson-distributed random + # variables. + # HourOffset = skellam + # SecondOffset = skellam + # TotalOffset = 60*60*HourOffset + 60*MinuteOffset + SecondOffset + # Each skellam = poisson(1) - poisson(1) + # Reasoning: We consider most computers' local time offset from UTC + # to be a two time-scale random variable, one on the hour scale and one on + # the second scale. We make + x = nprandom.poisson(1, (2,2)) + 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 - self.state = 0.0 - self.maxTime = 1000.0 + 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): - assert self.maxTime > 0.0 - while self.t <= self.maxTime: - deltaT = self.getNextTime() - self.updateState(deltaT) - #print(str(self.t) + ", " + str(self.state)) + # 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 + return 1 # Magic number right now - def updateState(self, deltaT): + 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): +class Test_StochasticProcess(unittest.TestCase): def test_sp(self): - sally = stochasticProcess() + sally = StochasticProcess() sally.go() -suite = unittest.TestLoader().loadTestsFromTestCase(Test_stochasticProcess) -unittest.TextTestRunner(verbosity=1).run(suite) +#suite = unittest.TestLoader().loadTestsFromTestCase(Test_StochasticProcess) +#unittest.TextTestRunner(verbosity=1).run(suite) -class Node(object): - def __init__(self, params=["", {}]): - self.ident = params[0] - self.data = params[1] - self.edges = {} +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): - def __init__(self, params=["", {}]): - self.ident = params[0] - self.data = params[1] - self.nodes = {} + ''' + 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): - result = nodeIdent in self.nodes + # 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): - def __init__(self, params={}): + ''' + 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 newIdent(self, nonce): - return hash(str(nonce) + str(random.random())) + def createGraph(self, numNodes, probEdge, maxNeighbors): - self.data.update({"numNodes":numNodes, "probEdge":probEdge, "maxNeighbors":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 = self.newIdent(i) - n = Node([nIdent,{}]) + 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}) - touched = {} + + # 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] + n = self.nodes[nIdent] # Pick a node for mIdent in self.nodes: - m = self.nodes[mIdent] - notSameNode = (nIdent != mIdent) - nOpenSlots = (len(n.edges) < self.data["maxNeighbors"]) + 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) + 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([self.newIdent(nonce),{"length":random.random(), "pendingBlocks":[]}]) + 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): - n = Node([self.newIdent(len(self.nodes)), {}]) + # 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":[]}]) + 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}) @@ -100,10 +539,13 @@ class Graph(object): 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: - if edgeIdent in self.edges: - del self.edges[edgeIdent] + 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): @@ -111,8 +553,6 @@ class Test_Graph(unittest.TestCase): greg = Graph() greg.createGraph(3, 0.5, 10) self.assertEqual(len(greg.nodes),3) - for edge in greg.edges: - print(greg.edges[edge].ident, "\t", [greg.edges[edge].nodes[n].ident for n in greg.edges[edge].nodes], "\n") greg.addNode() self.assertEqual(len(greg.nodes),4) for edge in greg.edges: @@ -127,43 +567,69 @@ class Test_Graph(unittest.TestCase): suite = unittest.TestLoader().loadTestsFromTestCase(Test_Graph) unittest.TextTestRunner(verbosity=1).run(suite) - - - - - -class FishGraph(stochasticProcess): - def __init__(self, params=None): +class FishGraph(StochasticProcess): + ''' + Stochastic process on a graph + with the graph growing in a stochastic process too + ''' + # TODO: Check if output.txt exists before beginning. If so, clear it and create a new one. + # TODO: Instead of/in addition to storing graph data in a text file, can we plot with ggplot in R? + def __init__(self, params=None, verbosity=True): + # Initialize + + assert "maxTime" in params + self.maxTime = copy.deepcopy(params["maxTime"]) + del params["maxTime"] + + assert "numNodes" in params + numNodes = params["numNodes"] + del params["numNodes"] + self.data = params self.t = 0.0 self.state = Graph() - self.state.createGraph(self.data["numNodes"], self.data["probEdge"], self.data["maxNeighbors"]) + self.filename = "output.txt" + self.verbose = verbosity + + # Create graph + self.state.createGraph(numNodes, self.data["probEdge"], self.data["maxNeighbors"]) + + # Update node data for nIdent in self.state.nodes: n = self.state.nodes[nIdent] - difficulty = 10000.0 - intensity = random.random()/difficulty - offset = 2.0*random.random() - 1.0 - n.data.update({"intensity":intensity, "offset":offset, "blockchain":{}}) + difficulty = 1.0 + intensity = newIntensity(params="uniform") + offset = newOffset(params="sumOfSkellams") + dat = {"intensity":intensity, "offset":offset, "blockchain":Blockchain([], verbosity=self.verbose)} + n.data.update(dat) + + # Update edge data. for eIdent in self.state.edges: e = self.state.edges[eIdent] - e.data.update({"pendingBlocks":[]}) - self.maxTime = self.data["maxTime"] + e.data.update({"pendingBlocks":{}}) def go(self): assert self.maxTime > 0.0 while self.t <= self.maxTime and len(self.state.nodes) > 0: deltaT = self.getNextTime() - self.updateState(deltaT) - #print(str(self.t) + ", " + str(self.state)) + self.updateState(self.t, deltaT) + self.record() def getNextTime(self): + # Each Poisson process event generates an exponential random variable. + # The smallest of these is selected + # The rate of the smallest determines event type. eventTag = None - u = copy.deepcopy(random.random()) + u = 0.0 + while(u == 0.0): + u = copy.deepcopy(random.random()) u = -1.0*math.log(copy.deepcopy(u))/self.data["birthRate"] # Time until next stochastic birth eventTag = "birth" - v = copy.deepcopy(random.random()) + v = 0.0 + while(v == 0.0): + v = copy.deepcopy(random.random()) v = -1.0*math.log(copy.deepcopy(v))/self.data["deathRate"] # Time until next stochastic death if v < u: u = copy.deepcopy(v) @@ -171,94 +637,160 @@ class FishGraph(stochasticProcess): for nIdent in self.state.nodes: n = self.state.nodes[nIdent] # n.ident = nIdent - v = copy.deepcopy(random.random()) + v = 0.0 + while(v == 0.0): + v = copy.deepcopy(random.random()) v = -1.0*math.log(copy.deepcopy(v))/n.data["intensity"] if v < u: u = copy.deepcopy(v) eventTag = ["discovery", n.ident] + # Now that all the STOCHASTIC arrivals have been decided, + # We check if any of the deterministic events fire off instead. for eIdent in self.state.edges: e = self.state.edges[eIdent] # e.ident = eIdent - bufferedBlocks = e.data["pendingBlocks"] - if len(bufferedBlocks) > 0: - for pendingIdent in bufferedBlocks: - pB = bufferedBlocks[pendingIdent] - v = pB["timeOfArrival"] - if v < u: + pB = e.data["pendingBlocks"] + if len(pB) > 0: + for pendingIdent in pB: + arrivalInfo = pB[pendingIdent] + v = arrivalInfo["timeOfArrival"] - self.t + if v < u and 0.0 < v: u = copy.deepcopy(v) eventTag = ["arrival", e.ident, pendingIdent] deltaT = (u, eventTag) - # eventTag = ["arrival", e.ident, pendingIdent] - # eventTag = ["discovery", n.ident] - # eventTag = "death" - # eventTag = "birth" + # Formats: + # eventTag = ["arrival", e.ident, pendingIdent] + # eventTag = ["discovery", n.ident] + # eventTag = "death" + # eventTag = "birth" return deltaT - def updateState(self, deltaT): + def updateState(self, t, deltaT, mode="Nakamoto", targetRate=1.0/1209600.0): + # Depending on eventTag, update the state... u = deltaT[0] + shout = "" eventTag = deltaT[1] if type(eventTag)==type("birthordeath"): if eventTag == "death": # Picks random nodeIdent and kills it toDie = random.choice(list(self.state.nodes.keys())) - print("DEATH EVENT:") - print("Pre-death population = ", len(self.state.nodes)) + x = len(self.state.nodes) + shout += "DEATH, Pop(Old)=" + str(x) + ", Pop(New)=" + if self.verbose: + print(shout) self.state.delNode(toDie) - print("Post-death population = ", len(self.state.nodes)) - print("Done. \n") + y = len(self.state.nodes) + assert y == x - 1 + shout += str(y) + "\n" elif eventTag == "birth": # Adds node with some randomly determined edges - print("BIRTH EVENT:") - print("Pre-birth population = ", len(self.state.nodes)) + x = len(self.state.nodes) + shout += "BIRTH, Pop(Old)=" + str(x) + ", Pop(New)=" + if self.verbose: + print(shout) nIdent = self.state.addNode() - print("Post-death population = ", len(self.state.nodes)) n = self.state.nodes[nIdent] - intensity = random.random()/10000.0 + intensity = random.random()/1000.0 offset = 2.0*random.random() - 1.0 n.data.update({"intensity":intensity, "offset":offset, "blockchain":{}}) # Auto syncs new node. - print("Auto syncing new node...") for eIdent in n.edges: e = n.edges[eIdent] - e.data.update({"pendingBlocks":[]}) + e.data.update({"pendingBlocks":{}}) mIdent = e.getNeighbor(n.ident) m = self.state.nodes[mIdent] mdata = m.data["blockchain"] n.data["blockchain"].update(mdata) - print("Done. \n") + y = len(self.state.nodes) + assert y == x + 1 + shout += str(y) + "\n" else: - print("Error: eventTag had length 1 but was neighter a birth or a death. We had ", eventTag) + print("Error: eventTag had length 1 but was neighter a birth or a death, this shouldn't happen so this else case will eventually be removed, I guess? Our eventTag = ", eventTag) elif len(eventTag)==2: + # Block is discovered and plunked into each edge's pendingBlock list. + + shout += "DISCOVERY" + if self.verbose: + print(shout) + assert eventTag[0]=="discovery" - nIdent = eventTag[1] - n = self.state.nodes[n] - s = self.t + n.data["offset"] - n.data["blockchain"].append(s) - for edgeIdent in n.edges: - e = n.edges[edgeIdent] - l = e.data["length"] - toa = self.t + l - mIdent = e.getNeighbor(n.ident) - e.data["pendingBlocks"].append({"timestamp":s, "timeOfArrival":toa, "destIdent":mIdent}) - print("Block Discovery event. To be coded. For now, blah blah.") - elif len(eventTag)==3: + assert eventTag[1] in self.state.nodes + nIdent = eventTag[1] # get founding node's identity + n = self.state.nodes[nIdent] # get founding node + s = self.t + n.data["offset"] # get founding node's wall clock + + newBlockIdent = newIdent(len(n.data["blockchain"].blocks)) # generate new identity + disco = s + arriv = s + parent = n.data["blockchain"].miningIdent + diff = copy.deepcopy(n.diff) + verbosity = self.verbose + + newBlock = Block([newBlockIdent, disco, arriv, parent, diff, verbosity]) + n.updateBlockchain({newBlockIdent:newBlock}) + n.updateDifficulty(mode, targetRate) + n.propagate(newBlockIdent) + + elif len(eventTag)==3: + #eventTag = ("arrival", e.ident, pendingIdent) + # A block deterministically arrives at the end of an edge. + assert eventTag[0]=="arrival" - print("Block Arrival event. To be coded. For now, blah blah.") + shout += "ARRIVAL" + if self.verbose: + print(shout) + + eIdent = eventTag[1] + pendingIdent = eventTag[2] + e = self.state.edges[eIdent] + pB = e.data["pendingBlocks"] + arrivalInfo = pB[pendingIdent] # arrivalInfo = {"timeOfArrival":toa, "destIdent":mIdent, "block":newBlock} + + assert arrivalInfo["destIdent"] in self.state.nodes + assert self.t + u == arrivalInfo["timeOfArrival"] + receiver = self.state.nodes[arrivalInfo["destIdent"]] + arriv = self.t + u + receiver.data["offset"] + newBlock = arrivalInfo["block"] + newBlock.arrivTimestamp = copy.deepcopy(arriv) + receiver.data["blockchain"].updateBlockchain({newBlock.ident:newBlock}) + receiver.updateDifficulty(mode, targetRate) + receiver.propagate(newBlock.ident) + else: print("Error: eventTag was not a string, or not an array length 2 or 3. In fact, we have eventTag = ", eventTag) - + if self.verbose: + print("u = ", u) + self.t += u + if self.verbose: + print(str(self.t) + "\t" + shout) + + def record(self): + with open(self.filename, "a") as f: + line = "" + # Format will be edgeIdent,nodeAident,nodeBident + line += str("t=" + str(self.t) + ",") + ordKeyList = sorted(list(self.state.edges.keys())) + for key in ordKeyList: + entry = [] + entry.append(key) + nodeKeyList = sorted(list(self.state.edges[key].nodes)) + for kkey in nodeKeyList: + entry.append(kkey) + line += str(entry) + "," + f.write(line + "\n") class Test_FishGraph(unittest.TestCase): def test_fishGraph(self): - params = {"numNodes":10, "probEdge":0.5, "maxNeighbors":10, "maxTime":100.0, "birthRate":1.1, "deathRate":0.2} - greg = FishGraph(params) - greg.go() - - + for i in range(10): + params = {"numNodes":10, "probEdge":0.5, "maxNeighbors":10, "maxTime":10.0, "birthRate":0.001, "deathRate":0.001} + greg = FishGraph(params, verbosity=True) + greg.go() + + suite = unittest.TestLoader().loadTestsFromTestCase(Test_FishGraph) unittest.TextTestRunner(verbosity=1).run(suite) From 7c8aa166090c6082dcb022284b695264f1ac3a7e Mon Sep 17 00:00:00 2001 From: Brandon Goodell Date: Thu, 1 Feb 2018 17:36:30 -0700 Subject: [PATCH 04/13] Separating into files for easier bugfindin --- source-code/Poisson-Graphs/Block.py | 46 ++ source-code/Poisson-Graphs/Blockchain.py | 158 +++++ source-code/Poisson-Graphs/Edge.py | 95 +++ source-code/Poisson-Graphs/FishGraph.py | 582 ++---------------- source-code/Poisson-Graphs/Graph.py | 101 +++ source-code/Poisson-Graphs/Node.py | 271 ++++++++ .../Poisson-Graphs/StochasticProcess.py | 53 ++ 7 files changed, 770 insertions(+), 536 deletions(-) create mode 100644 source-code/Poisson-Graphs/Block.py create mode 100644 source-code/Poisson-Graphs/Blockchain.py create mode 100644 source-code/Poisson-Graphs/Edge.py create mode 100644 source-code/Poisson-Graphs/Graph.py create mode 100644 source-code/Poisson-Graphs/Node.py create mode 100644 source-code/Poisson-Graphs/StochasticProcess.py diff --git a/source-code/Poisson-Graphs/Block.py b/source-code/Poisson-Graphs/Block.py new file mode 100644 index 0000000..dece697 --- /dev/null +++ b/source-code/Poisson-Graphs/Block.py @@ -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) diff --git a/source-code/Poisson-Graphs/Blockchain.py b/source-code/Poisson-Graphs/Blockchain.py new file mode 100644 index 0000000..c580717 --- /dev/null +++ b/source-code/Poisson-Graphs/Blockchain.py @@ -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) diff --git a/source-code/Poisson-Graphs/Edge.py b/source-code/Poisson-Graphs/Edge.py new file mode 100644 index 0000000..37574c2 --- /dev/null +++ b/source-code/Poisson-Graphs/Edge.py @@ -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) diff --git a/source-code/Poisson-Graphs/FishGraph.py b/source-code/Poisson-Graphs/FishGraph.py index 0d1e4f5..b1168d5 100644 --- a/source-code/Poisson-Graphs/FishGraph.py +++ b/source-code/Poisson-Graphs/FishGraph.py @@ -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() diff --git a/source-code/Poisson-Graphs/Graph.py b/source-code/Poisson-Graphs/Graph.py new file mode 100644 index 0000000..ade6267 --- /dev/null +++ b/source-code/Poisson-Graphs/Graph.py @@ -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) + diff --git a/source-code/Poisson-Graphs/Node.py b/source-code/Poisson-Graphs/Node.py new file mode 100644 index 0000000..2866283 --- /dev/null +++ b/source-code/Poisson-Graphs/Node.py @@ -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) + diff --git a/source-code/Poisson-Graphs/StochasticProcess.py b/source-code/Poisson-Graphs/StochasticProcess.py new file mode 100644 index 0000000..e6739de --- /dev/null +++ b/source-code/Poisson-Graphs/StochasticProcess.py @@ -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) + + From f51f35669a1d48f566ab5367327781d6cd2fcdbf Mon Sep 17 00:00:00 2001 From: Brandon Goodell Date: Thu, 8 Feb 2018 13:06:01 -0700 Subject: [PATCH 05/13] Block and blockchain working, computing fucking difficulty correctly finally --- source-code/Poisson-Graphs/Blockchain.py | 44 +- source-code/Poisson-Graphs/Node.py | 926 +++++- source-code/Poisson-Graphs/new/Block.py | 46 + source-code/Poisson-Graphs/new/Block.py~ | 46 + source-code/Poisson-Graphs/new/Blockchain.py | 1142 ++++++++ source-code/Poisson-Graphs/new/Blockchain.py~ | 1142 ++++++++ source-code/Poisson-Graphs/new/Node.py | 116 + source-code/Poisson-Graphs/new/Node.py~ | 116 + .../new/__pycache__/Block.cpython-35.pyc | Bin 0 -> 2174 bytes .../new/__pycache__/Blockchain.cpython-35.pyc | Bin 0 -> 9813 bytes source-code/Poisson-Graphs/new/output.txt | 2501 +++++++++++++++++ source-code/Poisson-Graphs/new/outputM.txt | 602 ++++ source-code/Poisson-Graphs/new/outputM.txt~ | 201 ++ 13 files changed, 6752 insertions(+), 130 deletions(-) create mode 100644 source-code/Poisson-Graphs/new/Block.py create mode 100644 source-code/Poisson-Graphs/new/Block.py~ create mode 100644 source-code/Poisson-Graphs/new/Blockchain.py create mode 100644 source-code/Poisson-Graphs/new/Blockchain.py~ create mode 100644 source-code/Poisson-Graphs/new/Node.py create mode 100644 source-code/Poisson-Graphs/new/Node.py~ create mode 100644 source-code/Poisson-Graphs/new/__pycache__/Block.cpython-35.pyc create mode 100644 source-code/Poisson-Graphs/new/__pycache__/Blockchain.cpython-35.pyc create mode 100644 source-code/Poisson-Graphs/new/output.txt create mode 100644 source-code/Poisson-Graphs/new/outputM.txt create mode 100644 source-code/Poisson-Graphs/new/outputM.txt~ diff --git a/source-code/Poisson-Graphs/Blockchain.py b/source-code/Poisson-Graphs/Blockchain.py index c580717..f0e23c5 100644 --- a/source-code/Poisson-Graphs/Blockchain.py +++ b/source-code/Poisson-Graphs/Blockchain.py @@ -51,9 +51,49 @@ class Blockchain(object): elif tempCumDiff == maxCumDiff: self.miningIdents.append(ident) #print("leaf ident = ", str(ident), ", and tempCumDiff = ", str(tempCumDiff), " and maxCumDiff = ", str(maxCumDiff)) - + assert len(self.miningIdents) > 0 class Test_Blockchain(unittest.TestCase): + def test_addBlock(self): + bill = Blockchain([], verbosity=True) + + name = newIdent(0) + t = time.time() + s = t+random.random() + 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,s) + 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]) + self.assertEqual(len(bill.blocks),1) + + name = newIdent(1) + t = time.time() + s = t+random.random() + diff = 1.0 + params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} + blockA = Block(params) + bill.addBlock(blockA) + + self.assertTrue(blockA.ident in bill.blocks) + self.assertTrue(blockA.ident in bill.leaves) + self.assertTrue(genesis.ident not in bill.leaves) + self.assertEqual(len(bill.miningIdents),1) + self.assertEqual(blockA.ident, bill.miningIdents[0]) + self.assertEqual(len(bill.blocks),2) + + def test_bc(self): bill = Blockchain([], verbosity=True) @@ -115,7 +155,7 @@ class Test_Blockchain(unittest.TestCase): self.assertTrue(bill.blocks[genesis.ident].parent is None) bill.whichLeaf() - print(bill.miningIdents) + #print(bill.miningIdents) self.assertEqual(type(bill.miningIdents), type([])) self.assertTrue(len(bill.miningIdents), 2) diff --git a/source-code/Poisson-Graphs/Node.py b/source-code/Poisson-Graphs/Node.py index 2866283..25447b2 100644 --- a/source-code/Poisson-Graphs/Node.py +++ b/source-code/Poisson-Graphs/Node.py @@ -1,4 +1,5 @@ from Blockchain import * +import copy class Node(object): ''' @@ -17,12 +18,9 @@ class Node(object): 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) + # 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. - # 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.") @@ -36,18 +34,19 @@ class Node(object): if self.verbose: print("\t\t Now tempData has " + str(len(tempData)) + " entries.") - for key in incBlocks.blocks: + for key in incBlocks: 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]) + elif incBlocks[key].parent in self.data["blockchain"].blocks or incBlocks[key].parent is None: + self.data["blockchain"].addBlock(incBlocks[key]) + self.data["blockchain"].whichLeaf() #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.") + print("\t\t Now incBlocks has " + str(len(incBlocks)) + " entries.") if self.verbose: print("\t\tRemaining steps (while loop)") @@ -58,8 +57,9 @@ class Node(object): 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]) + elif incBlocks[key].parent in self.data["blockchain"].blocks: + self.data["blockchain"].addBlock(incBlocks[key]) + self.data["blockchain"].whichLeaf() del tempData[key] incBlocks = copy.deepcopy(tempData) if self.verbose: @@ -71,78 +71,118 @@ class Node(object): # 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. + if self.verbose: + print("Beginning update of difficulty with Nakamoto method") 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) + if self.verbose: + print("Checking that blockchain is 2016*n blocks long and some mining identity has been set") + if len(bc.blocks) % 2016 == 0 and len(bc.miningIdents) > 0: + + ident = random.choice(bc.miningIdents) + topTime = copy.deepcopy(bc.blocks[ident].discoTimestamp) 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! + 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) - # We use it nonetheless. + # Algebra is okay: + assert topTime != botTime + + # MLE estimate of arrivals per second: + mleDiscoRate = float(2015)/float(topTime - botTime) + + # Rates can't be negative, but this estimate could be (although it's highly unlikely given Bitcoin's standard choices + # of difficulty update rate, etc. + mleDiscoRate = abs(mleDiscoRate) + + if self.verbose: + print("MLE disco rate = " + str(mleDiscoRate) + " and targetRate = " + str(targetRate)) + # 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. + + if self.verbose: + print("MLE discovery rate = " + str(mleDiscoRate)) + print("Difficulty before adjustment = " + str(self.diff)) + + # Update difficulty multiplicatively + self.diff = self.diff*mleDiscoRate/targetRate + + if self.verbose: + print("Difficulty after adjustment = ", str(self.diff)) - # 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 + # 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) - + bc = self.data["blockchain"] + bc.whichLeaf() + assert self.diff != 0.0 + if len(bc.blocks) > 120: + assert type(bc.miningIdents)==type([]) + assert len(bc.miningIdents) > 0 + ident = random.choice(bc.miningIdents) + bl = [] + bl.append(copy.deepcopy(bc.blocks[ident].discoTimestamp)) + parent = bc.blocks[ident].parent + count = count - 1 + while count > 0 and parent is not None: + ident = copy.deepcopy(parent) + bl.append(copy.deepcopy(bc.blocks[ident].discoTimestamp)) + parent = bc.blocks[ident].parent + count = count-1 + # sort + bl = sorted(bl) + assert len(bl)<=1200 + + #print("Sample size = " + str(len(bl))) + # remove 10 and 90 %-iles + numOutliers = round(len(bl)/5)//2 + assert numOutliers <= 120 + #print("Number of outliers = " + str(numOutliers)) + if numOutliers > 0: + bl = bl[numOutliers:-numOutliers] + #print("New Sample Size = " + str(len(bl))) + + + # get topTime and botTime + if self.verbose: + print("bl[0] = " + str(bl[0]) + ",\tbl[-1] = " + str(bl[-1])) + topTime = bl[-1] + botTime = bl[0] + if self.verbose: + print("list of timestamps = " + str(bl)) + print("topTime = " + str(bl[-1])) + print("botTime = " + str(bl[0])) + + # Assert algebra will work + # 1200 - 2*120 = 1200 - 240 = 960 + assert 0 < len(bl) and len(bl) < 961 + assert topTime - botTime >= 0.0 + + # 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" @@ -158,20 +198,26 @@ class Node(object): # lambda ~ (1.0/(stdev))*(2/skewness)**(1.0/3.0) assert targetRate==1.0/120.0 count = 1200 - ident = self.data.miningIdent + bc = self.data["blockchain"] + if len(bc.miningIdents) > 0: + ident = random.choice(bc.miningIdents) + bl = [] - bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp)) - parent = self.data.blocks[ident].parent + bl.append(copy.deepcopy(bc.blocks[ident].discoTimestamp)) + parent = bc.blocks[ident].parent count = count - 1 - while count > 0 and parent is not NOne: + 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 + bl.append(copy.deepcopy(bc.blocks[ident].discoTimestamp)) + parent = bc.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) + if len(bl) > 120: + sk = skew(bl) + va = var(bl) + stdv = sqrt(va) + lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0) + else: + lam = targetRate # we will not change difficulty unless we have at least 120 blocks of data (arbitrarily selected) self.diff = self.diff*(lam/targetRate) else: print("Error, invalid difficulty mode entered.") @@ -193,77 +239,701 @@ class Node(object): class Test_Node(unittest.TestCase): - def test_node(self): - verbose = True - nellyIdent = newIdent(0) - bill = Blockchain([], verbosity=verbose) + # TODO test each method separately + '''def test_nakamoto(self): + print("Beginning test of Nakamoto difficulty adjustment") + print("Setting initial values") + target = 100.0 # rate = blocks/s + verbose = False + deltaT = 1.0/target # forced wait time + arrivalList = [] + mode="Nakamoto" + print("Generating node") + nellyIdent = newIdent(0) + offset = random.random() + intensity = random.random() + + print("Generating initial blockchain") + # Create a new initial blockchain object + bill = Blockchain([], verbosity=verbose) name = newIdent(0) t = time.time() - s = t+1 + t += offset + arrivalList.append(t) + s = t+random.random() + diff = 1.0 + oldDiff = copy.deepcopy(diff) + params = {"ident":name, "disco":t, "arriv":s, "parent":None, "diff":diff} + genesis = Block(params) + print("Adding block") + bill.addBlock(genesis) + bill.whichLeaf() + + # Check that it consists only of the genesis block + self.assertTrue(len(bill.blocks)==1) + self.assertTrue(genesis.ident in bill.blocks) + self.assertTrue(genesis.parent is None) + + print("Finish creating node") + # Create node with this blockchain. + nodeData = {"blockchain":bill, "intensity":intensity, "offset":offset} + params = {"ident":nellyIdent, "data":nodeData, "diff":diff, "verbose":verbose} + nelly = Node(params) + + # Check node creation worked + self.assertEqual(nelly.ident, nellyIdent) + self.assertEqual(nelly.data["blockchain"], bill) + self.assertEqual(nelly.diff, diff) + self.assertEqual(nelly.data["intensity"], intensity) + self.assertEqual(nelly.data["offset"], offset) + + # Sleep and add a block on top of genesis + if verbose: + print("sleeping") + time.sleep(deltaT) + + print("Giving genesis block a child") + name = newIdent(1) + t = time.time() + t += nelly.data["offset"] + arrivalList.append(t) + s = t+random.random() + diff = oldDiff + params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} + blockA = Block(params) + nelly.updateBlockchain({blockA.ident:blockA}) + oldIdent = blockA.ident + + # Check this worked + self.assertEqual(len(nelly.data["blockchain"].blocks),2) + self.assertTrue(blockA.ident in nelly.data["blockchain"].blocks) + self.assertTrue(genesis.ident in nelly.data["blockchain"].blocks) + self.assertEqual(genesis.ident, nelly.data["blockchain"].blocks[blockA.ident].parent) + + print("Updating difficulty") + # Update the difficulty score + nelly.updateDifficulty(mode, targetRate = target) # With only two blocks, nothing should change. + self.assertEqual(nelly.diff, oldDiff) + + # Print regardless of verbosity: + print("Now generating first difficulty adjustment period.") + + # Now we are going to fast forward to right before the first difficulty adjustment. + N = len(nelly.data["blockchain"].blocks) + while(N < 2015): + if N % 100 == 0: + print("\tN=" + str(N)) + name = newIdent(N) + t = time.time() + t += nelly.data["offset"] + arrivalList.append(t) + s = t+random.random() + diff = nelly.diff + oldDiff = diff + params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":diff} + oldIdent = copy.deepcopy(name) + block = Block(params) + nelly.updateBlockchain({block.ident:block}) + + # Check this worked + self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) + self.assertTrue(block.ident in nelly.data["blockchain"].blocks) + + # Update the difficulty score + nelly.updateDifficulty(mode, targetRate = target) # With N < 2016, nothing should change. + self.assertEqual(nelly.diff, oldDiff) + N = len(nelly.data["blockchain"].blocks) + time.sleep(deltaT) + + name = newIdent(N) + t = time.time() + t += nelly.data["offset"] + arrivalList.append(t) + s = t+random.random() + diff = oldDiff + params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":diff} + block = Block(params) + nelly.updateBlockchain({block.ident:block}) + + # Check this worked + self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) + self.assertTrue(block.ident in nelly.data["blockchain"].blocks) + + # Update the difficulty score + nelly.updateDifficulty(mode, targetRate = target) # With N < 2016, nothing should change. + # Note: 2016 blocks is 2015 block inter-arrival times. + expRatioNumerator = float(2015)/(arrivalList[-1] - arrivalList[-2016]) + expRatio = expRatioNumerator/target + expDiff = oldDiff*expRatio + self.assertEqual(nelly.diff, expDiff) + + # The following should fail, because our sample size is incorrect. + expRatioNumerator = float(2016)/(arrivalList[-1] - arrivalList[-2016]) + expRatio = expRatioNumerator/target + expDiff = oldDiff*expRatio + self.assertFalse(nelly.diff - expDiff == 0.0) + + + # Print regardless of verbosity: + print("Now generating second difficulty adjustment period.") + + # Now we are going to fast forward to right before the next difficulty adjustment. + # This time, though, we are going to re-set the block inter-arrival time deltaT + # to half. This should drive difficulty up. + lastDifficultyScore = copy.deepcopy(nelly.diff) + N = len(nelly.data["blockchain"].blocks) + while(N < 4031): + if N % 100 == 0: + print("\tN=" + str(N)) + name = newIdent(N) + t = time.time() + t += nelly.data["offset"] + arrivalList.append(t) + s = t+random.random() + diff = nelly.diff + oldDiff = diff + params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":diff} + oldIdent = copy.deepcopy(name) + block = Block(params) + nelly.updateBlockchain({block.ident:block}) + + # Check this worked + self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) + self.assertTrue(block.ident in nelly.data["blockchain"].blocks) + + # Update the difficulty score + nelly.updateDifficulty(mode, targetRate = target) # With N < 2016, nothing should change. + self.assertEqual(nelly.diff, oldDiff) + N = len(nelly.data["blockchain"].blocks) + time.sleep(0.01*deltaT) + + # Now if we add a single new block, we should trigger difficulty adjustment. + name = newIdent(N) + t = time.time() + t += nelly.data["offset"] + arrivalList.append(t) + s = t+random.random() + diff = oldDiff + params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":diff} + block = Block(params) + nelly.updateBlockchain({block.ident:block}) + + # Check this worked + self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) + self.assertTrue(block.ident in nelly.data["blockchain"].blocks) + + # Update the difficulty score. + nelly.updateDifficulty(mode, targetRate = target) + expRatioNumerator = float(2015)/(arrivalList[-1] - arrivalList[-2016]) + expRatio = expRatioNumerator/target + expDiff = oldDiff*expRatio + print("expRatio = " + str(expRatio) + ", lastDifficultyScore = " + str(lastDifficultyScore) + ", new difficulty = " + str(nelly.diff)) + self.assertEqual(nelly.diff, expDiff) + ''' + + + + + def test_vs(self): + print("Beginning test of vanSaberhagen difficulty adjustment.") + print("Setting initial values") + target = 10.0 # 1.0/240.0 # rate = blocks/s + verbose = False + deltaT = 1.0/target # forced wait time + arrivalList = [] + mode="vanSaberhagen" + + print("Instantiating new node") + nellyIdent = newIdent(0) + offset = random.random() + intensity = random.random() + + print("Creating new blockchain for new node") + # Create a new initial blockchain object + bill = Blockchain([], verbosity=verbose) + name = newIdent(0) + t = time.time() + t += offset + arrivalList.append(t) + s = t+random.random() diff = 1.0 params = {"ident":name, "disco":t, "arriv":s, "parent":None, "diff":diff} genesis = Block(params) + print("Adding genesis block") bill.addBlock(genesis) + bill.whichLeaf() - time.sleep(10) + # Check that it consists only of the genesis block + self.assertTrue(len(bill.blocks)==1) + self.assertTrue(genesis.ident in bill.blocks) + self.assertTrue(genesis.parent is None) + self.assertTrue(genesis.ident in bill.leaves) + print("Making node") + # Create node with this blockchain. + nodeData = {"blockchain":bill, "intensity":intensity, "offset":offset} + params = {"ident":nellyIdent, "data":nodeData, "diff":diff, "verbose":verbose} + nelly = Node(params) + + # Check node creation worked + self.assertEqual(nelly.ident, nellyIdent) + self.assertEqual(nelly.data["blockchain"], bill) + self.assertEqual(nelly.diff, diff) + self.assertEqual(nelly.data["intensity"], intensity) + self.assertEqual(nelly.data["offset"], offset) + + # Sleep and add a block on top of genesis + if verbose: + print("sleeping") + time.sleep(deltaT) + + print("Give genesis a child") name = newIdent(1) t = time.time() - s = t+1 - diff = 1.0 + t += nelly.data["offset"] + arrivalList.append(t) + s = t+random.random() + oldDiff = copy.deepcopy(diff) + diff = copy.deepcopy(nelly.diff) + assert diff != 0.0 params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} blockA = Block(params) - bill.addBlock(blockA) + nelly.updateBlockchain({blockA.ident:blockA}) + oldIdent = blockA.ident - # 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") + # Check this worked + self.assertEqual(len(nelly.data["blockchain"].blocks),2) + self.assertTrue(blockA.ident in nelly.data["blockchain"].blocks) + self.assertTrue(genesis.ident in nelly.data["blockchain"].blocks) + self.assertEqual(genesis.ident, nelly.data["blockchain"].blocks[blockA.ident].parent) - time.sleep(9) + # Update the difficulty score + nelly.updateDifficulty(mode, targetRate = target) # With only two blocks, nothing should change. + assert nelly.diff != 0.0 + self.assertEqual(nelly.diff, oldDiff) + self.assertFalse(nelly.diff == -0.0) - name = newIdent(len(nelly.data)) + # Print regardless of verbosity: + print("Now generating fulls sample size.") + + # Now we are going to fast forward to a "full sample size" period of time. + N = len(nelly.data["blockchain"].blocks) + while(N < 1200): + name = newIdent(N) + if N % 100 == 0: + print("\tNow adding block N=" + str(N)) + t = time.time() + t += nelly.data["offset"] + arrivalList.append(t) + s = t+random.random() + oldDiff = copy.deepcopy(diff) + diff = copy.deepcopy(nelly.diff) + assert diff != 0.0 + params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":diff} + oldIdent = copy.deepcopy(name) + block = Block(params) + nelly.updateBlockchain({block.ident:block}) + + # Check this worked + self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) + self.assertTrue(block.ident in nelly.data["blockchain"].blocks) + + # Update the difficulty score + nelly.updateDifficulty(mode, targetRate = target) # With N < 100, nothing should change. + if N < 100: + self.assertEqual(nelly.diff, oldDiff) + N = len(nelly.data["blockchain"].blocks) + time.sleep(0.5*deltaT) + + print("Adding one more block") + name = newIdent(N) 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}) + t += nelly.data["offset"] + arrivalList.append(t) + s = t+random.random() + oldDiff = diff + diff = nelly.diff + assert diff != 0.0 + params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":nelly.diff} + block = Block(params) + nelly.updateBlockchain({block.ident:block}) - time.sleep(8) + # Check this worked + self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) + self.assertTrue(block.ident in nelly.data["blockchain"].blocks) - name = newIdent(len(nelly.data)) + # Update the difficulty score + nelly.updateDifficulty(mode, targetRate = target) # With N < 2016, nothing should change. + # Note: 2016 blocks is 2015 block inter-arrival times. + print(str(arrivalList[-120]) + ", " + str(arrivalList[-1080]) + ", " + str(arrivalList[-120]-arrivalList[-1080]) + ", " + str(float(959)/(arrivalList[-120]-arrivalList[-1080]))+ ", " + str(float(float(959)/(arrivalList[-120]-arrivalList[-1080]))/float(target))) + expRatioNumerator = float(959)/(arrivalList[-120] - arrivalList[-1080]) + expRatio = expRatioNumerator/target + print(expRatio) + expDiff = oldDiff*expRatio + print(expDiff) + print("expDiff = " + str(expDiff) + " and nelly.diff = " + str(nelly.diff)) + self.assertEqual(nelly.diff, expDiff) + + + # Print regardless of verbosity: + print("Now fast forwarding past the tail end of the last period..") + # Now we are going to fast forward to right before the next difficulty adjustment. + # This time, though, we are going to re-set the block inter-arrival time deltaT + # to half. This should drive difficulty up. + lastDifficultyScore = copy.deepcopy(nelly.diff) + N = len(nelly.data["blockchain"].blocks) + while(N < 1700): + name = newIdent(N) + t = time.time() + t += nelly.data["offset"] + arrivalList.append(t) + s = t+random.random() + diff = nelly.diff + oldDiff = diff + params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":diff} + oldIdent = copy.deepcopy(name) + block = Block(params) + nelly.updateBlockchain({block.ident:block}) + + # Check this worked + self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) + self.assertTrue(block.ident in nelly.data["blockchain"].blocks) + + # Update the difficulty score + nelly.updateDifficulty(mode, targetRate = target) # With N < 2016, nothing should change. + self.assertEqual(nelly.diff, oldDiff) + N = len(nelly.data["blockchain"].blocks) + time.sleep(0.01*deltaT) + + # Now if we add a single new block, we should trigger difficulty adjustment. + name = newIdent(N) 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}) + t += nelly.data["offset"] + arrivalList.append(t) + s = t+random.random() + diff = oldDiff + params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":diff} + block = Block(params) + nelly.updateBlockchain({block.ident:block}) - 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}) + # Check this worked + self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) + self.assertTrue(block.ident in nelly.data["blockchain"].blocks) - 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}) + # Update the difficulty score. + nelly.updateDifficulty(mode, targetRate = target) + expRatioNumerator = float(959)/(arrivalList[-120] - arrivalList[-1080]) + expRatio = expRatioNumerator/target + expDiff = oldDiff*expRatio + print("expRatio = " + str(expRatio) + ", lastDifficultyScore = " + str(lastDifficultyScore) + ", new difficulty = " + str(nelly.diff)) + self.assertEqual(nelly.diff, expDiff) - 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}) + + def test_modexp(self): + pass + + '''# Check this worked + if mode == "Nakamoto": + # In this case we take simple MLE estimate + ratio = 1.0/abs(t1-t) + print("Nakamoto mle = " + str(ratio)) + ratio = ratio/target + print("Normalized = " + str(ratio)) + print("New diff = " + str(ratio*oldDiff)) + self.assertEqual(nelly.diff, ratio*oldDiff) + elif mode == "vanSaberhagen": + # In this case, with only 2 blocks, we just use simple MLE again + ratio = 1.0/abs(t1-t) + ratio = ratio/target + self.assertEqual(nelly.diff, ratio*oldDiff) + elif mode == "MOM:expModGauss": + self.assertEqual(nelly.diff, 1.0) + # With at least 120 blocks of data... + #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) + # Otherwise, set to 1.0 + else: + print("what world are you living in?") + + if verbose: + print("sleeping 1 seconds") + time.sleep(deltaT/5.0) + + listOfTimes = [copy.deepcopy(t), copy.deepcopy(t1)] + listOfBlocks = [] + + N = len(nelly.data["blockchain"].blocks) + lastIdent = blockA.ident + + bail = False + while N < 10 and not bail: + # Generate new block + name = newIdent(N) + t = time.time() + t += nelly.data["offset"] + s = t+random.random() + oldDiff = copy.deepcopy(nelly.diff) + print("Current difficulty = ", oldDiff) + params = {"ident":name, "disco":t, "arriv":s, "parent":lastIdent, "diff":oldDiff} + newBlock = Block(params) + + # Append new block to running list along with creation time + listOfBlocks.append(newBlock) + listOfTimes.append(copy.deepcopy(t)) + + # Update nelly's blockchain with newBlock + nelly.updateBlockchain({newBlock.ident:newBlock}) + lastIdent = name + + # Quick check that this worked: + self.assertTrue(name in nelly.data["blockchain"].blocks) + self.assertEqual(len(nelly.data["blockchain"].blocks), N+1) + N = len(nelly.data["blockchain"].blocks) + + # Update difficulty + nelly.updateDifficulty(mode, targetRate = 100.0) + + # Quick check that this worked: + if mode == "Nakamoto": + # In this case we take use top block and genesis block + ratio = float(len(nelly.data["blockchain"].blocks) - 1)/(listOfTimes[-1] - listOfTimes[0]) + ratio = ratio / target + self.assertEqual(nelly.diff, ratio*oldDiff) + print("Hoped for difficulty = " + str(oldDiff*ratio) + ", and computed = " + str(nelly.diff)) + elif mode == "vanSaberhagen": + # This case coincides with nakamoto until block 10 + ratio = float( len(nelly.data["blockchain"].blocks) - 1)/(listOfTimes[-1] - listOfTimes[0]) + ratio = ratio / target + self.assertEqual(nelly.diff, ratio*oldDiff) + elif mode == "MOM:expModGauss": + self.assertEqual(nelly.diff, 1.0) + # With at least 120 blocks of data... + #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) + # Otherwise, set to 1.0 + else: + print("what world are you living in?") + + # Sleep a random time + print("Sleeping a random sub-second, working on block " + str(N)) + deltaT = deltaT*ratio + time.sleep(deltaT/5.0) + + while N < 120 and not bail: + # Generate new block + name = newIdent(N) + t = time.time() + t += nelly.data["offset"] + s = t+random.random() + oldDiff = copy.deepcopy(nelly.diff) + params = {"ident":name, "disco":t, "arriv":s, "parent":lastIdent, "diff":oldDiff} + newBlock = Block(params) + + # Append new block to running list along with creation time + listOfBlocks.append(newBlock) + listOfTimes.append(copy.deepcopy(t)) + + # Update nelly's blockchain with newBlock + nelly.updateBlockchain({newBlock.ident:newBlock}) + lastIdent = name + + # Quick check that this worked: + self.assertTrue(name in nelly.data["blockchain"].blocks) + self.assertEqual(len(nelly.data["blockchain"].blocks), N+1) + N = len(nelly.data["blockchain"].blocks) + + # Update difficulty + nelly.updateDifficulty(mode, targetRate = 100.0) + + # Quick check that this worked: + if mode == "Nakamoto": + # In this case we take use top block and genesis block + ratio = float(len(nelly.data["blockchain"].blocks)-1)/(listOfTimes[-1] - listOfTimes[0]) + ratio = ratio / target + self.assertEqual(nelly.diff, oldDiff*ratio) + print("Hoped for difficulty = " + str(oldDiff*ratio) + ", and computed = " + str(nelly.diff)) + elif mode == "vanSaberhagen": + # This case no longer coincides with Nakamoto... + numOutliers = len(nelly.data["blockchain"].blocks)//10 + numOutliers = min(numOutliers, 120) + ratio = float(len(nelly.data["blockchain"].blocks) - 2*numOutliers - 1)/(listOfTimes[-numOutliers] - listOfTimes[numOutliers]) + ratio = ratio / target + self.assertEqual(nelly.diff, oldDiff*ratio) + elif mode == "MOM:expModGauss": + # With at least 120 blocks of data... + count = 1200 + bl = [] + bl.append(copy.deepcopy(bc.blocks[lastIdent].discoTimestamp)) + parent = bc.blocks[lastIdent].parent + count = count - 1 + while count > 0 and parent is not None: + ident = copy.deepcopy(parent) + bl.append(copy.deepcopy(bc.blocks[ident].discoTimestamp)) + parent = bc.blocks[ident].parent + count = count-1 + if len(bl) > 120: + sk = skew(bl) + va = var(bl) + stdv = sqrt(va) + lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0) + else: + lam = target + ratio = lam/target + self.assertEqual(nelly.diff, oldDiff*ratio) + + else: + print("what world are you living in?") + + # Sleep a random time + print("Sleeping a random sub-second, working on block " + str(N)) + deltaT = deltaT*ratio + time.sleep(deltaT/5.0) + + while N < 2400 and not bail: + # Generate new block + name = newIdent(N) + t = time.time() + t += nelly.data["offset"] + s = t+random.random() + oldDiff = copy.deepcopy(nelly.diff) + params = {"ident":name, "disco":t, "arriv":s, "parent":lastIdent, "diff":oldDiff} + newBlock = Block(params) + + # Append new block to running list along with creation time + listOfBlocks.append(newBlock) + listOfTimes.append(copy.deepcopy(t)) + + # Update nelly's blockchain with newBlock + nelly.updateBlockchain({newBlock.ident:newBlock}) + lastIdent = name + + # Quick check that this worked: + self.assertTrue(name in nelly.data["blockchain"].blocks) + self.assertEqual(len(nelly.data["blockchain"].blocks), N+1) + N = len(nelly.data["blockchain"].blocks) + + # Update difficulty + nelly.updateDifficulty(mode, targetRate = 100.0) + + # Quick check that this worked: + if mode == "Nakamoto": + # In this case we take use top block and genesis block + ratio = float(len(nelly.data["blockchain"].blocks)-1)/(listOfTimes[-1] - listOfTimes[0]) + ratio = ratio / target + self.assertEqual(nelly.diff, oldDiff*ratio) + print("Hoped for difficulty = " + str(oldDiff*ratio) + ", and computed = " + str(nelly.diff)) + elif mode == "vanSaberhagen": + # This case no longer coincides with Nakamoto... + numOutliers = len(nelly.data["blockchain"].blocks)//10 + numOutliers = min(numOutliers, 120) + ratio = float(len(nelly.data["blockchain"].blocks) - 2*numOutliers - 1)/(listOfTimes[-numOutliers] - listOfTimes[numOutliers]) + ratio = ratio / target + self.assertEqual(nelly.diff, ratio*oldDiff) + elif mode == "MOM:expModGauss": + # With at least 120 blocks of data... + count = 1200 + bl = [] + bl.append(copy.deepcopy(bc.blocks[lastIdent].discoTimestamp)) + parent = bc.blocks[lastIdent].parent + count = count - 1 + while count > 0 and parent is not None: + ident = copy.deepcopy(parent) + bl.append(copy.deepcopy(bc.blocks[ident].discoTimestamp)) + parent = bc.blocks[ident].parent + count = count-1 + if len(bl) > 120: + sk = skew(bl) + va = var(bl) + stdv = sqrt(va) + lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0) + else: + lam = targetRate + ratio = lam/targetRate + self.assertEqual(nelly.diff, ratio*oldDiff) + + else: + print("what world are you living in?") + + # Sleep a random time + print("Sleeping a random sub-second, working on block " + str(N)) + deltaT = deltaT*ratio + time.sleep(deltaT/5.0) + + + while N < 3600 and not bail: + # Generate new block + name = newIdent(N) + t = time.time() + t += nelly.data["offset"] + s = t+random.random() + oldDiff = nelly.diff + params = {"ident":name, "disco":t, "arriv":s, "parent":lastIdent, "diff":oldDiff} + newBlock = Block(params) + + # Append new block to running list along with creation time + listOfBlocks.append(newBlock) + listOfTimes.append(copy.deepcopy(t)) + + # Update nelly's blockchain with newBlock + nelly.updateBlockchain({newBlock.ident:newBlock}) + lastIdent = name + + # Quick check that this worked: + self.assertTrue(name in nelly.data["blockchain"].blocks) + self.assertEqual(len(nelly.data["blockchain"].blocks), N+1) + N = len(nelly.data["blockchain"].blocks) + + # Update difficulty + nelly.updateDifficulty(mode, targetRate = 100.0) + + # Quick check that this worked: + if mode == "Nakamoto": + # In this case we take use top block and genesis block + ratio = float(2400)/(listOfTimes[-1] - listOfTimes[-2400]) + self.assertEqual(nelly.diff, ratio*oldDiff) + elif mode == "vanSaberhagen": + # This case no longer coincides with Nakamoto... + numOutliers = len(nelly.data["blockchain"].blocks)//10 + numOutliers = min(numOutliers, 120) + ratio = float(len(nelly.data["blockchain"].blocks) - 2*numOutliers)/(listOfTimes[-numOutliers] - listOfTimes[numOutliers]) + self.assertEqual(nelly.diff, ratio*oldDiff) + elif mode == "MOM:expModGauss": + # With at least 120 blocks of data... + count = 1200 + bl = [] + bl.append(copy.deepcopy(bc.blocks[lastIdent].discoTimestamp)) + parent = bc.blocks[lastIdent].parent + count = count - 1 + while count > 0 and parent is not None: + ident = copy.deepcopy(parent) + bl.append(copy.deepcopy(bc.blocks[ident].discoTimestamp)) + parent = bc.blocks[ident].parent + count = count-1 + if len(bl) > 120: + sk = skew(bl) + va = var(bl) + stdv = sqrt(va) + lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0) + else: + lam = targetRate + ratio = lam/targetRate + self.assertEqual(nelly.diff, ratio*oldDiff) + + else: + print("what world are you living in?") + + # Sleep a random time + print("Sleeping a random sub-second, working on block " + str(N)) + deltaT = deltaT*ratio + time.sleep(deltaT/5.0)''' suite = unittest.TestLoader().loadTestsFromTestCase(Test_Node) diff --git a/source-code/Poisson-Graphs/new/Block.py b/source-code/Poisson-Graphs/new/Block.py new file mode 100644 index 0000000..dece697 --- /dev/null +++ b/source-code/Poisson-Graphs/new/Block.py @@ -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) diff --git a/source-code/Poisson-Graphs/new/Block.py~ b/source-code/Poisson-Graphs/new/Block.py~ new file mode 100644 index 0000000..dece697 --- /dev/null +++ b/source-code/Poisson-Graphs/new/Block.py~ @@ -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) diff --git a/source-code/Poisson-Graphs/new/Blockchain.py b/source-code/Poisson-Graphs/new/Blockchain.py new file mode 100644 index 0000000..97a3ba2 --- /dev/null +++ b/source-code/Poisson-Graphs/new/Blockchain.py @@ -0,0 +1,1142 @@ +from Block import * +import math +from scipy.stats import * +from numpy import * +from copy import deepcopy + +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.mIdent = None + self.verbose = verbosity + self.diff = None + self.targetRate = None + + def addBlock(self, blockToAdd, mode="Nakamoto", targetRate=1.0/600000.0): + # 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). + assert blockToAdd.ident not in self.blocks + if len(self.blocks)==0: + # In this case, blockToAdd is a genesis block, so we set difficulty + self.diff = deepcopy(blockToAdd.diff) + + 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() + return self.computeDifficulty(mode, targetRate) + + 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)) + assert len(self.miningIdents) > 0 + self.mIdent = random.choice(self.miningIdents) + + + # 1 block in 6*10^5 milliseconds=10min + def computeDifficulty(self, mode="Nakamoto", targetRate=1.0/600000.0): + result = None + if mode=="Nakamoto": + # Use MLE estimate of poisson process, compare to targetRate, update by multiplying by resulting ratio. + #if self.verbose: + # print("Beginning update of difficulty with Nakamoto method") + count = 2016 + #if self.verbose: + # print("Checking that blockchain is 2016*n blocks long and some mining identity has been set") + if len(self.blocks) % 2016 == 0 and len(self.miningIdents) > 0: + ident = self.mIdent + topTime = deepcopy(self.blocks[ident].discoTimestamp) + parent = self.blocks[ident].parent + count = count - 1 + touched = False + while count > 0 and parent is not None: + ident = deepcopy(parent) + parent = self.blocks[ident].parent + count = count - 1 + touched = True + if not touched: + mleDiscoRate = targetRate + else: + botTime = deepcopy(self.blocks[ident].discoTimestamp) + + # Algebra is okay: + assert topTime != botTime + + # MLE estimate of arrivals per second: + mleDiscoRate = float(2015)/float(topTime - botTime) + + # Rates can't be negative, but this estimate could be (although it's highly unlikely given Bitcoin's standard choices + # of difficulty update rate, etc. + mleDiscoRate = abs(mleDiscoRate) + + if self.verbose: + print("MLE disco rate = " + str(mleDiscoRate) + " and targetRate = " + str(targetRate)) + # 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. + + if self.verbose: + print("MLE discovery rate = " + str(mleDiscoRate)) + print("Difficulty before adjustment = " + str(self.diff)) + + # Update difficulty multiplicatively + self.diff = self.diff*mleDiscoRate/targetRate + + if self.verbose: + print("Difficulty after adjustment = ", str(self.diff)) + + 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 + #print(self.diff) + assert self.diff != 0.0 + if len(self.blocks) > 120 and len(self.miningIdents) > 0: + ident = self.mIdent + bl = [] + bl.append(deepcopy(self.blocks[ident].discoTimestamp)) + parent = self.blocks[ident].parent + count = count - 1 + while count > 0 and parent is not None: + ident = deepcopy(parent) + bl.append(deepcopy(self.blocks[ident].discoTimestamp)) + parent = self.blocks[ident].parent + count = count-1 + # sort + bl = sorted(bl) + assert len(bl)<=1200 + + #print("Sample size = " + str(len(bl))) + # remove 10 and 90 %-iles + numOutliers = math.ceil(float(len(bl))/float(10)) + assert numOutliers <= 120 + #print("Number of outliers = " + str(numOutliers)) + oldBL = deepcopy(bl) + if numOutliers > 0: + bl = bl[numOutliers:-numOutliers] + #if numOutliers == 120: + # print("\n\nSORTED TS LIST = " + str(oldBL) + "\nModified list = " + str(bl)) + + + # get topTime and botTime + #if self.verbose: + # print("bl[0] = " + str(bl[0]) + ",\tbl[-1] = " + str(bl[-1])) + topTime = bl[-1] + botTime = bl[0] + result = [float(topTime - botTime)] + #print(topTime - botTime) + #if self.verbose: + # print("list of timestamps = " + str(bl)) + # print("topTime = " + str(bl[-1])) + # print("botTime = " + str(bl[0])) + + # Assert algebra will work + # 1200 - 2*120 = 1200 - 240 = 960 + assert 0 < len(bl) and len(bl) < 961 + assert topTime - botTime >= 0.0 + result.append(len(bl)-1) + # 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 + if len(bl)==0: + print("WOOP WOOP NO TIMESTAMPS WTF? We have " + str(len(self.blocks)) + " blocks available, and we are counting " + str(2*numOutliers) + " as outliers. bl = " + str(bl)) + naiveDiscoRate = float(len(bl)-1)/float(topTime - botTime) + + # How much should difficulty change? + assert naiveDiscoRate != 0.0 + assert targetRate != 0.0 + assert self.diff != 0.0 + 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 + + # Really a trash metric unless sample sizes are huge. + count = 1200 + ident = self.mIdent + bl = [] + bl.append(deepcopy(self.blocks[ident].discoTimestamp)) + parent = self.blocks[ident].parent + count = count - 1 + while count > 0 and parent is not None: + ident = deepcopy(parent) + bl.append(deepcopy(self.blocks[ident].discoTimestamp)) + parent = self.blocks[ident].parent + count = count-1 + if len(bl) > 120: + sk = abs(skew(bl)) + va = var(bl) + stdv = sqrt(va) + lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0) + else: + lam = targetRate # we will not change difficulty unless we have at least 120 blocks of data (arbitrarily selected) + self.diff = self.diff*(lam/targetRate) + elif mode=="reciprocalOfMedian": + # In this mode we use a bitcoin-style metric except instead of 1/average inter-arrival time + # we use 1/median magnitude of inter-arrival time. + # And updated each block like with monero instead of every 2016 blocks like bitcoin. + # We assume a sample size of only 600 blocks for now + count = 600 + interArrivals = [] + if len(self.blocks) < count: + estDiscoRate = targetRate + elif len(self.miningIdents) > 0: + ident = self.mIdent + parent = self.blocks[ident].parent + if parent is not None: + dT = abs(self.blocks[ident].discoTimestamp - self.blocks[parent].discoTimestamp) + interArrivals.append(dT) + count = count - 1 + touched = False + while count > 0 and parent is not None: + ident = deepcopy(parent) + parent = self.blocks[ident].parent + if parent is not None: + dT = abs(self.blocks[ident].discoTimestamp - self.blocks[parent].discoTimestamp) + interArrivals.append(dT) + count = count - 1 + touched = True + if not touched: + estDiscoRate = targetRate + else: + estDiscoRate = 1.0/median(interArrivals) + if self.verbose: + print("Est disco rate = " + str(estDiscoRate) + " and targetRate = " + str(targetRate)) + + + if self.verbose: + print("MLE discovery rate = " + str(estDiscoRate)) + print("Difficulty before adjustment = " + str(self.diff)) + + # Update difficulty multiplicatively + self.diff = self.diff*estDiscoRate/targetRate + + if self.verbose: + print("Difficulty after adjustment = ", str(self.diff)) + else: + print("Error, invalid difficulty mode entered.") + return result + +class Test_Blockchain(unittest.TestCase): + def test_addBlock(self): + bill = Blockchain([], verbosity=True) + mode="Nakamoto" + tr = 1.0/100.0 + bill.targetRate = tr + + name = newIdent(0) + t = time.time() + s = t+random.random() + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + + 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]) + self.assertEqual(len(bill.blocks),1) + + name = newIdent(1) + t = time.time() + s = t+random.random() + diff = 1.0 + params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} + blockA = Block(params) + bill.addBlock(blockA, mode, tr) + + self.assertTrue(blockA.ident in bill.blocks) + self.assertTrue(blockA.ident in bill.leaves) + self.assertTrue(genesis.ident not in bill.leaves) + self.assertEqual(len(bill.miningIdents),1) + self.assertEqual(blockA.ident, bill.miningIdents[0]) + self.assertEqual(len(bill.blocks),2) + + + + + + bill = Blockchain([], verbosity=True) + mode="vanSaberhagen" + tr = 1.0/100.0 + bill.targetRate = tr + + name = newIdent(0) + t = time.time() + s = t+random.random() + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + + 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]) + self.assertEqual(len(bill.blocks),1) + + name = newIdent(1) + t = time.time() + s = t+random.random() + diff = 1.0 + params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} + blockA = Block(params) + bill.addBlock(blockA, mode, tr) + + self.assertTrue(blockA.ident in bill.blocks) + self.assertTrue(blockA.ident in bill.leaves) + self.assertTrue(genesis.ident not in bill.leaves) + self.assertEqual(len(bill.miningIdents),1) + self.assertEqual(blockA.ident, bill.miningIdents[0]) + self.assertEqual(len(bill.blocks),2) + + + + + + bill = Blockchain([], verbosity=True) + mode="MOM:expModGauss" + tr = 1.0/100.0 + bill.targetRate = tr + + name = newIdent(0) + t = time.time() + s = t+random.random() + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + + 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]) + self.assertEqual(len(bill.blocks),1) + + name = newIdent(1) + t = time.time() + s = t+random.random() + diff = 1.0 + params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} + blockA = Block(params) + bill.addBlock(blockA, mode, tr) + + self.assertTrue(blockA.ident in bill.blocks) + self.assertTrue(blockA.ident in bill.leaves) + self.assertTrue(genesis.ident not in bill.leaves) + self.assertEqual(len(bill.miningIdents),1) + self.assertEqual(blockA.ident, bill.miningIdents[0]) + self.assertEqual(len(bill.blocks),2) + + + def test_bc(self): + bill = Blockchain([], verbosity=True) + mode="Nakamoto" + tr = 1.0/100.0 + bill.targetRate = tr + + 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, mode, tr) + + 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, mode, tr) + + #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, mode, tr) + + 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, mode, tr) + + 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) + ''' + def test_median(self): + # TODO: everything. + mode = "reciprocalOfMedian" + tr = 1.0 # one block per millisecond why not + deltaT = 1.0 # let's just make this easy + bill = Blockchain([], verbosity=True) + bill.targetRate = tr + + with open("outputM.txt", "w") as writeFile: + # We will send (t, a, diff) to writeFile. + writeFile.write("time,rateConstant,difficulty\n") + name = newIdent(0) + t = 0.0 + s = 0.0 + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + a = 1.0 + b = 1.0/a + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + + 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]) + + parent = genesis.ident + oldDiff = bill.diff + + while len(bill.blocks)<601: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + a = 1.01 # slightly slower blocks, median won't change until half the data is corrupted! + b = 1.0/a + while len(bill.blocks)<899: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + self.assertEqual(bill.diff, oldDiff) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + # One more block and our median inter-arrival time is deltaT*(1.0+a)/2.0 + # and so estRate = 1/median = (2.0/(1.0+a))/deltaT, whereas before it was just + # 1/deltaT. So estRate/targetRate = 2.0/(1.0+a) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + err = bill.diff - oldDiff*2.0/(1.0+a) + self.assertTrue(err*err < 10**-15) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + # One more block and our median inter-arrival time is deltaT*a + # and so estRate = 1/median = (1.0/a)/deltaT, whereas before it was just + # 1/deltaT. So estRate/targetRate = 1.0/a = b + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + err = bill.diff - oldDiff*b + self.assertTrue(err*err < 10**-15) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + # Note that until the median changes again, this estimated block arrival rate + # does not change. This may be true even if a lot of new data has come in. + # It is possible that the same pair of blocks remain the median inter-arrival + # magnitude for the entire time both blocks are in the sample size. + # During this period of time, difficulty will update multiplicatively, so + # will either exponentially grow or shrink. + # In other words, this model can be looked at as: exponential change over + # time with a rate proportional to the deviation between the median and + # the target inter-arrival rates. + + + + + + def test_mine(self): + # TODO: everything. + mode = "MOM:expModGauss" + tr = 1.0/120000.0 # one block per two minutes + deltaT = 120000.0 + bill = Blockchain([], verbosity=True) + bill.targetRate = tr + + with open("outputM.txt", "w") as writeFile: + # We will send (t, a, diff, ratio, awayFromOne) to writeFile. + writeFile.write("time,rateConstant,difficulty\n") + name = newIdent(0) + t = 0.0 + s = 0.0 + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + a = 1.0 + b = 1.0/a + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + + 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]) + + parent = genesis.ident + oldDiff = bill.diff + + while len(bill.blocks)<120: + # Our metric divides by skewness. In reality, this is zero with + # probability zero. But for our tests, it's assured. So we + # will perturb each arrival by a small, up-to-half-percent + # variation to ensure a nonzero skewness without altering things + # too much. + newName = newIdent(len(bill.blocks)) + t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + # Just one more block and difficulty should be computed for the first time. + print("Just one more block and difficulty should be computed for the first time.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + #self.assertEqual(bill.diff, oldDiff) + + oldDiff = bill.diff + + # what if we add a bunch of blocks this way? + # In the case of a static hash rate, I suppose we hope to not + # vary too far from a multiplicative factor of 1.0, or rather + # a constant difficulty. + + while len(bill.blocks)<200: + # Our metric divides by skewness. In reality, this is zero with + # probability zero. But for our tests, it's assured. So we + # will perturb each arrival by a small, up-to-half-percent + # variation to ensure a nonzero skewness without altering things + # too much. + newName = newIdent(len(bill.blocks)) + t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + + + def test_vs(self): + # TODO: Still must test that outliers are being removed "appropriately" according to specifications + # TODO: Test that scrambled lists of timestamps produce the same difficulty estimate. + # TODO: Show that in the case of homogeneous poisson processes, unusual estimates are a little + # more common than in the Nakamoto difficulty (which must be the case because Nakamoto uses + # the UMVUE). + mode = "vanSaberhagen" + tr = 1.0/60000.0 # one block per minute + deltaT = 60000.0 + bill = Blockchain([], verbosity=True) + bill.targetRate = tr + + with open("output.txt", "w") as writeFile: + # We will send (t, a, diff, ratio, awayFromOne) to writeFile. + writeFile.write("time,rateConstant,difficulty,ratio\n") + name = newIdent(0) + t = 0.0 + s = 0.0 + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") + + 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]) + self.assertEqual(bill.diff, 1.0) + + parent = genesis.ident + oldDiff = bill.diff + a = 1.0 + b = 1.0/a + + while len(bill.blocks)<120: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + + writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") + parent = newName + oldDiff = bill.diff + + # Just one more block and difficulty should be computed for the first time. + print("Just one more block and difficulty should be computed for the first time.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") + parent = newName + self.assertEqual(bill.diff, oldDiff) + + oldDiff = bill.diff + + print("Let's add more blocks at the same rate.") + a = 1.0 + b = 1.0/a + + while len(bill.blocks)<1200: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") + parent = newName + self.assertEqual(bill.diff, oldDiff) + oldDiff = bill.diff + + print("Let's add more blocks at a slower rate.") + a = 1.1 + b = 1.0/a + + # If blocks arrive slightly further apart, difficulty should drop. + # However, since vanSaberhagen discards top 10% and bottom 10% of + # timestamps, it will take 120 blocks for this change to register + # in difficulty. + print("If blocks arrive slightly further apart, difficulty should drop. However, since vanSaberhagen discards top 10% and bottom 10% of timestamps, it will take 120 blocks for this change to register in difficulty.") + while len(bill.blocks)<1320: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertEqual(bill.diff, oldDiff) + oldDiff = bill.diff + + print("One more block and difficulty should register a change.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertTrue(bill.diff < oldDiff) + oldDiff = bill.diff + + # Let's add another fifty blocks at this same rate and verify that difficulty continually + # drops. + print("Let's add another fifty blocks at this same rate and verify that difficulty continually drops.") + a = 1.1 + b = 1.0/a + + while len(bill.blocks)<1370: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertTrue(bill.diff < oldDiff) + oldDiff = bill.diff + + # Now we go back to the target rate. We have 170 slow blocks in the queue and 50 in the sample size. Difficulty will continue to drop for another 120 blocks... + print("Now we go back to the target rate. We have 170 slow blocks in the queue and 50 in the sample size. Difficulty will continue to drop for another 120 blocks...") + a = 1.0 + b = 1.0/a + + while len(bill.blocks)<1490: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertTrue(bill.diff < oldDiff) + oldRatio = bill.diff/oldDiff + oldDiff = bill.diff + #print(str(result) + ", " + str(bill.diff) + ", " + str(oldDiff)) + + # Now all 170 slow blocks are not only in the queue but in our sample. The *multiplicative factor* between timesteps should be identical for the next 790 blocks.. leading to AN EXPONENTIAL DECAY OF DIFFICULTY. + print("Now all 170 slow blocks are not only in the queue but in our sample. The *multiplicative factor* between timesteps should be identical for the next 790 blocks.. leading to AN EXPONENTIAL DECAY OF DIFFICULTY.") + a = 1.0 + b = 1.0/a + while len(bill.blocks)<2279: + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + ratio = bill.diff/oldDiff + parent = newName + err = ratio - oldRatio + #print("Difference between last ratio and next ratio:" + str(err)) + self.assertTrue(err*err < 10**-15) + oldDiff = bill.diff + oldRatio = ratio + + print("Now adding a single new block will cause our 170 slow blocks to start dropping out of our sample, so the ratio should start returning to 1.0.") + oldAwayFromOne = abs(oldRatio - 1.0) # Ratio should be returning to 1.0 so this difference should go to zero + oldAwayFromOne = oldAwayFromOne*oldAwayFromOne + + # For the next 170 blocks as our perturbed blocks drop out of our sample, our + # estimated block arrival rate will return to "normal" so the multiplicative + # difference in difficulty should return to 1.0. + print("For the next 170 blocks as our perturbed blocks drop out of our sample, ourestimated block arrival rate will return to normal so the multiplicative difference in difficulty should return to 1.0.") + a = 1.0 + b = 1.0/a + while len(bill.blocks)<2449: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + ratio = bill.diff/oldDiff + #print("New ratio = " + str(ratio) + " and oldRatio = " + str(oldRatio)) + self.assertTrue(ratio > oldRatio) + awayFromOne = abs(ratio - 1.0) # Ratio should be returning to 1.0 so this difference should go to zero + awayFromOne = awayFromOne*awayFromOne + self.assertTrue(awayFromOne < oldAwayFromOne) # This return will be monotonic in our manufactured example. + parent = newName + oldDiff = bill.diff + oldRatio = ratio + oldAwayFromOne = awayFromOne + + + # Now difficulty should remain frozen for as long as we like. + + a = 1.0 + b = 1.0/a + while len(bill.blocks)<2500: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertEqual(bill.diff, oldDiff) + oldDiff = bill.diff + + + + + def test_nak(self): + # Since Nakamoto difficulty is derived from the MLE of the block arrival rate, + # we already know how it "should" behave in a poisson process, etc. + # TODO: Generate N samples of MLEs of Poisson rates compared to known homog. + # poisson rate, show that the resulting code does not result in unusual measurements + # more often than expected. + mode = "Nakamoto" + tr = 1.0/600000.0 + deltaT = 600000.0 + bill = Blockchain([], verbosity=True) + bill.targetRate = tr + # Bitcoin updating at 1 block per 10 minutes + + name = newIdent(0) + t = 0.0 + s = 0.0 + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + + 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]) + self.assertEqual(bill.diff, 1.0) + + parent = genesis.ident + oldDiff = bill.diff + i = 1 + + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + + # Just one more block and difficulty should recompute. + print("Just one more block and difficulty should recompute.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + self.assertEqual(bill.diff, oldDiff) + + oldDiff = bill.diff + i += 1 + + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + parent = newName + + # Just one more block and difficulty should recompute. + print("Just one more block and difficulty should again recompute.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + self.assertEqual(bill.diff, oldDiff) + + oldDiff = bill.diff + i += 1 + a = 1.1 + b = 1.0/a + + # If blocks arrive slightly further apart, difficulty should drop. + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + parent = newName + + print("Just one more block and difficulty will go down.") + + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + err = abs(bill.diff - oldDiff*b) + self.assertTrue(err*err < 10**-15) + oldDiff = bill.diff + i += 1 + + + # If blocks then arrive on target, difficulty should freeze. + a = 1.0 + b = 1.0/a + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + parent = newName + + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + self.assertEqual(bill.diff, oldDiff) + oldDiff = bill.diff + i += 1 + + # If blocks arrive too close together, difficulty should increase. + a = 0.9 + b = 1.0/a + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + parent = newName + + print("Just one more block and difficulty should go up.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + err = abs(bill.diff - oldDiff*b) + self.assertTrue(err*err < 10**-15) + ''' + + + + +suite = unittest.TestLoader().loadTestsFromTestCase(Test_Blockchain) +unittest.TextTestRunner(verbosity=1).run(suite) diff --git a/source-code/Poisson-Graphs/new/Blockchain.py~ b/source-code/Poisson-Graphs/new/Blockchain.py~ new file mode 100644 index 0000000..97a3ba2 --- /dev/null +++ b/source-code/Poisson-Graphs/new/Blockchain.py~ @@ -0,0 +1,1142 @@ +from Block import * +import math +from scipy.stats import * +from numpy import * +from copy import deepcopy + +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.mIdent = None + self.verbose = verbosity + self.diff = None + self.targetRate = None + + def addBlock(self, blockToAdd, mode="Nakamoto", targetRate=1.0/600000.0): + # 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). + assert blockToAdd.ident not in self.blocks + if len(self.blocks)==0: + # In this case, blockToAdd is a genesis block, so we set difficulty + self.diff = deepcopy(blockToAdd.diff) + + 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() + return self.computeDifficulty(mode, targetRate) + + 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)) + assert len(self.miningIdents) > 0 + self.mIdent = random.choice(self.miningIdents) + + + # 1 block in 6*10^5 milliseconds=10min + def computeDifficulty(self, mode="Nakamoto", targetRate=1.0/600000.0): + result = None + if mode=="Nakamoto": + # Use MLE estimate of poisson process, compare to targetRate, update by multiplying by resulting ratio. + #if self.verbose: + # print("Beginning update of difficulty with Nakamoto method") + count = 2016 + #if self.verbose: + # print("Checking that blockchain is 2016*n blocks long and some mining identity has been set") + if len(self.blocks) % 2016 == 0 and len(self.miningIdents) > 0: + ident = self.mIdent + topTime = deepcopy(self.blocks[ident].discoTimestamp) + parent = self.blocks[ident].parent + count = count - 1 + touched = False + while count > 0 and parent is not None: + ident = deepcopy(parent) + parent = self.blocks[ident].parent + count = count - 1 + touched = True + if not touched: + mleDiscoRate = targetRate + else: + botTime = deepcopy(self.blocks[ident].discoTimestamp) + + # Algebra is okay: + assert topTime != botTime + + # MLE estimate of arrivals per second: + mleDiscoRate = float(2015)/float(topTime - botTime) + + # Rates can't be negative, but this estimate could be (although it's highly unlikely given Bitcoin's standard choices + # of difficulty update rate, etc. + mleDiscoRate = abs(mleDiscoRate) + + if self.verbose: + print("MLE disco rate = " + str(mleDiscoRate) + " and targetRate = " + str(targetRate)) + # 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. + + if self.verbose: + print("MLE discovery rate = " + str(mleDiscoRate)) + print("Difficulty before adjustment = " + str(self.diff)) + + # Update difficulty multiplicatively + self.diff = self.diff*mleDiscoRate/targetRate + + if self.verbose: + print("Difficulty after adjustment = ", str(self.diff)) + + 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 + #print(self.diff) + assert self.diff != 0.0 + if len(self.blocks) > 120 and len(self.miningIdents) > 0: + ident = self.mIdent + bl = [] + bl.append(deepcopy(self.blocks[ident].discoTimestamp)) + parent = self.blocks[ident].parent + count = count - 1 + while count > 0 and parent is not None: + ident = deepcopy(parent) + bl.append(deepcopy(self.blocks[ident].discoTimestamp)) + parent = self.blocks[ident].parent + count = count-1 + # sort + bl = sorted(bl) + assert len(bl)<=1200 + + #print("Sample size = " + str(len(bl))) + # remove 10 and 90 %-iles + numOutliers = math.ceil(float(len(bl))/float(10)) + assert numOutliers <= 120 + #print("Number of outliers = " + str(numOutliers)) + oldBL = deepcopy(bl) + if numOutliers > 0: + bl = bl[numOutliers:-numOutliers] + #if numOutliers == 120: + # print("\n\nSORTED TS LIST = " + str(oldBL) + "\nModified list = " + str(bl)) + + + # get topTime and botTime + #if self.verbose: + # print("bl[0] = " + str(bl[0]) + ",\tbl[-1] = " + str(bl[-1])) + topTime = bl[-1] + botTime = bl[0] + result = [float(topTime - botTime)] + #print(topTime - botTime) + #if self.verbose: + # print("list of timestamps = " + str(bl)) + # print("topTime = " + str(bl[-1])) + # print("botTime = " + str(bl[0])) + + # Assert algebra will work + # 1200 - 2*120 = 1200 - 240 = 960 + assert 0 < len(bl) and len(bl) < 961 + assert topTime - botTime >= 0.0 + result.append(len(bl)-1) + # 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 + if len(bl)==0: + print("WOOP WOOP NO TIMESTAMPS WTF? We have " + str(len(self.blocks)) + " blocks available, and we are counting " + str(2*numOutliers) + " as outliers. bl = " + str(bl)) + naiveDiscoRate = float(len(bl)-1)/float(topTime - botTime) + + # How much should difficulty change? + assert naiveDiscoRate != 0.0 + assert targetRate != 0.0 + assert self.diff != 0.0 + 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 + + # Really a trash metric unless sample sizes are huge. + count = 1200 + ident = self.mIdent + bl = [] + bl.append(deepcopy(self.blocks[ident].discoTimestamp)) + parent = self.blocks[ident].parent + count = count - 1 + while count > 0 and parent is not None: + ident = deepcopy(parent) + bl.append(deepcopy(self.blocks[ident].discoTimestamp)) + parent = self.blocks[ident].parent + count = count-1 + if len(bl) > 120: + sk = abs(skew(bl)) + va = var(bl) + stdv = sqrt(va) + lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0) + else: + lam = targetRate # we will not change difficulty unless we have at least 120 blocks of data (arbitrarily selected) + self.diff = self.diff*(lam/targetRate) + elif mode=="reciprocalOfMedian": + # In this mode we use a bitcoin-style metric except instead of 1/average inter-arrival time + # we use 1/median magnitude of inter-arrival time. + # And updated each block like with monero instead of every 2016 blocks like bitcoin. + # We assume a sample size of only 600 blocks for now + count = 600 + interArrivals = [] + if len(self.blocks) < count: + estDiscoRate = targetRate + elif len(self.miningIdents) > 0: + ident = self.mIdent + parent = self.blocks[ident].parent + if parent is not None: + dT = abs(self.blocks[ident].discoTimestamp - self.blocks[parent].discoTimestamp) + interArrivals.append(dT) + count = count - 1 + touched = False + while count > 0 and parent is not None: + ident = deepcopy(parent) + parent = self.blocks[ident].parent + if parent is not None: + dT = abs(self.blocks[ident].discoTimestamp - self.blocks[parent].discoTimestamp) + interArrivals.append(dT) + count = count - 1 + touched = True + if not touched: + estDiscoRate = targetRate + else: + estDiscoRate = 1.0/median(interArrivals) + if self.verbose: + print("Est disco rate = " + str(estDiscoRate) + " and targetRate = " + str(targetRate)) + + + if self.verbose: + print("MLE discovery rate = " + str(estDiscoRate)) + print("Difficulty before adjustment = " + str(self.diff)) + + # Update difficulty multiplicatively + self.diff = self.diff*estDiscoRate/targetRate + + if self.verbose: + print("Difficulty after adjustment = ", str(self.diff)) + else: + print("Error, invalid difficulty mode entered.") + return result + +class Test_Blockchain(unittest.TestCase): + def test_addBlock(self): + bill = Blockchain([], verbosity=True) + mode="Nakamoto" + tr = 1.0/100.0 + bill.targetRate = tr + + name = newIdent(0) + t = time.time() + s = t+random.random() + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + + 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]) + self.assertEqual(len(bill.blocks),1) + + name = newIdent(1) + t = time.time() + s = t+random.random() + diff = 1.0 + params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} + blockA = Block(params) + bill.addBlock(blockA, mode, tr) + + self.assertTrue(blockA.ident in bill.blocks) + self.assertTrue(blockA.ident in bill.leaves) + self.assertTrue(genesis.ident not in bill.leaves) + self.assertEqual(len(bill.miningIdents),1) + self.assertEqual(blockA.ident, bill.miningIdents[0]) + self.assertEqual(len(bill.blocks),2) + + + + + + bill = Blockchain([], verbosity=True) + mode="vanSaberhagen" + tr = 1.0/100.0 + bill.targetRate = tr + + name = newIdent(0) + t = time.time() + s = t+random.random() + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + + 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]) + self.assertEqual(len(bill.blocks),1) + + name = newIdent(1) + t = time.time() + s = t+random.random() + diff = 1.0 + params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} + blockA = Block(params) + bill.addBlock(blockA, mode, tr) + + self.assertTrue(blockA.ident in bill.blocks) + self.assertTrue(blockA.ident in bill.leaves) + self.assertTrue(genesis.ident not in bill.leaves) + self.assertEqual(len(bill.miningIdents),1) + self.assertEqual(blockA.ident, bill.miningIdents[0]) + self.assertEqual(len(bill.blocks),2) + + + + + + bill = Blockchain([], verbosity=True) + mode="MOM:expModGauss" + tr = 1.0/100.0 + bill.targetRate = tr + + name = newIdent(0) + t = time.time() + s = t+random.random() + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + + 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]) + self.assertEqual(len(bill.blocks),1) + + name = newIdent(1) + t = time.time() + s = t+random.random() + diff = 1.0 + params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} + blockA = Block(params) + bill.addBlock(blockA, mode, tr) + + self.assertTrue(blockA.ident in bill.blocks) + self.assertTrue(blockA.ident in bill.leaves) + self.assertTrue(genesis.ident not in bill.leaves) + self.assertEqual(len(bill.miningIdents),1) + self.assertEqual(blockA.ident, bill.miningIdents[0]) + self.assertEqual(len(bill.blocks),2) + + + def test_bc(self): + bill = Blockchain([], verbosity=True) + mode="Nakamoto" + tr = 1.0/100.0 + bill.targetRate = tr + + 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, mode, tr) + + 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, mode, tr) + + #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, mode, tr) + + 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, mode, tr) + + 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) + ''' + def test_median(self): + # TODO: everything. + mode = "reciprocalOfMedian" + tr = 1.0 # one block per millisecond why not + deltaT = 1.0 # let's just make this easy + bill = Blockchain([], verbosity=True) + bill.targetRate = tr + + with open("outputM.txt", "w") as writeFile: + # We will send (t, a, diff) to writeFile. + writeFile.write("time,rateConstant,difficulty\n") + name = newIdent(0) + t = 0.0 + s = 0.0 + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + a = 1.0 + b = 1.0/a + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + + 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]) + + parent = genesis.ident + oldDiff = bill.diff + + while len(bill.blocks)<601: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + a = 1.01 # slightly slower blocks, median won't change until half the data is corrupted! + b = 1.0/a + while len(bill.blocks)<899: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + self.assertEqual(bill.diff, oldDiff) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + # One more block and our median inter-arrival time is deltaT*(1.0+a)/2.0 + # and so estRate = 1/median = (2.0/(1.0+a))/deltaT, whereas before it was just + # 1/deltaT. So estRate/targetRate = 2.0/(1.0+a) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + err = bill.diff - oldDiff*2.0/(1.0+a) + self.assertTrue(err*err < 10**-15) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + # One more block and our median inter-arrival time is deltaT*a + # and so estRate = 1/median = (1.0/a)/deltaT, whereas before it was just + # 1/deltaT. So estRate/targetRate = 1.0/a = b + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + err = bill.diff - oldDiff*b + self.assertTrue(err*err < 10**-15) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + # Note that until the median changes again, this estimated block arrival rate + # does not change. This may be true even if a lot of new data has come in. + # It is possible that the same pair of blocks remain the median inter-arrival + # magnitude for the entire time both blocks are in the sample size. + # During this period of time, difficulty will update multiplicatively, so + # will either exponentially grow or shrink. + # In other words, this model can be looked at as: exponential change over + # time with a rate proportional to the deviation between the median and + # the target inter-arrival rates. + + + + + + def test_mine(self): + # TODO: everything. + mode = "MOM:expModGauss" + tr = 1.0/120000.0 # one block per two minutes + deltaT = 120000.0 + bill = Blockchain([], verbosity=True) + bill.targetRate = tr + + with open("outputM.txt", "w") as writeFile: + # We will send (t, a, diff, ratio, awayFromOne) to writeFile. + writeFile.write("time,rateConstant,difficulty\n") + name = newIdent(0) + t = 0.0 + s = 0.0 + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + a = 1.0 + b = 1.0/a + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + + 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]) + + parent = genesis.ident + oldDiff = bill.diff + + while len(bill.blocks)<120: + # Our metric divides by skewness. In reality, this is zero with + # probability zero. But for our tests, it's assured. So we + # will perturb each arrival by a small, up-to-half-percent + # variation to ensure a nonzero skewness without altering things + # too much. + newName = newIdent(len(bill.blocks)) + t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + # Just one more block and difficulty should be computed for the first time. + print("Just one more block and difficulty should be computed for the first time.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + #self.assertEqual(bill.diff, oldDiff) + + oldDiff = bill.diff + + # what if we add a bunch of blocks this way? + # In the case of a static hash rate, I suppose we hope to not + # vary too far from a multiplicative factor of 1.0, or rather + # a constant difficulty. + + while len(bill.blocks)<200: + # Our metric divides by skewness. In reality, this is zero with + # probability zero. But for our tests, it's assured. So we + # will perturb each arrival by a small, up-to-half-percent + # variation to ensure a nonzero skewness without altering things + # too much. + newName = newIdent(len(bill.blocks)) + t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + + + def test_vs(self): + # TODO: Still must test that outliers are being removed "appropriately" according to specifications + # TODO: Test that scrambled lists of timestamps produce the same difficulty estimate. + # TODO: Show that in the case of homogeneous poisson processes, unusual estimates are a little + # more common than in the Nakamoto difficulty (which must be the case because Nakamoto uses + # the UMVUE). + mode = "vanSaberhagen" + tr = 1.0/60000.0 # one block per minute + deltaT = 60000.0 + bill = Blockchain([], verbosity=True) + bill.targetRate = tr + + with open("output.txt", "w") as writeFile: + # We will send (t, a, diff, ratio, awayFromOne) to writeFile. + writeFile.write("time,rateConstant,difficulty,ratio\n") + name = newIdent(0) + t = 0.0 + s = 0.0 + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") + + 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]) + self.assertEqual(bill.diff, 1.0) + + parent = genesis.ident + oldDiff = bill.diff + a = 1.0 + b = 1.0/a + + while len(bill.blocks)<120: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + + writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") + parent = newName + oldDiff = bill.diff + + # Just one more block and difficulty should be computed for the first time. + print("Just one more block and difficulty should be computed for the first time.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") + parent = newName + self.assertEqual(bill.diff, oldDiff) + + oldDiff = bill.diff + + print("Let's add more blocks at the same rate.") + a = 1.0 + b = 1.0/a + + while len(bill.blocks)<1200: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") + parent = newName + self.assertEqual(bill.diff, oldDiff) + oldDiff = bill.diff + + print("Let's add more blocks at a slower rate.") + a = 1.1 + b = 1.0/a + + # If blocks arrive slightly further apart, difficulty should drop. + # However, since vanSaberhagen discards top 10% and bottom 10% of + # timestamps, it will take 120 blocks for this change to register + # in difficulty. + print("If blocks arrive slightly further apart, difficulty should drop. However, since vanSaberhagen discards top 10% and bottom 10% of timestamps, it will take 120 blocks for this change to register in difficulty.") + while len(bill.blocks)<1320: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertEqual(bill.diff, oldDiff) + oldDiff = bill.diff + + print("One more block and difficulty should register a change.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertTrue(bill.diff < oldDiff) + oldDiff = bill.diff + + # Let's add another fifty blocks at this same rate and verify that difficulty continually + # drops. + print("Let's add another fifty blocks at this same rate and verify that difficulty continually drops.") + a = 1.1 + b = 1.0/a + + while len(bill.blocks)<1370: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertTrue(bill.diff < oldDiff) + oldDiff = bill.diff + + # Now we go back to the target rate. We have 170 slow blocks in the queue and 50 in the sample size. Difficulty will continue to drop for another 120 blocks... + print("Now we go back to the target rate. We have 170 slow blocks in the queue and 50 in the sample size. Difficulty will continue to drop for another 120 blocks...") + a = 1.0 + b = 1.0/a + + while len(bill.blocks)<1490: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertTrue(bill.diff < oldDiff) + oldRatio = bill.diff/oldDiff + oldDiff = bill.diff + #print(str(result) + ", " + str(bill.diff) + ", " + str(oldDiff)) + + # Now all 170 slow blocks are not only in the queue but in our sample. The *multiplicative factor* between timesteps should be identical for the next 790 blocks.. leading to AN EXPONENTIAL DECAY OF DIFFICULTY. + print("Now all 170 slow blocks are not only in the queue but in our sample. The *multiplicative factor* between timesteps should be identical for the next 790 blocks.. leading to AN EXPONENTIAL DECAY OF DIFFICULTY.") + a = 1.0 + b = 1.0/a + while len(bill.blocks)<2279: + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + ratio = bill.diff/oldDiff + parent = newName + err = ratio - oldRatio + #print("Difference between last ratio and next ratio:" + str(err)) + self.assertTrue(err*err < 10**-15) + oldDiff = bill.diff + oldRatio = ratio + + print("Now adding a single new block will cause our 170 slow blocks to start dropping out of our sample, so the ratio should start returning to 1.0.") + oldAwayFromOne = abs(oldRatio - 1.0) # Ratio should be returning to 1.0 so this difference should go to zero + oldAwayFromOne = oldAwayFromOne*oldAwayFromOne + + # For the next 170 blocks as our perturbed blocks drop out of our sample, our + # estimated block arrival rate will return to "normal" so the multiplicative + # difference in difficulty should return to 1.0. + print("For the next 170 blocks as our perturbed blocks drop out of our sample, ourestimated block arrival rate will return to normal so the multiplicative difference in difficulty should return to 1.0.") + a = 1.0 + b = 1.0/a + while len(bill.blocks)<2449: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + ratio = bill.diff/oldDiff + #print("New ratio = " + str(ratio) + " and oldRatio = " + str(oldRatio)) + self.assertTrue(ratio > oldRatio) + awayFromOne = abs(ratio - 1.0) # Ratio should be returning to 1.0 so this difference should go to zero + awayFromOne = awayFromOne*awayFromOne + self.assertTrue(awayFromOne < oldAwayFromOne) # This return will be monotonic in our manufactured example. + parent = newName + oldDiff = bill.diff + oldRatio = ratio + oldAwayFromOne = awayFromOne + + + # Now difficulty should remain frozen for as long as we like. + + a = 1.0 + b = 1.0/a + while len(bill.blocks)<2500: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertEqual(bill.diff, oldDiff) + oldDiff = bill.diff + + + + + def test_nak(self): + # Since Nakamoto difficulty is derived from the MLE of the block arrival rate, + # we already know how it "should" behave in a poisson process, etc. + # TODO: Generate N samples of MLEs of Poisson rates compared to known homog. + # poisson rate, show that the resulting code does not result in unusual measurements + # more often than expected. + mode = "Nakamoto" + tr = 1.0/600000.0 + deltaT = 600000.0 + bill = Blockchain([], verbosity=True) + bill.targetRate = tr + # Bitcoin updating at 1 block per 10 minutes + + name = newIdent(0) + t = 0.0 + s = 0.0 + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + + 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]) + self.assertEqual(bill.diff, 1.0) + + parent = genesis.ident + oldDiff = bill.diff + i = 1 + + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + + # Just one more block and difficulty should recompute. + print("Just one more block and difficulty should recompute.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + self.assertEqual(bill.diff, oldDiff) + + oldDiff = bill.diff + i += 1 + + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + parent = newName + + # Just one more block and difficulty should recompute. + print("Just one more block and difficulty should again recompute.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + self.assertEqual(bill.diff, oldDiff) + + oldDiff = bill.diff + i += 1 + a = 1.1 + b = 1.0/a + + # If blocks arrive slightly further apart, difficulty should drop. + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + parent = newName + + print("Just one more block and difficulty will go down.") + + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + err = abs(bill.diff - oldDiff*b) + self.assertTrue(err*err < 10**-15) + oldDiff = bill.diff + i += 1 + + + # If blocks then arrive on target, difficulty should freeze. + a = 1.0 + b = 1.0/a + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + parent = newName + + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + self.assertEqual(bill.diff, oldDiff) + oldDiff = bill.diff + i += 1 + + # If blocks arrive too close together, difficulty should increase. + a = 0.9 + b = 1.0/a + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + parent = newName + + print("Just one more block and difficulty should go up.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + err = abs(bill.diff - oldDiff*b) + self.assertTrue(err*err < 10**-15) + ''' + + + + +suite = unittest.TestLoader().loadTestsFromTestCase(Test_Blockchain) +unittest.TextTestRunner(verbosity=1).run(suite) diff --git a/source-code/Poisson-Graphs/new/Node.py b/source-code/Poisson-Graphs/new/Node.py new file mode 100644 index 0000000..716fe4f --- /dev/null +++ b/source-code/Poisson-Graphs/new/Node.py @@ -0,0 +1,116 @@ +from Blockchain import * + +class Node(object): + ''' + Node object. params [identity, blockchain (data), verbosity, difficulty] + ''' + def __init__(self, params={}): + 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, nonce): + newName = newIdent(nonce) + 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 = copy.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 = copy.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 = copy.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(0) + 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) + bill.addBlock(newBlock, mode, tr) + parent = name + + + while len(nelly.data["blockchain"].blocks) < 5000: + name = newIdent(0) + diff = nelly.data["blockchain"].diff + t += deltaT*diff + s = t + params = {"ident":name, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = name + +suite = unittest.TestLoader().loadTestsFromTestCase(Test_Node) +unittest.TextTestRunner(verbosity=1).run(suite) + diff --git a/source-code/Poisson-Graphs/new/Node.py~ b/source-code/Poisson-Graphs/new/Node.py~ new file mode 100644 index 0000000..716fe4f --- /dev/null +++ b/source-code/Poisson-Graphs/new/Node.py~ @@ -0,0 +1,116 @@ +from Blockchain import * + +class Node(object): + ''' + Node object. params [identity, blockchain (data), verbosity, difficulty] + ''' + def __init__(self, params={}): + 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, nonce): + newName = newIdent(nonce) + 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 = copy.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 = copy.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 = copy.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(0) + 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) + bill.addBlock(newBlock, mode, tr) + parent = name + + + while len(nelly.data["blockchain"].blocks) < 5000: + name = newIdent(0) + diff = nelly.data["blockchain"].diff + t += deltaT*diff + s = t + params = {"ident":name, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = name + +suite = unittest.TestLoader().loadTestsFromTestCase(Test_Node) +unittest.TextTestRunner(verbosity=1).run(suite) + diff --git a/source-code/Poisson-Graphs/new/__pycache__/Block.cpython-35.pyc b/source-code/Poisson-Graphs/new/__pycache__/Block.cpython-35.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62b87ddcec7ee932ed4ed40af7a7a7dc297f5f63 GIT binary patch literal 2174 zcmZuy&2k(y5N^%S{$=fuqGFRG0pT)`rEKrr#ZMrFKo#Yph^qp=OwG<{y<^YLERxn_ zjeQ|`fjkD!z>QbnrYoo1ICJ7_jn_6Qv{p;qYPEX0zizLstn^;W(}VZgM1Rq>5n=xT z)4T-9@h7MxI=I{mIuJ{FKnDTk_zUR;bV7QZN0i6RfU1a|hjb7_+M>MerEO9~M=gKQ z>^t-vk}jz>J%{a{SGYlWhw?6t#8IDAkB1b%`xz5yf29@D=bzw|hZ9mrO zY+{C$`g!<8S!c&PvxVz``zBYF4cT%20urr2cd%y6EC(Myz%=iJAa+ggIqV2lNChI@ z#2+kTAK}wt7yD;U|LjSOM`<>ZBYs!nb9ZH0$$}p)>|#r%(iT%?Y&xCEdMxw8Wc7*C z3weK58&izRg&e1)QG+e%jlsg}cT%m3leCnnm3E>e9s*P0`>)xUSCz_?F{xg_Qrg%; zoZ&{}+K1-4Q&kR~ycmy*Y+l-hgi~7Wcn300I1gTLU_Qh&zk}pH_sQkPqZDXJ5TE8K zTy7#-+$9@d-~*974#<2>wne`RdRl!%5w^!Y(sL3**fz)5b}rP{fp*4__^}f710aT= z-{E)g2$;BGJotcne-ikUcEg08=`;*dMo3wA?^TRXIDPy`>srgAl0Jj?2fK1#7b=&w zmTAsWnWp8q)>DXlN_XT_B{gDOoIundSAHT-pKZy}+)6X4=Vgw(pD2)A8erH?it3P) z&DmmG@|O({h>cypY{?A|gNihNY_9>gUENZHv0{L2~g-7nRi^6i~I1 zQRP^35PiKi1ig)1CrOH`ut~!9%v}%?-OFDn?ur$053~85uKLLDEHCY$oM53!rz%NY zFG;3#J}-ISPm-VJX?bzdPLjONl0>h;{xui4d1&@ScR{$keX;lBmhh4clFPxEet_w{ ze6834!56;A0)9^hk?ZZFY!mNnwrqy{P7l-9#2?t84FcT&E4M^JN7$o10*xTk`>S zH&TzAN1UwY-Wz=77KrOLXZw1tuGwho+k6;tYMro77!N~_E*Fi8vb>gHkFl2JWQ^X$ zHNya)pfCEOCt6|?>znwki#2h2>ndkE&5gZ5q}QRZ-(qry$tDvIn=9f0^fnIO!88n$ zzR=9OF1q!oHfZLWp*>h}ojK6X)^!AdzQ=@d=UVmXNM*K(N{3x}n4+S*8IPuK>oix| z-7c~6vH42Z)1{t^&AhP6t?sL5mWAJQQ`auk^XmWHb)Ao!Qu&lKXBd{7K|fwwyL0ol F_!q>w>s$Z; literal 0 HcmV?d00001 diff --git a/source-code/Poisson-Graphs/new/__pycache__/Blockchain.cpython-35.pyc b/source-code/Poisson-Graphs/new/__pycache__/Blockchain.cpython-35.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9602d14e03af90a75dde0daf88c94a4f247f1544 GIT binary patch literal 9813 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 Ii29`VFMs7IR{#J2 literal 0 HcmV?d00001 diff --git a/source-code/Poisson-Graphs/new/output.txt b/source-code/Poisson-Graphs/new/output.txt new file mode 100644 index 0000000..776c5db --- /dev/null +++ b/source-code/Poisson-Graphs/new/output.txt @@ -0,0 +1,2501 @@ +time,rateConstant,difficulty,ratio +0.0,1.0,1.0,1.0 +60000.0,1.0,1.0,1.0 +120000.0,1.0,1.0,1.0 +180000.0,1.0,1.0,1.0 +240000.0,1.0,1.0,1.0 +300000.0,1.0,1.0,1.0 +360000.0,1.0,1.0,1.0 +420000.0,1.0,1.0,1.0 +480000.0,1.0,1.0,1.0 +540000.0,1.0,1.0,1.0 +600000.0,1.0,1.0,1.0 +660000.0,1.0,1.0,1.0 +720000.0,1.0,1.0,1.0 +780000.0,1.0,1.0,1.0 +840000.0,1.0,1.0,1.0 +900000.0,1.0,1.0,1.0 +960000.0,1.0,1.0,1.0 +1020000.0,1.0,1.0,1.0 +1080000.0,1.0,1.0,1.0 +1140000.0,1.0,1.0,1.0 +1200000.0,1.0,1.0,1.0 +1260000.0,1.0,1.0,1.0 +1320000.0,1.0,1.0,1.0 +1380000.0,1.0,1.0,1.0 +1440000.0,1.0,1.0,1.0 +1500000.0,1.0,1.0,1.0 +1560000.0,1.0,1.0,1.0 +1620000.0,1.0,1.0,1.0 +1680000.0,1.0,1.0,1.0 +1740000.0,1.0,1.0,1.0 +1800000.0,1.0,1.0,1.0 +1860000.0,1.0,1.0,1.0 +1920000.0,1.0,1.0,1.0 +1980000.0,1.0,1.0,1.0 +2040000.0,1.0,1.0,1.0 +2100000.0,1.0,1.0,1.0 +2160000.0,1.0,1.0,1.0 +2220000.0,1.0,1.0,1.0 +2280000.0,1.0,1.0,1.0 +2340000.0,1.0,1.0,1.0 +2400000.0,1.0,1.0,1.0 +2460000.0,1.0,1.0,1.0 +2520000.0,1.0,1.0,1.0 +2580000.0,1.0,1.0,1.0 +2640000.0,1.0,1.0,1.0 +2700000.0,1.0,1.0,1.0 +2760000.0,1.0,1.0,1.0 +2820000.0,1.0,1.0,1.0 +2880000.0,1.0,1.0,1.0 +2940000.0,1.0,1.0,1.0 +3000000.0,1.0,1.0,1.0 +3060000.0,1.0,1.0,1.0 +3120000.0,1.0,1.0,1.0 +3180000.0,1.0,1.0,1.0 +3240000.0,1.0,1.0,1.0 +3300000.0,1.0,1.0,1.0 +3360000.0,1.0,1.0,1.0 +3420000.0,1.0,1.0,1.0 +3480000.0,1.0,1.0,1.0 +3540000.0,1.0,1.0,1.0 +3600000.0,1.0,1.0,1.0 +3660000.0,1.0,1.0,1.0 +3720000.0,1.0,1.0,1.0 +3780000.0,1.0,1.0,1.0 +3840000.0,1.0,1.0,1.0 +3900000.0,1.0,1.0,1.0 +3960000.0,1.0,1.0,1.0 +4020000.0,1.0,1.0,1.0 +4080000.0,1.0,1.0,1.0 +4140000.0,1.0,1.0,1.0 +4200000.0,1.0,1.0,1.0 +4260000.0,1.0,1.0,1.0 +4320000.0,1.0,1.0,1.0 +4380000.0,1.0,1.0,1.0 +4440000.0,1.0,1.0,1.0 +4500000.0,1.0,1.0,1.0 +4560000.0,1.0,1.0,1.0 +4620000.0,1.0,1.0,1.0 +4680000.0,1.0,1.0,1.0 +4740000.0,1.0,1.0,1.0 +4800000.0,1.0,1.0,1.0 +4860000.0,1.0,1.0,1.0 +4920000.0,1.0,1.0,1.0 +4980000.0,1.0,1.0,1.0 +5040000.0,1.0,1.0,1.0 +5100000.0,1.0,1.0,1.0 +5160000.0,1.0,1.0,1.0 +5220000.0,1.0,1.0,1.0 +5280000.0,1.0,1.0,1.0 +5340000.0,1.0,1.0,1.0 +5400000.0,1.0,1.0,1.0 +5460000.0,1.0,1.0,1.0 +5520000.0,1.0,1.0,1.0 +5580000.0,1.0,1.0,1.0 +5640000.0,1.0,1.0,1.0 +5700000.0,1.0,1.0,1.0 +5760000.0,1.0,1.0,1.0 +5820000.0,1.0,1.0,1.0 +5880000.0,1.0,1.0,1.0 +5940000.0,1.0,1.0,1.0 +6000000.0,1.0,1.0,1.0 +6060000.0,1.0,1.0,1.0 +6120000.0,1.0,1.0,1.0 +6180000.0,1.0,1.0,1.0 +6240000.0,1.0,1.0,1.0 +6300000.0,1.0,1.0,1.0 +6360000.0,1.0,1.0,1.0 +6420000.0,1.0,1.0,1.0 +6480000.0,1.0,1.0,1.0 +6540000.0,1.0,1.0,1.0 +6600000.0,1.0,1.0,1.0 +6660000.0,1.0,1.0,1.0 +6720000.0,1.0,1.0,1.0 +6780000.0,1.0,1.0,1.0 +6840000.0,1.0,1.0,1.0 +6900000.0,1.0,1.0,1.0 +6960000.0,1.0,1.0,1.0 +7020000.0,1.0,1.0,1.0 +7080000.0,1.0,1.0,1.0 +7140000.0,1.0,1.0,1.0 +7200000.0,1.0,1.0,1.0 +7260000.0,1.0,1.0,1.0 +7320000.0,1.0,1.0,1.0 +7380000.0,1.0,1.0,1.0 +7440000.0,1.0,1.0,1.0 +7500000.0,1.0,1.0,1.0 +7560000.0,1.0,1.0,1.0 +7620000.0,1.0,1.0,1.0 +7680000.0,1.0,1.0,1.0 +7740000.0,1.0,1.0,1.0 +7800000.0,1.0,1.0,1.0 +7860000.0,1.0,1.0,1.0 +7920000.0,1.0,1.0,1.0 +7980000.0,1.0,1.0,1.0 +8040000.0,1.0,1.0,1.0 +8100000.0,1.0,1.0,1.0 +8160000.0,1.0,1.0,1.0 +8220000.0,1.0,1.0,1.0 +8280000.0,1.0,1.0,1.0 +8340000.0,1.0,1.0,1.0 +8400000.0,1.0,1.0,1.0 +8460000.0,1.0,1.0,1.0 +8520000.0,1.0,1.0,1.0 +8580000.0,1.0,1.0,1.0 +8640000.0,1.0,1.0,1.0 +8700000.0,1.0,1.0,1.0 +8760000.0,1.0,1.0,1.0 +8820000.0,1.0,1.0,1.0 +8880000.0,1.0,1.0,1.0 +8940000.0,1.0,1.0,1.0 +9000000.0,1.0,1.0,1.0 +9060000.0,1.0,1.0,1.0 +9120000.0,1.0,1.0,1.0 +9180000.0,1.0,1.0,1.0 +9240000.0,1.0,1.0,1.0 +9300000.0,1.0,1.0,1.0 +9360000.0,1.0,1.0,1.0 +9420000.0,1.0,1.0,1.0 +9480000.0,1.0,1.0,1.0 +9540000.0,1.0,1.0,1.0 +9600000.0,1.0,1.0,1.0 +9660000.0,1.0,1.0,1.0 +9720000.0,1.0,1.0,1.0 +9780000.0,1.0,1.0,1.0 +9840000.0,1.0,1.0,1.0 +9900000.0,1.0,1.0,1.0 +9960000.0,1.0,1.0,1.0 +10020000.0,1.0,1.0,1.0 +10080000.0,1.0,1.0,1.0 +10140000.0,1.0,1.0,1.0 +10200000.0,1.0,1.0,1.0 +10260000.0,1.0,1.0,1.0 +10320000.0,1.0,1.0,1.0 +10380000.0,1.0,1.0,1.0 +10440000.0,1.0,1.0,1.0 +10500000.0,1.0,1.0,1.0 +10560000.0,1.0,1.0,1.0 +10620000.0,1.0,1.0,1.0 +10680000.0,1.0,1.0,1.0 +10740000.0,1.0,1.0,1.0 +10800000.0,1.0,1.0,1.0 +10860000.0,1.0,1.0,1.0 +10920000.0,1.0,1.0,1.0 +10980000.0,1.0,1.0,1.0 +11040000.0,1.0,1.0,1.0 +11100000.0,1.0,1.0,1.0 +11160000.0,1.0,1.0,1.0 +11220000.0,1.0,1.0,1.0 +11280000.0,1.0,1.0,1.0 +11340000.0,1.0,1.0,1.0 +11400000.0,1.0,1.0,1.0 +11460000.0,1.0,1.0,1.0 +11520000.0,1.0,1.0,1.0 +11580000.0,1.0,1.0,1.0 +11640000.0,1.0,1.0,1.0 +11700000.0,1.0,1.0,1.0 +11760000.0,1.0,1.0,1.0 +11820000.0,1.0,1.0,1.0 +11880000.0,1.0,1.0,1.0 +11940000.0,1.0,1.0,1.0 +12000000.0,1.0,1.0,1.0 +12060000.0,1.0,1.0,1.0 +12120000.0,1.0,1.0,1.0 +12180000.0,1.0,1.0,1.0 +12240000.0,1.0,1.0,1.0 +12300000.0,1.0,1.0,1.0 +12360000.0,1.0,1.0,1.0 +12420000.0,1.0,1.0,1.0 +12480000.0,1.0,1.0,1.0 +12540000.0,1.0,1.0,1.0 +12600000.0,1.0,1.0,1.0 +12660000.0,1.0,1.0,1.0 +12720000.0,1.0,1.0,1.0 +12780000.0,1.0,1.0,1.0 +12840000.0,1.0,1.0,1.0 +12900000.0,1.0,1.0,1.0 +12960000.0,1.0,1.0,1.0 +13020000.0,1.0,1.0,1.0 +13080000.0,1.0,1.0,1.0 +13140000.0,1.0,1.0,1.0 +13200000.0,1.0,1.0,1.0 +13260000.0,1.0,1.0,1.0 +13320000.0,1.0,1.0,1.0 +13380000.0,1.0,1.0,1.0 +13440000.0,1.0,1.0,1.0 +13500000.0,1.0,1.0,1.0 +13560000.0,1.0,1.0,1.0 +13620000.0,1.0,1.0,1.0 +13680000.0,1.0,1.0,1.0 +13740000.0,1.0,1.0,1.0 +13800000.0,1.0,1.0,1.0 +13860000.0,1.0,1.0,1.0 +13920000.0,1.0,1.0,1.0 +13980000.0,1.0,1.0,1.0 +14040000.0,1.0,1.0,1.0 +14100000.0,1.0,1.0,1.0 +14160000.0,1.0,1.0,1.0 +14220000.0,1.0,1.0,1.0 +14280000.0,1.0,1.0,1.0 +14340000.0,1.0,1.0,1.0 +14400000.0,1.0,1.0,1.0 +14460000.0,1.0,1.0,1.0 +14520000.0,1.0,1.0,1.0 +14580000.0,1.0,1.0,1.0 +14640000.0,1.0,1.0,1.0 +14700000.0,1.0,1.0,1.0 +14760000.0,1.0,1.0,1.0 +14820000.0,1.0,1.0,1.0 +14880000.0,1.0,1.0,1.0 +14940000.0,1.0,1.0,1.0 +15000000.0,1.0,1.0,1.0 +15060000.0,1.0,1.0,1.0 +15120000.0,1.0,1.0,1.0 +15180000.0,1.0,1.0,1.0 +15240000.0,1.0,1.0,1.0 +15300000.0,1.0,1.0,1.0 +15360000.0,1.0,1.0,1.0 +15420000.0,1.0,1.0,1.0 +15480000.0,1.0,1.0,1.0 +15540000.0,1.0,1.0,1.0 +15600000.0,1.0,1.0,1.0 +15660000.0,1.0,1.0,1.0 +15720000.0,1.0,1.0,1.0 +15780000.0,1.0,1.0,1.0 +15840000.0,1.0,1.0,1.0 +15900000.0,1.0,1.0,1.0 +15960000.0,1.0,1.0,1.0 +16020000.0,1.0,1.0,1.0 +16080000.0,1.0,1.0,1.0 +16140000.0,1.0,1.0,1.0 +16200000.0,1.0,1.0,1.0 +16260000.0,1.0,1.0,1.0 +16320000.0,1.0,1.0,1.0 +16380000.0,1.0,1.0,1.0 +16440000.0,1.0,1.0,1.0 +16500000.0,1.0,1.0,1.0 +16560000.0,1.0,1.0,1.0 +16620000.0,1.0,1.0,1.0 +16680000.0,1.0,1.0,1.0 +16740000.0,1.0,1.0,1.0 +16800000.0,1.0,1.0,1.0 +16860000.0,1.0,1.0,1.0 +16920000.0,1.0,1.0,1.0 +16980000.0,1.0,1.0,1.0 +17040000.0,1.0,1.0,1.0 +17100000.0,1.0,1.0,1.0 +17160000.0,1.0,1.0,1.0 +17220000.0,1.0,1.0,1.0 +17280000.0,1.0,1.0,1.0 +17340000.0,1.0,1.0,1.0 +17400000.0,1.0,1.0,1.0 +17460000.0,1.0,1.0,1.0 +17520000.0,1.0,1.0,1.0 +17580000.0,1.0,1.0,1.0 +17640000.0,1.0,1.0,1.0 +17700000.0,1.0,1.0,1.0 +17760000.0,1.0,1.0,1.0 +17820000.0,1.0,1.0,1.0 +17880000.0,1.0,1.0,1.0 +17940000.0,1.0,1.0,1.0 +18000000.0,1.0,1.0,1.0 +18060000.0,1.0,1.0,1.0 +18120000.0,1.0,1.0,1.0 +18180000.0,1.0,1.0,1.0 +18240000.0,1.0,1.0,1.0 +18300000.0,1.0,1.0,1.0 +18360000.0,1.0,1.0,1.0 +18420000.0,1.0,1.0,1.0 +18480000.0,1.0,1.0,1.0 +18540000.0,1.0,1.0,1.0 +18600000.0,1.0,1.0,1.0 +18660000.0,1.0,1.0,1.0 +18720000.0,1.0,1.0,1.0 +18780000.0,1.0,1.0,1.0 +18840000.0,1.0,1.0,1.0 +18900000.0,1.0,1.0,1.0 +18960000.0,1.0,1.0,1.0 +19020000.0,1.0,1.0,1.0 +19080000.0,1.0,1.0,1.0 +19140000.0,1.0,1.0,1.0 +19200000.0,1.0,1.0,1.0 +19260000.0,1.0,1.0,1.0 +19320000.0,1.0,1.0,1.0 +19380000.0,1.0,1.0,1.0 +19440000.0,1.0,1.0,1.0 +19500000.0,1.0,1.0,1.0 +19560000.0,1.0,1.0,1.0 +19620000.0,1.0,1.0,1.0 +19680000.0,1.0,1.0,1.0 +19740000.0,1.0,1.0,1.0 +19800000.0,1.0,1.0,1.0 +19860000.0,1.0,1.0,1.0 +19920000.0,1.0,1.0,1.0 +19980000.0,1.0,1.0,1.0 +20040000.0,1.0,1.0,1.0 +20100000.0,1.0,1.0,1.0 +20160000.0,1.0,1.0,1.0 +20220000.0,1.0,1.0,1.0 +20280000.0,1.0,1.0,1.0 +20340000.0,1.0,1.0,1.0 +20400000.0,1.0,1.0,1.0 +20460000.0,1.0,1.0,1.0 +20520000.0,1.0,1.0,1.0 +20580000.0,1.0,1.0,1.0 +20640000.0,1.0,1.0,1.0 +20700000.0,1.0,1.0,1.0 +20760000.0,1.0,1.0,1.0 +20820000.0,1.0,1.0,1.0 +20880000.0,1.0,1.0,1.0 +20940000.0,1.0,1.0,1.0 +21000000.0,1.0,1.0,1.0 +21060000.0,1.0,1.0,1.0 +21120000.0,1.0,1.0,1.0 +21180000.0,1.0,1.0,1.0 +21240000.0,1.0,1.0,1.0 +21300000.0,1.0,1.0,1.0 +21360000.0,1.0,1.0,1.0 +21420000.0,1.0,1.0,1.0 +21480000.0,1.0,1.0,1.0 +21540000.0,1.0,1.0,1.0 +21600000.0,1.0,1.0,1.0 +21660000.0,1.0,1.0,1.0 +21720000.0,1.0,1.0,1.0 +21780000.0,1.0,1.0,1.0 +21840000.0,1.0,1.0,1.0 +21900000.0,1.0,1.0,1.0 +21960000.0,1.0,1.0,1.0 +22020000.0,1.0,1.0,1.0 +22080000.0,1.0,1.0,1.0 +22140000.0,1.0,1.0,1.0 +22200000.0,1.0,1.0,1.0 +22260000.0,1.0,1.0,1.0 +22320000.0,1.0,1.0,1.0 +22380000.0,1.0,1.0,1.0 +22440000.0,1.0,1.0,1.0 +22500000.0,1.0,1.0,1.0 +22560000.0,1.0,1.0,1.0 +22620000.0,1.0,1.0,1.0 +22680000.0,1.0,1.0,1.0 +22740000.0,1.0,1.0,1.0 +22800000.0,1.0,1.0,1.0 +22860000.0,1.0,1.0,1.0 +22920000.0,1.0,1.0,1.0 +22980000.0,1.0,1.0,1.0 +23040000.0,1.0,1.0,1.0 +23100000.0,1.0,1.0,1.0 +23160000.0,1.0,1.0,1.0 +23220000.0,1.0,1.0,1.0 +23280000.0,1.0,1.0,1.0 +23340000.0,1.0,1.0,1.0 +23400000.0,1.0,1.0,1.0 +23460000.0,1.0,1.0,1.0 +23520000.0,1.0,1.0,1.0 +23580000.0,1.0,1.0,1.0 +23640000.0,1.0,1.0,1.0 +23700000.0,1.0,1.0,1.0 +23760000.0,1.0,1.0,1.0 +23820000.0,1.0,1.0,1.0 +23880000.0,1.0,1.0,1.0 +23940000.0,1.0,1.0,1.0 +24000000.0,1.0,1.0,1.0 +24060000.0,1.0,1.0,1.0 +24120000.0,1.0,1.0,1.0 +24180000.0,1.0,1.0,1.0 +24240000.0,1.0,1.0,1.0 +24300000.0,1.0,1.0,1.0 +24360000.0,1.0,1.0,1.0 +24420000.0,1.0,1.0,1.0 +24480000.0,1.0,1.0,1.0 +24540000.0,1.0,1.0,1.0 +24600000.0,1.0,1.0,1.0 +24660000.0,1.0,1.0,1.0 +24720000.0,1.0,1.0,1.0 +24780000.0,1.0,1.0,1.0 +24840000.0,1.0,1.0,1.0 +24900000.0,1.0,1.0,1.0 +24960000.0,1.0,1.0,1.0 +25020000.0,1.0,1.0,1.0 +25080000.0,1.0,1.0,1.0 +25140000.0,1.0,1.0,1.0 +25200000.0,1.0,1.0,1.0 +25260000.0,1.0,1.0,1.0 +25320000.0,1.0,1.0,1.0 +25380000.0,1.0,1.0,1.0 +25440000.0,1.0,1.0,1.0 +25500000.0,1.0,1.0,1.0 +25560000.0,1.0,1.0,1.0 +25620000.0,1.0,1.0,1.0 +25680000.0,1.0,1.0,1.0 +25740000.0,1.0,1.0,1.0 +25800000.0,1.0,1.0,1.0 +25860000.0,1.0,1.0,1.0 +25920000.0,1.0,1.0,1.0 +25980000.0,1.0,1.0,1.0 +26040000.0,1.0,1.0,1.0 +26100000.0,1.0,1.0,1.0 +26160000.0,1.0,1.0,1.0 +26220000.0,1.0,1.0,1.0 +26280000.0,1.0,1.0,1.0 +26340000.0,1.0,1.0,1.0 +26400000.0,1.0,1.0,1.0 +26460000.0,1.0,1.0,1.0 +26520000.0,1.0,1.0,1.0 +26580000.0,1.0,1.0,1.0 +26640000.0,1.0,1.0,1.0 +26700000.0,1.0,1.0,1.0 +26760000.0,1.0,1.0,1.0 +26820000.0,1.0,1.0,1.0 +26880000.0,1.0,1.0,1.0 +26940000.0,1.0,1.0,1.0 +27000000.0,1.0,1.0,1.0 +27060000.0,1.0,1.0,1.0 +27120000.0,1.0,1.0,1.0 +27180000.0,1.0,1.0,1.0 +27240000.0,1.0,1.0,1.0 +27300000.0,1.0,1.0,1.0 +27360000.0,1.0,1.0,1.0 +27420000.0,1.0,1.0,1.0 +27480000.0,1.0,1.0,1.0 +27540000.0,1.0,1.0,1.0 +27600000.0,1.0,1.0,1.0 +27660000.0,1.0,1.0,1.0 +27720000.0,1.0,1.0,1.0 +27780000.0,1.0,1.0,1.0 +27840000.0,1.0,1.0,1.0 +27900000.0,1.0,1.0,1.0 +27960000.0,1.0,1.0,1.0 +28020000.0,1.0,1.0,1.0 +28080000.0,1.0,1.0,1.0 +28140000.0,1.0,1.0,1.0 +28200000.0,1.0,1.0,1.0 +28260000.0,1.0,1.0,1.0 +28320000.0,1.0,1.0,1.0 +28380000.0,1.0,1.0,1.0 +28440000.0,1.0,1.0,1.0 +28500000.0,1.0,1.0,1.0 +28560000.0,1.0,1.0,1.0 +28620000.0,1.0,1.0,1.0 +28680000.0,1.0,1.0,1.0 +28740000.0,1.0,1.0,1.0 +28800000.0,1.0,1.0,1.0 +28860000.0,1.0,1.0,1.0 +28920000.0,1.0,1.0,1.0 +28980000.0,1.0,1.0,1.0 +29040000.0,1.0,1.0,1.0 +29100000.0,1.0,1.0,1.0 +29160000.0,1.0,1.0,1.0 +29220000.0,1.0,1.0,1.0 +29280000.0,1.0,1.0,1.0 +29340000.0,1.0,1.0,1.0 +29400000.0,1.0,1.0,1.0 +29460000.0,1.0,1.0,1.0 +29520000.0,1.0,1.0,1.0 +29580000.0,1.0,1.0,1.0 +29640000.0,1.0,1.0,1.0 +29700000.0,1.0,1.0,1.0 +29760000.0,1.0,1.0,1.0 +29820000.0,1.0,1.0,1.0 +29880000.0,1.0,1.0,1.0 +29940000.0,1.0,1.0,1.0 +30000000.0,1.0,1.0,1.0 +30060000.0,1.0,1.0,1.0 +30120000.0,1.0,1.0,1.0 +30180000.0,1.0,1.0,1.0 +30240000.0,1.0,1.0,1.0 +30300000.0,1.0,1.0,1.0 +30360000.0,1.0,1.0,1.0 +30420000.0,1.0,1.0,1.0 +30480000.0,1.0,1.0,1.0 +30540000.0,1.0,1.0,1.0 +30600000.0,1.0,1.0,1.0 +30660000.0,1.0,1.0,1.0 +30720000.0,1.0,1.0,1.0 +30780000.0,1.0,1.0,1.0 +30840000.0,1.0,1.0,1.0 +30900000.0,1.0,1.0,1.0 +30960000.0,1.0,1.0,1.0 +31020000.0,1.0,1.0,1.0 +31080000.0,1.0,1.0,1.0 +31140000.0,1.0,1.0,1.0 +31200000.0,1.0,1.0,1.0 +31260000.0,1.0,1.0,1.0 +31320000.0,1.0,1.0,1.0 +31380000.0,1.0,1.0,1.0 +31440000.0,1.0,1.0,1.0 +31500000.0,1.0,1.0,1.0 +31560000.0,1.0,1.0,1.0 +31620000.0,1.0,1.0,1.0 +31680000.0,1.0,1.0,1.0 +31740000.0,1.0,1.0,1.0 +31800000.0,1.0,1.0,1.0 +31860000.0,1.0,1.0,1.0 +31920000.0,1.0,1.0,1.0 +31980000.0,1.0,1.0,1.0 +32040000.0,1.0,1.0,1.0 +32100000.0,1.0,1.0,1.0 +32160000.0,1.0,1.0,1.0 +32220000.0,1.0,1.0,1.0 +32280000.0,1.0,1.0,1.0 +32340000.0,1.0,1.0,1.0 +32400000.0,1.0,1.0,1.0 +32460000.0,1.0,1.0,1.0 +32520000.0,1.0,1.0,1.0 +32580000.0,1.0,1.0,1.0 +32640000.0,1.0,1.0,1.0 +32700000.0,1.0,1.0,1.0 +32760000.0,1.0,1.0,1.0 +32820000.0,1.0,1.0,1.0 +32880000.0,1.0,1.0,1.0 +32940000.0,1.0,1.0,1.0 +33000000.0,1.0,1.0,1.0 +33060000.0,1.0,1.0,1.0 +33120000.0,1.0,1.0,1.0 +33180000.0,1.0,1.0,1.0 +33240000.0,1.0,1.0,1.0 +33300000.0,1.0,1.0,1.0 +33360000.0,1.0,1.0,1.0 +33420000.0,1.0,1.0,1.0 +33480000.0,1.0,1.0,1.0 +33540000.0,1.0,1.0,1.0 +33600000.0,1.0,1.0,1.0 +33660000.0,1.0,1.0,1.0 +33720000.0,1.0,1.0,1.0 +33780000.0,1.0,1.0,1.0 +33840000.0,1.0,1.0,1.0 +33900000.0,1.0,1.0,1.0 +33960000.0,1.0,1.0,1.0 +34020000.0,1.0,1.0,1.0 +34080000.0,1.0,1.0,1.0 +34140000.0,1.0,1.0,1.0 +34200000.0,1.0,1.0,1.0 +34260000.0,1.0,1.0,1.0 +34320000.0,1.0,1.0,1.0 +34380000.0,1.0,1.0,1.0 +34440000.0,1.0,1.0,1.0 +34500000.0,1.0,1.0,1.0 +34560000.0,1.0,1.0,1.0 +34620000.0,1.0,1.0,1.0 +34680000.0,1.0,1.0,1.0 +34740000.0,1.0,1.0,1.0 +34800000.0,1.0,1.0,1.0 +34860000.0,1.0,1.0,1.0 +34920000.0,1.0,1.0,1.0 +34980000.0,1.0,1.0,1.0 +35040000.0,1.0,1.0,1.0 +35100000.0,1.0,1.0,1.0 +35160000.0,1.0,1.0,1.0 +35220000.0,1.0,1.0,1.0 +35280000.0,1.0,1.0,1.0 +35340000.0,1.0,1.0,1.0 +35400000.0,1.0,1.0,1.0 +35460000.0,1.0,1.0,1.0 +35520000.0,1.0,1.0,1.0 +35580000.0,1.0,1.0,1.0 +35640000.0,1.0,1.0,1.0 +35700000.0,1.0,1.0,1.0 +35760000.0,1.0,1.0,1.0 +35820000.0,1.0,1.0,1.0 +35880000.0,1.0,1.0,1.0 +35940000.0,1.0,1.0,1.0 +36000000.0,1.0,1.0,1.0 +36060000.0,1.0,1.0,1.0 +36120000.0,1.0,1.0,1.0 +36180000.0,1.0,1.0,1.0 +36240000.0,1.0,1.0,1.0 +36300000.0,1.0,1.0,1.0 +36360000.0,1.0,1.0,1.0 +36420000.0,1.0,1.0,1.0 +36480000.0,1.0,1.0,1.0 +36540000.0,1.0,1.0,1.0 +36600000.0,1.0,1.0,1.0 +36660000.0,1.0,1.0,1.0 +36720000.0,1.0,1.0,1.0 +36780000.0,1.0,1.0,1.0 +36840000.0,1.0,1.0,1.0 +36900000.0,1.0,1.0,1.0 +36960000.0,1.0,1.0,1.0 +37020000.0,1.0,1.0,1.0 +37080000.0,1.0,1.0,1.0 +37140000.0,1.0,1.0,1.0 +37200000.0,1.0,1.0,1.0 +37260000.0,1.0,1.0,1.0 +37320000.0,1.0,1.0,1.0 +37380000.0,1.0,1.0,1.0 +37440000.0,1.0,1.0,1.0 +37500000.0,1.0,1.0,1.0 +37560000.0,1.0,1.0,1.0 +37620000.0,1.0,1.0,1.0 +37680000.0,1.0,1.0,1.0 +37740000.0,1.0,1.0,1.0 +37800000.0,1.0,1.0,1.0 +37860000.0,1.0,1.0,1.0 +37920000.0,1.0,1.0,1.0 +37980000.0,1.0,1.0,1.0 +38040000.0,1.0,1.0,1.0 +38100000.0,1.0,1.0,1.0 +38160000.0,1.0,1.0,1.0 +38220000.0,1.0,1.0,1.0 +38280000.0,1.0,1.0,1.0 +38340000.0,1.0,1.0,1.0 +38400000.0,1.0,1.0,1.0 +38460000.0,1.0,1.0,1.0 +38520000.0,1.0,1.0,1.0 +38580000.0,1.0,1.0,1.0 +38640000.0,1.0,1.0,1.0 +38700000.0,1.0,1.0,1.0 +38760000.0,1.0,1.0,1.0 +38820000.0,1.0,1.0,1.0 +38880000.0,1.0,1.0,1.0 +38940000.0,1.0,1.0,1.0 +39000000.0,1.0,1.0,1.0 +39060000.0,1.0,1.0,1.0 +39120000.0,1.0,1.0,1.0 +39180000.0,1.0,1.0,1.0 +39240000.0,1.0,1.0,1.0 +39300000.0,1.0,1.0,1.0 +39360000.0,1.0,1.0,1.0 +39420000.0,1.0,1.0,1.0 +39480000.0,1.0,1.0,1.0 +39540000.0,1.0,1.0,1.0 +39600000.0,1.0,1.0,1.0 +39660000.0,1.0,1.0,1.0 +39720000.0,1.0,1.0,1.0 +39780000.0,1.0,1.0,1.0 +39840000.0,1.0,1.0,1.0 +39900000.0,1.0,1.0,1.0 +39960000.0,1.0,1.0,1.0 +40020000.0,1.0,1.0,1.0 +40080000.0,1.0,1.0,1.0 +40140000.0,1.0,1.0,1.0 +40200000.0,1.0,1.0,1.0 +40260000.0,1.0,1.0,1.0 +40320000.0,1.0,1.0,1.0 +40380000.0,1.0,1.0,1.0 +40440000.0,1.0,1.0,1.0 +40500000.0,1.0,1.0,1.0 +40560000.0,1.0,1.0,1.0 +40620000.0,1.0,1.0,1.0 +40680000.0,1.0,1.0,1.0 +40740000.0,1.0,1.0,1.0 +40800000.0,1.0,1.0,1.0 +40860000.0,1.0,1.0,1.0 +40920000.0,1.0,1.0,1.0 +40980000.0,1.0,1.0,1.0 +41040000.0,1.0,1.0,1.0 +41100000.0,1.0,1.0,1.0 +41160000.0,1.0,1.0,1.0 +41220000.0,1.0,1.0,1.0 +41280000.0,1.0,1.0,1.0 +41340000.0,1.0,1.0,1.0 +41400000.0,1.0,1.0,1.0 +41460000.0,1.0,1.0,1.0 +41520000.0,1.0,1.0,1.0 +41580000.0,1.0,1.0,1.0 +41640000.0,1.0,1.0,1.0 +41700000.0,1.0,1.0,1.0 +41760000.0,1.0,1.0,1.0 +41820000.0,1.0,1.0,1.0 +41880000.0,1.0,1.0,1.0 +41940000.0,1.0,1.0,1.0 +42000000.0,1.0,1.0,1.0 +42060000.0,1.0,1.0,1.0 +42120000.0,1.0,1.0,1.0 +42180000.0,1.0,1.0,1.0 +42240000.0,1.0,1.0,1.0 +42300000.0,1.0,1.0,1.0 +42360000.0,1.0,1.0,1.0 +42420000.0,1.0,1.0,1.0 +42480000.0,1.0,1.0,1.0 +42540000.0,1.0,1.0,1.0 +42600000.0,1.0,1.0,1.0 +42660000.0,1.0,1.0,1.0 +42720000.0,1.0,1.0,1.0 +42780000.0,1.0,1.0,1.0 +42840000.0,1.0,1.0,1.0 +42900000.0,1.0,1.0,1.0 +42960000.0,1.0,1.0,1.0 +43020000.0,1.0,1.0,1.0 +43080000.0,1.0,1.0,1.0 +43140000.0,1.0,1.0,1.0 +43200000.0,1.0,1.0,1.0 +43260000.0,1.0,1.0,1.0 +43320000.0,1.0,1.0,1.0 +43380000.0,1.0,1.0,1.0 +43440000.0,1.0,1.0,1.0 +43500000.0,1.0,1.0,1.0 +43560000.0,1.0,1.0,1.0 +43620000.0,1.0,1.0,1.0 +43680000.0,1.0,1.0,1.0 +43740000.0,1.0,1.0,1.0 +43800000.0,1.0,1.0,1.0 +43860000.0,1.0,1.0,1.0 +43920000.0,1.0,1.0,1.0 +43980000.0,1.0,1.0,1.0 +44040000.0,1.0,1.0,1.0 +44100000.0,1.0,1.0,1.0 +44160000.0,1.0,1.0,1.0 +44220000.0,1.0,1.0,1.0 +44280000.0,1.0,1.0,1.0 +44340000.0,1.0,1.0,1.0 +44400000.0,1.0,1.0,1.0 +44460000.0,1.0,1.0,1.0 +44520000.0,1.0,1.0,1.0 +44580000.0,1.0,1.0,1.0 +44640000.0,1.0,1.0,1.0 +44700000.0,1.0,1.0,1.0 +44760000.0,1.0,1.0,1.0 +44820000.0,1.0,1.0,1.0 +44880000.0,1.0,1.0,1.0 +44940000.0,1.0,1.0,1.0 +45000000.0,1.0,1.0,1.0 +45060000.0,1.0,1.0,1.0 +45120000.0,1.0,1.0,1.0 +45180000.0,1.0,1.0,1.0 +45240000.0,1.0,1.0,1.0 +45300000.0,1.0,1.0,1.0 +45360000.0,1.0,1.0,1.0 +45420000.0,1.0,1.0,1.0 +45480000.0,1.0,1.0,1.0 +45540000.0,1.0,1.0,1.0 +45600000.0,1.0,1.0,1.0 +45660000.0,1.0,1.0,1.0 +45720000.0,1.0,1.0,1.0 +45780000.0,1.0,1.0,1.0 +45840000.0,1.0,1.0,1.0 +45900000.0,1.0,1.0,1.0 +45960000.0,1.0,1.0,1.0 +46020000.0,1.0,1.0,1.0 +46080000.0,1.0,1.0,1.0 +46140000.0,1.0,1.0,1.0 +46200000.0,1.0,1.0,1.0 +46260000.0,1.0,1.0,1.0 +46320000.0,1.0,1.0,1.0 +46380000.0,1.0,1.0,1.0 +46440000.0,1.0,1.0,1.0 +46500000.0,1.0,1.0,1.0 +46560000.0,1.0,1.0,1.0 +46620000.0,1.0,1.0,1.0 +46680000.0,1.0,1.0,1.0 +46740000.0,1.0,1.0,1.0 +46800000.0,1.0,1.0,1.0 +46860000.0,1.0,1.0,1.0 +46920000.0,1.0,1.0,1.0 +46980000.0,1.0,1.0,1.0 +47040000.0,1.0,1.0,1.0 +47100000.0,1.0,1.0,1.0 +47160000.0,1.0,1.0,1.0 +47220000.0,1.0,1.0,1.0 +47280000.0,1.0,1.0,1.0 +47340000.0,1.0,1.0,1.0 +47400000.0,1.0,1.0,1.0 +47460000.0,1.0,1.0,1.0 +47520000.0,1.0,1.0,1.0 +47580000.0,1.0,1.0,1.0 +47640000.0,1.0,1.0,1.0 +47700000.0,1.0,1.0,1.0 +47760000.0,1.0,1.0,1.0 +47820000.0,1.0,1.0,1.0 +47880000.0,1.0,1.0,1.0 +47940000.0,1.0,1.0,1.0 +48000000.0,1.0,1.0,1.0 +48060000.0,1.0,1.0,1.0 +48120000.0,1.0,1.0,1.0 +48180000.0,1.0,1.0,1.0 +48240000.0,1.0,1.0,1.0 +48300000.0,1.0,1.0,1.0 +48360000.0,1.0,1.0,1.0 +48420000.0,1.0,1.0,1.0 +48480000.0,1.0,1.0,1.0 +48540000.0,1.0,1.0,1.0 +48600000.0,1.0,1.0,1.0 +48660000.0,1.0,1.0,1.0 +48720000.0,1.0,1.0,1.0 +48780000.0,1.0,1.0,1.0 +48840000.0,1.0,1.0,1.0 +48900000.0,1.0,1.0,1.0 +48960000.0,1.0,1.0,1.0 +49020000.0,1.0,1.0,1.0 +49080000.0,1.0,1.0,1.0 +49140000.0,1.0,1.0,1.0 +49200000.0,1.0,1.0,1.0 +49260000.0,1.0,1.0,1.0 +49320000.0,1.0,1.0,1.0 +49380000.0,1.0,1.0,1.0 +49440000.0,1.0,1.0,1.0 +49500000.0,1.0,1.0,1.0 +49560000.0,1.0,1.0,1.0 +49620000.0,1.0,1.0,1.0 +49680000.0,1.0,1.0,1.0 +49740000.0,1.0,1.0,1.0 +49800000.0,1.0,1.0,1.0 +49860000.0,1.0,1.0,1.0 +49920000.0,1.0,1.0,1.0 +49980000.0,1.0,1.0,1.0 +50040000.0,1.0,1.0,1.0 +50100000.0,1.0,1.0,1.0 +50160000.0,1.0,1.0,1.0 +50220000.0,1.0,1.0,1.0 +50280000.0,1.0,1.0,1.0 +50340000.0,1.0,1.0,1.0 +50400000.0,1.0,1.0,1.0 +50460000.0,1.0,1.0,1.0 +50520000.0,1.0,1.0,1.0 +50580000.0,1.0,1.0,1.0 +50640000.0,1.0,1.0,1.0 +50700000.0,1.0,1.0,1.0 +50760000.0,1.0,1.0,1.0 +50820000.0,1.0,1.0,1.0 +50880000.0,1.0,1.0,1.0 +50940000.0,1.0,1.0,1.0 +51000000.0,1.0,1.0,1.0 +51060000.0,1.0,1.0,1.0 +51120000.0,1.0,1.0,1.0 +51180000.0,1.0,1.0,1.0 +51240000.0,1.0,1.0,1.0 +51300000.0,1.0,1.0,1.0 +51360000.0,1.0,1.0,1.0 +51420000.0,1.0,1.0,1.0 +51480000.0,1.0,1.0,1.0 +51540000.0,1.0,1.0,1.0 +51600000.0,1.0,1.0,1.0 +51660000.0,1.0,1.0,1.0 +51720000.0,1.0,1.0,1.0 +51780000.0,1.0,1.0,1.0 +51840000.0,1.0,1.0,1.0 +51900000.0,1.0,1.0,1.0 +51960000.0,1.0,1.0,1.0 +52020000.0,1.0,1.0,1.0 +52080000.0,1.0,1.0,1.0 +52140000.0,1.0,1.0,1.0 +52200000.0,1.0,1.0,1.0 +52260000.0,1.0,1.0,1.0 +52320000.0,1.0,1.0,1.0 +52380000.0,1.0,1.0,1.0 +52440000.0,1.0,1.0,1.0 +52500000.0,1.0,1.0,1.0 +52560000.0,1.0,1.0,1.0 +52620000.0,1.0,1.0,1.0 +52680000.0,1.0,1.0,1.0 +52740000.0,1.0,1.0,1.0 +52800000.0,1.0,1.0,1.0 +52860000.0,1.0,1.0,1.0 +52920000.0,1.0,1.0,1.0 +52980000.0,1.0,1.0,1.0 +53040000.0,1.0,1.0,1.0 +53100000.0,1.0,1.0,1.0 +53160000.0,1.0,1.0,1.0 +53220000.0,1.0,1.0,1.0 +53280000.0,1.0,1.0,1.0 +53340000.0,1.0,1.0,1.0 +53400000.0,1.0,1.0,1.0 +53460000.0,1.0,1.0,1.0 +53520000.0,1.0,1.0,1.0 +53580000.0,1.0,1.0,1.0 +53640000.0,1.0,1.0,1.0 +53700000.0,1.0,1.0,1.0 +53760000.0,1.0,1.0,1.0 +53820000.0,1.0,1.0,1.0 +53880000.0,1.0,1.0,1.0 +53940000.0,1.0,1.0,1.0 +54000000.0,1.0,1.0,1.0 +54060000.0,1.0,1.0,1.0 +54120000.0,1.0,1.0,1.0 +54180000.0,1.0,1.0,1.0 +54240000.0,1.0,1.0,1.0 +54300000.0,1.0,1.0,1.0 +54360000.0,1.0,1.0,1.0 +54420000.0,1.0,1.0,1.0 +54480000.0,1.0,1.0,1.0 +54540000.0,1.0,1.0,1.0 +54600000.0,1.0,1.0,1.0 +54660000.0,1.0,1.0,1.0 +54720000.0,1.0,1.0,1.0 +54780000.0,1.0,1.0,1.0 +54840000.0,1.0,1.0,1.0 +54900000.0,1.0,1.0,1.0 +54960000.0,1.0,1.0,1.0 +55020000.0,1.0,1.0,1.0 +55080000.0,1.0,1.0,1.0 +55140000.0,1.0,1.0,1.0 +55200000.0,1.0,1.0,1.0 +55260000.0,1.0,1.0,1.0 +55320000.0,1.0,1.0,1.0 +55380000.0,1.0,1.0,1.0 +55440000.0,1.0,1.0,1.0 +55500000.0,1.0,1.0,1.0 +55560000.0,1.0,1.0,1.0 +55620000.0,1.0,1.0,1.0 +55680000.0,1.0,1.0,1.0 +55740000.0,1.0,1.0,1.0 +55800000.0,1.0,1.0,1.0 +55860000.0,1.0,1.0,1.0 +55920000.0,1.0,1.0,1.0 +55980000.0,1.0,1.0,1.0 +56040000.0,1.0,1.0,1.0 +56100000.0,1.0,1.0,1.0 +56160000.0,1.0,1.0,1.0 +56220000.0,1.0,1.0,1.0 +56280000.0,1.0,1.0,1.0 +56340000.0,1.0,1.0,1.0 +56400000.0,1.0,1.0,1.0 +56460000.0,1.0,1.0,1.0 +56520000.0,1.0,1.0,1.0 +56580000.0,1.0,1.0,1.0 +56640000.0,1.0,1.0,1.0 +56700000.0,1.0,1.0,1.0 +56760000.0,1.0,1.0,1.0 +56820000.0,1.0,1.0,1.0 +56880000.0,1.0,1.0,1.0 +56940000.0,1.0,1.0,1.0 +57000000.0,1.0,1.0,1.0 +57060000.0,1.0,1.0,1.0 +57120000.0,1.0,1.0,1.0 +57180000.0,1.0,1.0,1.0 +57240000.0,1.0,1.0,1.0 +57300000.0,1.0,1.0,1.0 +57360000.0,1.0,1.0,1.0 +57420000.0,1.0,1.0,1.0 +57480000.0,1.0,1.0,1.0 +57540000.0,1.0,1.0,1.0 +57600000.0,1.0,1.0,1.0 +57660000.0,1.0,1.0,1.0 +57720000.0,1.0,1.0,1.0 +57780000.0,1.0,1.0,1.0 +57840000.0,1.0,1.0,1.0 +57900000.0,1.0,1.0,1.0 +57960000.0,1.0,1.0,1.0 +58020000.0,1.0,1.0,1.0 +58080000.0,1.0,1.0,1.0 +58140000.0,1.0,1.0,1.0 +58200000.0,1.0,1.0,1.0 +58260000.0,1.0,1.0,1.0 +58320000.0,1.0,1.0,1.0 +58380000.0,1.0,1.0,1.0 +58440000.0,1.0,1.0,1.0 +58500000.0,1.0,1.0,1.0 +58560000.0,1.0,1.0,1.0 +58620000.0,1.0,1.0,1.0 +58680000.0,1.0,1.0,1.0 +58740000.0,1.0,1.0,1.0 +58800000.0,1.0,1.0,1.0 +58860000.0,1.0,1.0,1.0 +58920000.0,1.0,1.0,1.0 +58980000.0,1.0,1.0,1.0 +59040000.0,1.0,1.0,1.0 +59100000.0,1.0,1.0,1.0 +59160000.0,1.0,1.0,1.0 +59220000.0,1.0,1.0,1.0 +59280000.0,1.0,1.0,1.0 +59340000.0,1.0,1.0,1.0 +59400000.0,1.0,1.0,1.0 +59460000.0,1.0,1.0,1.0 +59520000.0,1.0,1.0,1.0 +59580000.0,1.0,1.0,1.0 +59640000.0,1.0,1.0,1.0 +59700000.0,1.0,1.0,1.0 +59760000.0,1.0,1.0,1.0 +59820000.0,1.0,1.0,1.0 +59880000.0,1.0,1.0,1.0 +59940000.0,1.0,1.0,1.0 +60000000.0,1.0,1.0,1.0 +60060000.0,1.0,1.0,1.0 +60120000.0,1.0,1.0,1.0 +60180000.0,1.0,1.0,1.0 +60240000.0,1.0,1.0,1.0 +60300000.0,1.0,1.0,1.0 +60360000.0,1.0,1.0,1.0 +60420000.0,1.0,1.0,1.0 +60480000.0,1.0,1.0,1.0 +60540000.0,1.0,1.0,1.0 +60600000.0,1.0,1.0,1.0 +60660000.0,1.0,1.0,1.0 +60720000.0,1.0,1.0,1.0 +60780000.0,1.0,1.0,1.0 +60840000.0,1.0,1.0,1.0 +60900000.0,1.0,1.0,1.0 +60960000.0,1.0,1.0,1.0 +61020000.0,1.0,1.0,1.0 +61080000.0,1.0,1.0,1.0 +61140000.0,1.0,1.0,1.0 +61200000.0,1.0,1.0,1.0 +61260000.0,1.0,1.0,1.0 +61320000.0,1.0,1.0,1.0 +61380000.0,1.0,1.0,1.0 +61440000.0,1.0,1.0,1.0 +61500000.0,1.0,1.0,1.0 +61560000.0,1.0,1.0,1.0 +61620000.0,1.0,1.0,1.0 +61680000.0,1.0,1.0,1.0 +61740000.0,1.0,1.0,1.0 +61800000.0,1.0,1.0,1.0 +61860000.0,1.0,1.0,1.0 +61920000.0,1.0,1.0,1.0 +61980000.0,1.0,1.0,1.0 +62040000.0,1.0,1.0,1.0 +62100000.0,1.0,1.0,1.0 +62160000.0,1.0,1.0,1.0 +62220000.0,1.0,1.0,1.0 +62280000.0,1.0,1.0,1.0 +62340000.0,1.0,1.0,1.0 +62400000.0,1.0,1.0,1.0 +62460000.0,1.0,1.0,1.0 +62520000.0,1.0,1.0,1.0 +62580000.0,1.0,1.0,1.0 +62640000.0,1.0,1.0,1.0 +62700000.0,1.0,1.0,1.0 +62760000.0,1.0,1.0,1.0 +62820000.0,1.0,1.0,1.0 +62880000.0,1.0,1.0,1.0 +62940000.0,1.0,1.0,1.0 +63000000.0,1.0,1.0,1.0 +63060000.0,1.0,1.0,1.0 +63120000.0,1.0,1.0,1.0 +63180000.0,1.0,1.0,1.0 +63240000.0,1.0,1.0,1.0 +63300000.0,1.0,1.0,1.0 +63360000.0,1.0,1.0,1.0 +63420000.0,1.0,1.0,1.0 +63480000.0,1.0,1.0,1.0 +63540000.0,1.0,1.0,1.0 +63600000.0,1.0,1.0,1.0 +63660000.0,1.0,1.0,1.0 +63720000.0,1.0,1.0,1.0 +63780000.0,1.0,1.0,1.0 +63840000.0,1.0,1.0,1.0 +63900000.0,1.0,1.0,1.0 +63960000.0,1.0,1.0,1.0 +64020000.0,1.0,1.0,1.0 +64080000.0,1.0,1.0,1.0 +64140000.0,1.0,1.0,1.0 +64200000.0,1.0,1.0,1.0 +64260000.0,1.0,1.0,1.0 +64320000.0,1.0,1.0,1.0 +64380000.0,1.0,1.0,1.0 +64440000.0,1.0,1.0,1.0 +64500000.0,1.0,1.0,1.0 +64560000.0,1.0,1.0,1.0 +64620000.0,1.0,1.0,1.0 +64680000.0,1.0,1.0,1.0 +64740000.0,1.0,1.0,1.0 +64800000.0,1.0,1.0,1.0 +64860000.0,1.0,1.0,1.0 +64920000.0,1.0,1.0,1.0 +64980000.0,1.0,1.0,1.0 +65040000.0,1.0,1.0,1.0 +65100000.0,1.0,1.0,1.0 +65160000.0,1.0,1.0,1.0 +65220000.0,1.0,1.0,1.0 +65280000.0,1.0,1.0,1.0 +65340000.0,1.0,1.0,1.0 +65400000.0,1.0,1.0,1.0 +65460000.0,1.0,1.0,1.0 +65520000.0,1.0,1.0,1.0 +65580000.0,1.0,1.0,1.0 +65640000.0,1.0,1.0,1.0 +65700000.0,1.0,1.0,1.0 +65760000.0,1.0,1.0,1.0 +65820000.0,1.0,1.0,1.0 +65880000.0,1.0,1.0,1.0 +65940000.0,1.0,1.0,1.0 +66000000.0,1.0,1.0,1.0 +66060000.0,1.0,1.0,1.0 +66120000.0,1.0,1.0,1.0 +66180000.0,1.0,1.0,1.0 +66240000.0,1.0,1.0,1.0 +66300000.0,1.0,1.0,1.0 +66360000.0,1.0,1.0,1.0 +66420000.0,1.0,1.0,1.0 +66480000.0,1.0,1.0,1.0 +66540000.0,1.0,1.0,1.0 +66600000.0,1.0,1.0,1.0 +66660000.0,1.0,1.0,1.0 +66720000.0,1.0,1.0,1.0 +66780000.0,1.0,1.0,1.0 +66840000.0,1.0,1.0,1.0 +66900000.0,1.0,1.0,1.0 +66960000.0,1.0,1.0,1.0 +67020000.0,1.0,1.0,1.0 +67080000.0,1.0,1.0,1.0 +67140000.0,1.0,1.0,1.0 +67200000.0,1.0,1.0,1.0 +67260000.0,1.0,1.0,1.0 +67320000.0,1.0,1.0,1.0 +67380000.0,1.0,1.0,1.0 +67440000.0,1.0,1.0,1.0 +67500000.0,1.0,1.0,1.0 +67560000.0,1.0,1.0,1.0 +67620000.0,1.0,1.0,1.0 +67680000.0,1.0,1.0,1.0 +67740000.0,1.0,1.0,1.0 +67800000.0,1.0,1.0,1.0 +67860000.0,1.0,1.0,1.0 +67920000.0,1.0,1.0,1.0 +67980000.0,1.0,1.0,1.0 +68040000.0,1.0,1.0,1.0 +68100000.0,1.0,1.0,1.0 +68160000.0,1.0,1.0,1.0 +68220000.0,1.0,1.0,1.0 +68280000.0,1.0,1.0,1.0 +68340000.0,1.0,1.0,1.0 +68400000.0,1.0,1.0,1.0 +68460000.0,1.0,1.0,1.0 +68520000.0,1.0,1.0,1.0 +68580000.0,1.0,1.0,1.0 +68640000.0,1.0,1.0,1.0 +68700000.0,1.0,1.0,1.0 +68760000.0,1.0,1.0,1.0 +68820000.0,1.0,1.0,1.0 +68880000.0,1.0,1.0,1.0 +68940000.0,1.0,1.0,1.0 +69000000.0,1.0,1.0,1.0 +69060000.0,1.0,1.0,1.0 +69120000.0,1.0,1.0,1.0 +69180000.0,1.0,1.0,1.0 +69240000.0,1.0,1.0,1.0 +69300000.0,1.0,1.0,1.0 +69360000.0,1.0,1.0,1.0 +69420000.0,1.0,1.0,1.0 +69480000.0,1.0,1.0,1.0 +69540000.0,1.0,1.0,1.0 +69600000.0,1.0,1.0,1.0 +69660000.0,1.0,1.0,1.0 +69720000.0,1.0,1.0,1.0 +69780000.0,1.0,1.0,1.0 +69840000.0,1.0,1.0,1.0 +69900000.0,1.0,1.0,1.0 +69960000.0,1.0,1.0,1.0 +70020000.0,1.0,1.0,1.0 +70080000.0,1.0,1.0,1.0 +70140000.0,1.0,1.0,1.0 +70200000.0,1.0,1.0,1.0 +70260000.0,1.0,1.0,1.0 +70320000.0,1.0,1.0,1.0 +70380000.0,1.0,1.0,1.0 +70440000.0,1.0,1.0,1.0 +70500000.0,1.0,1.0,1.0 +70560000.0,1.0,1.0,1.0 +70620000.0,1.0,1.0,1.0 +70680000.0,1.0,1.0,1.0 +70740000.0,1.0,1.0,1.0 +70800000.0,1.0,1.0,1.0 +70860000.0,1.0,1.0,1.0 +70920000.0,1.0,1.0,1.0 +70980000.0,1.0,1.0,1.0 +71040000.0,1.0,1.0,1.0 +71100000.0,1.0,1.0,1.0 +71160000.0,1.0,1.0,1.0 +71220000.0,1.0,1.0,1.0 +71280000.0,1.0,1.0,1.0 +71340000.0,1.0,1.0,1.0 +71400000.0,1.0,1.0,1.0 +71460000.0,1.0,1.0,1.0 +71520000.0,1.0,1.0,1.0 +71580000.0,1.0,1.0,1.0 +71640000.0,1.0,1.0,1.0 +71700000.0,1.0,1.0,1.0 +71760000.0,1.0,1.0,1.0 +71820000.0,1.0,1.0,1.0 +71880000.0,1.0,1.0,1.0 +71940000.0,1.0,1.0,1.0 +72006000.0,1.1,1.0,1.0 +72072000.0,1.1,1.0,1.0 +72138000.0,1.1,1.0,1.0 +72204000.0,1.1,1.0,1.0 +72270000.0,1.1,1.0,1.0 +72336000.0,1.1,1.0,1.0 +72402000.0,1.1,1.0,1.0 +72468000.0,1.1,1.0,1.0 +72534000.0,1.1,1.0,1.0 +72600000.0,1.1,1.0,1.0 +72666000.0,1.1,1.0,1.0 +72732000.0,1.1,1.0,1.0 +72798000.0,1.1,1.0,1.0 +72864000.0,1.1,1.0,1.0 +72930000.0,1.1,1.0,1.0 +72996000.0,1.1,1.0,1.0 +73062000.0,1.1,1.0,1.0 +73128000.0,1.1,1.0,1.0 +73194000.0,1.1,1.0,1.0 +73260000.0,1.1,1.0,1.0 +73326000.0,1.1,1.0,1.0 +73392000.0,1.1,1.0,1.0 +73458000.0,1.1,1.0,1.0 +73524000.0,1.1,1.0,1.0 +73590000.0,1.1,1.0,1.0 +73656000.0,1.1,1.0,1.0 +73722000.0,1.1,1.0,1.0 +73788000.0,1.1,1.0,1.0 +73854000.0,1.1,1.0,1.0 +73920000.0,1.1,1.0,1.0 +73986000.0,1.1,1.0,1.0 +74052000.0,1.1,1.0,1.0 +74118000.0,1.1,1.0,1.0 +74184000.0,1.1,1.0,1.0 +74250000.0,1.1,1.0,1.0 +74316000.0,1.1,1.0,1.0 +74382000.0,1.1,1.0,1.0 +74448000.0,1.1,1.0,1.0 +74514000.0,1.1,1.0,1.0 +74580000.0,1.1,1.0,1.0 +74646000.0,1.1,1.0,1.0 +74712000.0,1.1,1.0,1.0 +74778000.0,1.1,1.0,1.0 +74844000.0,1.1,1.0,1.0 +74910000.0,1.1,1.0,1.0 +74976000.0,1.1,1.0,1.0 +75042000.0,1.1,1.0,1.0 +75108000.0,1.1,1.0,1.0 +75174000.0,1.1,1.0,1.0 +75240000.0,1.1,1.0,1.0 +75306000.0,1.1,1.0,1.0 +75372000.0,1.1,1.0,1.0 +75438000.0,1.1,1.0,1.0 +75504000.0,1.1,1.0,1.0 +75570000.0,1.1,1.0,1.0 +75636000.0,1.1,1.0,1.0 +75702000.0,1.1,1.0,1.0 +75768000.0,1.1,1.0,1.0 +75834000.0,1.1,1.0,1.0 +75900000.0,1.1,1.0,1.0 +75966000.0,1.1,1.0,1.0 +76032000.0,1.1,1.0,1.0 +76098000.0,1.1,1.0,1.0 +76164000.0,1.1,1.0,1.0 +76230000.0,1.1,1.0,1.0 +76296000.0,1.1,1.0,1.0 +76362000.0,1.1,1.0,1.0 +76428000.0,1.1,1.0,1.0 +76494000.0,1.1,1.0,1.0 +76560000.0,1.1,1.0,1.0 +76626000.0,1.1,1.0,1.0 +76692000.0,1.1,1.0,1.0 +76758000.0,1.1,1.0,1.0 +76824000.0,1.1,1.0,1.0 +76890000.0,1.1,1.0,1.0 +76956000.0,1.1,1.0,1.0 +77022000.0,1.1,1.0,1.0 +77088000.0,1.1,1.0,1.0 +77154000.0,1.1,1.0,1.0 +77220000.0,1.1,1.0,1.0 +77286000.0,1.1,1.0,1.0 +77352000.0,1.1,1.0,1.0 +77418000.0,1.1,1.0,1.0 +77484000.0,1.1,1.0,1.0 +77550000.0,1.1,1.0,1.0 +77616000.0,1.1,1.0,1.0 +77682000.0,1.1,1.0,1.0 +77748000.0,1.1,1.0,1.0 +77814000.0,1.1,1.0,1.0 +77880000.0,1.1,1.0,1.0 +77946000.0,1.1,1.0,1.0 +78012000.0,1.1,1.0,1.0 +78078000.0,1.1,1.0,1.0 +78144000.0,1.1,1.0,1.0 +78210000.0,1.1,1.0,1.0 +78276000.0,1.1,1.0,1.0 +78342000.0,1.1,1.0,1.0 +78408000.0,1.1,1.0,1.0 +78474000.0,1.1,1.0,1.0 +78540000.0,1.1,1.0,1.0 +78606000.0,1.1,1.0,1.0 +78672000.0,1.1,1.0,1.0 +78738000.0,1.1,1.0,1.0 +78804000.0,1.1,1.0,1.0 +78870000.0,1.1,1.0,1.0 +78936000.0,1.1,1.0,1.0 +79002000.0,1.1,1.0,1.0 +79068000.0,1.1,1.0,1.0 +79134000.0,1.1,1.0,1.0 +79200000.0,1.1,1.0,1.0 +79266000.0,1.1,1.0,1.0 +79332000.0,1.1,1.0,1.0 +79398000.0,1.1,1.0,1.0 +79464000.0,1.1,1.0,1.0 +79530000.0,1.1,1.0,1.0 +79596000.0,1.1,1.0,1.0 +79662000.0,1.1,1.0,1.0 +79728000.0,1.1,1.0,1.0 +79794000.0,1.1,1.0,1.0 +79860000.0,1.1,1.0,1.0 +79926000.0,1.1,0.9998957355854446,0.9998957355854446 +79992000.0,1.1,0.9996872502360732,0.999791492910759 +80058000.0,1.1,0.9993746200108351,0.9996872719691441 +80124000.0,1.1,0.9989579535025962,0.9995830727538044 +80190000.0,1.1,0.9984373917759143,0.9994788952579469 +80256000.0,1.1,0.9978131082879343,0.9993747394747812 +80322000.0,1.1,0.9970853087924654,0.99927060539752 +80388000.0,1.1,0.9962542312273122,0.999166493019379 +80454000.0,1.1,0.9953201455849489,0.9990624023335765 +80520000.0,1.1,0.9942833537666311,0.9989583333333333 +80586000.0,1.1,0.9931441894200597,0.9988542860118738 +80652000.0,1.1,0.9919030177607137,0.9987502603624245 +80718000.0,1.1,0.9905602353769908,0.998646256378215 +80784000.0,1.1,0.9891162700192984,0.9985422740524781 +80850000.0,1.1,0.9875715803732504,0.9984383133784487 +80916000.0,1.1,0.9859266558171426,0.998334374349365 +80982000.0,1.1,0.9841820161638802,0.9982304569584678 +81048000.0,1.1,0.9823382113875533,0.9981265611990009 +81114000.0,1.1,0.9803958213348565,0.9980226870642106 +81180000.0,1.1,0.9783554554215684,0.9979188345473464 +81246000.0,1.1,0.9762177523143108,0.9978150036416606 +81312000.0,1.1,0.9739833795978193,0.9977111943404077 +81378000.0,1.1,0.9716530334279712,0.9976074066368459 +81444000.0,1.1,0.9692274381708178,0.9975036405242353 +81510000.0,1.1,0.9667073460278879,0.9973998959958398 +81576000.0,1.1,0.9640935366480288,0.9972961730449251 +81642000.0,1.1,0.961386816726068,0.9971924716647603 +81708000.0,1.1,0.958588019588583,0.9970887918486171 +81774000.0,1.1,0.9556980047670767,0.9969851335897703 +81840000.0,1.1,0.9527176575588633,0.9968814968814969 +81906000.0,1.1,0.9496478885759794,0.996777881717077 +81972000.0,1.1,0.9464896332824403,0.9966742880897941 +82038000.0,1.1,0.9432438515201707,0.9965707159929336 +82104000.0,1.1,0.9399115270239439,0.9964671654197838 +82170000.0,1.1,0.9364936669256748,0.9963636363636362 +82236000.0,1.1,0.9329913012484127,0.996260128817785 +82302000.0,1.1,0.9294054823903891,0.996156642775527 +82368000.0,1.1,0.9257372845994841,0.9960531782301622 +82434000.0,1.1,0.9219878034384726,0.9959497351749923 +82500000.0,1.1,0.9181581552414281,0.9958463136033229 +82566000.0,1.1,0.9142494765616546,0.9957429135084622 +82632000.0,1.1,0.910262923611531,0.9956395348837209 +82698000.0,1.1,0.9061996716946521,0.9955361777224127 +82764000.0,1.1,0.9020609146306532,0.9954328420178533 +82830000.0,1.1,0.8978478641731151,0.9953295277633628 +82896000.0,1.1,0.8935617494209395,0.9952262349522621 +82962000.0,1.1,0.8892038162235975,0.9951229635778769 +83028000.0,1.1,0.8847753265806494,0.9950197136335338 +83094000.0,1.1,0.8802775580359402,0.9949164851125636 +83160000.0,1.1,0.8757118030668741,0.9948132780082988 +83220000.0,1.0,0.8710793684691757,0.9947100923140753 +83280000.0,1.0,0.8663815747375434,0.9946069280232316 +83340000.0,1.0,0.861619755442605,0.9945037851291091 +83400000.0,1.0,0.8567952566045813,0.9944006636250519 +83460000.0,1.0,0.8519094360640679,0.9942975635044063 +83520000.0,1.0,0.8469636628503432,0.9941944847605224 +83580000.0,1.0,0.8419593165476097,0.9940914273867523 +83640000.0,1.0,0.8368977866595746,0.9939883913764509 +83700000.0,1.0,0.8317804719727764,0.9938853767229764 +83760000.0,1.0,0.8266087799190596,0.993782383419689 +83820000.0,1.0,0.8213841259376004,0.9936794114599523 +83880000.0,1.0,0.8161079328368822,0.9935764608371321 +83940000.0,1.0,0.8107816301570185,0.9934735315445975 +84000000.0,1.0,0.805406653532816,0.9933706235757197 +84060000.0,1.0,0.7999844440579705,0.9932677369238736 +84120000.0,1.0,0.7945164476507806,0.9931648715824357 +84180000.0,1.0,0.7890041144217651,0.9930620275447861 +84240000.0,1.0,0.7834488980435625,0.9929592048043073 +84300000.0,1.0,0.7778522551234873,0.9928564033543844 +84360000.0,1.0,0.7722156445791142,0.9927536231884058 +84420000.0,1.0,0.7665405270172554,0.9926508642997618 +84480000.0,1.0,0.760828364116692,0.9925481266818462 +84540000.0,1.0,0.7550806180150135,0.9924454103280553 +84600000.0,1.0,0.749298750699915,0.9923427152317881 +84660000.0,1.0,0.7434842234052959,0.9922400413864458 +84720000.0,1.0,0.737638496012496,0.9921373887854333 +84780000.0,1.0,0.7317630264570019,0.992034757422158 +84840000.0,1.0,0.7258592701409441,0.9919321472900289 +84900000.0,1.0,0.7199286793517069,0.9918295583824595 +84960000.0,1.0,0.7139727026869563,0.9917269906928643 +85020000.0,1.0,0.7079927844863935,0.9916244442146623 +85080000.0,1.0,0.7019903642705245,0.9915219189412737 +85140000.0,1.0,0.6959668761867394,0.9914194148661222 +85200000.0,1.0,0.6899237484629761,0.9913169319826339 +85260000.0,1.0,0.6838624028692445,0.9912144702842377 +85320000.0,1.0,0.6777842541872732,0.9911120297643655 +85380000.0,1.0,0.6716907096885346,0.9910096104164513 +85440000.0,1.0,0.6655831686208976,0.9909072122339326 +85500000.0,1.0,0.6594630217041438,0.990804835210249 +85560000.0,1.0,0.6533316506345804,0.9907024793388428 +85620000.0,1.0,0.6471904275989696,0.9906001446131596 +85680000.0,1.0,0.6410407147979879,0.9904978310266473 +85740000.0,1.0,0.634883863979418,0.9903955385727565 +85800000.0,1.0,0.62872121598127,0.99029326724494 +85860000.0,1.0,0.6225541002850159,0.9901910170366547 +85920000.0,1.0,0.6163838345791145,0.9900887879413588 +85980000.0,1.0,0.6102117243329934,0.9899865799525136 +86040000.0,1.0,0.6040390623816481,0.9898843930635838 +86100000.0,1.0,0.5978671285210037,0.9897822272680359 +86160000.0,1.0,0.5916971891141822,0.9896800825593397 +86220000.0,1.0,0.5855304967088028,0.9895779589309669 +86280000.0,1.0,0.5793682896654372,0.9894758563763927 +86340000.0,1.0,0.5732117917973324,0.9893737748890953 +86400000.0,1.0,0.5670622120214996,0.9892717144625541 +86460000.0,1.0,0.5609207440212668,0.9891696750902527 +86520000.0,1.0,0.5547885659203742,0.9890676567656764 +86580000.0,1.0,0.5486668399686901,0.9889656594823141 +86640000.0,1.0,0.54255671223961,0.9888636832336563 +86700000.0,1.0,0.5364593123391957,0.9887617280131972 +86760000.0,1.0,0.5303757531271017,0.9886597938144329 +86820000.0,1.0,0.5243071304493254,0.9885578806308629 +86880000.0,1.0,0.5182545228828108,0.9884559884559884 +86940000.0,1.0,0.5122189914919257,0.9883541172833143 +87000000.0,1.0,0.5062015795968227,0.9882522671063477 +87060000.0,1.0,0.5002033125536866,0.9881504379185984 +87120000.0,1.0,0.4942251975468632,0.9880486297135793 +87180000.0,1.0,0.48826822339285236,0.9879468424848047 +87240000.0,1.0,0.4823333603561447,0.9878450762257929 +87300000.0,1.0,0.4764215599768696,0.9877433309300646 +87360000.0,1.0,0.47053375491021404,0.987641606591143 +87420000.0,1.0,0.464670858777567,0.9875399032025538 +87480000.0,1.0,0.45883376602933146,0.9874382207578253 +87540000.0,1.0,0.45302335181934406,0.987336559250489 +87600000.0,1.0,0.4472404718908286,0.9872349186740785 +87660000.0,1.0,0.44148596247380817,0.9871332990221308 +87720000.0,1.0,0.4357606401938885,0.9870317002881845 +87780000.0,1.0,0.4300653019923218,0.9869301224657817 +87840000.0,1.0,0.42440072505725107,0.9868285655484668 +87900000.0,1.0,0.4187676667660292,0.9867270295297871 +87960000.0,1.0,0.4131668646385,0.9866255144032922 +88020000.0,1.0,0.4075990363011228,0.9865240201625347 +88080000.0,1.0,0.40206487946181524,0.9864225468010697 +88140000.0,1.0,0.39656507189538287,0.9863210943124548 +88200000.0,1.0,0.39110027143939957,0.9862196626902509 +88260000.0,1.0,0.38567111600039505,0.9861182519280206 +88320000.0,1.0,0.3802782235702024,0.9860168620193297 +88380000.0,1.0,0.37492219225231216,0.9859154929577463 +88440000.0,1.0,0.369603600298075,0.985814144736842 +88500000.0,1.0,0.3643230061525891,0.9857128173501902 +88560000.0,1.0,0.3590809485101058,0.9856115107913669 +88620000.0,1.0,0.3538779463787806,0.9855102250539511 +88680000.0,1.0,0.3487144991545937,0.9854089601315248 +88740000.0,1.0,0.34359108670425903,0.9853077160176718 +88800000.0,1.0,0.338508169456939,0.9852064927059792 +88860000.0,1.0,0.33346618850457577,0.9851052901900359 +88920000.0,1.0,0.3284655657106493,0.9850041084634346 +88980000.0,1.0,0.3235067038271672,0.98490294751977 +89040000.0,1.0,0.3185899866196892,0.9848018073526391 +89100000.0,1.0,0.31371577900018677,0.9847006879556421 +89160000.0,1.0,0.308884427167535,0.9845995893223819 +89220000.0,1.0,0.3040962587554317,0.9844985114464633 +89280000.0,1.0,0.2993515829875375,0.9843974543214946 +89340000.0,1.0,0.29465069083962686,0.9842964179410859 +89400000.0,1.0,0.28999385520854076,0.9841954022988504 +89460000.0,1.0,0.2853813310877277,0.9840944073884044 +89520000.0,1.0,0.28081335574915944,0.9839934332033653 +89580000.0,1.0,0.2762901489314086,0.9838924797373552 +89640000.0,1.0,0.27181191303366925,0.9837915469839966 +89700000.0,1.0,0.26737883331550805,0.9836906349369166 +89760000.0,1.0,0.26299107810212535,0.9835897435897436 +89820000.0,1.0,0.25864879899491156,0.9834888729361093 +89880000.0,1.0,0.25435213108707977,0.9833880229696473 +89940000.0,1.0,0.2501011931841582,0.9832871936839946 +90000000.0,1.0,0.24589608802912413,0.9831863850727905 +90060000.0,1.0,0.24173690253196312,0.983085597129677 +90120000.0,1.0,0.23762370800343652,0.9829848298482986 +90180000.0,1.0,0.23355656039284162,0.9828840832223017 +90240000.0,1.0,0.2295355005295502,0.982783357245337 +90300000.0,1.0,0.22556055436811012,0.9826826519110565 +90360000.0,1.0,0.22163173323669835,0.9825819672131146 +90420000.0,1.0,0.2177713444405673,0.9825819672131146 +90480000.0,1.0,0.2139781960230574,0.9825819672131146 +90540000.0,1.0,0.2102511167890492,0.9825819672131146 +90600000.0,1.0,0.2065889559433383,0.9825819672131146 +90660000.0,1.0,0.2029905827353088,0.9825819672131145 +90720000.0,1.0,0.19945488610979623,0.9825819672131146 +90780000.0,1.0,0.19598077436403133,0.9825819672131147 +90840000.0,1.0,0.19256717481055946,0.9825819672131147 +90900000.0,1.0,0.18921303344603124,0.9825819672131146 +90960000.0,1.0,0.18591731462576222,0.9825819672131145 +91020000.0,1.0,0.182679000743961,0.9825819672131146 +91080000.0,1.0,0.17949709191952723,0.9825819672131145 +91140000.0,1.0,0.17637060568732235,0.9825819672131147 +91200000.0,1.0,0.17329857669481774,0.9825819672131146 +91260000.0,1.0,0.17028005640402682,0.9825819672131145 +91320000.0,1.0,0.16731411279862882,0.9825819672131147 +91380000.0,1.0,0.16439983009619366,0.9825819672131146 +91440000.0,1.0,0.16153630846541978,0.9825819672131146 +91500000.0,1.0,0.15872266374829666,0.9825819672131146 +91560000.0,1.0,0.15595802718710705,0.9825819672131146 +91620000.0,1.0,0.15324154515618407,0.9825819672131147 +91680000.0,1.0,0.1505723788983407,0.9825819672131146 +91740000.0,1.0,0.14794970426589008,0.9825819672131147 +91800000.0,1.0,0.1453727114661768,0.9825819672131146 +91860000.0,1.0,0.1428406048115405,0.9825819672131146 +91920000.0,1.0,0.14035260247363457,0.9825819672131146 +91980000.0,1.0,0.13790793624202413,0.9825819672131147 +92040000.0,1.0,0.13550585128698886,0.9825819672131146 +92100000.0,1.0,0.13314560592645727,0.9825819672131146 +92160000.0,1.0,0.13082647139700054,0.9825819672131147 +92220000.0,1.0,0.12854773162881508,0.9825819672131147 +92280000.0,1.0,0.12630868302462464,0.9825819672131146 +92340000.0,1.0,0.12410863424243343,0.9825819672131147 +92400000.0,1.0,0.12194690598206316,0.9825819672131146 +92460000.0,1.0,0.11982283077540837,0.9825819672131147 +92520000.0,1.0,0.11773575278034487,0.9825819672131145 +92580000.0,1.0,0.1156850275782282,0.9825819672131146 +92640000.0,1.0,0.11367002197491888,0.9825819672131146 +92700000.0,1.0,0.11169011380527376,0.9825819672131146 +92760000.0,1.0,0.10974469174104254,0.9825819672131146 +92820000.0,1.0,0.10783315510211043,0.9825819672131146 +92880000.0,1.0,0.10595491367102858,0.9825819672131146 +92940000.0,1.0,0.104109387510775,0.9825819672131147 +93000000.0,1.0,0.10229600678568977,0.9825819672131146 +93060000.0,1.0,0.10051421158552919,0.9825819672131147 +93120000.0,1.0,0.09876345175258451,0.9825819672131146 +93180000.0,1.0,0.09704318671181203,0.9825819672131147 +93240000.0,1.0,0.09535288530392186,0.9825819672131147 +93300000.0,1.0,0.09369202562137403,0.9825819672131147 +93360000.0,1.0,0.09206009484723122,0.9825819672131145 +93420000.0,1.0,0.09045658909681838,0.9825819672131146 +93480000.0,1.0,0.08888101326214018,0.9825819672131146 +93540000.0,1.0,0.08733288085900863,0.9825819672131146 +93600000.0,1.0,0.08581171387683327,0.9825819672131147 +93660000.0,1.0,0.08431704263102777,0.9825819672131146 +93720000.0,1.0,0.08284840561798731,0.9825819672131146 +93780000.0,1.0,0.08140534937259203,0.9825819672131145 +93840000.0,1.0,0.07998742832819236,0.9825819672131146 +93900000.0,1.0,0.07859420467903326,0.9825819672131146 +93960000.0,1.0,0.07722524824507468,0.9825819672131146 +94020000.0,1.0,0.07588013633916661,0.9825819672131147 +94080000.0,1.0,0.07455845363653768,0.9825819672131146 +94140000.0,1.0,0.073259792046557,0.9825819672131146 +94200000.0,1.0,0.07198375058672966,0.9825819672131146 +94260000.0,1.0,0.07072993525888703,0.9825819672131146 +94320000.0,1.0,0.06949795892753347,0.9825819672131147 +94380000.0,1.0,0.06828744120031208,0.9825819672131146 +94440000.0,1.0,0.06709800831055253,0.9825819672131145 +94500000.0,1.0,0.06592929300186462,0.9825819672131146 +94560000.0,1.0,0.06478093441474196,0.9825819672131145 +94620000.0,1.0,0.06365257797514091,0.9825819672131146 +94680000.0,1.0,0.06254387528500013,0.9825819672131146 +94740000.0,1.0,0.06145448401466714,0.9825819672131147 +94800000.0,1.0,0.06038406779719854,0.9825819672131146 +94860000.0,1.0,0.05933229612450143,0.9825819672131146 +94920000.0,1.0,0.05829884424528368,0.9825819672131147 +94980000.0,1.0,0.05728339306478181,0.9825819672131146 +95040000.0,1.0,0.056285629046235394,0.9825819672131146 +95100000.0,1.0,0.0553052441140776,0.9825819672131146 +95160000.0,1.0,0.054341935558811894,0.9825819672131146 +95220000.0,1.0,0.05339540594354569,0.9825819672131145 +95280000.0,1.0,0.05246536301215196,0.9825819672131146 +95340000.0,1.0,0.051551519599030454,0.9825819672131146 +95400000.0,1.0,0.05065359354044078,0.9825819672131146 +95460000.0,1.0,0.04977130758737981,0.9825819672131145 +95520000.0,1.0,0.04890438931997667,0.9825819672131145 +95580000.0,1.0,0.048052571063378714,0.9825819672131147 +95640000.0,1.0,0.047215589805102647,0.9825819672131146 +95700000.0,1.0,0.046393187113825236,0.9825819672131146 +95760000.0,1.0,0.04558510905958852,0.9825819672131146 +95820000.0,1.0,0.044791106135394866,0.9825819672131147 +95880000.0,1.0,0.044010933180167695,0.9825819672131146 +95940000.0,1.0,0.04324434930305411,0.9825819672131145 +96000000.0,1.0,0.04249111780904599,0.9825819672131146 +96060000.0,1.0,0.04175100612589662,0.9825819672131146 +96120000.0,1.0,0.041023785732310296,0.9825819672131145 +96180000.0,1.0,0.04030923208738276,0.9825819672131147 +96240000.0,1.0,0.03960712456127056,0.9825819672131146 +96300000.0,1.0,0.0389172463670681,0.9825819672131147 +96360000.0,1.0,0.038239384493871216,0.9825819672131147 +96420000.0,1.0,0.03757332964100665,0.9825819672131146 +96480000.0,1.0,0.03691887615340715,0.9825819672131147 +96540000.0,1.0,0.036275821958112145,0.9825819672131146 +96600000.0,1.0,0.03564396850187453,0.9825819672131146 +96660000.0,1.0,0.03502312068985417,0.9825819672131146 +96720000.0,1.0,0.03441308682537925,0.9825819672131145 +96780000.0,1.0,0.03381367855075686,0.9825819672131146 +96840000.0,1.0,0.03322471078911457,0.9825819672131146 +96900000.0,1.0,0.03264600168725499,0.9825819672131146 +96960000.0,1.0,0.032077372559505664,0.9825819672131145 +97020000.0,1.0,0.031518647832547056,0.9825819672131146 +97080000.0,1.0,0.03096965499120146,0.9825819672131147 +97140000.0,1.0,0.030430224525166187,0.9825819672131146 +97200000.0,1.0,0.029900189876674556,0.9825819672131145 +97260000.0,1.0,0.02937938738906854,0.9825819672131146 +97320000.0,1.0,0.028867656256267134,0.9825819672131145 +97380000.0,1.0,0.028364838473114936,0.9825819672131146 +97440000.0,1.0,0.027870778786595513,0.9825819672131146 +97500000.0,1.0,0.027385324647894563,0.9825819672131146 +97560000.0,1.0,0.026908326165298035,0.9825819672131146 +97620000.0,1.0,0.02643963605791067,0.9825819672131146 +97680000.0,1.0,0.025979109610180663,0.9825819672131146 +97740000.0,1.0,0.025526604627216447,0.9825819672131146 +97800000.0,1.0,0.02508198139088173,0.9825819672131145 +97860000.0,1.0,0.024645102616655305,0.9825819672131147 +97920000.0,1.0,0.024215833411242248,0.9825819672131146 +97980000.0,1.0,0.023794041230923477,0.9825819672131146 +98040000.0,1.0,0.023379595840630752,0.9825819672131147 +98100000.0,1.0,0.022972369273734517,0.9825819672131146 +98160000.0,1.0,0.02257223579253217,0.9825819672131146 +98220000.0,1.0,0.022179071849424536,0.9825819672131145 +98280000.0,1.0,0.021792756048768572,0.9825819672131146 +98340000.0,1.0,0.021413169109394527,0.9825819672131146 +98400000.0,1.0,0.021040193827775975,0.9825819672131147 +98460000.0,1.0,0.02067371504184135,0.9825819672131145 +98520000.0,1.0,0.02031361959541583,0.9825819672131145 +98580000.0,1.0,0.01995979630328256,0.9825819672131147 +98640000.0,1.0,0.019612135916852434,0.9825819672131147 +98700000.0,1.0,0.019270531090431847,0.9825819672131146 +98760000.0,1.0,0.01893487634807801,0.9825819672131146 +98820000.0,1.0,0.018605068051031567,0.9825819672131146 +98880000.0,1.0,0.018281004365716464,0.9825819672131145 +98940000.0,1.0,0.01796258523229722,0.9825819672131146 +99000000.0,1.0,0.017649712333783842,0.9825819672131145 +99060000.0,1.0,0.0173422890656749,0.9825819672131146 +99120000.0,1.0,0.01704022050612933,0.9825819672131146 +99180000.0,1.0,0.016743413386657816,0.9825819672131147 +99240000.0,1.0,0.016451776063324635,0.9825819672131146 +99300000.0,1.0,0.01616521848845115,0.9825819672131145 +99360000.0,1.0,0.015883652182812142,0.9825819672131146 +99420000.0,1.0,0.015606990208316435,0.9825819672131145 +99480000.0,1.0,0.01533514714116338,0.9825819672131146 +99540000.0,1.0,0.015068039045466884,0.9825819672131145 +99600000.0,1.0,0.014805583447338871,0.9825819672131145 +99660000.0,1.0,0.014547699309424155,0.9825819672131146 +99720000.0,1.0,0.014294307005878856,0.9825819672131146 +99780000.0,1.0,0.014045328297784652,0.9825819672131146 +99840000.0,1.0,0.013800686308991271,0.9825819672131147 +99900000.0,1.0,0.01356030550237974,0.9825819672131145 +99960000.0,1.0,0.013324111656539107,0.9825819672131146 +100020000.0,1.0,0.013092031842849388,0.9825819672131146 +100080000.0,1.0,0.012863994402963689,0.9825819672131145 +100140000.0,1.0,0.012639928926682557,0.9825819672131146 +100200000.0,1.0,0.0124197662302137,0.9825819672131146 +100260000.0,1.0,0.012203438334810385,0.9825819672131145 +100320000.0,1.0,0.011990878445781925,0.9825819672131147 +100380000.0,1.0,0.011782020931869738,0.9825819672131146 +100440000.0,1.0,0.011576801304982662,0.9825819672131146 +100500000.0,1.0,0.011375156200285216,0.9825819672131146 +100560000.0,1.0,0.011177023356632705,0.9825819672131145 +100620000.0,1.0,0.010982341597347093,0.9825819672131146 +100680000.0,1.0,0.010791050811327727,0.9825819672131146 +100740000.0,1.0,0.010603091934491075,0.9825819672131146 +100800000.0,1.0,0.01041840693153375,0.9825819672131146 +100860000.0,1.0,0.010236938778013181,0.9825819672131146 +100920000.0,1.0,0.010058631442740409,0.9825819672131146 +100980000.0,1.0,0.00988342987047956,0.9825819672131146 +101040000.0,1.0,0.009711279964948666,0.9825819672131146 +101100000.0,1.0,0.009542128572116566,0.9825819672131145 +101160000.0,1.0,0.009375923463790764,0.9825819672131146 +101220000.0,1.0,0.009212613321491129,0.9825819672131146 +101280000.0,1.0,0.0090521477206045,0.9825819672131146 +101340000.0,1.0,0.008894477114815282,0.9825819672131147 +101400000.0,1.0,0.008739552820807227,0.9825819672131145 +101460000.0,1.0,0.00858732700323169,0.9825819672131145 +101520000.0,1.0,0.008437752659937695,0.9825819672131146 +101580000.0,1.0,0.00829078360745927,0.9825819672131145 +101640000.0,1.0,0.008146374466755573,0.9825819672131146 +101700000.0,1.0,0.00800448064919938,0.9825819672131147 +101760000.0,1.0,0.007865058342809634,0.9825819672131145 +101820000.0,1.0,0.00772806449872381,0.9825819672131147 +101880000.0,1.0,0.007593456817905873,0.9825819672131145 +101940000.0,1.0,0.007461193738085791,0.9825819672131146 +102000000.0,1.0,0.00733123442092651,0.9825819672131147 +102060000.0,1.0,0.007203538739414469,0.9825819672131145 +102120000.0,1.0,0.007078067265469749,0.9825819672131146 +102180000.0,1.0,0.006954781257772017,0.9825819672131147 +102240000.0,1.0,0.006833642649798528,0.9825819672131146 +102300000.0,1.0,0.006714614038070479,0.9825819672131145 +102360000.0,1.0,0.006597658670604086,0.9825819672131146 +102420000.0,1.0,0.006482740435562825,0.9825819672131145 +102480000.0,1.0,0.006369823850107325,0.9825819672131147 +102540000.0,1.0,0.006258874049439471,0.9825819672131146 +102600000.0,1.0,0.006149856776037348,0.9825819672131145 +102660000.0,1.0,0.0060427383690776805,0.9825819672131147 +102720000.0,1.0,0.005937485754042515,0.9825819672131146 +102780000.0,1.0,0.005834066432506938,0.9825819672131146 +102840000.0,1.0,0.0057324484721046655,0.9825819672131147 +102900000.0,1.0,0.005632600496668416,0.9825819672131147 +102960000.0,1.0,0.005534491676542019,0.9825819672131147 +103020000.0,1.0,0.005438091719061266,0.9825819672131146 +103080000.0,1.0,0.0053433708592005675,0.9825819672131146 +103140000.0,1.0,0.005250299850382525,0.9825819672131147 +103200000.0,1.0,0.005158849955447582,0.9825819672131145 +103260000.0,1.0,0.005068992937780974,0.9825819672131146 +103320000.0,1.0,0.004980701052594215,0.9825819672131147 +103380000.0,1.0,0.0048939470383584545,0.9825819672131146 +103440000.0,1.0,0.004808704108387046,0.9825819672131146 +103500000.0,1.0,0.00472494594256473,0.9825819672131145 +103560000.0,1.0,0.004642646679220877,0.9825819672131147 +103620000.0,1.0,0.004561780907144283,0.9825819672131146 +103680000.0,1.0,0.004482323657737056,0.9825819672131145 +103740000.0,1.0,0.0044042503973051594,0.9825819672131145 +103800000.0,1.0,0.004327537019483245,0.9825819672131146 +103860000.0,1.0,0.004252159837791426,0.9825819672131146 +103920000.0,1.0,0.0041780955783216975,0.9825819672131146 +103980000.0,1.0,0.004105321372551749,0.9825819672131146 +104040000.0,1.0,0.004033814750283942,0.9825819672131146 +104100000.0,1.0,0.0039635536327072745,0.9825819672131146 +104160000.0,1.0,0.0038945163255802006,0.9825819672131146 +104220000.0,1.0,0.003826681512532184,0.9825819672131145 +104280000.0,1.0,0.0037600282484819307,0.9825819672131147 +104340000.0,1.0,0.0036945359531702577,0.9825819672131147 +104400000.0,1.0,0.0036301844048056116,0.9825819672131147 +104460000.0,1.0,0.0035669537338202674,0.9825819672131146 +104520000.0,1.0,0.0035048244167352827,0.9825819672131146 +104580000.0,1.0,0.003443777270132311,0.9825819672131145 +104640000.0,1.0,0.0033837934447304156,0.9825819672131146 +104700000.0,1.0,0.0033248544195660537,0.9825819672131147 +104760000.0,1.0,0.003266941996274431,0.9825819672131145 +104820000.0,1.0,0.0032100382934704703,0.9825819672131146 +104880000.0,1.0,0.003154125741227644,0.9825819672131146 +104940000.0,1.0,0.003099187075652982,0.9825819672131146 +105000000.0,1.0,0.003045205333556567,0.9825819672131146 +105060000.0,1.0,0.00299216384721388,0.9825819672131145 +105120000.0,1.0,0.002940046239219376,0.9825819672131147 +105180000.0,1.0,0.002888836417429694,0.9825819672131147 +105240000.0,1.0,0.0028385185699949553,0.9825819672131146 +105300000.0,1.0,0.0027890771604766004,0.9825819672131147 +105360000.0,1.0,0.002740496923050266,0.9825819672131146 +105420000.0,1.0,0.002692762857792218,0.9825819672131146 +105480000.0,1.0,0.002645860226047886,0.9825819672131146 +105540000.0,1.0,0.002599774545881068,0.9825819672131145 +105600000.0,1.0,0.0025544915876024015,0.9825819672131147 +105660000.0,1.0,0.00250999736937572,0.9825819672131146 +105720000.0,1.0,0.0024662781529009377,0.9825819672131146 +105780000.0,1.0,0.00242332043917213,0.9825819672131146 +105840000.0,1.0,0.0023811109643095004,0.9825819672131145 +105900000.0,1.0,0.0023396366954639455,0.9825819672131147 +105960000.0,1.0,0.0022988848267929546,0.9825819672131147 +106020000.0,1.0,0.0022588427755066017,0.9825819672131146 +106080000.0,1.0,0.0022194981779824086,0.9825819672131146 +106140000.0,1.0,0.002180838885947879,0.9825819672131147 +106200000.0,1.0,0.002142852962729524,0.9825819672131145 +106260000.0,1.0,0.0021055286795672265,0.9825819672131145 +106320000.0,1.0,0.0020688545119927973,0.9825819672131147 +106380000.0,1.0,0.0020328191362716107,0.9825819672131145 +106440000.0,1.0,0.0019974114259062236,0.9825819672131145 +106500000.0,1.0,0.0019626204482008896,0.9825819672131146 +106560000.0,1.0,0.0019284354608859148,0.9825819672131146 +106620000.0,1.0,0.0018948459088008113,0.9825819672131145 +106680000.0,1.0,0.001861841420635223,0.9825819672131145 +106740000.0,1.0,0.0018294118057266173,0.9825819672131145 +106800000.0,1.0,0.001797547050913756,0.9825819672131147 +106860000.0,1.0,0.001766237317444971,0.9825819672131145 +106920000.0,1.0,0.001735472937940294,0.9825819672131146 +106980000.0,1.0,0.0017052444134064977,0.9825819672131146 +107040000.0,1.0,0.0016755424103041302,0.9825819672131146 +107100000.0,1.0,0.001646357757665636,0.9825819672131146 +107160000.0,1.0,0.0016176814442636729,0.9825819672131146 +107220000.0,1.0,0.0015895046158287522,0.9825819672131146 +107280000.0,1.0,0.0015618185723153414,0.9825819672131146 +107340000.0,1.0,0.0015346147652155863,0.9825819672131146 +107400000.0,1.0,0.0015078847949198228,0.9825819672131146 +107460000.0,1.0,0.0014816204081230635,0.9825819672131146 +107520000.0,1.0,0.0014558134952766576,0.9825819672131147 +107580000.0,1.0,0.0014304560880843387,0.9825819672131147 +107640000.0,1.0,0.001405540357041886,0.9825819672131146 +107700000.0,1.0,0.0013810586090196397,0.9825819672131146 +107760000.0,1.0,0.0013570032848871255,0.9825819672131147 +107820000.0,1.0,0.0013333669571790503,0.9825819672131146 +107880000.0,1.0,0.0013101423278019562,0.9825819672131147 +107940000.0,1.0,0.0012873222257808153,0.9825819672131145 +108000000.0,1.0,0.0012648996050448789,0.9825819672131146 +108060000.0,1.0,0.0012428675422520889,0.9825819672131146 +108120000.0,1.0,0.0012212192346513864,0.9825819672131146 +108180000.0,1.0,0.0011999479979822535,0.9825819672131146 +108240000.0,1.0,0.0011790472644108412,0.9825819672131146 +108300000.0,1.0,0.0011585105805020457,0.9825819672131146 +108360000.0,1.0,0.0011383316052269075,0.9825819672131146 +108420000.0,1.0,0.0011185041080047173,0.9825819672131146 +108480000.0,1.0,0.0010990219667792252,0.9825819672131146 +108540000.0,1.0,0.0010798791661283573,0.9825819672131145 +108600000.0,1.0,0.001061069795406859,0.9825819672131145 +108660000.0,1.0,0.0010425880469212886,0.9825819672131145 +108720000.0,1.0,0.001024428214136799,0.9825819672131149 +108780000.0,1.0,0.0010065846899151537,0.9825819672131145 +108840000.0,1.0,0.0009890519647834347,0.9825819672131146 +108900000.0,1.0,0.0009718246252329035,0.9825819672131146 +108960000.0,1.0,0.0009548973520474941,0.9825819672131146 +109020000.0,1.0,0.0009382649186614209,0.9825819672131146 +109080000.0,1.0,0.0009219221895453919,0.9825819672131146 +109140000.0,1.0,0.0009058641186209332,0.9825819672131147 +109200000.0,1.0,0.0008900857477023308,0.9825819672131146 +109260000.0,1.0,0.0008745822049657123,0.9825819672131147 +109320000.0,1.0,0.0008593487034447931,0.9825819672131147 +109380000.0,1.0,0.0008443805395528243,0.9825819672131147 +109440000.0,1.0,0.0008296730916302853,0.9825819672131147 +109500000.0,1.0,0.0008152218185178725,0.9825819672131146 +109560000.0,1.0,0.0008010222581543439,0.9825819672131146 +109620000.0,1.0,0.0007870700261987866,0.9825819672131146 +109680000.0,1.0,0.0007733608146768814,0.9825819672131145 +109740000.0,1.0,0.0007598903906507471,0.9825819672131147 +109800000.0,1.0,0.0007466545949119534,0.9825819672131147 +109860000.0,1.0,0.0007336493406972985,0.9825819672131147 +109920000.0,1.0,0.000720870612426956,0.9825819672131145 +109980000.0,1.0,0.0007083144644646011,0.9825819672131146 +110040000.0,1.0,0.0006959770198991316,0.9825819672131146 +110100000.0,1.0,0.0006838544693476097,0.9825819672131146 +110160000.0,1.0,0.000671943069779055,0.9825819672131146 +110220000.0,1.0,0.000660239143358723,0.9825819672131146 +110280000.0,1.0,0.0006487390763125156,0.9825819672131146 +110340000.0,1.0,0.0006374393178111706,0.9825819672131147 +110400000.0,1.0,0.0006263363788738857,0.9825819672131146 +110460000.0,1.0,0.0006154268312910413,0.9825819672131145 +110520000.0,1.0,0.000604707306565685,0.9825819672131146 +110580000.0,1.0,0.0005941744948734547,0.9825819672131145 +110640000.0,1.0,0.0005838251440406178,0.9825819672131147 +110700000.0,1.0,0.0005736560585399103,0.9825819672131147 +110760000.0,1.0,0.0005636640985038667,0.9825819672131146 +110820000.0,1.0,0.0005538461787553361,0.9825819672131146 +110880000.0,1.0,0.0005441992678548845,0.9825819672131146 +110940000.0,1.0,0.0005347203871647892,0.9825819672131147 +111000000.0,1.0,0.0005254066099293368,0.9825819672131145 +111060000.0,1.0,0.0005162550603711413,0.9825819672131146 +111120000.0,1.0,0.0005072629128032013,0.9825819672131145 +111180000.0,1.0,0.0004984273907564241,0.9825819672131146 +111240000.0,1.0,0.000489745766122347,0.9825819672131145 +111300000.0,1.0,0.0004812153583107897,0.9825819672131147 +111360000.0,1.0,0.00047283353342217956,0.9825819672131146 +111420000.0,1.0,0.00046459770343429315,0.9825819672131145 +111480000.0,1.0,0.000456505325403163,0.9825819672131146 +111540000.0,1.0,0.0004485539006779029,0.9825819672131146 +111600000.0,1.0,0.0004407409741292098,0.9825819672131145 +111660000.0,1.0,0.0004330641333913034,0.9825819672131145 +111720000.0,1.0,0.0004255210081170696,0.9825819672131146 +111780000.0,1.0,0.000418109269246178,0.9825819672131146 +111840000.0,1.0,0.0004108266282859474,0.9825819672131146 +111900000.0,1.0,0.0004036708366047372,0.9825819672131146 +111960000.0,1.0,0.0003966396847376465,0.9825819672131146 +112020000.0,1.0,0.0003897310017043063,0.9825819672131146 +112080000.0,1.0,0.000382942654338555,0.9825819672131146 +112140000.0,1.0,0.00037627254662978915,0.9825819672131146 +112200000.0,1.0,0.0003697186190757867,0.9825819672131147 +112260000.0,1.0,0.0003632788480468026,0.9825819672131146 +112320000.0,1.0,0.00035695124516074147,0.9825819672131146 +112380000.0,1.0,0.0003507338566692121,0.9825819672131146 +112440000.0,1.0,0.00034462476285427706,0.9825819672131147 +112500000.0,1.0,0.0003386220774357087,0.9825819672131146 +112560000.0,1.0,0.0003327239469885703,0.9825819672131146 +112620000.0,1.0,0.00032692855037094146,0.9825819672131146 +112680000.0,1.0,0.0003212340981616115,0.9825819672131146 +112740000.0,1.0,0.00031563883210756696,0.9825819672131145 +112800000.0,1.0,0.00031014102458110316,0.9825819672131146 +112860000.0,1.0,0.00030473897804639125,0.9825819672131145 +112920000.0,1.0,0.00029943102453533726,0.9825819672131146 +112980000.0,1.0,0.0002942155251325701,0.9825819672131146 +113040000.0,1.0,0.00028909086946940027,0.9825819672131145 +113100000.0,1.0,0.00028405547522659307,0.9825819672131146 +113160000.0,1.0,0.000279107787645802,0.9825819672131147 +113220000.0,1.0,0.00027424627904951236,0.9825819672131145 +113280000.0,1.0,0.00026946944836934663,0.9825819672131146 +113340000.0,1.0,0.00026477582068258545,0.9825819672131146 +113400000.0,1.0,0.0002601639467567617,0.9825819672131146 +113460000.0,1.0,0.00025563240260218693,0.9825819672131147 +113520000.0,1.0,0.0002511797890322718,0.9825819672131147 +113580000.0,1.0,0.00024680473123150475,0.9825819672131147 +113640000.0,1.0,0.00024250587833095596,0.9825819672131146 +113700000.0,1.0,0.00023828190299117493,0.9825819672131146 +113760000.0,1.0,0.00023413150099235318,0.9825819672131145 +113820000.0,1.0,0.00023005339083162572,0.9825819672131147 +113880000.0,1.0,0.00022604631332738632,0.9825819672131146 +113940000.0,1.0,0.00022210903123049536,0.9825819672131147 +114000000.0,1.0,0.00021824032884225925,0.9825819672131146 +114060000.0,1.0,0.00021443901163906414,0.9825819672131146 +114120000.0,1.0,0.00021070390590354764,0.9825819672131147 +114180000.0,1.0,0.00020703385836219485,0.9825819672131146 +114240000.0,1.0,0.00020342773582924677,0.9825819672131146 +114300000.0,1.0,0.00019988442485681108,0.9825819672131146 +114360000.0,1.0,0.0001964028313910674,0.9825819672131145 +114420000.0,1.0,0.0001929818804344607,0.9825819672131147 +114480000.0,1.0,0.00018962051571377848,0.9825819672131147 +114540000.0,1.0,0.00018631769935400976,0.9825819672131145 +114600000.0,1.0,0.00018307241155788459,0.9825819672131147 +114660000.0,1.0,0.0001798836502909952,0.9825819672131146 +114720000.0,1.0,0.000176750430972402,0.9825819672131146 +114780000.0,1.0,0.0001736717861706286,0.9825819672131146 +114840000.0,1.0,0.00017064676530495165,0.9825819672131146 +114900000.0,1.0,0.00016767443435189409,0.9825819672131147 +114960000.0,1.0,0.00016475387555683034,0.9825819672131146 +115020000.0,1.0,0.00016188418715061504,0.9825819672131146 +115080000.0,1.0,0.00015906448307114734,0.9825819672131146 +115140000.0,1.0,0.00015629389268978513,0.9825819672131146 +115200000.0,1.0,0.0001535715605425245,0.9825819672131145 +115260000.0,1.0,0.00015089664606586164,0.9825819672131145 +115320000.0,1.0,0.00014826832333725542,0.9825819672131145 +115380000.0,1.0,0.00014568578082011058,0.9825819672131146 +115440000.0,1.0,0.00014314822111320292,0.9825819672131147 +115500000.0,1.0,0.00014065486070446884,0.9825819672131146 +115560000.0,1.0,0.0001382049297290836,0.9825819672131146 +115620000.0,1.0,0.00013579767173175323,0.9825819672131145 +115680000.0,1.0,0.00013343234343314686,0.9825819672131146 +115740000.0,1.0,0.00013110821450039738,0.9825819672131147 +115800000.0,1.0,0.00012882456732159943,0.9825819672131144 +115860000.0,1.0,0.0001265806967842355,0.9825819672131147 +115920000.0,1.0,0.0001243759100574609,0.9825819672131146 +115980000.0,1.0,0.00012220952637818133,0.9825819672131145 +116040000.0,1.0,0.00012008087684085644,0.9825819672131146 +116100000.0,1.0,0.00011798930419096445,0.9825819672131145 +116160000.0,1.0,0.00011593416262206445,0.9825819672131147 +116220000.0,1.0,0.00011391481757639323,0.9825819672131146 +116280000.0,1.0,0.00011193064554893556,0.9825819672131147 +116340000.0,1.0,0.00010998103389490696,0.9825819672131146 +116400000.0,1.0,0.00010806538064058992,0.9825819672131146 +116460000.0,1.0,0.00010618309429746488,0.9825819672131146 +116520000.0,1.0,0.00010433359367957869,0.9825819672131145 +116580000.0,1.0,0.0001025163077240942,0.9825819672131145 +116640000.0,1.0,0.00010073067531496551,0.9825819672131146 +116700000.0,1.0,9.897614510968433e-05,0.9825819672131145 +116760000.0,1.0,9.725217536904433e-05,0.9825819672131147 +116820000.0,1.0,9.555823378987039e-05,0.9825819672131146 +116880000.0,1.0,9.389379734066157e-05,0.9825819672131146 +116940000.0,1.0,9.225835210009676e-05,0.9825819672131146 +117000000.0,1.0,9.065139309835325e-05,0.9825819672131145 +117060000.0,1.0,8.907242416118929e-05,0.9825819672131145 +117120000.0,1.0,8.752095775674234e-05,0.9825819672131146 +117180000.0,1.0,8.59965148449958e-05,0.9825819672131147 +117240000.0,1.0,8.449862472986779e-05,0.9825819672131146 +117300000.0,1.0,8.302682491387624e-05,0.9825819672131147 +117360000.0,1.0,8.158066095533535e-05,0.9825819672131146 +117420000.0,1.0,8.015968632803954e-05,0.9825819672131147 +117480000.0,1.0,7.87634622833913e-05,0.9825819672131146 +117540000.0,1.0,7.739155771493057e-05,0.9825819672131145 +117600000.0,1.0,7.604354902522378e-05,0.9825819672131146 +117660000.0,1.0,7.471901999507131e-05,0.9825819672131147 +117720000.0,1.0,7.341756165499322e-05,0.9825819672131146 +117780000.0,1.0,7.213877215895336e-05,0.9825819672131145 +117840000.0,1.0,7.088225666028305e-05,0.9825819672131145 +117900000.0,1.0,6.964762718976581e-05,0.9825819672131146 +117960000.0,1.0,6.84345025358457e-05,0.9825819672131146 +118020000.0,1.0,6.724250812692215e-05,0.9825819672131145 +118080000.0,1.0,6.607127591569502e-05,0.9825819672131147 +118140000.0,1.0,6.49204442655241e-05,0.9825819672131147 +118200000.0,1.0,6.378965783876803e-05,0.9825819672131145 +118260000.0,1.0,6.267856748706817e-05,0.9825819672131146 +118320000.0,1.0,6.158683014354341e-05,0.9825819672131146 +118380000.0,1.0,6.051410871686284e-05,0.9825819672131147 +118440000.0,1.0,5.946007198716338e-05,0.9825819672131146 +118500000.0,1.0,5.842439450378041e-05,0.9825819672131147 +118560000.0,1.0,5.740675648475964e-05,0.9825819672131147 +118620000.0,1.0,5.640684371811936e-05,0.9825819672131147 +118680000.0,1.0,5.5424347464832436e-05,0.9825819672131146 +118740000.0,1.0,5.445896436349826e-05,0.9825819672131147 +118800000.0,1.0,5.351039633667503e-05,0.9825819672131146 +118860000.0,1.0,5.2578350498843596e-05,0.9825819672131147 +118920000.0,1.0,5.166253906597439e-05,0.9825819672131146 +118980000.0,1.0,5.07626792666695e-05,0.9825819672131146 +119040000.0,1.0,4.987849325485251e-05,0.9825819672131146 +119100000.0,1.0,4.900970802397905e-05,0.9825819672131146 +119160000.0,1.0,4.81560553227417e-05,0.9825819672131145 +119220000.0,1.0,4.7317271572243115e-05,0.9825819672131145 +119280000.0,1.0,4.6493097784611825e-05,0.9825819672131146 +119340000.0,1.0,4.5683279483035594e-05,0.9825819672131147 +119400000.0,1.0,4.488756662318763e-05,0.9825819672131146 +119460000.0,1.0,4.4105713516021446e-05,0.9825819672131145 +119520000.0,1.0,4.333747875191041e-05,0.9825819672131146 +119580000.0,1.0,4.258262512610869e-05,0.9825819672131146 +119640000.0,1.0,4.1840919565510475e-05,0.9825819672131145 +119700000.0,1.0,4.1112133056684975e-05,0.9825819672131145 +119760000.0,1.0,4.039604057516484e-05,0.9825819672131146 +119820000.0,1.0,3.969242101596627e-05,0.9825819672131146 +119880000.0,1.0,3.9001057125319315e-05,0.9825819672131147 +119940000.0,1.0,3.8321735433587314e-05,0.9825819672131146 +120000000.0,1.0,3.7654246189354746e-05,0.9825819672131146 +120060000.0,1.0,3.699838329466311e-05,0.9825819672131146 +120120000.0,1.0,3.6353944241374925e-05,0.9825819672131147 +120180000.0,1.0,3.572073004864606e-05,0.9825819672131147 +120240000.0,1.0,3.509854520148726e-05,0.9825819672131145 +120300000.0,1.0,3.448719759039578e-05,0.9825819672131147 +120360000.0,1.0,3.3886498452038475e-05,0.9825819672131147 +120420000.0,1.0,3.329626231096813e-05,0.9825819672131146 +120480000.0,1.0,3.271630692235495e-05,0.9825819672131147 +120540000.0,1.0,3.2146453215715574e-05,0.9825819672131147 +120600000.0,1.0,3.158652523962216e-05,0.9825819672131145 +120660000.0,1.0,3.1036350107374636e-05,0.9825819672131145 +120720000.0,1.0,3.049575794361913e-05,0.9825819672131146 +120780000.0,1.0,2.996458183189625e-05,0.9825819672131145 +120840000.0,1.0,2.944265776310297e-05,0.9825819672131146 +120900000.0,1.0,2.89298245848522e-05,0.9825819672131146 +120960000.0,1.0,2.84259239517144e-05,0.9825819672131146 +121020000.0,1.0,2.793080027632593e-05,0.9825819672131146 +121080000.0,1.0,2.7444300681348936e-05,0.9825819672131146 +121140000.0,1.0,2.696627495226806e-05,0.9825819672131146 +121200000.0,1.0,2.649657549100929e-05,0.9825819672131146 +121260000.0,1.0,2.6035057270366706e-05,0.9825819672131146 +121320000.0,1.0,2.558157778922302e-05,0.9825819672131146 +121380000.0,1.0,2.5135997028550072e-05,0.9825819672131145 +121440000.0,1.0,2.4698177408175732e-05,0.9825819672131145 +121500000.0,1.0,2.4267983744303818e-05,0.9825819672131147 +121560000.0,1.0,2.3845283207773933e-05,0.9825819672131146 +121620000.0,1.0,2.3429945283048362e-05,0.9825819672131147 +121680000.0,1.0,2.3021841727913294e-05,0.9825819672131145 +121740000.0,1.0,2.2620846533882015e-05,0.9825819672131146 +121800000.0,1.0,2.2226835887287756e-05,0.9825819672131146 +121860000.0,1.0,2.1839688131054256e-05,0.9825819672131146 +121920000.0,1.0,2.14592837271322e-05,0.9825819672131146 +121980000.0,1.0,2.1085505219589938e-05,0.9825819672131146 +122040000.0,1.0,2.0718237198347075e-05,0.9825819672131145 +122100000.0,1.0,2.0357366263539797e-05,0.9825819672131146 +122160000.0,1.0,2.0002780990506826e-05,0.9825819672131146 +122220000.0,1.0,1.965437189538529e-05,0.9825819672131146 +122280000.0,1.0,1.9312031401305832e-05,0.9825819672131146 +122340000.0,1.0,1.8975653805176526e-05,0.9825819672131145 +122400000.0,1.0,1.8645135245045376e-05,0.9825819672131146 +122460000.0,1.0,1.8320373668031265e-05,0.9825819672131146 +122520000.0,1.0,1.8001268798813505e-05,0.9825819672131146 +122580000.0,1.0,1.7687722108670238e-05,0.9825819672131147 +122640000.0,1.0,1.7379636785056103e-05,0.9825819672131146 +122700000.0,1.0,1.7076917701709834e-05,0.9825819672131145 +122760000.0,1.0,1.677947138928251e-05,0.9825819672131146 +122820000.0,1.0,1.6487206006477383e-05,0.9825819672131146 +122880000.0,1.0,1.6200031311692426e-05,0.9825819672131146 +122940000.0,1.0,1.5917858635156797e-05,0.9825819672131146 +123000000.0,1.0,1.564060085155263e-05,0.9825819672131147 +123060000.0,1.0,1.53681723531137e-05,0.9825819672131146 +123120000.0,1.0,1.5100489023192662e-05,0.9825819672131147 +123180000.0,1.0,1.4837468210288691e-05,0.9825819672131147 +123240000.0,1.0,1.4579028702527514e-05,0.9825819672131146 +123300000.0,1.0,1.4325090702585948e-05,0.9825819672131146 +123360000.0,1.0,1.4075575803053197e-05,0.9825819672131145 +123420000.0,1.0,1.3830406962221326e-05,0.9825819672131146 +123480000.0,1.0,1.3589508480297388e-05,0.9825819672131146 +123540000.0,1.0,1.3352805976029911e-05,0.9825819672131146 +123600000.0,1.0,1.3120226363742503e-05,0.9825819672131146 +123660000.0,1.0,1.289169783076748e-05,0.9825819672131146 +123720000.0,1.0,1.2667149815272553e-05,0.9825819672131146 +123780000.0,1.0,1.2446512984473746e-05,0.9825819672131146 +123840000.0,1.0,1.222971921322779e-05,0.9825819672131147 +123900000.0,1.0,1.2016701562997386e-05,0.9825819672131146 +123960000.0,1.0,1.180739426118288e-05,0.9825819672131145 +124020000.0,1.0,1.1601732680813916e-05,0.9825819672131147 +124080000.0,1.0,1.1399653320594818e-05,0.9825819672131145 +124140000.0,1.0,1.120109378529757e-05,0.9825819672131145 +124200000.0,1.0,1.100599276649628e-05,0.9825819672131146 +124260000.0,1.0,1.0814290023637225e-05,0.9825819672131147 +124320000.0,1.0,1.0625926365438625e-05,0.9825819672131146 +124380000.0,1.0,1.0440843631614386e-05,0.9825819672131146 +124440000.0,1.0,1.0258984674916183e-05,0.9825819672131146 +124500000.0,1.0,1.008029334348834e-05,0.9825819672131147 +124560000.0,1.0,9.90471446353004e-06,0.9825819672131147 +124620000.0,1.0,9.732193822259536e-06,0.9825819672131147 +124680000.0,1.0,9.562678151175097e-06,0.9825819672131146 +124740000.0,1.0,9.396115109607496e-06,0.9825819672131145 +124800000.0,1.0,9.232453268559004e-06,0.9825819672131147 +124860000.0,1.0,9.071642094823856e-06,0.9825819672131145 +124920000.0,1.0,8.913631935385325e-06,0.9825819672131146 +124980000.0,1.0,8.758374002084554e-06,0.9825819672131146 +125040000.0,1.0,8.605820356556442e-06,0.9825819672131146 +125100000.0,1.0,8.455923895427896e-06,0.9825819672131146 +125160000.0,1.0,8.308638335773926e-06,0.9825819672131146 +125220000.0,1.0,8.163918200827042e-06,0.9825819672131145 +125280000.0,1.0,8.021718805935587e-06,0.9825819672131147 +125340000.0,1.0,7.881996244766627e-06,0.9825819672131147 +125400000.0,1.0,7.744707375749175e-06,0.9825819672131147 +125460000.0,1.0,7.609809808753544e-06,0.9825819672131147 +125520000.0,1.0,7.477261892002713e-06,0.9825819672131146 +125580000.0,1.0,7.347022699211681e-06,0.9825819672131146 +125640000.0,1.0,7.219052016950821e-06,0.9825819672131147 +125700000.0,1.0,7.093310332229341e-06,0.9825819672131146 +125760000.0,1.0,6.969758820295018e-06,0.9825819672131146 +125820000.0,1.0,6.848359332646436e-06,0.9825819672131146 +125880000.0,1.0,6.729074385254028e-06,0.9825819672131146 +125940000.0,1.0,6.611867146986283e-06,0.9825819672131146 +126000000.0,1.0,6.496701428237545e-06,0.9825819672131145 +126060000.0,1.0,6.3835416697538985e-06,0.9825819672131146 +126120000.0,1.0,6.2723529316536756e-06,0.9825819672131145 +126180000.0,1.0,6.163100882639216e-06,0.9825819672131147 +126240000.0,1.0,6.055751789396524e-06,0.9825819672131146 +126300000.0,1.0,5.950272506179576e-06,0.9825819672131146 +126360000.0,1.0,5.846630464576037e-06,0.9825819672131146 +126420000.0,1.0,5.744793663451248e-06,0.9825819672131145 +126480000.0,1.0,5.6447306590673635e-06,0.9825819672131146 +126540000.0,1.0,5.546410555374591e-06,0.9825819672131146 +126600000.0,1.0,5.4498029944715496e-06,0.9825819672131146 +126660000.0,1.0,5.354878147231778e-06,0.9825819672131145 +126720000.0,1.0,5.261606704093519e-06,0.9825819672131146 +126780000.0,1.0,5.1699598660099225e-06,0.9825819672131147 +126840000.0,1.0,5.07990933555688e-06,0.9825819672131146 +126900000.0,1.0,4.991427308195745e-06,0.9825819672131145 +126960000.0,1.0,4.904486463688236e-06,0.9825819672131146 +127020000.0,1.0,4.8190599576608785e-06,0.9825819672131145 +127080000.0,1.0,4.735121413316375e-06,0.9825819672131146 +127140000.0,1.0,4.652644913289347e-06,0.9825819672131146 +127200000.0,1.0,4.571604991643938e-06,0.9825819672131146 +127260000.0,1.0,4.491976626010795e-06,0.9825819672131146 +127320000.0,1.0,4.413735229861016e-06,0.9825819672131146 +127380000.0,1.0,4.336856644914666e-06,0.9825819672131146 +127440000.0,1.0,4.2613171336815206e-06,0.9825819672131146 +127500000.0,1.0,4.187093372131739e-06,0.9825819672131146 +127560000.0,1.0,4.114162442494198e-06,0.9825819672131146 +127620000.0,1.0,4.042501826180262e-06,0.9825819672131145 +127680000.0,1.0,3.97208939683081e-06,0.9825819672131147 +127740000.0,1.0,3.9029034134843715e-06,0.9825819672131146 +127800000.0,1.0,3.834922513864253e-06,0.9825819672131145 +127860000.0,1.0,3.768125707782601e-06,0.9825819672131146 +127920000.0,1.0,3.702492370659338e-06,0.9825819672131145 +127980000.0,1.0,3.638002237154001e-06,0.9825819672131147 +128040000.0,1.0,3.5746353949084897e-06,0.9825819672131145 +128100000.0,1.0,3.5123722783988132e-06,0.9825819672131147 +128160000.0,1.0,3.4511936628939156e-06,0.9825819672131146 +128220000.0,1.0,3.391080658519738e-06,0.9825819672131145 +128280000.0,1.0,3.3320147044266683e-06,0.9825819672131145 +128340000.0,1.0,3.2739775630585803e-06,0.9825819672131146 +128400000.0,1.0,3.216951314521699e-06,0.9825819672131146 +128460000.0,1.0,3.160918351051546e-06,0.9825819672131146 +128520000.0,1.0,3.1058613715762627e-06,0.9825819672131146 +128580000.0,1.0,3.0517633763746264e-06,0.9825819672131145 +128640000.0,1.0,2.998607661827117e-06,0.9825819672131146 +128700000.0,1.0,2.946377815258407e-06,0.9825819672131146 +128760000.0,1.0,2.895057709869684e-06,0.9825819672131146 +128820000.0,1.0,2.844631499759249e-06,0.9825819672131146 +128880000.0,1.0,2.7950836150298356e-06,0.9825819672131147 +128940000.0,1.0,2.7463987569811596e-06,0.9825819672131145 +129000000.0,1.0,2.6985618933862005e-06,0.9825819672131146 +129060000.0,1.0,2.65155825384976e-06,0.9825819672131146 +129120000.0,1.0,2.6053733252478687e-06,0.9825819672131146 +129180000.0,1.0,2.559992847246625e-06,0.9825819672131147 +129240000.0,1.0,2.5154028078990914e-06,0.9825819672131146 +129300000.0,1.0,2.4715894393188813e-06,0.9825819672131145 +129360000.0,1.0,2.428539213429105e-06,0.9825819672131145 +129420000.0,1.0,2.3862388377853603e-06,0.9825819672131146 +129480000.0,1.0,2.3446752514714754e-06,0.9825819672131145 +129540000.0,1.0,2.3038356210667464e-06,0.9825819672131146 +129600000.0,1.0,2.2637073366834116e-06,0.9825819672131146 +129660000.0,1.0,2.224278008073147e-06,0.9825819672131147 +129720000.0,1.0,2.185535460801381e-06,0.9825819672131145 +129780000.0,1.0,2.1474677324882417e-06,0.9825819672131146 +129840000.0,1.0,2.110063069114983e-06,0.9825819672131146 +129900000.0,1.0,2.0733099213947423e-06,0.9825819672131146 +129960000.0,1.0,2.0371969412065142e-06,0.9825819672131147 +130020000.0,1.0,2.0017129780912366e-06,0.9825819672131146 +130080000.0,1.0,1.9668470758089094e-06,0.9825819672131146 +130140000.0,1.0,1.93258846895568e-06,0.9825819672131146 +130200000.0,1.0,1.8989265796398537e-06,0.9825819672131146 +130260000.0,1.0,1.8658510142157986e-06,0.9825819672131146 +130320000.0,1.0,1.8333515600747444e-06,0.9825819672131146 +130380000.0,1.0,1.8014181824914751e-06,0.9825819672131146 +130440000.0,1.0,1.770041021525947e-06,0.9825819672131145 +130500000.0,1.0,1.739210388978876e-06,0.9825819672131146 +130560000.0,1.0,1.7089167654003503e-06,0.9825819672131146 +130620000.0,1.0,1.6791507971505488e-06,0.9825819672131145 +130680000.0,1.0,1.6499032935116558e-06,0.9825819672131146 +130740000.0,1.0,1.6211652238500797e-06,0.9825819672131146 +130800000.0,1.0,1.5929277148281007e-06,0.9825819672131146 +130860000.0,1.0,1.5651820476640864e-06,0.9825819672131146 +130920000.0,1.0,1.537919655440429e-06,0.9825819672131146 +130980000.0,1.0,1.5111321204583722e-06,0.9825819672131146 +131040000.0,1.0,1.4848111716389126e-06,0.9825819672131146 +131100000.0,1.0,1.4589486819689724e-06,0.9825819672131146 +131160000.0,1.0,1.4335366659920537e-06,0.9825819672131146 +131220000.0,1.0,1.4085672773426019e-06,0.9825819672131147 +131280000.0,1.0,1.3840328063233146e-06,0.9825819672131146 +131340000.0,1.0,1.3599256775246503e-06,0.9825819672131147 +131400000.0,1.0,1.3362384474857986e-06,0.9825819672131146 +131460000.0,1.0,1.3129638023963942e-06,0.9825819672131146 +131520000.0,1.0,1.2900945558382602e-06,0.9825819672131146 +131580000.0,1.0,1.267623646566487e-06,0.9825819672131145 +131640000.0,1.0,1.2455441363291606e-06,0.9825819672131146 +131700000.0,1.0,1.2238492077250665e-06,0.9825819672131146 +131760000.0,1.0,1.2025321620987076e-06,0.9825819672131146 +131820000.0,1.0,1.1815864174719884e-06,0.9825819672131147 +131880000.0,1.0,1.1610055065119228e-06,0.9825819672131146 +131940000.0,1.0,1.1407830745337438e-06,0.9825819672131147 +132000000.0,1.0,1.120912877538791e-06,0.9825819672131145 +132060000.0,1.0,1.1013887802865785e-06,0.9825819672131147 +132120000.0,1.0,1.0822047544004391e-06,0.9825819672131146 +132180000.0,1.0,1.0633548765061692e-06,0.9825819672131147 +132240000.0,1.0,1.0448333264030902e-06,0.9825819672131145 +132300000.0,1.0,1.0266343852669705e-06,0.9825819672131145 +132360000.0,1.0,1.0087524338842466e-06,0.9825819672131147 +132420000.0,1.0,9.911819509170003e-07,0.9825819672131145 +132480000.0,1.0,9.73917511198159e-07,0.9825819672131145 +132540000.0,1.0,9.569537840563876e-07,0.9825819672131146 +132600000.0,1.0,9.402855316701593e-07,0.9825819672131145 +132660000.0,1.0,9.239076074504945e-07,0.9825819672131146 +132720000.0,1.0,9.078149544518688e-07,0.9825819672131145 +132780000.0,1.0,8.920026038108014e-07,0.9825819672131147 +132840000.0,1.0,8.764656732116378e-07,0.9825819672131146 +132900000.0,1.0,8.61199365379058e-07,0.9825819672131146 +132960000.0,1.0,8.461989665968407e-07,0.9825819672131146 +133020000.0,1.0,8.314598452524284e-07,0.9825819672131146 +133080000.0,1.0,8.169774504068431e-07,0.9825819672131147 +133140000.0,1.0,8.027473103895107e-07,0.9825819672131146 +133200000.0,1.0,7.887650314175621e-07,0.9825819672131146 +133260000.0,1.0,7.750262962391823e-07,0.9825819672131145 +133320000.0,1.0,7.615268628005899e-07,0.9825819672131146 +133380000.0,1.0,7.482625629362353e-07,0.9825819672131146 +133440000.0,1.0,7.352293010818131e-07,0.9825819672131146 +133500000.0,1.0,7.224230530096913e-07,0.9825819672131146 +133560000.0,1.0,7.098398645863665e-07,0.9825819672131145 +133620000.0,1.0,6.974758505515629e-07,0.9825819672131146 +133680000.0,1.0,6.85327193318595e-07,0.9825819672131146 +133740000.0,1.0,6.733901417956277e-07,0.9825819672131147 +133800000.0,1.0,6.61661010227466e-07,0.9825819672131145 +133860000.0,1.0,6.501361770575204e-07,0.9825819672131147 +133920000.0,1.0,6.388120838095922e-07,0.9825819672131146 +133980000.0,1.0,6.276852339891381e-07,0.9825819672131145 +134040000.0,1.0,6.167521920036715e-07,0.9825819672131146 +134100000.0,1.0,6.060095821019682e-07,0.9825819672131146 +134160000.0,1.0,5.954540873317494e-07,0.9825819672131146 +134220000.0,1.0,5.850824485155201e-07,0.9825819672131146 +134280000.0,1.0,5.748914632442456e-07,0.9825819672131146 +134340000.0,1.0,5.648779848885569e-07,0.9825819672131147 +134400000.0,1.0,5.550389216271783e-07,0.9825819672131146 +134460000.0,1.0,5.453712354922786e-07,0.9825819672131146 +134520000.0,1.0,5.358719414314499e-07,0.9825819672131146 +134580000.0,1.0,5.265381063860249e-07,0.9825819672131145 +134640000.0,1.0,5.173668483854486e-07,0.9825819672131147 +134700000.0,1.0,5.083553356574234e-07,0.9825819672131147 +134760000.0,1.0,4.995007857535543e-07,0.9825819672131147 +134820000.0,1.0,4.908004646902239e-07,0.9825819672131145 +134880000.0,1.0,4.82251686104431e-07,0.9825819672131146 +134940000.0,1.0,4.738518104243333e-07,0.9825819672131146 +135000000.0,1.0,4.655982440542373e-07,0.9825819672131146 +135060000.0,1.0,4.574884385737843e-07,0.9825819672131145 +135120000.0,1.0,4.495198899510851e-07,0.9825819672131145 +135180000.0,1.0,4.4169013776956e-07,0.9825819672131145 +135240000.0,1.0,4.3399676446824586e-07,0.9825819672131146 +135300000.0,1.0,4.264373945953358e-07,0.9825819672131146 +135360000.0,1.0,4.1900969407472026e-07,0.9825819672131146 +135420000.0,1.0,4.1171136948530394e-07,0.9825819672131145 +135480000.0,1.0,4.045401673528754e-07,0.9825819672131146 +135540000.0,1.0,3.9749387345431095e-07,0.9825819672131146 +135600000.0,1.0,3.905703121338977e-07,0.9825819672131146 +135660000.0,1.0,3.8376734563156545e-07,0.9825819672131147 +135720000.0,1.0,3.7708287342281887e-07,0.9825819672131146 +135780000.0,1.0,3.7051483157016726e-07,0.9825819672131146 +135840000.0,1.0,3.640611920858508e-07,0.9825819672131146 +135900000.0,1.0,3.577199623056669e-07,0.9825819672131147 +135960000.0,1.0,3.514891842737034e-07,0.9825819672131146 +136020000.0,1.0,3.453669341377884e-07,0.9825819672131146 +136080000.0,1.0,3.3935132155547034e-07,0.9825819672131146 +136140000.0,1.0,3.334404891103443e-07,0.9825819672131146 +136200000.0,1.0,3.276326117385452e-07,0.9825819672131146 +136260000.0,1.0,3.2192589616523036e-07,0.9825819672131147 +136320000.0,1.0,3.1631858035087694e-07,0.9825819672131146 +136380000.0,1.0,3.1080893294722435e-07,0.9825819672131146 +136440000.0,1.0,3.0539525276269277e-07,0.9825819672131147 +136500000.0,1.0,3.000758682371131e-07,0.9825819672131147 +136560000.0,1.0,2.9484913692560596e-07,0.9825819672131146 +136620000.0,1.0,2.897134449914509e-07,0.9825819672131146 +136680000.0,1.0,2.846672067077883e-07,0.9825819672131145 +136740000.0,1.0,2.7970886396800096e-07,0.9825819672131146 +136800000.0,1.0,2.748368858046239e-07,0.9825819672131146 +136860000.0,1.0,2.700497679166335e-07,0.9825819672131147 +136920000.0,1.0,2.653460322049708e-07,0.9825819672131146 +136980000.0,1.0,2.607242263161547e-07,0.9825819672131146 +137040000.0,1.0,2.5618292319384457e-07,0.9825819672131146 +137100000.0,1.0,2.51720720638214e-07,0.9825819672131145 +137160000.0,1.0,2.473362408729992e-07,0.9825819672131145 +137220000.0,1.0,2.430281301200883e-07,0.9825819672131145 +137280000.0,1.0,2.3879505818152113e-07,0.9825819672131146 +137340000.0,1.0,2.3463571802876918e-07,0.9825819672131145 +137400000.0,1.0,2.3054882539916969e-07,0.9825819672131146 +137460000.0,1.0,2.2653311839938902e-07,0.9825819672131145 +137520000.0,1.0,2.2258735711579306e-07,0.9825819672131145 +137580000.0,1.0,2.1871032323160402e-07,0.9825819672131146 +137640000.0,1.0,2.1490081965072563e-07,0.9825819672131145 +137700000.0,1.0,2.1115767012812076e-07,0.9825819672131146 +137760000.0,1.0,2.0750097925286178e-07,0.9826826519110565 +137820000.0,1.0,2.0392850902182252e-07,0.982783357245337 +137880000.0,1.0,2.0043808563280492e-07,0.9828840832223017 +137940000.0,1.0,1.9702759750088145e-07,0.9829848298482986 +138000000.0,1.0,1.9369499334017967e-07,0.9830855971296769 +138060000.0,1.0,1.904382803088295e-07,0.9831863850727905 +138120000.0,1.0,1.8725552221487487e-07,0.9832871936839945 +138180000.0,1.0,1.8414483778103466e-07,0.9833880229696472 +138240000.0,1.0,1.8110439896627242e-07,0.9834888729361091 +138300000.0,1.0,1.781324293422105e-07,0.9835897435897436 +138360000.0,1.0,1.7522720252249447e-07,0.9836906349369164 +138420000.0,1.0,1.7238704064328294e-07,0.9837915469839967 +138480000.0,1.0,1.6961031289310385e-07,0.983892479737355 +138540000.0,1.0,1.668954340903823e-07,0.9839934332033655 +138600000.0,1.0,1.6424086330700527e-07,0.9840944073884044 +138660000.0,1.0,1.6164510253634856e-07,0.9841954022988505 +138720000.0,1.0,1.5910669540424743e-07,0.9842964179410859 +138780000.0,1.0,1.566242259214466e-07,0.9843974543214945 +138840000.0,1.0,1.5419631727611874e-07,0.9844985114464632 +138900000.0,1.0,1.5182163066509022e-07,0.984599589322382 +138960000.0,1.0,1.4949886416246176e-07,0.9847006879556421 +139020000.0,1.0,1.4722675162435902e-07,0.9848018073526391 +139080000.0,1.0,1.4500406162859228e-07,0.98490294751977 +139140000.0,1.0,1.4282959644804849e-07,0.9850041084634347 +139200000.0,1.0,1.4070219105668052e-07,0.9851052901900359 +139260000.0,1.0,1.386207121669988e-07,0.985206492705979 +139320000.0,1.0,1.3658405729800863e-07,0.9853077160176715 +139380000.0,1.0,1.345911538725753e-07,0.9854089601315249 +139440000.0,1.0,1.3264095834323266e-07,0.9855102250539512 +139500000.0,1.0,1.307324553454883e-07,0.9856115107913668 +139560000.0,1.0,1.288646568777092e-07,0.9857128173501902 +139620000.0,1.0,1.2703660150670552e-07,0.9858141447368421 +139680000.0,1.0,1.2524735359816037e-07,0.9859154929577464 +139740000.0,1.0,1.234960025710835e-07,0.9860168620193298 +139800000.0,1.0,1.2178166217549518e-07,0.9861182519280205 +139860000.0,1.0,1.2010346979257493e-07,0.9862196626902507 +139920000.0,1.0,1.1846058575653537e-07,0.9863210943124548 +139980000.0,1.0,1.1685219269750814e-07,0.9864225468010697 +140040000.0,1.0,1.1527749490475291e-07,0.9865240201625347 +140100000.0,1.0,1.137357177095247e-07,0.9866255144032919 +140160000.0,1.0,1.1222610688695772e-07,0.9867270295297871 +140220000.0,1.0,1.1074792807634539e-07,0.9868285655484668 +140280000.0,1.0,1.0930046621921913e-07,0.9869301224657816 +140340000.0,1.0,1.0788302501464712e-07,0.9870317002881843 +140400000.0,1.0,1.0649492639119567e-07,0.9871332990221308 +140460000.0,1.0,1.0513550999501404e-07,0.9872349186740785 +140520000.0,1.0,1.0380413269352256e-07,0.987336559250489 +140580000.0,1.0,1.0250016809420113e-07,0.9874382207578254 +140640000.0,1.0,1.0122300607799288e-07,0.9875399032025538 +140700000.0,1.0,9.997205234685392e-08,0.9876416065911431 +140760000.0,1.0,9.87467279849963e-08,0.9877433309300648 +140820000.0,1.0,9.754646903338632e-08,0.987845076225793 +140880000.0,1.0,9.637072607707578e-08,0.9879468424848046 +140940000.0,1.0,9.521896384495742e-08,0.9880486297135792 +141000000.0,1.0,9.409066082154987e-08,0.9881504379185985 +141060000.0,1.0,9.298530887043108e-08,0.9882522671063478 +141120000.0,1.0,9.190241286895126e-08,0.9883541172833145 +141180000.0,1.0,9.084149035386958e-08,0.9884559884559885 +141240000.0,1.0,8.980207117757027e-08,0.9885578806308628 +141300000.0,1.0,8.878369717452564e-08,0.9886597938144328 +141360000.0,1.0,8.778592183768438e-08,0.9887617280131972 +141420000.0,1.0,8.680831000447446e-08,0.9888636832336565 +141480000.0,1.0,8.585043755212024e-08,0.9889656594823141 +141540000.0,1.0,8.491189110198363e-08,0.9890676567656768 +141600000.0,1.0,8.399226773264807e-08,0.9891696750902528 +141660000.0,1.0,8.309117470147462e-08,0.989271714462554 +141720000.0,1.0,8.220822917436723e-08,0.9893737748890952 +141780000.0,1.0,8.134305796349377e-08,0.9894758563763928 +141840000.0,1.0,8.049529727271749e-08,0.9895779589309668 +141900000.0,1.0,7.966459245050161e-08,0.9896800825593394 +141960000.0,1.0,7.885059775005784e-08,0.9897822272680359 +142020000.0,1.0,7.80529760965168e-08,0.9898843930635838 +142080000.0,1.0,7.727139886090595e-08,0.9899865799525135 +142140000.0,1.0,7.650554564072766e-08,0.9900887879413587 +142200000.0,1.0,7.57551040469363e-08,0.9901910170366545 +142260000.0,1.0,7.501976949712093e-08,0.9902932672449399 +142320000.0,1.0,7.429924501470513e-08,0.9903955385727564 +142380000.0,1.0,7.359324103398287e-08,0.9904978310266475 +142440000.0,1.0,7.290147521081455e-08,0.9906001446131597 +142500000.0,1.0,7.222367223881317e-08,0.9907024793388429 +142560000.0,1.0,7.155956367085632e-08,0.990804835210249 +142620000.0,1.0,7.090888774576483e-08,0.9909072122339325 +142680000.0,1.0,7.027138921999428e-08,0.9910096104164514 +142740000.0,1.0,6.964681920419028e-08,0.9911120297643654 +142800000.0,1.0,6.903493500446354e-08,0.9912144702842377 +142860000.0,1.0,6.843549996824533e-08,0.9913169319826338 +142920000.0,1.0,6.78482833345883e-08,0.9914194148661221 +142980000.0,1.0,6.727306008878224e-08,0.9915219189412737 +143040000.0,1.0,6.670961082115827e-08,0.9916244442146623 +143100000.0,1.0,6.615772158995944e-08,0.9917269906928644 +143160000.0,1.0,6.561718378815917e-08,0.9918295583824595 +143220000.0,1.0,6.50877940141132e-08,0.9919321472900289 +143280000.0,1.0,6.456935394593417e-08,0.9920347574221579 +143340000.0,1.0,6.406167021948154e-08,0.9921373887854333 +143400000.0,1.0,6.35645543098632e-08,0.9922400413864458 +143460000.0,1.0,6.307782241634811e-08,0.9923427152317881 +143520000.0,1.0,6.26012953505928e-08,0.9924454103280553 +143580000.0,1.0,6.213479842808786e-08,0.9925481266818462 +143640000.0,1.0,6.16781613627329e-08,0.992650864299762 +143700000.0,1.0,6.123121816445224e-08,0.9927536231884059 +143760000.0,1.0,6.079380703976569e-08,0.9928564033543843 +143820000.0,1.0,6.036577029523223e-08,0.9929592048043072 +143880000.0,1.0,5.994695424368614e-08,0.9930620275447861 +143940000.0,1.0,5.9537209113188705e-08,0.9931648715824358 +144000000.0,1.0,5.913638895862038e-08,0.9932677369238738 +144060000.0,1.0,5.874435157584103e-08,0.9933706235757197 +144120000.0,1.0,5.836095841834823e-08,0.9934735315445975 +144180000.0,1.0,5.798607451636547e-08,0.9935764608371321 +144240000.0,1.0,5.7619568398294986e-08,0.9936794114599524 +144300000.0,1.0,5.7261312014471385e-08,0.993782383419689 +144360000.0,1.0,5.691118066315479e-08,0.9938853767229765 +144420000.0,1.0,5.656905291870382e-08,0.9939883913764511 +144480000.0,1.0,5.6234810561871007e-08,0.9940914273867524 +144540000.0,1.0,5.590833851216493e-08,0.9941944847605224 +144600000.0,1.0,5.558952476222517e-08,0.9942975635044065 +144660000.0,1.0,5.527826031415796e-08,0.9944006636250519 +144720000.0,1.0,5.4974439117782304e-08,0.9945037851291091 +144780000.0,1.0,5.467795801073763e-08,0.9946069280232316 +144840000.0,1.0,5.4388716660405967e-08,0.9947100923140754 +144900000.0,1.0,5.410661750760303e-08,0.9948132780082988 +144960000.0,1.0,5.38315657119943e-08,0.9949164851125636 +145020000.0,1.0,5.3563469099193324e-08,0.9950197136335337 +145080000.0,1.0,5.330223810950129e-08,0.9951229635778768 +145140000.0,1.0,5.3047785748247955e-08,0.9952262349522623 +145200000.0,1.0,5.280002753769568e-08,0.9953295277633628 +145260000.0,1.0,5.255888147046933e-08,0.9954328420178533 +145320000.0,1.0,5.232426796447637e-08,0.9955361777224125 +145380000.0,1.0,5.209610981928244e-08,0.995639534883721 +145440000.0,1.0,5.1874332173909104e-08,0.9957429135084622 +145500000.0,1.0,5.165886246602163e-08,0.9958463136033229 +145560000.0,1.0,5.1449630392475583e-08,0.9959497351749922 +145620000.0,1.0,5.1246567871192444e-08,0.996053178230162 +145680000.0,1.0,5.1049609004335255e-08,0.9961566427755271 +145740000.0,1.0,5.08586900427566e-08,0.996260128817785 +145800000.0,1.0,5.067374935169203e-08,0.9963636363636362 +145860000.0,1.0,5.049472737767316e-08,0.9964671654197838 +145920000.0,1.0,5.032156661663572e-08,0.9965707159929335 +145980000.0,1.0,5.0154211583198554e-08,0.9966742880897942 +146040000.0,1.0,4.999260878109075e-08,0.9967778817170772 +146100000.0,1.0,4.9836706674704806e-08,0.9968814968814969 +146160000.0,1.0,4.968645566175477e-08,0.9969851335897704 +146220000.0,1.0,4.954180804701894e-08,0.997088791848617 +146280000.0,1.0,4.940271801714792e-08,0.9971924716647602 +146340000.0,1.0,4.926914161651919e-08,0.997296173044925 +146400000.0,1.0,4.914103672412054e-08,0.9973998959958398 +146460000.0,1.0,4.9018363031445386e-08,0.9975036405242355 +146520000.0,1.0,4.8901082021383666e-08,0.9976074066368458 +146580000.0,1.0,4.8789156948092936e-08,0.9977111943404077 +146640000.0,1.0,4.8682552817834904e-08,0.9978150036416606 +146700000.0,1.0,4.8581236370763446e-08,0.9979188345473465 +146760000.0,1.0,4.848517606365089e-08,0.9980226870642105 +146820000.0,1.0,4.839434205353997e-08,0.9981265611990009 +146880000.0,1.0,4.8308706182309606e-08,0.9982304569584679 +146940000.0,1.0,4.822824196214336e-08,0.998334374349365 +147000000.0,1.0,4.8152924561890135e-08,0.9984383133784485 +147060000.0,1.0,4.8082730794307205e-08,0.9985422740524782 +147120000.0,1.0,4.801763910417641e-08,0.9986462563782151 +147180000.0,1.0,4.795762955728513e-08,0.9987502603624245 +147240000.0,1.0,4.790268383026396e-08,0.9988542860118736 +147300000.0,1.0,4.785278520127409e-08,0.9989583333333331 +147360000.0,1.0,4.7807918541537506e-08,0.9990624023335763 +147420000.0,1.0,4.776807030770417e-08,0.9991664930193789 +147480000.0,1.0,4.773322853505085e-08,0.9992706053975201 +147540000.0,1.0,4.770338283150663e-08,0.9993747394747812 +147600000.0,1.0,4.7678524372501155e-08,0.9994788952579469 +147660000.0,1.0,4.765864589663186e-08,0.9995830727538044 +147720000.0,1.0,4.7643741702147346e-08,0.9996872719691441 +147780000.0,1.0,4.763380764424448e-08,0.999791492910759 +147840000.0,1.0,4.762884113317741e-08,0.9998957355854446 +147900000.0,1.0,4.762884113317741e-08,1.0 +147960000.0,1.0,4.762884113317741e-08,1.0 +148020000.0,1.0,4.762884113317741e-08,1.0 +148080000.0,1.0,4.762884113317741e-08,1.0 +148140000.0,1.0,4.762884113317741e-08,1.0 +148200000.0,1.0,4.762884113317741e-08,1.0 +148260000.0,1.0,4.762884113317741e-08,1.0 +148320000.0,1.0,4.762884113317741e-08,1.0 +148380000.0,1.0,4.762884113317741e-08,1.0 +148440000.0,1.0,4.762884113317741e-08,1.0 +148500000.0,1.0,4.762884113317741e-08,1.0 +148560000.0,1.0,4.762884113317741e-08,1.0 +148620000.0,1.0,4.762884113317741e-08,1.0 +148680000.0,1.0,4.762884113317741e-08,1.0 +148740000.0,1.0,4.762884113317741e-08,1.0 +148800000.0,1.0,4.762884113317741e-08,1.0 +148860000.0,1.0,4.762884113317741e-08,1.0 +148920000.0,1.0,4.762884113317741e-08,1.0 +148980000.0,1.0,4.762884113317741e-08,1.0 +149040000.0,1.0,4.762884113317741e-08,1.0 +149100000.0,1.0,4.762884113317741e-08,1.0 +149160000.0,1.0,4.762884113317741e-08,1.0 +149220000.0,1.0,4.762884113317741e-08,1.0 +149280000.0,1.0,4.762884113317741e-08,1.0 +149340000.0,1.0,4.762884113317741e-08,1.0 +149400000.0,1.0,4.762884113317741e-08,1.0 +149460000.0,1.0,4.762884113317741e-08,1.0 +149520000.0,1.0,4.762884113317741e-08,1.0 +149580000.0,1.0,4.762884113317741e-08,1.0 +149640000.0,1.0,4.762884113317741e-08,1.0 +149700000.0,1.0,4.762884113317741e-08,1.0 +149760000.0,1.0,4.762884113317741e-08,1.0 +149820000.0,1.0,4.762884113317741e-08,1.0 +149880000.0,1.0,4.762884113317741e-08,1.0 +149940000.0,1.0,4.762884113317741e-08,1.0 +150000000.0,1.0,4.762884113317741e-08,1.0 +150060000.0,1.0,4.762884113317741e-08,1.0 +150120000.0,1.0,4.762884113317741e-08,1.0 +150180000.0,1.0,4.762884113317741e-08,1.0 +150240000.0,1.0,4.762884113317741e-08,1.0 +150300000.0,1.0,4.762884113317741e-08,1.0 +150360000.0,1.0,4.762884113317741e-08,1.0 +150420000.0,1.0,4.762884113317741e-08,1.0 +150480000.0,1.0,4.762884113317741e-08,1.0 +150540000.0,1.0,4.762884113317741e-08,1.0 +150600000.0,1.0,4.762884113317741e-08,1.0 +150660000.0,1.0,4.762884113317741e-08,1.0 +150720000.0,1.0,4.762884113317741e-08,1.0 +150780000.0,1.0,4.762884113317741e-08,1.0 +150840000.0,1.0,4.762884113317741e-08,1.0 +150900000.0,1.0,4.762884113317741e-08,1.0 +150960000.0,1.0,4.762884113317741e-08,1.0 diff --git a/source-code/Poisson-Graphs/new/outputM.txt b/source-code/Poisson-Graphs/new/outputM.txt new file mode 100644 index 0000000..99e4ad2 --- /dev/null +++ b/source-code/Poisson-Graphs/new/outputM.txt @@ -0,0 +1,602 @@ +time,rateConstant,difficulty +0.0,1.0,1.0 +1.0,1.0,1.0 +2.0,1.0,1.0 +3.0,1.0,1.0 +4.0,1.0,1.0 +5.0,1.0,1.0 +6.0,1.0,1.0 +7.0,1.0,1.0 +8.0,1.0,1.0 +9.0,1.0,1.0 +10.0,1.0,1.0 +11.0,1.0,1.0 +12.0,1.0,1.0 +13.0,1.0,1.0 +14.0,1.0,1.0 +15.0,1.0,1.0 +16.0,1.0,1.0 +17.0,1.0,1.0 +18.0,1.0,1.0 +19.0,1.0,1.0 +20.0,1.0,1.0 +21.0,1.0,1.0 +22.0,1.0,1.0 +23.0,1.0,1.0 +24.0,1.0,1.0 +25.0,1.0,1.0 +26.0,1.0,1.0 +27.0,1.0,1.0 +28.0,1.0,1.0 +29.0,1.0,1.0 +30.0,1.0,1.0 +31.0,1.0,1.0 +32.0,1.0,1.0 +33.0,1.0,1.0 +34.0,1.0,1.0 +35.0,1.0,1.0 +36.0,1.0,1.0 +37.0,1.0,1.0 +38.0,1.0,1.0 +39.0,1.0,1.0 +40.0,1.0,1.0 +41.0,1.0,1.0 +42.0,1.0,1.0 +43.0,1.0,1.0 +44.0,1.0,1.0 +45.0,1.0,1.0 +46.0,1.0,1.0 +47.0,1.0,1.0 +48.0,1.0,1.0 +49.0,1.0,1.0 +50.0,1.0,1.0 +51.0,1.0,1.0 +52.0,1.0,1.0 +53.0,1.0,1.0 +54.0,1.0,1.0 +55.0,1.0,1.0 +56.0,1.0,1.0 +57.0,1.0,1.0 +58.0,1.0,1.0 +59.0,1.0,1.0 +60.0,1.0,1.0 +61.0,1.0,1.0 +62.0,1.0,1.0 +63.0,1.0,1.0 +64.0,1.0,1.0 +65.0,1.0,1.0 +66.0,1.0,1.0 +67.0,1.0,1.0 +68.0,1.0,1.0 +69.0,1.0,1.0 +70.0,1.0,1.0 +71.0,1.0,1.0 +72.0,1.0,1.0 +73.0,1.0,1.0 +74.0,1.0,1.0 +75.0,1.0,1.0 +76.0,1.0,1.0 +77.0,1.0,1.0 +78.0,1.0,1.0 +79.0,1.0,1.0 +80.0,1.0,1.0 +81.0,1.0,1.0 +82.0,1.0,1.0 +83.0,1.0,1.0 +84.0,1.0,1.0 +85.0,1.0,1.0 +86.0,1.0,1.0 +87.0,1.0,1.0 +88.0,1.0,1.0 +89.0,1.0,1.0 +90.0,1.0,1.0 +91.0,1.0,1.0 +92.0,1.0,1.0 +93.0,1.0,1.0 +94.0,1.0,1.0 +95.0,1.0,1.0 +96.0,1.0,1.0 +97.0,1.0,1.0 +98.0,1.0,1.0 +99.0,1.0,1.0 +100.0,1.0,1.0 +101.0,1.0,1.0 +102.0,1.0,1.0 +103.0,1.0,1.0 +104.0,1.0,1.0 +105.0,1.0,1.0 +106.0,1.0,1.0 +107.0,1.0,1.0 +108.0,1.0,1.0 +109.0,1.0,1.0 +110.0,1.0,1.0 +111.0,1.0,1.0 +112.0,1.0,1.0 +113.0,1.0,1.0 +114.0,1.0,1.0 +115.0,1.0,1.0 +116.0,1.0,1.0 +117.0,1.0,1.0 +118.0,1.0,1.0 +119.0,1.0,1.0 +120.0,1.0,1.0 +121.0,1.0,1.0 +122.0,1.0,1.0 +123.0,1.0,1.0 +124.0,1.0,1.0 +125.0,1.0,1.0 +126.0,1.0,1.0 +127.0,1.0,1.0 +128.0,1.0,1.0 +129.0,1.0,1.0 +130.0,1.0,1.0 +131.0,1.0,1.0 +132.0,1.0,1.0 +133.0,1.0,1.0 +134.0,1.0,1.0 +135.0,1.0,1.0 +136.0,1.0,1.0 +137.0,1.0,1.0 +138.0,1.0,1.0 +139.0,1.0,1.0 +140.0,1.0,1.0 +141.0,1.0,1.0 +142.0,1.0,1.0 +143.0,1.0,1.0 +144.0,1.0,1.0 +145.0,1.0,1.0 +146.0,1.0,1.0 +147.0,1.0,1.0 +148.0,1.0,1.0 +149.0,1.0,1.0 +150.0,1.0,1.0 +151.0,1.0,1.0 +152.0,1.0,1.0 +153.0,1.0,1.0 +154.0,1.0,1.0 +155.0,1.0,1.0 +156.0,1.0,1.0 +157.0,1.0,1.0 +158.0,1.0,1.0 +159.0,1.0,1.0 +160.0,1.0,1.0 +161.0,1.0,1.0 +162.0,1.0,1.0 +163.0,1.0,1.0 +164.0,1.0,1.0 +165.0,1.0,1.0 +166.0,1.0,1.0 +167.0,1.0,1.0 +168.0,1.0,1.0 +169.0,1.0,1.0 +170.0,1.0,1.0 +171.0,1.0,1.0 +172.0,1.0,1.0 +173.0,1.0,1.0 +174.0,1.0,1.0 +175.0,1.0,1.0 +176.0,1.0,1.0 +177.0,1.0,1.0 +178.0,1.0,1.0 +179.0,1.0,1.0 +180.0,1.0,1.0 +181.0,1.0,1.0 +182.0,1.0,1.0 +183.0,1.0,1.0 +184.0,1.0,1.0 +185.0,1.0,1.0 +186.0,1.0,1.0 +187.0,1.0,1.0 +188.0,1.0,1.0 +189.0,1.0,1.0 +190.0,1.0,1.0 +191.0,1.0,1.0 +192.0,1.0,1.0 +193.0,1.0,1.0 +194.0,1.0,1.0 +195.0,1.0,1.0 +196.0,1.0,1.0 +197.0,1.0,1.0 +198.0,1.0,1.0 +199.0,1.0,1.0 +200.0,1.0,1.0 +201.0,1.0,1.0 +202.0,1.0,1.0 +203.0,1.0,1.0 +204.0,1.0,1.0 +205.0,1.0,1.0 +206.0,1.0,1.0 +207.0,1.0,1.0 +208.0,1.0,1.0 +209.0,1.0,1.0 +210.0,1.0,1.0 +211.0,1.0,1.0 +212.0,1.0,1.0 +213.0,1.0,1.0 +214.0,1.0,1.0 +215.0,1.0,1.0 +216.0,1.0,1.0 +217.0,1.0,1.0 +218.0,1.0,1.0 +219.0,1.0,1.0 +220.0,1.0,1.0 +221.0,1.0,1.0 +222.0,1.0,1.0 +223.0,1.0,1.0 +224.0,1.0,1.0 +225.0,1.0,1.0 +226.0,1.0,1.0 +227.0,1.0,1.0 +228.0,1.0,1.0 +229.0,1.0,1.0 +230.0,1.0,1.0 +231.0,1.0,1.0 +232.0,1.0,1.0 +233.0,1.0,1.0 +234.0,1.0,1.0 +235.0,1.0,1.0 +236.0,1.0,1.0 +237.0,1.0,1.0 +238.0,1.0,1.0 +239.0,1.0,1.0 +240.0,1.0,1.0 +241.0,1.0,1.0 +242.0,1.0,1.0 +243.0,1.0,1.0 +244.0,1.0,1.0 +245.0,1.0,1.0 +246.0,1.0,1.0 +247.0,1.0,1.0 +248.0,1.0,1.0 +249.0,1.0,1.0 +250.0,1.0,1.0 +251.0,1.0,1.0 +252.0,1.0,1.0 +253.0,1.0,1.0 +254.0,1.0,1.0 +255.0,1.0,1.0 +256.0,1.0,1.0 +257.0,1.0,1.0 +258.0,1.0,1.0 +259.0,1.0,1.0 +260.0,1.0,1.0 +261.0,1.0,1.0 +262.0,1.0,1.0 +263.0,1.0,1.0 +264.0,1.0,1.0 +265.0,1.0,1.0 +266.0,1.0,1.0 +267.0,1.0,1.0 +268.0,1.0,1.0 +269.0,1.0,1.0 +270.0,1.0,1.0 +271.0,1.0,1.0 +272.0,1.0,1.0 +273.0,1.0,1.0 +274.0,1.0,1.0 +275.0,1.0,1.0 +276.0,1.0,1.0 +277.0,1.0,1.0 +278.0,1.0,1.0 +279.0,1.0,1.0 +280.0,1.0,1.0 +281.0,1.0,1.0 +282.0,1.0,1.0 +283.0,1.0,1.0 +284.0,1.0,1.0 +285.0,1.0,1.0 +286.0,1.0,1.0 +287.0,1.0,1.0 +288.0,1.0,1.0 +289.0,1.0,1.0 +290.0,1.0,1.0 +291.0,1.0,1.0 +292.0,1.0,1.0 +293.0,1.0,1.0 +294.0,1.0,1.0 +295.0,1.0,1.0 +296.0,1.0,1.0 +297.0,1.0,1.0 +298.0,1.0,1.0 +299.0,1.0,1.0 +300.0,1.0,1.0 +301.0,1.0,1.0 +302.0,1.0,1.0 +303.0,1.0,1.0 +304.0,1.0,1.0 +305.0,1.0,1.0 +306.0,1.0,1.0 +307.0,1.0,1.0 +308.0,1.0,1.0 +309.0,1.0,1.0 +310.0,1.0,1.0 +311.0,1.0,1.0 +312.0,1.0,1.0 +313.0,1.0,1.0 +314.0,1.0,1.0 +315.0,1.0,1.0 +316.0,1.0,1.0 +317.0,1.0,1.0 +318.0,1.0,1.0 +319.0,1.0,1.0 +320.0,1.0,1.0 +321.0,1.0,1.0 +322.0,1.0,1.0 +323.0,1.0,1.0 +324.0,1.0,1.0 +325.0,1.0,1.0 +326.0,1.0,1.0 +327.0,1.0,1.0 +328.0,1.0,1.0 +329.0,1.0,1.0 +330.0,1.0,1.0 +331.0,1.0,1.0 +332.0,1.0,1.0 +333.0,1.0,1.0 +334.0,1.0,1.0 +335.0,1.0,1.0 +336.0,1.0,1.0 +337.0,1.0,1.0 +338.0,1.0,1.0 +339.0,1.0,1.0 +340.0,1.0,1.0 +341.0,1.0,1.0 +342.0,1.0,1.0 +343.0,1.0,1.0 +344.0,1.0,1.0 +345.0,1.0,1.0 +346.0,1.0,1.0 +347.0,1.0,1.0 +348.0,1.0,1.0 +349.0,1.0,1.0 +350.0,1.0,1.0 +351.0,1.0,1.0 +352.0,1.0,1.0 +353.0,1.0,1.0 +354.0,1.0,1.0 +355.0,1.0,1.0 +356.0,1.0,1.0 +357.0,1.0,1.0 +358.0,1.0,1.0 +359.0,1.0,1.0 +360.0,1.0,1.0 +361.0,1.0,1.0 +362.0,1.0,1.0 +363.0,1.0,1.0 +364.0,1.0,1.0 +365.0,1.0,1.0 +366.0,1.0,1.0 +367.0,1.0,1.0 +368.0,1.0,1.0 +369.0,1.0,1.0 +370.0,1.0,1.0 +371.0,1.0,1.0 +372.0,1.0,1.0 +373.0,1.0,1.0 +374.0,1.0,1.0 +375.0,1.0,1.0 +376.0,1.0,1.0 +377.0,1.0,1.0 +378.0,1.0,1.0 +379.0,1.0,1.0 +380.0,1.0,1.0 +381.0,1.0,1.0 +382.0,1.0,1.0 +383.0,1.0,1.0 +384.0,1.0,1.0 +385.0,1.0,1.0 +386.0,1.0,1.0 +387.0,1.0,1.0 +388.0,1.0,1.0 +389.0,1.0,1.0 +390.0,1.0,1.0 +391.0,1.0,1.0 +392.0,1.0,1.0 +393.0,1.0,1.0 +394.0,1.0,1.0 +395.0,1.0,1.0 +396.0,1.0,1.0 +397.0,1.0,1.0 +398.0,1.0,1.0 +399.0,1.0,1.0 +400.0,1.0,1.0 +401.0,1.0,1.0 +402.0,1.0,1.0 +403.0,1.0,1.0 +404.0,1.0,1.0 +405.0,1.0,1.0 +406.0,1.0,1.0 +407.0,1.0,1.0 +408.0,1.0,1.0 +409.0,1.0,1.0 +410.0,1.0,1.0 +411.0,1.0,1.0 +412.0,1.0,1.0 +413.0,1.0,1.0 +414.0,1.0,1.0 +415.0,1.0,1.0 +416.0,1.0,1.0 +417.0,1.0,1.0 +418.0,1.0,1.0 +419.0,1.0,1.0 +420.0,1.0,1.0 +421.0,1.0,1.0 +422.0,1.0,1.0 +423.0,1.0,1.0 +424.0,1.0,1.0 +425.0,1.0,1.0 +426.0,1.0,1.0 +427.0,1.0,1.0 +428.0,1.0,1.0 +429.0,1.0,1.0 +430.0,1.0,1.0 +431.0,1.0,1.0 +432.0,1.0,1.0 +433.0,1.0,1.0 +434.0,1.0,1.0 +435.0,1.0,1.0 +436.0,1.0,1.0 +437.0,1.0,1.0 +438.0,1.0,1.0 +439.0,1.0,1.0 +440.0,1.0,1.0 +441.0,1.0,1.0 +442.0,1.0,1.0 +443.0,1.0,1.0 +444.0,1.0,1.0 +445.0,1.0,1.0 +446.0,1.0,1.0 +447.0,1.0,1.0 +448.0,1.0,1.0 +449.0,1.0,1.0 +450.0,1.0,1.0 +451.0,1.0,1.0 +452.0,1.0,1.0 +453.0,1.0,1.0 +454.0,1.0,1.0 +455.0,1.0,1.0 +456.0,1.0,1.0 +457.0,1.0,1.0 +458.0,1.0,1.0 +459.0,1.0,1.0 +460.0,1.0,1.0 +461.0,1.0,1.0 +462.0,1.0,1.0 +463.0,1.0,1.0 +464.0,1.0,1.0 +465.0,1.0,1.0 +466.0,1.0,1.0 +467.0,1.0,1.0 +468.0,1.0,1.0 +469.0,1.0,1.0 +470.0,1.0,1.0 +471.0,1.0,1.0 +472.0,1.0,1.0 +473.0,1.0,1.0 +474.0,1.0,1.0 +475.0,1.0,1.0 +476.0,1.0,1.0 +477.0,1.0,1.0 +478.0,1.0,1.0 +479.0,1.0,1.0 +480.0,1.0,1.0 +481.0,1.0,1.0 +482.0,1.0,1.0 +483.0,1.0,1.0 +484.0,1.0,1.0 +485.0,1.0,1.0 +486.0,1.0,1.0 +487.0,1.0,1.0 +488.0,1.0,1.0 +489.0,1.0,1.0 +490.0,1.0,1.0 +491.0,1.0,1.0 +492.0,1.0,1.0 +493.0,1.0,1.0 +494.0,1.0,1.0 +495.0,1.0,1.0 +496.0,1.0,1.0 +497.0,1.0,1.0 +498.0,1.0,1.0 +499.0,1.0,1.0 +500.0,1.0,1.0 +501.0,1.0,1.0 +502.0,1.0,1.0 +503.0,1.0,1.0 +504.0,1.0,1.0 +505.0,1.0,1.0 +506.0,1.0,1.0 +507.0,1.0,1.0 +508.0,1.0,1.0 +509.0,1.0,1.0 +510.0,1.0,1.0 +511.0,1.0,1.0 +512.0,1.0,1.0 +513.0,1.0,1.0 +514.0,1.0,1.0 +515.0,1.0,1.0 +516.0,1.0,1.0 +517.0,1.0,1.0 +518.0,1.0,1.0 +519.0,1.0,1.0 +520.0,1.0,1.0 +521.0,1.0,1.0 +522.0,1.0,1.0 +523.0,1.0,1.0 +524.0,1.0,1.0 +525.0,1.0,1.0 +526.0,1.0,1.0 +527.0,1.0,1.0 +528.0,1.0,1.0 +529.0,1.0,1.0 +530.0,1.0,1.0 +531.0,1.0,1.0 +532.0,1.0,1.0 +533.0,1.0,1.0 +534.0,1.0,1.0 +535.0,1.0,1.0 +536.0,1.0,1.0 +537.0,1.0,1.0 +538.0,1.0,1.0 +539.0,1.0,1.0 +540.0,1.0,1.0 +541.0,1.0,1.0 +542.0,1.0,1.0 +543.0,1.0,1.0 +544.0,1.0,1.0 +545.0,1.0,1.0 +546.0,1.0,1.0 +547.0,1.0,1.0 +548.0,1.0,1.0 +549.0,1.0,1.0 +550.0,1.0,1.0 +551.0,1.0,1.0 +552.0,1.0,1.0 +553.0,1.0,1.0 +554.0,1.0,1.0 +555.0,1.0,1.0 +556.0,1.0,1.0 +557.0,1.0,1.0 +558.0,1.0,1.0 +559.0,1.0,1.0 +560.0,1.0,1.0 +561.0,1.0,1.0 +562.0,1.0,1.0 +563.0,1.0,1.0 +564.0,1.0,1.0 +565.0,1.0,1.0 +566.0,1.0,1.0 +567.0,1.0,1.0 +568.0,1.0,1.0 +569.0,1.0,1.0 +570.0,1.0,1.0 +571.0,1.0,1.0 +572.0,1.0,1.0 +573.0,1.0,1.0 +574.0,1.0,1.0 +575.0,1.0,1.0 +576.0,1.0,1.0 +577.0,1.0,1.0 +578.0,1.0,1.0 +579.0,1.0,1.0 +580.0,1.0,1.0 +581.0,1.0,1.0 +582.0,1.0,1.0 +583.0,1.0,1.0 +584.0,1.0,1.0 +585.0,1.0,1.0 +586.0,1.0,1.0 +587.0,1.0,1.0 +588.0,1.0,1.0 +589.0,1.0,1.0 +590.0,1.0,1.0 +591.0,1.0,1.0 +592.0,1.0,1.0 +593.0,1.0,1.0 +594.0,1.0,1.0 +595.0,1.0,1.0 +596.0,1.0,1.0 +597.0,1.0,1.0 +598.0,1.0,1.0 +599.0,1.0,1.0 +600.0,1.0,1.0 diff --git a/source-code/Poisson-Graphs/new/outputM.txt~ b/source-code/Poisson-Graphs/new/outputM.txt~ new file mode 100644 index 0000000..7af84cc --- /dev/null +++ b/source-code/Poisson-Graphs/new/outputM.txt~ @@ -0,0 +1,201 @@ +time,rateConstant,difficulty +0.0,1.0,1.0 +120458.46590012926,1.0,1.0 +240605.33373578714,1.0,1.0 +360287.44233708055,1.0,1.0 +480034.84471731156,1.0,1.0 +601021.4072874074,1.0,1.0 +720155.9397105722,1.0,1.0 +840557.1097845419,1.0,1.0 +960142.4715273783,1.0,1.0 +1080326.4196690398,1.0,1.0 +1200228.3567496322,1.0,1.0 +1321393.1769845556,1.0,1.0 +1442403.7637959223,1.0,1.0 +1563312.6884154421,1.0,1.0 +1683650.3562759135,1.0,1.0 +1804242.0878089573,1.0,1.0 +1923957.6270508477,1.0,1.0 +2043603.5193920576,1.0,1.0 +2162815.4440224506,1.0,1.0 +2282288.929005547,1.0,1.0 +2402114.705755926,1.0,1.0 +2522303.585101224,1.0,1.0 +2643267.2988593285,1.0,1.0 +2762617.5338163865,1.0,1.0 +2882563.380726947,1.0,1.0 +3003489.532699132,1.0,1.0 +3124641.562256374,1.0,1.0 +3245100.9215427977,1.0,1.0 +3365536.420458877,1.0,1.0 +3484337.996609595,1.0,1.0 +3604694.7847104715,1.0,1.0 +3724951.576782264,1.0,1.0 +3846026.69613723,1.0,1.0 +3967024.7982774274,1.0,1.0 +4085911.063711746,1.0,1.0 +4205815.028144305,1.0,1.0 +4325421.232620401,1.0,1.0 +4444827.052513202,1.0,1.0 +4565099.154304211,1.0,1.0 +4686236.387124661,1.0,1.0 +4806734.051850467,1.0,1.0 +4926566.647937627,1.0,1.0 +5045488.866484466,1.0,1.0 +5166502.755345808,1.0,1.0 +5287233.653263695,1.0,1.0 +5407908.334559678,1.0,1.0 +5528786.366282915,1.0,1.0 +5647678.3122160155,1.0,1.0 +5767244.089580304,1.0,1.0 +5886473.651601109,1.0,1.0 +6006435.897878854,1.0,1.0 +6125341.27352921,1.0,1.0 +6245479.785253891,1.0,1.0 +6365523.236878065,1.0,1.0 +6485749.795878896,1.0,1.0 +6605240.444894265,1.0,1.0 +6724686.210446132,1.0,1.0 +6844155.218750592,1.0,1.0 +6963341.181369955,1.0,1.0 +7082695.6923123505,1.0,1.0 +7203655.141225615,1.0,1.0 +7322627.204840391,1.0,1.0 +7441799.159546731,1.0,1.0 +7562822.49932097,1.0,1.0 +7683323.524066307,1.0,1.0 +7804477.48899398,1.0,1.0 +7923457.857637024,1.0,1.0 +8043898.249303401,1.0,1.0 +8164187.296374614,1.0,1.0 +8283469.107294493,1.0,1.0 +8402627.723784521,1.0,1.0 +8523745.89200794,1.0,1.0 +8643778.9729813,1.0,1.0 +8764635.624631569,1.0,1.0 +8885538.059417976,1.0,1.0 +9006433.79263568,1.0,1.0 +9127489.588015186,1.0,1.0 +9247569.28782317,1.0,1.0 +9367958.084440755,1.0,1.0 +9487586.29442678,1.0,1.0 +9606427.886281481,1.0,1.0 +9725872.946140163,1.0,1.0 +9846820.596317865,1.0,1.0 +9966080.313457007,1.0,1.0 +10085765.108573573,1.0,1.0 +10206008.374459436,1.0,1.0 +10326807.845790997,1.0,1.0 +10447513.423168132,1.0,1.0 +10566795.347145824,1.0,1.0 +10686179.997466285,1.0,1.0 +10805522.808373246,1.0,1.0 +10925591.945700001,1.0,1.0 +11045387.795286857,1.0,1.0 +11165651.717103494,1.0,1.0 +11286830.098064246,1.0,1.0 +11406728.71699933,1.0,1.0 +11525922.789870113,1.0,1.0 +11646476.03903497,1.0,1.0 +11766081.434268286,1.0,1.0 +11886554.737807736,1.0,1.0 +12006591.066234503,1.0,1.0 +12125475.542528223,1.0,1.0 +12245282.608027933,1.0,1.0 +12364566.337023206,1.0,1.0 +12484001.537563423,1.0,1.0 +12604914.58673975,1.0,1.0 +12725797.319719713,1.0,1.0 +12844665.121160349,1.0,1.0 +12964577.111847764,1.0,1.0 +13083664.372389417,1.0,1.0 +13204397.122438395,1.0,1.0 +13324189.80038166,1.0,1.0 +13445349.787586372,1.0,1.0 +13564667.751049513,1.0,1.0 +13684270.141374838,1.0,1.0 +13804933.175467426,1.0,1.0 +13926075.032552686,1.0,1.0 +14045914.739095576,1.0,1.0 +14166631.90042476,1.0,1.0 +14286319.61550191,1.0,1.0 +14406521.722056692,1.0,0.434741782576 +14527069.972553544,1.0,0.19008730551 +14647876.788742132,1.0,0.0840528337448 +14766882.452974409,1.0,0.037302520528 +14886464.10335911,1.0,0.0165590754676 +15006383.983595744,1.0,0.00734675515331 +15125494.942906044,1.0,0.00323569454228 +15246686.831929097,1.0,0.00142738653296 +15365567.953963466,1.0,0.000625305203658 +15485210.071059253,1.0,0.00027137099116 +15605744.64157258,1.0,0.00011715461834 +15726806.733151415,1.0,5.07010986398e-05 +15847473.63409661,1.0,2.20995440742e-05 +15967616.71119521,1.0,9.7101015984e-06 +16087483.017800437,1.0,4.29503052419e-06 +16208328.180223988,1.0,1.92536966438e-06 +16328269.772618307,1.0,8.74191715802e-07 +16448898.77254663,1.0,4.04296324402e-07 +16569068.683560552,1.0,1.90818902578e-07 +16689986.260285133,1.0,9.28633545485e-08 +16810906.032656457,1.0,4.72143204458e-08 +16930289.120968327,1.0,2.49711320542e-08 +17050753.779718515,1.0,1.3892256413e-08 +17170600.427019596,1.0,8.15844774794e-09 +17289713.01359109,1.0,4.99225078962e-09 +17410383.421790008,1.0,3.25190780354e-09 +17530744.28431112,1.0,2.30869255122e-09 +17649863.36025036,1.0,1.75633116174e-09 +17771007.11205409,1.0,1.56057621444e-09 +17891565.791564044,1.0,2.04725172611e-09 +18010728.55130284,1.0,3.28303372549e-09 +18131143.8843602,1.0,3.25496154055e-09 +18251685.45446652,1.0,2.60736708269e-09 +18371507.007102486,1.0,1.85630105501e-09 +18491403.948076807,1.0,1.21928659457e-09 +18611553.039604228,1.0,7.49792394597e-10 +18730744.251551468,1.0,4.45186327526e-10 +18849856.685056694,1.0,2.61385070862e-10 +18968926.062500622,1.0,1.55149613051e-10 +19088067.33909847,1.0,9.51850841053e-11 +19207594.13354391,1.0,6.1434055957e-11 +19328017.11208744,1.0,4.14815251504e-11 +19448814.951942917,1.0,2.86357848006e-11 +19567841.28162038,1.0,2.11150220091e-11 +19688388.179414563,1.0,1.6406642243e-11 +19807627.873820234,1.0,1.43187359575e-11 +19928483.04169874,1.0,1.35913238196e-11 +20048557.76424465,1.0,1.4331290576e-11 +20167664.74191137,1.0,2.37342192382e-11 +20287610.467802733,1.0,3.14063325262e-11 +20408382.62417472,1.0,3.36556321046e-11 +20527817.08243184,1.0,3.00938650991e-11 +20646637.59478427,1.0,2.24650389033e-11 +20766577.880066067,1.0,1.49080962166e-11 +20885785.934438772,1.0,8.89077367161e-12 +21005536.07104386,1.0,4.87060433801e-12 +21124902.305172637,1.0,2.47052077112e-12 +21244885.7076036,1.0,1.17786853388e-12 +21364581.43746038,1.0,5.31532928098e-13 +21484493.676516064,1.0,2.28757375026e-13 +21603916.644479226,1.0,9.40324531004e-14 +21723733.860411238,1.0,3.70938898759e-14 +21844771.38186735,1.0,1.42162474893e-14 +21964794.368862327,1.0,5.30817642226e-15 +22085627.151319932,1.0,1.94525471916e-15 +22205153.52382764,1.0,6.9852514309e-16 +22324878.40351138,1.0,2.45757880538e-16 +22444702.60558849,1.0,8.47565473955e-17 +22564133.40356277,1.0,2.8611824903e-17 +22684916.550748322,1.0,9.50429614301e-18 +22804924.811821572,1.0,3.10972949853e-18 +22924555.89776567,1.0,1.00142181128e-18 +23044673.155832924,1.0,3.17850639386e-19 +23164255.219417505,1.0,9.93453168398e-20 +23283874.042637054,1.0,3.05585142432e-20 +23403159.90787814,1.0,9.23510334381e-21 +23522146.490881946,1.0,2.7354446299e-21 +23641985.23572208,1.0,7.94854774163e-22 +23760806.412928328,1.0,2.26044417743e-22 +23881715.30956635,1.0,6.31881915952e-23 From 0cd82ab66bd998922fd093b51ee62dd58b252df9 Mon Sep 17 00:00:00 2001 From: Brandon Goodell Date: Thu, 8 Feb 2018 13:51:24 -0700 Subject: [PATCH 06/13] Adding brief technical note on Monero BPs --- publications/standards/bulletproofs.bib | 7 ++ publications/standards/bulletproofs.tex | 105 ++++++++++++++++++++++++ publications/standards/logo.png | Bin 0 -> 41390 bytes publications/standards/mrl.cls | 43 ++++++++++ 4 files changed, 155 insertions(+) create mode 100644 publications/standards/bulletproofs.bib create mode 100644 publications/standards/bulletproofs.tex create mode 100644 publications/standards/logo.png create mode 100644 publications/standards/mrl.cls diff --git a/publications/standards/bulletproofs.bib b/publications/standards/bulletproofs.bib new file mode 100644 index 0000000..bb700f3 --- /dev/null +++ b/publications/standards/bulletproofs.bib @@ -0,0 +1,7 @@ +@misc{bp, + author = {Benedikt B\"unz and Jonathan Bootle and Dan Boneh and Andrew Poelstra and Pieter Wuille and Greg Maxwell}, + title = {Bulletproofs: Efficient Range Proofs for Confidential Transactions}, + howpublished = {Cryptology ePrint Archive, Report 2017/1066}, + year = {2017}, + note = {\url{https://eprint.iacr.org/2017/1066}}, +} diff --git a/publications/standards/bulletproofs.tex b/publications/standards/bulletproofs.tex new file mode 100644 index 0000000..399575f --- /dev/null +++ b/publications/standards/bulletproofs.tex @@ -0,0 +1,105 @@ +\documentclass{mrl} + +\title{Application of Bulletproofs in Monero Transactions} +\authors{Sarang Noether\footnote{\texttt{sarang.noether@protonmail.com}}} +\affiliations{Monero Research Lab} +\date{\today} + +\type{TECHNICAL NOTE} +\ident{MRL-XXXX} + +\begin{document} + +\begin{abstract} +This technical note briefly describes the proposed application of Bulletproofs \cite{bp} in Monero. The proofs are used as a drop-in replacement of the existing Borromean bitwise non-interactive zero-knowledge range proofs used to show that a committed amount is in a specified range. Bulletproofs reduce both proof size and verification time, as well as provide a straightforward method for batch verification of proofs from multiple transactions. We describe our implementation, noting specific areas of optimization from the original paper. +\end{abstract} + +\section{Introduction} +The implementation of confidential transaction amounts in Monero is accomplished using homomorphic commitments. Each input and output amount, including fees, is represented by a commitment of the form $vG + \mu H$, where $G$ and $H$ are elliptic curve generators, $v$ is the amount, and $\mu$ is a mask. Without knowledge of the commitment opening, a third party cannot determine the amount; however, it is trivial for the third party to convince itself that a transaction balances (that is, that the difference between inputs and output amounts is zero). The homomorphic property of the commitments is such that the difference in commitments must itself be a commitment to zero. + +However, this is not sufficient to ensure a correct and safe transaction model. An adversary could easily construct a combination of positive and negative outputs such that the transaction amounts balance. A third party would still verify that the transaction balances, though the adversary has effectively printed free money in an undetected fashion. To combat this, we require that each amount commitment come equipped with a \textit{range proof} that convinces a verifier that the corresponding output is both positive and does not risk an overflow by being too large. The range proof scheme must be non-interactive and zero-knowledge; that is, the verifier does not need to communicate with the prover once the proof is generated, and the proof itself reveals no information about the amount except that it is within the stated range. + +The current range proof style used in Monero confidential transactions is a \textit{Borromean bitwise} range proof. To generate a proof that a commitment $C \equiv vG + \mu H$ represents an amount $v \in [0,2^n-1]$ for some bit length $n > 0$ (in Monero $n = 64$), the prover generates separate commitments for each bit. The prover then generates a Borromean ring signature showing that each commitment is to either $0$ or $2^i$ for appropriate $i$. Any third-party verifier can then convince itself that the bit commitments reconstruct the committed amount, that each commitment is to either $0$ or $2^i$, and therefore that the committed amount lies in the correct range. + +However, this comes at a cost. Borromean bitwise proofs scale linearly in size with the number of bits in the range. Further, if multiple outputs are used in a transaction, a separate proof is required for each. Each proof is large, taking up $6.2$ kB of space. + +\section{Bulletproofs} +Bulletproofs are a recent general non-interactive zero-knowledge proof construction \cite{bp}. Using a novel inner product argument, they can be used in a variety of applications ranging from range proofs (pun intended) to verifiable shuffles and even proofs of general arithmetic circuit evaluation. For our purposes, they can accomplish the same goal as Borromean bitwise range proofs: convincing a verifier that a committed amount is within a claimed range. + +The details of Bulletproof construction, both for prover and verifier, are discussed in the paper \cite{bp}, so we will not duplicate them here. However, several definitions are useful when discussing the scaling. A standard Bulletproof that shows an amount is within the $n$-bit range $[0,2^n-1]$ is called a \textit{single-output proof} or a \textit{1-proof}. However, it is possible for a prover to construct a single proof showing that $m$ separate amounts (with separate random masks) each lie within the range $[0,2^n-1]$, where $m$ is a power of two. Such a proof is called an \textit{aggregate proof} or, more precisely, an $m$\textit{-proof}. The scheme is constructed in such a way that a single-output proof is trivially an $m$-proof with $m=1$ (which simplifies the code). It is important to note that the construction of an aggregate proof requires that the prover know each amount and mask; this means that while it is useful for all outputs in a transaction to be contained within a single aggregate proof for space savings, it is not possible for a third party to take existing proofs and construct an aggregate proof, either within a single transaction or between different transactions. + +The size scaling benefits of Bulletproofs occur at two levels: +\begin{enumerate} +\item \textbf{Bit length of range}. The size of a Bulletproof increases logarithmically with the number of bits in the range. In bitwise range proofs, the proof size increased linearly with the number of bits. +\item \textbf{Number of amounts in aggregate proof}. The size of a Bulletproof increases logarithmically with the number of amounts included in a single aggregate proof. In bitwise range proofs, the proof size increased linearly with the number of bits (since a separate proof was needed for each amount). +\end{enumerate} +We discuss efficiency in more detail below. + +There is a separate scaling argument that is useful. A new node that comes online will receive many $m$-proofs, at least one per post-Bulletproof transaction in the blockchain. Instead of verifying each of the proofs separately, the node can perform a \textit{batch verification} of as many proofs at a time as it wishes. As described below, this process requires that certain portions of each proof be verified separately, but allows for the remaining parts of the proofs to be batched and verified together. The resulting verification time is linear in the number of proofs, but with a significantly lower time per proof. An existing node that has already verified the transactions in the blockchain can still use batch verification on new transactions it receives, but the benefits are not as great due to the lower number of transactions that must be verified in a short time. + +\section{Optimizations} +For the most part, the proposed implementation of Bulletproofs in Monero follows the Bulletproofs paper in scope and notation wherever possible. However, we include several optimizations that have also been discussed for other projects. These optimizations are algebraically equivalent to those in the paper, but reduce the time required for verification. The author understands that some or all of the optimizations may be included in an update to the Bulletproofs paper sometime in the future. However, we document them here for completeness and ease of code review. The reader is encouraged to refer to the paper for the complete context of our changes. + +\subsection{Curve group notation} +The paper is written with a general group structure in mind, so scalar-group operations are written multiplicatively (\textit{e.g.} $x = a^bc^d$). In the case of elliptic curve groups, we use additive notation instead (\textit{e.g.} $X = bA + dC$) and use case to differentiate between curve points and scalars for clarity. This is purely a notational convenience. + +\subsection{Basepoint notation} +Throughout the paper, amount commitments are expressed as $V \equiv vG + \mu H$, where $G$ and $H$ are distinct (but arbitrary) fixed elliptic curve group generators. We interchange the roles of $G$ and $H$ throughout our implementation to match the use of existing base points used in commitments elsewhere in the Monero codebase. Note that the indexed $\{G_i\}$ and $\{H_i\}$ curve points are not modified in this way. + +\subsection{Fiat-Shamir challenges} +To make the Bulletproof scheme non-interactive, we follow the paper by introducing Fiat-Shamir challenges computed by hashing the proof transcript up to the point that a new challenge is needed. This is done by introducing a rolling hash that uses as input the previous challenge and any new proof elements introduced. The prover and verifier compute these challenges identically. + +\subsection{Inner product argment} +The inner product argument in Protocol 1 of the Bulletproofs paper uses recursion to shrink the size of its input vectors down to single elements. These inputs include distinct curve group generators $\{G_i\}$ and $\{H_i\}$, which we compute using an indexed hash function. We make several optimizations to this protocol for the verifier. + +First, we observe that the curve points in Equation (10) are in fact linear combinations of $\{G_i\}$ and $\{H_i\}$ that use the scalar challenges in Equations (24)-(25). Next, we note that the point $P$ in Equation (62) is passed into Protocol 1 as described in Section 4.2 of the paper. Since this curve point contains a linear combination of the same group generators as Protocol 1, we can take advantage of this and compute a single linear combination, rather than separately compute Equations (62) and (10). + +In practice, we replace Equations (62) and (10) with the following check, where $M \equiv |\{L_j\}| = |\{R_j\}|$: +$$A + xS - \mu G + \sum_{j=0}^{M-1}(w_j^2 L_j + w_j^{-2} R_j) + (t - ab)xH - \sum_{i=0}^{mn-1}(g_iG_i + h_iH_i) = 0$$ +The symbols are mostly those used in the paper. However, we use $w_j$ to represent the round challenges in Lines (21)-(22), and $x$ to represent the challenge in Lines (32)-(33) to avoid reuse of symbols. The scalars $g_i$ and $h_i$ are computed in the following way. Express the index $i = b_0b_1 \cdots b_{M-1}$ bitwise, where $b_{M-1}$ is the least-significant bit. Then +$$g_i = a\prod_{j=0}^{M-1} w_j^{2b_j-1} + z$$ +and +$$h_i = \left(by^{-i}\prod_{j=0}^{M-1} w_j^{-2b_j+1} - zy^i + z^{2+\lfloor i/N \rfloor}2^{i\operatorname{mod}N}\right)y^{-i}$$ +This optimization is applied only to the verifier. + +\subsection{Batch verification} +Our implementation permits the verifier to take many aggregate proofs and verify them together as a batch. We do not assume that the proofs each have the same number of outputs, nor make any restrictions on the maximum size of a batch. The batch verification we describe will only succeed if each proof is valid, and will fail if one or more proofs are invalid. + +Batch verification is split into two checks, performed after iterating over each proof in the batch. During the iteration, the verifier keeps ongoing sums of components from each proof and then performs the first-stage check for Equation (61): +\begin{equation} +\sum_l (\beta_l\tau_{xl}) G + \sum_l \beta_l\left[ t_l - (k_l + z_l \langle \overline{1}^{mn},\overline{y_l}^{mn} \rangle) \right] H - \sum_l \beta_l \left( \sum_j z_l^{j+2} V_{lj} - x_lT_{1l} - x_l^2T_{2l} \right) = 0 \nonumber +\end{equation} +The second-phase check proceeds similarly: +\begin{multline} +\sum_l \beta_l(A_l + x_lS_l) - \sum_l(\beta_l\mu_l) G + \sum_l\left[\beta_l \sum_j(w_{lj}^2 L_{lj} + w_{lj}^{-2} R_{lj})\right] + \sum_l \beta_l x_l(t_l - a_lb_l) H \\ +- \sum_i \left[\sum_l(\beta_l g_{li})G_i + \sum_l(\beta_l h_{li})H_i\right] = 0 \nonumber +\end{multline} +Here each $l$-indexed sum is over each proof in the batch, and $\beta_l$ is a weighting factor chosen at random (not deterministically) by the verifier. This ensures that, except with negligible probability, the checks will only succeed if each proof is separately valid; an adversary cannot selectively provide a batch containing invalid proofs in an attempt to fool the verifier. The benefit to this approach is that the sums can be computed as large multi-exponentiation operations after the scalars from all proofs have been assembled. + +If the batch fails either check, at least one proof in the batch is invalid. To identify which proofs are at fault, the verifier can either iterate through each proof and perform the checks separately (in linear time), or perform a binary search by successively performing the checks on half-batches until it identifies all faulty proofs (in logarithmic time). + +\section{Proof size} +Including the amount commitment $V$, a single Borromean bitwise range proof occupies $6.2$ kB of space; a transaction with $m$ outputs therefore requires $6.2m$ kB of space. An $m$-proof (with a $64$-bit range) requires $2\lg m + 17$ group elements and $5$ scalars, each of which takes up $32$ bytes. Table \ref{table:size} shows the space savings from Bulletproofs for several values of $m$. + +\begin{table}[h] +\begin{center} +\begin{tabular}{r|rr|c} +$m$ & Bulletproof & Borromean & Relative size \\ +\hline +$1$ & $704$ & $6200$ & $0.114$ \\ +$2$ & $768$ & $12400$ & $0.062$ \\ +$8$ & $896$ & $49600$ & $0.018$ \\ +$16$ & $960$ & $99200$ & $0.010$ \\ +$128$ & $1152$ & $793600$ & $0.001$ +\end{tabular} +\caption{Size (bytes) of $m$ Borromean proofs versus $m$-proof} +\label{table:size} +\end{center} +\end{table} + +Using data from the Monero blockchain\footnote{Data was taken from blocks 1400000 through 1500000} on the distribution of the number of outputs in transactions, the use of Bulletproofs would reduce the total size of range proofs by $94\%$. + +\bibliographystyle{plain} +\bibliography{bulletproofs} + +\end{document} \ No newline at end of file diff --git a/publications/standards/logo.png b/publications/standards/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7f647a20ac3a785db4010b1a3516997f86d02377 GIT binary patch literal 41390 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 Date: Thu, 8 Feb 2018 13:52:34 -0700 Subject: [PATCH 07/13] Deleted bullshit --- source-code/Simulator/Simulator.py | 275 ----------------------------- 1 file changed, 275 deletions(-) delete mode 100644 source-code/Simulator/Simulator.py diff --git a/source-code/Simulator/Simulator.py b/source-code/Simulator/Simulator.py deleted file mode 100644 index 8978cd1..0000000 --- a/source-code/Simulator/Simulator.py +++ /dev/null @@ -1,275 +0,0 @@ -import copy, hashlib, time - -class Event(object): - ''' Generalized event object ''' - def __init__(self,params): - self.data = {} - self.timeOfEvent = None - self.eventType = None - - -class Block(object): - ''' Block object, very simple... has an identity and a timestamp''' - def __init__(self, params): - self.ident = params[0] - self.timestamp = params[1] - -class Node(object): - ''' - Node object, represents a computer on the network. - Has an identity, a dict (?) of edges, a time offset on [-1,1] - representing how inaccurate the node's wall clock appears to be, - an intensity representing the node's hash rate, a blockchain - which is merely a list of block objects (ordered by their arrival - at the node, for simplicity), and a difficulty score (which is - a function of the blockchain) - ''' - def __init__(self, params): - self.ident = params[0] # string - self.edges = params[1] # dict of edges - self.timeOffset = params[2] # float - self.intensity = params[3] # float (positive) - self.difficulty = params[4] - self.blockchain = [] - - def makeBlock(self, t): - ts = t + self.timeOffset - newBlock = Block() - salt = random.random() - n = len(self.blockchain) - x = hash(str(n) + str(salt)) - newBlock.ident = x - newBlock.timestamp = ts - self.blockchain.append(newBlock) - self.computeDifficulty() - return newBlock - - # node object needs receiveBlock(block) method - def receiveBlock(self, blockToReceive): - self.blockchain.append(blockToReceive) - - def computeDifficulty(self, target, sampleSize): - N = min(sampleSize,len(self.blockchain)) - tempSum = 0.0 - for i in range(N): - tempSum += abs(self.blockchain[-i].timestamp - self.blockchain[-i-1].timestamp) - tempSum = float(tempSum)/float(N) # Average absolute time difference of last N blocks - lambdaMLE = 1.0/tempSum - self.difficulty = float(lambdaMLE/target)*self.difficulty - -class Edge(object): - ''' - Edge object representing the connection between one node and another. - Has two node objects, a and b, a length l, and a dictionary of pending blocks. - ''' - def __init__(self, params): - self.ident = params[0] # edge ident - self.a = params[1] # node ident of one incident node - self.b = params[2] # node ident of the other incident node (may be None when creating new blocks?) - self.l = params[3] # length of edge as measured in propagation time. - self.pendingBlocks = {} # blockIdent:(block, destination node ident, deterministic time of arrival) - - def addBlock(self, blockToAdd, blockFinderIdent, curTime): - # Include new block to self.pendingBlocks - timeOfArrival = curTime + self.l - if blockFinderIdent == self.a.ident: - self.pendingBlocks.update({blockToAdd.ident:(blockToAdd, self.b.ident, timeOfArrival)}) - elif blockFinderIdent == self.b.ident: - self.pendingBlocks.update({blockToAdd.ident:(blockToAdd, self.a.ident, timeOfArrival)}) - else: - print("fish sticks.") - - -class Network(object): - ''' - Network object consisting of a number of vertices, a probability that any pair of vertices - has an edge between them, a death rate of vertices, a dictionary of vertices, a dictionary - of edges, and a clock t. - ''' - def __init__(self, params): - self.numVertices = params[0] # integer - self.probOfEdge = params[1] # float (between 0.0 and 1.0) - self.deathRate = params[2] # float 1.0/(avg vertex lifespan) - self.vertices = {} # Dict with keys=node idents and values=nodes - self.edges = {} # Dict with keys=edge idents and values=edges - self.t = 0.0 - self.defaultNodeLength = 30.0 # milliseconds - self.initialize() - - def initialize(self): - # Generate self.numVertices new nodes with probability self.probOfEdge - # that any pair are incident. Discard any disconnected nodes (unless - # there is only one node) - try: - assert self.numVertices > 1 - except AssertionError: - print("Fish sticks... AGAIN! Come ON, fellas!") - - count = self.numVertices - 1 - - e = Event() - e.eventType = "node birth" - e.timeOfEvent = 0.0 - e.data = {"neighbors":[]} - self.birthNode(e) - - while count > 0: - count -= 1 - e.eventType = "node birth" - e.timeOfEvent = 0.0 - e.data = {"neighbors":[]} - for x in self.vertices: - u = random.random() - if u < self.probOfEdge: - e.data["neighbors"].append(x) - self.birthNode(e) - - def run(self, maxTime, birthrate=lambda x:math.exp(-(x-10.0)**2.0)): - # Run the simulation for maxTime and birthrate function (of time) - while self.t < maxTime: - if type(birthrate) is float: # We may pass in a constant birthrate - birthrate = lambda x:birthrate # but we want it treated as a function - e = self.nextEvent(birthrate) # Generate next event. - try: - assert e is not None - except AssertionError: - print("Got null event in run, bailing...") - break - self.t = e.timeOfEvent # Get time until next event. - self.execute(e) # Run the execute method - - def nextEvent(self, birthrate, t): - # The whole network experiences stochastic birth and death as a Poisson - # process, each node experiences stochastic block discovery as a (non- - # homogeneous) Poisson process. Betwixt these arrivals, other - # deterministic events occur as blocks are propagated along edges, - # changing the (local) block discovery rates. - - # Birth of node? - u = random.random() - u = math.ln(1.0-u)/birthrate(t) - e = Event() - e.eventType = "node birth" - e.timeOfEvent = self.t + u - e.data = {"neighbors":[]} - for x in self.vertices: - u = random.random() - if u < self.probOfEdge: - e.data["neighbors"].append(x) - - for x in self.vertices: - u = random.random() - u = math.ln(1.0 - u)/self.deathRate - tempTime = self.t + u - if tempTime < e.timeOfEvent: - e.eventType = "node death" - e.timeOfEvent = tempTime - e.data = {"identToKill":x} - - u = random.random() - localIntensity = self.vertices[x].intensity/self.vertices[x].difficulty - u = math.ln(1.0 - u)/localIntensity - tempTime = self.t + u - if tempTime < e.timeOfEvent: - e.eventType = "block found" - e.timeOfEvent = tempTime - e.data = {"blockFinderIdent":x} - - for edgeIdent in self.vertices[x].edges: - for pendingBlockIdent in self.edges[edgeIdent]: - timeOfBlockArrival = self.edges[edgeIdent].pendingBlocks[pendingBlockIdent][2] - if timeOfBlockArrival < e.timeOfEvent: - e.eventType = "block propagated" - e.timeOfEvent = timeOfBlockArrival - e.data = {"propEdgeIdent":edgeIdent, "pendingBlockIdent":blockIdent} - return e - - def execute(self, e): - # Take an event e as input. Depending on eventType of e, execute - # the correspondsing method. - if e.eventType == "node birth": - self.birthNode(e) - elif e.eventType == "node death": - self.killNode(e) - elif e.eventType == "block found": - self.foundBlock(e) - elif e.eventType == "block propagated": - self.propBlock(e) - - def birthNode(self, e): - # In this event, a new node is added to the network and edges are randomly decided upon. - # I will probably limit the number of peers for a new node eventually: a fixed probability - # of even 1% of edges per pair of nodes, in a graph with, say 1000 nodes, will see 10 peers - # per node... - # Also, I was kind of thinking this code should probably run with less than 50 nodes at a time, in general, - # otherwise the simulation needs to be highly optimized to make for reasonable simulation times. - - newNodeIdent = hash(str(len(self.vertices)) + str(random.random())) # Pick a new random node ident - newOffset = 2.0*random.random() - 1.0 # New time offset for the new node - newIntensity = random.random() # New node hash rate - newDifficulty = 1.0 # Dummy variable, will be replaced - - newbc = [] # This will be the union of the blockchains of all neighbors in e.data["neighbors"] - count = 0 # This will be a nonce to be combined with a salt for a unique identifier - newEdges = {} - for neighborIdent in e.data["neighbors"]: - newbc += [z for z in self.vertices[neighborIdent].blockchain if z not in newbc] - newEdgeIdent = hash(str(count) + str(random.random())) - count += 1 - newLength = random.random()*self.defaultNodeLength - otherSide = random.choice(self.vertices.keys()) - newEdge = Edge([newEdgeIdent, newNodeIdent, otherSide, newLength]) - newEdges.update({newEdgeIdent:newEdge}) - - params = [newNodeIdent, newEdges, newOffset, newIntensity, newDifficulty] - newNode = Node(params) # Create new node - newNode.blockchain = newbc # Store new blockchain - newNode.computeDifficulty() # Compute new node's difficulty - - self.vertices.update({newIdent:newNode}) # Add new node to self.vertices - self.edges.update(newEdges) # Add all new edges to self.edges - - def killNode(self, e): - # Remove node and all incident edges - nodeIdentToKill = e.data["identToKill"] - #edgesToKill = e.data["edgesToKill"] - for edgeIdentToKill in self.vertices[nodeIdentToKill].edges: - del self.edges[edgeIdentToKill] - del self.vertices[nodeIdentToKill] - - def foundBlock(self, e): - # In this instance, a node found a new block. - blockFinderIdent = e.data["blockFinderIdent"] # get identity of node that found the block. - blockFound = self.vertices[blockFinderIdent].makeBlock() # Nodes need a makeBlock method - for nextEdge in self.vertices[blockFinderIdent].edges: # propagate to edges - self.edges[nextEdge].addBlock(blockFound, blockFinderIdent, self.t) # Edges need an addBlock method - - def propBlock(self, e): - # In this instance, a block on an edge is plunked onto its destination node and then - # propagated to the resulting edges. - propEdgeIdent = e.data["propEdgeIdent"] # get the identity of the edge along which the block was propagating - blockToPropIdent = e.data["blockIdent"] # get the identity of the block beign propagated - # Get the block being propagated and the node identity of the receiver. - (blockToAdd, destIdent) = self.edges[propEdgeIdent].pendingBlocks[blockToPropIdent] - self.vertices[destIdent].receiveBlock(blockToAdd) # Call receiveBlock from the destination node. - del self.edges[propEdgeIdent].pendingBlocks[blockToPropIdent] # Now that the block has been received - for nextEdge in self.vertices[destIdent].edges: - if nextEdge != propEdgeIdent: - if self.edges[nextEdge].a.ident == destIdent: - otherSideIdent = self.edges[nextEdge].b.ident - elif self.edges[nextEdge].b.ident == destIdent: - otherSideIdent = self.edges[nextEdge].a.ident - else: - print("awww fish sticks, fellas") - - if blockToAdd.ident not in self.vertices[otherSideIdent].blockchain: - self.edges[nextEdge].addBlock(blockToAdd, destIdent, self.t) - -class Simulator(object): - def __init__(self, params): - #todo lol cryptonote style - pass - - def go(self): - nelly = Network([43, 0.25, 1.0/150.0]) - self.run(maxTime, birthrate=lambda x:math.exp((-(x-500.0)**2.0))/(2.0*150.0)) From 2a45d98b3749ac68dfc9e28b8a48b96c064f84ef Mon Sep 17 00:00:00 2001 From: Brandon Goodell Date: Thu, 8 Feb 2018 14:02:53 -0700 Subject: [PATCH 08/13] Directory reorg, batch verification, other optimizations --- .../BulletProofs/LinearBulletproof.java | 372 --------- source-code/BulletProofs/LogBulletproof.java | 532 ------------- .../BulletProofs/OptimizedLogBulletproof.java | 522 ------------- .../hodl/bulletproof/LinearBulletproof.java | 68 +- .../hodl/bulletproof/LogBulletproof.java | 103 ++- .../hodl/bulletproof/MultiBulletproof.java | 721 ++++++++++++++++++ .../bulletproof/OptimizedLogBulletproof.java | 103 ++- .../how/monero/hodl/bulletproof}/readme.md | 0 8 files changed, 905 insertions(+), 1516 deletions(-) delete mode 100644 source-code/BulletProofs/LinearBulletproof.java delete mode 100644 source-code/BulletProofs/LogBulletproof.java delete mode 100644 source-code/BulletProofs/OptimizedLogBulletproof.java create mode 100644 source-code/StringCT-java/src/how/monero/hodl/bulletproof/MultiBulletproof.java rename source-code/{BulletProofs => StringCT-java/src/how/monero/hodl/bulletproof}/readme.md (100%) diff --git a/source-code/BulletProofs/LinearBulletproof.java b/source-code/BulletProofs/LinearBulletproof.java deleted file mode 100644 index f33913e..0000000 --- a/source-code/BulletProofs/LinearBulletproof.java +++ /dev/null @@ -1,372 +0,0 @@ -// NOTE: this interchanges the roles of G and H to match other code's behavior - -package how.monero.hodl.bulletproof; - -import how.monero.hodl.crypto.Curve25519Point; -import how.monero.hodl.crypto.Scalar; -import how.monero.hodl.crypto.CryptoUtil; -import how.monero.hodl.util.ByteUtil; -import java.math.BigInteger; -import how.monero.hodl.util.VarInt; -import java.util.Random; - -import static how.monero.hodl.crypto.Scalar.randomScalar; -import static how.monero.hodl.crypto.CryptoUtil.*; -import static how.monero.hodl.util.ByteUtil.*; - -public class LinearBulletproof -{ - private static int N; - private static Curve25519Point G; - private static Curve25519Point H; - private static Curve25519Point[] Gi; - private static Curve25519Point[] Hi; - - public static class ProofTuple - { - private Curve25519Point V; - private Curve25519Point A; - private Curve25519Point S; - private Curve25519Point T1; - private Curve25519Point T2; - private Scalar taux; - private Scalar mu; - private Scalar[] l; - private Scalar[] r; - - public ProofTuple(Curve25519Point V, Curve25519Point A, Curve25519Point S, Curve25519Point T1, Curve25519Point T2, Scalar taux, Scalar mu, Scalar[] l, Scalar[] r) - { - this.V = V; - this.A = A; - this.S = S; - this.T1 = T1; - this.T2 = T2; - this.taux = taux; - this.mu = mu; - this.l = l; - this.r = r; - } - } - - /* Given two scalar arrays, construct a vector commitment */ - public static Curve25519Point VectorExponent(Scalar[] a, Scalar[] b) - { - Curve25519Point Result = Curve25519Point.ZERO; - for (int i = 0; i < N; i++) - { - Result = Result.add(Gi[i].scalarMultiply(a[i])); - Result = Result.add(Hi[i].scalarMultiply(b[i])); - } - return Result; - } - - /* Given a scalar, construct a vector of powers */ - public static Scalar[] VectorPowers(Scalar x) - { - Scalar[] result = new Scalar[N]; - for (int i = 0; i < N; i++) - { - result[i] = x.pow(i); - } - return result; - } - - /* Given two scalar arrays, construct the inner product */ - public static Scalar InnerProduct(Scalar[] a, Scalar[] b) - { - Scalar result = Scalar.ZERO; - for (int i = 0; i < N; i++) - { - result = result.add(a[i].mul(b[i])); - } - return result; - } - - /* Given two scalar arrays, construct the Hadamard product */ - public static Scalar[] Hadamard(Scalar[] a, Scalar[] b) - { - Scalar[] result = new Scalar[N]; - for (int i = 0; i < N; i++) - { - result[i] = a[i].mul(b[i]); - } - return result; - } - - /* Add two vectors */ - public static Scalar[] VectorAdd(Scalar[] a, Scalar[] b) - { - Scalar[] result = new Scalar[N]; - for (int i = 0; i < N; i++) - { - result[i] = a[i].add(b[i]); - } - return result; - } - - /* Subtract two vectors */ - public static Scalar[] VectorSubtract(Scalar[] a, Scalar[] b) - { - Scalar[] result = new Scalar[N]; - for (int i = 0; i < N; i++) - { - result[i] = a[i].sub(b[i]); - } - return result; - } - - /* Multiply a scalar and a vector */ - public static Scalar[] VectorScalar(Scalar[] a, Scalar x) - { - Scalar[] result = new Scalar[N]; - for (int i = 0; i < N; i++) - { - result[i] = a[i].mul(x); - } - return result; - } - - /* Compute the inverse of a scalar, the stupid way */ - public static Scalar Invert(Scalar x) - { - Scalar inverse = new Scalar(x.toBigInteger().modInverse(CryptoUtil.l)); - assert x.mul(inverse).equals(Scalar.ONE); - - return inverse; - } - - /* Compute the value of k(y,z) */ - public static Scalar ComputeK(Scalar y, Scalar z) - { - Scalar result = Scalar.ZERO; - result = result.sub(z.sq().mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); - result = result.sub(z.pow(3).mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(Scalar.TWO)))); - - return result; - } - - /* Given a value v (0..2^N-1) and a mask gamma, construct a range proof */ - public static ProofTuple PROVE(Scalar v, Scalar gamma) - { - Curve25519Point V = H.scalarMultiply(v).add(G.scalarMultiply(gamma)); - - // This hash is updated for Fiat-Shamir throughout the proof - Scalar hashCache = hashToScalar(V.toBytes()); - - // PAPER LINES 36-37 - Scalar[] aL = new Scalar[N]; - Scalar[] aR = new Scalar[N]; - - BigInteger tempV = v.toBigInteger(); - for (int i = N-1; i >= 0; i--) - { - BigInteger basePow = BigInteger.valueOf(2).pow(i); - if (tempV.divide(basePow).equals(BigInteger.ZERO)) - { - aL[i] = Scalar.ZERO; - } - else - { - aL[i] = Scalar.ONE; - tempV = tempV.subtract(basePow); - } - - aR[i] = aL[i].sub(Scalar.ONE); - } - - // DEBUG: Test to ensure this recovers the value - BigInteger test_aL = BigInteger.ZERO; - BigInteger test_aR = BigInteger.ZERO; - for (int i = 0; i < N; i++) - { - if (aL[i].equals(Scalar.ONE)) - test_aL = test_aL.add(BigInteger.valueOf(2).pow(i)); - if (aR[i].equals(Scalar.ZERO)) - test_aR = test_aR.add(BigInteger.valueOf(2).pow(i)); - } - assert test_aL.equals(v.toBigInteger()); - assert test_aR.equals(v.toBigInteger()); - - // PAPER LINES 38-39 - Scalar alpha = randomScalar(); - Curve25519Point A = VectorExponent(aL,aR).add(G.scalarMultiply(alpha)); - - // PAPER LINES 40-42 - Scalar[] sL = new Scalar[N]; - Scalar[] sR = new Scalar[N]; - for (int i = 0; i < N; i++) - { - sL[i] = randomScalar(); - sR[i] = randomScalar(); - } - Scalar rho = randomScalar(); - Curve25519Point S = VectorExponent(sL,sR).add(G.scalarMultiply(rho)); - - // PAPER LINES 43-45 - hashCache = hashToScalar(concat(hashCache.bytes,A.toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,S.toBytes())); - Scalar y = hashCache; - hashCache = hashToScalar(hashCache.bytes); - Scalar z = hashCache; - - Scalar t0 = Scalar.ZERO; - Scalar t1 = Scalar.ZERO; - Scalar t2 = Scalar.ZERO; - - t0 = t0.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); - t0 = t0.add(z.sq().mul(v)); - Scalar k = ComputeK(y,z); - t0 = t0.add(k); - - // DEBUG: Test the value of t0 has the correct form - Scalar test_t0 = Scalar.ZERO; - test_t0 = test_t0.add(InnerProduct(aL,Hadamard(aR,VectorPowers(y)))); - test_t0 = test_t0.add(z.mul(InnerProduct(VectorSubtract(aL,aR),VectorPowers(y)))); - test_t0 = test_t0.add(z.sq().mul(InnerProduct(VectorPowers(Scalar.TWO),aL))); - test_t0 = test_t0.add(k); - assert test_t0.equals(t0); - - t1 = t1.add(InnerProduct(VectorSubtract(aL,VectorScalar(VectorPowers(Scalar.ONE),z)),Hadamard(VectorPowers(y),sR))); - t1 = t1.add(InnerProduct(sL,VectorAdd(Hadamard(VectorPowers(y),VectorAdd(aR,VectorScalar(VectorPowers(Scalar.ONE),z))),VectorScalar(VectorPowers(Scalar.TWO),z.sq())))); - t2 = t2.add(InnerProduct(sL,Hadamard(VectorPowers(y),sR))); - - // PAPER LINES 47-48 - Scalar tau1 = randomScalar(); - Scalar tau2 = randomScalar(); - Curve25519Point T1 = H.scalarMultiply(t1).add(G.scalarMultiply(tau1)); - Curve25519Point T2 = H.scalarMultiply(t2).add(G.scalarMultiply(tau2)); - - // PAPER LINES 49-51 - hashCache = hashToScalar(concat(hashCache.bytes,z.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,T1.toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,T2.toBytes())); - Scalar x = hashCache; - - // PAPER LINES 52-53 - Scalar taux = Scalar.ZERO; - taux = tau1.mul(x); - taux = taux.add(tau2.mul(x.sq())); - taux = taux.add(gamma.mul(z.sq())); - Scalar mu = x.mul(rho).add(alpha); - - // PAPER LINES 54-57 - Scalar[] l = new Scalar[N]; - Scalar[] r = new Scalar[N]; - - l = VectorAdd(VectorSubtract(aL,VectorScalar(VectorPowers(Scalar.ONE),z)),VectorScalar(sL,x)); - r = VectorAdd(Hadamard(VectorPowers(y),VectorAdd(aR,VectorAdd(VectorScalar(VectorPowers(Scalar.ONE),z),VectorScalar(sR,x)))),VectorScalar(VectorPowers(Scalar.TWO),z.sq())); - - // DEBUG: Test if the l and r vectors match the polynomial forms - Scalar test_t = Scalar.ZERO; - test_t = test_t.add(t0).add(t1.mul(x)); - test_t = test_t.add(t2.mul(x.sq())); - assert test_t.equals(InnerProduct(l,r)); - - // PAPER LINE 58 - return new ProofTuple(V,A,S,T1,T2,taux,mu,l,r); - } - - /* Given a range proof, determine if it is valid */ - public static boolean VERIFY(ProofTuple proof) - { - // Reconstruct the challenges - Scalar hashCache = hashToScalar(proof.V.toBytes()); - hashCache = hashToScalar(concat(hashCache.bytes,proof.A.toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,proof.S.toBytes())); - Scalar y = hashCache; - hashCache = hashToScalar(hashCache.bytes); - Scalar z = hashCache; - hashCache = hashToScalar(concat(hashCache.bytes,z.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,proof.T1.toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,proof.T2.toBytes())); - Scalar x = hashCache; - - // PAPER LINE 60 - Scalar t = InnerProduct(proof.l,proof.r); - - // PAPER LINE 61 - Curve25519Point L61Left = G.scalarMultiply(proof.taux).add(H.scalarMultiply(t)); - - Scalar k = ComputeK(y,z); - - Curve25519Point L61Right = H.scalarMultiply(k.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y))))); - L61Right = L61Right.add(proof.V.scalarMultiply(z.sq())); - L61Right = L61Right.add(proof.T1.scalarMultiply(x)); - L61Right = L61Right.add(proof.T2.scalarMultiply(x.sq())); - - if (!L61Right.equals(L61Left)) - { - return false; - } - - // PAPER LINE 62 - Curve25519Point P = Curve25519Point.ZERO; - P = P.add(proof.A); - P = P.add(proof.S.scalarMultiply(x)); - - Scalar[] Gexp = new Scalar[N]; - for (int i = 0; i < N; i++) - Gexp[i] = Scalar.ZERO.sub(z); - - Scalar[] Hexp = new Scalar[N]; - for (int i = 0; i < N; i++) - { - Hexp[i] = Scalar.ZERO; - Hexp[i] = Hexp[i].add(z.mul(y.pow(i))); - Hexp[i] = Hexp[i].add(z.sq().mul(Scalar.TWO.pow(i))); - Hexp[i] = Hexp[i].mul(Invert(y).pow(i)); - } - P = P.add(VectorExponent(Gexp,Hexp)); - - // PAPER LINE 63 - for (int i = 0; i < N; i++) - { - Hexp[i] = Scalar.ZERO; - Hexp[i] = Hexp[i].add(proof.r[i]); - Hexp[i] = Hexp[i].mul(Invert(y).pow(i)); - } - Curve25519Point L63Right = VectorExponent(proof.l,Hexp).add(G.scalarMultiply(proof.mu)); - - if (!L63Right.equals(P)) - { - return false; - } - - return true; - } - - public static void main(String[] args) - { - // Number of bits in the range - N = 64; - - // Set the curve base points - G = Curve25519Point.G; - H = Curve25519Point.hashToPoint(G); - Gi = new Curve25519Point[N]; - Hi = new Curve25519Point[N]; - for (int i = 0; i < N; i++) - { - Gi[i] = getHpnGLookup(2*i); - Hi[i] = getHpnGLookup(2*i+1); - } - - // Run a bunch of randomized trials - Random rando = new Random(); - int TRIALS = 250; - int count = 0; - - while (count < TRIALS) - { - long amount = rando.nextLong(); - if (amount > Math.pow(2,N)-1 || amount < 0) - continue; - - ProofTuple proof = PROVE(new Scalar(BigInteger.valueOf(amount)),randomScalar()); - if (!VERIFY(proof)) - System.out.println("Test failed"); - - count += 1; - } - } -} diff --git a/source-code/BulletProofs/LogBulletproof.java b/source-code/BulletProofs/LogBulletproof.java deleted file mode 100644 index 7061cd9..0000000 --- a/source-code/BulletProofs/LogBulletproof.java +++ /dev/null @@ -1,532 +0,0 @@ -// NOTE: this interchanges the roles of G and H to match other code's behavior - -package how.monero.hodl.bulletproof; - -import how.monero.hodl.crypto.Curve25519Point; -import how.monero.hodl.crypto.Scalar; -import how.monero.hodl.crypto.CryptoUtil; -import java.math.BigInteger; -import java.util.Random; - -import static how.monero.hodl.crypto.Scalar.randomScalar; -import static how.monero.hodl.crypto.CryptoUtil.*; -import static how.monero.hodl.util.ByteUtil.*; - -public class LogBulletproof -{ - private static int N; - private static int logN; - private static Curve25519Point G; - private static Curve25519Point H; - private static Curve25519Point[] Gi; - private static Curve25519Point[] Hi; - - public static class ProofTuple - { - private Curve25519Point V; - private Curve25519Point A; - private Curve25519Point S; - private Curve25519Point T1; - private Curve25519Point T2; - private Scalar taux; - private Scalar mu; - private Curve25519Point[] L; - private Curve25519Point[] R; - private Scalar a; - private Scalar b; - private Scalar t; - - public ProofTuple(Curve25519Point V, Curve25519Point A, Curve25519Point S, Curve25519Point T1, Curve25519Point T2, Scalar taux, Scalar mu, Curve25519Point[] L, Curve25519Point[] R, Scalar a, Scalar b, Scalar t) - { - this.V = V; - this.A = A; - this.S = S; - this.T1 = T1; - this.T2 = T2; - this.taux = taux; - this.mu = mu; - this.L = L; - this.R = R; - this.a = a; - this.b = b; - this.t = t; - } - } - - /* Given two scalar arrays, construct a vector commitment */ - public static Curve25519Point VectorExponent(Scalar[] a, Scalar[] b) - { - assert a.length == N && b.length == N; - - Curve25519Point Result = Curve25519Point.ZERO; - for (int i = 0; i < N; i++) - { - Result = Result.add(Gi[i].scalarMultiply(a[i])); - Result = Result.add(Hi[i].scalarMultiply(b[i])); - } - return Result; - } - - /* Compute a custom vector-scalar commitment */ - public static Curve25519Point VectorExponentCustom(Curve25519Point[] A, Curve25519Point[] B, Scalar[] a, Scalar[] b) - { - assert a.length == A.length && b.length == B.length && a.length == b.length; - - Curve25519Point Result = Curve25519Point.ZERO; - for (int i = 0; i < a.length; i++) - { - Result = Result.add(A[i].scalarMultiply(a[i])); - Result = Result.add(B[i].scalarMultiply(b[i])); - } - return Result; - } - - /* Given a scalar, construct a vector of powers */ - public static Scalar[] VectorPowers(Scalar x) - { - Scalar[] result = new Scalar[N]; - for (int i = 0; i < N; i++) - { - result[i] = x.pow(i); - } - return result; - } - - /* Given two scalar arrays, construct the inner product */ - public static Scalar InnerProduct(Scalar[] a, Scalar[] b) - { - assert a.length == b.length; - - Scalar result = Scalar.ZERO; - for (int i = 0; i < a.length; i++) - { - result = result.add(a[i].mul(b[i])); - } - return result; - } - - /* Given two scalar arrays, construct the Hadamard product */ - public static Scalar[] Hadamard(Scalar[] a, Scalar[] b) - { - assert a.length == b.length; - - Scalar[] result = new Scalar[a.length]; - for (int i = 0; i < a.length; i++) - { - result[i] = a[i].mul(b[i]); - } - return result; - } - - /* Given two curvepoint arrays, construct the Hadamard product */ - public static Curve25519Point[] Hadamard2(Curve25519Point[] A, Curve25519Point[] B) - { - assert A.length == B.length; - - Curve25519Point[] Result = new Curve25519Point[A.length]; - for (int i = 0; i < A.length; i++) - { - Result[i] = A[i].add(B[i]); - } - return Result; - } - - /* Add two vectors */ - public static Scalar[] VectorAdd(Scalar[] a, Scalar[] b) - { - assert a.length == b.length; - - Scalar[] result = new Scalar[a.length]; - for (int i = 0; i < a.length; i++) - { - result[i] = a[i].add(b[i]); - } - return result; - } - - /* Subtract two vectors */ - public static Scalar[] VectorSubtract(Scalar[] a, Scalar[] b) - { - assert a.length == b.length; - - Scalar[] result = new Scalar[a.length]; - for (int i = 0; i < a.length; i++) - { - result[i] = a[i].sub(b[i]); - } - return result; - } - - /* Multiply a scalar and a vector */ - public static Scalar[] VectorScalar(Scalar[] a, Scalar x) - { - Scalar[] result = new Scalar[a.length]; - for (int i = 0; i < a.length; i++) - { - result[i] = a[i].mul(x); - } - return result; - } - - /* Exponentiate a curve vector by a scalar */ - public static Curve25519Point[] VectorScalar2(Curve25519Point[] A, Scalar x) - { - Curve25519Point[] Result = new Curve25519Point[A.length]; - for (int i = 0; i < A.length; i++) - { - Result[i] = A[i].scalarMultiply(x); - } - return Result; - } - - /* Compute the inverse of a scalar, the stupid way */ - public static Scalar Invert(Scalar x) - { - Scalar inverse = new Scalar(x.toBigInteger().modInverse(CryptoUtil.l)); - - assert x.mul(inverse).equals(Scalar.ONE); - return inverse; - } - - /* Compute the slice of a curvepoint vector */ - public static Curve25519Point[] CurveSlice(Curve25519Point[] a, int start, int stop) - { - Curve25519Point[] Result = new Curve25519Point[stop-start]; - for (int i = start; i < stop; i++) - { - Result[i-start] = a[i]; - } - return Result; - } - - /* Compute the slice of a scalar vector */ - public static Scalar[] ScalarSlice(Scalar[] a, int start, int stop) - { - Scalar[] result = new Scalar[stop-start]; - for (int i = start; i < stop; i++) - { - result[i-start] = a[i]; - } - return result; - } - - /* Compute the value of k(y,z) */ - public static Scalar ComputeK(Scalar y, Scalar z) - { - Scalar result = Scalar.ZERO; - result = result.sub(z.sq().mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); - result = result.sub(z.pow(3).mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(Scalar.TWO)))); - - return result; - } - - /* Given a value v (0..2^N-1) and a mask gamma, construct a range proof */ - public static ProofTuple PROVE(Scalar v, Scalar gamma) - { - Curve25519Point V = H.scalarMultiply(v).add(G.scalarMultiply(gamma)); - - // This hash is updated for Fiat-Shamir throughout the proof - Scalar hashCache = hashToScalar(V.toBytes()); - - // PAPER LINES 36-37 - Scalar[] aL = new Scalar[N]; - Scalar[] aR = new Scalar[N]; - - BigInteger tempV = v.toBigInteger(); - for (int i = N-1; i >= 0; i--) - { - BigInteger basePow = BigInteger.valueOf(2).pow(i); - if (tempV.divide(basePow).equals(BigInteger.ZERO)) - { - aL[i] = Scalar.ZERO; - } - else - { - aL[i] = Scalar.ONE; - tempV = tempV.subtract(basePow); - } - - aR[i] = aL[i].sub(Scalar.ONE); - } - - // PAPER LINES 38-39 - Scalar alpha = randomScalar(); - Curve25519Point A = VectorExponent(aL,aR).add(G.scalarMultiply(alpha)); - - // PAPER LINES 40-42 - Scalar[] sL = new Scalar[N]; - Scalar[] sR = new Scalar[N]; - for (int i = 0; i < N; i++) - { - sL[i] = randomScalar(); - sR[i] = randomScalar(); - } - Scalar rho = randomScalar(); - Curve25519Point S = VectorExponent(sL,sR).add(G.scalarMultiply(rho)); - - // PAPER LINES 43-45 - hashCache = hashToScalar(concat(hashCache.bytes,A.toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,S.toBytes())); - Scalar y = hashCache; - hashCache = hashToScalar(hashCache.bytes); - Scalar z = hashCache; - - // Polynomial construction before PAPER LINE 46 - Scalar t0 = Scalar.ZERO; - Scalar t1 = Scalar.ZERO; - Scalar t2 = Scalar.ZERO; - - t0 = t0.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); - t0 = t0.add(z.sq().mul(v)); - Scalar k = ComputeK(y,z); - t0 = t0.add(k); - - t1 = t1.add(InnerProduct(VectorSubtract(aL,VectorScalar(VectorPowers(Scalar.ONE),z)),Hadamard(VectorPowers(y),sR))); - t1 = t1.add(InnerProduct(sL,VectorAdd(Hadamard(VectorPowers(y),VectorAdd(aR,VectorScalar(VectorPowers(Scalar.ONE),z))),VectorScalar(VectorPowers(Scalar.TWO),z.sq())))); - - t2 = t2.add(InnerProduct(sL,Hadamard(VectorPowers(y),sR))); - - // PAPER LINES 47-48 - Scalar tau1 = randomScalar(); - Scalar tau2 = randomScalar(); - Curve25519Point T1 = H.scalarMultiply(t1).add(G.scalarMultiply(tau1)); - Curve25519Point T2 = H.scalarMultiply(t2).add(G.scalarMultiply(tau2)); - - // PAPER LINES 49-51 - hashCache = hashToScalar(concat(hashCache.bytes,z.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,T1.toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,T2.toBytes())); - Scalar x = hashCache; - - // PAPER LINES 52-53 - Scalar taux = Scalar.ZERO; - taux = tau1.mul(x); - taux = taux.add(tau2.mul(x.sq())); - taux = taux.add(gamma.mul(z.sq())); - Scalar mu = x.mul(rho).add(alpha); - - // PAPER LINES 54-57 - Scalar[] l = new Scalar[N]; - Scalar[] r = new Scalar[N]; - - l = VectorAdd(VectorSubtract(aL,VectorScalar(VectorPowers(Scalar.ONE),z)),VectorScalar(sL,x)); - r = VectorAdd(Hadamard(VectorPowers(y),VectorAdd(aR,VectorAdd(VectorScalar(VectorPowers(Scalar.ONE),z),VectorScalar(sR,x)))),VectorScalar(VectorPowers(Scalar.TWO),z.sq())); - - Scalar t = InnerProduct(l,r); - - // PAPER LINES 32-33 - hashCache = hashToScalar(concat(hashCache.bytes,x.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,taux.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,mu.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,t.bytes)); - Scalar x_ip = hashCache; - - // These are used in the inner product rounds - int nprime = N; - Curve25519Point[] Gprime = new Curve25519Point[N]; - Curve25519Point[] Hprime = new Curve25519Point[N]; - Scalar[] aprime = new Scalar[N]; - Scalar[] bprime = new Scalar[N]; - for (int i = 0; i < N; i++) - { - Gprime[i] = Gi[i]; - Hprime[i] = Hi[i].scalarMultiply(Invert(y).pow(i)); - aprime[i] = l[i]; - bprime[i] = r[i]; - } - Curve25519Point[] L = new Curve25519Point[logN]; - Curve25519Point[] R = new Curve25519Point[logN]; - int round = 0; // track the index based on number of rounds - Scalar[] w = new Scalar[logN]; // this is the challenge x in the inner product protocol - - // PAPER LINE 13 - while (nprime > 1) - { - // PAPER LINE 15 - nprime /= 2; - - // PAPER LINES 16-17 - Scalar cL = InnerProduct(ScalarSlice(aprime,0,nprime),ScalarSlice(bprime,nprime,bprime.length)); - Scalar cR = InnerProduct(ScalarSlice(aprime,nprime,aprime.length),ScalarSlice(bprime,0,nprime)); - - // PAPER LINES 18-19 - L[round] = VectorExponentCustom(CurveSlice(Gprime,nprime,Gprime.length),CurveSlice(Hprime,0,nprime),ScalarSlice(aprime,0,nprime),ScalarSlice(bprime,nprime,bprime.length)).add(H.scalarMultiply(cL.mul(x_ip))); - R[round] = VectorExponentCustom(CurveSlice(Gprime,0,nprime),CurveSlice(Hprime,nprime,Hprime.length),ScalarSlice(aprime,nprime,aprime.length),ScalarSlice(bprime,0,nprime)).add(H.scalarMultiply(cR.mul(x_ip))); - - // PAPER LINES 21-22 - hashCache = hashToScalar(concat(hashCache.bytes,L[round].toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,R[round].toBytes())); - w[round] = hashCache; - - // PAPER LINES 24-25 - Gprime = Hadamard2(VectorScalar2(CurveSlice(Gprime,0,nprime),Invert(w[round])),VectorScalar2(CurveSlice(Gprime,nprime,Gprime.length),w[round])); - Hprime = Hadamard2(VectorScalar2(CurveSlice(Hprime,0,nprime),w[round]),VectorScalar2(CurveSlice(Hprime,nprime,Hprime.length),Invert(w[round]))); - - // PAPER LINES 28-29 - aprime = VectorAdd(VectorScalar(ScalarSlice(aprime,0,nprime),w[round]),VectorScalar(ScalarSlice(aprime,nprime,aprime.length),Invert(w[round]))); - bprime = VectorAdd(VectorScalar(ScalarSlice(bprime,0,nprime),Invert(w[round])),VectorScalar(ScalarSlice(bprime,nprime,bprime.length),w[round])); - - round += 1; - } - - // PAPER LINE 58 (with inclusions from PAPER LINE 8 and PAPER LINE 20) - return new ProofTuple(V,A,S,T1,T2,taux,mu,L,R,aprime[0],bprime[0],t); - } - - /* Given a range proof, determine if it is valid */ - public static boolean VERIFY(ProofTuple proof) - { - // Reconstruct the challenges - Scalar hashCache = hashToScalar(proof.V.toBytes()); - hashCache = hashToScalar(concat(hashCache.bytes,proof.A.toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,proof.S.toBytes())); - Scalar y = hashCache; - hashCache = hashToScalar(hashCache.bytes); - Scalar z = hashCache; - hashCache = hashToScalar(concat(hashCache.bytes,z.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,proof.T1.toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,proof.T2.toBytes())); - Scalar x = hashCache; - hashCache = hashToScalar(concat(hashCache.bytes,x.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,proof.taux.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,proof.mu.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,proof.t.bytes)); - Scalar x_ip = hashCache; - - // PAPER LINE 61 - Curve25519Point L61Left = G.scalarMultiply(proof.taux).add(H.scalarMultiply(proof.t)); - - Scalar k = ComputeK(y,z); - - Curve25519Point L61Right = H.scalarMultiply(k.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y))))); - L61Right = L61Right.add(proof.V.scalarMultiply(z.sq())); - L61Right = L61Right.add(proof.T1.scalarMultiply(x)); - L61Right = L61Right.add(proof.T2.scalarMultiply(x.sq())); - - if (!L61Right.equals(L61Left)) - return false; - - // PAPER LINE 62 - Curve25519Point P = Curve25519Point.ZERO; - P = P.add(proof.A); - P = P.add(proof.S.scalarMultiply(x)); - - Scalar[] Gexp = new Scalar[N]; - for (int i = 0; i < N; i++) - Gexp[i] = Scalar.ZERO.sub(z); - - Scalar[] Hexp = new Scalar[N]; - for (int i = 0; i < N; i++) - { - Hexp[i] = Scalar.ZERO; - Hexp[i] = Hexp[i].add(z.mul(y.pow(i))); - Hexp[i] = Hexp[i].add(z.sq().mul(Scalar.TWO.pow(i))); - Hexp[i] = Hexp[i].mul(Invert(y).pow(i)); - } - P = P.add(VectorExponent(Gexp,Hexp)); - - // Compute the number of rounds for the inner product - int rounds = proof.L.length; - - // PAPER LINES 21-22 - // The inner product challenges are computed per round - Scalar[] w = new Scalar[rounds]; - hashCache = hashToScalar(concat(hashCache.bytes,proof.L[0].toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,proof.R[0].toBytes())); - w[0] = hashCache; - if (rounds > 1) - { - for (int i = 1; i < rounds; i++) - { - hashCache = hashToScalar(concat(hashCache.bytes,proof.L[i].toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,proof.R[i].toBytes())); - w[i] = hashCache; - } - } - - // Basically PAPER LINES 24-25 - // Compute the curvepoints from G[i] and H[i] - Curve25519Point InnerProdG = Curve25519Point.ZERO; - Curve25519Point InnerProdH = Curve25519Point.ZERO; - for (int i = 0; i < N; i++) - { - // Convert the index to binary IN REVERSE and construct the scalar exponent - int index = i; - Scalar gScalar = Scalar.ONE; - Scalar hScalar = Invert(y).pow(i); - - for (int j = rounds-1; j >= 0; j--) - { - int J = w.length - j - 1; // because this is done in reverse bit order - int basePow = (int) Math.pow(2,j); // assumes we don't get too big - if (index / basePow == 0) // bit is zero - { - gScalar = gScalar.mul(Invert(w[J])); - hScalar = hScalar.mul(w[J]); - } - else // bit is one - { - gScalar = gScalar.mul(w[J]); - hScalar = hScalar.mul(Invert(w[J])); - index -= basePow; - } - } - - // Now compute the basepoint's scalar multiplication - // Each of these could be written as a multiexp operation instead - InnerProdG = InnerProdG.add(Gi[i].scalarMultiply(gScalar)); - InnerProdH = InnerProdH.add(Hi[i].scalarMultiply(hScalar)); - } - - // PAPER LINE 26 - Curve25519Point Pprime = P.add(G.scalarMultiply(Scalar.ZERO.sub(proof.mu))); - - for (int i = 0; i < rounds; i++) - { - Pprime = Pprime.add(proof.L[i].scalarMultiply(w[i].sq())); - Pprime = Pprime.add(proof.R[i].scalarMultiply(Invert(w[i]).sq())); - } - Pprime = Pprime.add(H.scalarMultiply(proof.t.mul(x_ip))); - - if (!Pprime.equals(InnerProdG.scalarMultiply(proof.a).add(InnerProdH.scalarMultiply(proof.b)).add(H.scalarMultiply(proof.a.mul(proof.b).mul(x_ip))))) - return false; - - return true; - } - - public static void main(String[] args) - { - // Number of bits in the range - N = 64; - logN = 6; // its log, manually - - // Set the curve base points - G = Curve25519Point.G; - H = Curve25519Point.hashToPoint(G); - Gi = new Curve25519Point[N]; - Hi = new Curve25519Point[N]; - for (int i = 0; i < N; i++) - { - Gi[i] = getHpnGLookup(2*i); - Hi[i] = getHpnGLookup(2*i+1); - } - - // Run a bunch of randomized trials - Random rando = new Random(); - int TRIALS = 250; - int count = 0; - - while (count < TRIALS) - { - long amount = rando.nextLong(); - if (amount > Math.pow(2,N)-1 || amount < 0) - continue; - - ProofTuple proof = PROVE(new Scalar(BigInteger.valueOf(amount)),randomScalar()); - if (!VERIFY(proof)) - System.out.println("Test failed"); - - count += 1; - } - } -} diff --git a/source-code/BulletProofs/OptimizedLogBulletproof.java b/source-code/BulletProofs/OptimizedLogBulletproof.java deleted file mode 100644 index 6752fc7..0000000 --- a/source-code/BulletProofs/OptimizedLogBulletproof.java +++ /dev/null @@ -1,522 +0,0 @@ -// NOTE: this interchanges the roles of G and H to match other code's behavior - -package how.monero.hodl.bulletproof; - -import how.monero.hodl.crypto.Curve25519Point; -import how.monero.hodl.crypto.Scalar; -import how.monero.hodl.crypto.CryptoUtil; -import java.math.BigInteger; -import java.util.Random; - -import static how.monero.hodl.crypto.Scalar.randomScalar; -import static how.monero.hodl.crypto.CryptoUtil.*; -import static how.monero.hodl.util.ByteUtil.*; - -public class OptimizedLogBulletproof -{ - private static int N; - private static int logN; - private static Curve25519Point G; - private static Curve25519Point H; - private static Curve25519Point[] Gi; - private static Curve25519Point[] Hi; - - public static class ProofTuple - { - private Curve25519Point V; - private Curve25519Point A; - private Curve25519Point S; - private Curve25519Point T1; - private Curve25519Point T2; - private Scalar taux; - private Scalar mu; - private Curve25519Point[] L; - private Curve25519Point[] R; - private Scalar a; - private Scalar b; - private Scalar t; - - public ProofTuple(Curve25519Point V, Curve25519Point A, Curve25519Point S, Curve25519Point T1, Curve25519Point T2, Scalar taux, Scalar mu, Curve25519Point[] L, Curve25519Point[] R, Scalar a, Scalar b, Scalar t) - { - this.V = V; - this.A = A; - this.S = S; - this.T1 = T1; - this.T2 = T2; - this.taux = taux; - this.mu = mu; - this.L = L; - this.R = R; - this.a = a; - this.b = b; - this.t = t; - } - } - - /* Given two scalar arrays, construct a vector commitment */ - public static Curve25519Point VectorExponent(Scalar[] a, Scalar[] b) - { - assert a.length == N && b.length == N; - - Curve25519Point Result = Curve25519Point.ZERO; - for (int i = 0; i < N; i++) - { - Result = Result.add(Gi[i].scalarMultiply(a[i])); - Result = Result.add(Hi[i].scalarMultiply(b[i])); - } - return Result; - } - - /* Compute a custom vector-scalar commitment */ - public static Curve25519Point VectorExponentCustom(Curve25519Point[] A, Curve25519Point[] B, Scalar[] a, Scalar[] b) - { - assert a.length == A.length && b.length == B.length && a.length == b.length; - - Curve25519Point Result = Curve25519Point.ZERO; - for (int i = 0; i < a.length; i++) - { - Result = Result.add(A[i].scalarMultiply(a[i])); - Result = Result.add(B[i].scalarMultiply(b[i])); - } - return Result; - } - - /* Given a scalar, construct a vector of powers */ - public static Scalar[] VectorPowers(Scalar x) - { - Scalar[] result = new Scalar[N]; - for (int i = 0; i < N; i++) - { - result[i] = x.pow(i); - } - return result; - } - - /* Given two scalar arrays, construct the inner product */ - public static Scalar InnerProduct(Scalar[] a, Scalar[] b) - { - assert a.length == b.length; - - Scalar result = Scalar.ZERO; - for (int i = 0; i < a.length; i++) - { - result = result.add(a[i].mul(b[i])); - } - return result; - } - - /* Given two scalar arrays, construct the Hadamard product */ - public static Scalar[] Hadamard(Scalar[] a, Scalar[] b) - { - assert a.length == b.length; - - Scalar[] result = new Scalar[a.length]; - for (int i = 0; i < a.length; i++) - { - result[i] = a[i].mul(b[i]); - } - return result; - } - - /* Given two curvepoint arrays, construct the Hadamard product */ - public static Curve25519Point[] Hadamard2(Curve25519Point[] A, Curve25519Point[] B) - { - assert A.length == B.length; - - Curve25519Point[] Result = new Curve25519Point[A.length]; - for (int i = 0; i < A.length; i++) - { - Result[i] = A[i].add(B[i]); - } - return Result; - } - - /* Add two vectors */ - public static Scalar[] VectorAdd(Scalar[] a, Scalar[] b) - { - assert a.length == b.length; - - Scalar[] result = new Scalar[a.length]; - for (int i = 0; i < a.length; i++) - { - result[i] = a[i].add(b[i]); - } - return result; - } - - /* Subtract two vectors */ - public static Scalar[] VectorSubtract(Scalar[] a, Scalar[] b) - { - assert a.length == b.length; - - Scalar[] result = new Scalar[a.length]; - for (int i = 0; i < a.length; i++) - { - result[i] = a[i].sub(b[i]); - } - return result; - } - - /* Multiply a scalar and a vector */ - public static Scalar[] VectorScalar(Scalar[] a, Scalar x) - { - Scalar[] result = new Scalar[a.length]; - for (int i = 0; i < a.length; i++) - { - result[i] = a[i].mul(x); - } - return result; - } - - /* Exponentiate a curve vector by a scalar */ - public static Curve25519Point[] VectorScalar2(Curve25519Point[] A, Scalar x) - { - Curve25519Point[] Result = new Curve25519Point[A.length]; - for (int i = 0; i < A.length; i++) - { - Result[i] = A[i].scalarMultiply(x); - } - return Result; - } - - /* Compute the inverse of a scalar, the stupid way */ - public static Scalar Invert(Scalar x) - { - Scalar inverse = new Scalar(x.toBigInteger().modInverse(CryptoUtil.l)); - - assert x.mul(inverse).equals(Scalar.ONE); - return inverse; - } - - /* Compute the slice of a curvepoint vector */ - public static Curve25519Point[] CurveSlice(Curve25519Point[] a, int start, int stop) - { - Curve25519Point[] Result = new Curve25519Point[stop-start]; - for (int i = start; i < stop; i++) - { - Result[i-start] = a[i]; - } - return Result; - } - - /* Compute the slice of a scalar vector */ - public static Scalar[] ScalarSlice(Scalar[] a, int start, int stop) - { - Scalar[] result = new Scalar[stop-start]; - for (int i = start; i < stop; i++) - { - result[i-start] = a[i]; - } - return result; - } - - /* Compute the value of k(y,z) */ - public static Scalar ComputeK(Scalar y, Scalar z) - { - Scalar result = Scalar.ZERO; - result = result.sub(z.sq().mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); - result = result.sub(z.pow(3).mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(Scalar.TWO)))); - - return result; - } - - /* Given a value v (0..2^N-1) and a mask gamma, construct a range proof */ - public static ProofTuple PROVE(Scalar v, Scalar gamma) - { - Curve25519Point V = H.scalarMultiply(v).add(G.scalarMultiply(gamma)); - - // This hash is updated for Fiat-Shamir throughout the proof - Scalar hashCache = hashToScalar(V.toBytes()); - - // PAPER LINES 36-37 - Scalar[] aL = new Scalar[N]; - Scalar[] aR = new Scalar[N]; - - BigInteger tempV = v.toBigInteger(); - for (int i = N-1; i >= 0; i--) - { - BigInteger basePow = BigInteger.valueOf(2).pow(i); - if (tempV.divide(basePow).equals(BigInteger.ZERO)) - { - aL[i] = Scalar.ZERO; - } - else - { - aL[i] = Scalar.ONE; - tempV = tempV.subtract(basePow); - } - - aR[i] = aL[i].sub(Scalar.ONE); - } - - // PAPER LINES 38-39 - Scalar alpha = randomScalar(); - Curve25519Point A = VectorExponent(aL,aR).add(G.scalarMultiply(alpha)); - - // PAPER LINES 40-42 - Scalar[] sL = new Scalar[N]; - Scalar[] sR = new Scalar[N]; - for (int i = 0; i < N; i++) - { - sL[i] = randomScalar(); - sR[i] = randomScalar(); - } - Scalar rho = randomScalar(); - Curve25519Point S = VectorExponent(sL,sR).add(G.scalarMultiply(rho)); - - // PAPER LINES 43-45 - hashCache = hashToScalar(concat(hashCache.bytes,A.toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,S.toBytes())); - Scalar y = hashCache; - hashCache = hashToScalar(hashCache.bytes); - Scalar z = hashCache; - - // Polynomial construction before PAPER LINE 46 - Scalar t0 = Scalar.ZERO; - Scalar t1 = Scalar.ZERO; - Scalar t2 = Scalar.ZERO; - - t0 = t0.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); - t0 = t0.add(z.sq().mul(v)); - Scalar k = ComputeK(y,z); - t0 = t0.add(k); - - t1 = t1.add(InnerProduct(VectorSubtract(aL,VectorScalar(VectorPowers(Scalar.ONE),z)),Hadamard(VectorPowers(y),sR))); - t1 = t1.add(InnerProduct(sL,VectorAdd(Hadamard(VectorPowers(y),VectorAdd(aR,VectorScalar(VectorPowers(Scalar.ONE),z))),VectorScalar(VectorPowers(Scalar.TWO),z.sq())))); - - t2 = t2.add(InnerProduct(sL,Hadamard(VectorPowers(y),sR))); - - // PAPER LINES 47-48 - Scalar tau1 = randomScalar(); - Scalar tau2 = randomScalar(); - Curve25519Point T1 = H.scalarMultiply(t1).add(G.scalarMultiply(tau1)); - Curve25519Point T2 = H.scalarMultiply(t2).add(G.scalarMultiply(tau2)); - - // PAPER LINES 49-51 - hashCache = hashToScalar(concat(hashCache.bytes,z.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,T1.toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,T2.toBytes())); - Scalar x = hashCache; - - // PAPER LINES 52-53 - Scalar taux = Scalar.ZERO; - taux = tau1.mul(x); - taux = taux.add(tau2.mul(x.sq())); - taux = taux.add(gamma.mul(z.sq())); - Scalar mu = x.mul(rho).add(alpha); - - // PAPER LINES 54-57 - Scalar[] l = new Scalar[N]; - Scalar[] r = new Scalar[N]; - - l = VectorAdd(VectorSubtract(aL,VectorScalar(VectorPowers(Scalar.ONE),z)),VectorScalar(sL,x)); - r = VectorAdd(Hadamard(VectorPowers(y),VectorAdd(aR,VectorAdd(VectorScalar(VectorPowers(Scalar.ONE),z),VectorScalar(sR,x)))),VectorScalar(VectorPowers(Scalar.TWO),z.sq())); - - Scalar t = InnerProduct(l,r); - - // PAPER LINES 32-33 - hashCache = hashToScalar(concat(hashCache.bytes,x.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,taux.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,mu.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,t.bytes)); - Scalar x_ip = hashCache; - - // These are used in the inner product rounds - int nprime = N; - Curve25519Point[] Gprime = new Curve25519Point[N]; - Curve25519Point[] Hprime = new Curve25519Point[N]; - Scalar[] aprime = new Scalar[N]; - Scalar[] bprime = new Scalar[N]; - for (int i = 0; i < N; i++) - { - Gprime[i] = Gi[i]; - Hprime[i] = Hi[i].scalarMultiply(Invert(y).pow(i)); - aprime[i] = l[i]; - bprime[i] = r[i]; - } - Curve25519Point[] L = new Curve25519Point[logN]; - Curve25519Point[] R = new Curve25519Point[logN]; - int round = 0; // track the index based on number of rounds - Scalar[] w = new Scalar[logN]; // this is the challenge x in the inner product protocol - - // PAPER LINE 13 - while (nprime > 1) - { - // PAPER LINE 15 - nprime /= 2; - - // PAPER LINES 16-17 - Scalar cL = InnerProduct(ScalarSlice(aprime,0,nprime),ScalarSlice(bprime,nprime,bprime.length)); - Scalar cR = InnerProduct(ScalarSlice(aprime,nprime,aprime.length),ScalarSlice(bprime,0,nprime)); - - // PAPER LINES 18-19 - L[round] = VectorExponentCustom(CurveSlice(Gprime,nprime,Gprime.length),CurveSlice(Hprime,0,nprime),ScalarSlice(aprime,0,nprime),ScalarSlice(bprime,nprime,bprime.length)).add(H.scalarMultiply(cL.mul(x_ip))); - R[round] = VectorExponentCustom(CurveSlice(Gprime,0,nprime),CurveSlice(Hprime,nprime,Hprime.length),ScalarSlice(aprime,nprime,aprime.length),ScalarSlice(bprime,0,nprime)).add(H.scalarMultiply(cR.mul(x_ip))); - - // PAPER LINES 21-22 - hashCache = hashToScalar(concat(hashCache.bytes,L[round].toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,R[round].toBytes())); - w[round] = hashCache; - - // PAPER LINES 24-25 - Gprime = Hadamard2(VectorScalar2(CurveSlice(Gprime,0,nprime),Invert(w[round])),VectorScalar2(CurveSlice(Gprime,nprime,Gprime.length),w[round])); - Hprime = Hadamard2(VectorScalar2(CurveSlice(Hprime,0,nprime),w[round]),VectorScalar2(CurveSlice(Hprime,nprime,Hprime.length),Invert(w[round]))); - - // PAPER LINES 28-29 - aprime = VectorAdd(VectorScalar(ScalarSlice(aprime,0,nprime),w[round]),VectorScalar(ScalarSlice(aprime,nprime,aprime.length),Invert(w[round]))); - bprime = VectorAdd(VectorScalar(ScalarSlice(bprime,0,nprime),Invert(w[round])),VectorScalar(ScalarSlice(bprime,nprime,bprime.length),w[round])); - - round += 1; - } - - // PAPER LINE 58 (with inclusions from PAPER LINE 8 and PAPER LINE 20) - return new ProofTuple(V,A,S,T1,T2,taux,mu,L,R,aprime[0],bprime[0],t); - } - - /* Given a range proof, determine if it is valid */ - public static boolean VERIFY(ProofTuple proof) - { - // Reconstruct the challenges - Scalar hashCache = hashToScalar(proof.V.toBytes()); - hashCache = hashToScalar(concat(hashCache.bytes,proof.A.toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,proof.S.toBytes())); - Scalar y = hashCache; - hashCache = hashToScalar(hashCache.bytes); - Scalar z = hashCache; - hashCache = hashToScalar(concat(hashCache.bytes,z.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,proof.T1.toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,proof.T2.toBytes())); - Scalar x = hashCache; - hashCache = hashToScalar(concat(hashCache.bytes,x.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,proof.taux.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,proof.mu.bytes)); - hashCache = hashToScalar(concat(hashCache.bytes,proof.t.bytes)); - Scalar x_ip = hashCache; - - // PAPER LINE 61 - Curve25519Point L61Left = G.scalarMultiply(proof.taux).add(H.scalarMultiply(proof.t)); - - Scalar k = ComputeK(y,z); - - Curve25519Point L61Right = H.scalarMultiply(k.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y))))); - L61Right = L61Right.add(proof.V.scalarMultiply(z.sq())); - L61Right = L61Right.add(proof.T1.scalarMultiply(x)); - L61Right = L61Right.add(proof.T2.scalarMultiply(x.sq())); - - if (!L61Right.equals(L61Left)) - return false; - - // PAPER LINE 62 - Curve25519Point P = Curve25519Point.ZERO; - P = P.add(proof.A); - P = P.add(proof.S.scalarMultiply(x)); - - // Compute the number of rounds for the inner product - int rounds = proof.L.length; - - // PAPER LINES 21-22 - // The inner product challenges are computed per round - Scalar[] w = new Scalar[rounds]; - hashCache = hashToScalar(concat(hashCache.bytes,proof.L[0].toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,proof.R[0].toBytes())); - w[0] = hashCache; - if (rounds > 1) - { - for (int i = 1; i < rounds; i++) - { - hashCache = hashToScalar(concat(hashCache.bytes,proof.L[i].toBytes())); - hashCache = hashToScalar(concat(hashCache.bytes,proof.R[i].toBytes())); - w[i] = hashCache; - } - } - - // Basically PAPER LINES 24-25 - // Compute the curvepoints from G[i] and H[i] - Curve25519Point InnerProdG = Curve25519Point.ZERO; - Curve25519Point InnerProdH = Curve25519Point.ZERO; - for (int i = 0; i < N; i++) - { - // Convert the index to binary IN REVERSE and construct the scalar exponent - int index = i; - Scalar gScalar = proof.a; - Scalar hScalar = proof.b.mul(Invert(y).pow(i)); - - for (int j = rounds-1; j >= 0; j--) - { - int J = w.length - j - 1; // because this is done in reverse bit order - int basePow = (int) Math.pow(2,j); // assumes we don't get too big - if (index / basePow == 0) // bit is zero - { - gScalar = gScalar.mul(Invert(w[J])); - hScalar = hScalar.mul(w[J]); - } - else // bit is one - { - gScalar = gScalar.mul(w[J]); - hScalar = hScalar.mul(Invert(w[J])); - index -= basePow; - } - } - - // Adjust the scalars using the exponents from PAPER LINE 62 - gScalar = gScalar.add(z); - hScalar = hScalar.sub(z.mul(y.pow(i)).add(z.sq().mul(Scalar.TWO.pow(i))).mul(Invert(y).pow(i))); - - // Now compute the basepoint's scalar multiplication - // Each of these could be written as a multiexp operation instead - InnerProdG = InnerProdG.add(Gi[i].scalarMultiply(gScalar)); - InnerProdH = InnerProdH.add(Hi[i].scalarMultiply(hScalar)); - } - - // PAPER LINE 26 - Curve25519Point Pprime = P.add(G.scalarMultiply(Scalar.ZERO.sub(proof.mu))); - - for (int i = 0; i < rounds; i++) - { - Pprime = Pprime.add(proof.L[i].scalarMultiply(w[i].sq())); - Pprime = Pprime.add(proof.R[i].scalarMultiply(Invert(w[i]).sq())); - } - Pprime = Pprime.add(H.scalarMultiply(proof.t.mul(x_ip))); - - if (!Pprime.equals(InnerProdG.add(InnerProdH).add(H.scalarMultiply(proof.a.mul(proof.b).mul(x_ip))))) - return false; - - return true; - } - - public static void main(String[] args) - { - // Number of bits in the range - N = 64; - logN = 6; // its log, manually - - // Set the curve base points - G = Curve25519Point.G; - H = Curve25519Point.hashToPoint(G); - Gi = new Curve25519Point[N]; - Hi = new Curve25519Point[N]; - for (int i = 0; i < N; i++) - { - Gi[i] = getHpnGLookup(2*i); - Hi[i] = getHpnGLookup(2*i+1); - } - - // Run a bunch of randomized trials - Random rando = new Random(); - int TRIALS = 250; - int count = 0; - - while (count < TRIALS) - { - long amount = rando.nextLong(); - if (amount > Math.pow(2,N)-1 || amount < 0) - continue; - - ProofTuple proof = PROVE(new Scalar(BigInteger.valueOf(amount)),randomScalar()); - if (!VERIFY(proof)) - System.out.println("Test failed"); - - count += 1; - } - } -} diff --git a/source-code/StringCT-java/src/how/monero/hodl/bulletproof/LinearBulletproof.java b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/LinearBulletproof.java index 42c1d8a..f33913e 100644 --- a/source-code/StringCT-java/src/how/monero/hodl/bulletproof/LinearBulletproof.java +++ b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/LinearBulletproof.java @@ -1,3 +1,5 @@ +// NOTE: this interchanges the roles of G and H to match other code's behavior + package how.monero.hodl.bulletproof; import how.monero.hodl.crypto.Curve25519Point; @@ -133,10 +135,23 @@ public class LinearBulletproof return inverse; } + /* Compute the value of k(y,z) */ + public static Scalar ComputeK(Scalar y, Scalar z) + { + Scalar result = Scalar.ZERO; + result = result.sub(z.sq().mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); + result = result.sub(z.pow(3).mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(Scalar.TWO)))); + + return result; + } + /* Given a value v (0..2^N-1) and a mask gamma, construct a range proof */ public static ProofTuple PROVE(Scalar v, Scalar gamma) { - Curve25519Point V = G.scalarMultiply(v).add(H.scalarMultiply(gamma)); + Curve25519Point V = H.scalarMultiply(v).add(G.scalarMultiply(gamma)); + + // This hash is updated for Fiat-Shamir throughout the proof + Scalar hashCache = hashToScalar(V.toBytes()); // PAPER LINES 36-37 Scalar[] aL = new Scalar[N]; @@ -174,7 +189,7 @@ public class LinearBulletproof // PAPER LINES 38-39 Scalar alpha = randomScalar(); - Curve25519Point A = VectorExponent(aL,aR).add(H.scalarMultiply(alpha)); + Curve25519Point A = VectorExponent(aL,aR).add(G.scalarMultiply(alpha)); // PAPER LINES 40-42 Scalar[] sL = new Scalar[N]; @@ -185,11 +200,14 @@ public class LinearBulletproof sR[i] = randomScalar(); } Scalar rho = randomScalar(); - Curve25519Point S = VectorExponent(sL,sR).add(H.scalarMultiply(rho)); + Curve25519Point S = VectorExponent(sL,sR).add(G.scalarMultiply(rho)); // PAPER LINES 43-45 - Scalar y = hashToScalar(concat(A.toBytes(),S.toBytes())); - Scalar z = hashToScalar(y.bytes); + hashCache = hashToScalar(concat(hashCache.bytes,A.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,S.toBytes())); + Scalar y = hashCache; + hashCache = hashToScalar(hashCache.bytes); + Scalar z = hashCache; Scalar t0 = Scalar.ZERO; Scalar t1 = Scalar.ZERO; @@ -197,9 +215,7 @@ public class LinearBulletproof t0 = t0.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); t0 = t0.add(z.sq().mul(v)); - Scalar k = Scalar.ZERO; - k = k.sub(z.sq().mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); - k = k.sub(z.pow(3).mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(Scalar.TWO)))); + Scalar k = ComputeK(y,z); t0 = t0.add(k); // DEBUG: Test the value of t0 has the correct form @@ -217,11 +233,14 @@ public class LinearBulletproof // PAPER LINES 47-48 Scalar tau1 = randomScalar(); Scalar tau2 = randomScalar(); - Curve25519Point T1 = G.scalarMultiply(t1).add(H.scalarMultiply(tau1)); - Curve25519Point T2 = G.scalarMultiply(t2).add(H.scalarMultiply(tau2)); + Curve25519Point T1 = H.scalarMultiply(t1).add(G.scalarMultiply(tau1)); + Curve25519Point T2 = H.scalarMultiply(t2).add(G.scalarMultiply(tau2)); // PAPER LINES 49-51 - Scalar x = hashToScalar(concat(z.bytes,T1.toBytes(),T2.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,z.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,T1.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,T2.toBytes())); + Scalar x = hashCache; // PAPER LINES 52-53 Scalar taux = Scalar.ZERO; @@ -251,21 +270,26 @@ public class LinearBulletproof public static boolean VERIFY(ProofTuple proof) { // Reconstruct the challenges - Scalar y = hashToScalar(concat(proof.A.toBytes(),proof.S.toBytes())); - Scalar z = hashToScalar(y.bytes); - Scalar x = hashToScalar(concat(z.bytes,proof.T1.toBytes(),proof.T2.toBytes())); + Scalar hashCache = hashToScalar(proof.V.toBytes()); + hashCache = hashToScalar(concat(hashCache.bytes,proof.A.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.S.toBytes())); + Scalar y = hashCache; + hashCache = hashToScalar(hashCache.bytes); + Scalar z = hashCache; + hashCache = hashToScalar(concat(hashCache.bytes,z.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,proof.T1.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.T2.toBytes())); + Scalar x = hashCache; // PAPER LINE 60 Scalar t = InnerProduct(proof.l,proof.r); // PAPER LINE 61 - Curve25519Point L61Left = H.scalarMultiply(proof.taux).add(G.scalarMultiply(t)); + Curve25519Point L61Left = G.scalarMultiply(proof.taux).add(H.scalarMultiply(t)); - Scalar k = Scalar.ZERO; - k = k.sub(z.sq().mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); - k = k.sub(z.pow(3).mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(Scalar.TWO)))); + Scalar k = ComputeK(y,z); - Curve25519Point L61Right = G.scalarMultiply(k.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y))))); + Curve25519Point L61Right = H.scalarMultiply(k.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y))))); L61Right = L61Right.add(proof.V.scalarMultiply(z.sq())); L61Right = L61Right.add(proof.T1.scalarMultiply(x)); L61Right = L61Right.add(proof.T2.scalarMultiply(x.sq())); @@ -301,7 +325,7 @@ public class LinearBulletproof Hexp[i] = Hexp[i].add(proof.r[i]); Hexp[i] = Hexp[i].mul(Invert(y).pow(i)); } - Curve25519Point L63Right = VectorExponent(proof.l,Hexp).add(H.scalarMultiply(proof.mu)); + Curve25519Point L63Right = VectorExponent(proof.l,Hexp).add(G.scalarMultiply(proof.mu)); if (!L63Right.equals(P)) { @@ -323,8 +347,8 @@ public class LinearBulletproof Hi = new Curve25519Point[N]; for (int i = 0; i < N; i++) { - Gi[i] = getHpnGLookup(i); - Hi[i] = getHpnGLookup(N+i); + Gi[i] = getHpnGLookup(2*i); + Hi[i] = getHpnGLookup(2*i+1); } // Run a bunch of randomized trials diff --git a/source-code/StringCT-java/src/how/monero/hodl/bulletproof/LogBulletproof.java b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/LogBulletproof.java index cf1cda7..7061cd9 100644 --- a/source-code/StringCT-java/src/how/monero/hodl/bulletproof/LogBulletproof.java +++ b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/LogBulletproof.java @@ -1,3 +1,5 @@ +// NOTE: this interchanges the roles of G and H to match other code's behavior + package how.monero.hodl.bulletproof; import how.monero.hodl.crypto.Curve25519Point; @@ -208,10 +210,23 @@ public class LogBulletproof return result; } + /* Compute the value of k(y,z) */ + public static Scalar ComputeK(Scalar y, Scalar z) + { + Scalar result = Scalar.ZERO; + result = result.sub(z.sq().mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); + result = result.sub(z.pow(3).mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(Scalar.TWO)))); + + return result; + } + /* Given a value v (0..2^N-1) and a mask gamma, construct a range proof */ public static ProofTuple PROVE(Scalar v, Scalar gamma) { - Curve25519Point V = G.scalarMultiply(v).add(H.scalarMultiply(gamma)); + Curve25519Point V = H.scalarMultiply(v).add(G.scalarMultiply(gamma)); + + // This hash is updated for Fiat-Shamir throughout the proof + Scalar hashCache = hashToScalar(V.toBytes()); // PAPER LINES 36-37 Scalar[] aL = new Scalar[N]; @@ -236,7 +251,7 @@ public class LogBulletproof // PAPER LINES 38-39 Scalar alpha = randomScalar(); - Curve25519Point A = VectorExponent(aL,aR).add(H.scalarMultiply(alpha)); + Curve25519Point A = VectorExponent(aL,aR).add(G.scalarMultiply(alpha)); // PAPER LINES 40-42 Scalar[] sL = new Scalar[N]; @@ -247,11 +262,14 @@ public class LogBulletproof sR[i] = randomScalar(); } Scalar rho = randomScalar(); - Curve25519Point S = VectorExponent(sL,sR).add(H.scalarMultiply(rho)); + Curve25519Point S = VectorExponent(sL,sR).add(G.scalarMultiply(rho)); // PAPER LINES 43-45 - Scalar y = hashToScalar(concat(A.toBytes(),S.toBytes())); - Scalar z = hashToScalar(y.bytes); + hashCache = hashToScalar(concat(hashCache.bytes,A.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,S.toBytes())); + Scalar y = hashCache; + hashCache = hashToScalar(hashCache.bytes); + Scalar z = hashCache; // Polynomial construction before PAPER LINE 46 Scalar t0 = Scalar.ZERO; @@ -260,9 +278,7 @@ public class LogBulletproof t0 = t0.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); t0 = t0.add(z.sq().mul(v)); - Scalar k = Scalar.ZERO; - k = k.sub(z.sq().mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); - k = k.sub(z.pow(3).mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(Scalar.TWO)))); + Scalar k = ComputeK(y,z); t0 = t0.add(k); t1 = t1.add(InnerProduct(VectorSubtract(aL,VectorScalar(VectorPowers(Scalar.ONE),z)),Hadamard(VectorPowers(y),sR))); @@ -273,11 +289,14 @@ public class LogBulletproof // PAPER LINES 47-48 Scalar tau1 = randomScalar(); Scalar tau2 = randomScalar(); - Curve25519Point T1 = G.scalarMultiply(t1).add(H.scalarMultiply(tau1)); - Curve25519Point T2 = G.scalarMultiply(t2).add(H.scalarMultiply(tau2)); + Curve25519Point T1 = H.scalarMultiply(t1).add(G.scalarMultiply(tau1)); + Curve25519Point T2 = H.scalarMultiply(t2).add(G.scalarMultiply(tau2)); // PAPER LINES 49-51 - Scalar x = hashToScalar(concat(z.bytes,T1.toBytes(),T2.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,z.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,T1.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,T2.toBytes())); + Scalar x = hashCache; // PAPER LINES 52-53 Scalar taux = Scalar.ZERO; @@ -296,7 +315,11 @@ public class LogBulletproof Scalar t = InnerProduct(l,r); // PAPER LINES 32-33 - Scalar x_ip = hashToScalar(concat(x.bytes,taux.bytes,mu.bytes,t.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,x.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,taux.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,mu.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,t.bytes)); + Scalar x_ip = hashCache; // These are used in the inner product rounds int nprime = N; @@ -327,14 +350,13 @@ public class LogBulletproof Scalar cR = InnerProduct(ScalarSlice(aprime,nprime,aprime.length),ScalarSlice(bprime,0,nprime)); // PAPER LINES 18-19 - L[round] = VectorExponentCustom(CurveSlice(Gprime,nprime,Gprime.length),CurveSlice(Hprime,0,nprime),ScalarSlice(aprime,0,nprime),ScalarSlice(bprime,nprime,bprime.length)).add(G.scalarMultiply(cL.mul(x_ip))); - R[round] = VectorExponentCustom(CurveSlice(Gprime,0,nprime),CurveSlice(Hprime,nprime,Hprime.length),ScalarSlice(aprime,nprime,aprime.length),ScalarSlice(bprime,0,nprime)).add(G.scalarMultiply(cR.mul(x_ip))); + L[round] = VectorExponentCustom(CurveSlice(Gprime,nprime,Gprime.length),CurveSlice(Hprime,0,nprime),ScalarSlice(aprime,0,nprime),ScalarSlice(bprime,nprime,bprime.length)).add(H.scalarMultiply(cL.mul(x_ip))); + R[round] = VectorExponentCustom(CurveSlice(Gprime,0,nprime),CurveSlice(Hprime,nprime,Hprime.length),ScalarSlice(aprime,nprime,aprime.length),ScalarSlice(bprime,0,nprime)).add(H.scalarMultiply(cR.mul(x_ip))); // PAPER LINES 21-22 - if (round == 0) - w[0] = hashToScalar(concat(L[0].toBytes(),R[0].toBytes())); - else - w[round] = hashToScalar(concat(w[round-1].bytes,L[round].toBytes(),R[round].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,L[round].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,R[round].toBytes())); + w[round] = hashCache; // PAPER LINES 24-25 Gprime = Hadamard2(VectorScalar2(CurveSlice(Gprime,0,nprime),Invert(w[round])),VectorScalar2(CurveSlice(Gprime,nprime,Gprime.length),w[round])); @@ -355,19 +377,28 @@ public class LogBulletproof public static boolean VERIFY(ProofTuple proof) { // Reconstruct the challenges - Scalar y = hashToScalar(concat(proof.A.toBytes(),proof.S.toBytes())); - Scalar z = hashToScalar(y.bytes); - Scalar x = hashToScalar(concat(z.bytes,proof.T1.toBytes(),proof.T2.toBytes())); - Scalar x_ip = hashToScalar(concat(x.bytes,proof.taux.bytes,proof.mu.bytes,proof.t.bytes)); + Scalar hashCache = hashToScalar(proof.V.toBytes()); + hashCache = hashToScalar(concat(hashCache.bytes,proof.A.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.S.toBytes())); + Scalar y = hashCache; + hashCache = hashToScalar(hashCache.bytes); + Scalar z = hashCache; + hashCache = hashToScalar(concat(hashCache.bytes,z.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,proof.T1.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.T2.toBytes())); + Scalar x = hashCache; + hashCache = hashToScalar(concat(hashCache.bytes,x.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,proof.taux.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,proof.mu.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,proof.t.bytes)); + Scalar x_ip = hashCache; // PAPER LINE 61 - Curve25519Point L61Left = H.scalarMultiply(proof.taux).add(G.scalarMultiply(proof.t)); + Curve25519Point L61Left = G.scalarMultiply(proof.taux).add(H.scalarMultiply(proof.t)); - Scalar k = Scalar.ZERO; - k = k.sub(z.sq().mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); - k = k.sub(z.pow(3).mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(Scalar.TWO)))); + Scalar k = ComputeK(y,z); - Curve25519Point L61Right = G.scalarMultiply(k.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y))))); + Curve25519Point L61Right = H.scalarMultiply(k.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y))))); L61Right = L61Right.add(proof.V.scalarMultiply(z.sq())); L61Right = L61Right.add(proof.T1.scalarMultiply(x)); L61Right = L61Right.add(proof.T2.scalarMultiply(x.sq())); @@ -400,12 +431,16 @@ public class LogBulletproof // PAPER LINES 21-22 // The inner product challenges are computed per round Scalar[] w = new Scalar[rounds]; - w[0] = hashToScalar(concat(proof.L[0].toBytes(),proof.R[0].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.L[0].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.R[0].toBytes())); + w[0] = hashCache; if (rounds > 1) { for (int i = 1; i < rounds; i++) { - w[i] = hashToScalar(concat(w[i-1].bytes,proof.L[i].toBytes(),proof.R[i].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.L[i].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.R[i].toBytes())); + w[i] = hashCache; } } @@ -444,16 +479,16 @@ public class LogBulletproof } // PAPER LINE 26 - Curve25519Point Pprime = P.add(H.scalarMultiply(Scalar.ZERO.sub(proof.mu))); + Curve25519Point Pprime = P.add(G.scalarMultiply(Scalar.ZERO.sub(proof.mu))); for (int i = 0; i < rounds; i++) { Pprime = Pprime.add(proof.L[i].scalarMultiply(w[i].sq())); Pprime = Pprime.add(proof.R[i].scalarMultiply(Invert(w[i]).sq())); } - Pprime = Pprime.add(G.scalarMultiply(proof.t.mul(x_ip))); + Pprime = Pprime.add(H.scalarMultiply(proof.t.mul(x_ip))); - if (!Pprime.equals(InnerProdG.scalarMultiply(proof.a).add(InnerProdH.scalarMultiply(proof.b)).add(G.scalarMultiply(proof.a.mul(proof.b).mul(x_ip))))) + if (!Pprime.equals(InnerProdG.scalarMultiply(proof.a).add(InnerProdH.scalarMultiply(proof.b)).add(H.scalarMultiply(proof.a.mul(proof.b).mul(x_ip))))) return false; return true; @@ -472,8 +507,8 @@ public class LogBulletproof Hi = new Curve25519Point[N]; for (int i = 0; i < N; i++) { - Gi[i] = getHpnGLookup(i); - Hi[i] = getHpnGLookup(N+i); + Gi[i] = getHpnGLookup(2*i); + Hi[i] = getHpnGLookup(2*i+1); } // Run a bunch of randomized trials diff --git a/source-code/StringCT-java/src/how/monero/hodl/bulletproof/MultiBulletproof.java b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/MultiBulletproof.java new file mode 100644 index 0000000..8e9e381 --- /dev/null +++ b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/MultiBulletproof.java @@ -0,0 +1,721 @@ + + +(2) Inbox | surae.noether@protonmail.com | ProtonMail

Request timed out, please try again.

(4) Your eyes only RTRS RingCT

huohuli@protonmail.chSurae Noether

Repo changes

Sarang Noether

(4) Re: Speaker Nominations...

Sarang NoetherKevin RabinovichChristopher Gragtmans

[bpase18attendees] BPASE 18 videos and slides online; thank you!

Allison Berke

(4) Chris <> Sarang and Surae

Christopher GragtmansSarang NoetherPaul Shapiro

(5) RTRS RingCT linkability and amortization

Surae NoetherRussell W. F. LaiTim Ruffing

[bpase18attendees] BPASE Day Three (1/26) Schedule

Allison Berke

[bpase18attendees] BPASE Day Two 1/25 schedule

Allison Berke

Verify Your Email Address

Verification

(2) DHL Consignment Notification Arrival:PKO-HAF10803271, Please Receive Your Packages!

DHL Online

[reddit] 1 new message from u/rugbrn

Reddit

(3) [reddit] 1 new message from u/snirpie

Reddit

(2) [reddit] 1 new message from u/Vespco

Reddit

[reddit] 2 new messages from u/snirpie

Reddit

[reddit] 1 new message from u/gr33d3r

Reddit

[bpase18attendees] BPASE '18 Registration confirmation and attendee details

Allison Berke

[reddit] 1 new message from u/0x263a

Reddit

(5) [reddit] 1 new message from u/pebx

Reddit

(2) Fw: Monero Research Lab

Surae Noethermrwhythat

(2) test

Brandon Goodell

⬆️ The Best of Reddit 2017 ⬆️

Reddit Newsletter

Multi BP update

Sarang Noether

(3) Updated BP code

Sarang Noether

[reddit] 1 new message from u/justaq1233

Reddit

(2) [reddit] 1 new message from u/endogenic

Reddit

[reddit] 1 new message from u/NASA_Welder

Reddit

[reddit] 1 new message from u/malabaribiriyani

Reddit

(14) zcash linkability draft

JQSurae Noether

[reddit] 1 new message from u/e-mess

Reddit

(2) [reddit] 1 new message from u/cryptobrant

Reddit

[reddit] 1 new message from u/Truseus

Reddit

[reddit] 1 new message from u/daveselbow

Reddit

[reddit] 1 new message from u/driedapricots

Reddit

[reddit] 1 new message from u/foyamoon

Reddit

[reddit] 1 new message from u/not_really_a_troll

Reddit

[reddit] 1 new message from u/TheCryptoDivision

Reddit

[reddit] 1 new message from u/AsianHouseShrew

Reddit

[reddit] 1 new message from u/xmr_eric

Reddit

[reddit] 1 new message from u/BBQ_RIBS

Reddit

[reddit] 3 new messages from u/captainintarrnet, u/john_alan

Reddit

[reddit] 1 new message from u/IeatBitcoins

Reddit

[reddit] 1 new message from u/fresheneesz

Reddit

[reddit] 2 new messages from u/drfloydch, u/CapnFartfaceMcGee

Reddit

[reddit] 1 new message from u/Wasabi567

Reddit

[reddit] 2 new messages from u/c-789, u/TinusMars

Reddit

Bulletproof code

Sarang Noether

[reddit] 1 new message from u/yoyoyodayoyo

Reddit

(28) SB linking

Surae NoetherSarang NoetherJQ

[reddit] 1 new message from u/captainintarrnet

Reddit

[reddit] 1 new message from u/Whooshless

Reddit
+

Repo changes

From: Sarang Noether <sarang.noether@protonmail.com>
To: Surae Noether
To
  • Surae Noether <surae.noether@protonmail.com>
  • Filter on:
This message contains remote content
This message contains embedded images
+ +
Type 1 or more characters for results.
    + \ No newline at end of file diff --git a/source-code/StringCT-java/src/how/monero/hodl/bulletproof/OptimizedLogBulletproof.java b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/OptimizedLogBulletproof.java index 6b2acde..6752fc7 100644 --- a/source-code/StringCT-java/src/how/monero/hodl/bulletproof/OptimizedLogBulletproof.java +++ b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/OptimizedLogBulletproof.java @@ -1,3 +1,5 @@ +// NOTE: this interchanges the roles of G and H to match other code's behavior + package how.monero.hodl.bulletproof; import how.monero.hodl.crypto.Curve25519Point; @@ -208,10 +210,23 @@ public class OptimizedLogBulletproof return result; } + /* Compute the value of k(y,z) */ + public static Scalar ComputeK(Scalar y, Scalar z) + { + Scalar result = Scalar.ZERO; + result = result.sub(z.sq().mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); + result = result.sub(z.pow(3).mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(Scalar.TWO)))); + + return result; + } + /* Given a value v (0..2^N-1) and a mask gamma, construct a range proof */ public static ProofTuple PROVE(Scalar v, Scalar gamma) { - Curve25519Point V = G.scalarMultiply(v).add(H.scalarMultiply(gamma)); + Curve25519Point V = H.scalarMultiply(v).add(G.scalarMultiply(gamma)); + + // This hash is updated for Fiat-Shamir throughout the proof + Scalar hashCache = hashToScalar(V.toBytes()); // PAPER LINES 36-37 Scalar[] aL = new Scalar[N]; @@ -236,7 +251,7 @@ public class OptimizedLogBulletproof // PAPER LINES 38-39 Scalar alpha = randomScalar(); - Curve25519Point A = VectorExponent(aL,aR).add(H.scalarMultiply(alpha)); + Curve25519Point A = VectorExponent(aL,aR).add(G.scalarMultiply(alpha)); // PAPER LINES 40-42 Scalar[] sL = new Scalar[N]; @@ -247,11 +262,14 @@ public class OptimizedLogBulletproof sR[i] = randomScalar(); } Scalar rho = randomScalar(); - Curve25519Point S = VectorExponent(sL,sR).add(H.scalarMultiply(rho)); + Curve25519Point S = VectorExponent(sL,sR).add(G.scalarMultiply(rho)); // PAPER LINES 43-45 - Scalar y = hashToScalar(concat(A.toBytes(),S.toBytes())); - Scalar z = hashToScalar(y.bytes); + hashCache = hashToScalar(concat(hashCache.bytes,A.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,S.toBytes())); + Scalar y = hashCache; + hashCache = hashToScalar(hashCache.bytes); + Scalar z = hashCache; // Polynomial construction before PAPER LINE 46 Scalar t0 = Scalar.ZERO; @@ -260,9 +278,7 @@ public class OptimizedLogBulletproof t0 = t0.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); t0 = t0.add(z.sq().mul(v)); - Scalar k = Scalar.ZERO; - k = k.sub(z.sq().mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); - k = k.sub(z.pow(3).mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(Scalar.TWO)))); + Scalar k = ComputeK(y,z); t0 = t0.add(k); t1 = t1.add(InnerProduct(VectorSubtract(aL,VectorScalar(VectorPowers(Scalar.ONE),z)),Hadamard(VectorPowers(y),sR))); @@ -273,11 +289,14 @@ public class OptimizedLogBulletproof // PAPER LINES 47-48 Scalar tau1 = randomScalar(); Scalar tau2 = randomScalar(); - Curve25519Point T1 = G.scalarMultiply(t1).add(H.scalarMultiply(tau1)); - Curve25519Point T2 = G.scalarMultiply(t2).add(H.scalarMultiply(tau2)); + Curve25519Point T1 = H.scalarMultiply(t1).add(G.scalarMultiply(tau1)); + Curve25519Point T2 = H.scalarMultiply(t2).add(G.scalarMultiply(tau2)); // PAPER LINES 49-51 - Scalar x = hashToScalar(concat(z.bytes,T1.toBytes(),T2.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,z.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,T1.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,T2.toBytes())); + Scalar x = hashCache; // PAPER LINES 52-53 Scalar taux = Scalar.ZERO; @@ -296,7 +315,11 @@ public class OptimizedLogBulletproof Scalar t = InnerProduct(l,r); // PAPER LINES 32-33 - Scalar x_ip = hashToScalar(concat(x.bytes,taux.bytes,mu.bytes,t.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,x.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,taux.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,mu.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,t.bytes)); + Scalar x_ip = hashCache; // These are used in the inner product rounds int nprime = N; @@ -327,14 +350,13 @@ public class OptimizedLogBulletproof Scalar cR = InnerProduct(ScalarSlice(aprime,nprime,aprime.length),ScalarSlice(bprime,0,nprime)); // PAPER LINES 18-19 - L[round] = VectorExponentCustom(CurveSlice(Gprime,nprime,Gprime.length),CurveSlice(Hprime,0,nprime),ScalarSlice(aprime,0,nprime),ScalarSlice(bprime,nprime,bprime.length)).add(G.scalarMultiply(cL.mul(x_ip))); - R[round] = VectorExponentCustom(CurveSlice(Gprime,0,nprime),CurveSlice(Hprime,nprime,Hprime.length),ScalarSlice(aprime,nprime,aprime.length),ScalarSlice(bprime,0,nprime)).add(G.scalarMultiply(cR.mul(x_ip))); + L[round] = VectorExponentCustom(CurveSlice(Gprime,nprime,Gprime.length),CurveSlice(Hprime,0,nprime),ScalarSlice(aprime,0,nprime),ScalarSlice(bprime,nprime,bprime.length)).add(H.scalarMultiply(cL.mul(x_ip))); + R[round] = VectorExponentCustom(CurveSlice(Gprime,0,nprime),CurveSlice(Hprime,nprime,Hprime.length),ScalarSlice(aprime,nprime,aprime.length),ScalarSlice(bprime,0,nprime)).add(H.scalarMultiply(cR.mul(x_ip))); // PAPER LINES 21-22 - if (round == 0) - w[0] = hashToScalar(concat(L[0].toBytes(),R[0].toBytes())); - else - w[round] = hashToScalar(concat(w[round-1].bytes,L[round].toBytes(),R[round].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,L[round].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,R[round].toBytes())); + w[round] = hashCache; // PAPER LINES 24-25 Gprime = Hadamard2(VectorScalar2(CurveSlice(Gprime,0,nprime),Invert(w[round])),VectorScalar2(CurveSlice(Gprime,nprime,Gprime.length),w[round])); @@ -355,19 +377,28 @@ public class OptimizedLogBulletproof public static boolean VERIFY(ProofTuple proof) { // Reconstruct the challenges - Scalar y = hashToScalar(concat(proof.A.toBytes(),proof.S.toBytes())); - Scalar z = hashToScalar(y.bytes); - Scalar x = hashToScalar(concat(z.bytes,proof.T1.toBytes(),proof.T2.toBytes())); - Scalar x_ip = hashToScalar(concat(x.bytes,proof.taux.bytes,proof.mu.bytes,proof.t.bytes)); + Scalar hashCache = hashToScalar(proof.V.toBytes()); + hashCache = hashToScalar(concat(hashCache.bytes,proof.A.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.S.toBytes())); + Scalar y = hashCache; + hashCache = hashToScalar(hashCache.bytes); + Scalar z = hashCache; + hashCache = hashToScalar(concat(hashCache.bytes,z.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,proof.T1.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.T2.toBytes())); + Scalar x = hashCache; + hashCache = hashToScalar(concat(hashCache.bytes,x.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,proof.taux.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,proof.mu.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,proof.t.bytes)); + Scalar x_ip = hashCache; // PAPER LINE 61 - Curve25519Point L61Left = H.scalarMultiply(proof.taux).add(G.scalarMultiply(proof.t)); + Curve25519Point L61Left = G.scalarMultiply(proof.taux).add(H.scalarMultiply(proof.t)); - Scalar k = Scalar.ZERO; - k = k.sub(z.sq().mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y)))); - k = k.sub(z.pow(3).mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(Scalar.TWO)))); + Scalar k = ComputeK(y,z); - Curve25519Point L61Right = G.scalarMultiply(k.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y))))); + Curve25519Point L61Right = H.scalarMultiply(k.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE),VectorPowers(y))))); L61Right = L61Right.add(proof.V.scalarMultiply(z.sq())); L61Right = L61Right.add(proof.T1.scalarMultiply(x)); L61Right = L61Right.add(proof.T2.scalarMultiply(x.sq())); @@ -386,12 +417,16 @@ public class OptimizedLogBulletproof // PAPER LINES 21-22 // The inner product challenges are computed per round Scalar[] w = new Scalar[rounds]; - w[0] = hashToScalar(concat(proof.L[0].toBytes(),proof.R[0].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.L[0].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.R[0].toBytes())); + w[0] = hashCache; if (rounds > 1) { for (int i = 1; i < rounds; i++) { - w[i] = hashToScalar(concat(w[i-1].bytes,proof.L[i].toBytes(),proof.R[i].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.L[i].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.R[i].toBytes())); + w[i] = hashCache; } } @@ -434,16 +469,16 @@ public class OptimizedLogBulletproof } // PAPER LINE 26 - Curve25519Point Pprime = P.add(H.scalarMultiply(Scalar.ZERO.sub(proof.mu))); + Curve25519Point Pprime = P.add(G.scalarMultiply(Scalar.ZERO.sub(proof.mu))); for (int i = 0; i < rounds; i++) { Pprime = Pprime.add(proof.L[i].scalarMultiply(w[i].sq())); Pprime = Pprime.add(proof.R[i].scalarMultiply(Invert(w[i]).sq())); } - Pprime = Pprime.add(G.scalarMultiply(proof.t.mul(x_ip))); + Pprime = Pprime.add(H.scalarMultiply(proof.t.mul(x_ip))); - if (!Pprime.equals(InnerProdG.add(InnerProdH).add(G.scalarMultiply(proof.a.mul(proof.b).mul(x_ip))))) + if (!Pprime.equals(InnerProdG.add(InnerProdH).add(H.scalarMultiply(proof.a.mul(proof.b).mul(x_ip))))) return false; return true; @@ -462,8 +497,8 @@ public class OptimizedLogBulletproof Hi = new Curve25519Point[N]; for (int i = 0; i < N; i++) { - Gi[i] = getHpnGLookup(i); - Hi[i] = getHpnGLookup(N+i); + Gi[i] = getHpnGLookup(2*i); + Hi[i] = getHpnGLookup(2*i+1); } // Run a bunch of randomized trials diff --git a/source-code/BulletProofs/readme.md b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/readme.md similarity index 100% rename from source-code/BulletProofs/readme.md rename to source-code/StringCT-java/src/how/monero/hodl/bulletproof/readme.md From 15c4cac3b9d662b44125fa0f60cf0a1be41eb90a Mon Sep 17 00:00:00 2001 From: Brandon Goodell Date: Thu, 8 Feb 2018 14:04:51 -0700 Subject: [PATCH 09/13] Delete deprecated readme --- .../StringCT-java/src/how/monero/hodl/bulletproof/Readme.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 source-code/StringCT-java/src/how/monero/hodl/bulletproof/Readme.md diff --git a/source-code/StringCT-java/src/how/monero/hodl/bulletproof/Readme.md b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/Readme.md deleted file mode 100644 index b15d86d..0000000 --- a/source-code/StringCT-java/src/how/monero/hodl/bulletproof/Readme.md +++ /dev/null @@ -1,3 +0,0 @@ -Sarang and Moneromooo implement bulletproofs. - -Usually 90+% space complexity savings, up to 25% verification time complexity savings over traditional range proofs. From 2f6e30ec4c39fc9012a370f5fe4c1a4e82b5713b Mon Sep 17 00:00:00 2001 From: Brandon Goodell Date: Thu, 8 Feb 2018 14:05:11 -0700 Subject: [PATCH 10/13] Renamed correct readme --- .../src/how/monero/hodl/bulletproof/{readme.md => Readme.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename source-code/StringCT-java/src/how/monero/hodl/bulletproof/{readme.md => Readme.md} (100%) diff --git a/source-code/StringCT-java/src/how/monero/hodl/bulletproof/readme.md b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/Readme.md similarity index 100% rename from source-code/StringCT-java/src/how/monero/hodl/bulletproof/readme.md rename to source-code/StringCT-java/src/how/monero/hodl/bulletproof/Readme.md From 2a2d95e747e492ece6e4f88e55dc613b79c0f605 Mon Sep 17 00:00:00 2001 From: Brandon Goodell Date: Thu, 8 Feb 2018 14:05:31 -0700 Subject: [PATCH 11/13] Update Readme.md --- .../StringCT-java/src/how/monero/hodl/bulletproof/Readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/source-code/StringCT-java/src/how/monero/hodl/bulletproof/Readme.md b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/Readme.md index 8b13789..5fdb5d0 100644 --- a/source-code/StringCT-java/src/how/monero/hodl/bulletproof/Readme.md +++ b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/Readme.md @@ -1 +1,2 @@ +Sarang and Moneromoo implement bulletproofs. From d8bf538bf68c8f9f8472d8efc6dc51e73b43a3b8 Mon Sep 17 00:00:00 2001 From: Brandon Goodell Date: Thu, 8 Feb 2018 14:17:52 -0700 Subject: [PATCH 12/13] Directory reorg, batch verification, optimizations --- .../hodl/bulletproof/MultiBulletproof.java | 1357 ++++++++--------- 1 file changed, 640 insertions(+), 717 deletions(-) diff --git a/source-code/StringCT-java/src/how/monero/hodl/bulletproof/MultiBulletproof.java b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/MultiBulletproof.java index 8e9e381..21d84e3 100644 --- a/source-code/StringCT-java/src/how/monero/hodl/bulletproof/MultiBulletproof.java +++ b/source-code/StringCT-java/src/how/monero/hodl/bulletproof/MultiBulletproof.java @@ -1,721 +1,644 @@ - - -(2) Inbox | surae.noether@protonmail.com | ProtonMail

    Request timed out, please try again.

    (4) Your eyes only RTRS RingCT

    huohuli@protonmail.chSurae Noether

    Repo changes

    Sarang Noether

    (4) Re: Speaker Nominations...

    Sarang NoetherKevin RabinovichChristopher Gragtmans

    [bpase18attendees] BPASE 18 videos and slides online; thank you!

    Allison Berke

    (4) Chris <> Sarang and Surae

    Christopher GragtmansSarang NoetherPaul Shapiro

    (5) RTRS RingCT linkability and amortization

    Surae NoetherRussell W. F. LaiTim Ruffing

    [bpase18attendees] BPASE Day Three (1/26) Schedule

    Allison Berke

    [bpase18attendees] BPASE Day Two 1/25 schedule

    Allison Berke

    Verify Your Email Address

    Verification

    (2) DHL Consignment Notification Arrival:PKO-HAF10803271, Please Receive Your Packages!

    DHL Online

    [reddit] 1 new message from u/rugbrn

    Reddit

    (3) [reddit] 1 new message from u/snirpie

    Reddit

    (2) [reddit] 1 new message from u/Vespco

    Reddit

    [reddit] 2 new messages from u/snirpie

    Reddit

    [reddit] 1 new message from u/gr33d3r

    Reddit

    [bpase18attendees] BPASE '18 Registration confirmation and attendee details

    Allison Berke

    [reddit] 1 new message from u/0x263a

    Reddit

    (5) [reddit] 1 new message from u/pebx

    Reddit

    (2) Fw: Monero Research Lab

    Surae Noethermrwhythat

    (2) test

    Brandon Goodell

    ⬆️ The Best of Reddit 2017 ⬆️

    Reddit Newsletter

    Multi BP update

    Sarang Noether

    (3) Updated BP code

    Sarang Noether

    [reddit] 1 new message from u/justaq1233

    Reddit

    (2) [reddit] 1 new message from u/endogenic

    Reddit

    [reddit] 1 new message from u/NASA_Welder

    Reddit

    [reddit] 1 new message from u/malabaribiriyani

    Reddit

    (14) zcash linkability draft

    JQSurae Noether

    [reddit] 1 new message from u/e-mess

    Reddit

    (2) [reddit] 1 new message from u/cryptobrant

    Reddit

    [reddit] 1 new message from u/Truseus

    Reddit

    [reddit] 1 new message from u/daveselbow

    Reddit

    [reddit] 1 new message from u/driedapricots

    Reddit

    [reddit] 1 new message from u/foyamoon

    Reddit

    [reddit] 1 new message from u/not_really_a_troll

    Reddit

    [reddit] 1 new message from u/TheCryptoDivision

    Reddit

    [reddit] 1 new message from u/AsianHouseShrew

    Reddit

    [reddit] 1 new message from u/xmr_eric

    Reddit

    [reddit] 1 new message from u/BBQ_RIBS

    Reddit

    [reddit] 3 new messages from u/captainintarrnet, u/john_alan

    Reddit

    [reddit] 1 new message from u/IeatBitcoins

    Reddit

    [reddit] 1 new message from u/fresheneesz

    Reddit

    [reddit] 2 new messages from u/drfloydch, u/CapnFartfaceMcGee

    Reddit

    [reddit] 1 new message from u/Wasabi567

    Reddit

    [reddit] 2 new messages from u/c-789, u/TinusMars

    Reddit

    Bulletproof code

    Sarang Noether

    [reddit] 1 new message from u/yoyoyodayoyo

    Reddit

    (28) SB linking

    Surae NoetherSarang NoetherJQ

    [reddit] 1 new message from u/captainintarrnet

    Reddit

    [reddit] 1 new message from u/Whooshless

    Reddit
    -

    Repo changes

    From: Sarang Noether <sarang.noether@protonmail.com>
    To: Surae Noether
    To
    • Surae Noether <surae.noether@protonmail.com>
    • Filter on:
    This message contains remote content
    This message contains embedded images
    - -
    Type 1 or more characters for results.
      - \ No newline at end of file +public class MultiBulletproof +{ + private static int NEXP; + private static int N; + private static Curve25519Point G; + private static Curve25519Point H; + private static Curve25519Point[] Gi; + private static Curve25519Point[] Hi; + + public static class ProofTuple + { + private Curve25519Point V[]; + private Curve25519Point A; + private Curve25519Point S; + private Curve25519Point T1; + private Curve25519Point T2; + private Scalar taux; + private Scalar mu; + private Curve25519Point[] L; + private Curve25519Point[] R; + private Scalar a; + private Scalar b; + private Scalar t; + + public ProofTuple(Curve25519Point V[], Curve25519Point A, Curve25519Point S, Curve25519Point T1, Curve25519Point T2, Scalar taux, Scalar mu, Curve25519Point[] L, Curve25519Point[] R, Scalar a, Scalar b, Scalar t) + { + this.V = V; + this.A = A; + this.S = S; + this.T1 = T1; + this.T2 = T2; + this.taux = taux; + this.mu = mu; + this.L = L; + this.R = R; + this.a = a; + this.b = b; + this.t = t; + } + } + + /* Given two scalar arrays, construct a vector commitment */ + public static Curve25519Point VectorExponent(Scalar[] a, Scalar[] b) + { + assert a.length == b.length; + + Curve25519Point Result = Curve25519Point.ZERO; + for (int i = 0; i < a.length; i++) + { + Result = Result.add(Gi[i].scalarMultiply(a[i])); + Result = Result.add(Hi[i].scalarMultiply(b[i])); + } + return Result; + } + + /* Compute a custom vector-scalar commitment */ + public static Curve25519Point VectorExponentCustom(Curve25519Point[] A, Curve25519Point[] B, Scalar[] a, Scalar[] b) + { + assert a.length == A.length && b.length == B.length && a.length == b.length; + + Curve25519Point Result = Curve25519Point.ZERO; + for (int i = 0; i < a.length; i++) + { + Result = Result.add(A[i].scalarMultiply(a[i])); + Result = Result.add(B[i].scalarMultiply(b[i])); + } + return Result; + } + + /* Given a scalar, construct a vector of powers */ + public static Scalar[] VectorPowers(Scalar x, int size) + { + Scalar[] result = new Scalar[size]; + for (int i = 0; i < size; i++) + { + result[i] = x.pow(i); + } + return result; + } + + /* Given two scalar arrays, construct the inner product */ + public static Scalar InnerProduct(Scalar[] a, Scalar[] b) + { + assert a.length == b.length; + + Scalar result = Scalar.ZERO; + for (int i = 0; i < a.length; i++) + { + result = result.add(a[i].mul(b[i])); + } + return result; + } + + /* Given two scalar arrays, construct the Hadamard product */ + public static Scalar[] Hadamard(Scalar[] a, Scalar[] b) + { + assert a.length == b.length; + + Scalar[] result = new Scalar[a.length]; + for (int i = 0; i < a.length; i++) + { + result[i] = a[i].mul(b[i]); + } + return result; + } + + /* Given two curvepoint arrays, construct the Hadamard product */ + public static Curve25519Point[] Hadamard2(Curve25519Point[] A, Curve25519Point[] B) + { + assert A.length == B.length; + + Curve25519Point[] Result = new Curve25519Point[A.length]; + for (int i = 0; i < A.length; i++) + { + Result[i] = A[i].add(B[i]); + } + return Result; + } + + /* Add two vectors */ + public static Scalar[] VectorAdd(Scalar[] a, Scalar[] b) + { + assert a.length == b.length; + + Scalar[] result = new Scalar[a.length]; + for (int i = 0; i < a.length; i++) + { + result[i] = a[i].add(b[i]); + } + return result; + } + + /* Subtract two vectors */ + public static Scalar[] VectorSubtract(Scalar[] a, Scalar[] b) + { + assert a.length == b.length; + + Scalar[] result = new Scalar[a.length]; + for (int i = 0; i < a.length; i++) + { + result[i] = a[i].sub(b[i]); + } + return result; + } + + /* Multiply a scalar and a vector */ + public static Scalar[] VectorScalar(Scalar[] a, Scalar x) + { + Scalar[] result = new Scalar[a.length]; + for (int i = 0; i < a.length; i++) + { + result[i] = a[i].mul(x); + } + return result; + } + + /* Exponentiate a curve vector by a scalar */ + public static Curve25519Point[] VectorScalar2(Curve25519Point[] A, Scalar x) + { + Curve25519Point[] Result = new Curve25519Point[A.length]; + for (int i = 0; i < A.length; i++) + { + Result[i] = A[i].scalarMultiply(x); + } + return Result; + } + + /* Compute the inverse of a scalar, the stupid way */ + public static Scalar Invert(Scalar x) + { + Scalar inverse = new Scalar(x.toBigInteger().modInverse(CryptoUtil.l)); + + assert x.mul(inverse).equals(Scalar.ONE); + return inverse; + } + + /* Compute the slice of a curvepoint vector */ + public static Curve25519Point[] CurveSlice(Curve25519Point[] a, int start, int stop) + { + Curve25519Point[] Result = new Curve25519Point[stop-start]; + for (int i = start; i < stop; i++) + { + Result[i-start] = a[i]; + } + return Result; + } + + /* Compute the slice of a scalar vector */ + public static Scalar[] ScalarSlice(Scalar[] a, int start, int stop) + { + Scalar[] result = new Scalar[stop-start]; + for (int i = start; i < stop; i++) + { + result[i-start] = a[i]; + } + return result; + } + + /* Construct an aggregate range proof */ + public static ProofTuple PROVE(Scalar[] v, Scalar[] gamma, int logM) + { + int M = v.length; + int logMN = logM + NEXP; + + Curve25519Point[] V = new Curve25519Point[M]; + + V[0] = H.scalarMultiply(v[0]).add(G.scalarMultiply(gamma[0])); + // This hash is updated for Fiat-Shamir throughout the proof + Scalar hashCache = hashToScalar(V[0].toBytes()); + for (int j = 1; j < M; j++) + { + V[j] = H.scalarMultiply(v[j]).add(G.scalarMultiply(gamma[j])); + hashCache = hashToScalar(concat(hashCache.bytes,V[j].toBytes())); + } + + // PAPER LINES 36-37 + Scalar[] aL = new Scalar[M*N]; + Scalar[] aR = new Scalar[M*N]; + + for (int j = 0; j < M; j++) + { + BigInteger tempV = v[j].toBigInteger(); + for (int i = N-1; i >= 0; i--) + { + BigInteger basePow = BigInteger.valueOf(2).pow(i); + if (tempV.divide(basePow).equals(BigInteger.ZERO)) + { + aL[j*N+i] = Scalar.ZERO; + } + else + { + aL[j*N+i] = Scalar.ONE; + tempV = tempV.subtract(basePow); + } + + aR[j*N+i] = aL[j*N+i].sub(Scalar.ONE); + } + } + + // PAPER LINES 38-39 + Scalar alpha = randomScalar(); + Curve25519Point A = VectorExponent(aL,aR).add(G.scalarMultiply(alpha)); + + // PAPER LINES 40-42 + Scalar[] sL = new Scalar[M*N]; + Scalar[] sR = new Scalar[M*N]; + for (int i = 0; i < M*N; i++) + { + sL[i] = randomScalar(); + sR[i] = randomScalar(); + } + Scalar rho = randomScalar(); + Curve25519Point S = VectorExponent(sL,sR).add(G.scalarMultiply(rho)); + + // PAPER LINES 43-45 + hashCache = hashToScalar(concat(hashCache.bytes,A.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,S.toBytes())); + Scalar y = hashCache; + hashCache = hashToScalar(hashCache.bytes); + Scalar z = hashCache; + + // Polynomial construction by coefficients + Scalar[] l0; + Scalar[] l1; + Scalar[] r0; + Scalar[] r1; + + l0 = VectorSubtract(aL,VectorScalar(VectorPowers(Scalar.ONE,M*N),z)); + l1 = sL; + + // This computes the ugly sum/concatenation from PAPER LINE 65 + Scalar[] zerosTwos = new Scalar[M*N]; + for (int i = 0; i < M*N; i++) + { + zerosTwos[i] = Scalar.ZERO; + for (int j = 1; j <= M; j++) // note this starts from 1 + { + Scalar temp = Scalar.ZERO; + if (i >= (j-1)*N && i < j*N) + temp = Scalar.TWO.pow(i-(j-1)*N); // exponent ranges from 0..N-1 + zerosTwos[i] = zerosTwos[i].add(z.pow(1+j).mul(temp)); + } + } + + r0 = VectorAdd(aR,VectorScalar(VectorPowers(Scalar.ONE,M*N),z)); + r0 = Hadamard(r0,VectorPowers(y,M*N)); + r0 = VectorAdd(r0,zerosTwos); + r1 = Hadamard(VectorPowers(y,M*N),sR); + + // Polynomial construction before PAPER LINE 46 + Scalar t0 = InnerProduct(l0,r0); + Scalar t1 = InnerProduct(l0,r1).add(InnerProduct(l1,r0)); + Scalar t2 = InnerProduct(l1,r1); + + // PAPER LINES 47-48 + Scalar tau1 = randomScalar(); + Scalar tau2 = randomScalar(); + Curve25519Point T1 = H.scalarMultiply(t1).add(G.scalarMultiply(tau1)); + Curve25519Point T2 = H.scalarMultiply(t2).add(G.scalarMultiply(tau2)); + + // PAPER LINES 49-51 + hashCache = hashToScalar(concat(hashCache.bytes,z.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,T1.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,T2.toBytes())); + Scalar x = hashCache; + + // PAPER LINES 52-53 + Scalar taux = tau1.mul(x); + taux = taux.add(tau2.mul(x.sq())); + for (int j = 1; j <= M; j++) // note this starts from 1 + { + taux = taux.add(z.pow(1+j).mul(gamma[j-1])); + } + Scalar mu = x.mul(rho).add(alpha); + + // PAPER LINES 54-57 + Scalar[] l = l0; + l = VectorAdd(l,VectorScalar(l1,x)); + Scalar[] r = r0; + r = VectorAdd(r,VectorScalar(r1,x)); + + Scalar t = InnerProduct(l,r); + + // PAPER LINES 32-33 + hashCache = hashToScalar(concat(hashCache.bytes,x.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,taux.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,mu.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,t.bytes)); + Scalar x_ip = hashCache; + + // These are used in the inner product rounds + int nprime = M*N; + Curve25519Point[] Gprime = new Curve25519Point[M*N]; + Curve25519Point[] Hprime = new Curve25519Point[M*N]; + Scalar[] aprime = new Scalar[M*N]; + Scalar[] bprime = new Scalar[M*N]; + for (int i = 0; i < M*N; i++) + { + Gprime[i] = Gi[i]; + Hprime[i] = Hi[i].scalarMultiply(Invert(y).pow(i)); + aprime[i] = l[i]; + bprime[i] = r[i]; + } + Curve25519Point[] L = new Curve25519Point[logMN]; + Curve25519Point[] R = new Curve25519Point[logMN]; + int round = 0; // track the index based on number of rounds + Scalar[] w = new Scalar[logMN]; // this is the challenge x in the inner product protocol + + // PAPER LINE 13 + while (nprime > 1) + { + // PAPER LINE 15 + nprime /= 2; + + // PAPER LINES 16-17 + Scalar cL = InnerProduct(ScalarSlice(aprime,0,nprime),ScalarSlice(bprime,nprime,bprime.length)); + Scalar cR = InnerProduct(ScalarSlice(aprime,nprime,aprime.length),ScalarSlice(bprime,0,nprime)); + + // PAPER LINES 18-19 + L[round] = VectorExponentCustom(CurveSlice(Gprime,nprime,Gprime.length),CurveSlice(Hprime,0,nprime),ScalarSlice(aprime,0,nprime),ScalarSlice(bprime,nprime,bprime.length)).add(H.scalarMultiply(cL.mul(x_ip))); + R[round] = VectorExponentCustom(CurveSlice(Gprime,0,nprime),CurveSlice(Hprime,nprime,Hprime.length),ScalarSlice(aprime,nprime,aprime.length),ScalarSlice(bprime,0,nprime)).add(H.scalarMultiply(cR.mul(x_ip))); + + // PAPER LINES 21-22 + hashCache = hashToScalar(concat(hashCache.bytes,L[round].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,R[round].toBytes())); + w[round] = hashCache; + + // PAPER LINES 24-25 + Gprime = Hadamard2(VectorScalar2(CurveSlice(Gprime,0,nprime),Invert(w[round])),VectorScalar2(CurveSlice(Gprime,nprime,Gprime.length),w[round])); + Hprime = Hadamard2(VectorScalar2(CurveSlice(Hprime,0,nprime),w[round]),VectorScalar2(CurveSlice(Hprime,nprime,Hprime.length),Invert(w[round]))); + + // PAPER LINES 28-29 + aprime = VectorAdd(VectorScalar(ScalarSlice(aprime,0,nprime),w[round]),VectorScalar(ScalarSlice(aprime,nprime,aprime.length),Invert(w[round]))); + bprime = VectorAdd(VectorScalar(ScalarSlice(bprime,0,nprime),Invert(w[round])),VectorScalar(ScalarSlice(bprime,nprime,bprime.length),w[round])); + + round += 1; + } + + // PAPER LINE 58 (with inclusions from PAPER LINE 8 and PAPER LINE 20) + return new ProofTuple(V,A,S,T1,T2,taux,mu,L,R,aprime[0],bprime[0],t); + } + + /* Given a range proof, determine if it is valid */ + public static boolean VERIFY(ProofTuple[] proofs) + { + // Figure out which proof is longest + int maxLength = 0; + for (int p = 0; p < proofs.length; p++) + { + if (proofs[p].L.length > maxLength) + maxLength = proofs[p].L.length; + } + int maxMN = (int) Math.pow(2,maxLength); + + // Set up weighted aggregates for the first check + Scalar y0 = Scalar.ZERO; // tau_x + Scalar y1 = Scalar.ZERO; // t-(k+z+Sum(y^i)) + Curve25519Point Y2 = Curve25519Point.ZERO; // z-V sum + Curve25519Point Y3 = Curve25519Point.ZERO; // xT_1 + Curve25519Point Y4 = Curve25519Point.ZERO; // x^2T_2 + + + // Set up weighted aggregates for the second check + Curve25519Point Z0 = Curve25519Point.ZERO; // A + xS + Scalar z1 = Scalar.ZERO; // mu + Curve25519Point Z2 = Curve25519Point.ZERO; // Li/Ri sum + Scalar z3 = Scalar.ZERO; // (t-ab)x_ip + Scalar[] z4 = new Scalar[maxMN]; // g scalar sum + Scalar[] z5 = new Scalar[maxMN]; // h scalar sum + + for (int i = 0; i < maxMN; i++) + { + z4[i] = Scalar.ZERO; + z5[i] = Scalar.ZERO; + } + + for (int p = 0; p < proofs.length; p++) + { + ProofTuple proof = proofs[p]; + int logMN = proof.L.length; + int M = (int) Math.pow(2,logMN)/N; + + // For the current proof, get a random weighting factor + // NOTE: This must not be deterministic! Only the verifier knows it + Scalar weight = randomScalar(); + + // Reconstruct the challenges + Scalar hashCache = hashToScalar(proof.V[0].toBytes()); + for (int j = 1; j < M; j++) + hashCache = hashToScalar(concat(hashCache.bytes,proof.V[j].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.A.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.S.toBytes())); + Scalar y = hashCache; + hashCache = hashToScalar(hashCache.bytes); + Scalar z = hashCache; + hashCache = hashToScalar(concat(hashCache.bytes,z.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,proof.T1.toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.T2.toBytes())); + Scalar x = hashCache; + hashCache = hashToScalar(concat(hashCache.bytes,x.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,proof.taux.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,proof.mu.bytes)); + hashCache = hashToScalar(concat(hashCache.bytes,proof.t.bytes)); + Scalar x_ip = hashCache; + + // PAPER LINE 61 + y0 = y0.add(proof.taux.mul(weight)); + + Scalar k = Scalar.ZERO.sub(z.sq().mul(InnerProduct(VectorPowers(Scalar.ONE,M*N),VectorPowers(y,M*N)))); + for (int j = 1; j <= M; j++) // note this starts from 1 + { + k = k.sub(z.pow(j+2).mul(InnerProduct(VectorPowers(Scalar.ONE,N),VectorPowers(Scalar.TWO,N)))); + } + + y1 = y1.add(proof.t.sub(k.add(z.mul(InnerProduct(VectorPowers(Scalar.ONE,M*N),VectorPowers(y,M*N))))).mul(weight)); + + Curve25519Point temp = Curve25519Point.ZERO; + for (int j = 0; j < M; j++) + { + temp = temp.add(proof.V[j].scalarMultiply(z.pow(j+2))); + } + Y2 = Y2.add(temp.scalarMultiply(weight)); + Y3 = Y3.add(proof.T1.scalarMultiply(x.mul(weight))); + Y4 = Y4.add(proof.T2.scalarMultiply(x.sq().mul(weight))); + + // PAPER LINE 62 + Z0 = Z0.add((proof.A.add(proof.S.scalarMultiply(x))).scalarMultiply(weight)); + + // PAPER LINES 21-22 + // The inner product challenges are computed per round + Scalar[] w = new Scalar[logMN]; + hashCache = hashToScalar(concat(hashCache.bytes,proof.L[0].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.R[0].toBytes())); + w[0] = hashCache; + if (logMN > 1) + { + for (int i = 1; i < logMN; i++) + { + hashCache = hashToScalar(concat(hashCache.bytes,proof.L[i].toBytes())); + hashCache = hashToScalar(concat(hashCache.bytes,proof.R[i].toBytes())); + w[i] = hashCache; + } + } + + // Basically PAPER LINES 24-25 + // Compute the curvepoints from G[i] and H[i] + for (int i = 0; i < M*N; i++) + { + // Convert the index to binary IN REVERSE and construct the scalar exponent + int index = i; + Scalar gScalar = proof.a; + Scalar hScalar = proof.b.mul(Invert(y).pow(i)); + + for (int j = logMN-1; j >= 0; j--) + { + int J = w.length - j - 1; // because this is done in reverse bit order + int basePow = (int) Math.pow(2,j); // assumes we don't get too big + if (index / basePow == 0) // bit is zero + { + gScalar = gScalar.mul(Invert(w[J])); + hScalar = hScalar.mul(w[J]); + } + else // bit is one + { + gScalar = gScalar.mul(w[J]); + hScalar = hScalar.mul(Invert(w[J])); + index -= basePow; + } + } + + gScalar = gScalar.add(z); + hScalar = hScalar.sub(z.mul(y.pow(i)).add(z.pow(2+i/N).mul(Scalar.TWO.pow(i%N))).mul(Invert(y).pow(i))); + + // Now compute the basepoint's scalar multiplication + z4[i] = z4[i].add(gScalar.mul(weight)); + z5[i] = z5[i].add(hScalar.mul(weight)); + } + + // PAPER LINE 26 + z1 = z1.add(proof.mu.mul(weight)); + + temp = Curve25519Point.ZERO; + for (int i = 0; i < logMN; i++) + { + temp = temp.add(proof.L[i].scalarMultiply(w[i].sq())); + temp = temp.add(proof.R[i].scalarMultiply(Invert(w[i]).sq())); + } + Z2 = Z2.add(temp.scalarMultiply(weight)); + z3 = z3.add((proof.t.sub(proof.a.mul(proof.b))).mul(x_ip).mul(weight)); + + } + + // Perform the first- and second-stage check on all proofs at once + // NOTE: These checks could benefit from multiexp operations + Curve25519Point Check1 = Curve25519Point.ZERO; + Check1 = Check1.add(G.scalarMultiply(y0)); + Check1 = Check1.add(H.scalarMultiply(y1)); + Check1 = Check1.add(Y2.scalarMultiply(Scalar.ZERO.sub(Scalar.ONE))); + Check1 = Check1.add(Y3.scalarMultiply(Scalar.ZERO.sub(Scalar.ONE))); + Check1 = Check1.add(Y4.scalarMultiply(Scalar.ZERO.sub(Scalar.ONE))); + if (! Check1.equals(Curve25519Point.ZERO)) + { + System.out.println("Failed first-stage check"); + return false; + } + + Curve25519Point Check2 = Curve25519Point.ZERO; + Check2 = Check2.add(Z0); + Check2 = Check2.add(G.scalarMultiply(Scalar.ZERO.sub(z1))); + Check2 = Check2.add(Z2); + Check2 = Check2.add(H.scalarMultiply(z3)); + + for (int i = 0; i < maxMN; i++) + { + Check2 = Check2.add(Gi[i].scalarMultiply(Scalar.ZERO.sub(z4[i]))); + Check2 = Check2.add(Hi[i].scalarMultiply(Scalar.ZERO.sub(z5[i]))); + } + + if (! Check2.equals(Curve25519Point.ZERO)) + { + System.out.println("Failed second-stage check"); + return false; + } + + return true; + } + + /* Generate a random proof with specified bit size and number of outputs */ + public static ProofTuple randomProof(int mExp) + { + int M = (int) Math.pow(2,mExp); + + Random rando = new Random(); + Scalar[] amounts = new Scalar[M]; + Scalar[] masks = new Scalar[M]; + + // Generate the outputs and masks + for (int i = 0; i < M; i++) + { + long amount = -1L; + while (amount > Math.pow(2,N)-1 || amount < 0L) // Java doesn't handle random long ranges very well + amount = rando.nextLong(); + amounts[i] = new Scalar(BigInteger.valueOf(amount)); + masks[i] = randomScalar(); + } + + // Run and return the proof + // Have to pass in lg(M) because Java is stupid about logarithms + System.out.println("Generating proof with " + M + " outputs..."); + return PROVE(amounts,masks,mExp); + } + + public static void main(String[] args) + { + // Test parameters: currently only works when batching proofs of the same aggregation size + NEXP = 6; // N = 2^NEXP + N = (int) Math.pow(2,NEXP); // number of bits in amount range (so amounts are 0..2^(N-1)) + int MAXEXP = 4; // the maximum number of outputs used is 2^MAXEXP + int PROOFS = 5; // number of proofs in batch + + // Set the curve base points + G = Curve25519Point.G; + H = Curve25519Point.hashToPoint(G); + int MAXM = (int) Math.pow(2,MAXEXP); + Gi = new Curve25519Point[MAXM*N]; + Hi = new Curve25519Point[MAXM*N]; + for (int i = 0; i < MAXM*N; i++) + { + Gi[i] = getHpnGLookup(2*i); + Hi[i] = getHpnGLookup(2*i+1); + } + + // Set up all the proofs + ProofTuple[] proofs = new ProofTuple[PROOFS]; + Random rando = new Random(); + for (int i = 0; i < PROOFS; i++) + { + // Pick a random proof length: 2^0,...,2^MAXEXP + proofs[i] = randomProof(rando.nextInt(MAXEXP+1)); + } + + // Verify the batch + System.out.println("Verifying proof batch..."); + if (VERIFY(proofs)) + System.out.println("Success!"); + else + System.out.println("ERROR: failed verification"); + + } +} From 956c59ad24029d28eec5f0daa5d83d5cd9874eb5 Mon Sep 17 00:00:00 2001 From: Brandon Goodell Date: Thu, 8 Feb 2018 17:04:13 -0700 Subject: [PATCH 13/13] Architecture present for whole sims and transcripts. --- source-code/Poisson-Graphs/Blockchain.py | 983 ++++++- source-code/Poisson-Graphs/Edge.py | 67 +- source-code/Poisson-Graphs/FishGraph.py | 307 -- source-code/Poisson-Graphs/Graph.py | 264 +- source-code/Poisson-Graphs/Node.py | 948 +------ .../Poisson-Graphs/StochasticProcess.py | 53 - source-code/Poisson-Graphs/new/Block.py | 46 - source-code/Poisson-Graphs/new/Block.py~ | 46 - source-code/Poisson-Graphs/new/Blockchain.py | 1142 -------- source-code/Poisson-Graphs/new/Blockchain.py~ | 1142 -------- source-code/Poisson-Graphs/new/Node.py | 116 - source-code/Poisson-Graphs/new/Node.py~ | 116 - .../new/__pycache__/Block.cpython-35.pyc | Bin 2174 -> 0 bytes .../new/__pycache__/Blockchain.cpython-35.pyc | Bin 9813 -> 0 bytes source-code/Poisson-Graphs/new/output.txt | 2501 ----------------- source-code/Poisson-Graphs/new/outputM.txt | 602 ---- source-code/Poisson-Graphs/new/outputM.txt~ | 201 -- 17 files changed, 1210 insertions(+), 7324 deletions(-) delete mode 100644 source-code/Poisson-Graphs/FishGraph.py delete mode 100644 source-code/Poisson-Graphs/StochasticProcess.py delete mode 100644 source-code/Poisson-Graphs/new/Block.py delete mode 100644 source-code/Poisson-Graphs/new/Block.py~ delete mode 100644 source-code/Poisson-Graphs/new/Blockchain.py delete mode 100644 source-code/Poisson-Graphs/new/Blockchain.py~ delete mode 100644 source-code/Poisson-Graphs/new/Node.py delete mode 100644 source-code/Poisson-Graphs/new/Node.py~ delete mode 100644 source-code/Poisson-Graphs/new/__pycache__/Block.cpython-35.pyc delete mode 100644 source-code/Poisson-Graphs/new/__pycache__/Blockchain.cpython-35.pyc delete mode 100644 source-code/Poisson-Graphs/new/output.txt delete mode 100644 source-code/Poisson-Graphs/new/outputM.txt delete mode 100644 source-code/Poisson-Graphs/new/outputM.txt~ diff --git a/source-code/Poisson-Graphs/Blockchain.py b/source-code/Poisson-Graphs/Blockchain.py index f0e23c5..17808e3 100644 --- a/source-code/Poisson-Graphs/Blockchain.py +++ b/source-code/Poisson-Graphs/Blockchain.py @@ -1,4 +1,8 @@ from Block import * +import math +from scipy.stats import * +from numpy import * +from copy import deepcopy class Blockchain(object): ''' @@ -10,21 +14,26 @@ class Blockchain(object): self.blocks = {} self.leaves = {} self.miningIdents = None + self.mIdent = None self.verbose = verbosity + self.diff = None + self.targetRate = None + self.mode = None 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() + assert blockToAdd.ident not in self.blocks + if len(self.blocks)==0: + # In this case, blockToAdd is a genesis block, so we set difficulty + self.diff = deepcopy(blockToAdd.diff) + + 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() + return self.computeDifficulty() def whichLeaf(self): # Determine which leaf shall be the parent leaf. @@ -52,10 +61,217 @@ class Blockchain(object): self.miningIdents.append(ident) #print("leaf ident = ", str(ident), ", and tempCumDiff = ", str(tempCumDiff), " and maxCumDiff = ", str(maxCumDiff)) assert len(self.miningIdents) > 0 + self.mIdent = random.choice(self.miningIdents) + + + # 1 block in 6*10^5 milliseconds=10min + def computeDifficulty(self): + result = None + if self.mode=="Nakamoto": + # Use MLE estimate of poisson process, compare to targetRate, update by multiplying by resulting ratio. + #if self.verbose: + # print("Beginning update of difficulty with Nakamoto method") + count = 2016 + #if self.verbose: + # print("Checking that blockchain is 2016*n blocks long and some mining identity has been set") + if len(self.blocks) % 2016 == 0 and len(self.miningIdents) > 0: + ident = self.mIdent + topTime = deepcopy(self.blocks[ident].discoTimestamp) + parent = self.blocks[ident].parent + count = count - 1 + touched = False + while count > 0 and parent is not None: + ident = deepcopy(parent) + parent = self.blocks[ident].parent + count = count - 1 + touched = True + if not touched: + mleDiscoRate = deepcopy(self.targetRate) + else: + botTime = deepcopy(self.blocks[ident].discoTimestamp) + + # Algebra is okay: + assert topTime != botTime + + # MLE estimate of arrivals per second: + mleDiscoRate = float(2015)/float(topTime - botTime) + + # Rates can't be negative, but this estimate could be (although it's highly unlikely given Bitcoin's standard choices + # of difficulty update rate, etc. + mleDiscoRate = abs(mleDiscoRate) + + if self.verbose: + print("MLE disco rate = " + str(mleDiscoRate) + " and targetRate = " + str(self.targetRate)) + # 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. + + if self.verbose: + print("MLE discovery rate = " + str(mleDiscoRate)) + print("Difficulty before adjustment = " + str(self.diff)) + + # Update difficulty multiplicatively + self.diff = self.diff*mleDiscoRate/self.targetRate + + if self.verbose: + print("Difficulty after adjustment = ", str(self.diff)) + + elif self.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 + #print(self.diff) + assert self.diff != 0.0 + if len(self.blocks) > 120 and len(self.miningIdents) > 0: + ident = self.mIdent + bl = [] + bl.append(deepcopy(self.blocks[ident].discoTimestamp)) + parent = self.blocks[ident].parent + count = count - 1 + while count > 0 and parent is not None: + ident = deepcopy(parent) + bl.append(deepcopy(self.blocks[ident].discoTimestamp)) + parent = self.blocks[ident].parent + count = count-1 + # sort + bl = sorted(bl) + assert len(bl)<=1200 + + #print("Sample size = " + str(len(bl))) + # remove 10 and 90 %-iles + numOutliers = math.ceil(float(len(bl))/float(10)) + assert numOutliers <= 120 + #print("Number of outliers = " + str(numOutliers)) + oldBL = deepcopy(bl) + if numOutliers > 0: + bl = bl[numOutliers:-numOutliers] + #if numOutliers == 120: + # print("\n\nSORTED TS LIST = " + str(oldBL) + "\nModified list = " + str(bl)) + + + # get topTime and botTime + #if self.verbose: + # print("bl[0] = " + str(bl[0]) + ",\tbl[-1] = " + str(bl[-1])) + topTime = bl[-1] + botTime = bl[0] + result = [float(topTime - botTime)] + #print(topTime - botTime) + #if self.verbose: + # print("list of timestamps = " + str(bl)) + # print("topTime = " + str(bl[-1])) + # print("botTime = " + str(bl[0])) + + # Assert algebra will work + # 1200 - 2*120 = 1200 - 240 = 960 + assert 0 < len(bl) and len(bl) < 961 + assert topTime - botTime >= 0.0 + result.append(len(bl)-1) + # 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 + if len(bl)==0: + print("WOOP WOOP NO TIMESTAMPS WTF? We have " + str(len(self.blocks)) + " blocks available, and we are counting " + str(2*numOutliers) + " as outliers. bl = " + str(bl)) + naiveDiscoRate = float(len(bl)-1)/float(topTime - botTime) + + # How much should difficulty change? + assert naiveDiscoRate != 0.0 + assert self.targetRate != 0.0 + assert self.diff != 0.0 + self.diff = self.diff*naiveDiscoRate/self.targetRate + + elif self.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 + + # Really a trash metric unless sample sizes are huge. + count = 1200 + ident = self.mIdent + bl = [] + bl.append(deepcopy(self.blocks[ident].discoTimestamp)) + parent = self.blocks[ident].parent + count = count - 1 + while count > 0 and parent is not None: + ident = deepcopy(parent) + bl.append(deepcopy(self.blocks[ident].discoTimestamp)) + parent = self.blocks[ident].parent + count = count-1 + if len(bl) > 120: + sk = abs(skew(bl)) + va = var(bl) + stdv = sqrt(va) + lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0) + else: + lam = self.targetRate # we will not change difficulty unless we have at least 120 blocks of data (arbitrarily selected) + self.diff = self.diff*(lam/self.targetRate) + elif self.mode=="reciprocalOfMedian": + # In this mode we use a bitcoin-style metric except instead of 1/average inter-arrival time + # we use 1/median magnitude of inter-arrival time. + # And updated each block like with monero instead of every 2016 blocks like bitcoin. + # We assume a sample size of only 600 blocks for now + count = 600 + interArrivals = [] + if len(self.blocks) < count: + estDiscoRate = self.targetRate + elif len(self.miningIdents) > 0: + ident = self.mIdent + parent = self.blocks[ident].parent + if parent is not None: + dT = abs(self.blocks[ident].discoTimestamp - self.blocks[parent].discoTimestamp) + interArrivals.append(dT) + count = count - 1 + touched = False + while count > 0 and parent is not None: + ident = deepcopy(parent) + parent = self.blocks[ident].parent + if parent is not None: + dT = abs(self.blocks[ident].discoTimestamp - self.blocks[parent].discoTimestamp) + interArrivals.append(dT) + count = count - 1 + touched = True + if not touched: + estDiscoRate = self.targetRate + else: + estDiscoRate = 1.0/median(interArrivals) + if self.verbose: + print("Est disco rate = " + str(estDiscoRate) + " and targetRate = " + str(self.targetRate)) + + + if self.verbose: + print("MLE discovery rate = " + str(estDiscoRate)) + print("Difficulty before adjustment = " + str(self.diff)) + + # Update difficulty multiplicatively + self.diff = self.diff*estDiscoRate/self.targetRate + + if self.verbose: + print("Difficulty after adjustment = ", str(self.diff)) + else: + print("Error, invalid difficulty mode entered.") + return result class Test_Blockchain(unittest.TestCase): def test_addBlock(self): bill = Blockchain([], verbosity=True) + bill.mode="Nakamoto" + tr = 1.0/100.0 + bill.targetRate = tr name = newIdent(0) t = time.time() @@ -94,8 +310,101 @@ class Test_Blockchain(unittest.TestCase): self.assertEqual(len(bill.blocks),2) + + + + bill = Blockchain([], verbosity=True) + mode="vanSaberhagen" + tr = 1.0/100.0 + bill.targetRate = tr + + name = newIdent(0) + t = time.time() + s = t+random.random() + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + + 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]) + self.assertEqual(len(bill.blocks),1) + + name = newIdent(1) + t = time.time() + s = t+random.random() + diff = 1.0 + params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} + blockA = Block(params) + bill.addBlock(blockA, mode, tr) + + self.assertTrue(blockA.ident in bill.blocks) + self.assertTrue(blockA.ident in bill.leaves) + self.assertTrue(genesis.ident not in bill.leaves) + self.assertEqual(len(bill.miningIdents),1) + self.assertEqual(blockA.ident, bill.miningIdents[0]) + self.assertEqual(len(bill.blocks),2) + + + + + + bill = Blockchain([], verbosity=True) + mode="MOM:expModGauss" + tr = 1.0/100.0 + bill.targetRate = tr + + name = newIdent(0) + t = time.time() + s = t+random.random() + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + + 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]) + self.assertEqual(len(bill.blocks),1) + + name = newIdent(1) + t = time.time() + s = t+random.random() + diff = 1.0 + params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} + blockA = Block(params) + bill.addBlock(blockA, mode, tr) + + self.assertTrue(blockA.ident in bill.blocks) + self.assertTrue(blockA.ident in bill.leaves) + self.assertTrue(genesis.ident not in bill.leaves) + self.assertEqual(len(bill.miningIdents),1) + self.assertEqual(blockA.ident, bill.miningIdents[0]) + self.assertEqual(len(bill.blocks),2) + + def test_bc(self): bill = Blockchain([], verbosity=True) + mode="Nakamoto" + tr = 1.0/100.0 + bill.targetRate = tr name = newIdent(0) t = time.time() @@ -110,7 +419,7 @@ class Test_Blockchain(unittest.TestCase): self.assertTrue(genesis.parent is None) self.assertEqual(genesis.diff,diff) - bill.addBlock(genesis) + bill.addBlock(genesis, mode, tr) self.assertTrue(genesis.ident in bill.blocks) self.assertTrue(genesis.ident in bill.leaves) @@ -123,9 +432,9 @@ class Test_Blockchain(unittest.TestCase): diff = 2.0 params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} blockA = Block(params) - bill.addBlock(blockA) + bill.addBlock(blockA, mode, tr) - bill.whichLeaf() + #bill.whichLeaf() self.assertTrue(blockA.ident in bill.blocks) self.assertTrue(blockA.ident in bill.leaves) @@ -140,7 +449,7 @@ class Test_Blockchain(unittest.TestCase): diff = 2.0 params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} blockB = Block(params) - bill.addBlock(blockB) + bill.addBlock(blockB, mode, tr) self.assertTrue(blockB.ident in bill.blocks) self.assertTrue(blockB.ident in bill.leaves) @@ -154,7 +463,7 @@ class Test_Blockchain(unittest.TestCase): self.assertFalse(genesis.ident in bill.leaves) self.assertTrue(bill.blocks[genesis.ident].parent is None) - bill.whichLeaf() + #bill.whichLeaf() #print(bill.miningIdents) self.assertEqual(type(bill.miningIdents), type([])) @@ -165,7 +474,7 @@ class Test_Blockchain(unittest.TestCase): diff = 3.14159 params = {"ident":name, "disco":t, "arriv":s, "parent":blockB.ident, "diff":diff} blockC = Block(params) - bill.addBlock(blockC) + bill.addBlock(blockC, mode, tr) self.assertTrue(blockC.ident in bill.blocks) self.assertTrue(blockC.ident in bill.leaves) @@ -179,7 +488,7 @@ class Test_Blockchain(unittest.TestCase): self.assertTrue(genesis.ident in bill.blocks) self.assertFalse(genesis.ident in bill.leaves) - bill.whichLeaf() + #bill.whichLeaf() #for blockIdent in bill.blocks: # ident = bill.blocks[blockIdent].ident @@ -191,8 +500,644 @@ class Test_Blockchain(unittest.TestCase): #print(bill.miningIdents) self.assertEqual(len(bill.miningIdents), 1) self.assertEqual(bill.miningIdents[0], blockC.ident) + + def test_median(self): + # TODO: everything. + mode = "reciprocalOfMedian" + tr = 1.0 # one block per millisecond why not + deltaT = 1.0 # let's just make this easy + bill = Blockchain([], verbosity=True) + bill.targetRate = tr + + with open("outputM.txt", "w") as writeFile: + # We will send (t, a, diff) to writeFile. + writeFile.write("time,rateConstant,difficulty\n") + name = newIdent(0) + t = 0.0 + s = 0.0 + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + a = 1.0 + b = 1.0/a + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + + 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]) + + parent = genesis.ident + oldDiff = bill.diff + + while len(bill.blocks)<601: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + a = 1.01 # slightly slower blocks, median won't change until half the data is corrupted! + b = 1.0/a + while len(bill.blocks)<899: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + self.assertEqual(bill.diff, oldDiff) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + # One more block and our median inter-arrival time is deltaT*(1.0+a)/2.0 + # and so estRate = 1/median = (2.0/(1.0+a))/deltaT, whereas before it was just + # 1/deltaT. So estRate/targetRate = 2.0/(1.0+a) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + err = bill.diff - oldDiff*2.0/(1.0+a) + self.assertTrue(err*err < 10**-15) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + # One more block and our median inter-arrival time is deltaT*a + # and so estRate = 1/median = (1.0/a)/deltaT, whereas before it was just + # 1/deltaT. So estRate/targetRate = 1.0/a = b + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + err = bill.diff - oldDiff*b + self.assertTrue(err*err < 10**-15) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + # Note that until the median changes again, this estimated block arrival rate + # does not change. This may be true even if a lot of new data has come in. + # It is possible that the same pair of blocks remain the median inter-arrival + # magnitude for the entire time both blocks are in the sample size. + # During this period of time, difficulty will update multiplicatively, so + # will either exponentially grow or shrink. + # In other words, this model can be looked at as: exponential change over + # time with a rate proportional to the deviation between the median and + # the target inter-arrival rates. + + + + + + def test_mine(self): + # TODO: everything. + mode = "MOM:expModGauss" + tr = 1.0/120000.0 # one block per two minutes + deltaT = 120000.0 + bill = Blockchain([], verbosity=True) + bill.targetRate = tr + + with open("outputM.txt", "w") as writeFile: + # We will send (t, a, diff, ratio, awayFromOne) to writeFile. + writeFile.write("time,rateConstant,difficulty\n") + name = newIdent(0) + t = 0.0 + s = 0.0 + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + a = 1.0 + b = 1.0/a + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + + 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]) + + parent = genesis.ident + oldDiff = bill.diff + + while len(bill.blocks)<120: + # Our metric divides by skewness. In reality, this is zero with + # probability zero. But for our tests, it's assured. So we + # will perturb each arrival by a small, up-to-half-percent + # variation to ensure a nonzero skewness without altering things + # too much. + newName = newIdent(len(bill.blocks)) + t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + # Just one more block and difficulty should be computed for the first time. + print("Just one more block and difficulty should be computed for the first time.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + #self.assertEqual(bill.diff, oldDiff) + + oldDiff = bill.diff + + # what if we add a bunch of blocks this way? + # In the case of a static hash rate, I suppose we hope to not + # vary too far from a multiplicative factor of 1.0, or rather + # a constant difficulty. + + while len(bill.blocks)<200: + # Our metric divides by skewness. In reality, this is zero with + # probability zero. But for our tests, it's assured. So we + # will perturb each arrival by a small, up-to-half-percent + # variation to ensure a nonzero skewness without altering things + # too much. + newName = newIdent(len(bill.blocks)) + t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") + parent = newName + oldDiff = bill.diff + + + + def test_vs(self): + # TODO: Still must test that outliers are being removed "appropriately" according to specifications + # TODO: Test that scrambled lists of timestamps produce the same difficulty estimate. + # TODO: Show that in the case of homogeneous poisson processes, unusual estimates are a little + # more common than in the Nakamoto difficulty (which must be the case because Nakamoto uses + # the UMVUE). + mode = "vanSaberhagen" + tr = 1.0/60000.0 # one block per minute + deltaT = 60000.0 + bill = Blockchain([], verbosity=True) + bill.targetRate = tr + + with open("output.txt", "w") as writeFile: + # We will send (t, a, diff, ratio, awayFromOne) to writeFile. + writeFile.write("time,rateConstant,difficulty,ratio\n") + name = newIdent(0) + t = 0.0 + s = 0.0 + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") + + 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]) + self.assertEqual(bill.diff, 1.0) + + parent = genesis.ident + oldDiff = bill.diff + a = 1.0 + b = 1.0/a + + while len(bill.blocks)<120: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + self.assertEqual(bill.diff, oldDiff) + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + + writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") + parent = newName + oldDiff = bill.diff + + # Just one more block and difficulty should be computed for the first time. + print("Just one more block and difficulty should be computed for the first time.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") + parent = newName + self.assertEqual(bill.diff, oldDiff) + + oldDiff = bill.diff + + print("Let's add more blocks at the same rate.") + a = 1.0 + b = 1.0/a + + while len(bill.blocks)<1200: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") + parent = newName + self.assertEqual(bill.diff, oldDiff) + oldDiff = bill.diff + + print("Let's add more blocks at a slower rate.") + a = 1.1 + b = 1.0/a + + # If blocks arrive slightly further apart, difficulty should drop. + # However, since vanSaberhagen discards top 10% and bottom 10% of + # timestamps, it will take 120 blocks for this change to register + # in difficulty. + print("If blocks arrive slightly further apart, difficulty should drop. However, since vanSaberhagen discards top 10% and bottom 10% of timestamps, it will take 120 blocks for this change to register in difficulty.") + while len(bill.blocks)<1320: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertEqual(bill.diff, oldDiff) + oldDiff = bill.diff + + print("One more block and difficulty should register a change.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertTrue(bill.diff < oldDiff) + oldDiff = bill.diff + + # Let's add another fifty blocks at this same rate and verify that difficulty continually + # drops. + print("Let's add another fifty blocks at this same rate and verify that difficulty continually drops.") + a = 1.1 + b = 1.0/a + + while len(bill.blocks)<1370: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertTrue(bill.diff < oldDiff) + oldDiff = bill.diff + + # Now we go back to the target rate. We have 170 slow blocks in the queue and 50 in the sample size. Difficulty will continue to drop for another 120 blocks... + print("Now we go back to the target rate. We have 170 slow blocks in the queue and 50 in the sample size. Difficulty will continue to drop for another 120 blocks...") + a = 1.0 + b = 1.0/a + + while len(bill.blocks)<1490: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertTrue(bill.diff < oldDiff) + oldRatio = bill.diff/oldDiff + oldDiff = bill.diff + #print(str(result) + ", " + str(bill.diff) + ", " + str(oldDiff)) + + # Now all 170 slow blocks are not only in the queue but in our sample. The *multiplicative factor* between timesteps should be identical for the next 790 blocks.. leading to AN EXPONENTIAL DECAY OF DIFFICULTY. + print("Now all 170 slow blocks are not only in the queue but in our sample. The *multiplicative factor* between timesteps should be identical for the next 790 blocks.. leading to AN EXPONENTIAL DECAY OF DIFFICULTY.") + a = 1.0 + b = 1.0/a + while len(bill.blocks)<2279: + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + ratio = bill.diff/oldDiff + parent = newName + err = ratio - oldRatio + #print("Difference between last ratio and next ratio:" + str(err)) + self.assertTrue(err*err < 10**-15) + oldDiff = bill.diff + oldRatio = ratio + + print("Now adding a single new block will cause our 170 slow blocks to start dropping out of our sample, so the ratio should start returning to 1.0.") + oldAwayFromOne = abs(oldRatio - 1.0) # Ratio should be returning to 1.0 so this difference should go to zero + oldAwayFromOne = oldAwayFromOne*oldAwayFromOne + + # For the next 170 blocks as our perturbed blocks drop out of our sample, our + # estimated block arrival rate will return to "normal" so the multiplicative + # difference in difficulty should return to 1.0. + print("For the next 170 blocks as our perturbed blocks drop out of our sample, ourestimated block arrival rate will return to normal so the multiplicative difference in difficulty should return to 1.0.") + a = 1.0 + b = 1.0/a + while len(bill.blocks)<2449: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + ratio = bill.diff/oldDiff + #print("New ratio = " + str(ratio) + " and oldRatio = " + str(oldRatio)) + self.assertTrue(ratio > oldRatio) + awayFromOne = abs(ratio - 1.0) # Ratio should be returning to 1.0 so this difference should go to zero + awayFromOne = awayFromOne*awayFromOne + self.assertTrue(awayFromOne < oldAwayFromOne) # This return will be monotonic in our manufactured example. + parent = newName + oldDiff = bill.diff + oldRatio = ratio + oldAwayFromOne = awayFromOne + + + # Now difficulty should remain frozen for as long as we like. + + a = 1.0 + b = 1.0/a + while len(bill.blocks)<2500: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") + parent = newName + self.assertEqual(bill.diff, oldDiff) + oldDiff = bill.diff + + + + + def test_nak(self): + # Since Nakamoto difficulty is derived from the MLE of the block arrival rate, + # we already know how it "should" behave in a poisson process, etc. + # TODO: Generate N samples of MLEs of Poisson rates compared to known homog. + # poisson rate, show that the resulting code does not result in unusual measurements + # more often than expected. + mode = "Nakamoto" + tr = 1.0/600000.0 + deltaT = 600000.0 + bill = Blockchain([], verbosity=True) + bill.targetRate = tr + # Bitcoin updating at 1 block per 10 minutes + + name = newIdent(0) + t = 0.0 + s = 0.0 + 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,s) + self.assertTrue(genesis.parent is None) + self.assertEqual(genesis.diff,diff) + + bill.addBlock(genesis, mode, tr) + + 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]) + self.assertEqual(bill.diff, 1.0) + + parent = genesis.ident + oldDiff = bill.diff + i = 1 + + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + + # Just one more block and difficulty should recompute. + print("Just one more block and difficulty should recompute.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + self.assertEqual(bill.diff, oldDiff) + + oldDiff = bill.diff + i += 1 + + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + parent = newName + + # Just one more block and difficulty should recompute. + print("Just one more block and difficulty should again recompute.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT + s += deltaT + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + self.assertEqual(bill.diff, oldDiff) + + oldDiff = bill.diff + i += 1 + a = 1.1 + b = 1.0/a + + # If blocks arrive slightly further apart, difficulty should drop. + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + parent = newName + + print("Just one more block and difficulty will go down.") + + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + err = abs(bill.diff - oldDiff*b) + self.assertTrue(err*err < 10**-15) + oldDiff = bill.diff + i += 1 + + + # If blocks then arrive on target, difficulty should freeze. + a = 1.0 + b = 1.0/a + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + parent = newName + + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + self.assertEqual(bill.diff, oldDiff) + oldDiff = bill.diff + i += 1 + + # If blocks arrive too close together, difficulty should increase. + a = 0.9 + b = 1.0/a + while len(bill.blocks)<2016*i-1: + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + + bill.addBlock(newBlock, mode, tr) + parent = newName + + print("Just one more block and difficulty should go up.") + self.assertEqual(bill.diff, oldDiff) + newName = newIdent(len(bill.blocks)) + t += deltaT*a + s += deltaT*a + diff = bill.diff + params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} + newBlock = Block(params) + bill.addBlock(newBlock, mode, tr) + parent = newName + err = abs(bill.diff - oldDiff*b) + self.assertTrue(err*err < 10**-15) + + -suite = unittest.TestLoader().loadTestsFromTestCase(Test_Blockchain) -unittest.TextTestRunner(verbosity=1).run(suite) +#suite = unittest.TestLoader().loadTestsFromTestCase(Test_Blockchain) +#unittest.TextTestRunner(verbosity=1).run(suite) diff --git a/source-code/Poisson-Graphs/Edge.py b/source-code/Poisson-Graphs/Edge.py index 37574c2..b3634f2 100644 --- a/source-code/Poisson-Graphs/Edge.py +++ b/source-code/Poisson-Graphs/Edge.py @@ -4,7 +4,7 @@ class Edge(object): ''' Edge object. Has an identity, some data, and a dict of nodes. ''' - def __init__(self, params=["", {}, True]): + def __init__(self, params): try: assert len(params)==3 except AssertionError: @@ -29,66 +29,11 @@ class Edge(object): 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}) + 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) diff --git a/source-code/Poisson-Graphs/FishGraph.py b/source-code/Poisson-Graphs/FishGraph.py deleted file mode 100644 index b1168d5..0000000 --- a/source-code/Poisson-Graphs/FishGraph.py +++ /dev/null @@ -1,307 +0,0 @@ -import unittest, copy, random, math, time -from scipy.stats import skew -from numpy import var -from numpy import random as nprandom - -#TODO: Node.data["blockchain"] != node.data - -def newIdent(params): - nonce = params - # Generate new random identity. - return hash(str(nonce) + str(random.random())) - -def newIntensity(params): - mode = params - if mode=="uniform": - return random.random() - -def newOffset(params): - mode = params - if mode=="unifDST": - r = 2.0*random.random() - 1.0 # hours - r = 60.0*60.0*r #60 min/hr, 60 sec/min - return r - if mode=="sumOfSkellams": - # This mode uses a skellam distribution, which is - # the difference of two poisson-distributed random - # variables. - # HourOffset = skellam - # SecondOffset = skellam - # TotalOffset = 60*60*HourOffset + 60*MinuteOffset + SecondOffset - # Each skellam = poisson(1) - poisson(1) - # Reasoning: We consider most computers' local time offset from UTC - # to be a two time-scale random variable, one on the hour scale and one on - # the second scale. We make - x = nprandom.poisson(1, (2,2)) - totalOffset = 60.0*60.0*float(x[0][0] - x[1][0]) + float((x[0][1] - x[1][1])) - return totalOffset - - -class FishGraph(StochasticProcess): - ''' - Stochastic process on a graph - with the graph growing in a stochastic process too - ''' - # TODO: Check if output.txt exists before beginning. If so, clear it and create a new one. - # TODO: Instead of/in addition to storing graph data in a text file, can we plot with ggplot in R? - def __init__(self, params=None, verbosity=True): - # Initialize - - assert "maxTime" in params - self.maxTime = copy.deepcopy(params["maxTime"]) - del params["maxTime"] - - assert "numNodes" in params - numNodes = params["numNodes"] - del params["numNodes"] - - self.data = params - self.t = 0.0 - self.state = Graph() - self.filename = "output.txt" - self.verbose = verbosity - - # Create graph - self.state.createGraph(numNodes, self.data["probEdge"], self.data["maxNeighbors"]) - - # Update node data - for nIdent in self.state.nodes: - n = self.state.nodes[nIdent] - difficulty = 1.0 - intensity = newIntensity(params="uniform") - offset = newOffset(params="sumOfSkellams") - dat = {"intensity":intensity, "offset":offset, "blockchain":Blockchain([], verbosity=self.verbose)} - n.data.update(dat) - - # Update edge data. - for eIdent in self.state.edges: - e = self.state.edges[eIdent] - e.data.update({"pendingBlocks":{}}) - - def go(self): - assert self.maxTime > 0.0 - while self.t <= self.maxTime and len(self.state.nodes) > 0: - deltaT = self.getNextTime() - self.updateState(self.t, deltaT) - self.record() - - def getNextTime(self): - # Each Poisson process event generates an exponential random variable. - # The smallest of these is selected - # The rate of the smallest determines event type. - eventTag = None - - u = 0.0 - while(u == 0.0): - u = copy.deepcopy(random.random()) - u = -1.0*math.log(copy.deepcopy(u))/self.data["birthRate"] # Time until next stochastic birth - eventTag = "birth" - - v = 0.0 - while(v == 0.0): - v = copy.deepcopy(random.random()) - v = -1.0*math.log(copy.deepcopy(v))/self.data["deathRate"] # Time until next stochastic death - if v < u: - u = copy.deepcopy(v) - eventTag = "death" - - for nIdent in self.state.nodes: - n = self.state.nodes[nIdent] # n.ident = nIdent - v = 0.0 - while(v == 0.0): - v = copy.deepcopy(random.random()) - v = -1.0*math.log(copy.deepcopy(v))/n.data["intensity"] - if v < u: - u = copy.deepcopy(v) - eventTag = ["discovery", n.ident] - - # Now that all the STOCHASTIC arrivals have been decided, - # We check if any of the deterministic events fire off instead. - for eIdent in self.state.edges: - e = self.state.edges[eIdent] # e.ident = eIdent - pB = e.data["pendingBlocks"] - if len(pB) > 0: - for pendingIdent in pB: - arrivalInfo = pB[pendingIdent] - v = arrivalInfo["timeOfArrival"] - self.t - if v < u and 0.0 < v: - u = copy.deepcopy(v) - eventTag = ["arrival", e.ident, pendingIdent] - - deltaT = (u, eventTag) - # Formats: - # eventTag = ["arrival", e.ident, pendingIdent] - # eventTag = ["discovery", n.ident] - # eventTag = "death" - # eventTag = "birth" - return deltaT - - def updateState(self, t, deltaT, mode="Nakamoto", targetRate=1.0/1209600.0): - # Depending on eventTag, update the state... - u = deltaT[0] - shout = "" - eventTag = deltaT[1] - - if type(eventTag)==type("birthordeath"): - if eventTag == "death": - # Picks random nodeIdent and kills it - toDie = random.choice(list(self.state.nodes.keys())) - x = len(self.state.nodes) - shout += "DEATH, Pop(Old)=" + str(x) + ", Pop(New)=" - if self.verbose: - print(shout) - self.state.delNode(toDie) - y = len(self.state.nodes) - assert y == x - 1 - shout += str(y) + "\n" - - elif eventTag == "birth": - # Adds node with some randomly determined edges - x = len(self.state.nodes) - shout += "BIRTH, Pop(Old)=" + str(x) + ", Pop(New)=" - if self.verbose: - print(shout) - nIdent = self.state.addNode() - n = self.state.nodes[nIdent] - intensity = random.random()/1000.0 - offset = 2.0*random.random() - 1.0 - n.data.update({"intensity":intensity, "offset":offset, "blockchain":{}}) - # Auto syncs new node. - for eIdent in n.edges: - e = n.edges[eIdent] - e.data.update({"pendingBlocks":{}}) - mIdent = e.getNeighbor(n.ident) - m = self.state.nodes[mIdent] - mdata = m.data["blockchain"] - n.updateBlockchain(mdata) - y = len(self.state.nodes) - assert y == x + 1 - shout += str(y) + "\n" - else: - print("Error: eventTag had length 1 but was neighter a birth or a death, this shouldn't happen so this else case will eventually be removed, I guess? Our eventTag = ", eventTag) - elif len(eventTag)==2: - # Block is discovered and plunked into each edge's pendingBlock list. - - 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) - - 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) - # A block deterministically arrives at the end of an edge. - - assert eventTag[0]=="arrival" - shout += "ARRIVAL" - if self.verbose: - print(shout) - - eIdent = eventTag[1] - pendingIdent = eventTag[2] - e = self.state.edges[eIdent] - pB = e.data["pendingBlocks"] - arrivalInfo = pB[pendingIdent] # arrivalInfo = {"timeOfArrival":toa, "destIdent":mIdent, "block":newBlock} - - assert arrivalInfo["destIdent"] in self.state.nodes - assert self.t + u == arrivalInfo["timeOfArrival"] - receiver = self.state.nodes[arrivalInfo["destIdent"]] - arriv = self.t + u + receiver.data["offset"] - newBlock = arrivalInfo["block"] - newBlock.arrivTimestamp = copy.deepcopy(arriv) - receiver.updateBlockchain({newBlock.ident:newBlock}) - receiver.updateDifficulty(mode, targetRate) - 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) - - if self.verbose: - print("u = ", u) - self.t += u - if self.verbose: - print(str(self.t) + "\t" + shout) - - def record(self): - with open(self.filename, "a") as f: - line = "" - # Format will be edgeIdent,nodeAident,nodeBident - line += str("t=" + str(self.t) + ",") - ordKeyList = sorted(list(self.state.edges.keys())) - for key in ordKeyList: - entry = [] - entry.append(key) - nodeKeyList = sorted(list(self.state.edges[key].nodes)) - for kkey in nodeKeyList: - entry.append(kkey) - line += str(entry) + "," - f.write(line + "\n") - -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.1, "deathRate":0.1} - greg = FishGraph(params, verbosity=True) - greg.go() - - -suite = unittest.TestLoader().loadTestsFromTestCase(Test_FishGraph) -unittest.TextTestRunner(verbosity=1).run(suite) - - diff --git a/source-code/Poisson-Graphs/Graph.py b/source-code/Poisson-Graphs/Graph.py index ade6267..54e5645 100644 --- a/source-code/Poisson-Graphs/Graph.py +++ b/source-code/Poisson-Graphs/Graph.py @@ -1,101 +1,187 @@ - from Edge import * +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): ''' - Graph object. Contains some data, a dict of nodes, and a dict of edges. + Explanation ''' - def __init__(self, params={}, verbosity=True): - self.data=params - self.verbose = verbosity + 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] - 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. + self.blankBlockchain = Blockchain() + self.blankBlockchain.targetRate = self.data["targetRate"] + self.blankBlockchain.mode = self.data["mode"] - # First, include inputted information into self.data - self.data.update({"probEdge":probEdge, "maxNeighbors":maxNeighbors}) + self._createInit() - # 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 _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) - 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) + 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") + + + + + + + diff --git a/source-code/Poisson-Graphs/Node.py b/source-code/Poisson-Graphs/Node.py index 25447b2..be9e9d9 100644 --- a/source-code/Poisson-Graphs/Node.py +++ b/source-code/Poisson-Graphs/Node.py @@ -1,941 +1,123 @@ from Blockchain import * -import copy +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)==4 + assert len(params)==5 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 = {} + 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, diffUpdateRate=1, mode="Nakamoto", targetRate=1.0/1209600.0): + 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. - 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.") - + 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.data["blockchain"].whichLeaf() - #if len(self.data["blockchain"]) % diffUpdateRate == 0: - # self.updateDifficulty(mode, targetRate) + self.data["blockchain"].addBlock(incBlocks[key], self.mode, self.targetRate) del tempData[key] - incBlocks = copy.deepcopy(tempData) - - if self.verbose: - print("\t\t Now incBlocks has " + str(len(incBlocks)) + " entries.") - - if self.verbose: - print("\t\tRemaining steps (while loop)") - + incBlocks = deepcopy(tempData) 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[key].parent in self.data["blockchain"].blocks: - self.data["blockchain"].addBlock(incBlocks[key]) - self.data["blockchain"].whichLeaf() - del tempData[key] - incBlocks = copy.deepcopy(tempData) - if self.verbose: - print("\t\t Now incBlocks has " + str(len(incBlocks)) + " entries.") - + self.data["blockchain"].addBlock(incBlocks[key], self.mode, self.targetRate) + del tempData[key] + incBlocks = 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. - if self.verbose: - print("Beginning update of difficulty with Nakamoto method") - count = 2016 - bc = self.data["blockchain"] - if self.verbose: - print("Checking that blockchain is 2016*n blocks long and some mining identity has been set") - if len(bc.blocks) % 2016 == 0 and len(bc.miningIdents) > 0: - - ident = random.choice(bc.miningIdents) - 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 topTime != botTime - - # MLE estimate of arrivals per second: - mleDiscoRate = float(2015)/float(topTime - botTime) - - # Rates can't be negative, but this estimate could be (although it's highly unlikely given Bitcoin's standard choices - # of difficulty update rate, etc. - mleDiscoRate = abs(mleDiscoRate) - - if self.verbose: - print("MLE disco rate = " + str(mleDiscoRate) + " and targetRate = " + str(targetRate)) - # 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. - - if self.verbose: - print("MLE discovery rate = " + str(mleDiscoRate)) - print("Difficulty before adjustment = " + str(self.diff)) - - # Update difficulty multiplicatively - self.diff = self.diff*mleDiscoRate/targetRate - - if self.verbose: - print("Difficulty after adjustment = ", str(self.diff)) - - 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 - bc = self.data["blockchain"] - bc.whichLeaf() - assert self.diff != 0.0 - if len(bc.blocks) > 120: - assert type(bc.miningIdents)==type([]) - assert len(bc.miningIdents) > 0 - ident = random.choice(bc.miningIdents) - bl = [] - bl.append(copy.deepcopy(bc.blocks[ident].discoTimestamp)) - parent = bc.blocks[ident].parent - count = count - 1 - while count > 0 and parent is not None: - ident = copy.deepcopy(parent) - bl.append(copy.deepcopy(bc.blocks[ident].discoTimestamp)) - parent = bc.blocks[ident].parent - count = count-1 - # sort - bl = sorted(bl) - assert len(bl)<=1200 - - #print("Sample size = " + str(len(bl))) - # remove 10 and 90 %-iles - numOutliers = round(len(bl)/5)//2 - assert numOutliers <= 120 - #print("Number of outliers = " + str(numOutliers)) - if numOutliers > 0: - bl = bl[numOutliers:-numOutliers] - #print("New Sample Size = " + str(len(bl))) - - - # get topTime and botTime - if self.verbose: - print("bl[0] = " + str(bl[0]) + ",\tbl[-1] = " + str(bl[-1])) - topTime = bl[-1] - botTime = bl[0] - if self.verbose: - print("list of timestamps = " + str(bl)) - print("topTime = " + str(bl[-1])) - print("botTime = " + str(bl[0])) - - # Assert algebra will work - # 1200 - 2*120 = 1200 - 240 = 960 - assert 0 < len(bl) and len(bl) < 961 - assert topTime - botTime >= 0.0 - - # 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 - bc = self.data["blockchain"] - if len(bc.miningIdents) > 0: - ident = random.choice(bc.miningIdents) - - bl = [] - bl.append(copy.deepcopy(bc.blocks[ident].discoTimestamp)) - parent = bc.blocks[ident].parent - count = count - 1 - while count > 0 and parent is not None: - ident = copy.deepcopy(parent) - bl.append(copy.deepcopy(bc.blocks[ident].discoTimestamp)) - parent = bc.blocks[ident].parent - count = count-1 - if len(bl) > 120: - sk = skew(bl) - va = var(bl) - stdv = sqrt(va) - lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0) - else: - lam = targetRate # we will not change difficulty unless we have at least 120 blocks of data (arbitrarily selected) - self.diff = self.diff*(lam/targetRate) - else: - print("Error, invalid difficulty mode entered.") - - def propagate(self, t, blockIdent): + def propagate(self, timeOfProp, 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"] + 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"] - pendingDat = {"timeOfArrival":toa, "destIdent":mIdent, "block":mybc.blocks[blockIdent]} + 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_nakamoto(self): - print("Beginning test of Nakamoto difficulty adjustment") - print("Setting initial values") - target = 100.0 # rate = blocks/s - verbose = False - deltaT = 1.0/target # forced wait time - arrivalList = [] + def test_all(self): + bill = Blockchain([], verbosity=True) mode="Nakamoto" + tr = 1.0/600000.0 + deltaT = 600000.0 + bill.targetRate = tr - print("Generating node") - nellyIdent = newIdent(0) - offset = random.random() - intensity = random.random() - - print("Generating initial blockchain") - # Create a new initial blockchain object - bill = Blockchain([], verbosity=verbose) name = newIdent(0) - t = time.time() - t += offset - arrivalList.append(t) - s = t+random.random() - diff = 1.0 - oldDiff = copy.deepcopy(diff) - params = {"ident":name, "disco":t, "arriv":s, "parent":None, "diff":diff} - genesis = Block(params) - print("Adding block") - bill.addBlock(genesis) - bill.whichLeaf() - - # Check that it consists only of the genesis block - self.assertTrue(len(bill.blocks)==1) - self.assertTrue(genesis.ident in bill.blocks) - self.assertTrue(genesis.parent is None) - - print("Finish creating node") - # Create node with this blockchain. - nodeData = {"blockchain":bill, "intensity":intensity, "offset":offset} - params = {"ident":nellyIdent, "data":nodeData, "diff":diff, "verbose":verbose} - nelly = Node(params) - - # Check node creation worked - self.assertEqual(nelly.ident, nellyIdent) - self.assertEqual(nelly.data["blockchain"], bill) - self.assertEqual(nelly.diff, diff) - self.assertEqual(nelly.data["intensity"], intensity) - self.assertEqual(nelly.data["offset"], offset) - - # Sleep and add a block on top of genesis - if verbose: - print("sleeping") - time.sleep(deltaT) - - print("Giving genesis block a child") - name = newIdent(1) - t = time.time() - t += nelly.data["offset"] - arrivalList.append(t) - s = t+random.random() - diff = oldDiff - params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} - blockA = Block(params) - nelly.updateBlockchain({blockA.ident:blockA}) - oldIdent = blockA.ident - - # Check this worked - self.assertEqual(len(nelly.data["blockchain"].blocks),2) - self.assertTrue(blockA.ident in nelly.data["blockchain"].blocks) - self.assertTrue(genesis.ident in nelly.data["blockchain"].blocks) - self.assertEqual(genesis.ident, nelly.data["blockchain"].blocks[blockA.ident].parent) - - print("Updating difficulty") - # Update the difficulty score - nelly.updateDifficulty(mode, targetRate = target) # With only two blocks, nothing should change. - self.assertEqual(nelly.diff, oldDiff) - - # Print regardless of verbosity: - print("Now generating first difficulty adjustment period.") - - # Now we are going to fast forward to right before the first difficulty adjustment. - N = len(nelly.data["blockchain"].blocks) - while(N < 2015): - if N % 100 == 0: - print("\tN=" + str(N)) - name = newIdent(N) - t = time.time() - t += nelly.data["offset"] - arrivalList.append(t) - s = t+random.random() - diff = nelly.diff - oldDiff = diff - params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":diff} - oldIdent = copy.deepcopy(name) - block = Block(params) - nelly.updateBlockchain({block.ident:block}) - - # Check this worked - self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) - self.assertTrue(block.ident in nelly.data["blockchain"].blocks) - - # Update the difficulty score - nelly.updateDifficulty(mode, targetRate = target) # With N < 2016, nothing should change. - self.assertEqual(nelly.diff, oldDiff) - N = len(nelly.data["blockchain"].blocks) - time.sleep(deltaT) - - name = newIdent(N) - t = time.time() - t += nelly.data["offset"] - arrivalList.append(t) - s = t+random.random() - diff = oldDiff - params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":diff} - block = Block(params) - nelly.updateBlockchain({block.ident:block}) - - # Check this worked - self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) - self.assertTrue(block.ident in nelly.data["blockchain"].blocks) - - # Update the difficulty score - nelly.updateDifficulty(mode, targetRate = target) # With N < 2016, nothing should change. - # Note: 2016 blocks is 2015 block inter-arrival times. - expRatioNumerator = float(2015)/(arrivalList[-1] - arrivalList[-2016]) - expRatio = expRatioNumerator/target - expDiff = oldDiff*expRatio - self.assertEqual(nelly.diff, expDiff) - - # The following should fail, because our sample size is incorrect. - expRatioNumerator = float(2016)/(arrivalList[-1] - arrivalList[-2016]) - expRatio = expRatioNumerator/target - expDiff = oldDiff*expRatio - self.assertFalse(nelly.diff - expDiff == 0.0) - - - # Print regardless of verbosity: - print("Now generating second difficulty adjustment period.") - - # Now we are going to fast forward to right before the next difficulty adjustment. - # This time, though, we are going to re-set the block inter-arrival time deltaT - # to half. This should drive difficulty up. - lastDifficultyScore = copy.deepcopy(nelly.diff) - N = len(nelly.data["blockchain"].blocks) - while(N < 4031): - if N % 100 == 0: - print("\tN=" + str(N)) - name = newIdent(N) - t = time.time() - t += nelly.data["offset"] - arrivalList.append(t) - s = t+random.random() - diff = nelly.diff - oldDiff = diff - params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":diff} - oldIdent = copy.deepcopy(name) - block = Block(params) - nelly.updateBlockchain({block.ident:block}) - - # Check this worked - self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) - self.assertTrue(block.ident in nelly.data["blockchain"].blocks) - - # Update the difficulty score - nelly.updateDifficulty(mode, targetRate = target) # With N < 2016, nothing should change. - self.assertEqual(nelly.diff, oldDiff) - N = len(nelly.data["blockchain"].blocks) - time.sleep(0.01*deltaT) - - # Now if we add a single new block, we should trigger difficulty adjustment. - name = newIdent(N) - t = time.time() - t += nelly.data["offset"] - arrivalList.append(t) - s = t+random.random() - diff = oldDiff - params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":diff} - block = Block(params) - nelly.updateBlockchain({block.ident:block}) - - # Check this worked - self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) - self.assertTrue(block.ident in nelly.data["blockchain"].blocks) - - # Update the difficulty score. - nelly.updateDifficulty(mode, targetRate = target) - expRatioNumerator = float(2015)/(arrivalList[-1] - arrivalList[-2016]) - expRatio = expRatioNumerator/target - expDiff = oldDiff*expRatio - print("expRatio = " + str(expRatio) + ", lastDifficultyScore = " + str(lastDifficultyScore) + ", new difficulty = " + str(nelly.diff)) - self.assertEqual(nelly.diff, expDiff) - ''' - - - - - def test_vs(self): - print("Beginning test of vanSaberhagen difficulty adjustment.") - print("Setting initial values") - target = 10.0 # 1.0/240.0 # rate = blocks/s - verbose = False - deltaT = 1.0/target # forced wait time - arrivalList = [] - mode="vanSaberhagen" - - print("Instantiating new node") - nellyIdent = newIdent(0) - offset = random.random() - intensity = random.random() - - print("Creating new blockchain for new node") - # Create a new initial blockchain object - bill = Blockchain([], verbosity=verbose) - name = newIdent(0) - t = time.time() - t += offset - arrivalList.append(t) - s = t+random.random() + t = 0.0 + s = t diff = 1.0 params = {"ident":name, "disco":t, "arriv":s, "parent":None, "diff":diff} genesis = Block(params) - print("Adding genesis block") - bill.addBlock(genesis) - bill.whichLeaf() + bill.addBlock(genesis, mode, tr) - # Check that it consists only of the genesis block - self.assertTrue(len(bill.blocks)==1) - self.assertTrue(genesis.ident in bill.blocks) - self.assertTrue(genesis.parent is None) - self.assertTrue(genesis.ident in bill.leaves) + parent = genesis.ident - print("Making node") - # Create node with this blockchain. - nodeData = {"blockchain":bill, "intensity":intensity, "offset":offset} - params = {"ident":nellyIdent, "data":nodeData, "diff":diff, "verbose":verbose} + 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) - # Check node creation worked - self.assertEqual(nelly.ident, nellyIdent) - self.assertEqual(nelly.data["blockchain"], bill) - self.assertEqual(nelly.diff, diff) - self.assertEqual(nelly.data["intensity"], intensity) - self.assertEqual(nelly.data["offset"], offset) - - # Sleep and add a block on top of genesis - if verbose: - print("sleeping") - time.sleep(deltaT) - - print("Give genesis a child") - name = newIdent(1) - t = time.time() - t += nelly.data["offset"] - arrivalList.append(t) - s = t+random.random() - oldDiff = copy.deepcopy(diff) - diff = copy.deepcopy(nelly.diff) - assert diff != 0.0 - params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} - blockA = Block(params) - nelly.updateBlockchain({blockA.ident:blockA}) - oldIdent = blockA.ident - - # Check this worked - self.assertEqual(len(nelly.data["blockchain"].blocks),2) - self.assertTrue(blockA.ident in nelly.data["blockchain"].blocks) - self.assertTrue(genesis.ident in nelly.data["blockchain"].blocks) - self.assertEqual(genesis.ident, nelly.data["blockchain"].blocks[blockA.ident].parent) - - # Update the difficulty score - nelly.updateDifficulty(mode, targetRate = target) # With only two blocks, nothing should change. - assert nelly.diff != 0.0 - self.assertEqual(nelly.diff, oldDiff) - self.assertFalse(nelly.diff == -0.0) - - # Print regardless of verbosity: - print("Now generating fulls sample size.") - - # Now we are going to fast forward to a "full sample size" period of time. - N = len(nelly.data["blockchain"].blocks) - while(N < 1200): - name = newIdent(N) - if N % 100 == 0: - print("\tNow adding block N=" + str(N)) - t = time.time() - t += nelly.data["offset"] - arrivalList.append(t) - s = t+random.random() - oldDiff = copy.deepcopy(diff) - diff = copy.deepcopy(nelly.diff) - assert diff != 0.0 - params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":diff} - oldIdent = copy.deepcopy(name) - block = Block(params) - nelly.updateBlockchain({block.ident:block}) - - # Check this worked - self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) - self.assertTrue(block.ident in nelly.data["blockchain"].blocks) - - # Update the difficulty score - nelly.updateDifficulty(mode, targetRate = target) # With N < 100, nothing should change. - if N < 100: - self.assertEqual(nelly.diff, oldDiff) - N = len(nelly.data["blockchain"].blocks) - time.sleep(0.5*deltaT) - - print("Adding one more block") - name = newIdent(N) - t = time.time() - t += nelly.data["offset"] - arrivalList.append(t) - s = t+random.random() - oldDiff = diff - diff = nelly.diff - assert diff != 0.0 - params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":nelly.diff} - block = Block(params) - nelly.updateBlockchain({block.ident:block}) - - # Check this worked - self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) - self.assertTrue(block.ident in nelly.data["blockchain"].blocks) - - # Update the difficulty score - nelly.updateDifficulty(mode, targetRate = target) # With N < 2016, nothing should change. - # Note: 2016 blocks is 2015 block inter-arrival times. - print(str(arrivalList[-120]) + ", " + str(arrivalList[-1080]) + ", " + str(arrivalList[-120]-arrivalList[-1080]) + ", " + str(float(959)/(arrivalList[-120]-arrivalList[-1080]))+ ", " + str(float(float(959)/(arrivalList[-120]-arrivalList[-1080]))/float(target))) - expRatioNumerator = float(959)/(arrivalList[-120] - arrivalList[-1080]) - expRatio = expRatioNumerator/target - print(expRatio) - expDiff = oldDiff*expRatio - print(expDiff) - print("expDiff = " + str(expDiff) + " and nelly.diff = " + str(nelly.diff)) - self.assertEqual(nelly.diff, expDiff) - - - # Print regardless of verbosity: - print("Now fast forwarding past the tail end of the last period..") - # Now we are going to fast forward to right before the next difficulty adjustment. - # This time, though, we are going to re-set the block inter-arrival time deltaT - # to half. This should drive difficulty up. - lastDifficultyScore = copy.deepcopy(nelly.diff) - N = len(nelly.data["blockchain"].blocks) - while(N < 1700): - name = newIdent(N) - t = time.time() - t += nelly.data["offset"] - arrivalList.append(t) - s = t+random.random() - diff = nelly.diff - oldDiff = diff - params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":diff} - oldIdent = copy.deepcopy(name) - block = Block(params) - nelly.updateBlockchain({block.ident:block}) - - # Check this worked - self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) - self.assertTrue(block.ident in nelly.data["blockchain"].blocks) - - # Update the difficulty score - nelly.updateDifficulty(mode, targetRate = target) # With N < 2016, nothing should change. - self.assertEqual(nelly.diff, oldDiff) - N = len(nelly.data["blockchain"].blocks) - time.sleep(0.01*deltaT) - - # Now if we add a single new block, we should trigger difficulty adjustment. - name = newIdent(N) - t = time.time() - t += nelly.data["offset"] - arrivalList.append(t) - s = t+random.random() - diff = oldDiff - params = {"ident":name, "disco":t, "arriv":s, "parent":oldIdent, "diff":diff} - block = Block(params) - nelly.updateBlockchain({block.ident:block}) - - # Check this worked - self.assertEqual(len(nelly.data["blockchain"].blocks),N+1) - self.assertTrue(block.ident in nelly.data["blockchain"].blocks) - - # Update the difficulty score. - nelly.updateDifficulty(mode, targetRate = target) - expRatioNumerator = float(959)/(arrivalList[-120] - arrivalList[-1080]) - expRatio = expRatioNumerator/target - expDiff = oldDiff*expRatio - print("expRatio = " + str(expRatio) + ", lastDifficultyScore = " + str(lastDifficultyScore) + ", new difficulty = " + str(nelly.diff)) - self.assertEqual(nelly.diff, expDiff) - - - - def test_modexp(self): - pass - - '''# Check this worked - if mode == "Nakamoto": - # In this case we take simple MLE estimate - ratio = 1.0/abs(t1-t) - print("Nakamoto mle = " + str(ratio)) - ratio = ratio/target - print("Normalized = " + str(ratio)) - print("New diff = " + str(ratio*oldDiff)) - self.assertEqual(nelly.diff, ratio*oldDiff) - elif mode == "vanSaberhagen": - # In this case, with only 2 blocks, we just use simple MLE again - ratio = 1.0/abs(t1-t) - ratio = ratio/target - self.assertEqual(nelly.diff, ratio*oldDiff) - elif mode == "MOM:expModGauss": - self.assertEqual(nelly.diff, 1.0) - # With at least 120 blocks of data... - #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) - # Otherwise, set to 1.0 - else: - print("what world are you living in?") - - if verbose: - print("sleeping 1 seconds") - time.sleep(deltaT/5.0) - - listOfTimes = [copy.deepcopy(t), copy.deepcopy(t1)] - listOfBlocks = [] - - N = len(nelly.data["blockchain"].blocks) - lastIdent = blockA.ident - - bail = False - while N < 10 and not bail: - # Generate new block - name = newIdent(N) - t = time.time() - t += nelly.data["offset"] - s = t+random.random() - oldDiff = copy.deepcopy(nelly.diff) - print("Current difficulty = ", oldDiff) - params = {"ident":name, "disco":t, "arriv":s, "parent":lastIdent, "diff":oldDiff} + 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) - - # Append new block to running list along with creation time - listOfBlocks.append(newBlock) - listOfTimes.append(copy.deepcopy(t)) - - # Update nelly's blockchain with newBlock nelly.updateBlockchain({newBlock.ident:newBlock}) - lastIdent = name + parent = name - # Quick check that this worked: - self.assertTrue(name in nelly.data["blockchain"].blocks) - self.assertEqual(len(nelly.data["blockchain"].blocks), N+1) - N = len(nelly.data["blockchain"].blocks) - - # Update difficulty - nelly.updateDifficulty(mode, targetRate = 100.0) - # Quick check that this worked: - if mode == "Nakamoto": - # In this case we take use top block and genesis block - ratio = float(len(nelly.data["blockchain"].blocks) - 1)/(listOfTimes[-1] - listOfTimes[0]) - ratio = ratio / target - self.assertEqual(nelly.diff, ratio*oldDiff) - print("Hoped for difficulty = " + str(oldDiff*ratio) + ", and computed = " + str(nelly.diff)) - elif mode == "vanSaberhagen": - # This case coincides with nakamoto until block 10 - ratio = float( len(nelly.data["blockchain"].blocks) - 1)/(listOfTimes[-1] - listOfTimes[0]) - ratio = ratio / target - self.assertEqual(nelly.diff, ratio*oldDiff) - elif mode == "MOM:expModGauss": - self.assertEqual(nelly.diff, 1.0) - # With at least 120 blocks of data... - #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) - # Otherwise, set to 1.0 - else: - print("what world are you living in?") - - # Sleep a random time - print("Sleeping a random sub-second, working on block " + str(N)) - deltaT = deltaT*ratio - time.sleep(deltaT/5.0) - - while N < 120 and not bail: - # Generate new block - name = newIdent(N) - t = time.time() - t += nelly.data["offset"] - s = t+random.random() - oldDiff = copy.deepcopy(nelly.diff) - params = {"ident":name, "disco":t, "arriv":s, "parent":lastIdent, "diff":oldDiff} + 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) - - # Append new block to running list along with creation time - listOfBlocks.append(newBlock) - listOfTimes.append(copy.deepcopy(t)) - - # Update nelly's blockchain with newBlock nelly.updateBlockchain({newBlock.ident:newBlock}) - lastIdent = name - - # Quick check that this worked: - self.assertTrue(name in nelly.data["blockchain"].blocks) - self.assertEqual(len(nelly.data["blockchain"].blocks), N+1) - N = len(nelly.data["blockchain"].blocks) + parent = name - # Update difficulty - nelly.updateDifficulty(mode, targetRate = 100.0) - - # Quick check that this worked: - if mode == "Nakamoto": - # In this case we take use top block and genesis block - ratio = float(len(nelly.data["blockchain"].blocks)-1)/(listOfTimes[-1] - listOfTimes[0]) - ratio = ratio / target - self.assertEqual(nelly.diff, oldDiff*ratio) - print("Hoped for difficulty = " + str(oldDiff*ratio) + ", and computed = " + str(nelly.diff)) - elif mode == "vanSaberhagen": - # This case no longer coincides with Nakamoto... - numOutliers = len(nelly.data["blockchain"].blocks)//10 - numOutliers = min(numOutliers, 120) - ratio = float(len(nelly.data["blockchain"].blocks) - 2*numOutliers - 1)/(listOfTimes[-numOutliers] - listOfTimes[numOutliers]) - ratio = ratio / target - self.assertEqual(nelly.diff, oldDiff*ratio) - elif mode == "MOM:expModGauss": - # With at least 120 blocks of data... - count = 1200 - bl = [] - bl.append(copy.deepcopy(bc.blocks[lastIdent].discoTimestamp)) - parent = bc.blocks[lastIdent].parent - count = count - 1 - while count > 0 and parent is not None: - ident = copy.deepcopy(parent) - bl.append(copy.deepcopy(bc.blocks[ident].discoTimestamp)) - parent = bc.blocks[ident].parent - count = count-1 - if len(bl) > 120: - sk = skew(bl) - va = var(bl) - stdv = sqrt(va) - lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0) - else: - lam = target - ratio = lam/target - self.assertEqual(nelly.diff, oldDiff*ratio) - - else: - print("what world are you living in?") - - # Sleep a random time - print("Sleeping a random sub-second, working on block " + str(N)) - deltaT = deltaT*ratio - time.sleep(deltaT/5.0) - - while N < 2400 and not bail: - # Generate new block - name = newIdent(N) - t = time.time() - t += nelly.data["offset"] - s = t+random.random() - oldDiff = copy.deepcopy(nelly.diff) - params = {"ident":name, "disco":t, "arriv":s, "parent":lastIdent, "diff":oldDiff} - newBlock = Block(params) - - # Append new block to running list along with creation time - listOfBlocks.append(newBlock) - listOfTimes.append(copy.deepcopy(t)) - - # Update nelly's blockchain with newBlock - nelly.updateBlockchain({newBlock.ident:newBlock}) - lastIdent = name - - # Quick check that this worked: - self.assertTrue(name in nelly.data["blockchain"].blocks) - self.assertEqual(len(nelly.data["blockchain"].blocks), N+1) - N = len(nelly.data["blockchain"].blocks) - - # Update difficulty - nelly.updateDifficulty(mode, targetRate = 100.0) - - # Quick check that this worked: - if mode == "Nakamoto": - # In this case we take use top block and genesis block - ratio = float(len(nelly.data["blockchain"].blocks)-1)/(listOfTimes[-1] - listOfTimes[0]) - ratio = ratio / target - self.assertEqual(nelly.diff, oldDiff*ratio) - print("Hoped for difficulty = " + str(oldDiff*ratio) + ", and computed = " + str(nelly.diff)) - elif mode == "vanSaberhagen": - # This case no longer coincides with Nakamoto... - numOutliers = len(nelly.data["blockchain"].blocks)//10 - numOutliers = min(numOutliers, 120) - ratio = float(len(nelly.data["blockchain"].blocks) - 2*numOutliers - 1)/(listOfTimes[-numOutliers] - listOfTimes[numOutliers]) - ratio = ratio / target - self.assertEqual(nelly.diff, ratio*oldDiff) - elif mode == "MOM:expModGauss": - # With at least 120 blocks of data... - count = 1200 - bl = [] - bl.append(copy.deepcopy(bc.blocks[lastIdent].discoTimestamp)) - parent = bc.blocks[lastIdent].parent - count = count - 1 - while count > 0 and parent is not None: - ident = copy.deepcopy(parent) - bl.append(copy.deepcopy(bc.blocks[ident].discoTimestamp)) - parent = bc.blocks[ident].parent - count = count-1 - if len(bl) > 120: - sk = skew(bl) - va = var(bl) - stdv = sqrt(va) - lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0) - else: - lam = targetRate - ratio = lam/targetRate - self.assertEqual(nelly.diff, ratio*oldDiff) - - else: - print("what world are you living in?") - - # Sleep a random time - print("Sleeping a random sub-second, working on block " + str(N)) - deltaT = deltaT*ratio - time.sleep(deltaT/5.0) - - - while N < 3600 and not bail: - # Generate new block - name = newIdent(N) - t = time.time() - t += nelly.data["offset"] - s = t+random.random() - oldDiff = nelly.diff - params = {"ident":name, "disco":t, "arriv":s, "parent":lastIdent, "diff":oldDiff} - newBlock = Block(params) - - # Append new block to running list along with creation time - listOfBlocks.append(newBlock) - listOfTimes.append(copy.deepcopy(t)) - - # Update nelly's blockchain with newBlock - nelly.updateBlockchain({newBlock.ident:newBlock}) - lastIdent = name - - # Quick check that this worked: - self.assertTrue(name in nelly.data["blockchain"].blocks) - self.assertEqual(len(nelly.data["blockchain"].blocks), N+1) - N = len(nelly.data["blockchain"].blocks) - - # Update difficulty - nelly.updateDifficulty(mode, targetRate = 100.0) - - # Quick check that this worked: - if mode == "Nakamoto": - # In this case we take use top block and genesis block - ratio = float(2400)/(listOfTimes[-1] - listOfTimes[-2400]) - self.assertEqual(nelly.diff, ratio*oldDiff) - elif mode == "vanSaberhagen": - # This case no longer coincides with Nakamoto... - numOutliers = len(nelly.data["blockchain"].blocks)//10 - numOutliers = min(numOutliers, 120) - ratio = float(len(nelly.data["blockchain"].blocks) - 2*numOutliers)/(listOfTimes[-numOutliers] - listOfTimes[numOutliers]) - self.assertEqual(nelly.diff, ratio*oldDiff) - elif mode == "MOM:expModGauss": - # With at least 120 blocks of data... - count = 1200 - bl = [] - bl.append(copy.deepcopy(bc.blocks[lastIdent].discoTimestamp)) - parent = bc.blocks[lastIdent].parent - count = count - 1 - while count > 0 and parent is not None: - ident = copy.deepcopy(parent) - bl.append(copy.deepcopy(bc.blocks[ident].discoTimestamp)) - parent = bc.blocks[ident].parent - count = count-1 - if len(bl) > 120: - sk = skew(bl) - va = var(bl) - stdv = sqrt(va) - lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0) - else: - lam = targetRate - ratio = lam/targetRate - self.assertEqual(nelly.diff, ratio*oldDiff) - - else: - print("what world are you living in?") - - # Sleep a random time - print("Sleeping a random sub-second, working on block " + str(N)) - deltaT = deltaT*ratio - time.sleep(deltaT/5.0)''' - - -suite = unittest.TestLoader().loadTestsFromTestCase(Test_Node) -unittest.TextTestRunner(verbosity=1).run(suite) +#suite = unittest.TestLoader().loadTestsFromTestCase(Test_Node) +#unittest.TextTestRunner(verbosity=1).run(suite) diff --git a/source-code/Poisson-Graphs/StochasticProcess.py b/source-code/Poisson-Graphs/StochasticProcess.py deleted file mode 100644 index e6739de..0000000 --- a/source-code/Poisson-Graphs/StochasticProcess.py +++ /dev/null @@ -1,53 +0,0 @@ -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) - - diff --git a/source-code/Poisson-Graphs/new/Block.py b/source-code/Poisson-Graphs/new/Block.py deleted file mode 100644 index dece697..0000000 --- a/source-code/Poisson-Graphs/new/Block.py +++ /dev/null @@ -1,46 +0,0 @@ -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) diff --git a/source-code/Poisson-Graphs/new/Block.py~ b/source-code/Poisson-Graphs/new/Block.py~ deleted file mode 100644 index dece697..0000000 --- a/source-code/Poisson-Graphs/new/Block.py~ +++ /dev/null @@ -1,46 +0,0 @@ -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) diff --git a/source-code/Poisson-Graphs/new/Blockchain.py b/source-code/Poisson-Graphs/new/Blockchain.py deleted file mode 100644 index 97a3ba2..0000000 --- a/source-code/Poisson-Graphs/new/Blockchain.py +++ /dev/null @@ -1,1142 +0,0 @@ -from Block import * -import math -from scipy.stats import * -from numpy import * -from copy import deepcopy - -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.mIdent = None - self.verbose = verbosity - self.diff = None - self.targetRate = None - - def addBlock(self, blockToAdd, mode="Nakamoto", targetRate=1.0/600000.0): - # 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). - assert blockToAdd.ident not in self.blocks - if len(self.blocks)==0: - # In this case, blockToAdd is a genesis block, so we set difficulty - self.diff = deepcopy(blockToAdd.diff) - - 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() - return self.computeDifficulty(mode, targetRate) - - 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)) - assert len(self.miningIdents) > 0 - self.mIdent = random.choice(self.miningIdents) - - - # 1 block in 6*10^5 milliseconds=10min - def computeDifficulty(self, mode="Nakamoto", targetRate=1.0/600000.0): - result = None - if mode=="Nakamoto": - # Use MLE estimate of poisson process, compare to targetRate, update by multiplying by resulting ratio. - #if self.verbose: - # print("Beginning update of difficulty with Nakamoto method") - count = 2016 - #if self.verbose: - # print("Checking that blockchain is 2016*n blocks long and some mining identity has been set") - if len(self.blocks) % 2016 == 0 and len(self.miningIdents) > 0: - ident = self.mIdent - topTime = deepcopy(self.blocks[ident].discoTimestamp) - parent = self.blocks[ident].parent - count = count - 1 - touched = False - while count > 0 and parent is not None: - ident = deepcopy(parent) - parent = self.blocks[ident].parent - count = count - 1 - touched = True - if not touched: - mleDiscoRate = targetRate - else: - botTime = deepcopy(self.blocks[ident].discoTimestamp) - - # Algebra is okay: - assert topTime != botTime - - # MLE estimate of arrivals per second: - mleDiscoRate = float(2015)/float(topTime - botTime) - - # Rates can't be negative, but this estimate could be (although it's highly unlikely given Bitcoin's standard choices - # of difficulty update rate, etc. - mleDiscoRate = abs(mleDiscoRate) - - if self.verbose: - print("MLE disco rate = " + str(mleDiscoRate) + " and targetRate = " + str(targetRate)) - # 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. - - if self.verbose: - print("MLE discovery rate = " + str(mleDiscoRate)) - print("Difficulty before adjustment = " + str(self.diff)) - - # Update difficulty multiplicatively - self.diff = self.diff*mleDiscoRate/targetRate - - if self.verbose: - print("Difficulty after adjustment = ", str(self.diff)) - - 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 - #print(self.diff) - assert self.diff != 0.0 - if len(self.blocks) > 120 and len(self.miningIdents) > 0: - ident = self.mIdent - bl = [] - bl.append(deepcopy(self.blocks[ident].discoTimestamp)) - parent = self.blocks[ident].parent - count = count - 1 - while count > 0 and parent is not None: - ident = deepcopy(parent) - bl.append(deepcopy(self.blocks[ident].discoTimestamp)) - parent = self.blocks[ident].parent - count = count-1 - # sort - bl = sorted(bl) - assert len(bl)<=1200 - - #print("Sample size = " + str(len(bl))) - # remove 10 and 90 %-iles - numOutliers = math.ceil(float(len(bl))/float(10)) - assert numOutliers <= 120 - #print("Number of outliers = " + str(numOutliers)) - oldBL = deepcopy(bl) - if numOutliers > 0: - bl = bl[numOutliers:-numOutliers] - #if numOutliers == 120: - # print("\n\nSORTED TS LIST = " + str(oldBL) + "\nModified list = " + str(bl)) - - - # get topTime and botTime - #if self.verbose: - # print("bl[0] = " + str(bl[0]) + ",\tbl[-1] = " + str(bl[-1])) - topTime = bl[-1] - botTime = bl[0] - result = [float(topTime - botTime)] - #print(topTime - botTime) - #if self.verbose: - # print("list of timestamps = " + str(bl)) - # print("topTime = " + str(bl[-1])) - # print("botTime = " + str(bl[0])) - - # Assert algebra will work - # 1200 - 2*120 = 1200 - 240 = 960 - assert 0 < len(bl) and len(bl) < 961 - assert topTime - botTime >= 0.0 - result.append(len(bl)-1) - # 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 - if len(bl)==0: - print("WOOP WOOP NO TIMESTAMPS WTF? We have " + str(len(self.blocks)) + " blocks available, and we are counting " + str(2*numOutliers) + " as outliers. bl = " + str(bl)) - naiveDiscoRate = float(len(bl)-1)/float(topTime - botTime) - - # How much should difficulty change? - assert naiveDiscoRate != 0.0 - assert targetRate != 0.0 - assert self.diff != 0.0 - 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 - - # Really a trash metric unless sample sizes are huge. - count = 1200 - ident = self.mIdent - bl = [] - bl.append(deepcopy(self.blocks[ident].discoTimestamp)) - parent = self.blocks[ident].parent - count = count - 1 - while count > 0 and parent is not None: - ident = deepcopy(parent) - bl.append(deepcopy(self.blocks[ident].discoTimestamp)) - parent = self.blocks[ident].parent - count = count-1 - if len(bl) > 120: - sk = abs(skew(bl)) - va = var(bl) - stdv = sqrt(va) - lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0) - else: - lam = targetRate # we will not change difficulty unless we have at least 120 blocks of data (arbitrarily selected) - self.diff = self.diff*(lam/targetRate) - elif mode=="reciprocalOfMedian": - # In this mode we use a bitcoin-style metric except instead of 1/average inter-arrival time - # we use 1/median magnitude of inter-arrival time. - # And updated each block like with monero instead of every 2016 blocks like bitcoin. - # We assume a sample size of only 600 blocks for now - count = 600 - interArrivals = [] - if len(self.blocks) < count: - estDiscoRate = targetRate - elif len(self.miningIdents) > 0: - ident = self.mIdent - parent = self.blocks[ident].parent - if parent is not None: - dT = abs(self.blocks[ident].discoTimestamp - self.blocks[parent].discoTimestamp) - interArrivals.append(dT) - count = count - 1 - touched = False - while count > 0 and parent is not None: - ident = deepcopy(parent) - parent = self.blocks[ident].parent - if parent is not None: - dT = abs(self.blocks[ident].discoTimestamp - self.blocks[parent].discoTimestamp) - interArrivals.append(dT) - count = count - 1 - touched = True - if not touched: - estDiscoRate = targetRate - else: - estDiscoRate = 1.0/median(interArrivals) - if self.verbose: - print("Est disco rate = " + str(estDiscoRate) + " and targetRate = " + str(targetRate)) - - - if self.verbose: - print("MLE discovery rate = " + str(estDiscoRate)) - print("Difficulty before adjustment = " + str(self.diff)) - - # Update difficulty multiplicatively - self.diff = self.diff*estDiscoRate/targetRate - - if self.verbose: - print("Difficulty after adjustment = ", str(self.diff)) - else: - print("Error, invalid difficulty mode entered.") - return result - -class Test_Blockchain(unittest.TestCase): - def test_addBlock(self): - bill = Blockchain([], verbosity=True) - mode="Nakamoto" - tr = 1.0/100.0 - bill.targetRate = tr - - name = newIdent(0) - t = time.time() - s = t+random.random() - 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,s) - self.assertTrue(genesis.parent is None) - self.assertEqual(genesis.diff,diff) - - bill.addBlock(genesis, mode, tr) - - 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]) - self.assertEqual(len(bill.blocks),1) - - name = newIdent(1) - t = time.time() - s = t+random.random() - diff = 1.0 - params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} - blockA = Block(params) - bill.addBlock(blockA, mode, tr) - - self.assertTrue(blockA.ident in bill.blocks) - self.assertTrue(blockA.ident in bill.leaves) - self.assertTrue(genesis.ident not in bill.leaves) - self.assertEqual(len(bill.miningIdents),1) - self.assertEqual(blockA.ident, bill.miningIdents[0]) - self.assertEqual(len(bill.blocks),2) - - - - - - bill = Blockchain([], verbosity=True) - mode="vanSaberhagen" - tr = 1.0/100.0 - bill.targetRate = tr - - name = newIdent(0) - t = time.time() - s = t+random.random() - 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,s) - self.assertTrue(genesis.parent is None) - self.assertEqual(genesis.diff,diff) - - bill.addBlock(genesis, mode, tr) - - 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]) - self.assertEqual(len(bill.blocks),1) - - name = newIdent(1) - t = time.time() - s = t+random.random() - diff = 1.0 - params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} - blockA = Block(params) - bill.addBlock(blockA, mode, tr) - - self.assertTrue(blockA.ident in bill.blocks) - self.assertTrue(blockA.ident in bill.leaves) - self.assertTrue(genesis.ident not in bill.leaves) - self.assertEqual(len(bill.miningIdents),1) - self.assertEqual(blockA.ident, bill.miningIdents[0]) - self.assertEqual(len(bill.blocks),2) - - - - - - bill = Blockchain([], verbosity=True) - mode="MOM:expModGauss" - tr = 1.0/100.0 - bill.targetRate = tr - - name = newIdent(0) - t = time.time() - s = t+random.random() - 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,s) - self.assertTrue(genesis.parent is None) - self.assertEqual(genesis.diff,diff) - - bill.addBlock(genesis, mode, tr) - - 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]) - self.assertEqual(len(bill.blocks),1) - - name = newIdent(1) - t = time.time() - s = t+random.random() - diff = 1.0 - params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} - blockA = Block(params) - bill.addBlock(blockA, mode, tr) - - self.assertTrue(blockA.ident in bill.blocks) - self.assertTrue(blockA.ident in bill.leaves) - self.assertTrue(genesis.ident not in bill.leaves) - self.assertEqual(len(bill.miningIdents),1) - self.assertEqual(blockA.ident, bill.miningIdents[0]) - self.assertEqual(len(bill.blocks),2) - - - def test_bc(self): - bill = Blockchain([], verbosity=True) - mode="Nakamoto" - tr = 1.0/100.0 - bill.targetRate = tr - - 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, mode, tr) - - 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, mode, tr) - - #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, mode, tr) - - 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, mode, tr) - - 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) - ''' - def test_median(self): - # TODO: everything. - mode = "reciprocalOfMedian" - tr = 1.0 # one block per millisecond why not - deltaT = 1.0 # let's just make this easy - bill = Blockchain([], verbosity=True) - bill.targetRate = tr - - with open("outputM.txt", "w") as writeFile: - # We will send (t, a, diff) to writeFile. - writeFile.write("time,rateConstant,difficulty\n") - name = newIdent(0) - t = 0.0 - s = 0.0 - 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,s) - self.assertTrue(genesis.parent is None) - self.assertEqual(genesis.diff,diff) - - bill.addBlock(genesis, mode, tr) - a = 1.0 - b = 1.0/a - - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - - 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]) - - parent = genesis.ident - oldDiff = bill.diff - - while len(bill.blocks)<601: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - self.assertEqual(bill.diff, oldDiff) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - parent = newName - oldDiff = bill.diff - - a = 1.01 # slightly slower blocks, median won't change until half the data is corrupted! - b = 1.0/a - while len(bill.blocks)<899: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - self.assertEqual(bill.diff, oldDiff) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - self.assertEqual(bill.diff, oldDiff) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - parent = newName - oldDiff = bill.diff - - # One more block and our median inter-arrival time is deltaT*(1.0+a)/2.0 - # and so estRate = 1/median = (2.0/(1.0+a))/deltaT, whereas before it was just - # 1/deltaT. So estRate/targetRate = 2.0/(1.0+a) - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - self.assertEqual(bill.diff, oldDiff) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - err = bill.diff - oldDiff*2.0/(1.0+a) - self.assertTrue(err*err < 10**-15) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - parent = newName - oldDiff = bill.diff - - # One more block and our median inter-arrival time is deltaT*a - # and so estRate = 1/median = (1.0/a)/deltaT, whereas before it was just - # 1/deltaT. So estRate/targetRate = 1.0/a = b - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - self.assertEqual(bill.diff, oldDiff) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - err = bill.diff - oldDiff*b - self.assertTrue(err*err < 10**-15) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - parent = newName - oldDiff = bill.diff - - # Note that until the median changes again, this estimated block arrival rate - # does not change. This may be true even if a lot of new data has come in. - # It is possible that the same pair of blocks remain the median inter-arrival - # magnitude for the entire time both blocks are in the sample size. - # During this period of time, difficulty will update multiplicatively, so - # will either exponentially grow or shrink. - # In other words, this model can be looked at as: exponential change over - # time with a rate proportional to the deviation between the median and - # the target inter-arrival rates. - - - - - - def test_mine(self): - # TODO: everything. - mode = "MOM:expModGauss" - tr = 1.0/120000.0 # one block per two minutes - deltaT = 120000.0 - bill = Blockchain([], verbosity=True) - bill.targetRate = tr - - with open("outputM.txt", "w") as writeFile: - # We will send (t, a, diff, ratio, awayFromOne) to writeFile. - writeFile.write("time,rateConstant,difficulty\n") - name = newIdent(0) - t = 0.0 - s = 0.0 - 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,s) - self.assertTrue(genesis.parent is None) - self.assertEqual(genesis.diff,diff) - - bill.addBlock(genesis, mode, tr) - a = 1.0 - b = 1.0/a - - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - - 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]) - - parent = genesis.ident - oldDiff = bill.diff - - while len(bill.blocks)<120: - # Our metric divides by skewness. In reality, this is zero with - # probability zero. But for our tests, it's assured. So we - # will perturb each arrival by a small, up-to-half-percent - # variation to ensure a nonzero skewness without altering things - # too much. - newName = newIdent(len(bill.blocks)) - t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) - s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) - self.assertEqual(bill.diff, oldDiff) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - parent = newName - oldDiff = bill.diff - - # Just one more block and difficulty should be computed for the first time. - print("Just one more block and difficulty should be computed for the first time.") - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) - s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - parent = newName - #self.assertEqual(bill.diff, oldDiff) - - oldDiff = bill.diff - - # what if we add a bunch of blocks this way? - # In the case of a static hash rate, I suppose we hope to not - # vary too far from a multiplicative factor of 1.0, or rather - # a constant difficulty. - - while len(bill.blocks)<200: - # Our metric divides by skewness. In reality, this is zero with - # probability zero. But for our tests, it's assured. So we - # will perturb each arrival by a small, up-to-half-percent - # variation to ensure a nonzero skewness without altering things - # too much. - newName = newIdent(len(bill.blocks)) - t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) - s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) - self.assertEqual(bill.diff, oldDiff) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - parent = newName - oldDiff = bill.diff - - - - def test_vs(self): - # TODO: Still must test that outliers are being removed "appropriately" according to specifications - # TODO: Test that scrambled lists of timestamps produce the same difficulty estimate. - # TODO: Show that in the case of homogeneous poisson processes, unusual estimates are a little - # more common than in the Nakamoto difficulty (which must be the case because Nakamoto uses - # the UMVUE). - mode = "vanSaberhagen" - tr = 1.0/60000.0 # one block per minute - deltaT = 60000.0 - bill = Blockchain([], verbosity=True) - bill.targetRate = tr - - with open("output.txt", "w") as writeFile: - # We will send (t, a, diff, ratio, awayFromOne) to writeFile. - writeFile.write("time,rateConstant,difficulty,ratio\n") - name = newIdent(0) - t = 0.0 - s = 0.0 - 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,s) - self.assertTrue(genesis.parent is None) - self.assertEqual(genesis.diff,diff) - - bill.addBlock(genesis, mode, tr) - writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") - - 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]) - self.assertEqual(bill.diff, 1.0) - - parent = genesis.ident - oldDiff = bill.diff - a = 1.0 - b = 1.0/a - - while len(bill.blocks)<120: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - self.assertEqual(bill.diff, oldDiff) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - - writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") - parent = newName - oldDiff = bill.diff - - # Just one more block and difficulty should be computed for the first time. - print("Just one more block and difficulty should be computed for the first time.") - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") - parent = newName - self.assertEqual(bill.diff, oldDiff) - - oldDiff = bill.diff - - print("Let's add more blocks at the same rate.") - a = 1.0 - b = 1.0/a - - while len(bill.blocks)<1200: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") - parent = newName - self.assertEqual(bill.diff, oldDiff) - oldDiff = bill.diff - - print("Let's add more blocks at a slower rate.") - a = 1.1 - b = 1.0/a - - # If blocks arrive slightly further apart, difficulty should drop. - # However, since vanSaberhagen discards top 10% and bottom 10% of - # timestamps, it will take 120 blocks for this change to register - # in difficulty. - print("If blocks arrive slightly further apart, difficulty should drop. However, since vanSaberhagen discards top 10% and bottom 10% of timestamps, it will take 120 blocks for this change to register in difficulty.") - while len(bill.blocks)<1320: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") - parent = newName - self.assertEqual(bill.diff, oldDiff) - oldDiff = bill.diff - - print("One more block and difficulty should register a change.") - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") - parent = newName - self.assertTrue(bill.diff < oldDiff) - oldDiff = bill.diff - - # Let's add another fifty blocks at this same rate and verify that difficulty continually - # drops. - print("Let's add another fifty blocks at this same rate and verify that difficulty continually drops.") - a = 1.1 - b = 1.0/a - - while len(bill.blocks)<1370: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") - parent = newName - self.assertTrue(bill.diff < oldDiff) - oldDiff = bill.diff - - # Now we go back to the target rate. We have 170 slow blocks in the queue and 50 in the sample size. Difficulty will continue to drop for another 120 blocks... - print("Now we go back to the target rate. We have 170 slow blocks in the queue and 50 in the sample size. Difficulty will continue to drop for another 120 blocks...") - a = 1.0 - b = 1.0/a - - while len(bill.blocks)<1490: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") - parent = newName - self.assertTrue(bill.diff < oldDiff) - oldRatio = bill.diff/oldDiff - oldDiff = bill.diff - #print(str(result) + ", " + str(bill.diff) + ", " + str(oldDiff)) - - # Now all 170 slow blocks are not only in the queue but in our sample. The *multiplicative factor* between timesteps should be identical for the next 790 blocks.. leading to AN EXPONENTIAL DECAY OF DIFFICULTY. - print("Now all 170 slow blocks are not only in the queue but in our sample. The *multiplicative factor* between timesteps should be identical for the next 790 blocks.. leading to AN EXPONENTIAL DECAY OF DIFFICULTY.") - a = 1.0 - b = 1.0/a - while len(bill.blocks)<2279: - newName = newIdent(len(bill.blocks)) - t += deltaT - s += deltaT - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") - ratio = bill.diff/oldDiff - parent = newName - err = ratio - oldRatio - #print("Difference between last ratio and next ratio:" + str(err)) - self.assertTrue(err*err < 10**-15) - oldDiff = bill.diff - oldRatio = ratio - - print("Now adding a single new block will cause our 170 slow blocks to start dropping out of our sample, so the ratio should start returning to 1.0.") - oldAwayFromOne = abs(oldRatio - 1.0) # Ratio should be returning to 1.0 so this difference should go to zero - oldAwayFromOne = oldAwayFromOne*oldAwayFromOne - - # For the next 170 blocks as our perturbed blocks drop out of our sample, our - # estimated block arrival rate will return to "normal" so the multiplicative - # difference in difficulty should return to 1.0. - print("For the next 170 blocks as our perturbed blocks drop out of our sample, ourestimated block arrival rate will return to normal so the multiplicative difference in difficulty should return to 1.0.") - a = 1.0 - b = 1.0/a - while len(bill.blocks)<2449: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") - ratio = bill.diff/oldDiff - #print("New ratio = " + str(ratio) + " and oldRatio = " + str(oldRatio)) - self.assertTrue(ratio > oldRatio) - awayFromOne = abs(ratio - 1.0) # Ratio should be returning to 1.0 so this difference should go to zero - awayFromOne = awayFromOne*awayFromOne - self.assertTrue(awayFromOne < oldAwayFromOne) # This return will be monotonic in our manufactured example. - parent = newName - oldDiff = bill.diff - oldRatio = ratio - oldAwayFromOne = awayFromOne - - - # Now difficulty should remain frozen for as long as we like. - - a = 1.0 - b = 1.0/a - while len(bill.blocks)<2500: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") - parent = newName - self.assertEqual(bill.diff, oldDiff) - oldDiff = bill.diff - - - - - def test_nak(self): - # Since Nakamoto difficulty is derived from the MLE of the block arrival rate, - # we already know how it "should" behave in a poisson process, etc. - # TODO: Generate N samples of MLEs of Poisson rates compared to known homog. - # poisson rate, show that the resulting code does not result in unusual measurements - # more often than expected. - mode = "Nakamoto" - tr = 1.0/600000.0 - deltaT = 600000.0 - bill = Blockchain([], verbosity=True) - bill.targetRate = tr - # Bitcoin updating at 1 block per 10 minutes - - name = newIdent(0) - t = 0.0 - s = 0.0 - 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,s) - self.assertTrue(genesis.parent is None) - self.assertEqual(genesis.diff,diff) - - bill.addBlock(genesis, mode, tr) - - 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]) - self.assertEqual(bill.diff, 1.0) - - parent = genesis.ident - oldDiff = bill.diff - i = 1 - - while len(bill.blocks)<2016*i-1: - newName = newIdent(len(bill.blocks)) - t += deltaT - s += deltaT - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - parent = newName - - # Just one more block and difficulty should recompute. - print("Just one more block and difficulty should recompute.") - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT - s += deltaT - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - parent = newName - self.assertEqual(bill.diff, oldDiff) - - oldDiff = bill.diff - i += 1 - - while len(bill.blocks)<2016*i-1: - newName = newIdent(len(bill.blocks)) - t += deltaT - s += deltaT - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - parent = newName - - # Just one more block and difficulty should recompute. - print("Just one more block and difficulty should again recompute.") - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT - s += deltaT - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - parent = newName - self.assertEqual(bill.diff, oldDiff) - - oldDiff = bill.diff - i += 1 - a = 1.1 - b = 1.0/a - - # If blocks arrive slightly further apart, difficulty should drop. - while len(bill.blocks)<2016*i-1: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - parent = newName - - print("Just one more block and difficulty will go down.") - - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - parent = newName - err = abs(bill.diff - oldDiff*b) - self.assertTrue(err*err < 10**-15) - oldDiff = bill.diff - i += 1 - - - # If blocks then arrive on target, difficulty should freeze. - a = 1.0 - b = 1.0/a - while len(bill.blocks)<2016*i-1: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - parent = newName - - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - parent = newName - self.assertEqual(bill.diff, oldDiff) - oldDiff = bill.diff - i += 1 - - # If blocks arrive too close together, difficulty should increase. - a = 0.9 - b = 1.0/a - while len(bill.blocks)<2016*i-1: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - parent = newName - - print("Just one more block and difficulty should go up.") - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - parent = newName - err = abs(bill.diff - oldDiff*b) - self.assertTrue(err*err < 10**-15) - ''' - - - - -suite = unittest.TestLoader().loadTestsFromTestCase(Test_Blockchain) -unittest.TextTestRunner(verbosity=1).run(suite) diff --git a/source-code/Poisson-Graphs/new/Blockchain.py~ b/source-code/Poisson-Graphs/new/Blockchain.py~ deleted file mode 100644 index 97a3ba2..0000000 --- a/source-code/Poisson-Graphs/new/Blockchain.py~ +++ /dev/null @@ -1,1142 +0,0 @@ -from Block import * -import math -from scipy.stats import * -from numpy import * -from copy import deepcopy - -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.mIdent = None - self.verbose = verbosity - self.diff = None - self.targetRate = None - - def addBlock(self, blockToAdd, mode="Nakamoto", targetRate=1.0/600000.0): - # 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). - assert blockToAdd.ident not in self.blocks - if len(self.blocks)==0: - # In this case, blockToAdd is a genesis block, so we set difficulty - self.diff = deepcopy(blockToAdd.diff) - - 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() - return self.computeDifficulty(mode, targetRate) - - 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)) - assert len(self.miningIdents) > 0 - self.mIdent = random.choice(self.miningIdents) - - - # 1 block in 6*10^5 milliseconds=10min - def computeDifficulty(self, mode="Nakamoto", targetRate=1.0/600000.0): - result = None - if mode=="Nakamoto": - # Use MLE estimate of poisson process, compare to targetRate, update by multiplying by resulting ratio. - #if self.verbose: - # print("Beginning update of difficulty with Nakamoto method") - count = 2016 - #if self.verbose: - # print("Checking that blockchain is 2016*n blocks long and some mining identity has been set") - if len(self.blocks) % 2016 == 0 and len(self.miningIdents) > 0: - ident = self.mIdent - topTime = deepcopy(self.blocks[ident].discoTimestamp) - parent = self.blocks[ident].parent - count = count - 1 - touched = False - while count > 0 and parent is not None: - ident = deepcopy(parent) - parent = self.blocks[ident].parent - count = count - 1 - touched = True - if not touched: - mleDiscoRate = targetRate - else: - botTime = deepcopy(self.blocks[ident].discoTimestamp) - - # Algebra is okay: - assert topTime != botTime - - # MLE estimate of arrivals per second: - mleDiscoRate = float(2015)/float(topTime - botTime) - - # Rates can't be negative, but this estimate could be (although it's highly unlikely given Bitcoin's standard choices - # of difficulty update rate, etc. - mleDiscoRate = abs(mleDiscoRate) - - if self.verbose: - print("MLE disco rate = " + str(mleDiscoRate) + " and targetRate = " + str(targetRate)) - # 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. - - if self.verbose: - print("MLE discovery rate = " + str(mleDiscoRate)) - print("Difficulty before adjustment = " + str(self.diff)) - - # Update difficulty multiplicatively - self.diff = self.diff*mleDiscoRate/targetRate - - if self.verbose: - print("Difficulty after adjustment = ", str(self.diff)) - - 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 - #print(self.diff) - assert self.diff != 0.0 - if len(self.blocks) > 120 and len(self.miningIdents) > 0: - ident = self.mIdent - bl = [] - bl.append(deepcopy(self.blocks[ident].discoTimestamp)) - parent = self.blocks[ident].parent - count = count - 1 - while count > 0 and parent is not None: - ident = deepcopy(parent) - bl.append(deepcopy(self.blocks[ident].discoTimestamp)) - parent = self.blocks[ident].parent - count = count-1 - # sort - bl = sorted(bl) - assert len(bl)<=1200 - - #print("Sample size = " + str(len(bl))) - # remove 10 and 90 %-iles - numOutliers = math.ceil(float(len(bl))/float(10)) - assert numOutliers <= 120 - #print("Number of outliers = " + str(numOutliers)) - oldBL = deepcopy(bl) - if numOutliers > 0: - bl = bl[numOutliers:-numOutliers] - #if numOutliers == 120: - # print("\n\nSORTED TS LIST = " + str(oldBL) + "\nModified list = " + str(bl)) - - - # get topTime and botTime - #if self.verbose: - # print("bl[0] = " + str(bl[0]) + ",\tbl[-1] = " + str(bl[-1])) - topTime = bl[-1] - botTime = bl[0] - result = [float(topTime - botTime)] - #print(topTime - botTime) - #if self.verbose: - # print("list of timestamps = " + str(bl)) - # print("topTime = " + str(bl[-1])) - # print("botTime = " + str(bl[0])) - - # Assert algebra will work - # 1200 - 2*120 = 1200 - 240 = 960 - assert 0 < len(bl) and len(bl) < 961 - assert topTime - botTime >= 0.0 - result.append(len(bl)-1) - # 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 - if len(bl)==0: - print("WOOP WOOP NO TIMESTAMPS WTF? We have " + str(len(self.blocks)) + " blocks available, and we are counting " + str(2*numOutliers) + " as outliers. bl = " + str(bl)) - naiveDiscoRate = float(len(bl)-1)/float(topTime - botTime) - - # How much should difficulty change? - assert naiveDiscoRate != 0.0 - assert targetRate != 0.0 - assert self.diff != 0.0 - 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 - - # Really a trash metric unless sample sizes are huge. - count = 1200 - ident = self.mIdent - bl = [] - bl.append(deepcopy(self.blocks[ident].discoTimestamp)) - parent = self.blocks[ident].parent - count = count - 1 - while count > 0 and parent is not None: - ident = deepcopy(parent) - bl.append(deepcopy(self.blocks[ident].discoTimestamp)) - parent = self.blocks[ident].parent - count = count-1 - if len(bl) > 120: - sk = abs(skew(bl)) - va = var(bl) - stdv = sqrt(va) - lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0) - else: - lam = targetRate # we will not change difficulty unless we have at least 120 blocks of data (arbitrarily selected) - self.diff = self.diff*(lam/targetRate) - elif mode=="reciprocalOfMedian": - # In this mode we use a bitcoin-style metric except instead of 1/average inter-arrival time - # we use 1/median magnitude of inter-arrival time. - # And updated each block like with monero instead of every 2016 blocks like bitcoin. - # We assume a sample size of only 600 blocks for now - count = 600 - interArrivals = [] - if len(self.blocks) < count: - estDiscoRate = targetRate - elif len(self.miningIdents) > 0: - ident = self.mIdent - parent = self.blocks[ident].parent - if parent is not None: - dT = abs(self.blocks[ident].discoTimestamp - self.blocks[parent].discoTimestamp) - interArrivals.append(dT) - count = count - 1 - touched = False - while count > 0 and parent is not None: - ident = deepcopy(parent) - parent = self.blocks[ident].parent - if parent is not None: - dT = abs(self.blocks[ident].discoTimestamp - self.blocks[parent].discoTimestamp) - interArrivals.append(dT) - count = count - 1 - touched = True - if not touched: - estDiscoRate = targetRate - else: - estDiscoRate = 1.0/median(interArrivals) - if self.verbose: - print("Est disco rate = " + str(estDiscoRate) + " and targetRate = " + str(targetRate)) - - - if self.verbose: - print("MLE discovery rate = " + str(estDiscoRate)) - print("Difficulty before adjustment = " + str(self.diff)) - - # Update difficulty multiplicatively - self.diff = self.diff*estDiscoRate/targetRate - - if self.verbose: - print("Difficulty after adjustment = ", str(self.diff)) - else: - print("Error, invalid difficulty mode entered.") - return result - -class Test_Blockchain(unittest.TestCase): - def test_addBlock(self): - bill = Blockchain([], verbosity=True) - mode="Nakamoto" - tr = 1.0/100.0 - bill.targetRate = tr - - name = newIdent(0) - t = time.time() - s = t+random.random() - 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,s) - self.assertTrue(genesis.parent is None) - self.assertEqual(genesis.diff,diff) - - bill.addBlock(genesis, mode, tr) - - 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]) - self.assertEqual(len(bill.blocks),1) - - name = newIdent(1) - t = time.time() - s = t+random.random() - diff = 1.0 - params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} - blockA = Block(params) - bill.addBlock(blockA, mode, tr) - - self.assertTrue(blockA.ident in bill.blocks) - self.assertTrue(blockA.ident in bill.leaves) - self.assertTrue(genesis.ident not in bill.leaves) - self.assertEqual(len(bill.miningIdents),1) - self.assertEqual(blockA.ident, bill.miningIdents[0]) - self.assertEqual(len(bill.blocks),2) - - - - - - bill = Blockchain([], verbosity=True) - mode="vanSaberhagen" - tr = 1.0/100.0 - bill.targetRate = tr - - name = newIdent(0) - t = time.time() - s = t+random.random() - 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,s) - self.assertTrue(genesis.parent is None) - self.assertEqual(genesis.diff,diff) - - bill.addBlock(genesis, mode, tr) - - 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]) - self.assertEqual(len(bill.blocks),1) - - name = newIdent(1) - t = time.time() - s = t+random.random() - diff = 1.0 - params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} - blockA = Block(params) - bill.addBlock(blockA, mode, tr) - - self.assertTrue(blockA.ident in bill.blocks) - self.assertTrue(blockA.ident in bill.leaves) - self.assertTrue(genesis.ident not in bill.leaves) - self.assertEqual(len(bill.miningIdents),1) - self.assertEqual(blockA.ident, bill.miningIdents[0]) - self.assertEqual(len(bill.blocks),2) - - - - - - bill = Blockchain([], verbosity=True) - mode="MOM:expModGauss" - tr = 1.0/100.0 - bill.targetRate = tr - - name = newIdent(0) - t = time.time() - s = t+random.random() - 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,s) - self.assertTrue(genesis.parent is None) - self.assertEqual(genesis.diff,diff) - - bill.addBlock(genesis, mode, tr) - - 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]) - self.assertEqual(len(bill.blocks),1) - - name = newIdent(1) - t = time.time() - s = t+random.random() - diff = 1.0 - params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff} - blockA = Block(params) - bill.addBlock(blockA, mode, tr) - - self.assertTrue(blockA.ident in bill.blocks) - self.assertTrue(blockA.ident in bill.leaves) - self.assertTrue(genesis.ident not in bill.leaves) - self.assertEqual(len(bill.miningIdents),1) - self.assertEqual(blockA.ident, bill.miningIdents[0]) - self.assertEqual(len(bill.blocks),2) - - - def test_bc(self): - bill = Blockchain([], verbosity=True) - mode="Nakamoto" - tr = 1.0/100.0 - bill.targetRate = tr - - 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, mode, tr) - - 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, mode, tr) - - #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, mode, tr) - - 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, mode, tr) - - 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) - ''' - def test_median(self): - # TODO: everything. - mode = "reciprocalOfMedian" - tr = 1.0 # one block per millisecond why not - deltaT = 1.0 # let's just make this easy - bill = Blockchain([], verbosity=True) - bill.targetRate = tr - - with open("outputM.txt", "w") as writeFile: - # We will send (t, a, diff) to writeFile. - writeFile.write("time,rateConstant,difficulty\n") - name = newIdent(0) - t = 0.0 - s = 0.0 - 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,s) - self.assertTrue(genesis.parent is None) - self.assertEqual(genesis.diff,diff) - - bill.addBlock(genesis, mode, tr) - a = 1.0 - b = 1.0/a - - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - - 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]) - - parent = genesis.ident - oldDiff = bill.diff - - while len(bill.blocks)<601: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - self.assertEqual(bill.diff, oldDiff) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - parent = newName - oldDiff = bill.diff - - a = 1.01 # slightly slower blocks, median won't change until half the data is corrupted! - b = 1.0/a - while len(bill.blocks)<899: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - self.assertEqual(bill.diff, oldDiff) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - self.assertEqual(bill.diff, oldDiff) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - parent = newName - oldDiff = bill.diff - - # One more block and our median inter-arrival time is deltaT*(1.0+a)/2.0 - # and so estRate = 1/median = (2.0/(1.0+a))/deltaT, whereas before it was just - # 1/deltaT. So estRate/targetRate = 2.0/(1.0+a) - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - self.assertEqual(bill.diff, oldDiff) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - err = bill.diff - oldDiff*2.0/(1.0+a) - self.assertTrue(err*err < 10**-15) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - parent = newName - oldDiff = bill.diff - - # One more block and our median inter-arrival time is deltaT*a - # and so estRate = 1/median = (1.0/a)/deltaT, whereas before it was just - # 1/deltaT. So estRate/targetRate = 1.0/a = b - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - self.assertEqual(bill.diff, oldDiff) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - err = bill.diff - oldDiff*b - self.assertTrue(err*err < 10**-15) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - parent = newName - oldDiff = bill.diff - - # Note that until the median changes again, this estimated block arrival rate - # does not change. This may be true even if a lot of new data has come in. - # It is possible that the same pair of blocks remain the median inter-arrival - # magnitude for the entire time both blocks are in the sample size. - # During this period of time, difficulty will update multiplicatively, so - # will either exponentially grow or shrink. - # In other words, this model can be looked at as: exponential change over - # time with a rate proportional to the deviation between the median and - # the target inter-arrival rates. - - - - - - def test_mine(self): - # TODO: everything. - mode = "MOM:expModGauss" - tr = 1.0/120000.0 # one block per two minutes - deltaT = 120000.0 - bill = Blockchain([], verbosity=True) - bill.targetRate = tr - - with open("outputM.txt", "w") as writeFile: - # We will send (t, a, diff, ratio, awayFromOne) to writeFile. - writeFile.write("time,rateConstant,difficulty\n") - name = newIdent(0) - t = 0.0 - s = 0.0 - 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,s) - self.assertTrue(genesis.parent is None) - self.assertEqual(genesis.diff,diff) - - bill.addBlock(genesis, mode, tr) - a = 1.0 - b = 1.0/a - - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - - 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]) - - parent = genesis.ident - oldDiff = bill.diff - - while len(bill.blocks)<120: - # Our metric divides by skewness. In reality, this is zero with - # probability zero. But for our tests, it's assured. So we - # will perturb each arrival by a small, up-to-half-percent - # variation to ensure a nonzero skewness without altering things - # too much. - newName = newIdent(len(bill.blocks)) - t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) - s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) - self.assertEqual(bill.diff, oldDiff) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - parent = newName - oldDiff = bill.diff - - # Just one more block and difficulty should be computed for the first time. - print("Just one more block and difficulty should be computed for the first time.") - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) - s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - parent = newName - #self.assertEqual(bill.diff, oldDiff) - - oldDiff = bill.diff - - # what if we add a bunch of blocks this way? - # In the case of a static hash rate, I suppose we hope to not - # vary too far from a multiplicative factor of 1.0, or rather - # a constant difficulty. - - while len(bill.blocks)<200: - # Our metric divides by skewness. In reality, this is zero with - # probability zero. But for our tests, it's assured. So we - # will perturb each arrival by a small, up-to-half-percent - # variation to ensure a nonzero skewness without altering things - # too much. - newName = newIdent(len(bill.blocks)) - t += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) - s += deltaT*a*(1.0 + (2.0*random.random() - 1.0)/100.0) - self.assertEqual(bill.diff, oldDiff) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "\n") - parent = newName - oldDiff = bill.diff - - - - def test_vs(self): - # TODO: Still must test that outliers are being removed "appropriately" according to specifications - # TODO: Test that scrambled lists of timestamps produce the same difficulty estimate. - # TODO: Show that in the case of homogeneous poisson processes, unusual estimates are a little - # more common than in the Nakamoto difficulty (which must be the case because Nakamoto uses - # the UMVUE). - mode = "vanSaberhagen" - tr = 1.0/60000.0 # one block per minute - deltaT = 60000.0 - bill = Blockchain([], verbosity=True) - bill.targetRate = tr - - with open("output.txt", "w") as writeFile: - # We will send (t, a, diff, ratio, awayFromOne) to writeFile. - writeFile.write("time,rateConstant,difficulty,ratio\n") - name = newIdent(0) - t = 0.0 - s = 0.0 - 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,s) - self.assertTrue(genesis.parent is None) - self.assertEqual(genesis.diff,diff) - - bill.addBlock(genesis, mode, tr) - writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") - - 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]) - self.assertEqual(bill.diff, 1.0) - - parent = genesis.ident - oldDiff = bill.diff - a = 1.0 - b = 1.0/a - - while len(bill.blocks)<120: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - self.assertEqual(bill.diff, oldDiff) - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - - writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") - parent = newName - oldDiff = bill.diff - - # Just one more block and difficulty should be computed for the first time. - print("Just one more block and difficulty should be computed for the first time.") - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") - parent = newName - self.assertEqual(bill.diff, oldDiff) - - oldDiff = bill.diff - - print("Let's add more blocks at the same rate.") - a = 1.0 - b = 1.0/a - - while len(bill.blocks)<1200: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + ",1.0," + str(bill.diff) + ",1.0\n") - parent = newName - self.assertEqual(bill.diff, oldDiff) - oldDiff = bill.diff - - print("Let's add more blocks at a slower rate.") - a = 1.1 - b = 1.0/a - - # If blocks arrive slightly further apart, difficulty should drop. - # However, since vanSaberhagen discards top 10% and bottom 10% of - # timestamps, it will take 120 blocks for this change to register - # in difficulty. - print("If blocks arrive slightly further apart, difficulty should drop. However, since vanSaberhagen discards top 10% and bottom 10% of timestamps, it will take 120 blocks for this change to register in difficulty.") - while len(bill.blocks)<1320: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") - parent = newName - self.assertEqual(bill.diff, oldDiff) - oldDiff = bill.diff - - print("One more block and difficulty should register a change.") - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") - parent = newName - self.assertTrue(bill.diff < oldDiff) - oldDiff = bill.diff - - # Let's add another fifty blocks at this same rate and verify that difficulty continually - # drops. - print("Let's add another fifty blocks at this same rate and verify that difficulty continually drops.") - a = 1.1 - b = 1.0/a - - while len(bill.blocks)<1370: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") - parent = newName - self.assertTrue(bill.diff < oldDiff) - oldDiff = bill.diff - - # Now we go back to the target rate. We have 170 slow blocks in the queue and 50 in the sample size. Difficulty will continue to drop for another 120 blocks... - print("Now we go back to the target rate. We have 170 slow blocks in the queue and 50 in the sample size. Difficulty will continue to drop for another 120 blocks...") - a = 1.0 - b = 1.0/a - - while len(bill.blocks)<1490: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") - parent = newName - self.assertTrue(bill.diff < oldDiff) - oldRatio = bill.diff/oldDiff - oldDiff = bill.diff - #print(str(result) + ", " + str(bill.diff) + ", " + str(oldDiff)) - - # Now all 170 slow blocks are not only in the queue but in our sample. The *multiplicative factor* between timesteps should be identical for the next 790 blocks.. leading to AN EXPONENTIAL DECAY OF DIFFICULTY. - print("Now all 170 slow blocks are not only in the queue but in our sample. The *multiplicative factor* between timesteps should be identical for the next 790 blocks.. leading to AN EXPONENTIAL DECAY OF DIFFICULTY.") - a = 1.0 - b = 1.0/a - while len(bill.blocks)<2279: - newName = newIdent(len(bill.blocks)) - t += deltaT - s += deltaT - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") - ratio = bill.diff/oldDiff - parent = newName - err = ratio - oldRatio - #print("Difference between last ratio and next ratio:" + str(err)) - self.assertTrue(err*err < 10**-15) - oldDiff = bill.diff - oldRatio = ratio - - print("Now adding a single new block will cause our 170 slow blocks to start dropping out of our sample, so the ratio should start returning to 1.0.") - oldAwayFromOne = abs(oldRatio - 1.0) # Ratio should be returning to 1.0 so this difference should go to zero - oldAwayFromOne = oldAwayFromOne*oldAwayFromOne - - # For the next 170 blocks as our perturbed blocks drop out of our sample, our - # estimated block arrival rate will return to "normal" so the multiplicative - # difference in difficulty should return to 1.0. - print("For the next 170 blocks as our perturbed blocks drop out of our sample, ourestimated block arrival rate will return to normal so the multiplicative difference in difficulty should return to 1.0.") - a = 1.0 - b = 1.0/a - while len(bill.blocks)<2449: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") - ratio = bill.diff/oldDiff - #print("New ratio = " + str(ratio) + " and oldRatio = " + str(oldRatio)) - self.assertTrue(ratio > oldRatio) - awayFromOne = abs(ratio - 1.0) # Ratio should be returning to 1.0 so this difference should go to zero - awayFromOne = awayFromOne*awayFromOne - self.assertTrue(awayFromOne < oldAwayFromOne) # This return will be monotonic in our manufactured example. - parent = newName - oldDiff = bill.diff - oldRatio = ratio - oldAwayFromOne = awayFromOne - - - # Now difficulty should remain frozen for as long as we like. - - a = 1.0 - b = 1.0/a - while len(bill.blocks)<2500: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - writeFile.write(str(t) + "," + str(a) + "," + str(bill.diff) + "," + str(bill.diff/oldDiff) + "\n") - parent = newName - self.assertEqual(bill.diff, oldDiff) - oldDiff = bill.diff - - - - - def test_nak(self): - # Since Nakamoto difficulty is derived from the MLE of the block arrival rate, - # we already know how it "should" behave in a poisson process, etc. - # TODO: Generate N samples of MLEs of Poisson rates compared to known homog. - # poisson rate, show that the resulting code does not result in unusual measurements - # more often than expected. - mode = "Nakamoto" - tr = 1.0/600000.0 - deltaT = 600000.0 - bill = Blockchain([], verbosity=True) - bill.targetRate = tr - # Bitcoin updating at 1 block per 10 minutes - - name = newIdent(0) - t = 0.0 - s = 0.0 - 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,s) - self.assertTrue(genesis.parent is None) - self.assertEqual(genesis.diff,diff) - - bill.addBlock(genesis, mode, tr) - - 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]) - self.assertEqual(bill.diff, 1.0) - - parent = genesis.ident - oldDiff = bill.diff - i = 1 - - while len(bill.blocks)<2016*i-1: - newName = newIdent(len(bill.blocks)) - t += deltaT - s += deltaT - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - parent = newName - - # Just one more block and difficulty should recompute. - print("Just one more block and difficulty should recompute.") - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT - s += deltaT - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - parent = newName - self.assertEqual(bill.diff, oldDiff) - - oldDiff = bill.diff - i += 1 - - while len(bill.blocks)<2016*i-1: - newName = newIdent(len(bill.blocks)) - t += deltaT - s += deltaT - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - parent = newName - - # Just one more block and difficulty should recompute. - print("Just one more block and difficulty should again recompute.") - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT - s += deltaT - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - parent = newName - self.assertEqual(bill.diff, oldDiff) - - oldDiff = bill.diff - i += 1 - a = 1.1 - b = 1.0/a - - # If blocks arrive slightly further apart, difficulty should drop. - while len(bill.blocks)<2016*i-1: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - parent = newName - - print("Just one more block and difficulty will go down.") - - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - parent = newName - err = abs(bill.diff - oldDiff*b) - self.assertTrue(err*err < 10**-15) - oldDiff = bill.diff - i += 1 - - - # If blocks then arrive on target, difficulty should freeze. - a = 1.0 - b = 1.0/a - while len(bill.blocks)<2016*i-1: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - parent = newName - - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - parent = newName - self.assertEqual(bill.diff, oldDiff) - oldDiff = bill.diff - i += 1 - - # If blocks arrive too close together, difficulty should increase. - a = 0.9 - b = 1.0/a - while len(bill.blocks)<2016*i-1: - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - - bill.addBlock(newBlock, mode, tr) - parent = newName - - print("Just one more block and difficulty should go up.") - self.assertEqual(bill.diff, oldDiff) - newName = newIdent(len(bill.blocks)) - t += deltaT*a - s += deltaT*a - diff = bill.diff - params = {"ident":newName, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - parent = newName - err = abs(bill.diff - oldDiff*b) - self.assertTrue(err*err < 10**-15) - ''' - - - - -suite = unittest.TestLoader().loadTestsFromTestCase(Test_Blockchain) -unittest.TextTestRunner(verbosity=1).run(suite) diff --git a/source-code/Poisson-Graphs/new/Node.py b/source-code/Poisson-Graphs/new/Node.py deleted file mode 100644 index 716fe4f..0000000 --- a/source-code/Poisson-Graphs/new/Node.py +++ /dev/null @@ -1,116 +0,0 @@ -from Blockchain import * - -class Node(object): - ''' - Node object. params [identity, blockchain (data), verbosity, difficulty] - ''' - def __init__(self, params={}): - 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, nonce): - newName = newIdent(nonce) - 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 = copy.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 = copy.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 = copy.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(0) - 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) - bill.addBlock(newBlock, mode, tr) - parent = name - - - while len(nelly.data["blockchain"].blocks) < 5000: - name = newIdent(0) - diff = nelly.data["blockchain"].diff - t += deltaT*diff - s = t - params = {"ident":name, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - parent = name - -suite = unittest.TestLoader().loadTestsFromTestCase(Test_Node) -unittest.TextTestRunner(verbosity=1).run(suite) - diff --git a/source-code/Poisson-Graphs/new/Node.py~ b/source-code/Poisson-Graphs/new/Node.py~ deleted file mode 100644 index 716fe4f..0000000 --- a/source-code/Poisson-Graphs/new/Node.py~ +++ /dev/null @@ -1,116 +0,0 @@ -from Blockchain import * - -class Node(object): - ''' - Node object. params [identity, blockchain (data), verbosity, difficulty] - ''' - def __init__(self, params={}): - 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, nonce): - newName = newIdent(nonce) - 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 = copy.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 = copy.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 = copy.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(0) - 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) - bill.addBlock(newBlock, mode, tr) - parent = name - - - while len(nelly.data["blockchain"].blocks) < 5000: - name = newIdent(0) - diff = nelly.data["blockchain"].diff - t += deltaT*diff - s = t - params = {"ident":name, "disco":t, "arriv":s, "parent":parent, "diff":diff} - newBlock = Block(params) - bill.addBlock(newBlock, mode, tr) - parent = name - -suite = unittest.TestLoader().loadTestsFromTestCase(Test_Node) -unittest.TextTestRunner(verbosity=1).run(suite) - diff --git a/source-code/Poisson-Graphs/new/__pycache__/Block.cpython-35.pyc b/source-code/Poisson-Graphs/new/__pycache__/Block.cpython-35.pyc deleted file mode 100644 index 62b87ddcec7ee932ed4ed40af7a7a7dc297f5f63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2174 zcmZuy&2k(y5N^%S{$=fuqGFRG0pT)`rEKrr#ZMrFKo#Yph^qp=OwG<{y<^YLERxn_ zjeQ|`fjkD!z>QbnrYoo1ICJ7_jn_6Qv{p;qYPEX0zizLstn^;W(}VZgM1Rq>5n=xT z)4T-9@h7MxI=I{mIuJ{FKnDTk_zUR;bV7QZN0i6RfU1a|hjb7_+M>MerEO9~M=gKQ z>^t-vk}jz>J%{a{SGYlWhw?6t#8IDAkB1b%`xz5yf29@D=bzw|hZ9mrO zY+{C$`g!<8S!c&PvxVz``zBYF4cT%20urr2cd%y6EC(Myz%=iJAa+ggIqV2lNChI@ z#2+kTAK}wt7yD;U|LjSOM`<>ZBYs!nb9ZH0$$}p)>|#r%(iT%?Y&xCEdMxw8Wc7*C z3weK58&izRg&e1)QG+e%jlsg}cT%m3leCnnm3E>e9s*P0`>)xUSCz_?F{xg_Qrg%; zoZ&{}+K1-4Q&kR~ycmy*Y+l-hgi~7Wcn300I1gTLU_Qh&zk}pH_sQkPqZDXJ5TE8K zTy7#-+$9@d-~*974#<2>wne`RdRl!%5w^!Y(sL3**fz)5b}rP{fp*4__^}f710aT= z-{E)g2$;BGJotcne-ikUcEg08=`;*dMo3wA?^TRXIDPy`>srgAl0Jj?2fK1#7b=&w zmTAsWnWp8q)>DXlN_XT_B{gDOoIundSAHT-pKZy}+)6X4=Vgw(pD2)A8erH?it3P) z&DmmG@|O({h>cypY{?A|gNihNY_9>gUENZHv0{L2~g-7nRi^6i~I1 zQRP^35PiKi1ig)1CrOH`ut~!9%v}%?-OFDn?ur$053~85uKLLDEHCY$oM53!rz%NY zFG;3#J}-ISPm-VJX?bzdPLjONl0>h;{xui4d1&@ScR{$keX;lBmhh4clFPxEet_w{ ze6834!56;A0)9^hk?ZZFY!mNnwrqy{P7l-9#2?t84FcT&E4M^JN7$o10*xTk`>S zH&TzAN1UwY-Wz=77KrOLXZw1tuGwho+k6;tYMro77!N~_E*Fi8vb>gHkFl2JWQ^X$ zHNya)pfCEOCt6|?>znwki#2h2>ndkE&5gZ5q}QRZ-(qry$tDvIn=9f0^fnIO!88n$ zzR=9OF1q!oHfZLWp*>h}ojK6X)^!AdzQ=@d=UVmXNM*K(N{3x}n4+S*8IPuK>oix| z-7c~6vH42Z)1{t^&AhP6t?sL5mWAJQQ`auk^XmWHb)Ao!Qu&lKXBd{7K|fwwyL0ol F_!q>w>s$Z; diff --git a/source-code/Poisson-Graphs/new/__pycache__/Blockchain.cpython-35.pyc b/source-code/Poisson-Graphs/new/__pycache__/Blockchain.cpython-35.pyc deleted file mode 100644 index 9602d14e03af90a75dde0daf88c94a4f247f1544..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9813 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 Ii29`VFMs7IR{#J2 diff --git a/source-code/Poisson-Graphs/new/output.txt b/source-code/Poisson-Graphs/new/output.txt deleted file mode 100644 index 776c5db..0000000 --- a/source-code/Poisson-Graphs/new/output.txt +++ /dev/null @@ -1,2501 +0,0 @@ -time,rateConstant,difficulty,ratio -0.0,1.0,1.0,1.0 -60000.0,1.0,1.0,1.0 -120000.0,1.0,1.0,1.0 -180000.0,1.0,1.0,1.0 -240000.0,1.0,1.0,1.0 -300000.0,1.0,1.0,1.0 -360000.0,1.0,1.0,1.0 -420000.0,1.0,1.0,1.0 -480000.0,1.0,1.0,1.0 -540000.0,1.0,1.0,1.0 -600000.0,1.0,1.0,1.0 -660000.0,1.0,1.0,1.0 -720000.0,1.0,1.0,1.0 -780000.0,1.0,1.0,1.0 -840000.0,1.0,1.0,1.0 -900000.0,1.0,1.0,1.0 -960000.0,1.0,1.0,1.0 -1020000.0,1.0,1.0,1.0 -1080000.0,1.0,1.0,1.0 -1140000.0,1.0,1.0,1.0 -1200000.0,1.0,1.0,1.0 -1260000.0,1.0,1.0,1.0 -1320000.0,1.0,1.0,1.0 -1380000.0,1.0,1.0,1.0 -1440000.0,1.0,1.0,1.0 -1500000.0,1.0,1.0,1.0 -1560000.0,1.0,1.0,1.0 -1620000.0,1.0,1.0,1.0 -1680000.0,1.0,1.0,1.0 -1740000.0,1.0,1.0,1.0 -1800000.0,1.0,1.0,1.0 -1860000.0,1.0,1.0,1.0 -1920000.0,1.0,1.0,1.0 -1980000.0,1.0,1.0,1.0 -2040000.0,1.0,1.0,1.0 -2100000.0,1.0,1.0,1.0 -2160000.0,1.0,1.0,1.0 -2220000.0,1.0,1.0,1.0 -2280000.0,1.0,1.0,1.0 -2340000.0,1.0,1.0,1.0 -2400000.0,1.0,1.0,1.0 -2460000.0,1.0,1.0,1.0 -2520000.0,1.0,1.0,1.0 -2580000.0,1.0,1.0,1.0 -2640000.0,1.0,1.0,1.0 -2700000.0,1.0,1.0,1.0 -2760000.0,1.0,1.0,1.0 -2820000.0,1.0,1.0,1.0 -2880000.0,1.0,1.0,1.0 -2940000.0,1.0,1.0,1.0 -3000000.0,1.0,1.0,1.0 -3060000.0,1.0,1.0,1.0 -3120000.0,1.0,1.0,1.0 -3180000.0,1.0,1.0,1.0 -3240000.0,1.0,1.0,1.0 -3300000.0,1.0,1.0,1.0 -3360000.0,1.0,1.0,1.0 -3420000.0,1.0,1.0,1.0 -3480000.0,1.0,1.0,1.0 -3540000.0,1.0,1.0,1.0 -3600000.0,1.0,1.0,1.0 -3660000.0,1.0,1.0,1.0 -3720000.0,1.0,1.0,1.0 -3780000.0,1.0,1.0,1.0 -3840000.0,1.0,1.0,1.0 -3900000.0,1.0,1.0,1.0 -3960000.0,1.0,1.0,1.0 -4020000.0,1.0,1.0,1.0 -4080000.0,1.0,1.0,1.0 -4140000.0,1.0,1.0,1.0 -4200000.0,1.0,1.0,1.0 -4260000.0,1.0,1.0,1.0 -4320000.0,1.0,1.0,1.0 -4380000.0,1.0,1.0,1.0 -4440000.0,1.0,1.0,1.0 -4500000.0,1.0,1.0,1.0 -4560000.0,1.0,1.0,1.0 -4620000.0,1.0,1.0,1.0 -4680000.0,1.0,1.0,1.0 -4740000.0,1.0,1.0,1.0 -4800000.0,1.0,1.0,1.0 -4860000.0,1.0,1.0,1.0 -4920000.0,1.0,1.0,1.0 -4980000.0,1.0,1.0,1.0 -5040000.0,1.0,1.0,1.0 -5100000.0,1.0,1.0,1.0 -5160000.0,1.0,1.0,1.0 -5220000.0,1.0,1.0,1.0 -5280000.0,1.0,1.0,1.0 -5340000.0,1.0,1.0,1.0 -5400000.0,1.0,1.0,1.0 -5460000.0,1.0,1.0,1.0 -5520000.0,1.0,1.0,1.0 -5580000.0,1.0,1.0,1.0 -5640000.0,1.0,1.0,1.0 -5700000.0,1.0,1.0,1.0 -5760000.0,1.0,1.0,1.0 -5820000.0,1.0,1.0,1.0 -5880000.0,1.0,1.0,1.0 -5940000.0,1.0,1.0,1.0 -6000000.0,1.0,1.0,1.0 -6060000.0,1.0,1.0,1.0 -6120000.0,1.0,1.0,1.0 -6180000.0,1.0,1.0,1.0 -6240000.0,1.0,1.0,1.0 -6300000.0,1.0,1.0,1.0 -6360000.0,1.0,1.0,1.0 -6420000.0,1.0,1.0,1.0 -6480000.0,1.0,1.0,1.0 -6540000.0,1.0,1.0,1.0 -6600000.0,1.0,1.0,1.0 -6660000.0,1.0,1.0,1.0 -6720000.0,1.0,1.0,1.0 -6780000.0,1.0,1.0,1.0 -6840000.0,1.0,1.0,1.0 -6900000.0,1.0,1.0,1.0 -6960000.0,1.0,1.0,1.0 -7020000.0,1.0,1.0,1.0 -7080000.0,1.0,1.0,1.0 -7140000.0,1.0,1.0,1.0 -7200000.0,1.0,1.0,1.0 -7260000.0,1.0,1.0,1.0 -7320000.0,1.0,1.0,1.0 -7380000.0,1.0,1.0,1.0 -7440000.0,1.0,1.0,1.0 -7500000.0,1.0,1.0,1.0 -7560000.0,1.0,1.0,1.0 -7620000.0,1.0,1.0,1.0 -7680000.0,1.0,1.0,1.0 -7740000.0,1.0,1.0,1.0 -7800000.0,1.0,1.0,1.0 -7860000.0,1.0,1.0,1.0 -7920000.0,1.0,1.0,1.0 -7980000.0,1.0,1.0,1.0 -8040000.0,1.0,1.0,1.0 -8100000.0,1.0,1.0,1.0 -8160000.0,1.0,1.0,1.0 -8220000.0,1.0,1.0,1.0 -8280000.0,1.0,1.0,1.0 -8340000.0,1.0,1.0,1.0 -8400000.0,1.0,1.0,1.0 -8460000.0,1.0,1.0,1.0 -8520000.0,1.0,1.0,1.0 -8580000.0,1.0,1.0,1.0 -8640000.0,1.0,1.0,1.0 -8700000.0,1.0,1.0,1.0 -8760000.0,1.0,1.0,1.0 -8820000.0,1.0,1.0,1.0 -8880000.0,1.0,1.0,1.0 -8940000.0,1.0,1.0,1.0 -9000000.0,1.0,1.0,1.0 -9060000.0,1.0,1.0,1.0 -9120000.0,1.0,1.0,1.0 -9180000.0,1.0,1.0,1.0 -9240000.0,1.0,1.0,1.0 -9300000.0,1.0,1.0,1.0 -9360000.0,1.0,1.0,1.0 -9420000.0,1.0,1.0,1.0 -9480000.0,1.0,1.0,1.0 -9540000.0,1.0,1.0,1.0 -9600000.0,1.0,1.0,1.0 -9660000.0,1.0,1.0,1.0 -9720000.0,1.0,1.0,1.0 -9780000.0,1.0,1.0,1.0 -9840000.0,1.0,1.0,1.0 -9900000.0,1.0,1.0,1.0 -9960000.0,1.0,1.0,1.0 -10020000.0,1.0,1.0,1.0 -10080000.0,1.0,1.0,1.0 -10140000.0,1.0,1.0,1.0 -10200000.0,1.0,1.0,1.0 -10260000.0,1.0,1.0,1.0 -10320000.0,1.0,1.0,1.0 -10380000.0,1.0,1.0,1.0 -10440000.0,1.0,1.0,1.0 -10500000.0,1.0,1.0,1.0 -10560000.0,1.0,1.0,1.0 -10620000.0,1.0,1.0,1.0 -10680000.0,1.0,1.0,1.0 -10740000.0,1.0,1.0,1.0 -10800000.0,1.0,1.0,1.0 -10860000.0,1.0,1.0,1.0 -10920000.0,1.0,1.0,1.0 -10980000.0,1.0,1.0,1.0 -11040000.0,1.0,1.0,1.0 -11100000.0,1.0,1.0,1.0 -11160000.0,1.0,1.0,1.0 -11220000.0,1.0,1.0,1.0 -11280000.0,1.0,1.0,1.0 -11340000.0,1.0,1.0,1.0 -11400000.0,1.0,1.0,1.0 -11460000.0,1.0,1.0,1.0 -11520000.0,1.0,1.0,1.0 -11580000.0,1.0,1.0,1.0 -11640000.0,1.0,1.0,1.0 -11700000.0,1.0,1.0,1.0 -11760000.0,1.0,1.0,1.0 -11820000.0,1.0,1.0,1.0 -11880000.0,1.0,1.0,1.0 -11940000.0,1.0,1.0,1.0 -12000000.0,1.0,1.0,1.0 -12060000.0,1.0,1.0,1.0 -12120000.0,1.0,1.0,1.0 -12180000.0,1.0,1.0,1.0 -12240000.0,1.0,1.0,1.0 -12300000.0,1.0,1.0,1.0 -12360000.0,1.0,1.0,1.0 -12420000.0,1.0,1.0,1.0 -12480000.0,1.0,1.0,1.0 -12540000.0,1.0,1.0,1.0 -12600000.0,1.0,1.0,1.0 -12660000.0,1.0,1.0,1.0 -12720000.0,1.0,1.0,1.0 -12780000.0,1.0,1.0,1.0 -12840000.0,1.0,1.0,1.0 -12900000.0,1.0,1.0,1.0 -12960000.0,1.0,1.0,1.0 -13020000.0,1.0,1.0,1.0 -13080000.0,1.0,1.0,1.0 -13140000.0,1.0,1.0,1.0 -13200000.0,1.0,1.0,1.0 -13260000.0,1.0,1.0,1.0 -13320000.0,1.0,1.0,1.0 -13380000.0,1.0,1.0,1.0 -13440000.0,1.0,1.0,1.0 -13500000.0,1.0,1.0,1.0 -13560000.0,1.0,1.0,1.0 -13620000.0,1.0,1.0,1.0 -13680000.0,1.0,1.0,1.0 -13740000.0,1.0,1.0,1.0 -13800000.0,1.0,1.0,1.0 -13860000.0,1.0,1.0,1.0 -13920000.0,1.0,1.0,1.0 -13980000.0,1.0,1.0,1.0 -14040000.0,1.0,1.0,1.0 -14100000.0,1.0,1.0,1.0 -14160000.0,1.0,1.0,1.0 -14220000.0,1.0,1.0,1.0 -14280000.0,1.0,1.0,1.0 -14340000.0,1.0,1.0,1.0 -14400000.0,1.0,1.0,1.0 -14460000.0,1.0,1.0,1.0 -14520000.0,1.0,1.0,1.0 -14580000.0,1.0,1.0,1.0 -14640000.0,1.0,1.0,1.0 -14700000.0,1.0,1.0,1.0 -14760000.0,1.0,1.0,1.0 -14820000.0,1.0,1.0,1.0 -14880000.0,1.0,1.0,1.0 -14940000.0,1.0,1.0,1.0 -15000000.0,1.0,1.0,1.0 -15060000.0,1.0,1.0,1.0 -15120000.0,1.0,1.0,1.0 -15180000.0,1.0,1.0,1.0 -15240000.0,1.0,1.0,1.0 -15300000.0,1.0,1.0,1.0 -15360000.0,1.0,1.0,1.0 -15420000.0,1.0,1.0,1.0 -15480000.0,1.0,1.0,1.0 -15540000.0,1.0,1.0,1.0 -15600000.0,1.0,1.0,1.0 -15660000.0,1.0,1.0,1.0 -15720000.0,1.0,1.0,1.0 -15780000.0,1.0,1.0,1.0 -15840000.0,1.0,1.0,1.0 -15900000.0,1.0,1.0,1.0 -15960000.0,1.0,1.0,1.0 -16020000.0,1.0,1.0,1.0 -16080000.0,1.0,1.0,1.0 -16140000.0,1.0,1.0,1.0 -16200000.0,1.0,1.0,1.0 -16260000.0,1.0,1.0,1.0 -16320000.0,1.0,1.0,1.0 -16380000.0,1.0,1.0,1.0 -16440000.0,1.0,1.0,1.0 -16500000.0,1.0,1.0,1.0 -16560000.0,1.0,1.0,1.0 -16620000.0,1.0,1.0,1.0 -16680000.0,1.0,1.0,1.0 -16740000.0,1.0,1.0,1.0 -16800000.0,1.0,1.0,1.0 -16860000.0,1.0,1.0,1.0 -16920000.0,1.0,1.0,1.0 -16980000.0,1.0,1.0,1.0 -17040000.0,1.0,1.0,1.0 -17100000.0,1.0,1.0,1.0 -17160000.0,1.0,1.0,1.0 -17220000.0,1.0,1.0,1.0 -17280000.0,1.0,1.0,1.0 -17340000.0,1.0,1.0,1.0 -17400000.0,1.0,1.0,1.0 -17460000.0,1.0,1.0,1.0 -17520000.0,1.0,1.0,1.0 -17580000.0,1.0,1.0,1.0 -17640000.0,1.0,1.0,1.0 -17700000.0,1.0,1.0,1.0 -17760000.0,1.0,1.0,1.0 -17820000.0,1.0,1.0,1.0 -17880000.0,1.0,1.0,1.0 -17940000.0,1.0,1.0,1.0 -18000000.0,1.0,1.0,1.0 -18060000.0,1.0,1.0,1.0 -18120000.0,1.0,1.0,1.0 -18180000.0,1.0,1.0,1.0 -18240000.0,1.0,1.0,1.0 -18300000.0,1.0,1.0,1.0 -18360000.0,1.0,1.0,1.0 -18420000.0,1.0,1.0,1.0 -18480000.0,1.0,1.0,1.0 -18540000.0,1.0,1.0,1.0 -18600000.0,1.0,1.0,1.0 -18660000.0,1.0,1.0,1.0 -18720000.0,1.0,1.0,1.0 -18780000.0,1.0,1.0,1.0 -18840000.0,1.0,1.0,1.0 -18900000.0,1.0,1.0,1.0 -18960000.0,1.0,1.0,1.0 -19020000.0,1.0,1.0,1.0 -19080000.0,1.0,1.0,1.0 -19140000.0,1.0,1.0,1.0 -19200000.0,1.0,1.0,1.0 -19260000.0,1.0,1.0,1.0 -19320000.0,1.0,1.0,1.0 -19380000.0,1.0,1.0,1.0 -19440000.0,1.0,1.0,1.0 -19500000.0,1.0,1.0,1.0 -19560000.0,1.0,1.0,1.0 -19620000.0,1.0,1.0,1.0 -19680000.0,1.0,1.0,1.0 -19740000.0,1.0,1.0,1.0 -19800000.0,1.0,1.0,1.0 -19860000.0,1.0,1.0,1.0 -19920000.0,1.0,1.0,1.0 -19980000.0,1.0,1.0,1.0 -20040000.0,1.0,1.0,1.0 -20100000.0,1.0,1.0,1.0 -20160000.0,1.0,1.0,1.0 -20220000.0,1.0,1.0,1.0 -20280000.0,1.0,1.0,1.0 -20340000.0,1.0,1.0,1.0 -20400000.0,1.0,1.0,1.0 -20460000.0,1.0,1.0,1.0 -20520000.0,1.0,1.0,1.0 -20580000.0,1.0,1.0,1.0 -20640000.0,1.0,1.0,1.0 -20700000.0,1.0,1.0,1.0 -20760000.0,1.0,1.0,1.0 -20820000.0,1.0,1.0,1.0 -20880000.0,1.0,1.0,1.0 -20940000.0,1.0,1.0,1.0 -21000000.0,1.0,1.0,1.0 -21060000.0,1.0,1.0,1.0 -21120000.0,1.0,1.0,1.0 -21180000.0,1.0,1.0,1.0 -21240000.0,1.0,1.0,1.0 -21300000.0,1.0,1.0,1.0 -21360000.0,1.0,1.0,1.0 -21420000.0,1.0,1.0,1.0 -21480000.0,1.0,1.0,1.0 -21540000.0,1.0,1.0,1.0 -21600000.0,1.0,1.0,1.0 -21660000.0,1.0,1.0,1.0 -21720000.0,1.0,1.0,1.0 -21780000.0,1.0,1.0,1.0 -21840000.0,1.0,1.0,1.0 -21900000.0,1.0,1.0,1.0 -21960000.0,1.0,1.0,1.0 -22020000.0,1.0,1.0,1.0 -22080000.0,1.0,1.0,1.0 -22140000.0,1.0,1.0,1.0 -22200000.0,1.0,1.0,1.0 -22260000.0,1.0,1.0,1.0 -22320000.0,1.0,1.0,1.0 -22380000.0,1.0,1.0,1.0 -22440000.0,1.0,1.0,1.0 -22500000.0,1.0,1.0,1.0 -22560000.0,1.0,1.0,1.0 -22620000.0,1.0,1.0,1.0 -22680000.0,1.0,1.0,1.0 -22740000.0,1.0,1.0,1.0 -22800000.0,1.0,1.0,1.0 -22860000.0,1.0,1.0,1.0 -22920000.0,1.0,1.0,1.0 -22980000.0,1.0,1.0,1.0 -23040000.0,1.0,1.0,1.0 -23100000.0,1.0,1.0,1.0 -23160000.0,1.0,1.0,1.0 -23220000.0,1.0,1.0,1.0 -23280000.0,1.0,1.0,1.0 -23340000.0,1.0,1.0,1.0 -23400000.0,1.0,1.0,1.0 -23460000.0,1.0,1.0,1.0 -23520000.0,1.0,1.0,1.0 -23580000.0,1.0,1.0,1.0 -23640000.0,1.0,1.0,1.0 -23700000.0,1.0,1.0,1.0 -23760000.0,1.0,1.0,1.0 -23820000.0,1.0,1.0,1.0 -23880000.0,1.0,1.0,1.0 -23940000.0,1.0,1.0,1.0 -24000000.0,1.0,1.0,1.0 -24060000.0,1.0,1.0,1.0 -24120000.0,1.0,1.0,1.0 -24180000.0,1.0,1.0,1.0 -24240000.0,1.0,1.0,1.0 -24300000.0,1.0,1.0,1.0 -24360000.0,1.0,1.0,1.0 -24420000.0,1.0,1.0,1.0 -24480000.0,1.0,1.0,1.0 -24540000.0,1.0,1.0,1.0 -24600000.0,1.0,1.0,1.0 -24660000.0,1.0,1.0,1.0 -24720000.0,1.0,1.0,1.0 -24780000.0,1.0,1.0,1.0 -24840000.0,1.0,1.0,1.0 -24900000.0,1.0,1.0,1.0 -24960000.0,1.0,1.0,1.0 -25020000.0,1.0,1.0,1.0 -25080000.0,1.0,1.0,1.0 -25140000.0,1.0,1.0,1.0 -25200000.0,1.0,1.0,1.0 -25260000.0,1.0,1.0,1.0 -25320000.0,1.0,1.0,1.0 -25380000.0,1.0,1.0,1.0 -25440000.0,1.0,1.0,1.0 -25500000.0,1.0,1.0,1.0 -25560000.0,1.0,1.0,1.0 -25620000.0,1.0,1.0,1.0 -25680000.0,1.0,1.0,1.0 -25740000.0,1.0,1.0,1.0 -25800000.0,1.0,1.0,1.0 -25860000.0,1.0,1.0,1.0 -25920000.0,1.0,1.0,1.0 -25980000.0,1.0,1.0,1.0 -26040000.0,1.0,1.0,1.0 -26100000.0,1.0,1.0,1.0 -26160000.0,1.0,1.0,1.0 -26220000.0,1.0,1.0,1.0 -26280000.0,1.0,1.0,1.0 -26340000.0,1.0,1.0,1.0 -26400000.0,1.0,1.0,1.0 -26460000.0,1.0,1.0,1.0 -26520000.0,1.0,1.0,1.0 -26580000.0,1.0,1.0,1.0 -26640000.0,1.0,1.0,1.0 -26700000.0,1.0,1.0,1.0 -26760000.0,1.0,1.0,1.0 -26820000.0,1.0,1.0,1.0 -26880000.0,1.0,1.0,1.0 -26940000.0,1.0,1.0,1.0 -27000000.0,1.0,1.0,1.0 -27060000.0,1.0,1.0,1.0 -27120000.0,1.0,1.0,1.0 -27180000.0,1.0,1.0,1.0 -27240000.0,1.0,1.0,1.0 -27300000.0,1.0,1.0,1.0 -27360000.0,1.0,1.0,1.0 -27420000.0,1.0,1.0,1.0 -27480000.0,1.0,1.0,1.0 -27540000.0,1.0,1.0,1.0 -27600000.0,1.0,1.0,1.0 -27660000.0,1.0,1.0,1.0 -27720000.0,1.0,1.0,1.0 -27780000.0,1.0,1.0,1.0 -27840000.0,1.0,1.0,1.0 -27900000.0,1.0,1.0,1.0 -27960000.0,1.0,1.0,1.0 -28020000.0,1.0,1.0,1.0 -28080000.0,1.0,1.0,1.0 -28140000.0,1.0,1.0,1.0 -28200000.0,1.0,1.0,1.0 -28260000.0,1.0,1.0,1.0 -28320000.0,1.0,1.0,1.0 -28380000.0,1.0,1.0,1.0 -28440000.0,1.0,1.0,1.0 -28500000.0,1.0,1.0,1.0 -28560000.0,1.0,1.0,1.0 -28620000.0,1.0,1.0,1.0 -28680000.0,1.0,1.0,1.0 -28740000.0,1.0,1.0,1.0 -28800000.0,1.0,1.0,1.0 -28860000.0,1.0,1.0,1.0 -28920000.0,1.0,1.0,1.0 -28980000.0,1.0,1.0,1.0 -29040000.0,1.0,1.0,1.0 -29100000.0,1.0,1.0,1.0 -29160000.0,1.0,1.0,1.0 -29220000.0,1.0,1.0,1.0 -29280000.0,1.0,1.0,1.0 -29340000.0,1.0,1.0,1.0 -29400000.0,1.0,1.0,1.0 -29460000.0,1.0,1.0,1.0 -29520000.0,1.0,1.0,1.0 -29580000.0,1.0,1.0,1.0 -29640000.0,1.0,1.0,1.0 -29700000.0,1.0,1.0,1.0 -29760000.0,1.0,1.0,1.0 -29820000.0,1.0,1.0,1.0 -29880000.0,1.0,1.0,1.0 -29940000.0,1.0,1.0,1.0 -30000000.0,1.0,1.0,1.0 -30060000.0,1.0,1.0,1.0 -30120000.0,1.0,1.0,1.0 -30180000.0,1.0,1.0,1.0 -30240000.0,1.0,1.0,1.0 -30300000.0,1.0,1.0,1.0 -30360000.0,1.0,1.0,1.0 -30420000.0,1.0,1.0,1.0 -30480000.0,1.0,1.0,1.0 -30540000.0,1.0,1.0,1.0 -30600000.0,1.0,1.0,1.0 -30660000.0,1.0,1.0,1.0 -30720000.0,1.0,1.0,1.0 -30780000.0,1.0,1.0,1.0 -30840000.0,1.0,1.0,1.0 -30900000.0,1.0,1.0,1.0 -30960000.0,1.0,1.0,1.0 -31020000.0,1.0,1.0,1.0 -31080000.0,1.0,1.0,1.0 -31140000.0,1.0,1.0,1.0 -31200000.0,1.0,1.0,1.0 -31260000.0,1.0,1.0,1.0 -31320000.0,1.0,1.0,1.0 -31380000.0,1.0,1.0,1.0 -31440000.0,1.0,1.0,1.0 -31500000.0,1.0,1.0,1.0 -31560000.0,1.0,1.0,1.0 -31620000.0,1.0,1.0,1.0 -31680000.0,1.0,1.0,1.0 -31740000.0,1.0,1.0,1.0 -31800000.0,1.0,1.0,1.0 -31860000.0,1.0,1.0,1.0 -31920000.0,1.0,1.0,1.0 -31980000.0,1.0,1.0,1.0 -32040000.0,1.0,1.0,1.0 -32100000.0,1.0,1.0,1.0 -32160000.0,1.0,1.0,1.0 -32220000.0,1.0,1.0,1.0 -32280000.0,1.0,1.0,1.0 -32340000.0,1.0,1.0,1.0 -32400000.0,1.0,1.0,1.0 -32460000.0,1.0,1.0,1.0 -32520000.0,1.0,1.0,1.0 -32580000.0,1.0,1.0,1.0 -32640000.0,1.0,1.0,1.0 -32700000.0,1.0,1.0,1.0 -32760000.0,1.0,1.0,1.0 -32820000.0,1.0,1.0,1.0 -32880000.0,1.0,1.0,1.0 -32940000.0,1.0,1.0,1.0 -33000000.0,1.0,1.0,1.0 -33060000.0,1.0,1.0,1.0 -33120000.0,1.0,1.0,1.0 -33180000.0,1.0,1.0,1.0 -33240000.0,1.0,1.0,1.0 -33300000.0,1.0,1.0,1.0 -33360000.0,1.0,1.0,1.0 -33420000.0,1.0,1.0,1.0 -33480000.0,1.0,1.0,1.0 -33540000.0,1.0,1.0,1.0 -33600000.0,1.0,1.0,1.0 -33660000.0,1.0,1.0,1.0 -33720000.0,1.0,1.0,1.0 -33780000.0,1.0,1.0,1.0 -33840000.0,1.0,1.0,1.0 -33900000.0,1.0,1.0,1.0 -33960000.0,1.0,1.0,1.0 -34020000.0,1.0,1.0,1.0 -34080000.0,1.0,1.0,1.0 -34140000.0,1.0,1.0,1.0 -34200000.0,1.0,1.0,1.0 -34260000.0,1.0,1.0,1.0 -34320000.0,1.0,1.0,1.0 -34380000.0,1.0,1.0,1.0 -34440000.0,1.0,1.0,1.0 -34500000.0,1.0,1.0,1.0 -34560000.0,1.0,1.0,1.0 -34620000.0,1.0,1.0,1.0 -34680000.0,1.0,1.0,1.0 -34740000.0,1.0,1.0,1.0 -34800000.0,1.0,1.0,1.0 -34860000.0,1.0,1.0,1.0 -34920000.0,1.0,1.0,1.0 -34980000.0,1.0,1.0,1.0 -35040000.0,1.0,1.0,1.0 -35100000.0,1.0,1.0,1.0 -35160000.0,1.0,1.0,1.0 -35220000.0,1.0,1.0,1.0 -35280000.0,1.0,1.0,1.0 -35340000.0,1.0,1.0,1.0 -35400000.0,1.0,1.0,1.0 -35460000.0,1.0,1.0,1.0 -35520000.0,1.0,1.0,1.0 -35580000.0,1.0,1.0,1.0 -35640000.0,1.0,1.0,1.0 -35700000.0,1.0,1.0,1.0 -35760000.0,1.0,1.0,1.0 -35820000.0,1.0,1.0,1.0 -35880000.0,1.0,1.0,1.0 -35940000.0,1.0,1.0,1.0 -36000000.0,1.0,1.0,1.0 -36060000.0,1.0,1.0,1.0 -36120000.0,1.0,1.0,1.0 -36180000.0,1.0,1.0,1.0 -36240000.0,1.0,1.0,1.0 -36300000.0,1.0,1.0,1.0 -36360000.0,1.0,1.0,1.0 -36420000.0,1.0,1.0,1.0 -36480000.0,1.0,1.0,1.0 -36540000.0,1.0,1.0,1.0 -36600000.0,1.0,1.0,1.0 -36660000.0,1.0,1.0,1.0 -36720000.0,1.0,1.0,1.0 -36780000.0,1.0,1.0,1.0 -36840000.0,1.0,1.0,1.0 -36900000.0,1.0,1.0,1.0 -36960000.0,1.0,1.0,1.0 -37020000.0,1.0,1.0,1.0 -37080000.0,1.0,1.0,1.0 -37140000.0,1.0,1.0,1.0 -37200000.0,1.0,1.0,1.0 -37260000.0,1.0,1.0,1.0 -37320000.0,1.0,1.0,1.0 -37380000.0,1.0,1.0,1.0 -37440000.0,1.0,1.0,1.0 -37500000.0,1.0,1.0,1.0 -37560000.0,1.0,1.0,1.0 -37620000.0,1.0,1.0,1.0 -37680000.0,1.0,1.0,1.0 -37740000.0,1.0,1.0,1.0 -37800000.0,1.0,1.0,1.0 -37860000.0,1.0,1.0,1.0 -37920000.0,1.0,1.0,1.0 -37980000.0,1.0,1.0,1.0 -38040000.0,1.0,1.0,1.0 -38100000.0,1.0,1.0,1.0 -38160000.0,1.0,1.0,1.0 -38220000.0,1.0,1.0,1.0 -38280000.0,1.0,1.0,1.0 -38340000.0,1.0,1.0,1.0 -38400000.0,1.0,1.0,1.0 -38460000.0,1.0,1.0,1.0 -38520000.0,1.0,1.0,1.0 -38580000.0,1.0,1.0,1.0 -38640000.0,1.0,1.0,1.0 -38700000.0,1.0,1.0,1.0 -38760000.0,1.0,1.0,1.0 -38820000.0,1.0,1.0,1.0 -38880000.0,1.0,1.0,1.0 -38940000.0,1.0,1.0,1.0 -39000000.0,1.0,1.0,1.0 -39060000.0,1.0,1.0,1.0 -39120000.0,1.0,1.0,1.0 -39180000.0,1.0,1.0,1.0 -39240000.0,1.0,1.0,1.0 -39300000.0,1.0,1.0,1.0 -39360000.0,1.0,1.0,1.0 -39420000.0,1.0,1.0,1.0 -39480000.0,1.0,1.0,1.0 -39540000.0,1.0,1.0,1.0 -39600000.0,1.0,1.0,1.0 -39660000.0,1.0,1.0,1.0 -39720000.0,1.0,1.0,1.0 -39780000.0,1.0,1.0,1.0 -39840000.0,1.0,1.0,1.0 -39900000.0,1.0,1.0,1.0 -39960000.0,1.0,1.0,1.0 -40020000.0,1.0,1.0,1.0 -40080000.0,1.0,1.0,1.0 -40140000.0,1.0,1.0,1.0 -40200000.0,1.0,1.0,1.0 -40260000.0,1.0,1.0,1.0 -40320000.0,1.0,1.0,1.0 -40380000.0,1.0,1.0,1.0 -40440000.0,1.0,1.0,1.0 -40500000.0,1.0,1.0,1.0 -40560000.0,1.0,1.0,1.0 -40620000.0,1.0,1.0,1.0 -40680000.0,1.0,1.0,1.0 -40740000.0,1.0,1.0,1.0 -40800000.0,1.0,1.0,1.0 -40860000.0,1.0,1.0,1.0 -40920000.0,1.0,1.0,1.0 -40980000.0,1.0,1.0,1.0 -41040000.0,1.0,1.0,1.0 -41100000.0,1.0,1.0,1.0 -41160000.0,1.0,1.0,1.0 -41220000.0,1.0,1.0,1.0 -41280000.0,1.0,1.0,1.0 -41340000.0,1.0,1.0,1.0 -41400000.0,1.0,1.0,1.0 -41460000.0,1.0,1.0,1.0 -41520000.0,1.0,1.0,1.0 -41580000.0,1.0,1.0,1.0 -41640000.0,1.0,1.0,1.0 -41700000.0,1.0,1.0,1.0 -41760000.0,1.0,1.0,1.0 -41820000.0,1.0,1.0,1.0 -41880000.0,1.0,1.0,1.0 -41940000.0,1.0,1.0,1.0 -42000000.0,1.0,1.0,1.0 -42060000.0,1.0,1.0,1.0 -42120000.0,1.0,1.0,1.0 -42180000.0,1.0,1.0,1.0 -42240000.0,1.0,1.0,1.0 -42300000.0,1.0,1.0,1.0 -42360000.0,1.0,1.0,1.0 -42420000.0,1.0,1.0,1.0 -42480000.0,1.0,1.0,1.0 -42540000.0,1.0,1.0,1.0 -42600000.0,1.0,1.0,1.0 -42660000.0,1.0,1.0,1.0 -42720000.0,1.0,1.0,1.0 -42780000.0,1.0,1.0,1.0 -42840000.0,1.0,1.0,1.0 -42900000.0,1.0,1.0,1.0 -42960000.0,1.0,1.0,1.0 -43020000.0,1.0,1.0,1.0 -43080000.0,1.0,1.0,1.0 -43140000.0,1.0,1.0,1.0 -43200000.0,1.0,1.0,1.0 -43260000.0,1.0,1.0,1.0 -43320000.0,1.0,1.0,1.0 -43380000.0,1.0,1.0,1.0 -43440000.0,1.0,1.0,1.0 -43500000.0,1.0,1.0,1.0 -43560000.0,1.0,1.0,1.0 -43620000.0,1.0,1.0,1.0 -43680000.0,1.0,1.0,1.0 -43740000.0,1.0,1.0,1.0 -43800000.0,1.0,1.0,1.0 -43860000.0,1.0,1.0,1.0 -43920000.0,1.0,1.0,1.0 -43980000.0,1.0,1.0,1.0 -44040000.0,1.0,1.0,1.0 -44100000.0,1.0,1.0,1.0 -44160000.0,1.0,1.0,1.0 -44220000.0,1.0,1.0,1.0 -44280000.0,1.0,1.0,1.0 -44340000.0,1.0,1.0,1.0 -44400000.0,1.0,1.0,1.0 -44460000.0,1.0,1.0,1.0 -44520000.0,1.0,1.0,1.0 -44580000.0,1.0,1.0,1.0 -44640000.0,1.0,1.0,1.0 -44700000.0,1.0,1.0,1.0 -44760000.0,1.0,1.0,1.0 -44820000.0,1.0,1.0,1.0 -44880000.0,1.0,1.0,1.0 -44940000.0,1.0,1.0,1.0 -45000000.0,1.0,1.0,1.0 -45060000.0,1.0,1.0,1.0 -45120000.0,1.0,1.0,1.0 -45180000.0,1.0,1.0,1.0 -45240000.0,1.0,1.0,1.0 -45300000.0,1.0,1.0,1.0 -45360000.0,1.0,1.0,1.0 -45420000.0,1.0,1.0,1.0 -45480000.0,1.0,1.0,1.0 -45540000.0,1.0,1.0,1.0 -45600000.0,1.0,1.0,1.0 -45660000.0,1.0,1.0,1.0 -45720000.0,1.0,1.0,1.0 -45780000.0,1.0,1.0,1.0 -45840000.0,1.0,1.0,1.0 -45900000.0,1.0,1.0,1.0 -45960000.0,1.0,1.0,1.0 -46020000.0,1.0,1.0,1.0 -46080000.0,1.0,1.0,1.0 -46140000.0,1.0,1.0,1.0 -46200000.0,1.0,1.0,1.0 -46260000.0,1.0,1.0,1.0 -46320000.0,1.0,1.0,1.0 -46380000.0,1.0,1.0,1.0 -46440000.0,1.0,1.0,1.0 -46500000.0,1.0,1.0,1.0 -46560000.0,1.0,1.0,1.0 -46620000.0,1.0,1.0,1.0 -46680000.0,1.0,1.0,1.0 -46740000.0,1.0,1.0,1.0 -46800000.0,1.0,1.0,1.0 -46860000.0,1.0,1.0,1.0 -46920000.0,1.0,1.0,1.0 -46980000.0,1.0,1.0,1.0 -47040000.0,1.0,1.0,1.0 -47100000.0,1.0,1.0,1.0 -47160000.0,1.0,1.0,1.0 -47220000.0,1.0,1.0,1.0 -47280000.0,1.0,1.0,1.0 -47340000.0,1.0,1.0,1.0 -47400000.0,1.0,1.0,1.0 -47460000.0,1.0,1.0,1.0 -47520000.0,1.0,1.0,1.0 -47580000.0,1.0,1.0,1.0 -47640000.0,1.0,1.0,1.0 -47700000.0,1.0,1.0,1.0 -47760000.0,1.0,1.0,1.0 -47820000.0,1.0,1.0,1.0 -47880000.0,1.0,1.0,1.0 -47940000.0,1.0,1.0,1.0 -48000000.0,1.0,1.0,1.0 -48060000.0,1.0,1.0,1.0 -48120000.0,1.0,1.0,1.0 -48180000.0,1.0,1.0,1.0 -48240000.0,1.0,1.0,1.0 -48300000.0,1.0,1.0,1.0 -48360000.0,1.0,1.0,1.0 -48420000.0,1.0,1.0,1.0 -48480000.0,1.0,1.0,1.0 -48540000.0,1.0,1.0,1.0 -48600000.0,1.0,1.0,1.0 -48660000.0,1.0,1.0,1.0 -48720000.0,1.0,1.0,1.0 -48780000.0,1.0,1.0,1.0 -48840000.0,1.0,1.0,1.0 -48900000.0,1.0,1.0,1.0 -48960000.0,1.0,1.0,1.0 -49020000.0,1.0,1.0,1.0 -49080000.0,1.0,1.0,1.0 -49140000.0,1.0,1.0,1.0 -49200000.0,1.0,1.0,1.0 -49260000.0,1.0,1.0,1.0 -49320000.0,1.0,1.0,1.0 -49380000.0,1.0,1.0,1.0 -49440000.0,1.0,1.0,1.0 -49500000.0,1.0,1.0,1.0 -49560000.0,1.0,1.0,1.0 -49620000.0,1.0,1.0,1.0 -49680000.0,1.0,1.0,1.0 -49740000.0,1.0,1.0,1.0 -49800000.0,1.0,1.0,1.0 -49860000.0,1.0,1.0,1.0 -49920000.0,1.0,1.0,1.0 -49980000.0,1.0,1.0,1.0 -50040000.0,1.0,1.0,1.0 -50100000.0,1.0,1.0,1.0 -50160000.0,1.0,1.0,1.0 -50220000.0,1.0,1.0,1.0 -50280000.0,1.0,1.0,1.0 -50340000.0,1.0,1.0,1.0 -50400000.0,1.0,1.0,1.0 -50460000.0,1.0,1.0,1.0 -50520000.0,1.0,1.0,1.0 -50580000.0,1.0,1.0,1.0 -50640000.0,1.0,1.0,1.0 -50700000.0,1.0,1.0,1.0 -50760000.0,1.0,1.0,1.0 -50820000.0,1.0,1.0,1.0 -50880000.0,1.0,1.0,1.0 -50940000.0,1.0,1.0,1.0 -51000000.0,1.0,1.0,1.0 -51060000.0,1.0,1.0,1.0 -51120000.0,1.0,1.0,1.0 -51180000.0,1.0,1.0,1.0 -51240000.0,1.0,1.0,1.0 -51300000.0,1.0,1.0,1.0 -51360000.0,1.0,1.0,1.0 -51420000.0,1.0,1.0,1.0 -51480000.0,1.0,1.0,1.0 -51540000.0,1.0,1.0,1.0 -51600000.0,1.0,1.0,1.0 -51660000.0,1.0,1.0,1.0 -51720000.0,1.0,1.0,1.0 -51780000.0,1.0,1.0,1.0 -51840000.0,1.0,1.0,1.0 -51900000.0,1.0,1.0,1.0 -51960000.0,1.0,1.0,1.0 -52020000.0,1.0,1.0,1.0 -52080000.0,1.0,1.0,1.0 -52140000.0,1.0,1.0,1.0 -52200000.0,1.0,1.0,1.0 -52260000.0,1.0,1.0,1.0 -52320000.0,1.0,1.0,1.0 -52380000.0,1.0,1.0,1.0 -52440000.0,1.0,1.0,1.0 -52500000.0,1.0,1.0,1.0 -52560000.0,1.0,1.0,1.0 -52620000.0,1.0,1.0,1.0 -52680000.0,1.0,1.0,1.0 -52740000.0,1.0,1.0,1.0 -52800000.0,1.0,1.0,1.0 -52860000.0,1.0,1.0,1.0 -52920000.0,1.0,1.0,1.0 -52980000.0,1.0,1.0,1.0 -53040000.0,1.0,1.0,1.0 -53100000.0,1.0,1.0,1.0 -53160000.0,1.0,1.0,1.0 -53220000.0,1.0,1.0,1.0 -53280000.0,1.0,1.0,1.0 -53340000.0,1.0,1.0,1.0 -53400000.0,1.0,1.0,1.0 -53460000.0,1.0,1.0,1.0 -53520000.0,1.0,1.0,1.0 -53580000.0,1.0,1.0,1.0 -53640000.0,1.0,1.0,1.0 -53700000.0,1.0,1.0,1.0 -53760000.0,1.0,1.0,1.0 -53820000.0,1.0,1.0,1.0 -53880000.0,1.0,1.0,1.0 -53940000.0,1.0,1.0,1.0 -54000000.0,1.0,1.0,1.0 -54060000.0,1.0,1.0,1.0 -54120000.0,1.0,1.0,1.0 -54180000.0,1.0,1.0,1.0 -54240000.0,1.0,1.0,1.0 -54300000.0,1.0,1.0,1.0 -54360000.0,1.0,1.0,1.0 -54420000.0,1.0,1.0,1.0 -54480000.0,1.0,1.0,1.0 -54540000.0,1.0,1.0,1.0 -54600000.0,1.0,1.0,1.0 -54660000.0,1.0,1.0,1.0 -54720000.0,1.0,1.0,1.0 -54780000.0,1.0,1.0,1.0 -54840000.0,1.0,1.0,1.0 -54900000.0,1.0,1.0,1.0 -54960000.0,1.0,1.0,1.0 -55020000.0,1.0,1.0,1.0 -55080000.0,1.0,1.0,1.0 -55140000.0,1.0,1.0,1.0 -55200000.0,1.0,1.0,1.0 -55260000.0,1.0,1.0,1.0 -55320000.0,1.0,1.0,1.0 -55380000.0,1.0,1.0,1.0 -55440000.0,1.0,1.0,1.0 -55500000.0,1.0,1.0,1.0 -55560000.0,1.0,1.0,1.0 -55620000.0,1.0,1.0,1.0 -55680000.0,1.0,1.0,1.0 -55740000.0,1.0,1.0,1.0 -55800000.0,1.0,1.0,1.0 -55860000.0,1.0,1.0,1.0 -55920000.0,1.0,1.0,1.0 -55980000.0,1.0,1.0,1.0 -56040000.0,1.0,1.0,1.0 -56100000.0,1.0,1.0,1.0 -56160000.0,1.0,1.0,1.0 -56220000.0,1.0,1.0,1.0 -56280000.0,1.0,1.0,1.0 -56340000.0,1.0,1.0,1.0 -56400000.0,1.0,1.0,1.0 -56460000.0,1.0,1.0,1.0 -56520000.0,1.0,1.0,1.0 -56580000.0,1.0,1.0,1.0 -56640000.0,1.0,1.0,1.0 -56700000.0,1.0,1.0,1.0 -56760000.0,1.0,1.0,1.0 -56820000.0,1.0,1.0,1.0 -56880000.0,1.0,1.0,1.0 -56940000.0,1.0,1.0,1.0 -57000000.0,1.0,1.0,1.0 -57060000.0,1.0,1.0,1.0 -57120000.0,1.0,1.0,1.0 -57180000.0,1.0,1.0,1.0 -57240000.0,1.0,1.0,1.0 -57300000.0,1.0,1.0,1.0 -57360000.0,1.0,1.0,1.0 -57420000.0,1.0,1.0,1.0 -57480000.0,1.0,1.0,1.0 -57540000.0,1.0,1.0,1.0 -57600000.0,1.0,1.0,1.0 -57660000.0,1.0,1.0,1.0 -57720000.0,1.0,1.0,1.0 -57780000.0,1.0,1.0,1.0 -57840000.0,1.0,1.0,1.0 -57900000.0,1.0,1.0,1.0 -57960000.0,1.0,1.0,1.0 -58020000.0,1.0,1.0,1.0 -58080000.0,1.0,1.0,1.0 -58140000.0,1.0,1.0,1.0 -58200000.0,1.0,1.0,1.0 -58260000.0,1.0,1.0,1.0 -58320000.0,1.0,1.0,1.0 -58380000.0,1.0,1.0,1.0 -58440000.0,1.0,1.0,1.0 -58500000.0,1.0,1.0,1.0 -58560000.0,1.0,1.0,1.0 -58620000.0,1.0,1.0,1.0 -58680000.0,1.0,1.0,1.0 -58740000.0,1.0,1.0,1.0 -58800000.0,1.0,1.0,1.0 -58860000.0,1.0,1.0,1.0 -58920000.0,1.0,1.0,1.0 -58980000.0,1.0,1.0,1.0 -59040000.0,1.0,1.0,1.0 -59100000.0,1.0,1.0,1.0 -59160000.0,1.0,1.0,1.0 -59220000.0,1.0,1.0,1.0 -59280000.0,1.0,1.0,1.0 -59340000.0,1.0,1.0,1.0 -59400000.0,1.0,1.0,1.0 -59460000.0,1.0,1.0,1.0 -59520000.0,1.0,1.0,1.0 -59580000.0,1.0,1.0,1.0 -59640000.0,1.0,1.0,1.0 -59700000.0,1.0,1.0,1.0 -59760000.0,1.0,1.0,1.0 -59820000.0,1.0,1.0,1.0 -59880000.0,1.0,1.0,1.0 -59940000.0,1.0,1.0,1.0 -60000000.0,1.0,1.0,1.0 -60060000.0,1.0,1.0,1.0 -60120000.0,1.0,1.0,1.0 -60180000.0,1.0,1.0,1.0 -60240000.0,1.0,1.0,1.0 -60300000.0,1.0,1.0,1.0 -60360000.0,1.0,1.0,1.0 -60420000.0,1.0,1.0,1.0 -60480000.0,1.0,1.0,1.0 -60540000.0,1.0,1.0,1.0 -60600000.0,1.0,1.0,1.0 -60660000.0,1.0,1.0,1.0 -60720000.0,1.0,1.0,1.0 -60780000.0,1.0,1.0,1.0 -60840000.0,1.0,1.0,1.0 -60900000.0,1.0,1.0,1.0 -60960000.0,1.0,1.0,1.0 -61020000.0,1.0,1.0,1.0 -61080000.0,1.0,1.0,1.0 -61140000.0,1.0,1.0,1.0 -61200000.0,1.0,1.0,1.0 -61260000.0,1.0,1.0,1.0 -61320000.0,1.0,1.0,1.0 -61380000.0,1.0,1.0,1.0 -61440000.0,1.0,1.0,1.0 -61500000.0,1.0,1.0,1.0 -61560000.0,1.0,1.0,1.0 -61620000.0,1.0,1.0,1.0 -61680000.0,1.0,1.0,1.0 -61740000.0,1.0,1.0,1.0 -61800000.0,1.0,1.0,1.0 -61860000.0,1.0,1.0,1.0 -61920000.0,1.0,1.0,1.0 -61980000.0,1.0,1.0,1.0 -62040000.0,1.0,1.0,1.0 -62100000.0,1.0,1.0,1.0 -62160000.0,1.0,1.0,1.0 -62220000.0,1.0,1.0,1.0 -62280000.0,1.0,1.0,1.0 -62340000.0,1.0,1.0,1.0 -62400000.0,1.0,1.0,1.0 -62460000.0,1.0,1.0,1.0 -62520000.0,1.0,1.0,1.0 -62580000.0,1.0,1.0,1.0 -62640000.0,1.0,1.0,1.0 -62700000.0,1.0,1.0,1.0 -62760000.0,1.0,1.0,1.0 -62820000.0,1.0,1.0,1.0 -62880000.0,1.0,1.0,1.0 -62940000.0,1.0,1.0,1.0 -63000000.0,1.0,1.0,1.0 -63060000.0,1.0,1.0,1.0 -63120000.0,1.0,1.0,1.0 -63180000.0,1.0,1.0,1.0 -63240000.0,1.0,1.0,1.0 -63300000.0,1.0,1.0,1.0 -63360000.0,1.0,1.0,1.0 -63420000.0,1.0,1.0,1.0 -63480000.0,1.0,1.0,1.0 -63540000.0,1.0,1.0,1.0 -63600000.0,1.0,1.0,1.0 -63660000.0,1.0,1.0,1.0 -63720000.0,1.0,1.0,1.0 -63780000.0,1.0,1.0,1.0 -63840000.0,1.0,1.0,1.0 -63900000.0,1.0,1.0,1.0 -63960000.0,1.0,1.0,1.0 -64020000.0,1.0,1.0,1.0 -64080000.0,1.0,1.0,1.0 -64140000.0,1.0,1.0,1.0 -64200000.0,1.0,1.0,1.0 -64260000.0,1.0,1.0,1.0 -64320000.0,1.0,1.0,1.0 -64380000.0,1.0,1.0,1.0 -64440000.0,1.0,1.0,1.0 -64500000.0,1.0,1.0,1.0 -64560000.0,1.0,1.0,1.0 -64620000.0,1.0,1.0,1.0 -64680000.0,1.0,1.0,1.0 -64740000.0,1.0,1.0,1.0 -64800000.0,1.0,1.0,1.0 -64860000.0,1.0,1.0,1.0 -64920000.0,1.0,1.0,1.0 -64980000.0,1.0,1.0,1.0 -65040000.0,1.0,1.0,1.0 -65100000.0,1.0,1.0,1.0 -65160000.0,1.0,1.0,1.0 -65220000.0,1.0,1.0,1.0 -65280000.0,1.0,1.0,1.0 -65340000.0,1.0,1.0,1.0 -65400000.0,1.0,1.0,1.0 -65460000.0,1.0,1.0,1.0 -65520000.0,1.0,1.0,1.0 -65580000.0,1.0,1.0,1.0 -65640000.0,1.0,1.0,1.0 -65700000.0,1.0,1.0,1.0 -65760000.0,1.0,1.0,1.0 -65820000.0,1.0,1.0,1.0 -65880000.0,1.0,1.0,1.0 -65940000.0,1.0,1.0,1.0 -66000000.0,1.0,1.0,1.0 -66060000.0,1.0,1.0,1.0 -66120000.0,1.0,1.0,1.0 -66180000.0,1.0,1.0,1.0 -66240000.0,1.0,1.0,1.0 -66300000.0,1.0,1.0,1.0 -66360000.0,1.0,1.0,1.0 -66420000.0,1.0,1.0,1.0 -66480000.0,1.0,1.0,1.0 -66540000.0,1.0,1.0,1.0 -66600000.0,1.0,1.0,1.0 -66660000.0,1.0,1.0,1.0 -66720000.0,1.0,1.0,1.0 -66780000.0,1.0,1.0,1.0 -66840000.0,1.0,1.0,1.0 -66900000.0,1.0,1.0,1.0 -66960000.0,1.0,1.0,1.0 -67020000.0,1.0,1.0,1.0 -67080000.0,1.0,1.0,1.0 -67140000.0,1.0,1.0,1.0 -67200000.0,1.0,1.0,1.0 -67260000.0,1.0,1.0,1.0 -67320000.0,1.0,1.0,1.0 -67380000.0,1.0,1.0,1.0 -67440000.0,1.0,1.0,1.0 -67500000.0,1.0,1.0,1.0 -67560000.0,1.0,1.0,1.0 -67620000.0,1.0,1.0,1.0 -67680000.0,1.0,1.0,1.0 -67740000.0,1.0,1.0,1.0 -67800000.0,1.0,1.0,1.0 -67860000.0,1.0,1.0,1.0 -67920000.0,1.0,1.0,1.0 -67980000.0,1.0,1.0,1.0 -68040000.0,1.0,1.0,1.0 -68100000.0,1.0,1.0,1.0 -68160000.0,1.0,1.0,1.0 -68220000.0,1.0,1.0,1.0 -68280000.0,1.0,1.0,1.0 -68340000.0,1.0,1.0,1.0 -68400000.0,1.0,1.0,1.0 -68460000.0,1.0,1.0,1.0 -68520000.0,1.0,1.0,1.0 -68580000.0,1.0,1.0,1.0 -68640000.0,1.0,1.0,1.0 -68700000.0,1.0,1.0,1.0 -68760000.0,1.0,1.0,1.0 -68820000.0,1.0,1.0,1.0 -68880000.0,1.0,1.0,1.0 -68940000.0,1.0,1.0,1.0 -69000000.0,1.0,1.0,1.0 -69060000.0,1.0,1.0,1.0 -69120000.0,1.0,1.0,1.0 -69180000.0,1.0,1.0,1.0 -69240000.0,1.0,1.0,1.0 -69300000.0,1.0,1.0,1.0 -69360000.0,1.0,1.0,1.0 -69420000.0,1.0,1.0,1.0 -69480000.0,1.0,1.0,1.0 -69540000.0,1.0,1.0,1.0 -69600000.0,1.0,1.0,1.0 -69660000.0,1.0,1.0,1.0 -69720000.0,1.0,1.0,1.0 -69780000.0,1.0,1.0,1.0 -69840000.0,1.0,1.0,1.0 -69900000.0,1.0,1.0,1.0 -69960000.0,1.0,1.0,1.0 -70020000.0,1.0,1.0,1.0 -70080000.0,1.0,1.0,1.0 -70140000.0,1.0,1.0,1.0 -70200000.0,1.0,1.0,1.0 -70260000.0,1.0,1.0,1.0 -70320000.0,1.0,1.0,1.0 -70380000.0,1.0,1.0,1.0 -70440000.0,1.0,1.0,1.0 -70500000.0,1.0,1.0,1.0 -70560000.0,1.0,1.0,1.0 -70620000.0,1.0,1.0,1.0 -70680000.0,1.0,1.0,1.0 -70740000.0,1.0,1.0,1.0 -70800000.0,1.0,1.0,1.0 -70860000.0,1.0,1.0,1.0 -70920000.0,1.0,1.0,1.0 -70980000.0,1.0,1.0,1.0 -71040000.0,1.0,1.0,1.0 -71100000.0,1.0,1.0,1.0 -71160000.0,1.0,1.0,1.0 -71220000.0,1.0,1.0,1.0 -71280000.0,1.0,1.0,1.0 -71340000.0,1.0,1.0,1.0 -71400000.0,1.0,1.0,1.0 -71460000.0,1.0,1.0,1.0 -71520000.0,1.0,1.0,1.0 -71580000.0,1.0,1.0,1.0 -71640000.0,1.0,1.0,1.0 -71700000.0,1.0,1.0,1.0 -71760000.0,1.0,1.0,1.0 -71820000.0,1.0,1.0,1.0 -71880000.0,1.0,1.0,1.0 -71940000.0,1.0,1.0,1.0 -72006000.0,1.1,1.0,1.0 -72072000.0,1.1,1.0,1.0 -72138000.0,1.1,1.0,1.0 -72204000.0,1.1,1.0,1.0 -72270000.0,1.1,1.0,1.0 -72336000.0,1.1,1.0,1.0 -72402000.0,1.1,1.0,1.0 -72468000.0,1.1,1.0,1.0 -72534000.0,1.1,1.0,1.0 -72600000.0,1.1,1.0,1.0 -72666000.0,1.1,1.0,1.0 -72732000.0,1.1,1.0,1.0 -72798000.0,1.1,1.0,1.0 -72864000.0,1.1,1.0,1.0 -72930000.0,1.1,1.0,1.0 -72996000.0,1.1,1.0,1.0 -73062000.0,1.1,1.0,1.0 -73128000.0,1.1,1.0,1.0 -73194000.0,1.1,1.0,1.0 -73260000.0,1.1,1.0,1.0 -73326000.0,1.1,1.0,1.0 -73392000.0,1.1,1.0,1.0 -73458000.0,1.1,1.0,1.0 -73524000.0,1.1,1.0,1.0 -73590000.0,1.1,1.0,1.0 -73656000.0,1.1,1.0,1.0 -73722000.0,1.1,1.0,1.0 -73788000.0,1.1,1.0,1.0 -73854000.0,1.1,1.0,1.0 -73920000.0,1.1,1.0,1.0 -73986000.0,1.1,1.0,1.0 -74052000.0,1.1,1.0,1.0 -74118000.0,1.1,1.0,1.0 -74184000.0,1.1,1.0,1.0 -74250000.0,1.1,1.0,1.0 -74316000.0,1.1,1.0,1.0 -74382000.0,1.1,1.0,1.0 -74448000.0,1.1,1.0,1.0 -74514000.0,1.1,1.0,1.0 -74580000.0,1.1,1.0,1.0 -74646000.0,1.1,1.0,1.0 -74712000.0,1.1,1.0,1.0 -74778000.0,1.1,1.0,1.0 -74844000.0,1.1,1.0,1.0 -74910000.0,1.1,1.0,1.0 -74976000.0,1.1,1.0,1.0 -75042000.0,1.1,1.0,1.0 -75108000.0,1.1,1.0,1.0 -75174000.0,1.1,1.0,1.0 -75240000.0,1.1,1.0,1.0 -75306000.0,1.1,1.0,1.0 -75372000.0,1.1,1.0,1.0 -75438000.0,1.1,1.0,1.0 -75504000.0,1.1,1.0,1.0 -75570000.0,1.1,1.0,1.0 -75636000.0,1.1,1.0,1.0 -75702000.0,1.1,1.0,1.0 -75768000.0,1.1,1.0,1.0 -75834000.0,1.1,1.0,1.0 -75900000.0,1.1,1.0,1.0 -75966000.0,1.1,1.0,1.0 -76032000.0,1.1,1.0,1.0 -76098000.0,1.1,1.0,1.0 -76164000.0,1.1,1.0,1.0 -76230000.0,1.1,1.0,1.0 -76296000.0,1.1,1.0,1.0 -76362000.0,1.1,1.0,1.0 -76428000.0,1.1,1.0,1.0 -76494000.0,1.1,1.0,1.0 -76560000.0,1.1,1.0,1.0 -76626000.0,1.1,1.0,1.0 -76692000.0,1.1,1.0,1.0 -76758000.0,1.1,1.0,1.0 -76824000.0,1.1,1.0,1.0 -76890000.0,1.1,1.0,1.0 -76956000.0,1.1,1.0,1.0 -77022000.0,1.1,1.0,1.0 -77088000.0,1.1,1.0,1.0 -77154000.0,1.1,1.0,1.0 -77220000.0,1.1,1.0,1.0 -77286000.0,1.1,1.0,1.0 -77352000.0,1.1,1.0,1.0 -77418000.0,1.1,1.0,1.0 -77484000.0,1.1,1.0,1.0 -77550000.0,1.1,1.0,1.0 -77616000.0,1.1,1.0,1.0 -77682000.0,1.1,1.0,1.0 -77748000.0,1.1,1.0,1.0 -77814000.0,1.1,1.0,1.0 -77880000.0,1.1,1.0,1.0 -77946000.0,1.1,1.0,1.0 -78012000.0,1.1,1.0,1.0 -78078000.0,1.1,1.0,1.0 -78144000.0,1.1,1.0,1.0 -78210000.0,1.1,1.0,1.0 -78276000.0,1.1,1.0,1.0 -78342000.0,1.1,1.0,1.0 -78408000.0,1.1,1.0,1.0 -78474000.0,1.1,1.0,1.0 -78540000.0,1.1,1.0,1.0 -78606000.0,1.1,1.0,1.0 -78672000.0,1.1,1.0,1.0 -78738000.0,1.1,1.0,1.0 -78804000.0,1.1,1.0,1.0 -78870000.0,1.1,1.0,1.0 -78936000.0,1.1,1.0,1.0 -79002000.0,1.1,1.0,1.0 -79068000.0,1.1,1.0,1.0 -79134000.0,1.1,1.0,1.0 -79200000.0,1.1,1.0,1.0 -79266000.0,1.1,1.0,1.0 -79332000.0,1.1,1.0,1.0 -79398000.0,1.1,1.0,1.0 -79464000.0,1.1,1.0,1.0 -79530000.0,1.1,1.0,1.0 -79596000.0,1.1,1.0,1.0 -79662000.0,1.1,1.0,1.0 -79728000.0,1.1,1.0,1.0 -79794000.0,1.1,1.0,1.0 -79860000.0,1.1,1.0,1.0 -79926000.0,1.1,0.9998957355854446,0.9998957355854446 -79992000.0,1.1,0.9996872502360732,0.999791492910759 -80058000.0,1.1,0.9993746200108351,0.9996872719691441 -80124000.0,1.1,0.9989579535025962,0.9995830727538044 -80190000.0,1.1,0.9984373917759143,0.9994788952579469 -80256000.0,1.1,0.9978131082879343,0.9993747394747812 -80322000.0,1.1,0.9970853087924654,0.99927060539752 -80388000.0,1.1,0.9962542312273122,0.999166493019379 -80454000.0,1.1,0.9953201455849489,0.9990624023335765 -80520000.0,1.1,0.9942833537666311,0.9989583333333333 -80586000.0,1.1,0.9931441894200597,0.9988542860118738 -80652000.0,1.1,0.9919030177607137,0.9987502603624245 -80718000.0,1.1,0.9905602353769908,0.998646256378215 -80784000.0,1.1,0.9891162700192984,0.9985422740524781 -80850000.0,1.1,0.9875715803732504,0.9984383133784487 -80916000.0,1.1,0.9859266558171426,0.998334374349365 -80982000.0,1.1,0.9841820161638802,0.9982304569584678 -81048000.0,1.1,0.9823382113875533,0.9981265611990009 -81114000.0,1.1,0.9803958213348565,0.9980226870642106 -81180000.0,1.1,0.9783554554215684,0.9979188345473464 -81246000.0,1.1,0.9762177523143108,0.9978150036416606 -81312000.0,1.1,0.9739833795978193,0.9977111943404077 -81378000.0,1.1,0.9716530334279712,0.9976074066368459 -81444000.0,1.1,0.9692274381708178,0.9975036405242353 -81510000.0,1.1,0.9667073460278879,0.9973998959958398 -81576000.0,1.1,0.9640935366480288,0.9972961730449251 -81642000.0,1.1,0.961386816726068,0.9971924716647603 -81708000.0,1.1,0.958588019588583,0.9970887918486171 -81774000.0,1.1,0.9556980047670767,0.9969851335897703 -81840000.0,1.1,0.9527176575588633,0.9968814968814969 -81906000.0,1.1,0.9496478885759794,0.996777881717077 -81972000.0,1.1,0.9464896332824403,0.9966742880897941 -82038000.0,1.1,0.9432438515201707,0.9965707159929336 -82104000.0,1.1,0.9399115270239439,0.9964671654197838 -82170000.0,1.1,0.9364936669256748,0.9963636363636362 -82236000.0,1.1,0.9329913012484127,0.996260128817785 -82302000.0,1.1,0.9294054823903891,0.996156642775527 -82368000.0,1.1,0.9257372845994841,0.9960531782301622 -82434000.0,1.1,0.9219878034384726,0.9959497351749923 -82500000.0,1.1,0.9181581552414281,0.9958463136033229 -82566000.0,1.1,0.9142494765616546,0.9957429135084622 -82632000.0,1.1,0.910262923611531,0.9956395348837209 -82698000.0,1.1,0.9061996716946521,0.9955361777224127 -82764000.0,1.1,0.9020609146306532,0.9954328420178533 -82830000.0,1.1,0.8978478641731151,0.9953295277633628 -82896000.0,1.1,0.8935617494209395,0.9952262349522621 -82962000.0,1.1,0.8892038162235975,0.9951229635778769 -83028000.0,1.1,0.8847753265806494,0.9950197136335338 -83094000.0,1.1,0.8802775580359402,0.9949164851125636 -83160000.0,1.1,0.8757118030668741,0.9948132780082988 -83220000.0,1.0,0.8710793684691757,0.9947100923140753 -83280000.0,1.0,0.8663815747375434,0.9946069280232316 -83340000.0,1.0,0.861619755442605,0.9945037851291091 -83400000.0,1.0,0.8567952566045813,0.9944006636250519 -83460000.0,1.0,0.8519094360640679,0.9942975635044063 -83520000.0,1.0,0.8469636628503432,0.9941944847605224 -83580000.0,1.0,0.8419593165476097,0.9940914273867523 -83640000.0,1.0,0.8368977866595746,0.9939883913764509 -83700000.0,1.0,0.8317804719727764,0.9938853767229764 -83760000.0,1.0,0.8266087799190596,0.993782383419689 -83820000.0,1.0,0.8213841259376004,0.9936794114599523 -83880000.0,1.0,0.8161079328368822,0.9935764608371321 -83940000.0,1.0,0.8107816301570185,0.9934735315445975 -84000000.0,1.0,0.805406653532816,0.9933706235757197 -84060000.0,1.0,0.7999844440579705,0.9932677369238736 -84120000.0,1.0,0.7945164476507806,0.9931648715824357 -84180000.0,1.0,0.7890041144217651,0.9930620275447861 -84240000.0,1.0,0.7834488980435625,0.9929592048043073 -84300000.0,1.0,0.7778522551234873,0.9928564033543844 -84360000.0,1.0,0.7722156445791142,0.9927536231884058 -84420000.0,1.0,0.7665405270172554,0.9926508642997618 -84480000.0,1.0,0.760828364116692,0.9925481266818462 -84540000.0,1.0,0.7550806180150135,0.9924454103280553 -84600000.0,1.0,0.749298750699915,0.9923427152317881 -84660000.0,1.0,0.7434842234052959,0.9922400413864458 -84720000.0,1.0,0.737638496012496,0.9921373887854333 -84780000.0,1.0,0.7317630264570019,0.992034757422158 -84840000.0,1.0,0.7258592701409441,0.9919321472900289 -84900000.0,1.0,0.7199286793517069,0.9918295583824595 -84960000.0,1.0,0.7139727026869563,0.9917269906928643 -85020000.0,1.0,0.7079927844863935,0.9916244442146623 -85080000.0,1.0,0.7019903642705245,0.9915219189412737 -85140000.0,1.0,0.6959668761867394,0.9914194148661222 -85200000.0,1.0,0.6899237484629761,0.9913169319826339 -85260000.0,1.0,0.6838624028692445,0.9912144702842377 -85320000.0,1.0,0.6777842541872732,0.9911120297643655 -85380000.0,1.0,0.6716907096885346,0.9910096104164513 -85440000.0,1.0,0.6655831686208976,0.9909072122339326 -85500000.0,1.0,0.6594630217041438,0.990804835210249 -85560000.0,1.0,0.6533316506345804,0.9907024793388428 -85620000.0,1.0,0.6471904275989696,0.9906001446131596 -85680000.0,1.0,0.6410407147979879,0.9904978310266473 -85740000.0,1.0,0.634883863979418,0.9903955385727565 -85800000.0,1.0,0.62872121598127,0.99029326724494 -85860000.0,1.0,0.6225541002850159,0.9901910170366547 -85920000.0,1.0,0.6163838345791145,0.9900887879413588 -85980000.0,1.0,0.6102117243329934,0.9899865799525136 -86040000.0,1.0,0.6040390623816481,0.9898843930635838 -86100000.0,1.0,0.5978671285210037,0.9897822272680359 -86160000.0,1.0,0.5916971891141822,0.9896800825593397 -86220000.0,1.0,0.5855304967088028,0.9895779589309669 -86280000.0,1.0,0.5793682896654372,0.9894758563763927 -86340000.0,1.0,0.5732117917973324,0.9893737748890953 -86400000.0,1.0,0.5670622120214996,0.9892717144625541 -86460000.0,1.0,0.5609207440212668,0.9891696750902527 -86520000.0,1.0,0.5547885659203742,0.9890676567656764 -86580000.0,1.0,0.5486668399686901,0.9889656594823141 -86640000.0,1.0,0.54255671223961,0.9888636832336563 -86700000.0,1.0,0.5364593123391957,0.9887617280131972 -86760000.0,1.0,0.5303757531271017,0.9886597938144329 -86820000.0,1.0,0.5243071304493254,0.9885578806308629 -86880000.0,1.0,0.5182545228828108,0.9884559884559884 -86940000.0,1.0,0.5122189914919257,0.9883541172833143 -87000000.0,1.0,0.5062015795968227,0.9882522671063477 -87060000.0,1.0,0.5002033125536866,0.9881504379185984 -87120000.0,1.0,0.4942251975468632,0.9880486297135793 -87180000.0,1.0,0.48826822339285236,0.9879468424848047 -87240000.0,1.0,0.4823333603561447,0.9878450762257929 -87300000.0,1.0,0.4764215599768696,0.9877433309300646 -87360000.0,1.0,0.47053375491021404,0.987641606591143 -87420000.0,1.0,0.464670858777567,0.9875399032025538 -87480000.0,1.0,0.45883376602933146,0.9874382207578253 -87540000.0,1.0,0.45302335181934406,0.987336559250489 -87600000.0,1.0,0.4472404718908286,0.9872349186740785 -87660000.0,1.0,0.44148596247380817,0.9871332990221308 -87720000.0,1.0,0.4357606401938885,0.9870317002881845 -87780000.0,1.0,0.4300653019923218,0.9869301224657817 -87840000.0,1.0,0.42440072505725107,0.9868285655484668 -87900000.0,1.0,0.4187676667660292,0.9867270295297871 -87960000.0,1.0,0.4131668646385,0.9866255144032922 -88020000.0,1.0,0.4075990363011228,0.9865240201625347 -88080000.0,1.0,0.40206487946181524,0.9864225468010697 -88140000.0,1.0,0.39656507189538287,0.9863210943124548 -88200000.0,1.0,0.39110027143939957,0.9862196626902509 -88260000.0,1.0,0.38567111600039505,0.9861182519280206 -88320000.0,1.0,0.3802782235702024,0.9860168620193297 -88380000.0,1.0,0.37492219225231216,0.9859154929577463 -88440000.0,1.0,0.369603600298075,0.985814144736842 -88500000.0,1.0,0.3643230061525891,0.9857128173501902 -88560000.0,1.0,0.3590809485101058,0.9856115107913669 -88620000.0,1.0,0.3538779463787806,0.9855102250539511 -88680000.0,1.0,0.3487144991545937,0.9854089601315248 -88740000.0,1.0,0.34359108670425903,0.9853077160176718 -88800000.0,1.0,0.338508169456939,0.9852064927059792 -88860000.0,1.0,0.33346618850457577,0.9851052901900359 -88920000.0,1.0,0.3284655657106493,0.9850041084634346 -88980000.0,1.0,0.3235067038271672,0.98490294751977 -89040000.0,1.0,0.3185899866196892,0.9848018073526391 -89100000.0,1.0,0.31371577900018677,0.9847006879556421 -89160000.0,1.0,0.308884427167535,0.9845995893223819 -89220000.0,1.0,0.3040962587554317,0.9844985114464633 -89280000.0,1.0,0.2993515829875375,0.9843974543214946 -89340000.0,1.0,0.29465069083962686,0.9842964179410859 -89400000.0,1.0,0.28999385520854076,0.9841954022988504 -89460000.0,1.0,0.2853813310877277,0.9840944073884044 -89520000.0,1.0,0.28081335574915944,0.9839934332033653 -89580000.0,1.0,0.2762901489314086,0.9838924797373552 -89640000.0,1.0,0.27181191303366925,0.9837915469839966 -89700000.0,1.0,0.26737883331550805,0.9836906349369166 -89760000.0,1.0,0.26299107810212535,0.9835897435897436 -89820000.0,1.0,0.25864879899491156,0.9834888729361093 -89880000.0,1.0,0.25435213108707977,0.9833880229696473 -89940000.0,1.0,0.2501011931841582,0.9832871936839946 -90000000.0,1.0,0.24589608802912413,0.9831863850727905 -90060000.0,1.0,0.24173690253196312,0.983085597129677 -90120000.0,1.0,0.23762370800343652,0.9829848298482986 -90180000.0,1.0,0.23355656039284162,0.9828840832223017 -90240000.0,1.0,0.2295355005295502,0.982783357245337 -90300000.0,1.0,0.22556055436811012,0.9826826519110565 -90360000.0,1.0,0.22163173323669835,0.9825819672131146 -90420000.0,1.0,0.2177713444405673,0.9825819672131146 -90480000.0,1.0,0.2139781960230574,0.9825819672131146 -90540000.0,1.0,0.2102511167890492,0.9825819672131146 -90600000.0,1.0,0.2065889559433383,0.9825819672131146 -90660000.0,1.0,0.2029905827353088,0.9825819672131145 -90720000.0,1.0,0.19945488610979623,0.9825819672131146 -90780000.0,1.0,0.19598077436403133,0.9825819672131147 -90840000.0,1.0,0.19256717481055946,0.9825819672131147 -90900000.0,1.0,0.18921303344603124,0.9825819672131146 -90960000.0,1.0,0.18591731462576222,0.9825819672131145 -91020000.0,1.0,0.182679000743961,0.9825819672131146 -91080000.0,1.0,0.17949709191952723,0.9825819672131145 -91140000.0,1.0,0.17637060568732235,0.9825819672131147 -91200000.0,1.0,0.17329857669481774,0.9825819672131146 -91260000.0,1.0,0.17028005640402682,0.9825819672131145 -91320000.0,1.0,0.16731411279862882,0.9825819672131147 -91380000.0,1.0,0.16439983009619366,0.9825819672131146 -91440000.0,1.0,0.16153630846541978,0.9825819672131146 -91500000.0,1.0,0.15872266374829666,0.9825819672131146 -91560000.0,1.0,0.15595802718710705,0.9825819672131146 -91620000.0,1.0,0.15324154515618407,0.9825819672131147 -91680000.0,1.0,0.1505723788983407,0.9825819672131146 -91740000.0,1.0,0.14794970426589008,0.9825819672131147 -91800000.0,1.0,0.1453727114661768,0.9825819672131146 -91860000.0,1.0,0.1428406048115405,0.9825819672131146 -91920000.0,1.0,0.14035260247363457,0.9825819672131146 -91980000.0,1.0,0.13790793624202413,0.9825819672131147 -92040000.0,1.0,0.13550585128698886,0.9825819672131146 -92100000.0,1.0,0.13314560592645727,0.9825819672131146 -92160000.0,1.0,0.13082647139700054,0.9825819672131147 -92220000.0,1.0,0.12854773162881508,0.9825819672131147 -92280000.0,1.0,0.12630868302462464,0.9825819672131146 -92340000.0,1.0,0.12410863424243343,0.9825819672131147 -92400000.0,1.0,0.12194690598206316,0.9825819672131146 -92460000.0,1.0,0.11982283077540837,0.9825819672131147 -92520000.0,1.0,0.11773575278034487,0.9825819672131145 -92580000.0,1.0,0.1156850275782282,0.9825819672131146 -92640000.0,1.0,0.11367002197491888,0.9825819672131146 -92700000.0,1.0,0.11169011380527376,0.9825819672131146 -92760000.0,1.0,0.10974469174104254,0.9825819672131146 -92820000.0,1.0,0.10783315510211043,0.9825819672131146 -92880000.0,1.0,0.10595491367102858,0.9825819672131146 -92940000.0,1.0,0.104109387510775,0.9825819672131147 -93000000.0,1.0,0.10229600678568977,0.9825819672131146 -93060000.0,1.0,0.10051421158552919,0.9825819672131147 -93120000.0,1.0,0.09876345175258451,0.9825819672131146 -93180000.0,1.0,0.09704318671181203,0.9825819672131147 -93240000.0,1.0,0.09535288530392186,0.9825819672131147 -93300000.0,1.0,0.09369202562137403,0.9825819672131147 -93360000.0,1.0,0.09206009484723122,0.9825819672131145 -93420000.0,1.0,0.09045658909681838,0.9825819672131146 -93480000.0,1.0,0.08888101326214018,0.9825819672131146 -93540000.0,1.0,0.08733288085900863,0.9825819672131146 -93600000.0,1.0,0.08581171387683327,0.9825819672131147 -93660000.0,1.0,0.08431704263102777,0.9825819672131146 -93720000.0,1.0,0.08284840561798731,0.9825819672131146 -93780000.0,1.0,0.08140534937259203,0.9825819672131145 -93840000.0,1.0,0.07998742832819236,0.9825819672131146 -93900000.0,1.0,0.07859420467903326,0.9825819672131146 -93960000.0,1.0,0.07722524824507468,0.9825819672131146 -94020000.0,1.0,0.07588013633916661,0.9825819672131147 -94080000.0,1.0,0.07455845363653768,0.9825819672131146 -94140000.0,1.0,0.073259792046557,0.9825819672131146 -94200000.0,1.0,0.07198375058672966,0.9825819672131146 -94260000.0,1.0,0.07072993525888703,0.9825819672131146 -94320000.0,1.0,0.06949795892753347,0.9825819672131147 -94380000.0,1.0,0.06828744120031208,0.9825819672131146 -94440000.0,1.0,0.06709800831055253,0.9825819672131145 -94500000.0,1.0,0.06592929300186462,0.9825819672131146 -94560000.0,1.0,0.06478093441474196,0.9825819672131145 -94620000.0,1.0,0.06365257797514091,0.9825819672131146 -94680000.0,1.0,0.06254387528500013,0.9825819672131146 -94740000.0,1.0,0.06145448401466714,0.9825819672131147 -94800000.0,1.0,0.06038406779719854,0.9825819672131146 -94860000.0,1.0,0.05933229612450143,0.9825819672131146 -94920000.0,1.0,0.05829884424528368,0.9825819672131147 -94980000.0,1.0,0.05728339306478181,0.9825819672131146 -95040000.0,1.0,0.056285629046235394,0.9825819672131146 -95100000.0,1.0,0.0553052441140776,0.9825819672131146 -95160000.0,1.0,0.054341935558811894,0.9825819672131146 -95220000.0,1.0,0.05339540594354569,0.9825819672131145 -95280000.0,1.0,0.05246536301215196,0.9825819672131146 -95340000.0,1.0,0.051551519599030454,0.9825819672131146 -95400000.0,1.0,0.05065359354044078,0.9825819672131146 -95460000.0,1.0,0.04977130758737981,0.9825819672131145 -95520000.0,1.0,0.04890438931997667,0.9825819672131145 -95580000.0,1.0,0.048052571063378714,0.9825819672131147 -95640000.0,1.0,0.047215589805102647,0.9825819672131146 -95700000.0,1.0,0.046393187113825236,0.9825819672131146 -95760000.0,1.0,0.04558510905958852,0.9825819672131146 -95820000.0,1.0,0.044791106135394866,0.9825819672131147 -95880000.0,1.0,0.044010933180167695,0.9825819672131146 -95940000.0,1.0,0.04324434930305411,0.9825819672131145 -96000000.0,1.0,0.04249111780904599,0.9825819672131146 -96060000.0,1.0,0.04175100612589662,0.9825819672131146 -96120000.0,1.0,0.041023785732310296,0.9825819672131145 -96180000.0,1.0,0.04030923208738276,0.9825819672131147 -96240000.0,1.0,0.03960712456127056,0.9825819672131146 -96300000.0,1.0,0.0389172463670681,0.9825819672131147 -96360000.0,1.0,0.038239384493871216,0.9825819672131147 -96420000.0,1.0,0.03757332964100665,0.9825819672131146 -96480000.0,1.0,0.03691887615340715,0.9825819672131147 -96540000.0,1.0,0.036275821958112145,0.9825819672131146 -96600000.0,1.0,0.03564396850187453,0.9825819672131146 -96660000.0,1.0,0.03502312068985417,0.9825819672131146 -96720000.0,1.0,0.03441308682537925,0.9825819672131145 -96780000.0,1.0,0.03381367855075686,0.9825819672131146 -96840000.0,1.0,0.03322471078911457,0.9825819672131146 -96900000.0,1.0,0.03264600168725499,0.9825819672131146 -96960000.0,1.0,0.032077372559505664,0.9825819672131145 -97020000.0,1.0,0.031518647832547056,0.9825819672131146 -97080000.0,1.0,0.03096965499120146,0.9825819672131147 -97140000.0,1.0,0.030430224525166187,0.9825819672131146 -97200000.0,1.0,0.029900189876674556,0.9825819672131145 -97260000.0,1.0,0.02937938738906854,0.9825819672131146 -97320000.0,1.0,0.028867656256267134,0.9825819672131145 -97380000.0,1.0,0.028364838473114936,0.9825819672131146 -97440000.0,1.0,0.027870778786595513,0.9825819672131146 -97500000.0,1.0,0.027385324647894563,0.9825819672131146 -97560000.0,1.0,0.026908326165298035,0.9825819672131146 -97620000.0,1.0,0.02643963605791067,0.9825819672131146 -97680000.0,1.0,0.025979109610180663,0.9825819672131146 -97740000.0,1.0,0.025526604627216447,0.9825819672131146 -97800000.0,1.0,0.02508198139088173,0.9825819672131145 -97860000.0,1.0,0.024645102616655305,0.9825819672131147 -97920000.0,1.0,0.024215833411242248,0.9825819672131146 -97980000.0,1.0,0.023794041230923477,0.9825819672131146 -98040000.0,1.0,0.023379595840630752,0.9825819672131147 -98100000.0,1.0,0.022972369273734517,0.9825819672131146 -98160000.0,1.0,0.02257223579253217,0.9825819672131146 -98220000.0,1.0,0.022179071849424536,0.9825819672131145 -98280000.0,1.0,0.021792756048768572,0.9825819672131146 -98340000.0,1.0,0.021413169109394527,0.9825819672131146 -98400000.0,1.0,0.021040193827775975,0.9825819672131147 -98460000.0,1.0,0.02067371504184135,0.9825819672131145 -98520000.0,1.0,0.02031361959541583,0.9825819672131145 -98580000.0,1.0,0.01995979630328256,0.9825819672131147 -98640000.0,1.0,0.019612135916852434,0.9825819672131147 -98700000.0,1.0,0.019270531090431847,0.9825819672131146 -98760000.0,1.0,0.01893487634807801,0.9825819672131146 -98820000.0,1.0,0.018605068051031567,0.9825819672131146 -98880000.0,1.0,0.018281004365716464,0.9825819672131145 -98940000.0,1.0,0.01796258523229722,0.9825819672131146 -99000000.0,1.0,0.017649712333783842,0.9825819672131145 -99060000.0,1.0,0.0173422890656749,0.9825819672131146 -99120000.0,1.0,0.01704022050612933,0.9825819672131146 -99180000.0,1.0,0.016743413386657816,0.9825819672131147 -99240000.0,1.0,0.016451776063324635,0.9825819672131146 -99300000.0,1.0,0.01616521848845115,0.9825819672131145 -99360000.0,1.0,0.015883652182812142,0.9825819672131146 -99420000.0,1.0,0.015606990208316435,0.9825819672131145 -99480000.0,1.0,0.01533514714116338,0.9825819672131146 -99540000.0,1.0,0.015068039045466884,0.9825819672131145 -99600000.0,1.0,0.014805583447338871,0.9825819672131145 -99660000.0,1.0,0.014547699309424155,0.9825819672131146 -99720000.0,1.0,0.014294307005878856,0.9825819672131146 -99780000.0,1.0,0.014045328297784652,0.9825819672131146 -99840000.0,1.0,0.013800686308991271,0.9825819672131147 -99900000.0,1.0,0.01356030550237974,0.9825819672131145 -99960000.0,1.0,0.013324111656539107,0.9825819672131146 -100020000.0,1.0,0.013092031842849388,0.9825819672131146 -100080000.0,1.0,0.012863994402963689,0.9825819672131145 -100140000.0,1.0,0.012639928926682557,0.9825819672131146 -100200000.0,1.0,0.0124197662302137,0.9825819672131146 -100260000.0,1.0,0.012203438334810385,0.9825819672131145 -100320000.0,1.0,0.011990878445781925,0.9825819672131147 -100380000.0,1.0,0.011782020931869738,0.9825819672131146 -100440000.0,1.0,0.011576801304982662,0.9825819672131146 -100500000.0,1.0,0.011375156200285216,0.9825819672131146 -100560000.0,1.0,0.011177023356632705,0.9825819672131145 -100620000.0,1.0,0.010982341597347093,0.9825819672131146 -100680000.0,1.0,0.010791050811327727,0.9825819672131146 -100740000.0,1.0,0.010603091934491075,0.9825819672131146 -100800000.0,1.0,0.01041840693153375,0.9825819672131146 -100860000.0,1.0,0.010236938778013181,0.9825819672131146 -100920000.0,1.0,0.010058631442740409,0.9825819672131146 -100980000.0,1.0,0.00988342987047956,0.9825819672131146 -101040000.0,1.0,0.009711279964948666,0.9825819672131146 -101100000.0,1.0,0.009542128572116566,0.9825819672131145 -101160000.0,1.0,0.009375923463790764,0.9825819672131146 -101220000.0,1.0,0.009212613321491129,0.9825819672131146 -101280000.0,1.0,0.0090521477206045,0.9825819672131146 -101340000.0,1.0,0.008894477114815282,0.9825819672131147 -101400000.0,1.0,0.008739552820807227,0.9825819672131145 -101460000.0,1.0,0.00858732700323169,0.9825819672131145 -101520000.0,1.0,0.008437752659937695,0.9825819672131146 -101580000.0,1.0,0.00829078360745927,0.9825819672131145 -101640000.0,1.0,0.008146374466755573,0.9825819672131146 -101700000.0,1.0,0.00800448064919938,0.9825819672131147 -101760000.0,1.0,0.007865058342809634,0.9825819672131145 -101820000.0,1.0,0.00772806449872381,0.9825819672131147 -101880000.0,1.0,0.007593456817905873,0.9825819672131145 -101940000.0,1.0,0.007461193738085791,0.9825819672131146 -102000000.0,1.0,0.00733123442092651,0.9825819672131147 -102060000.0,1.0,0.007203538739414469,0.9825819672131145 -102120000.0,1.0,0.007078067265469749,0.9825819672131146 -102180000.0,1.0,0.006954781257772017,0.9825819672131147 -102240000.0,1.0,0.006833642649798528,0.9825819672131146 -102300000.0,1.0,0.006714614038070479,0.9825819672131145 -102360000.0,1.0,0.006597658670604086,0.9825819672131146 -102420000.0,1.0,0.006482740435562825,0.9825819672131145 -102480000.0,1.0,0.006369823850107325,0.9825819672131147 -102540000.0,1.0,0.006258874049439471,0.9825819672131146 -102600000.0,1.0,0.006149856776037348,0.9825819672131145 -102660000.0,1.0,0.0060427383690776805,0.9825819672131147 -102720000.0,1.0,0.005937485754042515,0.9825819672131146 -102780000.0,1.0,0.005834066432506938,0.9825819672131146 -102840000.0,1.0,0.0057324484721046655,0.9825819672131147 -102900000.0,1.0,0.005632600496668416,0.9825819672131147 -102960000.0,1.0,0.005534491676542019,0.9825819672131147 -103020000.0,1.0,0.005438091719061266,0.9825819672131146 -103080000.0,1.0,0.0053433708592005675,0.9825819672131146 -103140000.0,1.0,0.005250299850382525,0.9825819672131147 -103200000.0,1.0,0.005158849955447582,0.9825819672131145 -103260000.0,1.0,0.005068992937780974,0.9825819672131146 -103320000.0,1.0,0.004980701052594215,0.9825819672131147 -103380000.0,1.0,0.0048939470383584545,0.9825819672131146 -103440000.0,1.0,0.004808704108387046,0.9825819672131146 -103500000.0,1.0,0.00472494594256473,0.9825819672131145 -103560000.0,1.0,0.004642646679220877,0.9825819672131147 -103620000.0,1.0,0.004561780907144283,0.9825819672131146 -103680000.0,1.0,0.004482323657737056,0.9825819672131145 -103740000.0,1.0,0.0044042503973051594,0.9825819672131145 -103800000.0,1.0,0.004327537019483245,0.9825819672131146 -103860000.0,1.0,0.004252159837791426,0.9825819672131146 -103920000.0,1.0,0.0041780955783216975,0.9825819672131146 -103980000.0,1.0,0.004105321372551749,0.9825819672131146 -104040000.0,1.0,0.004033814750283942,0.9825819672131146 -104100000.0,1.0,0.0039635536327072745,0.9825819672131146 -104160000.0,1.0,0.0038945163255802006,0.9825819672131146 -104220000.0,1.0,0.003826681512532184,0.9825819672131145 -104280000.0,1.0,0.0037600282484819307,0.9825819672131147 -104340000.0,1.0,0.0036945359531702577,0.9825819672131147 -104400000.0,1.0,0.0036301844048056116,0.9825819672131147 -104460000.0,1.0,0.0035669537338202674,0.9825819672131146 -104520000.0,1.0,0.0035048244167352827,0.9825819672131146 -104580000.0,1.0,0.003443777270132311,0.9825819672131145 -104640000.0,1.0,0.0033837934447304156,0.9825819672131146 -104700000.0,1.0,0.0033248544195660537,0.9825819672131147 -104760000.0,1.0,0.003266941996274431,0.9825819672131145 -104820000.0,1.0,0.0032100382934704703,0.9825819672131146 -104880000.0,1.0,0.003154125741227644,0.9825819672131146 -104940000.0,1.0,0.003099187075652982,0.9825819672131146 -105000000.0,1.0,0.003045205333556567,0.9825819672131146 -105060000.0,1.0,0.00299216384721388,0.9825819672131145 -105120000.0,1.0,0.002940046239219376,0.9825819672131147 -105180000.0,1.0,0.002888836417429694,0.9825819672131147 -105240000.0,1.0,0.0028385185699949553,0.9825819672131146 -105300000.0,1.0,0.0027890771604766004,0.9825819672131147 -105360000.0,1.0,0.002740496923050266,0.9825819672131146 -105420000.0,1.0,0.002692762857792218,0.9825819672131146 -105480000.0,1.0,0.002645860226047886,0.9825819672131146 -105540000.0,1.0,0.002599774545881068,0.9825819672131145 -105600000.0,1.0,0.0025544915876024015,0.9825819672131147 -105660000.0,1.0,0.00250999736937572,0.9825819672131146 -105720000.0,1.0,0.0024662781529009377,0.9825819672131146 -105780000.0,1.0,0.00242332043917213,0.9825819672131146 -105840000.0,1.0,0.0023811109643095004,0.9825819672131145 -105900000.0,1.0,0.0023396366954639455,0.9825819672131147 -105960000.0,1.0,0.0022988848267929546,0.9825819672131147 -106020000.0,1.0,0.0022588427755066017,0.9825819672131146 -106080000.0,1.0,0.0022194981779824086,0.9825819672131146 -106140000.0,1.0,0.002180838885947879,0.9825819672131147 -106200000.0,1.0,0.002142852962729524,0.9825819672131145 -106260000.0,1.0,0.0021055286795672265,0.9825819672131145 -106320000.0,1.0,0.0020688545119927973,0.9825819672131147 -106380000.0,1.0,0.0020328191362716107,0.9825819672131145 -106440000.0,1.0,0.0019974114259062236,0.9825819672131145 -106500000.0,1.0,0.0019626204482008896,0.9825819672131146 -106560000.0,1.0,0.0019284354608859148,0.9825819672131146 -106620000.0,1.0,0.0018948459088008113,0.9825819672131145 -106680000.0,1.0,0.001861841420635223,0.9825819672131145 -106740000.0,1.0,0.0018294118057266173,0.9825819672131145 -106800000.0,1.0,0.001797547050913756,0.9825819672131147 -106860000.0,1.0,0.001766237317444971,0.9825819672131145 -106920000.0,1.0,0.001735472937940294,0.9825819672131146 -106980000.0,1.0,0.0017052444134064977,0.9825819672131146 -107040000.0,1.0,0.0016755424103041302,0.9825819672131146 -107100000.0,1.0,0.001646357757665636,0.9825819672131146 -107160000.0,1.0,0.0016176814442636729,0.9825819672131146 -107220000.0,1.0,0.0015895046158287522,0.9825819672131146 -107280000.0,1.0,0.0015618185723153414,0.9825819672131146 -107340000.0,1.0,0.0015346147652155863,0.9825819672131146 -107400000.0,1.0,0.0015078847949198228,0.9825819672131146 -107460000.0,1.0,0.0014816204081230635,0.9825819672131146 -107520000.0,1.0,0.0014558134952766576,0.9825819672131147 -107580000.0,1.0,0.0014304560880843387,0.9825819672131147 -107640000.0,1.0,0.001405540357041886,0.9825819672131146 -107700000.0,1.0,0.0013810586090196397,0.9825819672131146 -107760000.0,1.0,0.0013570032848871255,0.9825819672131147 -107820000.0,1.0,0.0013333669571790503,0.9825819672131146 -107880000.0,1.0,0.0013101423278019562,0.9825819672131147 -107940000.0,1.0,0.0012873222257808153,0.9825819672131145 -108000000.0,1.0,0.0012648996050448789,0.9825819672131146 -108060000.0,1.0,0.0012428675422520889,0.9825819672131146 -108120000.0,1.0,0.0012212192346513864,0.9825819672131146 -108180000.0,1.0,0.0011999479979822535,0.9825819672131146 -108240000.0,1.0,0.0011790472644108412,0.9825819672131146 -108300000.0,1.0,0.0011585105805020457,0.9825819672131146 -108360000.0,1.0,0.0011383316052269075,0.9825819672131146 -108420000.0,1.0,0.0011185041080047173,0.9825819672131146 -108480000.0,1.0,0.0010990219667792252,0.9825819672131146 -108540000.0,1.0,0.0010798791661283573,0.9825819672131145 -108600000.0,1.0,0.001061069795406859,0.9825819672131145 -108660000.0,1.0,0.0010425880469212886,0.9825819672131145 -108720000.0,1.0,0.001024428214136799,0.9825819672131149 -108780000.0,1.0,0.0010065846899151537,0.9825819672131145 -108840000.0,1.0,0.0009890519647834347,0.9825819672131146 -108900000.0,1.0,0.0009718246252329035,0.9825819672131146 -108960000.0,1.0,0.0009548973520474941,0.9825819672131146 -109020000.0,1.0,0.0009382649186614209,0.9825819672131146 -109080000.0,1.0,0.0009219221895453919,0.9825819672131146 -109140000.0,1.0,0.0009058641186209332,0.9825819672131147 -109200000.0,1.0,0.0008900857477023308,0.9825819672131146 -109260000.0,1.0,0.0008745822049657123,0.9825819672131147 -109320000.0,1.0,0.0008593487034447931,0.9825819672131147 -109380000.0,1.0,0.0008443805395528243,0.9825819672131147 -109440000.0,1.0,0.0008296730916302853,0.9825819672131147 -109500000.0,1.0,0.0008152218185178725,0.9825819672131146 -109560000.0,1.0,0.0008010222581543439,0.9825819672131146 -109620000.0,1.0,0.0007870700261987866,0.9825819672131146 -109680000.0,1.0,0.0007733608146768814,0.9825819672131145 -109740000.0,1.0,0.0007598903906507471,0.9825819672131147 -109800000.0,1.0,0.0007466545949119534,0.9825819672131147 -109860000.0,1.0,0.0007336493406972985,0.9825819672131147 -109920000.0,1.0,0.000720870612426956,0.9825819672131145 -109980000.0,1.0,0.0007083144644646011,0.9825819672131146 -110040000.0,1.0,0.0006959770198991316,0.9825819672131146 -110100000.0,1.0,0.0006838544693476097,0.9825819672131146 -110160000.0,1.0,0.000671943069779055,0.9825819672131146 -110220000.0,1.0,0.000660239143358723,0.9825819672131146 -110280000.0,1.0,0.0006487390763125156,0.9825819672131146 -110340000.0,1.0,0.0006374393178111706,0.9825819672131147 -110400000.0,1.0,0.0006263363788738857,0.9825819672131146 -110460000.0,1.0,0.0006154268312910413,0.9825819672131145 -110520000.0,1.0,0.000604707306565685,0.9825819672131146 -110580000.0,1.0,0.0005941744948734547,0.9825819672131145 -110640000.0,1.0,0.0005838251440406178,0.9825819672131147 -110700000.0,1.0,0.0005736560585399103,0.9825819672131147 -110760000.0,1.0,0.0005636640985038667,0.9825819672131146 -110820000.0,1.0,0.0005538461787553361,0.9825819672131146 -110880000.0,1.0,0.0005441992678548845,0.9825819672131146 -110940000.0,1.0,0.0005347203871647892,0.9825819672131147 -111000000.0,1.0,0.0005254066099293368,0.9825819672131145 -111060000.0,1.0,0.0005162550603711413,0.9825819672131146 -111120000.0,1.0,0.0005072629128032013,0.9825819672131145 -111180000.0,1.0,0.0004984273907564241,0.9825819672131146 -111240000.0,1.0,0.000489745766122347,0.9825819672131145 -111300000.0,1.0,0.0004812153583107897,0.9825819672131147 -111360000.0,1.0,0.00047283353342217956,0.9825819672131146 -111420000.0,1.0,0.00046459770343429315,0.9825819672131145 -111480000.0,1.0,0.000456505325403163,0.9825819672131146 -111540000.0,1.0,0.0004485539006779029,0.9825819672131146 -111600000.0,1.0,0.0004407409741292098,0.9825819672131145 -111660000.0,1.0,0.0004330641333913034,0.9825819672131145 -111720000.0,1.0,0.0004255210081170696,0.9825819672131146 -111780000.0,1.0,0.000418109269246178,0.9825819672131146 -111840000.0,1.0,0.0004108266282859474,0.9825819672131146 -111900000.0,1.0,0.0004036708366047372,0.9825819672131146 -111960000.0,1.0,0.0003966396847376465,0.9825819672131146 -112020000.0,1.0,0.0003897310017043063,0.9825819672131146 -112080000.0,1.0,0.000382942654338555,0.9825819672131146 -112140000.0,1.0,0.00037627254662978915,0.9825819672131146 -112200000.0,1.0,0.0003697186190757867,0.9825819672131147 -112260000.0,1.0,0.0003632788480468026,0.9825819672131146 -112320000.0,1.0,0.00035695124516074147,0.9825819672131146 -112380000.0,1.0,0.0003507338566692121,0.9825819672131146 -112440000.0,1.0,0.00034462476285427706,0.9825819672131147 -112500000.0,1.0,0.0003386220774357087,0.9825819672131146 -112560000.0,1.0,0.0003327239469885703,0.9825819672131146 -112620000.0,1.0,0.00032692855037094146,0.9825819672131146 -112680000.0,1.0,0.0003212340981616115,0.9825819672131146 -112740000.0,1.0,0.00031563883210756696,0.9825819672131145 -112800000.0,1.0,0.00031014102458110316,0.9825819672131146 -112860000.0,1.0,0.00030473897804639125,0.9825819672131145 -112920000.0,1.0,0.00029943102453533726,0.9825819672131146 -112980000.0,1.0,0.0002942155251325701,0.9825819672131146 -113040000.0,1.0,0.00028909086946940027,0.9825819672131145 -113100000.0,1.0,0.00028405547522659307,0.9825819672131146 -113160000.0,1.0,0.000279107787645802,0.9825819672131147 -113220000.0,1.0,0.00027424627904951236,0.9825819672131145 -113280000.0,1.0,0.00026946944836934663,0.9825819672131146 -113340000.0,1.0,0.00026477582068258545,0.9825819672131146 -113400000.0,1.0,0.0002601639467567617,0.9825819672131146 -113460000.0,1.0,0.00025563240260218693,0.9825819672131147 -113520000.0,1.0,0.0002511797890322718,0.9825819672131147 -113580000.0,1.0,0.00024680473123150475,0.9825819672131147 -113640000.0,1.0,0.00024250587833095596,0.9825819672131146 -113700000.0,1.0,0.00023828190299117493,0.9825819672131146 -113760000.0,1.0,0.00023413150099235318,0.9825819672131145 -113820000.0,1.0,0.00023005339083162572,0.9825819672131147 -113880000.0,1.0,0.00022604631332738632,0.9825819672131146 -113940000.0,1.0,0.00022210903123049536,0.9825819672131147 -114000000.0,1.0,0.00021824032884225925,0.9825819672131146 -114060000.0,1.0,0.00021443901163906414,0.9825819672131146 -114120000.0,1.0,0.00021070390590354764,0.9825819672131147 -114180000.0,1.0,0.00020703385836219485,0.9825819672131146 -114240000.0,1.0,0.00020342773582924677,0.9825819672131146 -114300000.0,1.0,0.00019988442485681108,0.9825819672131146 -114360000.0,1.0,0.0001964028313910674,0.9825819672131145 -114420000.0,1.0,0.0001929818804344607,0.9825819672131147 -114480000.0,1.0,0.00018962051571377848,0.9825819672131147 -114540000.0,1.0,0.00018631769935400976,0.9825819672131145 -114600000.0,1.0,0.00018307241155788459,0.9825819672131147 -114660000.0,1.0,0.0001798836502909952,0.9825819672131146 -114720000.0,1.0,0.000176750430972402,0.9825819672131146 -114780000.0,1.0,0.0001736717861706286,0.9825819672131146 -114840000.0,1.0,0.00017064676530495165,0.9825819672131146 -114900000.0,1.0,0.00016767443435189409,0.9825819672131147 -114960000.0,1.0,0.00016475387555683034,0.9825819672131146 -115020000.0,1.0,0.00016188418715061504,0.9825819672131146 -115080000.0,1.0,0.00015906448307114734,0.9825819672131146 -115140000.0,1.0,0.00015629389268978513,0.9825819672131146 -115200000.0,1.0,0.0001535715605425245,0.9825819672131145 -115260000.0,1.0,0.00015089664606586164,0.9825819672131145 -115320000.0,1.0,0.00014826832333725542,0.9825819672131145 -115380000.0,1.0,0.00014568578082011058,0.9825819672131146 -115440000.0,1.0,0.00014314822111320292,0.9825819672131147 -115500000.0,1.0,0.00014065486070446884,0.9825819672131146 -115560000.0,1.0,0.0001382049297290836,0.9825819672131146 -115620000.0,1.0,0.00013579767173175323,0.9825819672131145 -115680000.0,1.0,0.00013343234343314686,0.9825819672131146 -115740000.0,1.0,0.00013110821450039738,0.9825819672131147 -115800000.0,1.0,0.00012882456732159943,0.9825819672131144 -115860000.0,1.0,0.0001265806967842355,0.9825819672131147 -115920000.0,1.0,0.0001243759100574609,0.9825819672131146 -115980000.0,1.0,0.00012220952637818133,0.9825819672131145 -116040000.0,1.0,0.00012008087684085644,0.9825819672131146 -116100000.0,1.0,0.00011798930419096445,0.9825819672131145 -116160000.0,1.0,0.00011593416262206445,0.9825819672131147 -116220000.0,1.0,0.00011391481757639323,0.9825819672131146 -116280000.0,1.0,0.00011193064554893556,0.9825819672131147 -116340000.0,1.0,0.00010998103389490696,0.9825819672131146 -116400000.0,1.0,0.00010806538064058992,0.9825819672131146 -116460000.0,1.0,0.00010618309429746488,0.9825819672131146 -116520000.0,1.0,0.00010433359367957869,0.9825819672131145 -116580000.0,1.0,0.0001025163077240942,0.9825819672131145 -116640000.0,1.0,0.00010073067531496551,0.9825819672131146 -116700000.0,1.0,9.897614510968433e-05,0.9825819672131145 -116760000.0,1.0,9.725217536904433e-05,0.9825819672131147 -116820000.0,1.0,9.555823378987039e-05,0.9825819672131146 -116880000.0,1.0,9.389379734066157e-05,0.9825819672131146 -116940000.0,1.0,9.225835210009676e-05,0.9825819672131146 -117000000.0,1.0,9.065139309835325e-05,0.9825819672131145 -117060000.0,1.0,8.907242416118929e-05,0.9825819672131145 -117120000.0,1.0,8.752095775674234e-05,0.9825819672131146 -117180000.0,1.0,8.59965148449958e-05,0.9825819672131147 -117240000.0,1.0,8.449862472986779e-05,0.9825819672131146 -117300000.0,1.0,8.302682491387624e-05,0.9825819672131147 -117360000.0,1.0,8.158066095533535e-05,0.9825819672131146 -117420000.0,1.0,8.015968632803954e-05,0.9825819672131147 -117480000.0,1.0,7.87634622833913e-05,0.9825819672131146 -117540000.0,1.0,7.739155771493057e-05,0.9825819672131145 -117600000.0,1.0,7.604354902522378e-05,0.9825819672131146 -117660000.0,1.0,7.471901999507131e-05,0.9825819672131147 -117720000.0,1.0,7.341756165499322e-05,0.9825819672131146 -117780000.0,1.0,7.213877215895336e-05,0.9825819672131145 -117840000.0,1.0,7.088225666028305e-05,0.9825819672131145 -117900000.0,1.0,6.964762718976581e-05,0.9825819672131146 -117960000.0,1.0,6.84345025358457e-05,0.9825819672131146 -118020000.0,1.0,6.724250812692215e-05,0.9825819672131145 -118080000.0,1.0,6.607127591569502e-05,0.9825819672131147 -118140000.0,1.0,6.49204442655241e-05,0.9825819672131147 -118200000.0,1.0,6.378965783876803e-05,0.9825819672131145 -118260000.0,1.0,6.267856748706817e-05,0.9825819672131146 -118320000.0,1.0,6.158683014354341e-05,0.9825819672131146 -118380000.0,1.0,6.051410871686284e-05,0.9825819672131147 -118440000.0,1.0,5.946007198716338e-05,0.9825819672131146 -118500000.0,1.0,5.842439450378041e-05,0.9825819672131147 -118560000.0,1.0,5.740675648475964e-05,0.9825819672131147 -118620000.0,1.0,5.640684371811936e-05,0.9825819672131147 -118680000.0,1.0,5.5424347464832436e-05,0.9825819672131146 -118740000.0,1.0,5.445896436349826e-05,0.9825819672131147 -118800000.0,1.0,5.351039633667503e-05,0.9825819672131146 -118860000.0,1.0,5.2578350498843596e-05,0.9825819672131147 -118920000.0,1.0,5.166253906597439e-05,0.9825819672131146 -118980000.0,1.0,5.07626792666695e-05,0.9825819672131146 -119040000.0,1.0,4.987849325485251e-05,0.9825819672131146 -119100000.0,1.0,4.900970802397905e-05,0.9825819672131146 -119160000.0,1.0,4.81560553227417e-05,0.9825819672131145 -119220000.0,1.0,4.7317271572243115e-05,0.9825819672131145 -119280000.0,1.0,4.6493097784611825e-05,0.9825819672131146 -119340000.0,1.0,4.5683279483035594e-05,0.9825819672131147 -119400000.0,1.0,4.488756662318763e-05,0.9825819672131146 -119460000.0,1.0,4.4105713516021446e-05,0.9825819672131145 -119520000.0,1.0,4.333747875191041e-05,0.9825819672131146 -119580000.0,1.0,4.258262512610869e-05,0.9825819672131146 -119640000.0,1.0,4.1840919565510475e-05,0.9825819672131145 -119700000.0,1.0,4.1112133056684975e-05,0.9825819672131145 -119760000.0,1.0,4.039604057516484e-05,0.9825819672131146 -119820000.0,1.0,3.969242101596627e-05,0.9825819672131146 -119880000.0,1.0,3.9001057125319315e-05,0.9825819672131147 -119940000.0,1.0,3.8321735433587314e-05,0.9825819672131146 -120000000.0,1.0,3.7654246189354746e-05,0.9825819672131146 -120060000.0,1.0,3.699838329466311e-05,0.9825819672131146 -120120000.0,1.0,3.6353944241374925e-05,0.9825819672131147 -120180000.0,1.0,3.572073004864606e-05,0.9825819672131147 -120240000.0,1.0,3.509854520148726e-05,0.9825819672131145 -120300000.0,1.0,3.448719759039578e-05,0.9825819672131147 -120360000.0,1.0,3.3886498452038475e-05,0.9825819672131147 -120420000.0,1.0,3.329626231096813e-05,0.9825819672131146 -120480000.0,1.0,3.271630692235495e-05,0.9825819672131147 -120540000.0,1.0,3.2146453215715574e-05,0.9825819672131147 -120600000.0,1.0,3.158652523962216e-05,0.9825819672131145 -120660000.0,1.0,3.1036350107374636e-05,0.9825819672131145 -120720000.0,1.0,3.049575794361913e-05,0.9825819672131146 -120780000.0,1.0,2.996458183189625e-05,0.9825819672131145 -120840000.0,1.0,2.944265776310297e-05,0.9825819672131146 -120900000.0,1.0,2.89298245848522e-05,0.9825819672131146 -120960000.0,1.0,2.84259239517144e-05,0.9825819672131146 -121020000.0,1.0,2.793080027632593e-05,0.9825819672131146 -121080000.0,1.0,2.7444300681348936e-05,0.9825819672131146 -121140000.0,1.0,2.696627495226806e-05,0.9825819672131146 -121200000.0,1.0,2.649657549100929e-05,0.9825819672131146 -121260000.0,1.0,2.6035057270366706e-05,0.9825819672131146 -121320000.0,1.0,2.558157778922302e-05,0.9825819672131146 -121380000.0,1.0,2.5135997028550072e-05,0.9825819672131145 -121440000.0,1.0,2.4698177408175732e-05,0.9825819672131145 -121500000.0,1.0,2.4267983744303818e-05,0.9825819672131147 -121560000.0,1.0,2.3845283207773933e-05,0.9825819672131146 -121620000.0,1.0,2.3429945283048362e-05,0.9825819672131147 -121680000.0,1.0,2.3021841727913294e-05,0.9825819672131145 -121740000.0,1.0,2.2620846533882015e-05,0.9825819672131146 -121800000.0,1.0,2.2226835887287756e-05,0.9825819672131146 -121860000.0,1.0,2.1839688131054256e-05,0.9825819672131146 -121920000.0,1.0,2.14592837271322e-05,0.9825819672131146 -121980000.0,1.0,2.1085505219589938e-05,0.9825819672131146 -122040000.0,1.0,2.0718237198347075e-05,0.9825819672131145 -122100000.0,1.0,2.0357366263539797e-05,0.9825819672131146 -122160000.0,1.0,2.0002780990506826e-05,0.9825819672131146 -122220000.0,1.0,1.965437189538529e-05,0.9825819672131146 -122280000.0,1.0,1.9312031401305832e-05,0.9825819672131146 -122340000.0,1.0,1.8975653805176526e-05,0.9825819672131145 -122400000.0,1.0,1.8645135245045376e-05,0.9825819672131146 -122460000.0,1.0,1.8320373668031265e-05,0.9825819672131146 -122520000.0,1.0,1.8001268798813505e-05,0.9825819672131146 -122580000.0,1.0,1.7687722108670238e-05,0.9825819672131147 -122640000.0,1.0,1.7379636785056103e-05,0.9825819672131146 -122700000.0,1.0,1.7076917701709834e-05,0.9825819672131145 -122760000.0,1.0,1.677947138928251e-05,0.9825819672131146 -122820000.0,1.0,1.6487206006477383e-05,0.9825819672131146 -122880000.0,1.0,1.6200031311692426e-05,0.9825819672131146 -122940000.0,1.0,1.5917858635156797e-05,0.9825819672131146 -123000000.0,1.0,1.564060085155263e-05,0.9825819672131147 -123060000.0,1.0,1.53681723531137e-05,0.9825819672131146 -123120000.0,1.0,1.5100489023192662e-05,0.9825819672131147 -123180000.0,1.0,1.4837468210288691e-05,0.9825819672131147 -123240000.0,1.0,1.4579028702527514e-05,0.9825819672131146 -123300000.0,1.0,1.4325090702585948e-05,0.9825819672131146 -123360000.0,1.0,1.4075575803053197e-05,0.9825819672131145 -123420000.0,1.0,1.3830406962221326e-05,0.9825819672131146 -123480000.0,1.0,1.3589508480297388e-05,0.9825819672131146 -123540000.0,1.0,1.3352805976029911e-05,0.9825819672131146 -123600000.0,1.0,1.3120226363742503e-05,0.9825819672131146 -123660000.0,1.0,1.289169783076748e-05,0.9825819672131146 -123720000.0,1.0,1.2667149815272553e-05,0.9825819672131146 -123780000.0,1.0,1.2446512984473746e-05,0.9825819672131146 -123840000.0,1.0,1.222971921322779e-05,0.9825819672131147 -123900000.0,1.0,1.2016701562997386e-05,0.9825819672131146 -123960000.0,1.0,1.180739426118288e-05,0.9825819672131145 -124020000.0,1.0,1.1601732680813916e-05,0.9825819672131147 -124080000.0,1.0,1.1399653320594818e-05,0.9825819672131145 -124140000.0,1.0,1.120109378529757e-05,0.9825819672131145 -124200000.0,1.0,1.100599276649628e-05,0.9825819672131146 -124260000.0,1.0,1.0814290023637225e-05,0.9825819672131147 -124320000.0,1.0,1.0625926365438625e-05,0.9825819672131146 -124380000.0,1.0,1.0440843631614386e-05,0.9825819672131146 -124440000.0,1.0,1.0258984674916183e-05,0.9825819672131146 -124500000.0,1.0,1.008029334348834e-05,0.9825819672131147 -124560000.0,1.0,9.90471446353004e-06,0.9825819672131147 -124620000.0,1.0,9.732193822259536e-06,0.9825819672131147 -124680000.0,1.0,9.562678151175097e-06,0.9825819672131146 -124740000.0,1.0,9.396115109607496e-06,0.9825819672131145 -124800000.0,1.0,9.232453268559004e-06,0.9825819672131147 -124860000.0,1.0,9.071642094823856e-06,0.9825819672131145 -124920000.0,1.0,8.913631935385325e-06,0.9825819672131146 -124980000.0,1.0,8.758374002084554e-06,0.9825819672131146 -125040000.0,1.0,8.605820356556442e-06,0.9825819672131146 -125100000.0,1.0,8.455923895427896e-06,0.9825819672131146 -125160000.0,1.0,8.308638335773926e-06,0.9825819672131146 -125220000.0,1.0,8.163918200827042e-06,0.9825819672131145 -125280000.0,1.0,8.021718805935587e-06,0.9825819672131147 -125340000.0,1.0,7.881996244766627e-06,0.9825819672131147 -125400000.0,1.0,7.744707375749175e-06,0.9825819672131147 -125460000.0,1.0,7.609809808753544e-06,0.9825819672131147 -125520000.0,1.0,7.477261892002713e-06,0.9825819672131146 -125580000.0,1.0,7.347022699211681e-06,0.9825819672131146 -125640000.0,1.0,7.219052016950821e-06,0.9825819672131147 -125700000.0,1.0,7.093310332229341e-06,0.9825819672131146 -125760000.0,1.0,6.969758820295018e-06,0.9825819672131146 -125820000.0,1.0,6.848359332646436e-06,0.9825819672131146 -125880000.0,1.0,6.729074385254028e-06,0.9825819672131146 -125940000.0,1.0,6.611867146986283e-06,0.9825819672131146 -126000000.0,1.0,6.496701428237545e-06,0.9825819672131145 -126060000.0,1.0,6.3835416697538985e-06,0.9825819672131146 -126120000.0,1.0,6.2723529316536756e-06,0.9825819672131145 -126180000.0,1.0,6.163100882639216e-06,0.9825819672131147 -126240000.0,1.0,6.055751789396524e-06,0.9825819672131146 -126300000.0,1.0,5.950272506179576e-06,0.9825819672131146 -126360000.0,1.0,5.846630464576037e-06,0.9825819672131146 -126420000.0,1.0,5.744793663451248e-06,0.9825819672131145 -126480000.0,1.0,5.6447306590673635e-06,0.9825819672131146 -126540000.0,1.0,5.546410555374591e-06,0.9825819672131146 -126600000.0,1.0,5.4498029944715496e-06,0.9825819672131146 -126660000.0,1.0,5.354878147231778e-06,0.9825819672131145 -126720000.0,1.0,5.261606704093519e-06,0.9825819672131146 -126780000.0,1.0,5.1699598660099225e-06,0.9825819672131147 -126840000.0,1.0,5.07990933555688e-06,0.9825819672131146 -126900000.0,1.0,4.991427308195745e-06,0.9825819672131145 -126960000.0,1.0,4.904486463688236e-06,0.9825819672131146 -127020000.0,1.0,4.8190599576608785e-06,0.9825819672131145 -127080000.0,1.0,4.735121413316375e-06,0.9825819672131146 -127140000.0,1.0,4.652644913289347e-06,0.9825819672131146 -127200000.0,1.0,4.571604991643938e-06,0.9825819672131146 -127260000.0,1.0,4.491976626010795e-06,0.9825819672131146 -127320000.0,1.0,4.413735229861016e-06,0.9825819672131146 -127380000.0,1.0,4.336856644914666e-06,0.9825819672131146 -127440000.0,1.0,4.2613171336815206e-06,0.9825819672131146 -127500000.0,1.0,4.187093372131739e-06,0.9825819672131146 -127560000.0,1.0,4.114162442494198e-06,0.9825819672131146 -127620000.0,1.0,4.042501826180262e-06,0.9825819672131145 -127680000.0,1.0,3.97208939683081e-06,0.9825819672131147 -127740000.0,1.0,3.9029034134843715e-06,0.9825819672131146 -127800000.0,1.0,3.834922513864253e-06,0.9825819672131145 -127860000.0,1.0,3.768125707782601e-06,0.9825819672131146 -127920000.0,1.0,3.702492370659338e-06,0.9825819672131145 -127980000.0,1.0,3.638002237154001e-06,0.9825819672131147 -128040000.0,1.0,3.5746353949084897e-06,0.9825819672131145 -128100000.0,1.0,3.5123722783988132e-06,0.9825819672131147 -128160000.0,1.0,3.4511936628939156e-06,0.9825819672131146 -128220000.0,1.0,3.391080658519738e-06,0.9825819672131145 -128280000.0,1.0,3.3320147044266683e-06,0.9825819672131145 -128340000.0,1.0,3.2739775630585803e-06,0.9825819672131146 -128400000.0,1.0,3.216951314521699e-06,0.9825819672131146 -128460000.0,1.0,3.160918351051546e-06,0.9825819672131146 -128520000.0,1.0,3.1058613715762627e-06,0.9825819672131146 -128580000.0,1.0,3.0517633763746264e-06,0.9825819672131145 -128640000.0,1.0,2.998607661827117e-06,0.9825819672131146 -128700000.0,1.0,2.946377815258407e-06,0.9825819672131146 -128760000.0,1.0,2.895057709869684e-06,0.9825819672131146 -128820000.0,1.0,2.844631499759249e-06,0.9825819672131146 -128880000.0,1.0,2.7950836150298356e-06,0.9825819672131147 -128940000.0,1.0,2.7463987569811596e-06,0.9825819672131145 -129000000.0,1.0,2.6985618933862005e-06,0.9825819672131146 -129060000.0,1.0,2.65155825384976e-06,0.9825819672131146 -129120000.0,1.0,2.6053733252478687e-06,0.9825819672131146 -129180000.0,1.0,2.559992847246625e-06,0.9825819672131147 -129240000.0,1.0,2.5154028078990914e-06,0.9825819672131146 -129300000.0,1.0,2.4715894393188813e-06,0.9825819672131145 -129360000.0,1.0,2.428539213429105e-06,0.9825819672131145 -129420000.0,1.0,2.3862388377853603e-06,0.9825819672131146 -129480000.0,1.0,2.3446752514714754e-06,0.9825819672131145 -129540000.0,1.0,2.3038356210667464e-06,0.9825819672131146 -129600000.0,1.0,2.2637073366834116e-06,0.9825819672131146 -129660000.0,1.0,2.224278008073147e-06,0.9825819672131147 -129720000.0,1.0,2.185535460801381e-06,0.9825819672131145 -129780000.0,1.0,2.1474677324882417e-06,0.9825819672131146 -129840000.0,1.0,2.110063069114983e-06,0.9825819672131146 -129900000.0,1.0,2.0733099213947423e-06,0.9825819672131146 -129960000.0,1.0,2.0371969412065142e-06,0.9825819672131147 -130020000.0,1.0,2.0017129780912366e-06,0.9825819672131146 -130080000.0,1.0,1.9668470758089094e-06,0.9825819672131146 -130140000.0,1.0,1.93258846895568e-06,0.9825819672131146 -130200000.0,1.0,1.8989265796398537e-06,0.9825819672131146 -130260000.0,1.0,1.8658510142157986e-06,0.9825819672131146 -130320000.0,1.0,1.8333515600747444e-06,0.9825819672131146 -130380000.0,1.0,1.8014181824914751e-06,0.9825819672131146 -130440000.0,1.0,1.770041021525947e-06,0.9825819672131145 -130500000.0,1.0,1.739210388978876e-06,0.9825819672131146 -130560000.0,1.0,1.7089167654003503e-06,0.9825819672131146 -130620000.0,1.0,1.6791507971505488e-06,0.9825819672131145 -130680000.0,1.0,1.6499032935116558e-06,0.9825819672131146 -130740000.0,1.0,1.6211652238500797e-06,0.9825819672131146 -130800000.0,1.0,1.5929277148281007e-06,0.9825819672131146 -130860000.0,1.0,1.5651820476640864e-06,0.9825819672131146 -130920000.0,1.0,1.537919655440429e-06,0.9825819672131146 -130980000.0,1.0,1.5111321204583722e-06,0.9825819672131146 -131040000.0,1.0,1.4848111716389126e-06,0.9825819672131146 -131100000.0,1.0,1.4589486819689724e-06,0.9825819672131146 -131160000.0,1.0,1.4335366659920537e-06,0.9825819672131146 -131220000.0,1.0,1.4085672773426019e-06,0.9825819672131147 -131280000.0,1.0,1.3840328063233146e-06,0.9825819672131146 -131340000.0,1.0,1.3599256775246503e-06,0.9825819672131147 -131400000.0,1.0,1.3362384474857986e-06,0.9825819672131146 -131460000.0,1.0,1.3129638023963942e-06,0.9825819672131146 -131520000.0,1.0,1.2900945558382602e-06,0.9825819672131146 -131580000.0,1.0,1.267623646566487e-06,0.9825819672131145 -131640000.0,1.0,1.2455441363291606e-06,0.9825819672131146 -131700000.0,1.0,1.2238492077250665e-06,0.9825819672131146 -131760000.0,1.0,1.2025321620987076e-06,0.9825819672131146 -131820000.0,1.0,1.1815864174719884e-06,0.9825819672131147 -131880000.0,1.0,1.1610055065119228e-06,0.9825819672131146 -131940000.0,1.0,1.1407830745337438e-06,0.9825819672131147 -132000000.0,1.0,1.120912877538791e-06,0.9825819672131145 -132060000.0,1.0,1.1013887802865785e-06,0.9825819672131147 -132120000.0,1.0,1.0822047544004391e-06,0.9825819672131146 -132180000.0,1.0,1.0633548765061692e-06,0.9825819672131147 -132240000.0,1.0,1.0448333264030902e-06,0.9825819672131145 -132300000.0,1.0,1.0266343852669705e-06,0.9825819672131145 -132360000.0,1.0,1.0087524338842466e-06,0.9825819672131147 -132420000.0,1.0,9.911819509170003e-07,0.9825819672131145 -132480000.0,1.0,9.73917511198159e-07,0.9825819672131145 -132540000.0,1.0,9.569537840563876e-07,0.9825819672131146 -132600000.0,1.0,9.402855316701593e-07,0.9825819672131145 -132660000.0,1.0,9.239076074504945e-07,0.9825819672131146 -132720000.0,1.0,9.078149544518688e-07,0.9825819672131145 -132780000.0,1.0,8.920026038108014e-07,0.9825819672131147 -132840000.0,1.0,8.764656732116378e-07,0.9825819672131146 -132900000.0,1.0,8.61199365379058e-07,0.9825819672131146 -132960000.0,1.0,8.461989665968407e-07,0.9825819672131146 -133020000.0,1.0,8.314598452524284e-07,0.9825819672131146 -133080000.0,1.0,8.169774504068431e-07,0.9825819672131147 -133140000.0,1.0,8.027473103895107e-07,0.9825819672131146 -133200000.0,1.0,7.887650314175621e-07,0.9825819672131146 -133260000.0,1.0,7.750262962391823e-07,0.9825819672131145 -133320000.0,1.0,7.615268628005899e-07,0.9825819672131146 -133380000.0,1.0,7.482625629362353e-07,0.9825819672131146 -133440000.0,1.0,7.352293010818131e-07,0.9825819672131146 -133500000.0,1.0,7.224230530096913e-07,0.9825819672131146 -133560000.0,1.0,7.098398645863665e-07,0.9825819672131145 -133620000.0,1.0,6.974758505515629e-07,0.9825819672131146 -133680000.0,1.0,6.85327193318595e-07,0.9825819672131146 -133740000.0,1.0,6.733901417956277e-07,0.9825819672131147 -133800000.0,1.0,6.61661010227466e-07,0.9825819672131145 -133860000.0,1.0,6.501361770575204e-07,0.9825819672131147 -133920000.0,1.0,6.388120838095922e-07,0.9825819672131146 -133980000.0,1.0,6.276852339891381e-07,0.9825819672131145 -134040000.0,1.0,6.167521920036715e-07,0.9825819672131146 -134100000.0,1.0,6.060095821019682e-07,0.9825819672131146 -134160000.0,1.0,5.954540873317494e-07,0.9825819672131146 -134220000.0,1.0,5.850824485155201e-07,0.9825819672131146 -134280000.0,1.0,5.748914632442456e-07,0.9825819672131146 -134340000.0,1.0,5.648779848885569e-07,0.9825819672131147 -134400000.0,1.0,5.550389216271783e-07,0.9825819672131146 -134460000.0,1.0,5.453712354922786e-07,0.9825819672131146 -134520000.0,1.0,5.358719414314499e-07,0.9825819672131146 -134580000.0,1.0,5.265381063860249e-07,0.9825819672131145 -134640000.0,1.0,5.173668483854486e-07,0.9825819672131147 -134700000.0,1.0,5.083553356574234e-07,0.9825819672131147 -134760000.0,1.0,4.995007857535543e-07,0.9825819672131147 -134820000.0,1.0,4.908004646902239e-07,0.9825819672131145 -134880000.0,1.0,4.82251686104431e-07,0.9825819672131146 -134940000.0,1.0,4.738518104243333e-07,0.9825819672131146 -135000000.0,1.0,4.655982440542373e-07,0.9825819672131146 -135060000.0,1.0,4.574884385737843e-07,0.9825819672131145 -135120000.0,1.0,4.495198899510851e-07,0.9825819672131145 -135180000.0,1.0,4.4169013776956e-07,0.9825819672131145 -135240000.0,1.0,4.3399676446824586e-07,0.9825819672131146 -135300000.0,1.0,4.264373945953358e-07,0.9825819672131146 -135360000.0,1.0,4.1900969407472026e-07,0.9825819672131146 -135420000.0,1.0,4.1171136948530394e-07,0.9825819672131145 -135480000.0,1.0,4.045401673528754e-07,0.9825819672131146 -135540000.0,1.0,3.9749387345431095e-07,0.9825819672131146 -135600000.0,1.0,3.905703121338977e-07,0.9825819672131146 -135660000.0,1.0,3.8376734563156545e-07,0.9825819672131147 -135720000.0,1.0,3.7708287342281887e-07,0.9825819672131146 -135780000.0,1.0,3.7051483157016726e-07,0.9825819672131146 -135840000.0,1.0,3.640611920858508e-07,0.9825819672131146 -135900000.0,1.0,3.577199623056669e-07,0.9825819672131147 -135960000.0,1.0,3.514891842737034e-07,0.9825819672131146 -136020000.0,1.0,3.453669341377884e-07,0.9825819672131146 -136080000.0,1.0,3.3935132155547034e-07,0.9825819672131146 -136140000.0,1.0,3.334404891103443e-07,0.9825819672131146 -136200000.0,1.0,3.276326117385452e-07,0.9825819672131146 -136260000.0,1.0,3.2192589616523036e-07,0.9825819672131147 -136320000.0,1.0,3.1631858035087694e-07,0.9825819672131146 -136380000.0,1.0,3.1080893294722435e-07,0.9825819672131146 -136440000.0,1.0,3.0539525276269277e-07,0.9825819672131147 -136500000.0,1.0,3.000758682371131e-07,0.9825819672131147 -136560000.0,1.0,2.9484913692560596e-07,0.9825819672131146 -136620000.0,1.0,2.897134449914509e-07,0.9825819672131146 -136680000.0,1.0,2.846672067077883e-07,0.9825819672131145 -136740000.0,1.0,2.7970886396800096e-07,0.9825819672131146 -136800000.0,1.0,2.748368858046239e-07,0.9825819672131146 -136860000.0,1.0,2.700497679166335e-07,0.9825819672131147 -136920000.0,1.0,2.653460322049708e-07,0.9825819672131146 -136980000.0,1.0,2.607242263161547e-07,0.9825819672131146 -137040000.0,1.0,2.5618292319384457e-07,0.9825819672131146 -137100000.0,1.0,2.51720720638214e-07,0.9825819672131145 -137160000.0,1.0,2.473362408729992e-07,0.9825819672131145 -137220000.0,1.0,2.430281301200883e-07,0.9825819672131145 -137280000.0,1.0,2.3879505818152113e-07,0.9825819672131146 -137340000.0,1.0,2.3463571802876918e-07,0.9825819672131145 -137400000.0,1.0,2.3054882539916969e-07,0.9825819672131146 -137460000.0,1.0,2.2653311839938902e-07,0.9825819672131145 -137520000.0,1.0,2.2258735711579306e-07,0.9825819672131145 -137580000.0,1.0,2.1871032323160402e-07,0.9825819672131146 -137640000.0,1.0,2.1490081965072563e-07,0.9825819672131145 -137700000.0,1.0,2.1115767012812076e-07,0.9825819672131146 -137760000.0,1.0,2.0750097925286178e-07,0.9826826519110565 -137820000.0,1.0,2.0392850902182252e-07,0.982783357245337 -137880000.0,1.0,2.0043808563280492e-07,0.9828840832223017 -137940000.0,1.0,1.9702759750088145e-07,0.9829848298482986 -138000000.0,1.0,1.9369499334017967e-07,0.9830855971296769 -138060000.0,1.0,1.904382803088295e-07,0.9831863850727905 -138120000.0,1.0,1.8725552221487487e-07,0.9832871936839945 -138180000.0,1.0,1.8414483778103466e-07,0.9833880229696472 -138240000.0,1.0,1.8110439896627242e-07,0.9834888729361091 -138300000.0,1.0,1.781324293422105e-07,0.9835897435897436 -138360000.0,1.0,1.7522720252249447e-07,0.9836906349369164 -138420000.0,1.0,1.7238704064328294e-07,0.9837915469839967 -138480000.0,1.0,1.6961031289310385e-07,0.983892479737355 -138540000.0,1.0,1.668954340903823e-07,0.9839934332033655 -138600000.0,1.0,1.6424086330700527e-07,0.9840944073884044 -138660000.0,1.0,1.6164510253634856e-07,0.9841954022988505 -138720000.0,1.0,1.5910669540424743e-07,0.9842964179410859 -138780000.0,1.0,1.566242259214466e-07,0.9843974543214945 -138840000.0,1.0,1.5419631727611874e-07,0.9844985114464632 -138900000.0,1.0,1.5182163066509022e-07,0.984599589322382 -138960000.0,1.0,1.4949886416246176e-07,0.9847006879556421 -139020000.0,1.0,1.4722675162435902e-07,0.9848018073526391 -139080000.0,1.0,1.4500406162859228e-07,0.98490294751977 -139140000.0,1.0,1.4282959644804849e-07,0.9850041084634347 -139200000.0,1.0,1.4070219105668052e-07,0.9851052901900359 -139260000.0,1.0,1.386207121669988e-07,0.985206492705979 -139320000.0,1.0,1.3658405729800863e-07,0.9853077160176715 -139380000.0,1.0,1.345911538725753e-07,0.9854089601315249 -139440000.0,1.0,1.3264095834323266e-07,0.9855102250539512 -139500000.0,1.0,1.307324553454883e-07,0.9856115107913668 -139560000.0,1.0,1.288646568777092e-07,0.9857128173501902 -139620000.0,1.0,1.2703660150670552e-07,0.9858141447368421 -139680000.0,1.0,1.2524735359816037e-07,0.9859154929577464 -139740000.0,1.0,1.234960025710835e-07,0.9860168620193298 -139800000.0,1.0,1.2178166217549518e-07,0.9861182519280205 -139860000.0,1.0,1.2010346979257493e-07,0.9862196626902507 -139920000.0,1.0,1.1846058575653537e-07,0.9863210943124548 -139980000.0,1.0,1.1685219269750814e-07,0.9864225468010697 -140040000.0,1.0,1.1527749490475291e-07,0.9865240201625347 -140100000.0,1.0,1.137357177095247e-07,0.9866255144032919 -140160000.0,1.0,1.1222610688695772e-07,0.9867270295297871 -140220000.0,1.0,1.1074792807634539e-07,0.9868285655484668 -140280000.0,1.0,1.0930046621921913e-07,0.9869301224657816 -140340000.0,1.0,1.0788302501464712e-07,0.9870317002881843 -140400000.0,1.0,1.0649492639119567e-07,0.9871332990221308 -140460000.0,1.0,1.0513550999501404e-07,0.9872349186740785 -140520000.0,1.0,1.0380413269352256e-07,0.987336559250489 -140580000.0,1.0,1.0250016809420113e-07,0.9874382207578254 -140640000.0,1.0,1.0122300607799288e-07,0.9875399032025538 -140700000.0,1.0,9.997205234685392e-08,0.9876416065911431 -140760000.0,1.0,9.87467279849963e-08,0.9877433309300648 -140820000.0,1.0,9.754646903338632e-08,0.987845076225793 -140880000.0,1.0,9.637072607707578e-08,0.9879468424848046 -140940000.0,1.0,9.521896384495742e-08,0.9880486297135792 -141000000.0,1.0,9.409066082154987e-08,0.9881504379185985 -141060000.0,1.0,9.298530887043108e-08,0.9882522671063478 -141120000.0,1.0,9.190241286895126e-08,0.9883541172833145 -141180000.0,1.0,9.084149035386958e-08,0.9884559884559885 -141240000.0,1.0,8.980207117757027e-08,0.9885578806308628 -141300000.0,1.0,8.878369717452564e-08,0.9886597938144328 -141360000.0,1.0,8.778592183768438e-08,0.9887617280131972 -141420000.0,1.0,8.680831000447446e-08,0.9888636832336565 -141480000.0,1.0,8.585043755212024e-08,0.9889656594823141 -141540000.0,1.0,8.491189110198363e-08,0.9890676567656768 -141600000.0,1.0,8.399226773264807e-08,0.9891696750902528 -141660000.0,1.0,8.309117470147462e-08,0.989271714462554 -141720000.0,1.0,8.220822917436723e-08,0.9893737748890952 -141780000.0,1.0,8.134305796349377e-08,0.9894758563763928 -141840000.0,1.0,8.049529727271749e-08,0.9895779589309668 -141900000.0,1.0,7.966459245050161e-08,0.9896800825593394 -141960000.0,1.0,7.885059775005784e-08,0.9897822272680359 -142020000.0,1.0,7.80529760965168e-08,0.9898843930635838 -142080000.0,1.0,7.727139886090595e-08,0.9899865799525135 -142140000.0,1.0,7.650554564072766e-08,0.9900887879413587 -142200000.0,1.0,7.57551040469363e-08,0.9901910170366545 -142260000.0,1.0,7.501976949712093e-08,0.9902932672449399 -142320000.0,1.0,7.429924501470513e-08,0.9903955385727564 -142380000.0,1.0,7.359324103398287e-08,0.9904978310266475 -142440000.0,1.0,7.290147521081455e-08,0.9906001446131597 -142500000.0,1.0,7.222367223881317e-08,0.9907024793388429 -142560000.0,1.0,7.155956367085632e-08,0.990804835210249 -142620000.0,1.0,7.090888774576483e-08,0.9909072122339325 -142680000.0,1.0,7.027138921999428e-08,0.9910096104164514 -142740000.0,1.0,6.964681920419028e-08,0.9911120297643654 -142800000.0,1.0,6.903493500446354e-08,0.9912144702842377 -142860000.0,1.0,6.843549996824533e-08,0.9913169319826338 -142920000.0,1.0,6.78482833345883e-08,0.9914194148661221 -142980000.0,1.0,6.727306008878224e-08,0.9915219189412737 -143040000.0,1.0,6.670961082115827e-08,0.9916244442146623 -143100000.0,1.0,6.615772158995944e-08,0.9917269906928644 -143160000.0,1.0,6.561718378815917e-08,0.9918295583824595 -143220000.0,1.0,6.50877940141132e-08,0.9919321472900289 -143280000.0,1.0,6.456935394593417e-08,0.9920347574221579 -143340000.0,1.0,6.406167021948154e-08,0.9921373887854333 -143400000.0,1.0,6.35645543098632e-08,0.9922400413864458 -143460000.0,1.0,6.307782241634811e-08,0.9923427152317881 -143520000.0,1.0,6.26012953505928e-08,0.9924454103280553 -143580000.0,1.0,6.213479842808786e-08,0.9925481266818462 -143640000.0,1.0,6.16781613627329e-08,0.992650864299762 -143700000.0,1.0,6.123121816445224e-08,0.9927536231884059 -143760000.0,1.0,6.079380703976569e-08,0.9928564033543843 -143820000.0,1.0,6.036577029523223e-08,0.9929592048043072 -143880000.0,1.0,5.994695424368614e-08,0.9930620275447861 -143940000.0,1.0,5.9537209113188705e-08,0.9931648715824358 -144000000.0,1.0,5.913638895862038e-08,0.9932677369238738 -144060000.0,1.0,5.874435157584103e-08,0.9933706235757197 -144120000.0,1.0,5.836095841834823e-08,0.9934735315445975 -144180000.0,1.0,5.798607451636547e-08,0.9935764608371321 -144240000.0,1.0,5.7619568398294986e-08,0.9936794114599524 -144300000.0,1.0,5.7261312014471385e-08,0.993782383419689 -144360000.0,1.0,5.691118066315479e-08,0.9938853767229765 -144420000.0,1.0,5.656905291870382e-08,0.9939883913764511 -144480000.0,1.0,5.6234810561871007e-08,0.9940914273867524 -144540000.0,1.0,5.590833851216493e-08,0.9941944847605224 -144600000.0,1.0,5.558952476222517e-08,0.9942975635044065 -144660000.0,1.0,5.527826031415796e-08,0.9944006636250519 -144720000.0,1.0,5.4974439117782304e-08,0.9945037851291091 -144780000.0,1.0,5.467795801073763e-08,0.9946069280232316 -144840000.0,1.0,5.4388716660405967e-08,0.9947100923140754 -144900000.0,1.0,5.410661750760303e-08,0.9948132780082988 -144960000.0,1.0,5.38315657119943e-08,0.9949164851125636 -145020000.0,1.0,5.3563469099193324e-08,0.9950197136335337 -145080000.0,1.0,5.330223810950129e-08,0.9951229635778768 -145140000.0,1.0,5.3047785748247955e-08,0.9952262349522623 -145200000.0,1.0,5.280002753769568e-08,0.9953295277633628 -145260000.0,1.0,5.255888147046933e-08,0.9954328420178533 -145320000.0,1.0,5.232426796447637e-08,0.9955361777224125 -145380000.0,1.0,5.209610981928244e-08,0.995639534883721 -145440000.0,1.0,5.1874332173909104e-08,0.9957429135084622 -145500000.0,1.0,5.165886246602163e-08,0.9958463136033229 -145560000.0,1.0,5.1449630392475583e-08,0.9959497351749922 -145620000.0,1.0,5.1246567871192444e-08,0.996053178230162 -145680000.0,1.0,5.1049609004335255e-08,0.9961566427755271 -145740000.0,1.0,5.08586900427566e-08,0.996260128817785 -145800000.0,1.0,5.067374935169203e-08,0.9963636363636362 -145860000.0,1.0,5.049472737767316e-08,0.9964671654197838 -145920000.0,1.0,5.032156661663572e-08,0.9965707159929335 -145980000.0,1.0,5.0154211583198554e-08,0.9966742880897942 -146040000.0,1.0,4.999260878109075e-08,0.9967778817170772 -146100000.0,1.0,4.9836706674704806e-08,0.9968814968814969 -146160000.0,1.0,4.968645566175477e-08,0.9969851335897704 -146220000.0,1.0,4.954180804701894e-08,0.997088791848617 -146280000.0,1.0,4.940271801714792e-08,0.9971924716647602 -146340000.0,1.0,4.926914161651919e-08,0.997296173044925 -146400000.0,1.0,4.914103672412054e-08,0.9973998959958398 -146460000.0,1.0,4.9018363031445386e-08,0.9975036405242355 -146520000.0,1.0,4.8901082021383666e-08,0.9976074066368458 -146580000.0,1.0,4.8789156948092936e-08,0.9977111943404077 -146640000.0,1.0,4.8682552817834904e-08,0.9978150036416606 -146700000.0,1.0,4.8581236370763446e-08,0.9979188345473465 -146760000.0,1.0,4.848517606365089e-08,0.9980226870642105 -146820000.0,1.0,4.839434205353997e-08,0.9981265611990009 -146880000.0,1.0,4.8308706182309606e-08,0.9982304569584679 -146940000.0,1.0,4.822824196214336e-08,0.998334374349365 -147000000.0,1.0,4.8152924561890135e-08,0.9984383133784485 -147060000.0,1.0,4.8082730794307205e-08,0.9985422740524782 -147120000.0,1.0,4.801763910417641e-08,0.9986462563782151 -147180000.0,1.0,4.795762955728513e-08,0.9987502603624245 -147240000.0,1.0,4.790268383026396e-08,0.9988542860118736 -147300000.0,1.0,4.785278520127409e-08,0.9989583333333331 -147360000.0,1.0,4.7807918541537506e-08,0.9990624023335763 -147420000.0,1.0,4.776807030770417e-08,0.9991664930193789 -147480000.0,1.0,4.773322853505085e-08,0.9992706053975201 -147540000.0,1.0,4.770338283150663e-08,0.9993747394747812 -147600000.0,1.0,4.7678524372501155e-08,0.9994788952579469 -147660000.0,1.0,4.765864589663186e-08,0.9995830727538044 -147720000.0,1.0,4.7643741702147346e-08,0.9996872719691441 -147780000.0,1.0,4.763380764424448e-08,0.999791492910759 -147840000.0,1.0,4.762884113317741e-08,0.9998957355854446 -147900000.0,1.0,4.762884113317741e-08,1.0 -147960000.0,1.0,4.762884113317741e-08,1.0 -148020000.0,1.0,4.762884113317741e-08,1.0 -148080000.0,1.0,4.762884113317741e-08,1.0 -148140000.0,1.0,4.762884113317741e-08,1.0 -148200000.0,1.0,4.762884113317741e-08,1.0 -148260000.0,1.0,4.762884113317741e-08,1.0 -148320000.0,1.0,4.762884113317741e-08,1.0 -148380000.0,1.0,4.762884113317741e-08,1.0 -148440000.0,1.0,4.762884113317741e-08,1.0 -148500000.0,1.0,4.762884113317741e-08,1.0 -148560000.0,1.0,4.762884113317741e-08,1.0 -148620000.0,1.0,4.762884113317741e-08,1.0 -148680000.0,1.0,4.762884113317741e-08,1.0 -148740000.0,1.0,4.762884113317741e-08,1.0 -148800000.0,1.0,4.762884113317741e-08,1.0 -148860000.0,1.0,4.762884113317741e-08,1.0 -148920000.0,1.0,4.762884113317741e-08,1.0 -148980000.0,1.0,4.762884113317741e-08,1.0 -149040000.0,1.0,4.762884113317741e-08,1.0 -149100000.0,1.0,4.762884113317741e-08,1.0 -149160000.0,1.0,4.762884113317741e-08,1.0 -149220000.0,1.0,4.762884113317741e-08,1.0 -149280000.0,1.0,4.762884113317741e-08,1.0 -149340000.0,1.0,4.762884113317741e-08,1.0 -149400000.0,1.0,4.762884113317741e-08,1.0 -149460000.0,1.0,4.762884113317741e-08,1.0 -149520000.0,1.0,4.762884113317741e-08,1.0 -149580000.0,1.0,4.762884113317741e-08,1.0 -149640000.0,1.0,4.762884113317741e-08,1.0 -149700000.0,1.0,4.762884113317741e-08,1.0 -149760000.0,1.0,4.762884113317741e-08,1.0 -149820000.0,1.0,4.762884113317741e-08,1.0 -149880000.0,1.0,4.762884113317741e-08,1.0 -149940000.0,1.0,4.762884113317741e-08,1.0 -150000000.0,1.0,4.762884113317741e-08,1.0 -150060000.0,1.0,4.762884113317741e-08,1.0 -150120000.0,1.0,4.762884113317741e-08,1.0 -150180000.0,1.0,4.762884113317741e-08,1.0 -150240000.0,1.0,4.762884113317741e-08,1.0 -150300000.0,1.0,4.762884113317741e-08,1.0 -150360000.0,1.0,4.762884113317741e-08,1.0 -150420000.0,1.0,4.762884113317741e-08,1.0 -150480000.0,1.0,4.762884113317741e-08,1.0 -150540000.0,1.0,4.762884113317741e-08,1.0 -150600000.0,1.0,4.762884113317741e-08,1.0 -150660000.0,1.0,4.762884113317741e-08,1.0 -150720000.0,1.0,4.762884113317741e-08,1.0 -150780000.0,1.0,4.762884113317741e-08,1.0 -150840000.0,1.0,4.762884113317741e-08,1.0 -150900000.0,1.0,4.762884113317741e-08,1.0 -150960000.0,1.0,4.762884113317741e-08,1.0 diff --git a/source-code/Poisson-Graphs/new/outputM.txt b/source-code/Poisson-Graphs/new/outputM.txt deleted file mode 100644 index 99e4ad2..0000000 --- a/source-code/Poisson-Graphs/new/outputM.txt +++ /dev/null @@ -1,602 +0,0 @@ -time,rateConstant,difficulty -0.0,1.0,1.0 -1.0,1.0,1.0 -2.0,1.0,1.0 -3.0,1.0,1.0 -4.0,1.0,1.0 -5.0,1.0,1.0 -6.0,1.0,1.0 -7.0,1.0,1.0 -8.0,1.0,1.0 -9.0,1.0,1.0 -10.0,1.0,1.0 -11.0,1.0,1.0 -12.0,1.0,1.0 -13.0,1.0,1.0 -14.0,1.0,1.0 -15.0,1.0,1.0 -16.0,1.0,1.0 -17.0,1.0,1.0 -18.0,1.0,1.0 -19.0,1.0,1.0 -20.0,1.0,1.0 -21.0,1.0,1.0 -22.0,1.0,1.0 -23.0,1.0,1.0 -24.0,1.0,1.0 -25.0,1.0,1.0 -26.0,1.0,1.0 -27.0,1.0,1.0 -28.0,1.0,1.0 -29.0,1.0,1.0 -30.0,1.0,1.0 -31.0,1.0,1.0 -32.0,1.0,1.0 -33.0,1.0,1.0 -34.0,1.0,1.0 -35.0,1.0,1.0 -36.0,1.0,1.0 -37.0,1.0,1.0 -38.0,1.0,1.0 -39.0,1.0,1.0 -40.0,1.0,1.0 -41.0,1.0,1.0 -42.0,1.0,1.0 -43.0,1.0,1.0 -44.0,1.0,1.0 -45.0,1.0,1.0 -46.0,1.0,1.0 -47.0,1.0,1.0 -48.0,1.0,1.0 -49.0,1.0,1.0 -50.0,1.0,1.0 -51.0,1.0,1.0 -52.0,1.0,1.0 -53.0,1.0,1.0 -54.0,1.0,1.0 -55.0,1.0,1.0 -56.0,1.0,1.0 -57.0,1.0,1.0 -58.0,1.0,1.0 -59.0,1.0,1.0 -60.0,1.0,1.0 -61.0,1.0,1.0 -62.0,1.0,1.0 -63.0,1.0,1.0 -64.0,1.0,1.0 -65.0,1.0,1.0 -66.0,1.0,1.0 -67.0,1.0,1.0 -68.0,1.0,1.0 -69.0,1.0,1.0 -70.0,1.0,1.0 -71.0,1.0,1.0 -72.0,1.0,1.0 -73.0,1.0,1.0 -74.0,1.0,1.0 -75.0,1.0,1.0 -76.0,1.0,1.0 -77.0,1.0,1.0 -78.0,1.0,1.0 -79.0,1.0,1.0 -80.0,1.0,1.0 -81.0,1.0,1.0 -82.0,1.0,1.0 -83.0,1.0,1.0 -84.0,1.0,1.0 -85.0,1.0,1.0 -86.0,1.0,1.0 -87.0,1.0,1.0 -88.0,1.0,1.0 -89.0,1.0,1.0 -90.0,1.0,1.0 -91.0,1.0,1.0 -92.0,1.0,1.0 -93.0,1.0,1.0 -94.0,1.0,1.0 -95.0,1.0,1.0 -96.0,1.0,1.0 -97.0,1.0,1.0 -98.0,1.0,1.0 -99.0,1.0,1.0 -100.0,1.0,1.0 -101.0,1.0,1.0 -102.0,1.0,1.0 -103.0,1.0,1.0 -104.0,1.0,1.0 -105.0,1.0,1.0 -106.0,1.0,1.0 -107.0,1.0,1.0 -108.0,1.0,1.0 -109.0,1.0,1.0 -110.0,1.0,1.0 -111.0,1.0,1.0 -112.0,1.0,1.0 -113.0,1.0,1.0 -114.0,1.0,1.0 -115.0,1.0,1.0 -116.0,1.0,1.0 -117.0,1.0,1.0 -118.0,1.0,1.0 -119.0,1.0,1.0 -120.0,1.0,1.0 -121.0,1.0,1.0 -122.0,1.0,1.0 -123.0,1.0,1.0 -124.0,1.0,1.0 -125.0,1.0,1.0 -126.0,1.0,1.0 -127.0,1.0,1.0 -128.0,1.0,1.0 -129.0,1.0,1.0 -130.0,1.0,1.0 -131.0,1.0,1.0 -132.0,1.0,1.0 -133.0,1.0,1.0 -134.0,1.0,1.0 -135.0,1.0,1.0 -136.0,1.0,1.0 -137.0,1.0,1.0 -138.0,1.0,1.0 -139.0,1.0,1.0 -140.0,1.0,1.0 -141.0,1.0,1.0 -142.0,1.0,1.0 -143.0,1.0,1.0 -144.0,1.0,1.0 -145.0,1.0,1.0 -146.0,1.0,1.0 -147.0,1.0,1.0 -148.0,1.0,1.0 -149.0,1.0,1.0 -150.0,1.0,1.0 -151.0,1.0,1.0 -152.0,1.0,1.0 -153.0,1.0,1.0 -154.0,1.0,1.0 -155.0,1.0,1.0 -156.0,1.0,1.0 -157.0,1.0,1.0 -158.0,1.0,1.0 -159.0,1.0,1.0 -160.0,1.0,1.0 -161.0,1.0,1.0 -162.0,1.0,1.0 -163.0,1.0,1.0 -164.0,1.0,1.0 -165.0,1.0,1.0 -166.0,1.0,1.0 -167.0,1.0,1.0 -168.0,1.0,1.0 -169.0,1.0,1.0 -170.0,1.0,1.0 -171.0,1.0,1.0 -172.0,1.0,1.0 -173.0,1.0,1.0 -174.0,1.0,1.0 -175.0,1.0,1.0 -176.0,1.0,1.0 -177.0,1.0,1.0 -178.0,1.0,1.0 -179.0,1.0,1.0 -180.0,1.0,1.0 -181.0,1.0,1.0 -182.0,1.0,1.0 -183.0,1.0,1.0 -184.0,1.0,1.0 -185.0,1.0,1.0 -186.0,1.0,1.0 -187.0,1.0,1.0 -188.0,1.0,1.0 -189.0,1.0,1.0 -190.0,1.0,1.0 -191.0,1.0,1.0 -192.0,1.0,1.0 -193.0,1.0,1.0 -194.0,1.0,1.0 -195.0,1.0,1.0 -196.0,1.0,1.0 -197.0,1.0,1.0 -198.0,1.0,1.0 -199.0,1.0,1.0 -200.0,1.0,1.0 -201.0,1.0,1.0 -202.0,1.0,1.0 -203.0,1.0,1.0 -204.0,1.0,1.0 -205.0,1.0,1.0 -206.0,1.0,1.0 -207.0,1.0,1.0 -208.0,1.0,1.0 -209.0,1.0,1.0 -210.0,1.0,1.0 -211.0,1.0,1.0 -212.0,1.0,1.0 -213.0,1.0,1.0 -214.0,1.0,1.0 -215.0,1.0,1.0 -216.0,1.0,1.0 -217.0,1.0,1.0 -218.0,1.0,1.0 -219.0,1.0,1.0 -220.0,1.0,1.0 -221.0,1.0,1.0 -222.0,1.0,1.0 -223.0,1.0,1.0 -224.0,1.0,1.0 -225.0,1.0,1.0 -226.0,1.0,1.0 -227.0,1.0,1.0 -228.0,1.0,1.0 -229.0,1.0,1.0 -230.0,1.0,1.0 -231.0,1.0,1.0 -232.0,1.0,1.0 -233.0,1.0,1.0 -234.0,1.0,1.0 -235.0,1.0,1.0 -236.0,1.0,1.0 -237.0,1.0,1.0 -238.0,1.0,1.0 -239.0,1.0,1.0 -240.0,1.0,1.0 -241.0,1.0,1.0 -242.0,1.0,1.0 -243.0,1.0,1.0 -244.0,1.0,1.0 -245.0,1.0,1.0 -246.0,1.0,1.0 -247.0,1.0,1.0 -248.0,1.0,1.0 -249.0,1.0,1.0 -250.0,1.0,1.0 -251.0,1.0,1.0 -252.0,1.0,1.0 -253.0,1.0,1.0 -254.0,1.0,1.0 -255.0,1.0,1.0 -256.0,1.0,1.0 -257.0,1.0,1.0 -258.0,1.0,1.0 -259.0,1.0,1.0 -260.0,1.0,1.0 -261.0,1.0,1.0 -262.0,1.0,1.0 -263.0,1.0,1.0 -264.0,1.0,1.0 -265.0,1.0,1.0 -266.0,1.0,1.0 -267.0,1.0,1.0 -268.0,1.0,1.0 -269.0,1.0,1.0 -270.0,1.0,1.0 -271.0,1.0,1.0 -272.0,1.0,1.0 -273.0,1.0,1.0 -274.0,1.0,1.0 -275.0,1.0,1.0 -276.0,1.0,1.0 -277.0,1.0,1.0 -278.0,1.0,1.0 -279.0,1.0,1.0 -280.0,1.0,1.0 -281.0,1.0,1.0 -282.0,1.0,1.0 -283.0,1.0,1.0 -284.0,1.0,1.0 -285.0,1.0,1.0 -286.0,1.0,1.0 -287.0,1.0,1.0 -288.0,1.0,1.0 -289.0,1.0,1.0 -290.0,1.0,1.0 -291.0,1.0,1.0 -292.0,1.0,1.0 -293.0,1.0,1.0 -294.0,1.0,1.0 -295.0,1.0,1.0 -296.0,1.0,1.0 -297.0,1.0,1.0 -298.0,1.0,1.0 -299.0,1.0,1.0 -300.0,1.0,1.0 -301.0,1.0,1.0 -302.0,1.0,1.0 -303.0,1.0,1.0 -304.0,1.0,1.0 -305.0,1.0,1.0 -306.0,1.0,1.0 -307.0,1.0,1.0 -308.0,1.0,1.0 -309.0,1.0,1.0 -310.0,1.0,1.0 -311.0,1.0,1.0 -312.0,1.0,1.0 -313.0,1.0,1.0 -314.0,1.0,1.0 -315.0,1.0,1.0 -316.0,1.0,1.0 -317.0,1.0,1.0 -318.0,1.0,1.0 -319.0,1.0,1.0 -320.0,1.0,1.0 -321.0,1.0,1.0 -322.0,1.0,1.0 -323.0,1.0,1.0 -324.0,1.0,1.0 -325.0,1.0,1.0 -326.0,1.0,1.0 -327.0,1.0,1.0 -328.0,1.0,1.0 -329.0,1.0,1.0 -330.0,1.0,1.0 -331.0,1.0,1.0 -332.0,1.0,1.0 -333.0,1.0,1.0 -334.0,1.0,1.0 -335.0,1.0,1.0 -336.0,1.0,1.0 -337.0,1.0,1.0 -338.0,1.0,1.0 -339.0,1.0,1.0 -340.0,1.0,1.0 -341.0,1.0,1.0 -342.0,1.0,1.0 -343.0,1.0,1.0 -344.0,1.0,1.0 -345.0,1.0,1.0 -346.0,1.0,1.0 -347.0,1.0,1.0 -348.0,1.0,1.0 -349.0,1.0,1.0 -350.0,1.0,1.0 -351.0,1.0,1.0 -352.0,1.0,1.0 -353.0,1.0,1.0 -354.0,1.0,1.0 -355.0,1.0,1.0 -356.0,1.0,1.0 -357.0,1.0,1.0 -358.0,1.0,1.0 -359.0,1.0,1.0 -360.0,1.0,1.0 -361.0,1.0,1.0 -362.0,1.0,1.0 -363.0,1.0,1.0 -364.0,1.0,1.0 -365.0,1.0,1.0 -366.0,1.0,1.0 -367.0,1.0,1.0 -368.0,1.0,1.0 -369.0,1.0,1.0 -370.0,1.0,1.0 -371.0,1.0,1.0 -372.0,1.0,1.0 -373.0,1.0,1.0 -374.0,1.0,1.0 -375.0,1.0,1.0 -376.0,1.0,1.0 -377.0,1.0,1.0 -378.0,1.0,1.0 -379.0,1.0,1.0 -380.0,1.0,1.0 -381.0,1.0,1.0 -382.0,1.0,1.0 -383.0,1.0,1.0 -384.0,1.0,1.0 -385.0,1.0,1.0 -386.0,1.0,1.0 -387.0,1.0,1.0 -388.0,1.0,1.0 -389.0,1.0,1.0 -390.0,1.0,1.0 -391.0,1.0,1.0 -392.0,1.0,1.0 -393.0,1.0,1.0 -394.0,1.0,1.0 -395.0,1.0,1.0 -396.0,1.0,1.0 -397.0,1.0,1.0 -398.0,1.0,1.0 -399.0,1.0,1.0 -400.0,1.0,1.0 -401.0,1.0,1.0 -402.0,1.0,1.0 -403.0,1.0,1.0 -404.0,1.0,1.0 -405.0,1.0,1.0 -406.0,1.0,1.0 -407.0,1.0,1.0 -408.0,1.0,1.0 -409.0,1.0,1.0 -410.0,1.0,1.0 -411.0,1.0,1.0 -412.0,1.0,1.0 -413.0,1.0,1.0 -414.0,1.0,1.0 -415.0,1.0,1.0 -416.0,1.0,1.0 -417.0,1.0,1.0 -418.0,1.0,1.0 -419.0,1.0,1.0 -420.0,1.0,1.0 -421.0,1.0,1.0 -422.0,1.0,1.0 -423.0,1.0,1.0 -424.0,1.0,1.0 -425.0,1.0,1.0 -426.0,1.0,1.0 -427.0,1.0,1.0 -428.0,1.0,1.0 -429.0,1.0,1.0 -430.0,1.0,1.0 -431.0,1.0,1.0 -432.0,1.0,1.0 -433.0,1.0,1.0 -434.0,1.0,1.0 -435.0,1.0,1.0 -436.0,1.0,1.0 -437.0,1.0,1.0 -438.0,1.0,1.0 -439.0,1.0,1.0 -440.0,1.0,1.0 -441.0,1.0,1.0 -442.0,1.0,1.0 -443.0,1.0,1.0 -444.0,1.0,1.0 -445.0,1.0,1.0 -446.0,1.0,1.0 -447.0,1.0,1.0 -448.0,1.0,1.0 -449.0,1.0,1.0 -450.0,1.0,1.0 -451.0,1.0,1.0 -452.0,1.0,1.0 -453.0,1.0,1.0 -454.0,1.0,1.0 -455.0,1.0,1.0 -456.0,1.0,1.0 -457.0,1.0,1.0 -458.0,1.0,1.0 -459.0,1.0,1.0 -460.0,1.0,1.0 -461.0,1.0,1.0 -462.0,1.0,1.0 -463.0,1.0,1.0 -464.0,1.0,1.0 -465.0,1.0,1.0 -466.0,1.0,1.0 -467.0,1.0,1.0 -468.0,1.0,1.0 -469.0,1.0,1.0 -470.0,1.0,1.0 -471.0,1.0,1.0 -472.0,1.0,1.0 -473.0,1.0,1.0 -474.0,1.0,1.0 -475.0,1.0,1.0 -476.0,1.0,1.0 -477.0,1.0,1.0 -478.0,1.0,1.0 -479.0,1.0,1.0 -480.0,1.0,1.0 -481.0,1.0,1.0 -482.0,1.0,1.0 -483.0,1.0,1.0 -484.0,1.0,1.0 -485.0,1.0,1.0 -486.0,1.0,1.0 -487.0,1.0,1.0 -488.0,1.0,1.0 -489.0,1.0,1.0 -490.0,1.0,1.0 -491.0,1.0,1.0 -492.0,1.0,1.0 -493.0,1.0,1.0 -494.0,1.0,1.0 -495.0,1.0,1.0 -496.0,1.0,1.0 -497.0,1.0,1.0 -498.0,1.0,1.0 -499.0,1.0,1.0 -500.0,1.0,1.0 -501.0,1.0,1.0 -502.0,1.0,1.0 -503.0,1.0,1.0 -504.0,1.0,1.0 -505.0,1.0,1.0 -506.0,1.0,1.0 -507.0,1.0,1.0 -508.0,1.0,1.0 -509.0,1.0,1.0 -510.0,1.0,1.0 -511.0,1.0,1.0 -512.0,1.0,1.0 -513.0,1.0,1.0 -514.0,1.0,1.0 -515.0,1.0,1.0 -516.0,1.0,1.0 -517.0,1.0,1.0 -518.0,1.0,1.0 -519.0,1.0,1.0 -520.0,1.0,1.0 -521.0,1.0,1.0 -522.0,1.0,1.0 -523.0,1.0,1.0 -524.0,1.0,1.0 -525.0,1.0,1.0 -526.0,1.0,1.0 -527.0,1.0,1.0 -528.0,1.0,1.0 -529.0,1.0,1.0 -530.0,1.0,1.0 -531.0,1.0,1.0 -532.0,1.0,1.0 -533.0,1.0,1.0 -534.0,1.0,1.0 -535.0,1.0,1.0 -536.0,1.0,1.0 -537.0,1.0,1.0 -538.0,1.0,1.0 -539.0,1.0,1.0 -540.0,1.0,1.0 -541.0,1.0,1.0 -542.0,1.0,1.0 -543.0,1.0,1.0 -544.0,1.0,1.0 -545.0,1.0,1.0 -546.0,1.0,1.0 -547.0,1.0,1.0 -548.0,1.0,1.0 -549.0,1.0,1.0 -550.0,1.0,1.0 -551.0,1.0,1.0 -552.0,1.0,1.0 -553.0,1.0,1.0 -554.0,1.0,1.0 -555.0,1.0,1.0 -556.0,1.0,1.0 -557.0,1.0,1.0 -558.0,1.0,1.0 -559.0,1.0,1.0 -560.0,1.0,1.0 -561.0,1.0,1.0 -562.0,1.0,1.0 -563.0,1.0,1.0 -564.0,1.0,1.0 -565.0,1.0,1.0 -566.0,1.0,1.0 -567.0,1.0,1.0 -568.0,1.0,1.0 -569.0,1.0,1.0 -570.0,1.0,1.0 -571.0,1.0,1.0 -572.0,1.0,1.0 -573.0,1.0,1.0 -574.0,1.0,1.0 -575.0,1.0,1.0 -576.0,1.0,1.0 -577.0,1.0,1.0 -578.0,1.0,1.0 -579.0,1.0,1.0 -580.0,1.0,1.0 -581.0,1.0,1.0 -582.0,1.0,1.0 -583.0,1.0,1.0 -584.0,1.0,1.0 -585.0,1.0,1.0 -586.0,1.0,1.0 -587.0,1.0,1.0 -588.0,1.0,1.0 -589.0,1.0,1.0 -590.0,1.0,1.0 -591.0,1.0,1.0 -592.0,1.0,1.0 -593.0,1.0,1.0 -594.0,1.0,1.0 -595.0,1.0,1.0 -596.0,1.0,1.0 -597.0,1.0,1.0 -598.0,1.0,1.0 -599.0,1.0,1.0 -600.0,1.0,1.0 diff --git a/source-code/Poisson-Graphs/new/outputM.txt~ b/source-code/Poisson-Graphs/new/outputM.txt~ deleted file mode 100644 index 7af84cc..0000000 --- a/source-code/Poisson-Graphs/new/outputM.txt~ +++ /dev/null @@ -1,201 +0,0 @@ -time,rateConstant,difficulty -0.0,1.0,1.0 -120458.46590012926,1.0,1.0 -240605.33373578714,1.0,1.0 -360287.44233708055,1.0,1.0 -480034.84471731156,1.0,1.0 -601021.4072874074,1.0,1.0 -720155.9397105722,1.0,1.0 -840557.1097845419,1.0,1.0 -960142.4715273783,1.0,1.0 -1080326.4196690398,1.0,1.0 -1200228.3567496322,1.0,1.0 -1321393.1769845556,1.0,1.0 -1442403.7637959223,1.0,1.0 -1563312.6884154421,1.0,1.0 -1683650.3562759135,1.0,1.0 -1804242.0878089573,1.0,1.0 -1923957.6270508477,1.0,1.0 -2043603.5193920576,1.0,1.0 -2162815.4440224506,1.0,1.0 -2282288.929005547,1.0,1.0 -2402114.705755926,1.0,1.0 -2522303.585101224,1.0,1.0 -2643267.2988593285,1.0,1.0 -2762617.5338163865,1.0,1.0 -2882563.380726947,1.0,1.0 -3003489.532699132,1.0,1.0 -3124641.562256374,1.0,1.0 -3245100.9215427977,1.0,1.0 -3365536.420458877,1.0,1.0 -3484337.996609595,1.0,1.0 -3604694.7847104715,1.0,1.0 -3724951.576782264,1.0,1.0 -3846026.69613723,1.0,1.0 -3967024.7982774274,1.0,1.0 -4085911.063711746,1.0,1.0 -4205815.028144305,1.0,1.0 -4325421.232620401,1.0,1.0 -4444827.052513202,1.0,1.0 -4565099.154304211,1.0,1.0 -4686236.387124661,1.0,1.0 -4806734.051850467,1.0,1.0 -4926566.647937627,1.0,1.0 -5045488.866484466,1.0,1.0 -5166502.755345808,1.0,1.0 -5287233.653263695,1.0,1.0 -5407908.334559678,1.0,1.0 -5528786.366282915,1.0,1.0 -5647678.3122160155,1.0,1.0 -5767244.089580304,1.0,1.0 -5886473.651601109,1.0,1.0 -6006435.897878854,1.0,1.0 -6125341.27352921,1.0,1.0 -6245479.785253891,1.0,1.0 -6365523.236878065,1.0,1.0 -6485749.795878896,1.0,1.0 -6605240.444894265,1.0,1.0 -6724686.210446132,1.0,1.0 -6844155.218750592,1.0,1.0 -6963341.181369955,1.0,1.0 -7082695.6923123505,1.0,1.0 -7203655.141225615,1.0,1.0 -7322627.204840391,1.0,1.0 -7441799.159546731,1.0,1.0 -7562822.49932097,1.0,1.0 -7683323.524066307,1.0,1.0 -7804477.48899398,1.0,1.0 -7923457.857637024,1.0,1.0 -8043898.249303401,1.0,1.0 -8164187.296374614,1.0,1.0 -8283469.107294493,1.0,1.0 -8402627.723784521,1.0,1.0 -8523745.89200794,1.0,1.0 -8643778.9729813,1.0,1.0 -8764635.624631569,1.0,1.0 -8885538.059417976,1.0,1.0 -9006433.79263568,1.0,1.0 -9127489.588015186,1.0,1.0 -9247569.28782317,1.0,1.0 -9367958.084440755,1.0,1.0 -9487586.29442678,1.0,1.0 -9606427.886281481,1.0,1.0 -9725872.946140163,1.0,1.0 -9846820.596317865,1.0,1.0 -9966080.313457007,1.0,1.0 -10085765.108573573,1.0,1.0 -10206008.374459436,1.0,1.0 -10326807.845790997,1.0,1.0 -10447513.423168132,1.0,1.0 -10566795.347145824,1.0,1.0 -10686179.997466285,1.0,1.0 -10805522.808373246,1.0,1.0 -10925591.945700001,1.0,1.0 -11045387.795286857,1.0,1.0 -11165651.717103494,1.0,1.0 -11286830.098064246,1.0,1.0 -11406728.71699933,1.0,1.0 -11525922.789870113,1.0,1.0 -11646476.03903497,1.0,1.0 -11766081.434268286,1.0,1.0 -11886554.737807736,1.0,1.0 -12006591.066234503,1.0,1.0 -12125475.542528223,1.0,1.0 -12245282.608027933,1.0,1.0 -12364566.337023206,1.0,1.0 -12484001.537563423,1.0,1.0 -12604914.58673975,1.0,1.0 -12725797.319719713,1.0,1.0 -12844665.121160349,1.0,1.0 -12964577.111847764,1.0,1.0 -13083664.372389417,1.0,1.0 -13204397.122438395,1.0,1.0 -13324189.80038166,1.0,1.0 -13445349.787586372,1.0,1.0 -13564667.751049513,1.0,1.0 -13684270.141374838,1.0,1.0 -13804933.175467426,1.0,1.0 -13926075.032552686,1.0,1.0 -14045914.739095576,1.0,1.0 -14166631.90042476,1.0,1.0 -14286319.61550191,1.0,1.0 -14406521.722056692,1.0,0.434741782576 -14527069.972553544,1.0,0.19008730551 -14647876.788742132,1.0,0.0840528337448 -14766882.452974409,1.0,0.037302520528 -14886464.10335911,1.0,0.0165590754676 -15006383.983595744,1.0,0.00734675515331 -15125494.942906044,1.0,0.00323569454228 -15246686.831929097,1.0,0.00142738653296 -15365567.953963466,1.0,0.000625305203658 -15485210.071059253,1.0,0.00027137099116 -15605744.64157258,1.0,0.00011715461834 -15726806.733151415,1.0,5.07010986398e-05 -15847473.63409661,1.0,2.20995440742e-05 -15967616.71119521,1.0,9.7101015984e-06 -16087483.017800437,1.0,4.29503052419e-06 -16208328.180223988,1.0,1.92536966438e-06 -16328269.772618307,1.0,8.74191715802e-07 -16448898.77254663,1.0,4.04296324402e-07 -16569068.683560552,1.0,1.90818902578e-07 -16689986.260285133,1.0,9.28633545485e-08 -16810906.032656457,1.0,4.72143204458e-08 -16930289.120968327,1.0,2.49711320542e-08 -17050753.779718515,1.0,1.3892256413e-08 -17170600.427019596,1.0,8.15844774794e-09 -17289713.01359109,1.0,4.99225078962e-09 -17410383.421790008,1.0,3.25190780354e-09 -17530744.28431112,1.0,2.30869255122e-09 -17649863.36025036,1.0,1.75633116174e-09 -17771007.11205409,1.0,1.56057621444e-09 -17891565.791564044,1.0,2.04725172611e-09 -18010728.55130284,1.0,3.28303372549e-09 -18131143.8843602,1.0,3.25496154055e-09 -18251685.45446652,1.0,2.60736708269e-09 -18371507.007102486,1.0,1.85630105501e-09 -18491403.948076807,1.0,1.21928659457e-09 -18611553.039604228,1.0,7.49792394597e-10 -18730744.251551468,1.0,4.45186327526e-10 -18849856.685056694,1.0,2.61385070862e-10 -18968926.062500622,1.0,1.55149613051e-10 -19088067.33909847,1.0,9.51850841053e-11 -19207594.13354391,1.0,6.1434055957e-11 -19328017.11208744,1.0,4.14815251504e-11 -19448814.951942917,1.0,2.86357848006e-11 -19567841.28162038,1.0,2.11150220091e-11 -19688388.179414563,1.0,1.6406642243e-11 -19807627.873820234,1.0,1.43187359575e-11 -19928483.04169874,1.0,1.35913238196e-11 -20048557.76424465,1.0,1.4331290576e-11 -20167664.74191137,1.0,2.37342192382e-11 -20287610.467802733,1.0,3.14063325262e-11 -20408382.62417472,1.0,3.36556321046e-11 -20527817.08243184,1.0,3.00938650991e-11 -20646637.59478427,1.0,2.24650389033e-11 -20766577.880066067,1.0,1.49080962166e-11 -20885785.934438772,1.0,8.89077367161e-12 -21005536.07104386,1.0,4.87060433801e-12 -21124902.305172637,1.0,2.47052077112e-12 -21244885.7076036,1.0,1.17786853388e-12 -21364581.43746038,1.0,5.31532928098e-13 -21484493.676516064,1.0,2.28757375026e-13 -21603916.644479226,1.0,9.40324531004e-14 -21723733.860411238,1.0,3.70938898759e-14 -21844771.38186735,1.0,1.42162474893e-14 -21964794.368862327,1.0,5.30817642226e-15 -22085627.151319932,1.0,1.94525471916e-15 -22205153.52382764,1.0,6.9852514309e-16 -22324878.40351138,1.0,2.45757880538e-16 -22444702.60558849,1.0,8.47565473955e-17 -22564133.40356277,1.0,2.8611824903e-17 -22684916.550748322,1.0,9.50429614301e-18 -22804924.811821572,1.0,3.10972949853e-18 -22924555.89776567,1.0,1.00142181128e-18 -23044673.155832924,1.0,3.17850639386e-19 -23164255.219417505,1.0,9.93453168398e-20 -23283874.042637054,1.0,3.05585142432e-20 -23403159.90787814,1.0,9.23510334381e-21 -23522146.490881946,1.0,2.7354446299e-21 -23641985.23572208,1.0,7.94854774163e-22 -23760806.412928328,1.0,2.26044417743e-22 -23881715.30956635,1.0,6.31881915952e-23