Exemple #1
0
    def create(self, validated_data):
        """
        If validator is more trusted than the current primary validator:

        - switch to the new primary validator

        If validator is less trusted than the current primary validator:

        - delete the requesting validator (which is now on different network)
        """
        validator = validated_data['node_identifier']
        self_configuration = get_self_configuration(
            exception_class=RuntimeError)
        current_primary_validator = self_configuration.primary_validator

        if current_primary_validator == validator:
            return True

        if current_primary_validator.trust < validator.trust:
            self_configuration.primary_validator = validator
            self_configuration.save()
            send_primary_validator_updated_notices.delay()
            send_primary_validator_updated_notification()
            return True

        validator.delete()
        raise serializers.ValidationError('Networks out of sync')
Exemple #2
0
    def create(self, validated_data):
        """
        Create block and bank transactions
        Forward block to validator
        """

        validated_block = validated_data
        self_configuration = get_self_configuration(exception_class=RuntimeError)
        primary_validator = self_configuration.primary_validator

        try:
            with transaction.atomic():
                block = create_block_and_bank_transactions(validated_block)
                Account.objects.get_or_create(
                    account_number=validated_block['account_number'],
                    defaults={'trust': 0},
                )
                send_signed_block.delay(
                    block=validated_block,
                    ip_address=primary_validator.ip_address,
                    port=primary_validator.port,
                    protocol=primary_validator.protocol,
                    url_path='/bank_blocks'
                )
        except Exception as e:
            logger.exception(e)
            raise serializers.ValidationError(e)

        return block
Exemple #3
0
    def create(self, validated_data):
        """
        Handle banks primary validator updated notice

        A response of True indicates to the requesting bank that self (this validator) will remain on the same network
        Delete banks switching to different networks
        """
        bank = validated_data['node_identifier']
        ip_address = validated_data['ip_address']
        port = validated_data['port']
        protocol = validated_data['protocol']

        self_configuration = get_self_configuration(
            exception_class=RuntimeError)

        if self.primary_validator_synchronized(
                ip_address=ip_address, self_configuration=self_configuration):
            return True

        if (self_configuration.node_type == CONFIRMATION_VALIDATOR
                and bank == get_most_trusted_bank()):
            address = format_address(ip_address=ip_address,
                                     port=port,
                                     protocol=protocol)
            try:
                config = fetch(url=f'{address}/config', headers={})
            except Exception as e:
                capture_exception(e)
                logger.exception(e)
            else:
                sync_with_primary_validator.delay(config=config)
                return True

        bank.delete()
        raise serializers.ValidationError('Networks out of sync')
Exemple #4
0
    def validate(self, data):
        """
        Verify that correct payment exist for both Bank and Validator
        """

        data = super(BlockSerializerCreate, self).validate(data)

        account_number = data['account_number']
        message = data['message']
        txs = message['txs']

        self_configuration = get_self_configuration(exception_class=RuntimeError)
        primary_validator = get_primary_validator()

        if account_number != self_configuration.account_number:
            validate_transaction_exists(
                amount=self_configuration.default_transaction_fee,
                error=serializers.ValidationError,
                recipient=self_configuration.account_number,
                txs=txs
            )

        if account_number != primary_validator.account_number:
            validate_transaction_exists(
                amount=primary_validator.default_transaction_fee,
                error=serializers.ValidationError,
                recipient=primary_validator.account_number,
                txs=txs
            )

        return data
Exemple #5
0
    def create(self, validated_data):
        """
        Create block and bank transactions

        Forward block to validator
        """
        validated_block = validated_data
        self_configuration = get_self_configuration(
            exception_class=RuntimeError)
        primary_validator = self_configuration.primary_validator

        try:
            with transaction.atomic():
                block, created = create_block_and_related_objects(
                    validated_block)
                send_signed_block.delay(
                    block=validated_block,
                    ip_address=primary_validator.ip_address,
                    port=primary_validator.port,
                    protocol=primary_validator.protocol,
                    url_path='/bank_blocks')
        except serializers.ValidationError as e:
            logger.exception(e)
            raise e
        except Exception as e:
            logger.exception(e)
            raise serializers.ValidationError(e)

        return block
