From 13a30dedf47a3a6c15c1d7b3ebee768bc3011dc4 Mon Sep 17 00:00:00 2001 From: Vitalik Buterin Date: Tue, 31 Jul 2018 19:15:02 -0400 Subject: [PATCH] Added simulation of 99% fault tolerant synchronous consensus --- 99fault/consensus.py | 89 +++++++++++++++++++++++++++++++++++++++++ clock_disparity/test.py | 4 +- 2 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 99fault/consensus.py diff --git a/99fault/consensus.py b/99fault/consensus.py new file mode 100644 index 0000000..c30b45d --- /dev/null +++ b/99fault/consensus.py @@ -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() diff --git a/clock_disparity/test.py b/clock_disparity/test.py index 445ba05..0e9d1e6 100644 --- a/clock_disparity/test.py +++ b/clock_disparity/test.py @@ -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):