forked from LBRYCommunity/lbry-sdk
initial import of canonical url via the new sql wallet server
This commit is contained in:
parent
6b93705599
commit
affa46e0f6
5 changed files with 123 additions and 13 deletions
|
@ -175,6 +175,7 @@ class JSONResponseEncoder(JSONEncoder):
|
|||
})
|
||||
if include_meta:
|
||||
output['meta'] = self.encode_claim_meta(txo.meta)
|
||||
output['canonical_url'] = output['meta'].pop('canonical_url', None)
|
||||
if txo.script.is_claim_name or txo.script.is_update_claim:
|
||||
try:
|
||||
output['value'] = txo.claim
|
||||
|
|
|
@ -20,6 +20,7 @@ class Outputs:
|
|||
|
||||
def _inflate_claim(self, txo, message):
|
||||
txo.meta = {
|
||||
'canonical_url': message.canonical_url,
|
||||
'is_controlling': message.is_controlling,
|
||||
'activation_height': message.activation_height,
|
||||
'effective_amount': message.effective_amount,
|
||||
|
@ -91,6 +92,7 @@ class Outputs:
|
|||
txo_message.height = txo['height']
|
||||
txo_message.tx_hash = txo['txo_hash'][:32]
|
||||
txo_message.nout, = struct.unpack('<I', txo['txo_hash'][32:])
|
||||
txo_message.claim.canonical_url = txo['canonical_url']
|
||||
txo_message.claim.is_controlling = bool(txo['is_controlling'])
|
||||
txo_message.claim.activation_height = txo['activation_height']
|
||||
txo_message.claim.effective_amount = txo['effective_amount']
|
||||
|
|
28
lbrynet/wallet/server/canonical.py
Normal file
28
lbrynet/wallet/server/canonical.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from binascii import hexlify
|
||||
|
||||
|
||||
class FindShortestID:
|
||||
__slots__ = 'short_id', 'new_id'
|
||||
|
||||
def __init__(self):
|
||||
self.short_id = b''
|
||||
self.new_id = None
|
||||
|
||||
def step(self, other_hash, new_hash):
|
||||
if self.new_id is None:
|
||||
self.new_id = hexlify(new_hash[::-1])
|
||||
other_id = hexlify(other_hash[::-1])
|
||||
for i in range(len(self.new_id)):
|
||||
if other_id[i] != self.new_id[i]:
|
||||
if i > len(self.short_id)-1:
|
||||
self.short_id = self.new_id[:i+1]
|
||||
break
|
||||
|
||||
def finalize(self):
|
||||
if self.short_id:
|
||||
return '#'+self.short_id.decode()
|
||||
return ''
|
||||
|
||||
|
||||
def register_canonical_functions(connection):
|
||||
connection.create_aggregate("shortest_id", 2, FindShortestID)
|
|
@ -9,6 +9,7 @@ from torba.client.basedatabase import query, constraints_to_sql
|
|||
|
||||
from lbrynet.schema.url import URL, normalize_name
|
||||
from lbrynet.wallet.transaction import Transaction, Output
|
||||
from lbrynet.wallet.server.canonical import register_canonical_functions
|
||||
from lbrynet.wallet.server.trending import (
|
||||
CREATE_TREND_TABLE, calculate_trending, register_trending_functions
|
||||
)
|
||||
|
@ -66,8 +67,9 @@ class SQLDB:
|
|||
CREATE_CLAIM_TABLE = """
|
||||
create table if not exists claim (
|
||||
claim_hash bytes primary key,
|
||||
normalized text not null,
|
||||
claim_name text not null,
|
||||
normalized text not null,
|
||||
canonical text not null,
|
||||
is_channel bool not null,
|
||||
txo_hash bytes not null,
|
||||
tx_position integer not null,
|
||||
|
@ -150,6 +152,7 @@ class SQLDB:
|
|||
self.db = sqlite3.connect(self._db_path, isolation_level=None, check_same_thread=False)
|
||||
self.db.row_factory = sqlite3.Row
|
||||
self.db.executescript(self.CREATE_TABLES_QUERY)
|
||||
register_canonical_functions(self.db)
|
||||
register_trending_functions(self.db)
|
||||
|
||||
def close(self):
|
||||
|
@ -250,11 +253,19 @@ class SQLDB:
|
|||
self.db.executemany("""
|
||||
INSERT INTO claim (
|
||||
claim_hash, normalized, claim_name, is_channel, txo_hash, tx_position,
|
||||
height, amount, channel_hash, release_time, publish_time, activation_height)
|
||||
height, amount, channel_hash, release_time, publish_time, activation_height,
|
||||
canonical)
|
||||
VALUES (
|
||||
:claim_hash, :normalized, :claim_name, :is_channel, :txo_hash, :tx_position,
|
||||
:height, :amount, :channel_hash, :release_time, :publish_time,
|
||||
CASE WHEN :normalized NOT IN (SELECT normalized FROM claimtrie) THEN :height END
|
||||
CASE WHEN :normalized NOT IN (SELECT normalized FROM claimtrie) THEN :height END,
|
||||
CASE WHEN :channel_hash IS NOT NULL
|
||||
THEN (SELECT canonical FROM claim WHERE claim_hash=:channel_hash)||'/'||
|
||||
:normalized||COALESCE((SELECT shortest_id(claim_hash, :claim_hash)
|
||||
FROM claim WHERE normalized = :normalized), '')
|
||||
ELSE :normalized||COALESCE((SELECT shortest_id(claim_hash, :claim_hash)
|
||||
FROM claim WHERE normalized = :normalized), '')
|
||||
END
|
||||
)""", claims)
|
||||
|
||||
def update_claims(self, txos: Set[Output], header):
|
||||
|
@ -561,7 +572,7 @@ class SQLDB:
|
|||
return self.get_claims(
|
||||
"""
|
||||
claimtrie.claim_hash as is_controlling,
|
||||
claim.claim_hash, claim.txo_hash, claim.height,
|
||||
claim.claim_hash, claim.txo_hash, claim.height, claim.canonical,
|
||||
claim.activation_height, claim.effective_amount, claim.support_amount,
|
||||
claim.trending_group, claim.trending_mixed,
|
||||
claim.trending_local, claim.trending_global,
|
||||
|
@ -614,7 +625,10 @@ class SQLDB:
|
|||
continue
|
||||
channel = None
|
||||
if url.has_channel:
|
||||
matches = self._search(is_controlling=True, **url.channel.to_dict())
|
||||
query = url.channel.to_dict()
|
||||
if set(query) == {'name'}:
|
||||
query['is_controlling'] = True
|
||||
matches = self._search(**query)
|
||||
if matches:
|
||||
channel = matches[0]
|
||||
else:
|
||||
|
@ -624,7 +638,9 @@ class SQLDB:
|
|||
query = url.stream.to_dict()
|
||||
if channel is not None:
|
||||
query['channel_hash'] = channel['claim_hash']
|
||||
matches = self._search(is_controlling=True, **query)
|
||||
if set(query) == {'name'}:
|
||||
query['is_controlling'] = True
|
||||
matches = self._search(**query)
|
||||
if matches:
|
||||
result.append(matches[0])
|
||||
else:
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import unittest
|
||||
from binascii import hexlify
|
||||
import ecdsa
|
||||
import hashlib
|
||||
from binascii import hexlify, unhexlify
|
||||
from torba.client.constants import COIN, NULL_HASH32
|
||||
|
||||
from lbrynet.schema.claim import Claim
|
||||
from lbrynet.wallet.server.db import SQLDB
|
||||
from lbrynet.wallet.server.trending import TRENDING_WINDOW
|
||||
from lbrynet.wallet.server.canonical import FindShortestID
|
||||
from lbrynet.wallet.server.block_processor import Timer
|
||||
from lbrynet.wallet.transaction import Transaction, Input, Output
|
||||
|
||||
|
@ -23,10 +26,6 @@ def get_tx():
|
|||
return Transaction().add_inputs([get_input()])
|
||||
|
||||
|
||||
def claim_id(claim_hash):
|
||||
return hexlify(claim_hash[::-1]).decode()
|
||||
|
||||
|
||||
class OldWalletServerTransaction:
|
||||
def __init__(self, tx):
|
||||
self.tx = tx
|
||||
|
@ -57,7 +56,10 @@ class TestSQLDB(unittest.TestCase):
|
|||
claim = Claim()
|
||||
claim.channel.title = title
|
||||
channel = Output.pay_claim_name_pubkey_hash(amount, name, claim, b'abc')
|
||||
channel.generate_channel_private_key()
|
||||
private_key = ecdsa.SigningKey.from_string(b'c'*32, curve=ecdsa.SECP256k1, hashfunc=hashlib.sha256)
|
||||
channel.private_key = private_key.to_pem().decode()
|
||||
channel.claim.channel.public_key_bytes = private_key.get_verifying_key().to_der()
|
||||
channel.script.generate()
|
||||
return self._make_tx(channel)
|
||||
|
||||
def get_stream(self, title, amount, name='foo'):
|
||||
|
@ -284,8 +286,69 @@ class TestSQLDB(unittest.TestCase):
|
|||
self.get_support(up_biggly, (20+(window*(3 if window == 7 else 1)))*COIN),
|
||||
])
|
||||
results = self.sql._search(order_by=['trending_local'])
|
||||
self.assertEqual([c.claim_id for c in claims], [claim_id(c['claim_hash']) for c in results])
|
||||
self.assertEqual([c.claim_id for c in claims], [hexlify(c['claim_hash'][::-1]).decode() for c in results])
|
||||
self.assertEqual([10, 6, 2, 0, -2], [int(c['trending_local']) for c in results])
|
||||
self.assertEqual([53, 38, -32, 0, -6], [int(c['trending_global']) for c in results])
|
||||
self.assertEqual([4, 4, 2, 0, 1], [int(c['trending_group']) for c in results])
|
||||
self.assertEqual([53, 38, 2, 0, -6], [int(c['trending_mixed']) for c in results])
|
||||
|
||||
@staticmethod
|
||||
def _get_x_with_claim_id_prefix(getter, prefix, cached_iteration=None):
|
||||
iterations = 100
|
||||
for i in range(cached_iteration or 1, iterations):
|
||||
stream = getter(f'claim #{i}', COIN)
|
||||
if stream[0].tx.outputs[0].claim_id.startswith(prefix):
|
||||
print(f'Found "{prefix}" in {i} iterations.')
|
||||
return stream
|
||||
raise ValueError(f'Failed to find "{prefix}" in {iterations} iterations.')
|
||||
|
||||
def get_channel_with_claim_id_prefix(self, prefix, cached_iteration):
|
||||
return self._get_x_with_claim_id_prefix(self.get_channel, prefix, cached_iteration)
|
||||
|
||||
def get_stream_with_claim_id_prefix(self, prefix, cached_iteration):
|
||||
return self._get_x_with_claim_id_prefix(self.get_stream, prefix, cached_iteration)
|
||||
|
||||
def test_canonical_name(self):
|
||||
advance = self.advance
|
||||
tx_abc = self.get_stream_with_claim_id_prefix('abc', 65)
|
||||
tx_ab = self.get_stream_with_claim_id_prefix('ab', 42)
|
||||
tx_a = self.get_stream_with_claim_id_prefix('a', 2)
|
||||
advance(1, [tx_a])
|
||||
advance(2, [tx_ab])
|
||||
advance(3, [tx_abc])
|
||||
r_a, r_ab, r_abc = self.sql._search(order_by=['^height'])
|
||||
self.assertEqual("foo", r_a['canonical'])
|
||||
self.assertEqual(f"foo#ab", r_ab['canonical'])
|
||||
self.assertEqual(f"foo#abc", r_abc['canonical'])
|
||||
|
||||
tx_ab = self.get_channel_with_claim_id_prefix('ab', 72)
|
||||
tx_a = self.get_channel_with_claim_id_prefix('a', 1)
|
||||
advance(4, [tx_a])
|
||||
advance(5, [tx_ab])
|
||||
|
||||
tx_c = self.get_stream_with_claim_id_prefix('c', 2)
|
||||
tx_cd = self.get_stream_with_claim_id_prefix('cd', 2)
|
||||
advance(6, [tx_c])
|
||||
advance(7, [tx_cd])
|
||||
|
||||
r_a, r_ab, r_abc = self.sql._search(order_by=['^height'])
|
||||
self.assertEqual("foo", r_a['canonical'])
|
||||
self.assertEqual(f"foo#ab", r_ab['canonical'])
|
||||
self.assertEqual(f"foo#abc", r_abc['canonical'])
|
||||
|
||||
def test_canonical_find_shortest_id(self):
|
||||
new_hash = unhexlify('abcdef0123456789beef')[::-1]
|
||||
other0 = unhexlify('1bcdef0123456789beef')[::-1]
|
||||
other1 = unhexlify('ab1def0123456789beef')[::-1]
|
||||
other2 = unhexlify('abc1ef0123456789beef')[::-1]
|
||||
other3 = unhexlify('abcdef0123456789bee1')[::-1]
|
||||
f = FindShortestID()
|
||||
self.assertEqual('', f.finalize())
|
||||
f.step(other0, new_hash)
|
||||
self.assertEqual('#a', f.finalize())
|
||||
f.step(other1, new_hash)
|
||||
self.assertEqual('#abc', f.finalize())
|
||||
f.step(other2, new_hash)
|
||||
self.assertEqual('#abcd', f.finalize())
|
||||
f.step(other3, new_hash)
|
||||
self.assertEqual('#abcdef0123456789beef', f.finalize())
|
||||
|
|
Loading…
Reference in a new issue