Skip to content

Commit 6023e0e

Browse files
committed
process_block_confirmations task refactoring
1 parent c0e3edc commit 6023e0e

File tree

2 files changed

+39
-15
lines changed

2 files changed

+39
-15
lines changed

node/blockchain/models/block_confirmation.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from djongo import models
44

5+
from node.blockchain.inner_models import BlockConfirmation as PydanticBlockConfirmation
56
from node.core.models import CustomModel
67

78

@@ -17,5 +18,8 @@ class Meta:
1718
unique_together = ('number', 'signer')
1819
ordering = unique_together
1920

21+
def get_block_confirmation(self) -> PydanticBlockConfirmation:
22+
return PydanticBlockConfirmation.parse_raw(self.body)
23+
2024
def __str__(self):
2125
return f'block_number={self.number}, signer={self.signer}, hash={self.hash}'

node/blockchain/tasks/process_block_confirmations.py

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,88 @@
11
import logging
22
from itertools import groupby
33
from operator import attrgetter
4+
from typing import Optional
45

56
from celery import shared_task
67
from django.db import transaction
78

89
from node.blockchain.constants import BLOCK_LOCK
910
from node.blockchain.facade import BlockchainFacade
11+
from node.blockchain.inner_models import BlockConfirmation as PydanticBlockConfirmation
12+
from node.blockchain.mixins.crypto import HashableStringWrapper
1013
from node.blockchain.models import BlockConfirmation, PendingBlock
14+
from node.blockchain.types import Hash
1115
from node.blockchain.utils.lock import lock
1216
from node.core.exceptions import ValidationError
1317

1418
logger = logging.getLogger(__name__)
1519

1620

17-
@lock(BLOCK_LOCK)
18-
def process_next_block():
19-
facade = BlockchainFacade.get_instance()
20-
next_block_number = facade.get_next_block_number()
21+
def get_consensus_block_hash_with_confirmations(
22+
facade, next_block_number, minimum_consensus
23+
) -> Optional[tuple[Hash, list[PydanticBlockConfirmation]]]:
2124
cv_identifiers = facade.get_confirmation_validator_identifiers()
2225

2326
# Query only confirmations for the next block number and received from confirmation validators
2427
all_confirmations = BlockConfirmation.objects.filter(number=next_block_number, signer__in=cv_identifiers)
2528

2629
# Group confirmations by hash to see which hash wins the consensus
2730
grouped_confirmations = groupby(all_confirmations.order_by('hash'), key=attrgetter('hash'))
28-
minimum_consensus = facade.get_minimum_consensus()
29-
3031
finalizable_hashes = [(hash_, confirmations)
3132
for hash_, confirmations in grouped_confirmations
3233
if len(list(confirmations)) >= minimum_consensus]
3334

3435
if not finalizable_hashes:
35-
return False # No consensus, yet
36+
return None # No consensus, yet
3637

3738
if len(finalizable_hashes) >= 2:
38-
# We should never get here
39-
raise ValueError('More than one finalizable hash found')
39+
raise ValueError('More than one finalizable hash found') # We should never get here
4040

4141
assert len(finalizable_hashes) == 1
42-
hash_, consensus_confirmations = finalizable_hashes[0]
42+
block_hash, consensus_confirmations = finalizable_hashes[0]
43+
return block_hash, [confirmation.get_block_confirmation() for confirmation in consensus_confirmations]
44+
4345

46+
def is_valid_consensus(facade, confirmations, minimum_consensus):
4447
# Validate confirmations, since they may have not been validated on API call because some of them were added
4548
# much earlier then the next block number become equal to confirmation block number
4649
valid_confirmations = []
47-
for confirmation in consensus_confirmations:
50+
for confirmation in confirmations:
4851
try:
49-
confirmation.validate_all()
52+
confirmation.validate_all(facade)
5053
except ValidationError:
5154
logger.warning('Invalid confirmation detected: %s', confirmation)
5255
continue
5356

5457
valid_confirmations.append(confirmation)
5558

56-
if len(valid_confirmations) < minimum_consensus: # Check that we still have consensus after validation
59+
return len(valid_confirmations) >= minimum_consensus
60+
61+
62+
@lock(BLOCK_LOCK)
63+
def process_next_block():
64+
facade = BlockchainFacade.get_instance()
65+
next_block_number = facade.get_next_block_number()
66+
minimum_consensus = facade.get_minimum_consensus()
67+
68+
if not (result := get_consensus_block_hash_with_confirmations(facade, next_block_number, minimum_consensus)):
69+
return False
70+
71+
block_hash, confirmations = result
72+
if not is_valid_consensus(facade, confirmations, minimum_consensus):
5773
return False
5874

59-
pending_block = PendingBlock.objects.get_or_none(number=next_block_number, hash=hash_)
75+
pending_block = PendingBlock.objects.get_or_none(number=next_block_number, hash=block_hash)
6076
if pending_block is None:
6177
# TODO(dmu) CRITICAL: https://thenewboston.atlassian.net/browse/BC-283
6278
raise NotImplementedError('Edge case of processing confirmed missing pending block is not implemented')
6379

80+
block_body = pending_block.body
81+
if HashableStringWrapper(block_body).make_hash() != block_hash:
82+
raise ValidationError('Pending block body hash is not valid') # we should never get here
83+
6484
with transaction.atomic():
65-
facade.add_block_from_json(pending_block.body, expect_locked=True)
85+
facade.add_block_from_json(block_body, expect_locked=True)
6686
# There may be blocks with other hashes therefore we delete all of them
6787
PendingBlock.objects.filter(number__lte=next_block_number).delete()
6888
BlockConfirmation.objects.filter(number__lte=next_block_number).delete()

0 commit comments

Comments
 (0)