mirror of
https://github.com/logos-co/nomos-specs.git
synced 2025-02-12 23:36:29 +00:00
Merge branch 'python-impl-Tests2' into python-impl
This commit is contained in:
commit
72f4b5b795
@ -1,5 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import TypeAlias, List, Set, Self, Optional, Dict
|
||||
from typing import TypeAlias, List, Set, Self, Optional, Dict, FrozenSet
|
||||
from abc import abstractmethod
|
||||
|
||||
Id: TypeAlias = bytes
|
||||
@ -39,6 +39,7 @@ Qc: TypeAlias = StandardQc | AggregateQc
|
||||
class Block:
|
||||
view: View
|
||||
qc: Qc
|
||||
content: FrozenSet[Id]
|
||||
|
||||
def extends(self, ancestor: Self) -> bool:
|
||||
"""
|
||||
@ -51,7 +52,7 @@ class Block:
|
||||
return self.qc.block
|
||||
|
||||
def id(self) -> Id:
|
||||
return int_to_id(hash((self.view, self.qc.view, self.qc.block)))
|
||||
return int_to_id(hash(self.content))
|
||||
|
||||
|
||||
@dataclass(unsafe_hash=True)
|
||||
@ -201,10 +202,6 @@ def download(view) -> Block:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def build_timeout_qc(msgs) -> TimeoutQc:
|
||||
pass
|
||||
|
||||
|
||||
class Carnot:
|
||||
def __init__(self, _id: Id):
|
||||
self.id: Id = _id
|
||||
@ -318,7 +315,7 @@ class Carnot:
|
||||
for child_committee in self.overlay.child_of_root_committee():
|
||||
self.send(timeout_msg, child_committee)
|
||||
|
||||
def timeout(self, msgs: Set["Timeout"]):
|
||||
def timeout(self, msgs: Set[Timeout]):
|
||||
assert len(msgs) == self.overlay.super_majority_threshold(self.id)
|
||||
assert all(msg.view == msgs.pop().view for msg in msgs)
|
||||
assert msgs.pop().view > self.current_view
|
||||
@ -326,12 +323,12 @@ class Carnot:
|
||||
if self.local_high_qc.view < max_msg.high_qc.view:
|
||||
self.update_high_qc(max_msg.high_qc)
|
||||
if self.overlay.member_of_root_committee(self.id) and self.overlay.member_of_leaf_committee(self.id):
|
||||
timeout_qc = build_timeout_qc(msgs)
|
||||
timeout_qc = self.build_timeout_qc(msgs)
|
||||
self.update_timeout_qc(timeout_qc)
|
||||
else:
|
||||
self.update_timeout_qc(msgs.pop().timeout_qc)
|
||||
|
||||
def timeout_qc(self,timeout_qc: TimeoutQc):
|
||||
def build_timeout_qc(self, msgs: Set[Timeout]) -> TimeoutQc:
|
||||
pass
|
||||
|
||||
def send(self, vote: Vote | Timeout, *ids: Id):
|
||||
|
@ -5,7 +5,7 @@ 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))
|
||||
genesis_block = Block(view=0, qc=StandardQc(block=b"", view=0), content=frozenset(b""))
|
||||
carnot.safe_blocks[genesis_block.id()] = genesis_block
|
||||
carnot.committed_blocks[genesis_block.id()] = genesis_block
|
||||
return genesis_block
|
||||
@ -13,33 +13,33 @@ class TestCarnotHappyPath(TestCase):
|
||||
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))
|
||||
block = Block(view=1, qc=StandardQc(block=genesis_block.id(), view=0), content=frozenset(b"1"))
|
||||
carnot.receive_block(block)
|
||||
|
||||
def test_receive_multiple_blocks_for_the_same_view(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))
|
||||
block1 = Block(view=1, qc=StandardQc(block=genesis_block.id(), view=0), content=frozenset(b"1"))
|
||||
carnot.receive_block(block1)
|
||||
|
||||
# 2
|
||||
block2 = Block(view=2, qc=StandardQc(block=block1.id(), view=1))
|
||||
block2 = Block(view=2, qc=StandardQc(block=block1.id(), view=1), content=frozenset(b"2"))
|
||||
carnot.receive_block(block2)
|
||||
|
||||
# 3
|
||||
block3 = Block(view=3, qc=StandardQc(block=block2.id(), view=2))
|
||||
block3 = Block(view=3, qc=StandardQc(block=block2.id(), view=2), content=frozenset(b"3"))
|
||||
carnot.receive_block(block3)
|
||||
# 4
|
||||
block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3))
|
||||
block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3), content=frozenset(b"4"))
|
||||
carnot.receive_block(block4)
|
||||
self.assertEqual(len(carnot.safe_blocks), 5)
|
||||
# next block is duplicated and as it is already processed should be skipped
|
||||
|
||||
# In my opinion duplicated block is rejected because both blocks have the same ID.
|
||||
# In reality the IDs of blocks for the same view can be different if we compute ID based on
|
||||
# the hash of the block content. What do you think?
|
||||
block5 = Block(view=4, qc=StandardQc(block=block3.id(), view=3))
|
||||
block5 = Block(view=4, qc=StandardQc(block=block3.id(), view=3), content=frozenset(b"4"))
|
||||
carnot.receive_block(block5)
|
||||
self.assertEqual(len(carnot.safe_blocks), 5)
|
||||
# next block has a different view but is duplicated and as it is already processed should be skipped
|
||||
block5 = Block(view=5, qc=StandardQc(block=block3.id(), view=4), content=frozenset(b"4"))
|
||||
carnot.receive_block(block5)
|
||||
self.assertEqual(len(carnot.safe_blocks), 5)
|
||||
|
||||
@ -47,25 +47,26 @@ class TestCarnotHappyPath(TestCase):
|
||||
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))
|
||||
block1 = Block(view=1, qc=StandardQc(block=genesis_block.id(), view=0), content=frozenset(b"1"))
|
||||
carnot.receive_block(block1)
|
||||
|
||||
# 2
|
||||
block2 = Block(view=2, qc=StandardQc(block=block1.id(), view=1))
|
||||
block2 = Block(view=2, qc=StandardQc(block=block1.id(), view=1), content=frozenset(b"2"))
|
||||
carnot.receive_block(block2)
|
||||
|
||||
# 3
|
||||
block3 = Block(view=3, qc=StandardQc(block=block2.id(), view=2))
|
||||
block3 = Block(view=3, qc=StandardQc(block=block2.id(), view=2), content=frozenset(b"3"))
|
||||
carnot.receive_block(block3)
|
||||
# 4
|
||||
block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3))
|
||||
block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3), content=frozenset(b"4"))
|
||||
carnot.receive_block(block4)
|
||||
|
||||
self.assertEqual(len(carnot.safe_blocks), 5)
|
||||
# This block should be rejected based on the condition below in block_is_safe().
|
||||
# This block should be rejected based on the condition below in block_is_safe().
|
||||
# block.view >= self.latest_committed_view and block.view == (standard.view + 1)
|
||||
# block_is_safe() should return false.
|
||||
block5 = Block(view=3, qc=StandardQc(block=block4.id(), view=4))
|
||||
block5 = Block(view=3, qc=StandardQc(block=block4.id(), view=4), content=frozenset(b"5"))
|
||||
self.assertFalse(carnot.block_is_safe(block5))
|
||||
carnot.receive_block(block5)
|
||||
self.assertEqual(len(carnot.safe_blocks), 5)
|
||||
|
||||
@ -73,79 +74,85 @@ class TestCarnotHappyPath(TestCase):
|
||||
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))
|
||||
block1 = Block(view=1, qc=StandardQc(block=genesis_block.id(), view=0), content=frozenset(b"1"))
|
||||
carnot.receive_block(block1)
|
||||
|
||||
# 2
|
||||
block2 = Block(view=2, qc=StandardQc(block=block1.id(), view=1))
|
||||
block2 = Block(view=2, qc=StandardQc(block=block1.id(), view=1), content=frozenset(b"2"))
|
||||
carnot.receive_block(block2)
|
||||
|
||||
# 3
|
||||
block3 = Block(view=3, qc=StandardQc(block=block2.id(), view=2))
|
||||
block3 = Block(view=3, qc=StandardQc(block=block2.id(), view=2), content=frozenset(b"3"))
|
||||
carnot.receive_block(block3)
|
||||
# 4
|
||||
block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3))
|
||||
block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3), content=frozenset(b"4"))
|
||||
carnot.receive_block(block4)
|
||||
|
||||
self.assertEqual(len(carnot.safe_blocks), 5)
|
||||
# 5 This is the old standard qc of block number 3. For standarnd QC we must always have qc.view==block.view-1.
|
||||
# 5 This is the old standard qc of block number 3. For standard QC we must always have qc.view==block.view-1.
|
||||
# This block should be rejected based on the condition below in block_is_safe().
|
||||
# block.view >= self.latest_committed_view and block.view == (standard.view + 1)
|
||||
# block.view >= self.latest_committed_view and block.view == (standard.view + 1)
|
||||
# block_is_safe() should return false.
|
||||
block5 = Block(view=5, qc=StandardQc(block=block3.id(), view=3))
|
||||
block5 = Block(view=5, qc=StandardQc(block=block3.id(), view=3), content=frozenset(b"5"))
|
||||
self.assertFalse(carnot.block_is_safe(block5))
|
||||
carnot.receive_block(block5)
|
||||
self.assertEqual(len(carnot.safe_blocks), 5)
|
||||
|
||||
# Any block with block.view < 4 must be committed
|
||||
def test_receive_block_and_commit_its_grand_parent_chain(self):
|
||||
"""
|
||||
Any block with block.view < 4 must be committed
|
||||
"""
|
||||
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))
|
||||
block1 = Block(view=1, qc=StandardQc(block=genesis_block.id(), view=0), content=frozenset(b"1"))
|
||||
carnot.receive_block(block1)
|
||||
|
||||
# 2
|
||||
block2 = Block(view=2, qc=StandardQc(block=block1.id(), view=1))
|
||||
block2 = Block(view=2, qc=StandardQc(block=block1.id(), view=1), content=frozenset(b"2"))
|
||||
carnot.receive_block(block2)
|
||||
|
||||
# 3
|
||||
block3 = Block(view=3, qc=StandardQc(block=block2.id(), view=2))
|
||||
block3 = Block(view=3, qc=StandardQc(block=block2.id(), view=2), content=frozenset(b"3"))
|
||||
carnot.receive_block(block3)
|
||||
# 4
|
||||
block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3))
|
||||
block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3), content=frozenset(b"4"))
|
||||
carnot.receive_block(block4)
|
||||
|
||||
block5 = Block(view=5, qc=StandardQc(block=block4.id(), view=4))
|
||||
block5 = Block(view=5, qc=StandardQc(block=block4.id(), view=4), content=frozenset(b"5"))
|
||||
carnot.receive_block(block5)
|
||||
|
||||
for block in (block1, block2, block3):
|
||||
self.assertIn(block.id(), carnot.committed_blocks)
|
||||
|
||||
# Block3 must be committed as it is the grandparent of block5. Hence, it should not be possible
|
||||
# to avert it.
|
||||
def test_receive_block_has_an_old_qc_and_tries_to_revert_a_committed_block(self):
|
||||
"""
|
||||
Block3 must be committed as it is the grandparent of block5. Hence, it should not be possible
|
||||
to avert it.
|
||||
"""
|
||||
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))
|
||||
block1 = Block(view=1, qc=StandardQc(block=genesis_block.id(), view=0), content=frozenset(b"1"))
|
||||
carnot.receive_block(block1)
|
||||
|
||||
# 2
|
||||
block2 = Block(view=2, qc=StandardQc(block=block1.id(), view=1))
|
||||
block2 = Block(view=2, qc=StandardQc(block=block1.id(), view=1), content=frozenset(b"2"))
|
||||
carnot.receive_block(block2)
|
||||
|
||||
# 3
|
||||
block3 = Block(view=3, qc=StandardQc(block=block2.id(), view=2))
|
||||
block3 = Block(view=3, qc=StandardQc(block=block2.id(), view=2), content=frozenset(b"3"))
|
||||
carnot.receive_block(block3)
|
||||
# 4
|
||||
block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3))
|
||||
block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3), content=frozenset(b"4"))
|
||||
carnot.receive_block(block4)
|
||||
|
||||
self.assertEqual(len(carnot.safe_blocks), 5)
|
||||
# 5 This is the old standard qc of block number 2. By using the QC for block2, block5 tries to form a fork
|
||||
# to avert block3 and block b4. Block3 is a committed block
|
||||
# block_is_safe() should return false.
|
||||
block5 = Block(view=5, qc=StandardQc(block=block2.id(), view=2))
|
||||
block5 = Block(view=5, qc=StandardQc(block=block2.id(), view=2), content=frozenset(b"5"))
|
||||
self.assertFalse(carnot.block_is_safe(block5))
|
||||
carnot.receive_block(block5)
|
||||
self.assertEqual(len(carnot.safe_blocks), 5)
|
||||
|
||||
@ -153,22 +160,22 @@ class TestCarnotHappyPath(TestCase):
|
||||
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))
|
||||
block1 = Block(view=1, qc=StandardQc(block=genesis_block.id(), view=0), content=frozenset(b"1"))
|
||||
carnot.receive_block(block1)
|
||||
|
||||
# 2
|
||||
block2 = Block(view=2, qc=StandardQc(block=block1.id(), view=1))
|
||||
block2 = Block(view=2, qc=StandardQc(block=block1.id(), view=1), content=frozenset(b"2"))
|
||||
carnot.receive_block(block2)
|
||||
|
||||
# 3
|
||||
block3 = Block(view=3, qc=StandardQc(block=block2.id(), view=2))
|
||||
block3 = Block(view=3, qc=StandardQc(block=block2.id(), view=2), content=frozenset(b"3"))
|
||||
carnot.receive_block(block3)
|
||||
# 4
|
||||
block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3))
|
||||
block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3), content=frozenset(b"4"))
|
||||
carnot.receive_block(block4)
|
||||
|
||||
self.assertEqual(len(carnot.safe_blocks), 5)
|
||||
block5 = Block(view=5, qc=StandardQc(block=block4.id(), view=4))
|
||||
block5 = Block(view=5, qc=StandardQc(block=block4.id(), view=4), content=frozenset(b"5"))
|
||||
carnot.receive_block(block5)
|
||||
self.assertEqual(carnot.latest_committed_view, 3)
|
||||
self.assertEqual(carnot.local_high_qc.view, 4)
|
||||
@ -196,7 +203,7 @@ class TestCarnotHappyPath(TestCase):
|
||||
carnot.overlay = MockOverlay()
|
||||
genesis_block = self.add_genesis_block(carnot)
|
||||
# 1
|
||||
block1 = Block(view=1, qc=StandardQc(block=genesis_block.id(), view=0))
|
||||
block1 = Block(view=1, qc=StandardQc(block=genesis_block.id(), view=0), content=frozenset(b"1"))
|
||||
carnot.receive_block(block1)
|
||||
votes = set(
|
||||
Vote(
|
||||
@ -233,19 +240,22 @@ class TestCarnotHappyPath(TestCase):
|
||||
carnot.overlay = MockOverlay()
|
||||
genesis_block = self.add_genesis_block(carnot)
|
||||
# 1
|
||||
block1 = Block(view=1, qc=StandardQc(block=genesis_block.id(), view=0))
|
||||
block1 = Block(view=1, qc=StandardQc(block=genesis_block.id(), view=0), content=frozenset(b"1"))
|
||||
carnot.receive_block(block1)
|
||||
|
||||
votes = set(
|
||||
Vote(
|
||||
voter=int_to_id(i),
|
||||
view=1,
|
||||
block=block1.id(),
|
||||
qc=StandardQc(block=block1.id(), view=1)
|
||||
) for i in range(10)
|
||||
) for i in range(3)
|
||||
)
|
||||
carnot.vote(block1, votes)
|
||||
|
||||
with self.assertRaises((AssertionError, )):
|
||||
carnot.vote(block1, votes)
|
||||
|
||||
# The test passes as asserting fails in len(votes) == self.overlay.super_majority_threshold(self.id)
|
||||
# when number of votes are < 9
|
||||
self.assertEqual(carnot.highest_voted_view, 1)
|
||||
self.assertEqual(carnot.current_view, 1)
|
||||
self.assertEqual(carnot.highest_voted_view, 0)
|
||||
self.assertEqual(carnot.current_view, 0)
|
||||
|
Loading…
x
Reference in New Issue
Block a user