Exemple #6
0
def send_primary_validator_updated_notices():
    """
    Send a notice to all validators that the banks primary validator has been updated

    - 200 response > validator is syncing to new primary validator
    - 400 response > validator is not syncing to new primary validator (can be deleted)
    """
    self_configuration = get_self_configuration(exception_class=RuntimeError)
    primary_validator = self_configuration.primary_validator
    confirmation_validators = Validator.objects.all().exclude(node_identifier=primary_validator.node_identifier)

    data = {
        'ip_address': primary_validator.ip_address,
        'port': primary_validator.port,
        'protocol': primary_validator.protocol
    }

    for confirmation_validator in confirmation_validators:
        signed_request = generate_signed_request(
            data=data,
            nid_signing_key=get_signing_key()
        )
        node_address = format_address(
            ip_address=confirmation_validator.ip_address,
            port=confirmation_validator.port,
            protocol=confirmation_validator.protocol,
        )
        url = f'{node_address}/primary_validator_updated'

        try:
            post(url=url, body=signed_request)
        except Exception as e:
            confirmation_validator.delete()
            logger.exception(e)
Exemple #7
0
    def inner(request, *args, **kwargs):
        message = request.data.get('message')
        node_identifier = request.data.get('node_identifier')
        signature = request.data.get('signature')

        self_configuration = get_self_configuration(
            exception_class=RuntimeError)

        if node_identifier != self_configuration.node_identifier:
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        try:
            verify_signature(message=sort_and_encode(message),
                             signature=signature,
                             verify_key=node_identifier)
        except BadSignatureError as e:
            logger.exception(e)
            return Response({ERROR: BAD_SIGNATURE},
                            status=status.HTTP_401_UNAUTHORIZED)
        except Exception as e:
            logger.exception(e)
            return Response({ERROR: UNKNOWN},
                            status=status.HTTP_401_UNAUTHORIZED)

        return func(request, *args, **kwargs)
Exemple #8
0
    def validate_message(message):
        """
        Check that Txs exist
        Verify that correct payment exist for both Bank and Validator
        """

        self_configuration = get_self_configuration(exception_class=RuntimeError)
        primary_validator = get_primary_validator()

        bank_default_transaction_fee = self_configuration.default_transaction_fee
        validator_transaction_fee = primary_validator.default_transaction_fee

        txs = message['txs']

        if not txs:
            raise serializers.ValidationError('Invalid Txs')

        validate_transaction_exists(
            amount=bank_default_transaction_fee,
            error=serializers.ValidationError,
            recipient=self_configuration.account_number,
            txs=txs
        )
        validate_transaction_exists(
            amount=validator_transaction_fee,
            error=serializers.ValidationError,
            recipient=primary_validator.account_number,
            txs=txs
        )

        return txs
Exemple #9
0
def get_initial_block_identifier():
    """
    Return initial block identifier (seed_block_identifier or root_account_file_hash)
    """

    self_configuration = get_self_configuration(exception_class=RuntimeError)
    return self_configuration.seed_block_identifier or self_configuration.root_account_file_hash
Exemple #10
0
def start_crawl():
    """
    Start a network crawl
    """

    self_configuration = get_self_configuration(exception_class=RuntimeError)
    self_node_identifier = self_configuration.node_identifier
    primary_validator = self_configuration.primary_validator

    primary_validator_address = format_address(
        ip_address=primary_validator.ip_address,
        port=primary_validator.port,
        protocol=primary_validator.protocol)

    crawl_banks(primary_validator_address=primary_validator_address,
                self_node_identifier=self_node_identifier)
    crawl_validators(primary_validator_address=primary_validator_address)

    send_connection_requests(node_class=Bank,
                             self_configuration=self_configuration)
    send_connection_requests(node_class=Validator,
                             self_configuration=self_configuration)

    cache.set(CRAWL_LAST_COMPLETED, str(timezone.now()), None)
    cache.set(CRAWL_STATUS, CRAWL_STATUS_NOT_CRAWLING, None)

    send_crawl_status_notification()
