logos-blockchain-specs/carnot/tree_overlay.py

134 lines
5.3 KiB
Python
Raw Normal View History

2023-06-26 15:52:14 +02:00
import itertools
2023-06-26 10:51:18 +02:00
from typing import List, Dict, Tuple, Set, Optional, Self
from carnot import Id, Committee
2023-06-27 09:39:14 +02:00
from overlay import EntropyOverlay
2023-06-26 10:51:18 +02:00
import random
2023-06-26 15:52:14 +02:00
2023-06-26 10:51:18 +02:00
class CarnotTree:
2023-06-27 09:39:14 +02:00
def __init__(self, nodes: List[Id], number_of_committees: int):
2023-06-26 10:51:18 +02:00
self.number_of_committees = number_of_committees
self.committee_size = len(nodes) // number_of_committees
2023-06-26 15:52:14 +02:00
self.inner_committees, self.membership_committees = CarnotTree.build_committee_from_nodes_with_size(
2023-06-26 10:51:18 +02:00
nodes, self.number_of_committees, self.committee_size
)
2023-06-26 15:52:14 +02:00
self.committees = {k: v for v, k in self.inner_committees.items()}
2023-06-26 10:51:18 +02:00
self.nodes = CarnotTree.build_nodes_index(nodes, self.committee_size)
2023-06-26 15:52:14 +02:00
self.committees_by_member = {
member: self.inner_committees[committee]
for committee, v in self.membership_committees.items()
for member in v
}
2023-06-26 10:51:18 +02:00
@staticmethod
2023-06-26 15:52:14 +02:00
def build_committee_from_nodes_with_size(
nodes: List[Id],
number_of_committees: int,
committee_size: int
) -> Tuple[Dict[int, Id], Dict[int, Set[Id]]]:
committees = [
2023-06-26 10:51:18 +02:00
# TODO: This hash method should be specific to what we would want to use for the protocol
2023-06-27 09:39:14 +02:00
set(nodes[n*committee_size:(n+1)*committee_size])
for n in range(0, number_of_committees)
2023-06-26 15:52:14 +02:00
]
2023-06-27 09:39:14 +02:00
# TODO: for now simples solution is make latest committee bigger
remainder = len(nodes) % committee_size
remainder_nodes = set(nodes[-remainder:])
committees[number_of_committees-1] |= remainder_nodes
committees = [frozenset(s) for s in committees]
2023-06-26 15:52:14 +02:00
hashes = [hash(s) for s in committees]
return dict(enumerate(hashes)), dict(enumerate(committees))
2023-06-26 10:51:18 +02:00
@staticmethod
def build_nodes_index(nodes: List[Id], committee_size: int) -> Dict[Id, int]:
return {
_id: i // committee_size for i, _id in enumerate(nodes)
}
2023-06-26 15:52:14 +02:00
def parent_committee(self, committee_id: Id) -> Optional[Id]:
2023-06-26 10:51:18 +02:00
return self.inner_committees[min(self.committees[committee_id] // 2 - 1, 0)]
2023-06-26 15:52:14 +02:00
def child_committees(self, committee_id: Id) -> Tuple[Optional[Id], Optional[Id]]:
2023-06-26 10:51:18 +02:00
base = self.committees[committee_id] * 2
first_child = base + 1
second_child = base + 2
return self.inner_committees[first_child], self.inner_committees[second_child]
2023-06-26 15:52:14 +02:00
def leaf_committees(self) -> Dict[Id, Committee]:
total_leafs = (self.number_of_committees + 1) // 2
return {
2023-06-27 09:39:14 +02:00
self.inner_committees[i]: self.membership_committees[i]
2023-06-26 15:52:14 +02:00
for i in range(self.number_of_committees - total_leafs, self.number_of_committees)
}
def root_committee(self) -> Committee:
return self.membership_committees[0]
def committee_by_committee_id(self, committee_id: Id) -> Optional[Committee]:
return self.membership_committees.get(self.inner_committees[committee_id])
def committee_by_member_id(self, member_id: Id) -> Id:
return self.committees_by_member[member_id]
2023-06-26 10:51:18 +02:00
class CarnotOverlay(EntropyOverlay):
def __init__(self, nodes: List[Id], current_leader: Id, entropy: bytes, number_of_committees: int):
self.entropy = entropy
self.number_of_committees = number_of_committees
self.nodes = nodes.copy()
self.current_leader = current_leader
random.seed(a=self.entropy, version=2)
random.shuffle(self.nodes)
self.carnot_tree = CarnotTree(nodes, number_of_committees)
def advance(self, entropy: bytes) -> Self:
2023-06-27 09:39:14 +02:00
return CarnotOverlay(self.nodes, self.next_leader(), entropy, self.number_of_committees)
2023-06-26 10:51:18 +02:00
def is_leader(self, _id: Id):
return _id == self.leader()
def leader(self) -> Id:
return self.current_leader
def next_leader(self) -> Id:
random.seed(a=self.entropy, version=2)
return random.choice(self.nodes)
def is_member_of_leaf_committee(self, _id: Id) -> bool:
2023-06-26 15:52:14 +02:00
return _id in set(itertools.chain.from_iterable(self.carnot_tree.leaf_committees().values()))
2023-06-26 10:51:18 +02:00
def is_member_of_root_committee(self, _id: Id) -> bool:
2023-06-26 15:52:14 +02:00
return _id in self.carnot_tree.root_committee()
2023-06-26 10:51:18 +02:00
def is_member_of_child_committee(self, parent: Id, child: Id) -> bool:
2023-06-26 15:52:14 +02:00
l, r = self.carnot_tree.child_committees(parent)
l = self.carnot_tree.committee_by_committee_id(l) if l is not None else set() or set()
r = self.carnot_tree.committee_by_committee_id(r) if r is not None else set() or set()
return child in l.join(r)
2023-06-26 10:51:18 +02:00
def parent_committee(self, _id: Id) -> Optional[Committee]:
2023-06-26 15:52:14 +02:00
return self.carnot_tree.committee_by_committee_id(
self.carnot_tree.parent_committee(
self.carnot_tree.committee_by_member_id(_id)
)
)
2023-06-26 10:51:18 +02:00
def leaf_committees(self) -> Set[Committee]:
2023-06-26 15:52:14 +02:00
return set(self.carnot_tree.leaf_committees().values())
2023-06-26 10:51:18 +02:00
def root_committee(self) -> Committee:
2023-06-26 15:52:14 +02:00
return self.carnot_tree.root_committee()
2023-06-26 10:51:18 +02:00
def is_child_of_root_committee(self, _id: Id) -> bool:
2023-06-26 15:52:14 +02:00
return _id in self.root_committee()
2023-06-26 10:51:18 +02:00
def leader_super_majority_threshold(self, _id: Id) -> int:
2023-06-26 15:52:14 +02:00
return (self.carnot_tree.committee_size * 2 // 3) + 1
2023-06-26 10:51:18 +02:00
def super_majority_threshold(self, _id: Id) -> int:
2023-06-27 09:39:14 +02:00
if self.is_member_of_leaf_committee(_id):
return 0
2023-06-26 15:52:14 +02:00
return (self.carnot_tree.committee_size * 2 // 3) + 1