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)
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)
def sign_block_to_confirm_and_update_head_block_hash(*, block, existing_accounts, new_accounts): """ Sign block to confirm validity Update HEAD_BLOCK_HASH """ try: head_block_hash = cache.get(HEAD_BLOCK_HASH) message = { 'block': block, 'block_identifier': head_block_hash, 'updated_balances': format_updated_balances(existing_accounts, new_accounts) } confirmation_block = generate_signed_request( data=message, nid_signing_key=get_signing_key() ) message_hash = get_message_hash(message=message) cache.set(HEAD_BLOCK_HASH, message_hash, None) return confirmation_block, message_hash except Exception as e: capture_exception(e) logger.exception(e)
def test_invalid_account_number(client, account, self_configuration): client.patch_json( reverse('account-detail', args=[account.account_number + 'abcdef']), generate_signed_request(data={'trust': 100}, nid_signing_key=get_signing_key()), expected=HTTP_404_NOT_FOUND, )
def send_connection_request(*, node, self_configuration): """ Send connection request to node """ node_address = format_address( ip_address=node.ip_address, port=node.port, protocol=node.protocol, ) 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'{node_address}/connection_requests' try: post(url=url, body=signed_request) except Exception as e: logger.exception(e) raise e
def send_signed_block(*, block, ip_address, port, protocol, url_path): """ Sign block and send to recipient """ signing_key = get_signing_key() node_identifier = get_verify_key(signing_key=signing_key) node_identifier = encode_verify_key(verify_key=node_identifier) message = sort_and_encode(block) signed_block = { 'block': block, 'node_identifier': node_identifier, 'signature': generate_signature(message=message, signing_key=signing_key) } node_address = format_address(ip_address=ip_address, port=port, protocol=protocol) url = f'{node_address}{url_path}' try: post(url=url, body=signed_block) except Exception as e: request_new_primary_validator() logger.exception(e)
def crawl_request(client, command, status): return client.post_json( reverse('crawl-list'), generate_signed_request(data={'crawl': command}, nid_signing_key=get_signing_key()), expected=status, )
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)
async def test_crawl_status_async(client, confirmation_validator_configuration, celery_worker): communicator = WebsocketCommunicator( CrawlStatusConsumer, 'ws/crawl_status' ) connected, subprotocol = await communicator.connect() assert connected await sync_to_async( client.post_json )( reverse('crawl-list'), generate_signed_request( data={ 'crawl': CRAWL_COMMAND_START }, nid_signing_key=get_signing_key() ), expected=HTTP_200_OK ) async_response = await communicator.receive_json_from(timeout=3) await communicator.disconnect() crawl_status = async_response['payload'] assert async_response['notification_type'] == CRAWL_STATUS_NOTIFICATION assert crawl_status['crawl_last_completed'] assert crawl_status['crawl_status'] == CRAWL_STATUS_NOT_CRAWLING
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 test_bank_account_number_trust(client, account, self_configuration, trust, response_msg): response = client.patch_json( reverse('account-detail', args=[account.account_number]), generate_signed_request(data={'trust': trust}, nid_signing_key=get_signing_key()), expected=HTTP_400_BAD_REQUEST, ) assert response['trust'] == [response_msg]
def test_banks_post(client, bank_fake_data, self_configuration): response = client.post_json(reverse('bank-list'), generate_signed_request( data=bank_fake_data, nid_signing_key=get_signing_key(), ), expected=status.HTTP_201_CREATED) bank_fake_data['trust'] = f'{bank_fake_data["trust"]:.2f}' assert response == bank_fake_data
def test_accounts_patch(client, account, account_fake_data, self_configuration): response = client.patch_json( reverse('account-detail', args=[account.account_number]), generate_signed_request(data=account_fake_data, nid_signing_key=get_signing_key()), expected=HTTP_200_OK, ) assert response['account_number'] != account_fake_data['account_number'] assert float(response['trust']) == account_fake_data['trust']
def test_banks_patch(client, bank, bank_fake_data, self_configuration): response = client.patch_json( reverse('bank-detail', args=[bank.node_identifier]), generate_signed_request( data=bank_fake_data, nid_signing_key=get_signing_key(), ), expected=status.HTTP_200_OK, ) assert response['trust'] == f'{bank_fake_data["trust"]:.2f}'
def test_crawl_start_200(client, primary_validator_configuration): client.post_json( reverse('crawl-list'), generate_signed_request( data={'crawl': CRAWL_COMMAND_START}, nid_signing_key=get_signing_key(), ), expected=HTTP_404_NOT_FOUND, ) assert cache.get(CRAWL_STATUS) is None
def test_validator_patch(client, primary_validator_configuration, validator, validator_fake_data): response = client.patch_json( reverse('validator-detail', args=[validator.node_identifier]), generate_signed_request( data=validator_fake_data, nid_signing_key=get_signing_key(), ), expected=HTTP_200_OK, ) assert float(response['trust']) == validator_fake_data['trust']
def test_banks_post(client, primary_validator_configuration, bank, bank_fake_data): response = client.post_json( reverse('bank-list'), generate_signed_request( data=bank_fake_data, nid_signing_key=get_signing_key(), ), expected=HTTP_200_OK, ) assert float(response['trust']) == bank_fake_data['trust']
def test_update_bank_with_invalid_trust_value(client, bank, self_configuration, trust, response_msg): response = client.patch_json( reverse('bank-detail', args=[bank.node_identifier]), generate_signed_request( data={'trust': trust}, nid_signing_key=get_signing_key(), ), expected=status.HTTP_400_BAD_REQUEST, ) assert response['trust'] == [response_msg]
def test_create_bank_with_invalid_trust_value(client, bank, bank_fake_data, self_configuration, trust, response_msg): bank_fake_data['trust'] = trust response = client.post_json( reverse('bank-list'), generate_signed_request( data=bank_fake_data, nid_signing_key=get_signing_key(), ), expected=status.HTTP_400_BAD_REQUEST, ) assert response['trust'] == [response_msg]
def send_signed_post_request(*, data, ip_address, port, protocol, url_path): """Sign data and send to recipient""" signed_request = generate_signed_request(data=data, nid_signing_key=get_signing_key()) node_address = format_address(ip_address=ip_address, port=port, protocol=protocol) url = f'{node_address}{url_path}' try: post(url=url, body=signed_request) except Exception as e: capture_exception(e) logger.exception(e)
def set_primary_validator(): """ Set the primary validator to the validator that is the: - most trusted - online - configured as a primary validator """ self_configuration = get_self_configuration(exception_class=RuntimeError) primary_validator = self_configuration.primary_validator primary_validator_candidates = get_primary_validator_candidates(current_primary_validator=primary_validator) for validator in primary_validator_candidates: signed_request = generate_signed_request( data={ 'validator_node_identifier': validator.node_identifier }, nid_signing_key=get_signing_key() ) node_address = format_address( ip_address=validator.ip_address, port=validator.port, protocol=validator.protocol, ) url = f'{node_address}/upgrade_request' try: validator_config = post(url=url, body=signed_request) if validator_config['node_type'] != PRIMARY_VALIDATOR: continue self_configuration.primary_validator = validator self_configuration.save() send_primary_validator_updated_notices.delay() send_primary_validator_updated_notification() return except Exception as e: logger.exception(e)
def send_upgrade_notices(*, requesting_banks_node_identifier): """ Description: - notice from a previous confirmation validator that they are now a primary validator - triggered from an /upgrade_request from the validators most trusted bank - banks that trust self more than their existing primary validator will set self as new primary validator - banks that do not trust self more than their existing primary validator will remain on their existing network and can therefore be deleted Responses: - 200 response > bank set self as new primary validator - 400 response > bank is remaining on their existing network (can be deleted) Notes: The requesting (most trusted) bank may be excluded from notice recipients since it will already receive the updated information from the /upgrade_request response """ banks = Bank.objects.all().exclude(node_identifier=requesting_banks_node_identifier) for bank in banks: signed_request = generate_signed_request( data={ 'bank_node_identifier': bank.node_identifier }, nid_signing_key=get_signing_key() ) node_address = format_address( ip_address=bank.ip_address, port=bank.port, protocol=bank.protocol, ) url = f'{node_address}/upgrade_notice' try: post(url=url, body=signed_request) except Exception as e: bank.delete() capture_exception(e) logger.exception(e)
def post(self, request, format=None): serializer = FormSerializer(data=request.data, context={"request": request}) if serializer.is_valid(raise_exception=True): try: amount = FaucetOption.objects.get( pk=serializer.data['faucet_option_id']) url_str = serializer.data['url'] platform = get_platform(url_str) if platform: post = platform.process(url_str, amount) if post: receiver_account_number = post.get_account_number() post_id = post.get_id() platform = post.get_platform() user_id = post.get_user() bank_config = SelfConfiguration.objects.first() pv_config = bank_config.primary_validator signing_key = get_signing_key() sender_account_number = encode_verify_key( verify_key=signing_key.verify_key) account = validate_post_exists(receiver_account_number, post_id) faucet_model = validate_expiry(account, user_id) if account and not faucet_model: response = requests.get(( f'{pv_config.protocol}://{pv_config.ip_address}' f':{pv_config.port}' f'/accounts/' f'{sender_account_number}/balance_lock')) if response.status_code == 200: balance_lock = response.json().get( 'balance_lock') if not balance_lock: balance_lock = bank_config.node_identifier faucet_model, created = ( FaucetModel.objects.update_or_create( account=account, social_user_id=user_id, social_type=platform, defaults={ 'next_valid_access_time': (timezone.now() + timedelta(hours=amount.delay)) })) post_model, created = PostModel.objects.get_or_create( post_id=post_id, reward=amount, social_user=faucet_model) transactions = [{ 'amount': amount.coins, 'recipient': receiver_account_number, 'memo': "Thank you for using TNBExplorer testnet" }, { 'amount': bank_config.default_transaction_fee, 'recipient': bank_config.account_number, 'fee': "BANK" }, { 'amount': pv_config.default_transaction_fee, 'recipient': pv_config.account_number, 'fee': "PRIMARY_VALIDATOR" }] block = generate_block( account_number=signing_key.verify_key, balance_lock=balance_lock, signing_key=signing_key, transactions=transactions) serializer = BlockSerializerCreate( data=block, context={'request': request}, ) serializer.is_valid(raise_exception=True) block = serializer.save() return Response( success_response(( f'SUCCESS! {amount.coins} faucet funds' f' transferred to {receiver_account_number}.' ))) else: return Response(error_response( 'Unable to obtain TNB account details!'), status=status. HTTP_500_INTERNAL_SERVER_ERROR) else: if faucet_model: duration = ( faucet_model.next_valid_access_time - timezone.now()) totsec = duration.total_seconds() h = int(totsec // 3600) m = int((totsec % 3600) // 60) sec = round((totsec % 3600) % 60) return Response( error_response( ('Slow down! Try again after (' f'{h} hours {m} mins and {sec} secs' ') till cooldown period expires.')), status=status.HTTP_429_TOO_MANY_REQUESTS) else: return Response( error_response( ('Same post cannot be used again! ' ' Try again with a new one :P')), status=status.HTTP_400_BAD_REQUEST) else: return Response(error_response( ('Failed to extract information!' ' Make sure post is public,' ' contains #TNBFaucet and your account number')), status=status.HTTP_400_BAD_REQUEST) else: return Response(error_response( 'Only facebook and twitter URL allowed!'), status=status.HTTP_400_BAD_REQUEST) except FaucetOption.DoesNotExist: return Response(error_response('bad request format/data'), status=status.HTTP_400_BAD_REQUEST) else: return Response(error_response('bad request format/data'), status=status.HTTP_400_BAD_REQUEST)
def faucet_view(request): form = FaucetForm() if request.method == 'POST': form = FaucetForm(request.POST) if form.is_valid(): # form data url_str = form.cleaned_data['url'] amount = form.cleaned_data['amount'] platform = get_platform(url_str) if platform: post = platform.process(url_str, amount) if post: receiver_account_number = post.get_account_number() post_id = post.get_id() platform = post.get_platform() user_id = post.get_user() bank_config = SelfConfiguration.objects.first() pv_config = bank_config.primary_validator signing_key = get_signing_key() sender_account_number = encode_verify_key( verify_key=signing_key.verify_key) account = validate_post_exists(receiver_account_number, post_id) faucet_model = validate_expiry(account, user_id) if account and not faucet_model: response = requests.get( (f'{pv_config.protocol}://{pv_config.ip_address}' f':{pv_config.port}' f'/accounts/' f'{sender_account_number}/balance_lock')) if response.status_code == 200: balance_lock = response.json().get('balance_lock') if not balance_lock: balance_lock = bank_config.node_identifier faucet_model, created = ( FaucetModel.objects.update_or_create( account=account, social_user_id=user_id, social_type=platform, defaults={ 'next_valid_access_time': (timezone.now() + timedelta(hours=amount.delay)) })) post_model, created = PostModel.objects.get_or_create( post_id=post_id, reward=amount, social_user=faucet_model) transactions = [{ 'amount': amount.coins, 'recipient': receiver_account_number, 'memo': "Thank you for using TNBExplorer testnet" }, { 'amount': bank_config.default_transaction_fee, 'recipient': bank_config.account_number, 'fee': "BANK" }, { 'amount': pv_config.default_transaction_fee, 'recipient': pv_config.account_number, 'fee': "PRIMARY_VALIDATOR" }] block = generate_block( account_number=signing_key.verify_key, balance_lock=balance_lock, signing_key=signing_key, transactions=transactions) serializer = BlockSerializerCreate( data=block, context={'request': request}, ) serializer.is_valid(raise_exception=True) block = serializer.save() messages.success(request, ( f'SUCCESS! {amount.coins} faucet funds' f' transferred to {receiver_account_number}.')) form = FaucetForm() else: messages.error( request, 'Unable to obtain TNB account details!') else: form = FaucetForm() if faucet_model: duration = (faucet_model.next_valid_access_time - timezone.now()) totsec = duration.total_seconds() h = int(totsec // 3600) m = int((totsec % 3600) // 60) sec = round((totsec % 3600) % 60) messages.error( request, ('Slow down! Try again after (' f'{h} hours {m} mins and {sec} secs' ') till cooldown period expires.')) else: messages.error(request, ('Same post cannot be used again! ' ' Try again with a new one :P')) else: messages.error( request, ('Failed to extract information!' ' Make sure post is public,' ' contains #TNBFaucet and your account number')) else: messages.error(request, 'Only facebook and twitter URL allowed!') else: messages.error(request, 'Form invalid! Please provide correct details!') context = {'form': form} return render(request, 'index.html', context)