From 289014ae9b829019b57ec49d19d1069e30075835 Mon Sep 17 00:00:00 2001 From: Dmitry Mugtasimov Date: Mon, 21 Mar 2022 01:16:20 +0300 Subject: [PATCH] Unittest is_valid_consensus() --- .../inner_models/block_confirmation.py | 5 ++++ node/blockchain/models/block_confirmation.py | 24 +++++++++++++++++++ .../tasks/process_block_confirmations.py | 5 ++-- .../test_process_block_confirmations.py | 24 +++++++++++++++++-- node/blockchain/views/block_confirmation.py | 9 +------ 5 files changed, 55 insertions(+), 12 deletions(-) diff --git a/node/blockchain/inner_models/block_confirmation.py b/node/blockchain/inner_models/block_confirmation.py index d0f4a12c..f2ccdd97 100644 --- a/node/blockchain/inner_models/block_confirmation.py +++ b/node/blockchain/inner_models/block_confirmation.py @@ -45,8 +45,13 @@ def validate_signer(self, blockchain_facade): if not blockchain_facade.is_confirmation_validator(self.signer): raise ValidationError('Invalid block signer') + def validate_number(self, blockchain_facade): + if blockchain_facade.get_next_block_number() != self.get_number(): + raise ValidationError('Invalid block number') + def validate_business_logic(self): pass def validate_blockchain_state_dependent(self, blockchain_facade): self.validate_signer(blockchain_facade) + self.validate_number(blockchain_facade) diff --git a/node/blockchain/models/block_confirmation.py b/node/blockchain/models/block_confirmation.py index bcfca440..5e7fcd54 100644 --- a/node/blockchain/models/block_confirmation.py +++ b/node/blockchain/models/block_confirmation.py @@ -3,9 +3,31 @@ from djongo import models from node.blockchain.inner_models import BlockConfirmation as PydanticBlockConfirmation +from node.core.managers import CustomManager from node.core.models import CustomModel +class BlockConfirmationManager(CustomManager): + + def create_from_block_confirmation(self, block_confirmation: PydanticBlockConfirmation): + return self.create( + number=block_confirmation.get_number(), + signer=block_confirmation.signer, + hash=block_confirmation.get_hash(), + body=block_confirmation.json(), + ) + + def update_or_create_from_block_confirmation(self, block_confirmation: PydanticBlockConfirmation): + return self.update_or_create( + number=block_confirmation.get_number(), + signer=block_confirmation.signer, + defaults={ + 'hash': block_confirmation.get_hash(), + 'body': block_confirmation.json(), + }, + ) + + class BlockConfirmation(CustomModel): _id = models.UUIDField(primary_key=True, default=uuid.uuid4) # noqa: A003 @@ -14,6 +36,8 @@ class BlockConfirmation(CustomModel): signer = models.CharField(max_length=64) # noqa: A003 body = models.BinaryField() + objects = BlockConfirmationManager() + class Meta: unique_together = ('number', 'signer') ordering = unique_together diff --git a/node/blockchain/tasks/process_block_confirmations.py b/node/blockchain/tasks/process_block_confirmations.py index 2c16262c..ab44ab38 100644 --- a/node/blockchain/tasks/process_block_confirmations.py +++ b/node/blockchain/tasks/process_block_confirmations.py @@ -18,8 +18,7 @@ def get_next_block_confirmations(next_block_number) -> list[BlockConfirmation]: - facade = BlockchainFacade.get_instance() - cv_identifiers = facade.get_confirmation_validator_identifiers() + cv_identifiers = BlockchainFacade.get_instance().get_confirmation_validator_identifiers() return list(BlockConfirmation.objects.filter(number=next_block_number, signer__in=cv_identifiers)) @@ -51,6 +50,8 @@ def get_consensus_block_hash_with_confirmations( def is_valid_consensus(confirmations: list[BlockConfirmation], minimum_consensus: int): # Validate confirmations, since they may have not been validated on API call because some of them were added # much earlier then the next block number become equal to confirmation block number + assert len(set(confirmation.number for confirmation in confirmations)) <= 1 + assert len(set(confirmation.hash for confirmation in confirmations)) <= 1 assert len(set(confirmation.signer for confirmation in confirmations)) == len(confirmations) facade = BlockchainFacade.get_instance() diff --git a/node/blockchain/tests/test_tasks/test_process_block_confirmations.py b/node/blockchain/tests/test_tasks/test_process_block_confirmations.py index 4c038d3b..6c394d99 100644 --- a/node/blockchain/tests/test_tasks/test_process_block_confirmations.py +++ b/node/blockchain/tests/test_tasks/test_process_block_confirmations.py @@ -3,9 +3,11 @@ import pytest from model_bakery import baker -from node.blockchain.models import Node +from node.blockchain.facade import BlockchainFacade +from node.blockchain.inner_models import BlockConfirmation as PydanticBlockConfirmation +from node.blockchain.models import BlockConfirmation, Node from node.blockchain.tasks.process_block_confirmations import ( - get_consensus_block_hash_with_confirmations, get_next_block_confirmations + get_consensus_block_hash_with_confirmations, get_next_block_confirmations, is_valid_consensus ) from node.blockchain.types import NodeRole @@ -43,3 +45,21 @@ def test_get_consensus_block_hash_with_confirmations(): result = get_consensus_block_hash_with_confirmations(confirmations, 3) assert result is None + + +@pytest.mark.parametrize('delta, is_valid', ((0, True), (1, False))) +@pytest.mark.django_db +def test_is_valid_consensus(delta, is_valid): + block_number = BlockchainFacade.get_instance().get_next_block_number() + bc0 = BlockConfirmation.objects.create_from_block_confirmation( + PydanticBlockConfirmation.create(number=block_number + delta, hash_='a' * 64, signing_key='0' * 64) + ) + bc1 = BlockConfirmation.objects.create_from_block_confirmation( + PydanticBlockConfirmation.create(number=block_number + delta, hash_='a' * 64, signing_key='1' * 64) + ) + bc2 = BlockConfirmation.objects.create_from_block_confirmation( + PydanticBlockConfirmation.create(number=block_number + delta, hash_='a' * 64, signing_key='2' * 64) + ) + + confirmations = [bc0, bc1, bc2] + assert is_valid_consensus(confirmations, 2) == is_valid diff --git a/node/blockchain/views/block_confirmation.py b/node/blockchain/views/block_confirmation.py index a3299301..b3dae97d 100644 --- a/node/blockchain/views/block_confirmation.py +++ b/node/blockchain/views/block_confirmation.py @@ -25,14 +25,7 @@ def create(self, request, *args, **kwargs): else: block_confirmation.validate_business_logic() - ORMBlockConfirmation.objects.update_or_create( - number=block_confirmation.get_number(), - signer=block_confirmation.signer, - defaults={ - 'hash': block_confirmation.get_hash(), - 'body': block_confirmation.json(), # TODO(dmu) LOW: Pick request body AS IS instead - }, - ) + ORMBlockConfirmation.objects.update_or_create_from_block_confirmation(block_confirmation) if is_next_block_number and ( ORMBlockConfirmation.objects.filter(number=next_block_number).count() >= facade.get_minimum_consensus()