Ejemplo n.º 1
0
    def validate_account_root_files(self, is_partial_allowed: bool = True):
        account_root_files_iter = self.yield_blockchain_states(
        )  # type: ignore
        with validates('number of account root files (at least one)'):
            try:
                first_account_root_file = next(account_root_files_iter)
            except StopIteration:
                raise ValidationError(
                    'Blockchain must contain at least one account root file')

        is_initial = first_account_root_file.is_initial()
        if not is_partial_allowed and not is_initial:
            raise ValidationError(
                'Blockchain must start with initial account root file')

        is_first = True
        for counter, account_root_file in enumerate(
                chain((first_account_root_file, ), account_root_files_iter)):
            with validates(f'account root file number {counter}'):
                self.validate_account_root_file(
                    account_root_file=account_root_file,
                    is_initial=is_initial,
                    is_first=is_first)

            is_initial = False  # only first iteration can be with initial
            is_first = False
Ejemplo n.º 2
0
    def validate(self):
        with validates('recipient'):
            if not self.recipient:
                raise ValidationError(
                    f'{self.humanized_class_name} recipient is not set')

        with validates('amount'):
            amount = self.amount
            if not isinstance(amount, int):
                raise ValidationError(
                    f'{self.humanized_class_name} amount must be an integer')

            if amount < 1:
                raise ValidationError(
                    f'{self.humanized_class_name} amount must be greater or equal to 1'
                )

        with validates('fee'):
            if self.fee not in (True, False, None):
                raise ValidationError(
                    f'{self.humanized_class_name} fee value is invalid')

        with validates('memo'):
            max_len = settings.MEMO_MAX_LENGTH
            if self.memo and len(self.memo) > max_len:
                raise ValidationError(
                    f'{self.humanized_class_name} memo must be less than {max_len} characters'
                )
Ejemplo n.º 3
0
 def validate(self, blockchain):
     with validates(f'block number {self.message.block_number} (identifier: {self.message.block_identifier})'):
         validate_not_empty(f'{self.humanized_class_name} message', self.message)
         self.message.validate(blockchain)
         validate_exact_value(f'{self.humanized_class_name} hash', self.hash, self.message.get_hash())
         with validates('block signature'):
             self.validate_signature()
Ejemplo n.º 4
0
 def validate(self, blockchain):
     with validates(
             f'block number {self.message.block_number} (identifier: {self.message.block_identifier})'
     ):
         self.validate_message(blockchain)
         self.validate_message_hash()
         with validates('block signature'):
             self.validate_signature()
Ejemplo n.º 5
0
    def validate_account_root_file(self,
                                   *,
                                   account_root_file,
                                   is_initial=False,
                                   is_first=False):
        account_root_file.validate(is_initial=is_initial)
        if is_initial:
            return

        if is_first:
            logger.debug(
                'First account root file is not a subject of further validations'
            )
            return

        self.validate_account_root_file_balances(
            account_root_file=account_root_file)

        first_block = self.get_first_block()  # type: ignore
        if not first_block:
            return

        if first_block.message.block_number > account_root_file.last_block_number:
            logger.debug('First block is after the account root file')
            if first_block.message.block_number > account_root_file.last_block_number + 1:
                logger.warning('Unnecessary old account root file detected')

            return

        # If account root file is after first known block then we can validate its attributes
        account_root_file_last_block = self.get_block_by_number(
            account_root_file.last_block_number)  # type: ignore
        with validates('account root file last_block_number'):
            if account_root_file_last_block is None:
                raise ValidationError(
                    'Account root file last_block_number points to non-existing block'
                )

        with validates('account root file last_block_identifier'):
            if account_root_file_last_block.message.block_identifier != account_root_file.last_block_identifier:
                raise ValidationError(
                    'Account root file last_block_number does not match last_block_identifier'
                )

        with validates('account root file next_block_identifier'):
            if account_root_file_last_block.message_hash != account_root_file.next_block_identifier:
                raise ValidationError(
                    'Account root file next_block_identifier does not match last_block_number message hash'
                )
Ejemplo n.º 6
0
 def validate_accounts(self):
     for account, balance in self.account_states.items():
         with validates(f'account root file account {account}'):
             if not isinstance(account, str):
                 raise ValidationError(
                     'Account root file account number must be a string')
             balance.validate()
def validate_type(subject, value, type_):
    with validates(f'{subject} type'):
        if not isinstance(value, type_):
            raise ValidationError(
                upper_first(
                    f'{subject} must be {HUMANIZED_TYPE_NAMES.get(type_, type_.__name__)}'
                ))
