diff --git a/carnot/beaconized_carnot.py b/carnot/beaconized_carnot.py index 32b4863..3dd1d7f 100644 --- a/carnot/beaconized_carnot.py +++ b/carnot/beaconized_carnot.py @@ -18,7 +18,6 @@ class BeaconizedCarnot(Carnot): self.random_beacon = RandomBeaconHandler( RecoveryMode.generate_beacon(entropy, -1) ) - overlay.set_entropy(self.random_beacon.last_beacon.entropy()) super().__init__(self.pk, overlay=overlay) def approve_block(self, block: BeaconizedBlock, votes: Set[Vote]) -> Event: @@ -45,13 +44,13 @@ class BeaconizedCarnot(Carnot): # root members send votes to next leader, we update our beacon first if self.overlay.is_member_of_root_committee(self.id): assert(self.random_beacon.verify_happy(block.beacon, block.pk, block.qc.view)) - self.overlay.set_entropy(self.random_beacon.last_beacon.entropy()) + self.overlay = self.overlay.advance(self.random_beacon.last_beacon.entropy()) return Send(to=self.overlay.leader(), payload=vote) # otherwise we send to the parent committee and update the beacon second return_event = Send(to=self.overlay.parent_committee(self.id), payload=vote) assert(self.random_beacon.verify_happy(block.beacon, block.pk, block.qc.view)) - self.overlay.set_entropy(self.random_beacon.last_beacon.entropy()) + self.overlay = self.overlay.advance(self.random_beacon.last_beacon.entropy()) return return_event def receive_timeout_qc(self, timeout_qc: TimeoutQc): @@ -60,12 +59,12 @@ class BeaconizedCarnot(Carnot): return new_beacon = RecoveryMode.generate_beacon(self.random_beacon.last_beacon.entropy(), timeout_qc.view) self.random_beacon.verify_unhappy(new_beacon, timeout_qc.view) - self.overlay.set_entropy(self.random_beacon.last_beacon.entropy()) + self.overlay = self.overlay.advance(self.random_beacon.last_beacon.entropy()) def propose_block(self, view: View, quorum: Quorum) -> Event: - event: Event = super().propose_block(view, quorum) - block = event.payload - beacon = NormalMode.generate_beacon(self.sk, block.qc.view) - block = BeaconizedBlock(view=block.view, qc=block.qc, _id=block._id, beacon=beacon, pk = G1Element.from_bytes(self.pk)) - event.payload = block - return event + event: Event = super().propose_block(view, quorum) + block = event.payload + beacon = NormalMode.generate_beacon(self.sk, block.qc.view) + block = BeaconizedBlock(view=block.view, qc=block.qc, _id=block._id, beacon=beacon, pk = G1Element.from_bytes(self.pk)) + event.payload = block + return event diff --git a/carnot/overlay.py b/carnot/overlay.py index c68b3ea..ae51722 100644 --- a/carnot/overlay.py +++ b/carnot/overlay.py @@ -1,25 +1,34 @@ import random from abc import abstractmethod -from typing import Set, Optional, List +from typing import Set, Optional, List, Self from carnot import Overlay, Id, Committee, View class EntropyOverlay(Overlay): @abstractmethod - def set_entropy(self, entropy: bytes): + def advance(self, entropy: bytes) -> Self: pass class FlatOverlay(EntropyOverlay): - def set_entropy(self, entropy: bytes): + + def __init__(self, current_leader: Id, nodes: List[Id], entropy: bytes): + self.current_leader = current_leader + self.nodes = nodes self.entropy = entropy + def next_leader(self) -> Id: + random.seed(a=self.entropy, version=2) + return random.choice(self.nodes) + + def advance(self, entropy: bytes): + return FlatOverlay(self.next_leader(), self.nodes, entropy) + def is_leader(self, _id: Id): return _id == self.leader() def leader(self) -> Id: - random.seed(a=self.entropy, version=2) - return random.choice(self.nodes) + return self.current_leader def is_member_of_leaf_committee(self, _id: Id) -> bool: return True @@ -48,9 +57,5 @@ class FlatOverlay(EntropyOverlay): def super_majority_threshold(self, _id: Id) -> int: return 0 - def __init__(self, nodes: List[Id]): - self.nodes = nodes - self.entropy = None - diff --git a/carnot/test_beaconized_carnot.py b/carnot/test_beaconized_carnot.py index 268ef83..36ffc57 100644 --- a/carnot/test_beaconized_carnot.py +++ b/carnot/test_beaconized_carnot.py @@ -1,3 +1,4 @@ +import random from typing import Dict, List from unittest import TestCase from itertools import chain @@ -5,21 +6,19 @@ from itertools import chain from blspy import PrivateKey from carnot import Id, Carnot, Block, Overlay, Vote, StandardQc, NewView -from beacon import generate_random_sk, RandomBeacon, NormalMode +from beacon import generate_random_sk, RandomBeacon, NormalMode, RecoveryMode from beaconized_carnot import BeaconizedCarnot, BeaconizedBlock from overlay import FlatOverlay, EntropyOverlay from test_unhappy_path import parents_from_childs def gen_node(sk: PrivateKey, overlay: Overlay, entropy: bytes = b""): - node = BeaconizedCarnot(sk, overlay) + node = BeaconizedCarnot(sk, overlay, entropy) return node.id, node -def succeed(nodes: Dict[Id, BeaconizedCarnot], proposed_block: BeaconizedBlock) -> (List[Vote], EntropyOverlay): - overlay = FlatOverlay(list(nodes.keys())) - overlay.set_entropy(proposed_block.beacon.entropy()) - +def succeed(nodes: Dict[Id, BeaconizedCarnot], proposed_block: BeaconizedBlock, overlay: EntropyOverlay) -> (List[Vote], EntropyOverlay): + overlay = overlay.advance(proposed_block.beacon.entropy()) # broadcast the block for node in nodes.values(): node.receive_block(proposed_block) @@ -47,9 +46,8 @@ def succeed(nodes: Dict[Id, BeaconizedCarnot], proposed_block: BeaconizedBlock) return root_votes, overlay -def fail(nodes: Dict[Id, BeaconizedCarnot], proposed_block: BeaconizedBlock) -> (List[NewView], EntropyOverlay): - overlay = FlatOverlay(list(nodes.keys())) - overlay.set_entropy(proposed_block.beacon.entropy) +def fail(nodes: Dict[Id, BeaconizedCarnot], proposed_block: BeaconizedBlock, overlay: EntropyOverlay) -> (List[NewView], EntropyOverlay): + overlay = overlay.advance(proposed_block.beacon.entropy()) # broadcast the block for node in nodes.values(): node.receive_block(proposed_block) @@ -106,7 +104,7 @@ def add_genesis_block(carnot: BeaconizedCarnot, sk: PrivateKey) -> Block: carnot.receive_block(genesis_block) carnot.local_high_qc = genesis_block.qc carnot.current_view = 1 - carnot.overlay.set_entropy(beacon.entropy()) + carnot.overlay = carnot.overlay.advance(beacon.entropy()) return genesis_block @@ -114,10 +112,14 @@ def initial_setup(test_case: TestCase, size: int) -> (Dict[Id, Carnot], Carnot, keys = [generate_random_sk() for _ in range(size)] nodes_ids = [bytes(key.get_g1()) for key in keys] genesis_sk = generate_random_sk() - nodes = dict(gen_node(key, FlatOverlay(nodes_ids), bytes(genesis_sk.get_g1())) for key in keys) + entropy = RecoveryMode.generate_beacon(bytes(genesis_sk), -1).entropy() + + random.seed(a=entropy, version=2) + current_leader = random.choice(nodes_ids) + + nodes = dict(gen_node(key, FlatOverlay(current_leader, nodes_ids, entropy), entropy) for key in keys) genesis_block = None - overlay = FlatOverlay(nodes_ids) - overlay.set_entropy(NormalMode.generate_beacon(genesis_sk, -1).entropy()) + overlay = FlatOverlay(current_leader, nodes_ids, entropy) leader: Carnot = nodes[overlay.leader()] for node in nodes.values(): genesis_block = add_genesis_block(node, genesis_sk) @@ -135,8 +137,7 @@ def initial_setup(test_case: TestCase, size: int) -> (Dict[Id, Carnot], Carnot, ) proposed_block = leader.propose_block(1, genesis_votes).payload test_case.assertIsNotNone(proposed_block) - overlay = FlatOverlay(nodes_ids) - overlay.set_entropy(genesis_block.beacon.entropy()) + overlay = overlay.advance(genesis_block.beacon.entropy()) return nodes, leader, proposed_block, overlay @@ -151,25 +152,25 @@ class TestBeaconizedCarnot(TestCase): nodes, leader, proposed_block, overlay = initial_setup(self, 5) for view in range(2, 5): - root_votes, overlay = succeed(nodes, proposed_block) + root_votes, overlay = succeed(nodes, proposed_block, overlay) leader = nodes[overlay.leader()] proposed_block = leader.propose_block(view, root_votes).payload - root_votes, overlay = fail(nodes, proposed_block) + root_votes, overlay = fail(nodes, proposed_block, overlay) leader = nodes[overlay.leader()] proposed_block = leader.propose_block(6, root_votes).payload for view in range(7, 8): - root_votes, overlay = succeed(nodes, proposed_block) + root_votes, overlay = succeed(nodes, proposed_block, overlay) leader = nodes[overlay.leader()] proposed_block = leader.propose_block(view, root_votes).payload - root_votes, overlay = fail(nodes, proposed_block) + root_votes, overlay = fail(nodes, proposed_block, overlay) leader = nodes[overlay.leader()] proposed_block = leader.propose_block(9, root_votes).payload for view in range(10, 15): - root_votes, overlay = succeed(nodes, proposed_block) + root_votes, overlay = succeed(nodes, proposed_block, overlay) leader = nodes[overlay.leader()] proposed_block = leader.propose_block(view, root_votes).payload