add more tests, flag to exclude own supports

This commit is contained in:
Alex Grintsvayg 2020-10-16 14:14:38 -04:00 committed by Lex Berezhny
parent 4bb0344e05
commit abebc0d878
7 changed files with 98 additions and 27 deletions

View file

@ -288,8 +288,8 @@ class Database:
async def search_supports(self, **constraints) -> Result[Output]: async def search_supports(self, **constraints) -> Result[Output]:
return await self.fetch_result(q.search_supports, **constraints) return await self.fetch_result(q.search_supports, **constraints)
async def sum_supports(self, claim_hash, include_channel_content=False) -> List[Dict]: async def sum_supports(self, claim_hash, include_channel_content=False, exclude_own_supports=False) -> List[Dict]:
return await self.run(q.sum_supports, claim_hash, include_channel_content) return await self.run(q.sum_supports, claim_hash, include_channel_content, exclude_own_supports)
async def resolve(self, urls, **kwargs) -> Dict[str, Output]: async def resolve(self, urls, **kwargs) -> Dict[str, Output]:
return await self.run(q.resolve, urls, **kwargs) return await self.run(q.resolve, urls, **kwargs)

View file

@ -2,7 +2,7 @@ import struct
import logging import logging
from decimal import Decimal from decimal import Decimal
from binascii import unhexlify from binascii import unhexlify
from typing import Tuple, List, Optional from typing import Tuple, List, Optional, Dict
from sqlalchemy import func, case, text from sqlalchemy import func, case, text
from sqlalchemy.future import select, Select from sqlalchemy.future import select, Select
@ -62,30 +62,39 @@ def search_supports(**constraints) -> Tuple[List[Output], Optional[int]]:
return txos, total return txos, total
def sum_supports(claim_hash, include_channel_content = False) -> Tuple[List[Output], Optional[int]]: def sum_supports(claim_hash, include_channel_content=False, exclude_own_supports=False) -> List[Dict]:
supporter = Claim.alias("supporter") supporter = Claim.alias("supporter")
content = Claim.alias("content") content = Claim.alias("content")
where_condition = (content.c.claim_hash == claim_hash) where_condition = (content.c.claim_hash == claim_hash)
if include_channel_content: if include_channel_content:
where_condition |= (content.c.channel_hash == claim_hash) where_condition |= (content.c.channel_hash == claim_hash)
support_join_condition = TXO.c.channel_hash == supporter.c.claim_hash
if exclude_own_supports:
support_join_condition &= TXO.c.channel_hash != claim_hash
q = select( q = select(
supporter.c.claim_name.label("supporter"), supporter.c.short_url.label("supporter"),
func.sum(TXO.c.amount).label("staked"), func.sum(TXO.c.amount).label("staked"),
).select_from( ).select_from(
TXO TXO
.join(content, TXO.c.claim_hash == content.c.claim_hash) .join(content, TXO.c.claim_hash == content.c.claim_hash)
.join(supporter, TXO.c.channel_hash == supporter.c.claim_hash) .join(supporter, support_join_condition)
).where( ).where(
where_condition & where_condition &
(TXO.c.txo_type == TXO_TYPES["support"]) & (TXO.c.txo_type == TXO_TYPES["support"]) &
((TXO.c.address == content.c.address) | ((TXO.c.address != content.c.address) & (TXO.c.spent_height == 0))) ((TXO.c.address == content.c.address) | ((TXO.c.address != content.c.address) & (TXO.c.spent_height == 0)))
).group_by( ).group_by(
supporter.c.claim_name supporter.c.short_url
).order_by( ).order_by(
text("staked DESC") text("staked DESC, supporter ASC")
) )
return context().fetchall(q)
result = context().fetchall(q)
total = sum([row['staked'] for row in result])
for row in result:
row['percent'] = round(row['staked']/total*100, 4)
return result
def search_support_count(**constraints) -> int: def search_support_count(**constraints) -> int:

View file

