diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 93794e6..0000000 --- a/.gitignore +++ /dev/null @@ -1,79 +0,0 @@ -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.serpython -python -venv \ No newline at end of file diff --git a/carnot/carnot.py b/carnot/carnot.py index a2a9ef5..45dc308 100644 --- a/carnot/carnot.py +++ b/carnot/carnot.py @@ -158,6 +158,7 @@ class Carnot: def __init__(self, _id: Id): self.id: Id = _id self.current_view: View = 0 + self.highest_voted_view:View=0 self.local_high_qc: Optional[Qc] = None self.latest_committed_view: View = 0 self.safe_blocks: Dict[Id, Block] = dict() @@ -175,7 +176,8 @@ class Carnot: case AggregateQc() as aggregated: if aggregated.high_qc().view < self.latest_committed_view: return False - return block.view >= self.current_view + return block.view >= self.current_view and block.view == aggregated.view+1 # AggregatedQC must be + #formed from previous round. def update_high_qc(self, qc: Qc): match (self.local_high_qc, qc): @@ -190,9 +192,11 @@ class Carnot: def receive_block(self, block: Block): assert block.parent() in self.safe_blocks + # This condition is not needed because it will be true as qc of a block will hve lower view than + #the block. + # if block.qc.view < self.current_view: + # return - if block.qc.view < self.current_view: - return if block.id() in self.safe_blocks or block.view <= self.latest_committed_view: return @@ -200,14 +204,13 @@ class Carnot: 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 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) - + assert (block.view>highest_voted_view) if self.overlay.member_of_root_com(self.id): vote: Vote = Vote( block=block.id(), @@ -224,6 +227,8 @@ class Carnot: qc=None ) self.send(vote, *self.overlay.parent_committee(self.id)) + self.increment_voted_view(block.view) # to avoid voting again for this view. + self.increment_view_qc(block.qc) def forward_vote(self, vote: Vote): assert vote.block in self.safe_blocks @@ -245,6 +250,7 @@ class Carnot: def local_timeout(self, new_overlay: Overlay): self.last_timeout_view = self.current_view + increment_voted_view(self.current_view) # to avoid voting again for this view. self.overlay = new_overlay if self.overlay.member_of_leaf_committee(self.id): raise NotImplementedError() @@ -272,6 +278,8 @@ class Carnot: ) if can_commit: self.committed_blocks[block.id()] = block + def increment_voted_view(self,view: View): + self.highest_voted_view = max(view,self.highest_voted_view) def increment_view_qc(self, qc: Qc) -> bool: if qc.view < self.current_view: diff --git a/carnot/test_happy_path.py b/carnot/test_happy_path.py index 68a3df6..3316444 100644 --- a/carnot/test_happy_path.py +++ b/carnot/test_happy_path.py @@ -16,7 +16,16 @@ class TestCarnotHappyPath(TestCase): block = Block(view=1, qc=StandardQc(block=genesis_block.id(), view=0)) carnot.receive_block(block) - def test_receive_block_has_old_qc(self): + def test_prepare_vote_for_a_block(self,block:Block, carnot: Carnot) -> Vote: + vote: Vote = Vote( + block=block.id(), + voter=self.id, + view=self.current_view, + qc=self.build_qc(votes) + ) + return vote + + def test_receive_multiple_blocks_for_the_same_view(self): carnot = Carnot(int_to_id(0)) genesis_block = self.add_genesis_block(carnot) # 1 @@ -30,4 +39,143 @@ class TestCarnotHappyPath(TestCase): # 3 block3 = Block(view=3, qc=StandardQc(block=block2.id(), view=2)) carnot.receive_block(block3) + # 4 + block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3)) + carnot.receive_block(block4) + + # This test seem to fail because safe_block dict checks for id of + #the block, and we can receive blocks with different ids for the same view. + # There must be only one block per view at most. + # Maybe we have a dict with view as key and dict[block.id()]block as value? + block5 = Block(view=4, qc=StandardQc(block=block3.id(), view=3)) + carnot.receive_block(block5) + + +# This also covers the case when a block has a lower view number than its QC. + def test_receive_block_has_old_view_number(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) + # 4 + block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3)) + carnot.receive_block(block4) + + # 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)) + carnot.receive_block(block5) + +def test_receive_block_has_an_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) + # 4 + block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3)) + carnot.receive_block(block4) + + # 5 This is the old standard qc of block number 3. For standarnd 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_is_safe() should return false. + block5 = Block(view=5, qc=StandardQc(block=block3.id(), view=3)) + carnot.receive_block(block5) + + + + + #Any block with block.view < 4 must be committed + def test_receive_block_and_commit_its_grand_parent_chain(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) + # 4 + block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3)) + carnot.receive_block(block4) + + block5 = Block(view=5, qc=StandardQc(block=block3.id(), view=3)) + carnot.receive_block(block5) + + + + +# 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): + 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) + # 4 + block4 = Block(view=4, qc=StandardQc(block=block3.id(), view=3)) + carnot.receive_block(block4) + + # 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)) + carnot.receive_block(block5) + + + + # Test cases for vote: + #1: If a nodes votes for same block twice + #2: If a node votes for two different blocks in the same view. + #3: If a node in parent committee votes before it receives threshold of children's votes + #4: If a node counts duplicate votes + #6: If a node counts votes of nodes other than it's child committees. + #7: If a node counts distinct votes for a safe block from its child committees. + #8: If #7 is true, will the node vote for the mentioned safe block + + def test_vote_for_received_block(self): + carnot = Carnot(int_to_id(0)) + genesis_block = self.add_genesis_block(carnot) + blocklist=[] + for i in range(1,5): + blocklist.append(Block(view=i, qc=StandardQc(block=i - 1, view=i - 1))) + + + + + +