Exemple #11
0
def send_confirmation_block_history_request():
    """
    Request missing blocks from the primary validator
    """

    self_configuration = get_self_configuration(exception_class=RuntimeError)
    primary_validator = self_configuration.primary_validator

    address = format_address(
        ip_address=primary_validator.ip_address,
        port=primary_validator.port,
        protocol=primary_validator.protocol
    )
    url = f'{address}/confirmation_block_history'

    signed_request = generate_signed_request(
        data={
            'block_identifier': cache.get(HEAD_BLOCK_HASH)
        },
        nid_signing_key=get_signing_key()
    )

    try:
        post(url=url, body=signed_request)
    except Exception as e:
        capture_exception(e)
        logger.exception(e)
Exemple #12
0
def sync_from_primary_validators_initial_block(*, primary_validator):
    """
    Sync from the primary validators initial block (seed_block_identifier or root_account_file_hash)

    Invoked when self (confirmation validator):
    - is first being initialized
    - has a blockchain that is out of sync with the PV
    """

    try:
        self_configuration = get_self_configuration(exception_class=RuntimeError)
        download_root_account_file(url=primary_validator.root_account_file)

        self_configuration.root_account_file = get_root_account_file_url()
        self_configuration.root_account_file_hash = get_file_hash(settings.ROOT_ACCOUNT_FILE_PATH)
        self_configuration.seed_block_identifier = primary_validator.seed_block_identifier
        self_configuration.save()

        sync_accounts_table_to_root_account_file()
    except Exception as e:
        logger.exception(e)
        raise RuntimeError(e)

    rebuild_cache(head_block_hash=get_initial_block_identifier())
    send_confirmation_block_history_request()
Exemple #13
0
def sync_from_primary_validators_initial_block(*, primary_validator):
    """
    Sync from the primary validators initial block (seed_block_identifier or root_account_file_hash)

    Invoked when self (confirmation validator):
    - is first being initialized
    - has a blockchain that is out of sync with the PV
    """

    try:
        download_root_account_file(
            url=primary_validator.root_account_file,
            destination_file_path=settings.LOCAL_ROOT_ACCOUNT_FILE_PATH)
        file_hash = get_file_hash(settings.LOCAL_ROOT_ACCOUNT_FILE_PATH)

        # TODO: root_account_file should not be a copy of PVs URL but rather a unique path
        # TODO: this way, every validator maintains their own copy
        self_configuration = get_self_configuration(
            exception_class=RuntimeError)
        self_configuration.root_account_file = primary_validator.root_account_file
        self_configuration.root_account_file_hash = file_hash
        self_configuration.seed_block_identifier = primary_validator.seed_block_identifier
        self_configuration.save()

        sync_accounts_table_to_root_account_file()
    except Exception as e:
        logger.exception(e)
        raise RuntimeError(e)

    rebuild_cache(head_block_hash=get_initial_block_identifier())
    send_confirmation_block_history_request()
def connect_to_primary_validator(*, primary_validator):
    """
    Connect to a validator

    - used in the syncing process
    """
    self_configuration = get_self_configuration(exception_class=RuntimeError)

    primary_validator_address = format_address(
        ip_address=primary_validator.ip_address,
        port=primary_validator.port,
        protocol=primary_validator.protocol,
    )

    if is_connected_to_primary_validator(
            primary_validator_address=primary_validator_address,
            self_configuration=self_configuration):
        return

    signed_request = generate_signed_request(data={
        'ip_address':
        self_configuration.ip_address,
        'port':
        self_configuration.port,
        'protocol':
        self_configuration.protocol
    },
                                             nid_signing_key=get_signing_key())
    url = f'{primary_validator_address}/connection_requests'

    try:
        post(url=url, body=signed_request)
    except Exception as e:
        logger.exception(e)
        raise e
    def get_initial_block_identifier(self, primary_validator_config):
        """
        Return initial block identifier

        If seed_block_identifier, fetch related (seed) block and hash to get initial block identifier
        Otherwise, return root_account_file_hash
        """

        seed_block_identifier = primary_validator_config.get(
            'seed_block_identifier')

        if not seed_block_identifier:
            self_configuration = get_self_configuration(
                exception_class=RuntimeError)
            root_account_file_hash = self_configuration.root_account_file_hash
            pv_root_account_file_hash = primary_validator_config.get(
                'root_account_file_hash')

            if root_account_file_hash != pv_root_account_file_hash:
                self._error(
                    'SelfConfiguration.root_account_file_hash does not match primary validator root_account_file_hash'
                )
                self._error(f'SelfConfiguration: {root_account_file_hash}')
                self._error(f'Primary validator: {pv_root_account_file_hash}')
                raise RuntimeError()

            return root_account_file_hash

        address = self.get_primary_validator_address()
        confirmation_block = get_confirmation_block(
            address=address, block_identifier=seed_block_identifier)

        return get_message_hash(message=confirmation_block['message'])
