censored resolve responses return appropriate error
This commit is contained in:
parent
b7eec0586c
commit
15abf49211
5 changed files with 52 additions and 15 deletions
|
@ -4,7 +4,13 @@ from typing import List
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
from itertools import chain
|
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 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:
|
class Censor:
|
||||||
|
@ -73,7 +79,12 @@ class Outputs:
|
||||||
|
|
||||||
def message_to_txo(self, txo_message, tx_map):
|
def message_to_txo(self, txo_message, tx_map):
|
||||||
if txo_message.WhichOneof('meta') == 'error':
|
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]
|
txo = tx_map[txo_message.tx_hash].outputs[txo_message.nout]
|
||||||
if txo_message.WhichOneof('meta') == 'claim':
|
if txo_message.WhichOneof('meta') == 'claim':
|
||||||
claim = txo_message.claim
|
claim = txo_message.claim
|
||||||
|
@ -146,9 +157,11 @@ class Outputs:
|
||||||
if isinstance(txo, Exception):
|
if isinstance(txo, Exception):
|
||||||
txo_message.error.text = txo.args[0]
|
txo_message.error.text = txo.args[0]
|
||||||
if isinstance(txo, ValueError):
|
if isinstance(txo, ValueError):
|
||||||
txo_message.error.code = txo_message.error.INVALID
|
txo_message.error.code = ErrorMessage.INVALID
|
||||||
elif isinstance(txo, LookupError):
|
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
|
return
|
||||||
txo_message.tx_hash = txo['txo_hash'][:32]
|
txo_message.tx_hash = txo['txo_hash'][:32]
|
||||||
txo_message.nout, = struct.unpack('<I', txo['txo_hash'][32:])
|
txo_message.nout, = struct.unpack('<I', txo['txo_hash'][32:])
|
||||||
|
|
|
@ -12,7 +12,7 @@ from binascii import hexlify, unhexlify
|
||||||
from typing import Dict, Tuple, Type, Iterable, List, Optional, DefaultDict
|
from typing import Dict, Tuple, Type, Iterable, List, Optional, DefaultDict
|
||||||
|
|
||||||
import pylru
|
import pylru
|
||||||
from lbry.schema.result import Outputs
|
from lbry.schema.result import Outputs, INVALID, NOT_FOUND
|
||||||
from lbry.schema.url import URL
|
from lbry.schema.url import URL
|
||||||
from lbry.crypto.hash import hash160, double_sha256, sha256
|
from lbry.crypto.hash import hash160, double_sha256, sha256
|
||||||
from lbry.crypto.base58 import Base58
|
from lbry.crypto.base58 import Base58
|
||||||
|
@ -661,13 +661,13 @@ class Ledger(metaclass=LedgerRegistry):
|
||||||
assert len(urls) == len(txos), "Mismatch between urls requested for resolve and responses received."
|
assert len(urls) == len(txos), "Mismatch between urls requested for resolve and responses received."
|
||||||
result = {}
|
result = {}
|
||||||
for url, txo in zip(urls, txos):
|
for url, txo in zip(urls, txos):
|
||||||
if txo and URL.parse(url).has_stream_in_channel:
|
|
||||||
if not txo.channel or not txo.is_signed_by(txo.channel, self):
|
|
||||||
txo = None
|
|
||||||
if txo:
|
if txo:
|
||||||
result[url] = txo
|
if isinstance(txo, Output) and URL.parse(url).has_stream_in_channel:
|
||||||
|
if not txo.channel or not txo.is_signed_by(txo.channel, self):
|
||||||
|
txo = {'error': {'name': INVALID, 'text': f'{url} has invalid channel signature'}}
|
||||||
else:
|
else:
|
||||||
result[url] = {'error': f'{url} did not resolve to a claim'}
|
txo = {'error': {'name': NOT_FOUND, 'text': f'{url} did not resolve to a claim'}}
|
||||||
|
result[url] = txo
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def claim_search(self, accounts, **kwargs) -> Tuple[List[Output], dict, int, int]:
|
async def claim_search(self, accounts, **kwargs) -> Tuple[List[Output], dict, int, int]:
|
||||||
|
|
|
@ -4,14 +4,14 @@ import apsw
|
||||||
import logging
|
import logging
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from typing import Tuple, List, Dict, Union, Type, Optional
|
from typing import Tuple, List, Dict, Union, Type, Optional
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify, hexlify
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from lbry.wallet.database import query, interpolate
|
from lbry.wallet.database import query, interpolate
|
||||||
|
from lbry.error import ResolveCensoredError
|
||||||
from lbry.schema.url import URL, normalize_name
|
from lbry.schema.url import URL, normalize_name
|
||||||
from lbry.schema.tags import clean_tags
|
from lbry.schema.tags import clean_tags
|
||||||
from lbry.schema.result import Outputs, Censor
|
from lbry.schema.result import Outputs, Censor
|
||||||
|
@ -451,6 +451,8 @@ def resolve_url(raw_url):
|
||||||
matches = search_claims(censor, **query, limit=1)
|
matches = search_claims(censor, **query, limit=1)
|
||||||
if matches:
|
if matches:
|
||||||
channel = matches[0]
|
channel = matches[0]
|
||||||
|
elif censor.censored:
|
||||||
|
return ResolveCensoredError(raw_url, hexlify(next(iter(censor.censored))[::-1]).decode())
|
||||||
else:
|
else:
|
||||||
return LookupError(f'Could not find channel in "{raw_url}".')
|
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)
|
matches = search_claims(censor, **query, limit=1)
|
||||||
if matches:
|
if matches:
|
||||||
return matches[0]
|
return matches[0]
|
||||||
|
elif censor.censored:
|
||||||
|
return ResolveCensoredError(raw_url, hexlify(next(iter(censor.censored))[::-1]).decode())
|
||||||
else:
|
else:
|
||||||
return LookupError(f'Could not find stream in "{raw_url}".')
|
return LookupError(f'Could not find stream in "{raw_url}".')
|
||||||
|
|
||||||
|
|
|
@ -763,29 +763,48 @@ class StreamCommands(ClaimTestCase):
|
||||||
bad_content_id = self.get_claim_id(
|
bad_content_id = self.get_claim_id(
|
||||||
await self.stream_create('bad_content', '1.1', channel_name='@some_channel', tags=['bad'])
|
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')
|
await self.channel_create('@filtering', '1.0')
|
||||||
)
|
)
|
||||||
self.conductor.spv_node.server.db.sql.filtering_channel_hashes.add(
|
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')
|
await self.stream_repost(bad_content_id, 'filter1', '1.1', channel_name='@filtering')
|
||||||
|
|
||||||
# search for blocked content directly
|
# search for blocked content directly
|
||||||
result = await self.out(self.daemon.jsonrpc_claim_search(name='bad_content'))
|
result = await self.out(self.daemon.jsonrpc_claim_search(name='bad_content'))
|
||||||
self.assertEqual([], result['items'])
|
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
|
# search channel containing blocked content
|
||||||
result = await self.out(self.daemon.jsonrpc_claim_search(channel='@some_channel'))
|
result = await self.out(self.daemon.jsonrpc_claim_search(channel='@some_channel'))
|
||||||
self.assertEqual(1, len(result['items']))
|
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
|
# content was filtered by not_tag before censoring
|
||||||
result = await self.out(self.daemon.jsonrpc_claim_search(channel='@some_channel', not_tags=["good", "bad"]))
|
result = await self.out(self.daemon.jsonrpc_claim_search(channel='@some_channel', not_tags=["good", "bad"]))
|
||||||
self.assertEqual(0, len(result['items']))
|
self.assertEqual(0, len(result['items']))
|
||||||
self.assertEqual({"channels": {}, "total": 0}, result['blocked'])
|
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):
|
async def test_publish_updates_file_list(self):
|
||||||
tx = await self.stream_create(title='created')
|
tx = await self.stream_create(title='created')
|
||||||
txo = tx['outputs'][0]
|
txo = tx['outputs'][0]
|
||||||
|
|
|
@ -15,6 +15,7 @@ class BaseResolveTestCase(CommandTestCase):
|
||||||
other = (await self.resolve(name))[name]
|
other = (await self.resolve(name))[name]
|
||||||
if claim_id is None:
|
if claim_id is None:
|
||||||
self.assertIn('error', other)
|
self.assertIn('error', other)
|
||||||
|
self.assertEqual(other['error']['name'], 'not_found')
|
||||||
else:
|
else:
|
||||||
self.assertEqual(claim_id, other['claim_id'])
|
self.assertEqual(claim_id, other['claim_id'])
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue