|
| 1 | +import logging |
1 | 2 | from itertools import groupby
|
2 | 3 | from operator import attrgetter
|
3 | 4 |
|
|
8 | 9 | from node.blockchain.facade import BlockchainFacade
|
9 | 10 | from node.blockchain.models import BlockConfirmation, PendingBlock
|
10 | 11 | from node.blockchain.utils.lock import lock
|
| 12 | +from node.core.exceptions import ValidationError |
| 13 | + |
| 14 | +logger = logging.getLogger(__name__) |
11 | 15 |
|
12 | 16 |
|
13 | 17 | @lock(BLOCK_LOCK)
|
14 | 18 | def process_next_block():
|
15 | 19 | facade = BlockchainFacade.get_instance()
|
16 | 20 | next_block_number = facade.get_next_block_number()
|
17 | 21 | cv_identifiers = facade.get_confirmation_validator_identifiers()
|
18 |
| - confirmations = BlockConfirmation.objects.filter(number=next_block_number, signer__in=cv_identifiers) |
19 |
| - grouped_confirmations = groupby(confirmations.order_by('hash'), key=attrgetter('hash')) |
| 22 | + |
| 23 | + # Query only confirmations for the next block number and received from confirmation validators |
| 24 | + all_confirmations = BlockConfirmation.objects.filter(number=next_block_number, signer__in=cv_identifiers) |
| 25 | + |
| 26 | + # Group confirmations by hash to see which hash wins the consensus |
| 27 | + grouped_confirmations = groupby(all_confirmations.order_by('hash'), key=attrgetter('hash')) |
20 | 28 | minimum_consensus = facade.get_minimum_consensus()
|
21 | 29 |
|
22 |
| - finalizable_hashes = [ |
23 |
| - hash_ for hash_, _confirmations in grouped_confirmations if len(list(_confirmations)) >= minimum_consensus |
24 |
| - ] |
| 30 | + finalizable_hashes = [(hash_, confirmations) |
| 31 | + for hash_, confirmations in grouped_confirmations |
| 32 | + if len(list(confirmations)) >= minimum_consensus] |
25 | 33 |
|
26 | 34 | if not finalizable_hashes:
|
27 |
| - return False |
| 35 | + return False # No consensus, yet |
28 | 36 |
|
29 | 37 | if len(finalizable_hashes) >= 2:
|
30 | 38 | # We should never get here
|
31 | 39 | raise ValueError('More than one finalizable hash found')
|
32 | 40 |
|
33 | 41 | assert len(finalizable_hashes) == 1
|
34 |
| - hash_ = finalizable_hashes[0] |
| 42 | + hash_, consensus_confirmations = finalizable_hashes[0] |
| 43 | + |
| 44 | + # Validate confirmations, since they may have not been validated on API call because some of them were added |
| 45 | + # much earlier then the next block number become equal to confirmation block number |
| 46 | + valid_confirmations = [] |
| 47 | + for confirmation in consensus_confirmations: |
| 48 | + try: |
| 49 | + confirmation.validate_all() |
| 50 | + except ValidationError: |
| 51 | + logger.warning('Invalid confirmation detected: %s', confirmation) |
| 52 | + continue |
| 53 | + |
| 54 | + valid_confirmations.append(confirmation) |
| 55 | + |
| 56 | + if len(valid_confirmations) < minimum_consensus: # Check that we still have consensus after validation |
| 57 | + return False |
35 | 58 |
|
36 | 59 | pending_block = PendingBlock.objects.get_or_none(number=next_block_number, hash=hash_)
|
37 | 60 | if pending_block is None:
|
|
0 commit comments