def send_invalid_block_to_banks(*, confirmation_block):
    """
    Send invalid block to banks
    This function is called by the confirmation validators only
    """

    block = confirmation_block['block']
    block_identifier = confirmation_block['block_identifier']

    self_configuration = get_self_configuration(exception_class=RuntimeError)
    primary_validator_node_identifier = self_configuration.primary_validator.node_identifier
    self_configuration.primary_validator = None
    self_configuration.save()

    invalid_block = generate_signed_request(data={
        'block':
        block,
        'block_identifier':
        block_identifier,
        'primary_validator_node_identifier':
        primary_validator_node_identifier
    },
                                            nid_signing_key=get_signing_key())

    for bank in get_banks_with_active_confirmation_services():
        address = format_address(ip_address=bank.ip_address,
                                 port=bank.port,
                                 protocol=bank.protocol)
        url = f'{address}/invalid_blocks'

        try:
            post(url=url, body=invalid_block)
        except Exception as e:
            logger.exception(e)
Exemple #17
0
    def queued(request, pk):
        self_configuration = get_self_configuration(exception_class=RuntimeError)

        if self_configuration.node_type != CONFIRMATION_VALIDATOR:
            return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)

        return ConfirmationBlockViewSet.get_block(pk, get_queued_confirmation_block)
    def handle(self, *args, **options):
        """
        Run script
        """

        valid_environments = ['local', 'postgres_local']

        if ENVIRONMENT not in valid_environments:
            raise RuntimeError(
                f'DJANGO_APPLICATION_ENVIRONMENT must be in {valid_environments}'
            )

        ip = options['ip']
        validate_ipv46_address(ip)

        self.install_fixture_data()

        self_configuration = get_self_configuration(
            exception_class=RuntimeError)
        SelfConfiguration.objects.filter(pk=self_configuration.id).update(
            ip_address=ip)
        rebuild_cache(
            head_block_hash=self_configuration.root_account_file_hash)

        self.stdout.write(
            self.style.SUCCESS('Validator initialization complete'))
    def validate(self, requesting_node_primary_validator_configuration):
        """
        Validate that requesting nodes primary validator matches self primary validator
        """

        self_configuration = get_self_configuration(exception_class=RuntimeError)

        if self_configuration.node_type == PRIMARY_VALIDATOR:
            self_primary_validator_configuration = SelfConfigurationSerializer(self_configuration).data
        else:
            primary_validator = self_configuration.primary_validator
            self_primary_validator_configuration = ValidatorSerializer(primary_validator).data

        for key in ['account_number', 'ip_address', 'node_identifier', 'protocol']:
            requesting_node_value = requesting_node_primary_validator_configuration.get(key)
            self_primary_validator_value = self_primary_validator_configuration.get(key)

            if requesting_node_value is None:
                raise serializers.ValidationError(f'{key} not found on requesting nodes primary validator')

            if str(requesting_node_value) != str(self_primary_validator_value):
                raise serializers.ValidationError(
                    f'Inconsistent primary validator settings for {key}. '
                    f'Requesting nodes value of {requesting_node_value} '
                    f'does not match expected value of {self_primary_validator_value}.'
                )

        return requesting_node_primary_validator_configuration