Ejemplo n.º 8
0
    def validate_blocks(self, *, offset: int = 0, limit: Optional[int] = None):
        """
        Validate blocks persisted in the blockchain. Some blockchain level validations may overlap with
        block level validations. We consider it OK since it is better to double check something rather
        than miss something. We may reconsider this overlap in favor of validation performance.
        """
        assert offset >= 0

        blocks_iter = cast(Iterable[Block], self.yield_blocks())  # type: ignore
        if offset > 0 or limit is not None:
            # TODO(dmu) HIGH: Consider performance improvements when slicing
            if limit is None:
                blocks_iter = islice(blocks_iter, offset)
            else:
                blocks_iter = islice(blocks_iter, offset, offset + limit)

        try:
            first_block = next(blocks_iter)  # type: ignore
        except StopIteration:
            return

        first_account_root_file = self.get_first_blockchain_state()  # type: ignore
        if first_account_root_file is None:
            raise ValidationError('Account root file prior to first block is not found')

        first_block_number = first_block.message.block_number
        if offset == 0:
            with validates('basing on an account root file'):

                if first_account_root_file.get_next_block_number() != first_block_number:
                    raise ValidationError('First block number does not match base account root file last block number')

                if first_account_root_file.get_next_block_identifier() != first_block.message.block_identifier:
                    raise ValidationError(
                        'First block identifier does not match base account root file last block identifier'
                    )

            expected_block_identifier = first_account_root_file.get_next_block_identifier()
        else:
            prev_block = self.get_block_by_number(first_block_number - 1)  # type: ignore
            if prev_block is None:
                raise ValidationError(f'Previous block for block number {first_block_number} is not found')

            assert prev_block.hash
            expected_block_identifier = prev_block.hash

        expected_block_number = first_account_root_file.get_next_block_number() + offset
        for block in chain((first_block,), blocks_iter):
            block.validate(self)

            assert block.message

            self.validate_block(
                block=block,
                expected_block_number=expected_block_number,
                expected_block_identifier=expected_block_identifier
            )
            expected_block_number += 1
            expected_block_identifier = block.hash
Ejemplo n.º 9
0
    def validate_transactions(self):
        txs = self.txs
        validate_type(f'{self.humanized_class_name} txs', txs, list)
        validate_not_empty(f'{self.humanized_class_name} txs', txs)

        for tx in self.txs:
            with validates(f'Validating transaction {tx} on {self.get_humanized_class_name(False)} level'):
                validate_type(f'{self.humanized_class_name} txs', tx, CoinTransferTransaction)
                tx.validate()
Ejemplo n.º 10
0
    def validate_account_root_file_balances(self, *, account_root_file):
        generated_account_root_file = self.generate_blockchain_state(
            account_root_file.last_block_number)  # type: ignore
        with validates('number of account root file balances'):
            expected_accounts_count = len(
                generated_account_root_file.account_states)
            actual_accounts_count = len(account_root_file.account_states)
            if expected_accounts_count != actual_accounts_count:
                raise ValidationError(
                    f'Expected {expected_accounts_count} accounts, '
                    f'but got {actual_accounts_count} in the account root file'
                )

        actual_accounts = account_root_file.account_states
        for account_number, account_state in generated_account_root_file.account_states.items(
        ):
            with validates(f'account {account_number} existence'):
                actual_account_state = actual_accounts.get(account_number)
                if actual_account_state is None:
                    raise ValidationError(
                        f'Could not find {account_number} account in the account root file'
                    )

            with validates(f'account {account_number} balance value'):
                expect_balance = account_state.balance
                actual_state = actual_account_state.balance
                if actual_state != expect_balance:
                    raise ValidationError(
                        f'Expected {expect_balance} balance value, '
                        f'but got {actual_state} balance value for account {account_number}'
                    )

            with validates(f'account {account_number} balance lock'):
                expect_lock = account_state.balance_lock
                actual_lock = actual_account_state.balance_lock
                if actual_lock != expect_lock:
                    raise ValidationError(
                        f'Expected {expect_lock} balance lock, but got {actual_lock} balance '
                        f'lock for account {account_number}')
Ejemplo n.º 11
0
    def validate(self):
        amount = self.amount

        validate_not_empty(f'{self.humanized_class_name} recipient',
                           self.recipient)
        validate_type(f'{self.humanized_class_name} amount', amount, int)
        validate_gte_value(f'{self.humanized_class_name} amount', amount, 1)
        validate_in(f'{self.humanized_class_name} is_fee', self.is_fee,
                    (True, False, None))

        with validates('memo'):
            max_len = settings.MEMO_MAX_LENGTH
            if self.memo and len(self.memo) > max_len:
                raise ValidationError(
                    f'{self.humanized_class_name} memo must be less than {max_len} characters'
                )
