def __init__(self, k, gamma): random.seed() # RangeBST lets you efficiently lookup nodes in a range of positions self.nodes = RangeBST() self.k = k self.gamma = gamma self.two_above_array = [1.0 / pow(2, i) for i in range(400)] self.quorums = {} self.log = 0 self.redo_quorums()
class CuckooNetwork(object): def __init__(self, k, gamma): random.seed() # RangeBST lets you efficiently lookup nodes in a range of positions self.nodes = RangeBST() self.k = k self.gamma = gamma self.two_above_array = [1.0 / pow(2, i) for i in range(400)] self.quorums = {} self.log = 0 self.redo_quorums() def clone(self): c = CuckooNetwork(self.k, self.gamma) c.nodes = copy.deepcopy(self.nodes) c.quorums = self.quorums c.log = self.log return c def random_pos(self): return random.random() def __len__(self): return len(self.nodes) def two_above(self, val): if val >= 1.0: return 1.0 # We've cached the 1/powers of 2 values in self.two_above_array for (r, aval) in enumerate(self.two_above_array): if val > aval: return self.two_above_array[r - 1] elif val == aval: return aval raise Exception("Unknown above") def k_region_nodes(self, pos): (start, end) = self.k_region(pos) return self.nodes.range(start, end) def k_region(self, pos): kr_size = self.k_region_size() reg = math.floor(pos / kr_size) return (kr_size * reg, kr_size * (reg + 1)) def k_region_size(self): if len(self) == 0: return 1.0 # Assume network knows log estimate of size # and not actual size n = float(pow(2, self.log)) # Get the size of a k-region return self.two_above(self.k / n) def remove(self, node): self.remove_from_quorums(node) self.nodes.remove(node.pos) # Did we just shrink the network by a log size? # If so, it's time to adjust quorum size if len(self) == 0: self.quorums = {} elif int(math.log(len(self), 2)) < self.log: self.log = int(math.log(len(self), 2)) self.redo_quorums() def remove_from_quorums(self, node): reg = self.quorum_region(node.pos) self.quorums[reg].remove(node) def add_to_quorums(self, node): reg = self.quorum_region(node.pos) self.quorums[reg].add(node) def bootstrap_join(self, node): node.pos = self.random_pos() self.nodes.add(node.pos, node) self.add_to_quorums(node) # Did we just grow the network by a log size? # If so, it's time to adjust quorum size if math.floor(math.log(len(self), 2)) > self.log: self.log = math.floor(math.log(len(self), 2)) self.redo_quorums() def join(self, node, update_quorums=False): # Pick a random location rand = self.random_pos() evict = self.k_region_nodes(rand) # Reposition the other nodes in the k-region for evicted in evict: self.remove_from_quorums(evicted) self.nodes.remove(evicted.pos) # Secondary join (no evicting) evicted.pos = self.random_pos() self.nodes.add(evicted.pos, evicted) self.add_to_quorums(evicted) # Add the new node node.pos = rand self.add_to_quorums(node) self.nodes.add(node.pos, node) # Did we just grow the network by a log size? # If so, it's time to adjust quorum size if math.floor(math.log(len(self), 2)) > self.log: self.log = math.floor(math.log(len(self), 2)) self.redo_quorums() def quorum_size(self, pos): reg = self.quorum_region(pos) return len(self.quorums[reg]) def avg_quorum_size(self): sum = 0.0 for q in self.quorums.itervalues(): sum += len(q) return sum / len(self.quorums) def quorum_region_size(self): # gamma*log(n) k_regions # where gamma is a small integer if self.log == 0: return 1.0 # Get the size of a k-region g = float(self.gamma) return self.two_above(self.k_region_size() * (g * self.log)) def quorum_region(self, pos): q_size = self.quorum_region_size() reg = math.floor(pos / q_size) return (q_size * reg, q_size * (reg + 1)) def redo_quorums(self): self.quorums = {} q_size = self.quorum_region_size() cur = 0.0 while cur < 1.0: self.quorums[(cur, cur + q_size)] = set() cur += q_size for node in self.nodes: r = self.quorum_region(node.pos) self.quorums[r].add(node) def verify(self): # Verify each quorum region has no more than 1/4 byzantine for quorum in self.quorums.itervalues(): bad = len([1 for n in quorum if n.byzantine]) if float(bad) / len(quorum) > 0.25: return False return True def byzantine_from_least_faulty_quorum(self): faultiness = {} for (r, quorum) in self.quorums.iteritems(): bad = len([1 for n in quorum if n.byzantine]) faultiness[r] = float(bad) / len(quorum) # Find the least faulty least = (1.1, None) for (r, fault) in faultiness.iteritems(): if fault < least[0] and fault > 0.0: least = (fault, r) least_q = self.quorums[least[1]] # return the first byzantine node in this quorum for node in least_q: if node.byzantine: return node raise Exception("No node found")