2019-04-29 06:39:20 +02:00
|
|
|
import base64
|
|
|
|
import struct
|
2019-11-18 21:48:52 +01:00
|
|
|
from typing import List, Optional, Tuple
|
2019-04-29 06:39:20 +02:00
|
|
|
from binascii import hexlify
|
2019-05-26 05:06:22 +02:00
|
|
|
from itertools import chain
|
2019-04-29 06:39:20 +02:00
|
|
|
|
2019-06-21 02:55:47 +02:00
|
|
|
from lbry.schema.types.v2.result_pb2 import Outputs as OutputsMessage
|
2019-04-29 06:39:20 +02:00
|
|
|
|
|
|
|
|
2020-01-10 16:47:57 +01:00
|
|
|
class Censor:
|
|
|
|
|
|
|
|
def __init__(self, claim_ids: dict = None, channel_ids: set = None, tags: set = None):
|
|
|
|
self.claim_ids = claim_ids or {}
|
|
|
|
self.channel_ids = channel_ids or set()
|
|
|
|
self.tags = tags or set()
|
|
|
|
self.blocked_claims = {}
|
|
|
|
self.blocked_channels = {}
|
|
|
|
self.blocked_tags = {}
|
|
|
|
self.total = 0
|
|
|
|
|
|
|
|
def censor(self, row) -> bool:
|
|
|
|
censored = False
|
|
|
|
if row['claim_hash'] in self.claim_ids:
|
|
|
|
censored = True
|
|
|
|
channel_id = self.claim_ids[row['claim_hash']]
|
|
|
|
self.blocked_claims.setdefault(channel_id, 0)
|
|
|
|
self.blocked_claims[channel_id] += 1
|
|
|
|
if row['channel_hash'] in self.channel_ids:
|
|
|
|
censored = True
|
|
|
|
self.blocked_channels.setdefault(row['channel_hash'], 0)
|
|
|
|
self.blocked_channels[row['channel_hash']] += 1
|
|
|
|
if self.tags.intersection(row['tags']):
|
|
|
|
censored = True
|
|
|
|
for tag in self.tags:
|
|
|
|
if tag in row['tags']:
|
|
|
|
self.blocked_tags.setdefault(tag, 0)
|
|
|
|
self.blocked_tags[tag] += 1
|
|
|
|
if censored:
|
|
|
|
self.total += 1
|
|
|
|
return censored
|
|
|
|
|
|
|
|
def to_message(self, outputs: OutputsMessage):
|
|
|
|
outputs.blocked_total = self.total
|
|
|
|
for channel_hash, count in self.blocked_claims.items():
|
|
|
|
block = outputs.blocked.add()
|
|
|
|
block.count = count
|
|
|
|
block.ban_channel = channel_hash
|
|
|
|
for channel_hash, count in self.blocked_channels.items():
|
|
|
|
block = outputs.blocked.add()
|
|
|
|
block.count = count
|
|
|
|
block.not_channel = channel_hash
|
|
|
|
for tag, count in self.blocked_tags.items():
|
|
|
|
block = outputs.blocked.add()
|
|
|
|
block.count = count
|
|
|
|
block.not_tag = tag
|
|
|
|
|
|
|
|
|
2019-04-29 06:39:20 +02:00
|
|
|
class Outputs:
|
|
|
|
|
2020-01-10 16:47:57 +01:00
|
|
|
__slots__ = 'txos', 'extra_txos', 'txs', 'offset', 'total', 'blocked', 'blocked_total'
|
2019-04-29 06:39:20 +02:00
|
|
|
|
2020-01-10 16:47:57 +01:00
|
|
|
def __init__(self, txos: List, extra_txos: List, txs: set,
|
|
|
|
offset: int, total: int, blocked: List, blocked_total: int):
|
2019-04-29 06:39:20 +02:00
|
|
|
self.txos = txos
|
|
|
|
self.txs = txs
|
2019-05-26 05:06:22 +02:00
|
|
|
self.extra_txos = extra_txos
|
2019-04-29 06:39:20 +02:00
|
|
|
self.offset = offset
|
|
|
|
self.total = total
|
2020-01-10 16:47:57 +01:00
|
|
|
self.blocked = blocked
|
|
|
|
self.blocked_total = blocked_total
|
2019-04-29 06:39:20 +02:00
|
|
|
|
|
|
|
def inflate(self, txs):
|
2019-05-26 05:06:22 +02:00
|
|
|
tx_map = {tx.hash: tx for tx in txs}
|
|
|
|
for txo_message in self.extra_txos:
|
|
|
|
self.message_to_txo(txo_message, tx_map)
|
2020-01-10 16:47:57 +01:00
|
|
|
txos = [self.message_to_txo(txo_message, tx_map) for txo_message in self.txos]
|
|
|
|
return txos, self.inflate_blocked(txs)
|
2019-05-26 05:06:22 +02:00
|
|
|
|
|
|
|
def message_to_txo(self, txo_message, tx_map):
|
|
|
|
if txo_message.WhichOneof('meta') == 'error':
|
|
|
|
return None
|
|
|
|
txo = tx_map[txo_message.tx_hash].outputs[txo_message.nout]
|
|
|
|
if txo_message.WhichOneof('meta') == 'claim':
|
|
|
|
claim = txo_message.claim
|
|
|
|
txo.meta = {
|
2019-05-26 19:21:26 +02:00
|
|
|
'short_url': f'lbry://{claim.short_url}',
|
|
|
|
'canonical_url': f'lbry://{claim.canonical_url or claim.short_url}',
|
2019-11-18 21:48:52 +01:00
|
|
|
'reposted': claim.reposted,
|
2019-05-26 05:06:22 +02:00
|
|
|
'is_controlling': claim.is_controlling,
|
2019-06-23 04:38:21 +02:00
|
|
|
'take_over_height': claim.take_over_height,
|
2019-05-28 04:20:21 +02:00
|
|
|
'creation_height': claim.creation_height,
|
2019-05-26 05:06:22 +02:00
|
|
|
'activation_height': claim.activation_height,
|
|
|
|
'expiration_height': claim.expiration_height,
|
|
|
|
'effective_amount': claim.effective_amount,
|
|
|
|
'support_amount': claim.support_amount,
|
|
|
|
'trending_group': claim.trending_group,
|
|
|
|
'trending_mixed': claim.trending_mixed,
|
|
|
|
'trending_local': claim.trending_local,
|
|
|
|
'trending_global': claim.trending_global,
|
|
|
|
}
|
|
|
|
if claim.HasField('channel'):
|
|
|
|
txo.channel = tx_map[claim.channel.tx_hash].outputs[claim.channel.nout]
|
2019-11-18 21:48:52 +01:00
|
|
|
if claim.HasField('repost'):
|
|
|
|
txo.reposted_claim = tx_map[claim.repost.tx_hash].outputs[claim.repost.nout]
|
2019-05-28 04:20:21 +02:00
|
|
|
try:
|
|
|
|
if txo.claim.is_channel:
|
|
|
|
txo.meta['claims_in_channel'] = claim.claims_in_channel
|
|
|
|
except:
|
|
|
|
pass
|
2019-05-26 05:06:22 +02:00
|
|
|
return txo
|
2019-04-29 06:39:20 +02:00
|
|
|
|
2020-01-10 16:47:57 +01:00
|
|
|
def inflate_blocked(self, txs):
|
|
|
|
blocked = {}
|
|
|
|
return
|
|
|
|
if txo_message.WhichOneof('meta') == 'error':
|
|
|
|
return None
|
|
|
|
txo = tx_map[txo_message.tx_hash].outputs[txo_message.nout]
|
|
|
|
if txo_message.WhichOneof('meta') == 'claim':
|
|
|
|
claim = txo_message.claim
|
|
|
|
txo.meta = {
|
|
|
|
'short_url': f'lbry://{claim.short_url}',
|
|
|
|
'canonical_url': f'lbry://{claim.canonical_url or claim.short_url}',
|
|
|
|
'reposted': claim.reposted,
|
|
|
|
'is_controlling': claim.is_controlling,
|
|
|
|
'take_over_height': claim.take_over_height,
|
|
|
|
'creation_height': claim.creation_height,
|
|
|
|
'activation_height': claim.activation_height,
|
|
|
|
'expiration_height': claim.expiration_height,
|
|
|
|
'effective_amount': claim.effective_amount,
|
|
|
|
'support_amount': claim.support_amount,
|
|
|
|
'trending_group': claim.trending_group,
|
|
|
|
'trending_mixed': claim.trending_mixed,
|
|
|
|
'trending_local': claim.trending_local,
|
|
|
|
'trending_global': claim.trending_global,
|
|
|
|
}
|
|
|
|
if claim.HasField('channel'):
|
|
|
|
txo.channel = tx_map[claim.channel.tx_hash].outputs[claim.channel.nout]
|
|
|
|
if claim.HasField('repost'):
|
|
|
|
txo.reposted_claim = tx_map[claim.repost.tx_hash].outputs[claim.repost.nout]
|
|
|
|
try:
|
|
|
|
if txo.claim.is_channel:
|
|
|
|
txo.meta['claims_in_channel'] = claim.claims_in_channel
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
return txo
|
|
|
|
|
2019-04-29 06:39:20 +02:00
|
|
|
@classmethod
|
|
|
|
def from_base64(cls, data: str) -> 'Outputs':
|
|
|
|
return cls.from_bytes(base64.b64decode(data))
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_bytes(cls, data: bytes) -> 'Outputs':
|
|
|
|
outputs = OutputsMessage()
|
|
|
|
outputs.ParseFromString(data)
|
2019-05-26 05:06:22 +02:00
|
|
|
txs = set()
|
|
|
|
for txo_message in chain(outputs.txos, outputs.extra_txos):
|
2019-04-29 06:39:20 +02:00
|
|
|
if txo_message.WhichOneof('meta') == 'error':
|
|
|
|
continue
|
2019-05-26 05:06:22 +02:00
|
|
|
txs.add((hexlify(txo_message.tx_hash[::-1]).decode(), txo_message.height))
|
2020-01-10 16:47:57 +01:00
|
|
|
return cls(
|
|
|
|
outputs.txos, outputs.extra_txos, txs,
|
|
|
|
outputs.offset, outputs.total,
|
|
|
|
outputs.blocked, outputs.blocked_total
|
|
|
|
)
|
2019-04-29 06:39:20 +02:00
|
|
|
|
|
|
|
@classmethod
|
2020-01-10 16:47:57 +01:00
|
|
|
def to_base64(cls, txo_rows, extra_txo_rows, offset=0, total=None, blocked=None) -> str:
|
|
|
|
return base64.b64encode(cls.to_bytes(txo_rows, extra_txo_rows, offset, total, blocked)).decode()
|
2019-04-29 06:39:20 +02:00
|
|
|
|
|
|
|
@classmethod
|
2020-01-10 16:47:57 +01:00
|
|
|
def to_bytes(cls, txo_rows, extra_txo_rows, offset=0, total=None, blocked: Censor = None) -> bytes:
|
2019-04-29 06:39:20 +02:00
|
|
|
page = OutputsMessage()
|
|
|
|
page.offset = offset
|
2019-07-08 05:08:39 +02:00
|
|
|
if total is not None:
|
|
|
|
page.total = total
|
2020-01-10 16:47:57 +01:00
|
|
|
if blocked is not None:
|
|
|
|
blocked.to_message(page)
|
2019-05-26 05:06:22 +02:00
|
|
|
for row in txo_rows:
|
2019-11-18 21:48:52 +01:00
|
|
|
cls.row_to_message(row, page.txos.add(), extra_txo_rows)
|
2019-05-26 05:06:22 +02:00
|
|
|
for row in extra_txo_rows:
|
2019-11-18 21:48:52 +01:00
|
|
|
cls.row_to_message(row, page.extra_txos.add(), extra_txo_rows)
|
2019-05-26 05:06:22 +02:00
|
|
|
return page.SerializeToString()
|
|
|
|
|
|
|
|
@classmethod
|
2019-11-18 21:48:52 +01:00
|
|
|
def row_to_message(cls, txo, txo_message, extra_txo_rows):
|
2019-05-26 05:06:22 +02:00
|
|
|
if isinstance(txo, Exception):
|
|
|
|
txo_message.error.text = txo.args[0]
|
|
|
|
if isinstance(txo, ValueError):
|
|
|
|
txo_message.error.code = txo_message.error.INVALID
|
|
|
|
elif isinstance(txo, LookupError):
|
|
|
|
txo_message.error.code = txo_message.error.NOT_FOUND
|
|
|
|
return
|
|
|
|
txo_message.tx_hash = txo['txo_hash'][:32]
|
|
|
|
txo_message.nout, = struct.unpack('<I', txo['txo_hash'][32:])
|
|
|
|
txo_message.height = txo['height']
|
|
|
|
txo_message.claim.short_url = txo['short_url']
|
2019-11-18 21:48:52 +01:00
|
|
|
txo_message.claim.reposted = txo['reposted']
|
2019-05-26 05:06:22 +02:00
|
|
|
if txo['canonical_url'] is not None:
|
2019-05-24 05:55:57 +02:00
|
|
|
txo_message.claim.canonical_url = txo['canonical_url']
|
2019-05-26 05:06:22 +02:00
|
|
|
txo_message.claim.is_controlling = bool(txo['is_controlling'])
|
2019-06-23 05:01:26 +02:00
|
|
|
if txo['last_take_over_height'] is not None:
|
|
|
|
txo_message.claim.take_over_height = txo['last_take_over_height']
|
2019-05-28 04:20:21 +02:00
|
|
|
txo_message.claim.creation_height = txo['creation_height']
|
2019-05-26 05:06:22 +02:00
|
|
|
txo_message.claim.activation_height = txo['activation_height']
|
|
|
|
txo_message.claim.expiration_height = txo['expiration_height']
|
|
|
|
if txo['claims_in_channel'] is not None:
|
2019-04-29 06:39:20 +02:00
|
|
|
txo_message.claim.claims_in_channel = txo['claims_in_channel']
|
2019-05-26 05:06:22 +02:00
|
|
|
txo_message.claim.effective_amount = txo['effective_amount']
|
|
|
|
txo_message.claim.support_amount = txo['support_amount']
|
|
|
|
txo_message.claim.trending_group = txo['trending_group']
|
|
|
|
txo_message.claim.trending_mixed = txo['trending_mixed']
|
|
|
|
txo_message.claim.trending_local = txo['trending_local']
|
|
|
|
txo_message.claim.trending_global = txo['trending_global']
|
2019-11-18 21:48:52 +01:00
|
|
|
cls.set_reference(txo_message, 'channel', txo['channel_hash'], extra_txo_rows)
|
|
|
|
cls.set_reference(txo_message, 'repost', txo['reposted_claim_hash'], extra_txo_rows)
|
|
|
|
|
2020-01-10 16:47:57 +01:00
|
|
|
@staticmethod
|
|
|
|
def set_blocked(message, blocked):
|
|
|
|
message.blocked_total = blocked.total
|
|
|
|
|
2019-11-18 21:48:52 +01:00
|
|
|
@staticmethod
|
|
|
|
def set_reference(message, attr, claim_hash, rows):
|
|
|
|
if claim_hash:
|
|
|
|
for txo in rows:
|
|
|
|
if claim_hash == txo['claim_hash']:
|
|
|
|
reference = getattr(message.claim, attr)
|
|
|
|
reference.tx_hash = txo['txo_hash'][:32]
|
|
|
|
reference.nout = struct.unpack('<I', txo['txo_hash'][32:])[0]
|
|
|
|
reference.height = txo['height']
|
|
|
|
break
|