Ejemplo n.º 12
0
    def validate_transactions(self):
        txs = self.txs
        if not isinstance(txs, list):
            raise ValidationError(
                f'{self.humanized_class_name} txs must be a list')

        if not txs:
            raise ValidationError(
                f'{self.humanized_class_name} txs must contain at least one transaction'
            )

        for tx in self.txs:
            with validates(
                    f'Validating transaction {tx} on {self.get_humanized_class_name(False)} level'
            ):
                if not isinstance(tx, CoinTransferTransaction):
                    raise ValidationError(
                        f'{self.humanized_class_name} txs must contain only Transactions types'
                    )
                tx.validate()
Ejemplo n.º 13
0
    def validate_updated_account_states(self, blockchain):
        updated_account_states = self.updated_account_states

        humanized_class_name = self.humanized_class_name_lowered
        validate_not_empty(f'{humanized_class_name} updated_account_states',
                           updated_account_states)

        from .signed_change_request import CoinTransferSignedChangeRequest
        if isinstance(self.signed_change_request,
                      CoinTransferSignedChangeRequest):
            validate_min_item_count(
                f'{humanized_class_name} updated_account_states',
                updated_account_states, 2)

            signer = self.signed_change_request.signer
            sender_account_state = self.updated_account_states.get(signer)
            validate_not_empty(
                f'{humanized_class_name} updated_account_states.{signer}',
                sender_account_state)

            for account_number, account_state in updated_account_states.items(
            ):
                with validates(
                        f'{humanized_class_name} account {account_number} updated state'
                ):
                    account_subject = f'{humanized_class_name} updated_account_states key (account number)'
                    validate_not_empty(account_subject, account_number)
                    validate_type(account_subject, account_number, str)

                    account_state.validate()
                    is_sender = account_number == signer
                    self.validate_updated_account_balance_lock(
                        account_number=account_number,
                        account_state=account_state,
                        is_sender=is_sender)
                    self.validate_updated_account_balance(
                        account_number=account_number,
                        account_state=account_state,
                        blockchain=blockchain,
                        is_sender=is_sender)
def validate_min_item_count(subject, value, min_):
    with validates(f'{subject} item count'):
        if len(value) < min_:
            raise ValidationError(
                upper_first(f'{subject} must contain at least {min_} items'))
Ejemplo n.º 15
0
def validate_hexadecimal(subject, value):
    with validates(f'{subject} value'):
        try:
            hexstr(value).to_bytes()
        except ValueError:
            raise ValidationError(upper_first(f'{subject} must be hexadecimal string'))
Ejemplo n.º 16
0
def validate_network_address(subject, value):
    with validates(f'{subject} value'):
        result = urlparse(value)
        validate_not_empty(f'{subject} scheme', result.scheme)
        validate_in(f'{subject} scheme', result.scheme, VALID_SCHEMES)
        validate_not_empty(f'{subject} hostname', result.hostname)
def validate_greater_than_zero(subject, value):
    with validates(f'{subject} value'):
        if value <= 0:
            raise ValidationError(
                upper_first(f'{subject} must be greater than zero'))
def validate_exact_value(subject, value, expected_value):
    with validates(f'{subject} value'):
        if value != expected_value:
            raise ValidationError(
                upper_first(f'{subject} must be equal to {expected_value}'))
def validate_lt_value(subject, value, max_):
    with validates(f'{subject} value'):
        if value >= max_:
            raise ValidationError(
                upper_first(f'{subject} must be less than {max_}'))
def validate_in(subject, value, value_set):
    with validates(f'{subject} value'):
        if value not in value_set:
            value_set_str = ', '.join(map(str, value_set))
            raise ValidationError(
                upper_first(f'{subject} must be one of {value_set_str}'))
def validate_lte_value(subject, value, max_):
    with validates(f'{subject} value'):
        if value > max_:
            raise ValidationError(
                upper_first(f'{subject} must be less or equal to {max_}'))
Ejemplo n.º 22
0
 def validate(self, blockchain, block_number: int):
     self.validate_message()
     with validates('block signature'):
         self.validate_signature()
Ejemplo n.º 23
0
 def validate(self):
     self.validate_message()
     with validates('block signature'):
         self.validate_signature()
def validate_empty(subject, value):
    with validates(f'{subject} value'):
        if value:
            raise ValidationError(upper_first(f'{subject} must be empty'))
def validate_is_none(subject, value):
    with validates(f'{subject} value'):
        if value is not None:
            raise ValidationError(upper_first(f'{subject} must not be set'))
Ejemplo n.º 26
0
 def validate_accounts(self):
     for account, balance in self.account_states.items():
         with validates(f'blockchain state account {account}'):
             validate_type(f'{self.humanized_class_name} account', account,
                           str)
             balance.validate()
def validate_gt_value(subject, value, min_):
    with validates(f'{subject} value'):
        if value <= min_:
            raise ValidationError(
                upper_first(f'{subject} must be greater than {min_}'))