initial import of canonical url via the new sql wallet server

This commit is contained in:
Lex Berezhny 2019-05-23 23:55:57 -04:00
parent 6b93705599
commit affa46e0f6
5 changed files with 123 additions and 13 deletions

View file

@ -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

View file

@ -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']

View 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)

View file

@ -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:

View file

@ -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())