Exemple #20
0
def create_confirmation_service(*, bank, confirmation_service_amount):
    """Create confirmation service for bank"""
    current_confirmation_expiration = bank.confirmation_expiration
    now = timezone.now()

    if not current_confirmation_expiration:
        start = now
    else:
        start = max([current_confirmation_expiration, now])

    self_configuration = get_self_configuration(exception_class=RuntimeError)
    daily_confirmation_rate = self_configuration.daily_confirmation_rate

    confirmation_service_amount = int(confirmation_service_amount)
    days_purchased = confirmation_service_amount / daily_confirmation_rate
    seconds_purchased = days_purchased * 86400
    seconds_purchased = int(seconds_purchased)
    end = start + relativedelta(seconds=seconds_purchased)

    BankConfirmationService.objects.create(bank=bank, end=end, start=start)
    bank.confirmation_expiration = end
    bank.save()

    send_signed_post_request.delay(
        data={
            'end': str(end),
            'start': str(start)
        },
        ip_address=bank.ip_address,
        port=bank.port,
        protocol=bank.protocol,
        url_path='/validator_confirmation_services'
    )

    return seconds_purchased
Exemple #21
0
def process_confirmation_block_queue():
    """
    Process confirmation block queue

    - this is for confirmation validators only

    Ran after:
    - initial sync with primary validator
    - receiving confirmation block from the primary validator
    """
    self_configuration = get_self_configuration(exception_class=RuntimeError)
    head_block_hash = cache.get(HEAD_BLOCK_HASH)
    confirmation_block = get_queued_confirmation_block(block_identifier=head_block_hash)

    while confirmation_block:
        block = confirmation_block['block']
        is_valid, sender_account_balance = is_block_valid(block=block)

        if not is_valid:
            send_invalid_block_to_banks(confirmation_block=confirmation_block)
            return

        existing_accounts, new_accounts = get_updated_accounts(
            sender_account_balance=sender_account_balance,
            validated_block=block
        )

        if not updated_balances_match(
            confirmation_block['updated_balances'],
            format_updated_balances(existing_accounts, new_accounts)
        ):
            send_invalid_block_to_banks(confirmation_block=confirmation_block)
            return

        update_accounts_cache(
            existing_accounts=existing_accounts,
            new_accounts=new_accounts
        )
        update_accounts_table(
            existing_accounts=existing_accounts,
            new_accounts=new_accounts
        )
        confirmation_block, head_block_hash = sign_block_to_confirm_and_update_head_block_hash(
            block=block,
            existing_accounts=existing_accounts,
            new_accounts=new_accounts
        )

        delete_queued_confirmation_block(block_identifier=confirmation_block['message']['block_identifier'])
        add_valid_confirmation_block(confirmation_block=confirmation_block)

        if self_configuration.daily_confirmation_rate:
            handle_bank_confirmation_services.delay(
                block=block,
                self_account_number=self_configuration.account_number
            )

        send_confirmation_block_to_banks(confirmation_block=confirmation_block)
        confirmation_block = get_queued_confirmation_block(block_identifier=head_block_hash)
def confirmation_validator_configuration(monkeypatch):
    load_validator_fixtures(CONFIRMATION_VALIDATOR_FIXTURES_DIR)
    monkeypatch.setenv('NETWORK_SIGNING_KEY', '7a3359729b41f953d52818e787a312c8576e179e2ee50a2e4f28c4596b12dce0')

    self_configuration = get_self_configuration(exception_class=RuntimeError)
    rebuild_cache(head_block_hash=self_configuration.root_account_file_hash)

    yield self_configuration
def set_primary_validator(*, validator):
    """Set validator as primary validator"""
    self_configuration = get_self_configuration(exception_class=RuntimeError)
    self_configuration.primary_validator = validator
    self_configuration.save()

    connect_to_primary_validator(primary_validator=validator)
    sync_blockchains(primary_validator=validator)
    def validate(self, data):
        """Validate self is configured as primary validator"""
        self_configuration = get_self_configuration(
            exception_class=RuntimeError)

        if self_configuration.node_type != PRIMARY_VALIDATOR:
            raise serializers.ValidationError(
                'Node is not configured as a primary validator')

        return data
