diff --git a/lbry/schema/result.py b/lbry/schema/result.py index b9d47d3fe..13d683b56 100644 --- a/lbry/schema/result.py +++ b/lbry/schema/result.py @@ -4,7 +4,13 @@ from typing import List from binascii import hexlify from itertools import chain +from lbry.error import ResolveCensoredError from lbry.schema.types.v2.result_pb2 import Outputs as OutputsMessage +from lbry.schema.types.v2.result_pb2 import Error as ErrorMessage + +INVALID = ErrorMessage.Code.Name(ErrorMessage.INVALID) +NOT_FOUND = ErrorMessage.Code.Name(ErrorMessage.NOT_FOUND) +BLOCKED = ErrorMessage.Code.Name(ErrorMessage.BLOCKED) class Censor: @@ -73,7 +79,12 @@ class Outputs: def message_to_txo(self, txo_message, tx_map): if txo_message.WhichOneof('meta') == 'error': - return None + return { + 'error': { + 'name': txo_message.error.Code.Name(txo_message.error.code).lower(), + 'text': txo_message.error.text, + } + } txo = tx_map[txo_message.tx_hash].outputs[txo_message.nout] if txo_message.WhichOneof('meta') == 'claim': claim = txo_message.claim @@ -146,9 +157,11 @@ class Outputs: if isinstance(txo, Exception): txo_message.error.text = txo.args[0] if isinstance(txo, ValueError): - txo_message.error.code = txo_message.error.INVALID + txo_message.error.code = ErrorMessage.INVALID elif isinstance(txo, LookupError): - txo_message.error.code = txo_message.error.NOT_FOUND + txo_message.error.code = ErrorMessage.NOT_FOUND + elif isinstance(txo, ResolveCensoredError): + txo_message.error.code = ErrorMessage.BLOCKED return txo_message.tx_hash = txo['txo_hash'][:32] txo_message.nout, = struct.unpack(' Tuple[List[Output], dict, int, int]: diff --git a/lbry/wallet/server/db/reader.py b/lbry/wallet/server/db/reader.py index 7fd4ca1f3..6147e8dea 100644 --- a/lbry/wallet/server/db/reader.py +++ b/lbry/wallet/server/db/reader.py @@ -4,14 +4,14 @@ import apsw import logging from operator import itemgetter from typing import Tuple, List, Dict, Union, Type, Optional -from binascii import unhexlify +from binascii import unhexlify, hexlify from decimal import Decimal from contextvars import ContextVar from functools import wraps from dataclasses import dataclass from lbry.wallet.database import query, interpolate - +from lbry.error import ResolveCensoredError from lbry.schema.url import URL, normalize_name from lbry.schema.tags import clean_tags from lbry.schema.result import Outputs, Censor @@ -451,6 +451,8 @@ def resolve_url(raw_url): matches = search_claims(censor, **query, limit=1) if matches: channel = matches[0] + elif censor.censored: + return ResolveCensoredError(raw_url, hexlify(next(iter(censor.censored))[::-1]).decode()) else: return LookupError(f'Could not find channel in "{raw_url}".') @@ -469,6 +471,8 @@ def resolve_url(raw_url): matches = search_claims(censor, **query, limit=1) if matches: return matches[0] + elif censor.censored: + return ResolveCensoredError(raw_url, hexlify(next(iter(censor.censored))[::-1]).decode()) else: return LookupError(f'Could not find stream in "{raw_url}".') diff --git a/tests/integration/blockchain/test_claim_commands.py b/tests/integration/blockchain/test_claim_commands.py index b1e391522..2a93cb3d7 100644 --- a/tests/integration/blockchain/test_claim_commands.py +++ b/tests/integration/blockchain/test_claim_commands.py @@ -763,29 +763,48 @@ class StreamCommands(ClaimTestCase): bad_content_id = self.get_claim_id( await self.stream_create('bad_content', '1.1', channel_name='@some_channel', tags=['bad']) ) - blocking_channel_id = self.get_claim_id( + filtering_channel_id = self.get_claim_id( await self.channel_create('@filtering', '1.0') ) self.conductor.spv_node.server.db.sql.filtering_channel_hashes.add( - unhexlify(blocking_channel_id)[::-1] + unhexlify(filtering_channel_id)[::-1] ) await self.stream_repost(bad_content_id, 'filter1', '1.1', channel_name='@filtering') # search for blocked content directly result = await self.out(self.daemon.jsonrpc_claim_search(name='bad_content')) self.assertEqual([], result['items']) - self.assertEqual({"channels": {blocking_channel_id: 1}, "total": 1}, result['blocked']) + self.assertEqual({"channels": {filtering_channel_id: 1}, "total": 1}, result['blocked']) # search channel containing blocked content result = await self.out(self.daemon.jsonrpc_claim_search(channel='@some_channel')) self.assertEqual(1, len(result['items'])) - self.assertEqual({"channels": {blocking_channel_id: 1}, "total": 1}, result['blocked']) + self.assertEqual({"channels": {filtering_channel_id: 1}, "total": 1}, result['blocked']) # content was filtered by not_tag before censoring result = await self.out(self.daemon.jsonrpc_claim_search(channel='@some_channel', not_tags=["good", "bad"])) self.assertEqual(0, len(result['items'])) self.assertEqual({"channels": {}, "total": 0}, result['blocked']) + blocking_channel_id = self.get_claim_id( + await self.channel_create('@blocking', '1.0') + ) + self.conductor.spv_node.server.db.sql.blocking_channel_hashes.add( + unhexlify(blocking_channel_id)[::-1] + ) + + # filtered content can still be resolved + result = await self.out(self.daemon.jsonrpc_resolve('lbry://@some_channel/bad_content')) + self.assertEqual(bad_content_id, result['lbry://@some_channel/bad_content']['claim_id']) + + await self.stream_repost(bad_content_id, 'block1', '1.1', channel_name='@blocking') + + # blocked content is not resolveable + result = await self.out(self.daemon.jsonrpc_resolve('lbry://@some_channel/bad_content')) + error = result['lbry://@some_channel/bad_content']['error'] + self.assertTrue(error['name'], 'blocked') + self.assertTrue(error['text'].startswith("Resolve of 'lbry://@some_channel/bad_content' was censored")) + async def test_publish_updates_file_list(self): tx = await self.stream_create(title='created') txo = tx['outputs'][0] diff --git a/tests/integration/blockchain/test_resolve_command.py b/tests/integration/blockchain/test_resolve_command.py index ba43b3b23..ce0af98e6 100644 --- a/tests/integration/blockchain/test_resolve_command.py +++ b/tests/integration/blockchain/test_resolve_command.py @@ -15,6 +15,7 @@ class BaseResolveTestCase(CommandTestCase): other = (await self.resolve(name))[name] if claim_id is None: self.assertIn('error', other) + self.assertEqual(other['error']['name'], 'not_found') else: self.assertEqual(claim_id, other['claim_id'])