@ -2579,20 +2579,22 @@ class API:
claim_id: str, # id of claim to calculate support stats for claim_id: str, # id of claim to calculate support stats for
include_channel_content: bool = False, # if claim_id is for a channel, include supports for include_channel_content: bool = False, # if claim_id is for a channel, include supports for
# claims in that channel # claims in that channel
exclude_own_supports: bool = False, # exclude supports signed by claim_id (i.e. self-supports)
**pagination_kwargs **pagination_kwargs
) -> Paginated[Dict]: # supports grouped by channel ) -> Paginated[Dict]: # supports grouped by channel
# TODO: add unsigned supports to the output so the numbers add up. just a left join on identity # TODO: add unsigned supports to the output so the numbers add up. just a left join on identity
""" """
List total staked supports for a claim, grouped by the channel that signed the support. List total staked supports for a claim, grouped by the channel that signed the support.
If claim_id is a channel claim, you can use --include_channel_content to also include supports for If claim_id is a channel claim:
content claims in the channel. Use --include_channel_content to include supports for content claims in the channel.
Use --exclude_own_supports to exclude supports from the channel to itself.
Usage: Usage:
support sum <claim_id> [--inculde_channel_content] support sum <claim_id> [--inculde_channel_content]
{kwargs} {kwargs}
""" """
return await self.service.sum_supports(hex_str_to_hash(claim_id), include_channel_content) return await self.service.sum_supports(hex_str_to_hash(claim_id), include_channel_content, exclude_own_supports)
async def support_abandon( async def support_abandon(
self, self,

View file

@ -152,7 +152,8 @@ class Service:
async def search_transactions(self, txids): async def search_transactions(self, txids):
raise NotImplementedError raise NotImplementedError
async def sum_supports(self, claim_hash: bytes, include_channel_content=False) -> List[Dict]: async def sum_supports(self, claim_hash: bytes, include_channel_content=False, exclude_own_supports=False) \
-> List[Dict]:
raise NotImplementedError raise NotImplementedError
async def announce_addresses(self, address_manager, addresses: List[str]): async def announce_addresses(self, address_manager, addresses: List[str]):

View file

@ -69,5 +69,6 @@ class FullNode(Service):
async def protobuf_resolve(self, urls, **kwargs): async def protobuf_resolve(self, urls, **kwargs):
return await self.db.protobuf_resolve(urls, **kwargs) return await self.db.protobuf_resolve(urls, **kwargs)
async def sum_supports(self, claim_hash: bytes, include_channel_content=False) -> List[Dict]: async def sum_supports(self, claim_hash: bytes, include_channel_content=False, exclude_own_supports=False) \
return await self.db.sum_supports(claim_hash, include_channel_content) -> List[Dict]:
return await self.db.sum_supports(claim_hash, include_channel_content, exclude_own_supports)

View file

@ -46,5 +46,6 @@ class LightClient(Service):
async def search_supports(self, accounts, **kwargs): async def search_supports(self, accounts, **kwargs):
pass pass
async def sum_supports(self, claim_hash: bytes, include_channel_content=False) -> List[Dict]: async def sum_supports(self, claim_hash: bytes, include_channel_content=False, exclude_own_supports=False) \
return await self.client.sum_supports(claim_hash, include_channel_content) -> List[Dict]:
return await self.client.sum_supports(claim_hash, include_channel_content, exclude_own_supports)

View file

@ -945,34 +945,91 @@ class TestGeneralBlockchainSync(SyncingBlockchainTestCase):
self.assertEqual(claim.claim_id, results[0].claim_id) self.assertEqual(claim.claim_id, results[0].claim_id)
async def test_claim_search_sum(self): async def test_claim_search_sum(self):
# print("DB URL: " + self.chain.ledger.conf.db_url_or_default)
await self.generate(100) await self.generate(100)
# create a few channels with unique addresses
channel_a = await self.get_claim(await self.create_claim(name="@A", is_channel=True)) channel_a = await self.get_claim(await self.create_claim(name="@A", is_channel=True))
self.address = await self.chain.get_new_address()
channel_b = await self.get_claim(await self.create_claim(name="@B", is_channel=True)) channel_b = await self.get_claim(await self.create_claim(name="@B", is_channel=True))
self.address = await self.chain.get_new_address()
channel_c = await self.get_claim(await self.create_claim(name="@C", is_channel=True)) channel_c = await self.get_claim(await self.create_claim(name="@C", is_channel=True))
self.address = await self.chain.get_new_address()
await self.generate(1)
ch_c, ch_b, ch_a = await self.db.search_claims(order_by=['name'], claim_type="channel", limit=3)
await self.support_claim(channel_a, '10.0', sign=channel_b) # make some tips and supports from channels B and C to channel A
await self.support_claim(channel_a, '4.0', sign=channel_c) support_b = await self.support_claim(channel_a, '5.0', sign=channel_b)
tip_b = await self.support_claim(channel_a, '5.0', sign=channel_b, address=channel_a.get_address(self.chain.ledger))
await self.support_claim(channel_a, '2.0', sign=channel_c) await self.support_claim(channel_a, '2.0', sign=channel_c)
await self.support_claim(channel_a, '2.0', sign=channel_c)
tip_c = await self.support_claim(channel_a, '2.0', sign=channel_c, address=channel_a.get_address(self.chain.ledger))
await self.generate(1) await self.generate(1)
# check that supports sum correctly
results = await self.db.sum_supports(channel_a.claim_hash) results = await self.db.sum_supports(channel_a.claim_hash)
self.assertEqual(results, [{'supporter': '@B', 'staked': 1000000000}, {'supporter': '@C', 'staked': 600000000}]) self.assertEqual(results, [
{'supporter': ch_b.meta['short_url'], 'staked': 1000000000, 'percent': 62.5},
{'supporter': ch_c.meta['short_url'], 'staked': 600000000, 'percent': 37.5},
])
# create a claim in channel A and have channel B support that claim
claim_a = await self.get_claim(await self.create_claim(name="bob", amount='2.0', sign=channel_a)) claim_a = await self.get_claim(await self.create_claim(name="bob", amount='2.0', sign=channel_a))
await self.support_claim(claim_a, '1.0', sign=channel_b) await self.support_claim(claim_a, '1.0', sign=channel_b)
await self.generate(1) await self.generate(1)
# supports for just the channel claim should be unaffected ...
results = await self.db.sum_supports(channel_a.claim_hash) results = await self.db.sum_supports(channel_a.claim_hash)
self.assertEqual(results, [{'supporter': '@B', 'staked': 1000000000}, {'supporter': '@C', 'staked': 600000000}]) self.assertEqual(results, [
{'supporter': ch_b.meta['short_url'], 'staked': 1000000000, 'percent': 62.5},
{'supporter': ch_c.meta['short_url'], 'staked': 600000000, 'percent': 37.5},
])
# ... but when you include supports for content in the channel, the support for claim_a is added in
results = await self.db.sum_supports(channel_a.claim_hash, include_channel_content=True)
self.assertEqual(results, [
{'supporter': ch_b.meta['short_url'], 'staked': 1100000000, 'percent': 64.7059},
{'supporter': ch_c.meta['short_url'], 'staked': 600000000, 'percent': 35.2941},
])
results = await self.db.sum_supports(channel_a.claim_hash, True) # check that sum_supports works as expected for a non-channel claim (with and without including channel content)
self.assertEqual(results, [{'supporter': '@B', 'staked': 1100000000}, {'supporter': '@C', 'staked': 600000000}]) results = await self.db.sum_supports(claim_a.claim_hash, include_channel_content=False)
self.assertEqual(results, [{'supporter': ch_b.meta['short_url'], 'staked': 100000000, 'percent': 100}])
results = await self.db.sum_supports(claim_a.claim_hash, include_channel_content=True)
self.assertEqual(results, [{'supporter': ch_b.meta['short_url'], 'staked': 100000000, 'percent': 100}])
results = await self.db.sum_supports(claim_a.claim_hash, False) # if a support is abandoned, it stops counting
self.assertEqual(results, [{'supporter': '@B', 'staked': 100000000}]) await self.abandon_support(support_b)
results = await self.db.sum_supports(claim_a.claim_hash, True) await self.generate(1)
self.assertEqual(results, [{'supporter': '@B', 'staked': 100000000}]) results = await self.db.sum_supports(channel_a.claim_hash)
self.assertEqual(results, [
{'supporter': ch_c.meta['short_url'], 'staked': 600000000, 'percent': 54.5455},
{'supporter': ch_b.meta['short_url'], 'staked': 500000000, 'percent': 45.4545},
])
# but if a creator unlocks a tip, that still counts as the tipping channel's contribution
await self.abandon_support(tip_b)
await self.abandon_support(tip_c)
await self.generate(1)
results = await self.db.sum_supports(channel_a.claim_hash)
self.assertEqual(results, [
{'supporter': ch_c.meta['short_url'], 'staked': 600000000, 'percent': 54.5455},
{'supporter': ch_b.meta['short_url'], 'staked': 500000000, 'percent': 45.4545},
])
# a channel's own supports don't count if you exclude them
await self.support_claim(channel_a, '10.0', sign=channel_a)
await self.generate(1)
results = await self.db.sum_supports(channel_a.claim_hash, exclude_own_supports=False)
self.assertEqual(results, [
{'supporter': ch_a.meta['short_url'], 'staked': 1000000000, 'percent': 47.6190},
{'supporter': ch_c.meta['short_url'], 'staked': 600000000, 'percent': 28.5714},
{'supporter': ch_b.meta['short_url'], 'staked': 500000000, 'percent': 23.8095},
])
results = await self.db.sum_supports(channel_a.claim_hash, exclude_own_supports=True)
self.assertEqual(results, [
{'supporter': ch_c.meta['short_url'], 'staked': 600000000, 'percent': 54.5455},
{'supporter': ch_b.meta['short_url'], 'staked': 500000000, 'percent': 45.4545},
])
async def test_meta_fields_are_translated_to_protobuf(self): async def test_meta_fields_are_translated_to_protobuf(self):
chan_ab = await self.get_claim( chan_ab = await self.get_claim(