Exemple #25
0
def request_new_primary_validator():
    """
    Request a new primary validator
    Called if/when the existing primary validator goes offline
    """

    self_configuration = get_self_configuration(exception_class=RuntimeError)
    primary_validator = self_configuration.primary_validator
    primary_validator.trust = 0
    primary_validator.save()
    set_primary_validator.delay()
def get_primary_validator():
    """Return primary validator"""
    # TODO: This should be hitting the cache

    self_configuration = get_self_configuration(exception_class=RuntimeError)
    primary_validator = self_configuration.primary_validator

    if not primary_validator:
        raise RuntimeError('No primary validator')

    return primary_validator
Exemple #27
0
def set_primary_validator(*, validator):
    """Set validator as primary validator"""
    self_configuration = get_self_configuration(exception_class=RuntimeError)
    self_configuration.primary_validator = validator
    self_configuration.save()

    if is_self_known_to_node(node=validator,
                             self_configuration=self_configuration):
        return

    send_connection_request(node=validator,
                            self_configuration=self_configuration)
Exemple #28
0
    def inner(obj, request, *args, **kwargs):
        request, error = verify_request_signature(request=request, signed_data_key='message')

        if error:
            return Response(error, status=status.HTTP_401_UNAUTHORIZED)

        node_identifier = request.data['node_identifier']
        self_configuration = get_self_configuration(exception_class=RuntimeError)

        if node_identifier != self_configuration.node_identifier:
            return Response(status=status.HTTP_401_UNAUTHORIZED)

        return func(obj, request, *args, **kwargs)
Exemple #29
0
    def create(request):
        self_configuration = get_self_configuration(exception_class=RuntimeError)

        if self_configuration.node_type != CONFIRMATION_VALIDATOR:
            return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)

        serializer = ConfirmationBlockSerializerCreate(data=request.data['message'])

        serializer.is_valid(raise_exception=True)
        serializer.save()
        process_confirmation_block_queue.delay()

        return Response({}, status=status.HTTP_201_CREATED)
def process_confirmation_block_queue():
    """
    Process confirmation block queue
    - this is for confirmation validators only

    Ran after:
    - initial sync with primary validator
    - receiving confirmation block from the primary validator
    """

    self_configuration = get_self_configuration(exception_class=RuntimeError)
    queue = cache.get(CONFIRMATION_BLOCK_QUEUE)
    head_block_hash = cache.get(HEAD_BLOCK_HASH)
    confirmation_block = queue.pop(head_block_hash, None)

    while confirmation_block:
        block = confirmation_block['block']
        is_valid, sender_account_balance = is_block_valid(block=block)

        if not is_valid:
            send_invalid_block_to_banks(confirmation_block=confirmation_block)
            return

        existing_accounts, new_accounts = get_updated_accounts(
            sender_account_balance=sender_account_balance,
            validated_block=block)

        if not updated_balances_match(
                confirmation_block['updated_balances'],
                format_updated_balances(existing_accounts, new_accounts)):
            send_invalid_block_to_banks(confirmation_block=confirmation_block)
            return

        update_accounts_cache(existing_accounts=existing_accounts,
                              new_accounts=new_accounts)
        update_accounts_table(existing_accounts=existing_accounts,
                              new_accounts=new_accounts)
        confirmation_block = sign_block_to_confirm(
            block=block,
            existing_accounts=existing_accounts,
            new_accounts=new_accounts)

        # TODO: Run as task
        handle_bank_confirmation_services(
            block=block, self_account_number=self_configuration.account_number)
        send_confirmation_block_to_banks(confirmation_block=confirmation_block)

        head_block_hash = cache.get(HEAD_BLOCK_HASH)
        confirmation_block = queue.pop(head_block_hash, None)

    cache.set(CONFIRMATION_BLOCK_QUEUE, queue, None)