Python impl tests (#9)
* Make easy tests * Made sure an old aggregatedQC is not used. * Test when a block has an old qc * adding highest voted view so that a node doesn't vote twice. * adding highest voted view so that a node doesn't vote twice. * Tests for voting * Tests for voting * Tests for voting * Tests for voting * Update test_happy_path.py --------- Co-authored-by: mjalalzai <33738574+MForensic@users.noreply.github.com>
This commit is contained in:
parent
5dedab1d2f
commit
44253376bc
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue