Added simulation of 99% fault tolerant synchronous consensus
This commit is contained in:
parent
051e8a9e0c
commit
13a30dedf4
|
@ -0,0 +1,89 @@
|
|||
# Implementation of 99% fault tolerant consensus as decribed by Leslie Lamport
|
||||
# on page 391 of https://people.eecs.berkeley.edu/~luca/cs174/byzantine.pdf
|
||||
|
||||
import random
|
||||
|
||||
TIMEOUT = 10
|
||||
LATENCY = 5
|
||||
|
||||
# Network simulator
|
||||
class Network():
|
||||
def __init__(self):
|
||||
self.queue = {}
|
||||
self.time = 0
|
||||
|
||||
def add_node(self, node):
|
||||
self.queue[node.id] = {-1: node}
|
||||
|
||||
def broadcast(self, msg, at=0):
|
||||
for q in self.queue.keys():
|
||||
self.send_to(msg, q, at)
|
||||
|
||||
def send_to(self, msg, id, at):
|
||||
q = self.queue[id]
|
||||
newtime = max(self.time, at) + random.randrange(LATENCY * 2 - 1) + 1
|
||||
if newtime not in q:
|
||||
q[newtime] = []
|
||||
q[newtime].append(msg)
|
||||
|
||||
def tick(self):
|
||||
self.time += 1
|
||||
for q in self.queue.values():
|
||||
if self.time in q:
|
||||
for msg in q[self.time]:
|
||||
q[-1].on_receive(msg)
|
||||
|
||||
# A node in the network (not including the "commander")
|
||||
class Node():
|
||||
|
||||
def __init__(self, network, id, honest=True):
|
||||
self.seen = {}
|
||||
self.id = id
|
||||
self.network = network
|
||||
self.network.add_node(self)
|
||||
self.honest = honest
|
||||
|
||||
# Upon receiving a message...
|
||||
def on_receive(self, msg):
|
||||
if self.id == 0 and len(msg) == 1:
|
||||
print('Seen', msg[0], self.network.time, 'already seen', sorted(self.seen.keys()))
|
||||
# Only proceed if v not in V_i...
|
||||
# Byzantine nodes ignore this rule 5% of the time
|
||||
if (msg[0] in self.seen) and (self.honest or random.random() < 0.95):
|
||||
return
|
||||
# Timeout logic (see page 399)
|
||||
# Byzantine nodes ignore this rule
|
||||
if (len(msg) * TIMEOUT < self.network.time) and self.honest:
|
||||
return
|
||||
self.seen[msg[0]] = True
|
||||
new_msg = msg + [self.id]
|
||||
# Broadcast v:0:j1...jk:i
|
||||
# Byzantine nodes delay broadcast to split honest receiving nodes by timeout
|
||||
self.network.broadcast(new_msg, at=self.network.time if self.honest else self.network.time + int(TIMEOUT / 1.5))
|
||||
|
||||
def choice(self):
|
||||
return max(self.seen.keys())
|
||||
|
||||
def test():
|
||||
n = Network()
|
||||
nodes = [Node(n, i, i%4==0) for i in range(20)]
|
||||
for i in range(30):
|
||||
for _ in range(2):
|
||||
z = random.randrange(20)
|
||||
n.send_to([1000 + i], z, at=5+i*2)
|
||||
for i in range(21 * LATENCY):
|
||||
n.tick()
|
||||
if i % 10 == 0:
|
||||
print("Value sets", [sorted(node.seen.keys()) for node in nodes])
|
||||
countz = {}
|
||||
maxval = ""
|
||||
for node in nodes:
|
||||
if node.honest:
|
||||
k = str(sorted(node.seen.keys()))
|
||||
countz[k] = countz.get(k, 0) + 1
|
||||
if countz[k] > countz.get(maxval, 0):
|
||||
maxval = k
|
||||
print("Most popular: %s" % maxval, "with %d agreeing" % countz[maxval])
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
|
@ -2,8 +2,8 @@ from networksim import NetworkSimulator
|
|||
from ghost_node import Node, NOTARIES, Block, genesis
|
||||
from distributions import normal_distribution
|
||||
|
||||
net = NetworkSimulator(latency=12)
|
||||
notaries = [Node(i, net, ts=max(normal_distribution(200, 200)(), 0) * 0.1, sleepy=i%4==0) for i in range(NOTARIES)]
|
||||
net = NetworkSimulator(latency=22)
|
||||
notaries = [Node(i, net, ts=max(normal_distribution(300, 300)(), 0) * 0.1, sleepy=i%4==0) for i in range(NOTARIES)]
|
||||
net.agents = notaries
|
||||
net.generate_peers()
|
||||
for i in range(100000):
|
||||
|
|
Loading…
Reference in New Issue