mirror of
https://github.com/logos-co/nomos-specs.git
synced 2025-02-08 21:43:48 +00:00
Make easy tests
This commit is contained in:
parent
53d7efbb67
commit
5dedab1d2f
@ -1,5 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import TypeAlias, List, Set, Self, Optional
|
||||
from typing import TypeAlias, List, Set, Self, Optional, Dict
|
||||
from abc import abstractmethod
|
||||
|
||||
Id: TypeAlias = bytes
|
||||
@ -7,6 +7,10 @@ View: TypeAlias = int
|
||||
Committee: TypeAlias = Set[Id]
|
||||
|
||||
|
||||
def int_to_id(i: int) -> Id:
|
||||
return bytes(str(i), encoding="utf8")
|
||||
|
||||
|
||||
@dataclass
|
||||
class StandardQc:
|
||||
block: Id
|
||||
@ -46,7 +50,7 @@ class Block:
|
||||
return self.qc.block
|
||||
|
||||
def id(self) -> Id:
|
||||
return int.to_bytes(hash(self), length=32, byteorder="little")
|
||||
return int_to_id(hash((self.view, self.qc.view, self.qc.block)))
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -123,7 +127,7 @@ class Overlay:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def parent_committee(self, _id: Id) -> Option[Committee]:
|
||||
def parent_committee(self, _id: Id) -> Optional[Committee]:
|
||||
"""
|
||||
:param _id:
|
||||
:return: Some(parent committee) of the participant with Id _id withing the committee tree overlay
|
||||
@ -131,42 +135,45 @@ class Overlay:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def super_majority_threshold(self, _id: Id) -> int:
|
||||
"""
|
||||
Amount of distinct number of messages for a node with Id _id member of a committee
|
||||
The return value may change depending on which committee the node is member of, including the leader
|
||||
:return:
|
||||
"""
|
||||
if self.is_leader(_id):
|
||||
pass
|
||||
elif self.member_of_root_committee(_id):
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
def download(view) -> Block:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def supermajority(votes: Set[Vote]) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def leader_supermajorty(votes: Set[Vote]) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def more_than_supermajority(votes: Set[Vote]) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Carnot:
|
||||
def __init__(self, _id: Id):
|
||||
self.id: Id = _id
|
||||
self.current_view: View = 0
|
||||
self.local_high_qc: Optional[Qc] = None
|
||||
self.latest_committed_view: View = 0
|
||||
self.safe_blocks: Set[Id] = set()
|
||||
self.safe_blocks: Dict[Id, Block] = dict()
|
||||
self.last_timeout_view_qc: Optional[TimeoutQc] = None
|
||||
self.last_timeout_view: Optional[View] = None
|
||||
self.overlay: Overlay = Overlay() # TODO: integrate overlay
|
||||
self.committed_blocks: Dict[Id, Block] = dict()
|
||||
|
||||
def block_is_safe(self, block: Block) -> bool:
|
||||
match block.qc:
|
||||
case StandardQc() as standard:
|
||||
if standard.view <=self.latest_committed_view:
|
||||
if standard.view < self.latest_committed_view:
|
||||
return False
|
||||
return block.view >= self.latest_committed_view and block.view == (standard.view + 1)
|
||||
case AggregateQc() as aggregated:
|
||||
if aggregated.high_qc().view <= self.latest_committed_view:
|
||||
if aggregated.high_qc().view < self.latest_committed_view:
|
||||
return False
|
||||
return block.view >= self.current_view
|
||||
|
||||
@ -183,15 +190,21 @@ class Carnot:
|
||||
|
||||
def receive_block(self, block: Block):
|
||||
assert block.parent() in self.safe_blocks
|
||||
assert block.id() in self.safe_blocks or block.view <= self.latest_committed_view
|
||||
|
||||
if block.qc.view < self.current_view:
|
||||
return
|
||||
if block.id() in self.safe_blocks or block.view <= self.latest_committed_view:
|
||||
return
|
||||
|
||||
if self.block_is_safe(block):
|
||||
self.safe_blocks.add(block.id())
|
||||
self.safe_blocks[block.id()] = block
|
||||
self.update_high_qc(block.qc)
|
||||
self.try_commit_grand_parent(block)
|
||||
self.increment_view_qc(block.qc)
|
||||
|
||||
def vote(self, block: Block, votes: Set[Vote]):
|
||||
assert block.id() in self.safe_blocks
|
||||
assert supermajority(votes)
|
||||
assert len(votes) == self.overlay.super_majority_threshold(self.id)
|
||||
assert all(self.overlay.child_committee(self.id, vote.voter) for vote in votes)
|
||||
assert all(vote.block == block.id() for vote in votes)
|
||||
|
||||
@ -224,7 +237,7 @@ class Carnot:
|
||||
|
||||
def propose_block(self, view: View, quorum: Quorum):
|
||||
assert self.overlay.is_leader(self.id)
|
||||
assert leader_supermajorty(quorum)
|
||||
assert len(quorum) == self.overlay.super_majority_threshold(self.id)
|
||||
|
||||
qc = self.build_qc(quorum)
|
||||
block = Block(view=view, qc=qc)
|
||||
@ -245,6 +258,28 @@ class Carnot:
|
||||
def broadcast(self, block):
|
||||
pass
|
||||
|
||||
def try_commit_grand_parent(self, block: Block):
|
||||
parent = self.safe_blocks.get(block.parent())
|
||||
grand_parent = self.safe_blocks.get(parent.parent())
|
||||
# this case should just trigger on genesis_case,
|
||||
# as the preconditions on outer calls should check on block validity
|
||||
if not parent or not grand_parent:
|
||||
return
|
||||
can_commit = (
|
||||
parent.view == (grand_parent.view + 1) and
|
||||
isinstance(block.qc, (StandardQc, )) and
|
||||
isinstance(parent.qc, (StandardQc, ))
|
||||
)
|
||||
if can_commit:
|
||||
self.committed_blocks[block.id()] = block
|
||||
|
||||
def increment_view_qc(self, qc: Qc) -> bool:
|
||||
if qc.view < self.current_view:
|
||||
return False
|
||||
self.last_timeout_view_qc = None
|
||||
self.current_view = qc.view + 1
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
33
carnot/test_happy_path.py
Normal file
33
carnot/test_happy_path.py
Normal file
@ -0,0 +1,33 @@
|
||||
from .carnot import *
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestCarnotHappyPath(TestCase):
|
||||
@staticmethod
|
||||
def add_genesis_block(carnot: Carnot) -> Block:
|
||||
genesis_block = Block(view=0, qc=StandardQc(block=b"", view=0))
|
||||
carnot.safe_blocks[genesis_block.id()] = genesis_block
|
||||
carnot.committed_blocks[genesis_block.id()] = genesis_block
|
||||
return genesis_block
|
||||
|
||||
def test_receive_block(self):
|
||||
carnot = Carnot(int_to_id(0))
|
||||
genesis_block = self.add_genesis_block(carnot)
|
||||
block = Block(view=1, qc=StandardQc(block=genesis_block.id(), view=0))
|
||||
carnot.receive_block(block)
|
||||
|
||||
def test_receive_block_has_old_qc(self):
|
||||
carnot = Carnot(int_to_id(0))
|
||||
genesis_block = self.add_genesis_block(carnot)
|
||||
# 1
|
||||
block1 = Block(view=1, qc=StandardQc(block=genesis_block.id(), view=0))
|
||||
carnot.receive_block(block1)
|
||||
|
||||
# 2
|
||||
block2 = Block(view=2, qc=StandardQc(block=block1.id(), view=1))
|
||||
carnot.receive_block(block2)
|
||||
|
||||
# 3
|
||||
block3 = Block(view=3, qc=StandardQc(block=block2.id(), view=2))
|
||||
carnot.receive_block(block3)
|
||||
|
Loading…
x
Reference in New Issue
Block a user