mirror of
https://github.com/LBRYFoundation/lbry-sdk.git
synced 2025-08-23 17:27:25 +00:00
test_claim_commands.py
This commit is contained in:
parent
e6a9417988
commit
1d31a96c9b
18 changed files with 277 additions and 289 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -11,7 +11,7 @@ lbry.egg-info
|
||||||
__pycache__
|
__pycache__
|
||||||
_trial_temp/
|
_trial_temp/
|
||||||
|
|
||||||
/tests/integration/blockchain/files
|
/tests/integration/commands/files
|
||||||
/tests/.coverage.*
|
/tests/.coverage.*
|
||||||
|
|
||||||
/lbry/blockchain/bin
|
/lbry/blockchain/bin
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
from lbry.wallet.database import constraints_to_sql
|
|
||||||
|
|
||||||
CREATE_FULL_TEXT_SEARCH = """
|
|
||||||
create virtual table if not exists search using fts5(
|
|
||||||
claim_name, channel_name, title, description, author, tags,
|
|
||||||
content=claim, tokenize=porter
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
|
|
||||||
FTS_ORDER_BY = "bm25(search, 4.0, 8.0, 1.0, 0.5, 1.0, 0.5)"
|
|
||||||
|
|
||||||
|
|
||||||
def fts_action_sql(claims=None, action='insert'):
|
|
||||||
select = {
|
|
||||||
'rowid': "claim.rowid",
|
|
||||||
'claim_name': "claim.normalized",
|
|
||||||
'channel_name': "channel.normalized",
|
|
||||||
'title': "claim.title",
|
|
||||||
'description': "claim.description",
|
|
||||||
'author': "claim.author",
|
|
||||||
'tags': "(select group_concat(tag, ' ') from tag where tag.claim_hash=claim.claim_hash)"
|
|
||||||
}
|
|
||||||
if action == 'delete':
|
|
||||||
select['search'] = '"delete"'
|
|
||||||
|
|
||||||
where, values = "", {}
|
|
||||||
if claims:
|
|
||||||
where, values = constraints_to_sql({'claim.claim_hash__in': claims})
|
|
||||||
where = 'WHERE '+where
|
|
||||||
|
|
||||||
return f"""
|
|
||||||
INSERT INTO search ({','.join(select.keys())})
|
|
||||||
SELECT {','.join(select.values())} FROM claim
|
|
||||||
LEFT JOIN claim as channel ON (claim.channel_hash=channel.claim_hash) {where}
|
|
||||||
""", values
|
|
||||||
|
|
||||||
|
|
||||||
def update_full_text_search(action, outputs, db, is_first_sync):
|
|
||||||
if is_first_sync:
|
|
||||||
return
|
|
||||||
if not outputs:
|
|
||||||
return
|
|
||||||
if action in ("before-delete", "before-update"):
|
|
||||||
db.execute(*fts_action_sql(outputs, 'delete'))
|
|
||||||
elif action in ("after-insert", "after-update"):
|
|
||||||
db.execute(*fts_action_sql(outputs, 'insert'))
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Invalid action for updating full text search: '{action}'")
|
|
||||||
|
|
||||||
|
|
||||||
def first_sync_finished(db):
|
|
||||||
db.execute(*fts_action_sql())
|
|
|
@ -191,16 +191,16 @@ def select_claims(cols: List = None, for_count=False, **constraints) -> Select:
|
||||||
if 'public_key_id' in constraints:
|
if 'public_key_id' in constraints:
|
||||||
constraints['public_key_hash'] = (
|
constraints['public_key_hash'] = (
|
||||||
context().ledger.address_to_hash160(constraints.pop('public_key_id')))
|
context().ledger.address_to_hash160(constraints.pop('public_key_id')))
|
||||||
if 'channel_hash' in constraints:
|
if 'channel_id' in constraints:
|
||||||
constraints['channel_hash'] = constraints.pop('channel_hash')
|
channel_id = constraints.pop('channel_id')
|
||||||
if 'channel_ids' in constraints:
|
if channel_id:
|
||||||
channel_ids = constraints.pop('channel_ids')
|
if isinstance(channel_id, str):
|
||||||
if channel_ids:
|
channel_id = [channel_id]
|
||||||
constraints['channel_hash__in'] = {
|
constraints['channel_hash__in'] = {
|
||||||
unhexlify(cid)[::-1] for cid in channel_ids
|
unhexlify(cid)[::-1] for cid in channel_id
|
||||||
}
|
}
|
||||||
if 'not_channel_ids' in constraints:
|
if 'not_channel_id' in constraints:
|
||||||
not_channel_ids = constraints.pop('not_channel_ids')
|
not_channel_ids = constraints.pop('not_channel_id')
|
||||||
if not_channel_ids:
|
if not_channel_ids:
|
||||||
not_channel_ids_binary = {
|
not_channel_ids_binary = {
|
||||||
unhexlify(ncid)[::-1] for ncid in not_channel_ids
|
unhexlify(ncid)[::-1] for ncid in not_channel_ids
|
||||||
|
@ -213,17 +213,18 @@ def select_claims(cols: List = None, for_count=False, **constraints) -> Select:
|
||||||
'signature_valid__is_null': True,
|
'signature_valid__is_null': True,
|
||||||
'channel_hash__not_in': not_channel_ids_binary
|
'channel_hash__not_in': not_channel_ids_binary
|
||||||
}
|
}
|
||||||
if 'signature_valid' in constraints:
|
if 'is_signature_valid' in constraints:
|
||||||
has_channel_signature = constraints.pop('has_channel_signature', False)
|
has_channel_signature = constraints.pop('has_channel_signature', False)
|
||||||
|
is_signature_valid = constraints.pop('is_signature_valid')
|
||||||
if has_channel_signature:
|
if has_channel_signature:
|
||||||
constraints['signature_valid'] = constraints.pop('signature_valid')
|
constraints['is_signature_valid'] = is_signature_valid
|
||||||
else:
|
else:
|
||||||
constraints['null_or_signature__or'] = {
|
constraints['null_or_signature__or'] = {
|
||||||
'signature_valid__is_null': True,
|
'is_signature_valid__is_null': True,
|
||||||
'signature_valid': constraints.pop('signature_valid')
|
'is_signature_valid': is_signature_valid
|
||||||
}
|
}
|
||||||
elif constraints.pop('has_channel_signature', False):
|
elif constraints.pop('has_channel_signature', False):
|
||||||
constraints['signature_valid__is_not_null'] = True
|
constraints['is_signature_valid__is_not_null'] = True
|
||||||
|
|
||||||
if 'txid' in constraints:
|
if 'txid' in constraints:
|
||||||
tx_hash = unhexlify(constraints.pop('txid'))[::-1]
|
tx_hash = unhexlify(constraints.pop('txid'))[::-1]
|
||||||
|
@ -261,7 +262,7 @@ def select_claims(cols: List = None, for_count=False, **constraints) -> Select:
|
||||||
constraints["search"] = constraints.pop("text")
|
constraints["search"] = constraints.pop("text")
|
||||||
|
|
||||||
return query(
|
return query(
|
||||||
[Claim],
|
[Claim, TXO],
|
||||||
select(*cols)
|
select(*cols)
|
||||||
.select_from(
|
.select_from(
|
||||||
Claim.join(TXO).join(TX)
|
Claim.join(TXO).join(TX)
|
||||||
|
@ -276,18 +277,47 @@ def protobuf_search_claims(**constraints) -> str:
|
||||||
|
|
||||||
|
|
||||||
def search_claims(**constraints) -> Tuple[List[Output], Optional[int], Optional[Censor]]:
|
def search_claims(**constraints) -> Tuple[List[Output], Optional[int], Optional[Censor]]:
|
||||||
|
ctx = context()
|
||||||
|
search_censor = ctx.get_search_censor()
|
||||||
|
|
||||||
total = None
|
total = None
|
||||||
if constraints.pop('include_total', False):
|
if constraints.pop('include_total', False):
|
||||||
total = search_claim_count(**constraints)
|
total = search_claim_count(**constraints)
|
||||||
|
|
||||||
constraints['offset'] = abs(constraints.get('offset', 0))
|
constraints['offset'] = abs(constraints.get('offset', 0))
|
||||||
constraints['limit'] = min(abs(constraints.get('limit', 10)), 50)
|
constraints['limit'] = min(abs(constraints.get('limit', 10)), 50)
|
||||||
ctx = context()
|
|
||||||
search_censor = ctx.get_search_censor()
|
channel_url = constraints.pop('channel', None)
|
||||||
rows = context().fetchall(select_claims(**constraints))
|
if channel_url:
|
||||||
|
from .resolve import resolve_url
|
||||||
|
channel = resolve_url(channel_url)
|
||||||
|
if isinstance(channel, Output):
|
||||||
|
constraints['channel_hash'] = channel.claim_hash
|
||||||
|
else:
|
||||||
|
return [], total, search_censor
|
||||||
|
|
||||||
|
rows = ctx.fetchall(select_claims(**constraints))
|
||||||
txos = rows_to_txos(rows, include_tx=False)
|
txos = rows_to_txos(rows, include_tx=False)
|
||||||
|
annotate_with_channels(txos)
|
||||||
return txos, total, search_censor
|
return txos, total, search_censor
|
||||||
|
|
||||||
|
|
||||||
|
def annotate_with_channels(txos):
|
||||||
|
channel_hashes = set()
|
||||||
|
for txo in txos:
|
||||||
|
if txo.can_decode_claim and txo.claim.is_signed:
|
||||||
|
channel_hashes.add(txo.claim.signing_channel_hash)
|
||||||
|
if channel_hashes:
|
||||||
|
rows = context().fetchall(select_claims(claim_hash__in=channel_hashes))
|
||||||
|
channels = {
|
||||||
|
txo.claim_hash: txo for txo in
|
||||||
|
rows_to_txos(rows, include_tx=False)
|
||||||
|
}
|
||||||
|
for txo in txos:
|
||||||
|
if txo.can_decode_claim and txo.claim.is_signed:
|
||||||
|
txo.channel = channels.get(txo.claim.signing_channel_hash, None)
|
||||||
|
|
||||||
|
|
||||||
def search_claim_count(**constraints) -> int:
|
def search_claim_count(**constraints) -> int:
|
||||||
constraints.pop('offset', None)
|
constraints.pop('offset', None)
|
||||||
constraints.pop('limit', None)
|
constraints.pop('limit', None)
|
||||||
|
@ -305,9 +335,9 @@ END
|
||||||
|
|
||||||
|
|
||||||
def _apply_constraints_for_array_attributes(constraints, attr, cleaner, for_count=False):
|
def _apply_constraints_for_array_attributes(constraints, attr, cleaner, for_count=False):
|
||||||
any_items = set(cleaner(constraints.pop(f'any_{attr}s', []))[:ATTRIBUTE_ARRAY_MAX_LENGTH])
|
any_items = set(cleaner(constraints.pop(f'any_{attr}', []))[:ATTRIBUTE_ARRAY_MAX_LENGTH])
|
||||||
all_items = set(cleaner(constraints.pop(f'all_{attr}s', []))[:ATTRIBUTE_ARRAY_MAX_LENGTH])
|
all_items = set(cleaner(constraints.pop(f'all_{attr}', []))[:ATTRIBUTE_ARRAY_MAX_LENGTH])
|
||||||
not_items = set(cleaner(constraints.pop(f'not_{attr}s', []))[:ATTRIBUTE_ARRAY_MAX_LENGTH])
|
not_items = set(cleaner(constraints.pop(f'not_{attr}', []))[:ATTRIBUTE_ARRAY_MAX_LENGTH])
|
||||||
|
|
||||||
all_items = {item for item in all_items if item not in not_items}
|
all_items = {item for item in all_items if item not in not_items}
|
||||||
any_items = {item for item in any_items if item not in not_items}
|
any_items = {item for item in any_items if item not in not_items}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from typing import Tuple, List, Optional, Union
|
from typing import Tuple, List, Optional, Union
|
||||||
|
|
||||||
from sqlalchemy import union, func, text, between, distinct, case
|
from sqlalchemy import union, func, text, between, distinct, case, false
|
||||||
from sqlalchemy.future import select, Select
|
from sqlalchemy.future import select, Select
|
||||||
|
|
||||||
from ...blockchain.transaction import (
|
from ...blockchain.transaction import (
|
||||||
|
@ -372,7 +372,7 @@ def select_txos(
|
||||||
)
|
)
|
||||||
joins = TXO.join(TX)
|
joins = TXO.join(TX)
|
||||||
if constraints.pop('is_spent', None) is False:
|
if constraints.pop('is_spent', None) is False:
|
||||||
s = s.where((TXO.c.spent_height == 0) & (TXO.c.is_reserved == False))
|
s = s.where((TXO.c.spent_height == 0) & (TXO.c.is_reserved == false()))
|
||||||
if include_is_my_input:
|
if include_is_my_input:
|
||||||
joins = joins.join(TXI, (TXI.c.position == 0) & (TXI.c.tx_hash == TXO.c.tx_hash), isouter=True)
|
joins = joins.join(TXI, (TXI.c.position == 0) & (TXI.c.tx_hash == TXO.c.tx_hash), isouter=True)
|
||||||
if claim_id_not_in_claim_table:
|
if claim_id_not_in_claim_table:
|
||||||
|
@ -534,7 +534,7 @@ def get_balance(account_ids):
|
||||||
else:
|
else:
|
||||||
txo_address_check = TXO.c.address.in_(my_addresses)
|
txo_address_check = TXO.c.address.in_(my_addresses)
|
||||||
txi_address_check = TXI.c.address.in_(my_addresses)
|
txi_address_check = TXI.c.address.in_(my_addresses)
|
||||||
query = (
|
s: Select = (
|
||||||
select(
|
select(
|
||||||
func.coalesce(func.sum(TXO.c.amount), 0).label("total"),
|
func.coalesce(func.sum(TXO.c.amount), 0).label("total"),
|
||||||
func.coalesce(func.sum(case(
|
func.coalesce(func.sum(case(
|
||||||
|
@ -557,7 +557,7 @@ def get_balance(account_ids):
|
||||||
TXO.join(TXI, (TXI.c.position == 0) & (TXI.c.tx_hash == TXO.c.tx_hash), isouter=True)
|
TXO.join(TXI, (TXI.c.position == 0) & (TXI.c.tx_hash == TXO.c.tx_hash), isouter=True)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
result = ctx.fetchone(query)
|
result = ctx.fetchone(s)
|
||||||
return {
|
return {
|
||||||
"total": result["total"],
|
"total": result["total"],
|
||||||
"available": result["total"] - result["reserved"],
|
"available": result["total"] - result["reserved"],
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from itertools import islice
|
from itertools import islice
|
||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
|
|
||||||
from sqlalchemy import text, and_
|
from sqlalchemy import text, and_, or_
|
||||||
from sqlalchemy.sql.expression import Select, FunctionElement
|
from sqlalchemy.sql.expression import Select, FunctionElement
|
||||||
from sqlalchemy.types import Numeric
|
from sqlalchemy.types import Numeric
|
||||||
from sqlalchemy.ext.compiler import compiles
|
from sqlalchemy.ext.compiler import compiles
|
||||||
|
@ -98,9 +98,7 @@ def query(table, s: Select, **constraints) -> Select:
|
||||||
s = s.where(in_account_ids(account_ids))
|
s = s.where(in_account_ids(account_ids))
|
||||||
|
|
||||||
if constraints:
|
if constraints:
|
||||||
s = s.where(
|
s = s.where(and_(*constraints_to_clause(table, constraints)))
|
||||||
constraints_to_clause(table, constraints)
|
|
||||||
)
|
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
@ -148,6 +146,9 @@ def constraints_to_clause(tables, constraints):
|
||||||
raise ValueError(f"{col} requires a list, set or string as constraint value.")
|
raise ValueError(f"{col} requires a list, set or string as constraint value.")
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
elif key.endswith('__or'):
|
||||||
|
clause.append(or_(*constraints_to_clause(tables, constraint)))
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
col, op = key, '__eq__'
|
col, op = key, '__eq__'
|
||||||
attr = None
|
attr = None
|
||||||
|
@ -170,4 +171,4 @@ def constraints_to_clause(tables, constraints):
|
||||||
if attr is None:
|
if attr is None:
|
||||||
raise ValueError(f"Attribute '{col}' not found on tables: {', '.join([t.name for t in tables])}.")
|
raise ValueError(f"Attribute '{col}' not found on tables: {', '.join([t.name for t in tables])}.")
|
||||||
clause.append(getattr(attr, op)(constraint))
|
clause.append(getattr(attr, op)(constraint))
|
||||||
return and_(*clause)
|
return clause
|
||||||
|
|
|
@ -1098,8 +1098,9 @@ class API:
|
||||||
raise ValueError("--outputs must be an integer.")
|
raise ValueError("--outputs must be an integer.")
|
||||||
if everything and outputs > 1:
|
if everything and outputs > 1:
|
||||||
raise ValueError("Using --everything along with --outputs is not supported.")
|
raise ValueError("Using --everything along with --outputs is not supported.")
|
||||||
return await from_account.fund(
|
return await wallet.fund(
|
||||||
to_account=to_account, amount=amount, everything=everything,
|
from_account=from_account, to_account=to_account,
|
||||||
|
amount=amount, everything=everything,
|
||||||
outputs=outputs, broadcast=broadcast
|
outputs=outputs, broadcast=broadcast
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1518,7 +1519,7 @@ class API:
|
||||||
claim_type: str = None, # claim type: channel, stream, repost, collection
|
claim_type: str = None, # claim type: channel, stream, repost, collection
|
||||||
include_purchase_receipt=False, # lookup and include a receipt if this wallet has purchased the claim
|
include_purchase_receipt=False, # lookup and include a receipt if this wallet has purchased the claim
|
||||||
include_is_my_output=False, # lookup and include a boolean indicating if claim being resolved is yours
|
include_is_my_output=False, # lookup and include a boolean indicating if claim being resolved is yours
|
||||||
is_controlling=False, # winning claims of their respective name
|
is_controlling: bool = None, # winning claims of their respective name
|
||||||
activation_height: int = None, # height at which claim starts competing for name
|
activation_height: int = None, # height at which claim starts competing for name
|
||||||
# (supports equality constraints)
|
# (supports equality constraints)
|
||||||
expiration_height: int = None, # height at which claim will expire (supports equality constraints)
|
expiration_height: int = None, # height at which claim will expire (supports equality constraints)
|
||||||
|
@ -1578,16 +1579,23 @@ class API:
|
||||||
claim_filter_dict, kwargs = pop_kwargs('claim_filter', extract_claim_filter(
|
claim_filter_dict, kwargs = pop_kwargs('claim_filter', extract_claim_filter(
|
||||||
**claim_filter_and_stream_filter_and_pagination_kwargs
|
**claim_filter_and_stream_filter_and_pagination_kwargs
|
||||||
))
|
))
|
||||||
|
stream_filter_dict, kwargs = pop_kwargs('stream_filter', extract_stream_filter(**kwargs))
|
||||||
pagination, kwargs = pop_kwargs('pagination', extract_pagination(**kwargs))
|
pagination, kwargs = pop_kwargs('pagination', extract_pagination(**kwargs))
|
||||||
|
assert_consumed_kwargs(kwargs)
|
||||||
wallet = self.wallets.get_or_default(wallet_id)
|
wallet = self.wallets.get_or_default(wallet_id)
|
||||||
# if {'claim_id', 'claim_ids'}.issubset(kwargs):
|
# if {'claim_id', 'claim_ids'}.issubset(kwargs):
|
||||||
# raise ValueError("Only 'claim_id' or 'claim_ids' is allowed, not both.")
|
# raise ValueError("Only 'claim_id' or 'claim_ids' is allowed, not both.")
|
||||||
# if kwargs.pop('valid_channel_signature', False):
|
if stream_filter_dict.pop('valid_channel_signature', False):
|
||||||
# kwargs['signature_valid'] = 1
|
stream_filter_dict['is_signature_valid'] = True
|
||||||
# if kwargs.pop('invalid_channel_signature', False):
|
if stream_filter_dict.pop('invalid_channel_signature', False):
|
||||||
# kwargs['signature_valid'] = 0
|
stream_filter_dict['is_signature_valid'] = False
|
||||||
|
if is_controlling is not None:
|
||||||
|
claim_filter_dict["is_controlling"] = is_controlling
|
||||||
|
if public_key_id is not None:
|
||||||
|
claim_filter_dict["public_key_id"] = public_key_id
|
||||||
page_num = abs(pagination['page'] or 1)
|
page_num = abs(pagination['page'] or 1)
|
||||||
page_size = min(abs(pagination['page_size'] or DEFAULT_PAGE_SIZE), 50)
|
page_size = min(abs(pagination['page_size'] or DEFAULT_PAGE_SIZE), 50)
|
||||||
|
claim_filter_dict.update(stream_filter_dict)
|
||||||
claim_filter_dict.update({
|
claim_filter_dict.update({
|
||||||
'offset': page_size * (page_num - 1), 'limit': page_size,
|
'offset': page_size * (page_num - 1), 'limit': page_size,
|
||||||
'include_total': pagination['include_total'],
|
'include_total': pagination['include_total'],
|
||||||
|
@ -1701,31 +1709,18 @@ class API:
|
||||||
{kwargs}
|
{kwargs}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
wallet = self.wallets.get_or_default(wallet_id)
|
abandon_dict, kwargs = pop_kwargs('abandon', extract_abandon(**abandon_and_tx_kwargs))
|
||||||
assert not wallet.is_locked, "Cannot spend funds with locked wallet, unlock first."
|
tx_dict, kwargs = pop_kwargs('tx', extract_tx(**kwargs))
|
||||||
if account_id:
|
assert_consumed_kwargs(kwargs)
|
||||||
account = wallet.get_account_or_error(account_id)
|
wallet = self.wallets.get_or_default_for_spending(tx_dict.pop('wallet_id'))
|
||||||
accounts = [account]
|
funding_accounts = wallet.accounts.get_or_all(tx_dict.pop('fund_account_id'))
|
||||||
else:
|
change_account = wallet.accounts.get_or_default(tx_dict.pop('change_account_id'))
|
||||||
account = wallet.default_account
|
tx = await wallet.channels.delete(
|
||||||
accounts = wallet.accounts
|
claim_id=abandon_dict.pop('claim_id'),
|
||||||
|
txid=abandon_dict.pop('txid'),
|
||||||
if txid is not None and nout is not None:
|
nout=abandon_dict.pop('nout'),
|
||||||
claims = await self.ledger.get_claims(
|
funding_accounts=funding_accounts,
|
||||||
wallet=wallet, accounts=accounts, tx_hash=unhexlify(txid)[::-1], position=nout
|
change_account=change_account
|
||||||
)
|
|
||||||
elif claim_id is not None:
|
|
||||||
claims = await self.ledger.get_claims(
|
|
||||||
wallet=wallet, accounts=accounts, claim_id=claim_id
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise Exception('Must specify claim_id, or txid and nout')
|
|
||||||
|
|
||||||
if not claims:
|
|
||||||
raise Exception('No claim found for the specified claim_id or txid:nout')
|
|
||||||
|
|
||||||
tx = await Transaction.create(
|
|
||||||
[Input.spend(txo) for txo in claims], [], [account], account
|
|
||||||
)
|
)
|
||||||
await self.service.maybe_broadcast_or_release(tx, **tx_dict)
|
await self.service.maybe_broadcast_or_release(tx, **tx_dict)
|
||||||
return tx
|
return tx
|
||||||
|
@ -2078,34 +2073,20 @@ class API:
|
||||||
{kwargs}
|
{kwargs}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
wallet = self.wallets.get_or_default(wallet_id)
|
abandon_dict, kwargs = pop_kwargs('abandon', extract_abandon(**abandon_and_tx_kwargs))
|
||||||
assert not wallet.is_locked, "Cannot spend funds with locked wallet, unlock first."
|
tx_dict, kwargs = pop_kwargs('tx', extract_tx(**kwargs))
|
||||||
if account_id:
|
assert_consumed_kwargs(kwargs)
|
||||||
account = wallet.get_account_or_error(account_id)
|
wallet = self.wallets.get_or_default_for_spending(tx_dict.pop('wallet_id'))
|
||||||
accounts = [account]
|
funding_accounts = wallet.accounts.get_or_all(tx_dict.pop('fund_account_id'))
|
||||||
else:
|
change_account = wallet.accounts.get_or_default(tx_dict.pop('change_account_id'))
|
||||||
account = wallet.default_account
|
tx = await wallet.streams.delete(
|
||||||
accounts = wallet.accounts
|
claim_id=abandon_dict.pop('claim_id'),
|
||||||
|
txid=abandon_dict.pop('txid'),
|
||||||
if txid is not None and nout is not None:
|
nout=abandon_dict.pop('nout'),
|
||||||
claims = await self.ledger.get_claims(
|
funding_accounts=funding_accounts,
|
||||||
wallet=wallet, accounts=accounts, tx_hash=unhexlify(txid)[::-1], position=nout
|
change_account=change_account
|
||||||
)
|
|
||||||
elif claim_id is not None:
|
|
||||||
claims = await self.ledger.get_claims(
|
|
||||||
wallet=wallet, accounts=accounts, claim_id=claim_id
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise Exception('Must specify claim_id, or txid and nout')
|
|
||||||
|
|
||||||
if not claims:
|
|
||||||
raise Exception('No claim found for the specified claim_id or txid:nout')
|
|
||||||
|
|
||||||
tx = await Transaction.create(
|
|
||||||
[Input.spend(txo) for txo in claims], [], accounts, account
|
|
||||||
)
|
)
|
||||||
|
await self.service.maybe_broadcast_or_release(tx, **tx_dict)
|
||||||
await self.service.maybe_broadcast_or_release(tx, tx_dict)
|
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
async def stream_list(
|
async def stream_list(
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import os
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Optional, Tuple, NamedTuple, Dict
|
from typing import List, Optional, NamedTuple, Dict
|
||||||
|
|
||||||
from lbry.db import Database, Result
|
from lbry.db import Database, Result
|
||||||
from lbry.db.constants import TXO_TYPES
|
from lbry.db.constants import TXO_TYPES
|
||||||
from lbry.schema.result import Censor
|
|
||||||
from lbry.blockchain.transaction import Transaction, Output
|
from lbry.blockchain.transaction import Transaction, Output
|
||||||
from lbry.blockchain.ledger import Ledger
|
from lbry.blockchain.ledger import Ledger
|
||||||
from lbry.wallet import WalletManager
|
from lbry.wallet import WalletManager
|
||||||
|
|
|
@ -177,7 +177,8 @@ class Daemon:
|
||||||
subscribers = self.app["subscriptions"][event_name]["subscribers"]
|
subscribers = self.app["subscriptions"][event_name]["subscribers"]
|
||||||
subscribers.add(web_socket)
|
subscribers.add(web_socket)
|
||||||
|
|
||||||
def broadcast_event(self, event_name, subscribers, payload):
|
@staticmethod
|
||||||
|
def broadcast_event(event_name, subscribers, payload):
|
||||||
for web_socket in subscribers:
|
for web_socket in subscribers:
|
||||||
asyncio.create_task(web_socket.send_json({
|
asyncio.create_task(web_socket.send_json({
|
||||||
'event': event_name, 'payload': payload
|
'event': event_name, 'payload': payload
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Optional, List, Dict
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
|
|
||||||
from lbry.blockchain.lbrycrd import Lbrycrd
|
from lbry.blockchain import Ledger, Transaction
|
||||||
from lbry.blockchain.sync import BlockchainSync
|
from lbry.event import BroadcastSubscription
|
||||||
from lbry.blockchain.ledger import Ledger
|
|
||||||
from lbry.blockchain.transaction import Transaction
|
|
||||||
|
|
||||||
from .base import Service, Sync
|
from .base import Service, Sync
|
||||||
from .api import Client as APIClient
|
from .api import Client as APIClient
|
||||||
|
@ -24,27 +23,17 @@ class NoSync(Sync):
|
||||||
self.on_mempool = client.get_event_stream('blockchain.mempool')
|
self.on_mempool = client.get_event_stream('blockchain.mempool')
|
||||||
self.on_mempool_subscription: Optional[BroadcastSubscription] = None
|
self.on_mempool_subscription: Optional[BroadcastSubscription] = None
|
||||||
|
|
||||||
async def wait_for_client_ready(self):
|
|
||||||
await self.client.connect()
|
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
self.db.stop_event.clear()
|
pass
|
||||||
await self.wait_for_client_ready()
|
|
||||||
self.advance_loop_task = asyncio.create_task(self.advance())
|
|
||||||
await self.advance_loop_task
|
|
||||||
await self.client.subscribe()
|
|
||||||
self.advance_loop_task = asyncio.create_task(self.advance_loop())
|
|
||||||
self.on_block_subscription = self.on_block.listen(
|
|
||||||
lambda e: self.on_block_event.set()
|
|
||||||
)
|
|
||||||
self.on_mempool_subscription = self.on_mempool.listen(
|
|
||||||
lambda e: self.on_mempool_event.set()
|
|
||||||
)
|
|
||||||
await self.download_filters()
|
|
||||||
await self.download_headers()
|
|
||||||
|
|
||||||
async def stop(self):
|
async def stop(self):
|
||||||
await self.client.disconnect()
|
pass
|
||||||
|
|
||||||
|
async def get_block_headers(self, start_height: int, end_height: int = None):
|
||||||
|
return await self.db.get_block_headers(start_height, end_height)
|
||||||
|
|
||||||
|
async def get_best_block_height(self) -> int:
|
||||||
|
return await self.db.get_best_block_height()
|
||||||
|
|
||||||
|
|
||||||
class FullEndpoint(Service):
|
class FullEndpoint(Service):
|
||||||
|
@ -59,3 +48,36 @@ class FullEndpoint(Service):
|
||||||
f"http://{ledger.conf.full_nodes[0][0]}:{ledger.conf.full_nodes[0][1]}/api"
|
f"http://{ledger.conf.full_nodes[0][0]}:{ledger.conf.full_nodes[0][1]}/api"
|
||||||
)
|
)
|
||||||
self.sync = NoSync(self, self.client)
|
self.sync = NoSync(self, self.client)
|
||||||
|
|
||||||
|
async def get_block_headers(self, first, last=None):
|
||||||
|
return await self.db.get_block_headers(first, last)
|
||||||
|
|
||||||
|
async def get_address_filters(self, start_height: int, end_height: int = None, granularity: int = 0):
|
||||||
|
return await self.db.get_filters(
|
||||||
|
start_height=start_height, end_height=end_height, granularity=granularity
|
||||||
|
)
|
||||||
|
|
||||||
|
async def search_transactions(self, txids):
|
||||||
|
tx_hashes = [unhexlify(txid)[::-1] for txid in txids]
|
||||||
|
return {
|
||||||
|
hexlify(tx['tx_hash'][::-1]).decode(): hexlify(tx['raw']).decode()
|
||||||
|
for tx in await self.db.get_transactions(tx_hashes=tx_hashes)
|
||||||
|
}
|
||||||
|
|
||||||
|
async def broadcast(self, tx):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def wait(self, tx: Transaction, height=-1, timeout=1):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def resolve(self, urls, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def search_claims(self, accounts, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def search_supports(self, accounts, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def sum_supports(self, claim_hash: bytes, include_channel_content=False) -> List[Dict]:
|
||||||
|
return await self.db.sum_supports(claim_hash, include_channel_content)
|
||||||
|
|
|
@ -40,7 +40,7 @@ class FullNode(Service):
|
||||||
return 'everything is wonderful'
|
return 'everything is wonderful'
|
||||||
|
|
||||||
async def get_block_headers(self, first, last=None):
|
async def get_block_headers(self, first, last=None):
|
||||||
return await self.db.get_blocks(first, last)
|
return await self.db.get_block_headers(first, last)
|
||||||
|
|
||||||
async def get_address_filters(self, start_height: int, end_height: int = None, granularity: int = 0):
|
async def get_address_filters(self, start_height: int, end_height: int = None, granularity: int = 0):
|
||||||
return await self.db.get_filters(
|
return await self.db.get_filters(
|
||||||
|
|
|
@ -300,13 +300,19 @@ class JSONResponseEncoder(JSONEncoder):
|
||||||
output['purchase_receipt'] = self.encode_output(txo.purchase_receipt)
|
output['purchase_receipt'] = self.encode_output(txo.purchase_receipt)
|
||||||
if txo.claim.is_channel:
|
if txo.claim.is_channel:
|
||||||
output['has_signing_key'] = txo.has_private_key
|
output['has_signing_key'] = txo.has_private_key
|
||||||
if check_signature and txo.claim.is_signed:
|
if check_signature and txo.claim.is_signed and 'is_signature_valid' in txo.meta:
|
||||||
if txo.channel is not None:
|
if txo.channel is not None:
|
||||||
output['signing_channel'] = self.encode_output(txo.channel)
|
output['signing_channel'] = self.encode_output(txo.channel)
|
||||||
output['is_channel_signature_valid'] = txo.is_signed_by(txo.channel, self.service.ledger)
|
|
||||||
else:
|
else:
|
||||||
output['signing_channel'] = {'channel_id': txo.claim.signing_channel_id}
|
output['signing_channel'] = {'channel_id': txo.claim.signing_channel_id}
|
||||||
output['is_channel_signature_valid'] = False
|
output['is_channel_signature_valid'] = txo.meta.get('is_signature_valid', False)
|
||||||
|
# if check_signature and txo.claim.is_signed:
|
||||||
|
# if txo.channel is not None:
|
||||||
|
# output['signing_channel'] = self.encode_output(txo.channel)
|
||||||
|
# output['is_channel_signature_valid'] = txo.is_signed_by(txo.channel, self.service.ledger)
|
||||||
|
# else:
|
||||||
|
# output['signing_channel'] = {'channel_id': txo.claim.signing_channel_id}
|
||||||
|
# output['is_channel_signature_valid'] = txo.meta.get('is_signature_valid', False)
|
||||||
except DecodeError:
|
except DecodeError:
|
||||||
pass
|
pass
|
||||||
return output
|
return output
|
||||||
|
|
|
@ -1,19 +1,11 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Dict
|
from typing import Dict
|
||||||
#from io import StringIO
|
from typing import List, Optional, NamedTuple
|
||||||
#from functools import partial
|
from binascii import unhexlify
|
||||||
#from operator import itemgetter
|
|
||||||
from collections import defaultdict
|
|
||||||
#from binascii import hexlify, unhexlify
|
|
||||||
from typing import List, Optional, DefaultDict, NamedTuple
|
|
||||||
|
|
||||||
#from lbry.crypto.hash import double_sha256, sha256
|
|
||||||
|
|
||||||
from lbry.tasks import TaskGroup
|
|
||||||
from lbry.blockchain import Transaction
|
|
||||||
from lbry.blockchain.block import Block, get_address_filter
|
from lbry.blockchain.block import Block, get_address_filter
|
||||||
from lbry.event import BroadcastSubscription, EventController
|
from lbry.event import BroadcastSubscription
|
||||||
from lbry.wallet.account import AddressManager
|
from lbry.wallet.account import AddressManager
|
||||||
from lbry.blockchain import Ledger, Transaction
|
from lbry.blockchain import Ledger, Transaction
|
||||||
from lbry.db import Database
|
from lbry.db import Database
|
||||||
|
@ -119,30 +111,31 @@ class FilterManager:
|
||||||
async def download(self):
|
async def download(self):
|
||||||
filters_response = await self.client.get_address_filters(0, 500)
|
filters_response = await self.client.get_address_filters(0, 500)
|
||||||
filters = await filters_response.first
|
filters = await filters_response.first
|
||||||
|
address = None
|
||||||
address_array = [bytearray(self.client.ledger.address_to_hash160(address))]
|
address_array = [bytearray(self.client.ledger.address_to_hash160(address))]
|
||||||
for filter in filters:
|
for address_filter in filters:
|
||||||
print(filter)
|
print(address_filter)
|
||||||
filter = get_address_filter(unhexlify(filter['filter']))
|
address_filter = get_address_filter(unhexlify(address_filter['filter']))
|
||||||
print(filter.MatchAny(address_array))
|
print(address_filter.MatchAny(address_array))
|
||||||
|
|
||||||
|
|
||||||
address_array = [
|
# address_array = [
|
||||||
bytearray(a['address'].encode())
|
# bytearray(a['address'].encode())
|
||||||
for a in await self.service.db.get_all_addresses()
|
# for a in await self.service.db.get_all_addresses()
|
||||||
]
|
# ]
|
||||||
block_filters = await self.service.get_block_address_filters()
|
# block_filters = await self.service.get_block_address_filters()
|
||||||
for block_hash, block_filter in block_filters.items():
|
# for block_hash, block_filter in block_filters.items():
|
||||||
bf = get_address_filter(block_filter)
|
# bf = get_address_filter(block_filter)
|
||||||
if bf.MatchAny(address_array):
|
# if bf.MatchAny(address_array):
|
||||||
print(f'match: {block_hash} - {block_filter}')
|
# print(f'match: {block_hash} - {block_filter}')
|
||||||
tx_filters = await self.service.get_transaction_address_filters(block_hash=block_hash)
|
# tx_filters = await self.service.get_transaction_address_filters(block_hash=block_hash)
|
||||||
for txid, tx_filter in tx_filters.items():
|
# for txid, tx_filter in tx_filters.items():
|
||||||
tf = get_address_filter(tx_filter)
|
# tf = get_address_filter(tx_filter)
|
||||||
if tf.MatchAny(address_array):
|
# if tf.MatchAny(address_array):
|
||||||
print(f' match: {txid} - {tx_filter}')
|
# print(f' match: {txid} - {tx_filter}')
|
||||||
txs = await self.service.search_transactions([txid])
|
# txs = await self.service.search_transactions([txid])
|
||||||
tx = Transaction(unhexlify(txs[txid]))
|
# tx = Transaction(unhexlify(txs[txid]))
|
||||||
await self.service.db.insert_transaction(tx)
|
# await self.service.db.insert_transaction(tx)
|
||||||
|
|
||||||
async def get_filters(self, start_height, end_height, granularity):
|
async def get_filters(self, start_height, end_height, granularity):
|
||||||
return await self.client.address_filter(
|
return await self.client.address_filter(
|
||||||
|
@ -537,4 +530,4 @@ class FastSync(Sync):
|
||||||
# for account in self.accounts:
|
# for account in self.accounts:
|
||||||
# if account.id == details['account']:
|
# if account.id == details['account']:
|
||||||
# return account.address_managers[details['chain']]
|
# return account.address_managers[details['chain']]
|
||||||
# return None
|
# return None
|
||||||
|
|
|
@ -515,6 +515,9 @@ class IntegrationTestCase(AsyncioTestCase):
|
||||||
lambda e: e.tx.hash == tx_hash
|
lambda e: e.tx.hash == tx_hash
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def on_transaction_dict(self, tx):
|
||||||
|
await self.service.wait(Transaction(unhexlify(tx['hex'])))
|
||||||
|
|
||||||
def on_address_update(self, address):
|
def on_address_update(self, address):
|
||||||
return self.ledger.on_transaction.where(
|
return self.ledger.on_transaction.where(
|
||||||
lambda e: e.address == address
|
lambda e: e.address == address
|
||||||
|
@ -605,9 +608,6 @@ class CommandTestCase(IntegrationTestCase):
|
||||||
await self.on_transaction_id(txid, ledger)
|
await self.on_transaction_id(txid, ledger)
|
||||||
return txid
|
return txid
|
||||||
|
|
||||||
async def on_transaction_dict(self, tx):
|
|
||||||
await self.ledger.wait(Transaction(unhexlify(tx['hex'])))
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all_addresses(tx):
|
def get_all_addresses(tx):
|
||||||
addresses = set()
|
addresses = set()
|
||||||
|
@ -672,9 +672,6 @@ class CommandTestCase(IntegrationTestCase):
|
||||||
async def account_remove(self, *args, **kwargs):
|
async def account_remove(self, *args, **kwargs):
|
||||||
return await self.out(self.api.account_remove(*args, **kwargs))
|
return await self.out(self.api.account_remove(*args, **kwargs))
|
||||||
|
|
||||||
async def account_send(self, *args, **kwargs):
|
|
||||||
return await self.out(self.api.account_send(*args, **kwargs))
|
|
||||||
|
|
||||||
async def account_balance(self, *args, **kwargs):
|
async def account_balance(self, *args, **kwargs):
|
||||||
return await self.out(self.api.account_balance(*args, **kwargs))
|
return await self.out(self.api.account_balance(*args, **kwargs))
|
||||||
|
|
||||||
|
@ -716,8 +713,6 @@ class CommandTestCase(IntegrationTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
async def stream_abandon(self, *args, confirm=True, **kwargs):
|
async def stream_abandon(self, *args, confirm=True, **kwargs):
|
||||||
if 'blocking' not in kwargs:
|
|
||||||
kwargs['blocking'] = False
|
|
||||||
return await self.confirm_and_render(
|
return await self.confirm_and_render(
|
||||||
self.api.stream_abandon(*args, **kwargs), confirm
|
self.api.stream_abandon(*args, **kwargs), confirm
|
||||||
)
|
)
|
||||||
|
@ -743,8 +738,6 @@ class CommandTestCase(IntegrationTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
async def channel_abandon(self, *args, confirm=True, **kwargs):
|
async def channel_abandon(self, *args, confirm=True, **kwargs):
|
||||||
if 'blocking' not in kwargs:
|
|
||||||
kwargs['blocking'] = False
|
|
||||||
return await self.confirm_and_render(
|
return await self.confirm_and_render(
|
||||||
self.api.channel_abandon(*args, **kwargs), confirm
|
self.api.channel_abandon(*args, **kwargs), confirm
|
||||||
)
|
)
|
||||||
|
@ -762,8 +755,6 @@ class CommandTestCase(IntegrationTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
async def collection_abandon(self, *args, confirm=True, **kwargs):
|
async def collection_abandon(self, *args, confirm=True, **kwargs):
|
||||||
if 'blocking' not in kwargs:
|
|
||||||
kwargs['blocking'] = False
|
|
||||||
return await self.confirm_and_render(
|
return await self.confirm_and_render(
|
||||||
self.api.stream_abandon(*args, **kwargs), confirm
|
self.api.stream_abandon(*args, **kwargs), confirm
|
||||||
)
|
)
|
||||||
|
@ -783,11 +774,6 @@ class CommandTestCase(IntegrationTestCase):
|
||||||
self.api.account_fund(*args, **kwargs), confirm
|
self.api.account_fund(*args, **kwargs), confirm
|
||||||
)
|
)
|
||||||
|
|
||||||
async def account_send(self, *args, confirm=True, **kwargs):
|
|
||||||
return await self.confirm_and_render(
|
|
||||||
self.api.account_send(*args, **kwargs), confirm
|
|
||||||
)
|
|
||||||
|
|
||||||
async def wallet_send(self, *args, confirm=True, **kwargs):
|
async def wallet_send(self, *args, confirm=True, **kwargs):
|
||||||
return await self.confirm_and_render(
|
return await self.confirm_and_render(
|
||||||
self.api.wallet_send(*args, **kwargs), confirm
|
self.api.wallet_send(*args, **kwargs), confirm
|
||||||
|
|
|
@ -4,14 +4,13 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
import random
|
import random
|
||||||
from functools import partial
|
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from typing import Type, Dict, Tuple, Optional, Any, List
|
from typing import Type, Dict, Tuple, Optional, Any, List
|
||||||
|
|
||||||
import ecdsa
|
import ecdsa
|
||||||
|
|
||||||
from lbry.constants import COIN
|
from lbry.constants import COIN
|
||||||
from lbry.db import Database, CLAIM_TYPE_CODES, TXO_TYPES
|
from lbry.db import Database
|
||||||
from lbry.db.tables import AccountAddress
|
from lbry.db.tables import AccountAddress
|
||||||
from lbry.blockchain import Ledger
|
from lbry.blockchain import Ledger
|
||||||
from lbry.error import InvalidPasswordError
|
from lbry.error import InvalidPasswordError
|
||||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
||||||
from typing import Optional, Dict
|
from typing import Optional, Dict
|
||||||
|
|
||||||
from lbry.db import Database
|
from lbry.db import Database
|
||||||
|
from lbry.blockchain.dewies import dict_values_to_lbc
|
||||||
|
|
||||||
from .wallet import Wallet
|
from .wallet import Wallet
|
||||||
from .account import SingleKey, HierarchicalDeterministic
|
from .account import SingleKey, HierarchicalDeterministic
|
||||||
|
@ -106,21 +107,22 @@ class WalletManager:
|
||||||
|
|
||||||
async def _report_state(self):
|
async def _report_state(self):
|
||||||
try:
|
try:
|
||||||
for account in self.accounts:
|
for wallet in self.wallets.values():
|
||||||
balance = dewies_to_lbc(await account.get_balance(include_claims=True))
|
for account in wallet.accounts:
|
||||||
_, channel_count = await account.get_channels(limit=1)
|
balance = dict_values_to_lbc(await account.get_balance(include_claims=True))
|
||||||
claim_count = await account.get_claim_count()
|
_, channel_count = await account.get_channels(limit=1)
|
||||||
if isinstance(account.receiving, SingleKey):
|
claim_count = await account.get_claim_count()
|
||||||
log.info("Loaded single key account %s with %s LBC. "
|
if isinstance(account.receiving, SingleKey):
|
||||||
"%d channels, %d certificates and %d claims",
|
log.info("Loaded single key account %s with %s LBC. "
|
||||||
account.id, balance, channel_count, len(account.channel_keys), claim_count)
|
"%d channels, %d certificates and %d claims",
|
||||||
else:
|
account.id, balance, channel_count, len(account.channel_keys), claim_count)
|
||||||
total_receiving = len(await account.receiving.get_addresses())
|
else:
|
||||||
total_change = len(await account.change.get_addresses())
|
total_receiving = len(await account.receiving.get_addresses())
|
||||||
log.info("Loaded account %s with %s LBC, %d receiving addresses (gap: %d), "
|
total_change = len(await account.change.get_addresses())
|
||||||
"%d change addresses (gap: %d), %d channels, %d certificates and %d claims. ",
|
log.info("Loaded account %s with %s LBC, %d receiving addresses (gap: %d), "
|
||||||
account.id, balance, total_receiving, account.receiving.gap, total_change,
|
"%d change addresses (gap: %d), %d channels, %d certificates and %d claims. ",
|
||||||
account.change.gap, channel_count, len(account.channel_keys), claim_count)
|
account.id, balance, total_receiving, account.receiving.gap, total_change,
|
||||||
|
account.change.gap, channel_count, len(account.channel_keys), claim_count)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
if isinstance(err, asyncio.CancelledError): # TODO: remove when updated to 3.8
|
if isinstance(err, asyncio.CancelledError): # TODO: remove when updated to 3.8
|
||||||
raise
|
raise
|
||||||
|
@ -135,7 +137,7 @@ class WalletStorage:
|
||||||
async def prepare(self):
|
async def prepare(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def exists(self, walllet_id: str) -> bool:
|
async def exists(self, wallet_id: str) -> bool:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def get(self, wallet_id: str) -> Wallet:
|
async def get(self, wallet_id: str) -> Wallet:
|
||||||
|
|
|
@ -8,6 +8,7 @@ from typing import Awaitable, Callable, List, Tuple, Optional, Iterable, Union
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from binascii import unhexlify
|
||||||
|
|
||||||
from lbry.db import Database, SPENDABLE_TYPE_CODES, Result
|
from lbry.db import Database, SPENDABLE_TYPE_CODES, Result
|
||||||
from lbry.event import EventController
|
from lbry.event import EventController
|
||||||
|
@ -518,9 +519,10 @@ class ClaimListManager(BaseListManager):
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
async def update(
|
async def update(
|
||||||
self, previous_claim: Output, claim: Claim, amount: int, holding_address: str,
|
self, previous_claim: Output, claim: Claim, amount: int, holding_address: str,
|
||||||
funding_accounts: List[Account], change_account: Account,
|
funding_accounts: List[Account], change_account: Account,
|
||||||
signing_channel: Output = None) -> Transaction:
|
signing_channel: Output = None
|
||||||
|
) -> Transaction:
|
||||||
updated_claim = Output.pay_update_claim_pubkey_hash(
|
updated_claim = Output.pay_update_claim_pubkey_hash(
|
||||||
amount, previous_claim.claim_name, previous_claim.claim_id,
|
amount, previous_claim.claim_name, previous_claim.claim_id,
|
||||||
claim, self.wallet.ledger.address_to_hash160(holding_address)
|
claim, self.wallet.ledger.address_to_hash160(holding_address)
|
||||||
|
@ -533,18 +535,27 @@ class ClaimListManager(BaseListManager):
|
||||||
[Input.spend(previous_claim)], [updated_claim], funding_accounts, change_account
|
[Input.spend(previous_claim)], [updated_claim], funding_accounts, change_account
|
||||||
)
|
)
|
||||||
|
|
||||||
async def delete(self, claim_id=None, txid=None, nout=None):
|
async def delete(
|
||||||
|
self, claim_id=None, txid=None, nout=None,
|
||||||
|
funding_accounts: List[Account] = None, change_account: Account = None
|
||||||
|
):
|
||||||
claim = await self.get(claim_id=claim_id, txid=txid, nout=nout)
|
claim = await self.get(claim_id=claim_id, txid=txid, nout=nout)
|
||||||
return await self.wallet.create_transaction(
|
tx = await self.wallet.create_transaction(
|
||||||
[Input.spend(claim)], [], self.wallet._accounts, self.wallet._accounts[0]
|
[Input.spend(claim)], [],
|
||||||
|
funding_accounts or self.wallet._accounts,
|
||||||
|
change_account or self.wallet._accounts[0]
|
||||||
)
|
)
|
||||||
|
await self.wallet.sign(tx)
|
||||||
|
return tx
|
||||||
|
|
||||||
async def list(self, **constraints) -> Result[Output]:
|
async def list(self, **constraints) -> Result[Output]:
|
||||||
return await self.wallet.db.get_claims(wallet=self.wallet, **constraints)
|
return await self.wallet.db.get_claims(wallet=self.wallet, **constraints)
|
||||||
|
|
||||||
async def get(self, claim_id=None, claim_name=None, txid=None, nout=None) -> Output:
|
async def get(self, claim_id=None, claim_name=None, txid=None, nout=None) -> Output:
|
||||||
if txid is not None and nout is not None:
|
if txid is not None and nout is not None:
|
||||||
key, value, constraints = 'txid:nout', f'{txid}:{nout}', {'tx_hash': '', 'position': nout}
|
key, value, constraints = 'txid:nout', f'{txid}:{nout}', {
|
||||||
|
'tx_hash': unhexlify(txid)[::-1], 'position': nout
|
||||||
|
}
|
||||||
elif claim_id is not None:
|
elif claim_id is not None:
|
||||||
key, value, constraints = 'id', claim_id, {'claim_id': claim_id}
|
key, value, constraints = 'id', claim_id, {'claim_id': claim_id}
|
||||||
elif claim_name is not None:
|
elif claim_name is not None:
|
||||||
|
|
|
@ -2,6 +2,7 @@ import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from unittest import skip
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
|
@ -71,6 +72,7 @@ class ClaimSearchCommand(ClaimTestCase):
|
||||||
(result['txid'], result['claim_id'])
|
(result['txid'], result['claim_id'])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@skip
|
||||||
async def test_disconnect_on_memory_error(self):
|
async def test_disconnect_on_memory_error(self):
|
||||||
claim_ids = [
|
claim_ids = [
|
||||||
'0000000000000000000000000000000000000000',
|
'0000000000000000000000000000000000000000',
|
||||||
|
@ -116,9 +118,9 @@ class ClaimSearchCommand(ClaimTestCase):
|
||||||
|
|
||||||
# finding claims with and without a channel
|
# finding claims with and without a channel
|
||||||
await self.assertFindsClaims([signed2, signed], name='on-channel-claim')
|
await self.assertFindsClaims([signed2, signed], name='on-channel-claim')
|
||||||
await self.assertFindsClaims([signed2, signed], channel_ids=[self.channel_id, channel_id2])
|
await self.assertFindsClaims([signed2, signed], channel_id=[self.channel_id, channel_id2])
|
||||||
await self.assertFindsClaim(signed, name='on-channel-claim', channel_ids=[self.channel_id])
|
await self.assertFindsClaim(signed, name='on-channel-claim', channel_id=[self.channel_id])
|
||||||
await self.assertFindsClaim(signed2, name='on-channel-claim', channel_ids=[channel_id2])
|
await self.assertFindsClaim(signed2, name='on-channel-claim', channel_id=[channel_id2])
|
||||||
await self.assertFindsClaim(unsigned, name='unsigned')
|
await self.assertFindsClaim(unsigned, name='unsigned')
|
||||||
await self.assertFindsClaim(unsigned, txid=unsigned['txid'], nout=0)
|
await self.assertFindsClaim(unsigned, txid=unsigned['txid'], nout=0)
|
||||||
await self.assertFindsClaim(unsigned, claim_id=self.get_claim_id(unsigned))
|
await self.assertFindsClaim(unsigned, claim_id=self.get_claim_id(unsigned))
|
||||||
|
@ -128,37 +130,44 @@ class ClaimSearchCommand(ClaimTestCase):
|
||||||
|
|
||||||
# three streams in channel, zero streams in abandoned channel
|
# three streams in channel, zero streams in abandoned channel
|
||||||
claims = [three, two, signed]
|
claims = [three, two, signed]
|
||||||
await self.assertFindsClaims(claims, channel_ids=[self.channel_id])
|
await self.assertFindsClaims(claims, channel_id=[self.channel_id])
|
||||||
await self.assertFindsClaims(claims, channel=f"@abc#{self.channel_id}")
|
await self.assertFindsClaims(claims, channel=f"@abc#{self.channel_id}")
|
||||||
await self.assertFindsClaims([three, two, signed2, signed], channel_ids=[channel_id2, self.channel_id])
|
await self.assertFindsClaims([three, two, signed2, signed], channel_id=[channel_id2, self.channel_id])
|
||||||
await self.channel_abandon(claim_id=self.channel_id)
|
await self.channel_abandon(claim_id=self.channel_id)
|
||||||
await self.assertFindsClaims([], channel=f"@abc#{self.channel_id}", valid_channel_signature=True)
|
await self.assertFindsClaims([], channel=f"@abc#{self.channel_id}", valid_channel_signature=True)
|
||||||
await self.assertFindsClaims([], channel_ids=[self.channel_id], valid_channel_signature=True)
|
await self.assertFindsClaims([signed2], channel_id=[channel_id2], valid_channel_signature=True)
|
||||||
await self.assertFindsClaims([signed2], channel_ids=[channel_id2], valid_channel_signature=True)
|
|
||||||
# pass `invalid_channel_signature=False` to catch a bug in argument processing
|
# pass `invalid_channel_signature=False` to catch a bug in argument processing
|
||||||
await self.assertFindsClaims([signed2], channel_ids=[channel_id2, self.channel_id],
|
await self.assertFindsClaims([signed2], channel_id=[channel_id2],
|
||||||
valid_channel_signature=True, invalid_channel_signature=False)
|
valid_channel_signature=True, invalid_channel_signature=False)
|
||||||
|
# in old SDK abandoned channels caused content to have invalid signature,
|
||||||
|
# in new SDK this is not the case
|
||||||
|
# TODO: create situation where streams legitimately have invalid signature, harder in new SDK
|
||||||
|
# await self.assertFindsClaims([], channel_id=[self.channel_id], valid_channel_signature=True)
|
||||||
# invalid signature still returns channel_id
|
# invalid signature still returns channel_id
|
||||||
invalid_claims = await self.claim_search(invalid_channel_signature=True, has_channel_signature=True)
|
#invalid_claims = await self.claim_search(invalid_channel_signature=True, has_channel_signature=True)
|
||||||
self.assertEqual(3, len(invalid_claims))
|
#self.assertEqual(3, len(invalid_claims))
|
||||||
self.assertTrue(all([not c['is_channel_signature_valid'] for c in invalid_claims]))
|
#self.assertTrue(all([not c['is_channel_signature_valid'] for c in invalid_claims]))
|
||||||
self.assertEqual({'channel_id': self.channel_id}, invalid_claims[0]['signing_channel'])
|
#self.assertEqual({'channel_id': self.channel_id}, invalid_claims[0]['signing_channel'])
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
0, len(await self.claim_search(invalid_channel_signature=True, has_channel_signature=True))
|
||||||
|
)
|
||||||
|
|
||||||
valid_claims = await self.claim_search(valid_channel_signature=True, has_channel_signature=True)
|
valid_claims = await self.claim_search(valid_channel_signature=True, has_channel_signature=True)
|
||||||
self.assertEqual(1, len(valid_claims))
|
self.assertEqual(4, len(valid_claims))
|
||||||
self.assertTrue(all([c['is_channel_signature_valid'] for c in valid_claims]))
|
self.assertTrue(all([c['is_channel_signature_valid'] for c in valid_claims]))
|
||||||
self.assertEqual('@abc', valid_claims[0]['signing_channel']['name'])
|
self.assertEqual('@abc', valid_claims[1]['signing_channel']['name'])
|
||||||
|
|
||||||
# abandoned stream won't show up for streams in channel search
|
# abandoned stream won't show up for streams in channel search
|
||||||
await self.stream_abandon(txid=signed2['txid'], nout=0)
|
await self.stream_abandon(txid=signed2['txid'], nout=0)
|
||||||
await self.assertFindsClaims([], channel_ids=[channel_id2])
|
await self.assertFindsClaims([], channel_id=[channel_id2])
|
||||||
|
|
||||||
async def test_pagination(self):
|
async def test_pagination(self):
|
||||||
await self.create_channel()
|
await self.create_channel()
|
||||||
await self.create_lots_of_streams()
|
await self.create_lots_of_streams()
|
||||||
|
|
||||||
# with and without totals
|
# with and without totals
|
||||||
results = await self.api.claim_search(include_totals=True)
|
results = await self.api.claim_search(include_total=True)
|
||||||
self.assertEqual(results['total_pages'], 2)
|
self.assertEqual(results['total_pages'], 2)
|
||||||
self.assertEqual(results['total_items'], 25)
|
self.assertEqual(results['total_items'], 25)
|
||||||
results = await self.api.claim_search()
|
results = await self.api.claim_search()
|
||||||
|
@ -194,40 +203,40 @@ class ClaimSearchCommand(ClaimTestCase):
|
||||||
self.assertEqual(out_of_bounds, [])
|
self.assertEqual(out_of_bounds, [])
|
||||||
|
|
||||||
async def test_tag_search(self):
|
async def test_tag_search(self):
|
||||||
claim1 = await self.stream_create('claim1', tags=['aBc'])
|
claim1 = await self.stream_create('claim1', tag=['aBc'])
|
||||||
claim2 = await self.stream_create('claim2', tags=['#abc', 'def'])
|
claim2 = await self.stream_create('claim2', tag=['#abc', 'def'])
|
||||||
claim3 = await self.stream_create('claim3', tags=['abc', 'ghi', 'jkl'])
|
claim3 = await self.stream_create('claim3', tag=['abc', 'ghi', 'jkl'])
|
||||||
claim4 = await self.stream_create('claim4', tags=['abc\t', 'ghi', 'mno'])
|
claim4 = await self.stream_create('claim4', tag=['abc\t', 'ghi', 'mno'])
|
||||||
claim5 = await self.stream_create('claim5', tags=['pqr'])
|
claim5 = await self.stream_create('claim5', tag=['pqr'])
|
||||||
|
|
||||||
# any_tags
|
# any_tags
|
||||||
await self.assertFindsClaims([claim5, claim4, claim3, claim2, claim1], any_tags=['\tabc', 'pqr'])
|
await self.assertFindsClaims([claim5, claim4, claim3, claim2, claim1], any_tag=['\tabc', 'pqr'])
|
||||||
await self.assertFindsClaims([claim4, claim3, claim2, claim1], any_tags=['abc'])
|
await self.assertFindsClaims([claim4, claim3, claim2, claim1], any_tag=['abc'])
|
||||||
await self.assertFindsClaims([claim4, claim3, claim2, claim1], any_tags=['abc', 'ghi'])
|
await self.assertFindsClaims([claim4, claim3, claim2, claim1], any_tag=['abc', 'ghi'])
|
||||||
await self.assertFindsClaims([claim4, claim3], any_tags=['ghi'])
|
await self.assertFindsClaims([claim4, claim3], any_tag=['ghi'])
|
||||||
await self.assertFindsClaims([claim4, claim3], any_tags=['ghi', 'xyz'])
|
await self.assertFindsClaims([claim4, claim3], any_tag=['ghi', 'xyz'])
|
||||||
await self.assertFindsClaims([], any_tags=['xyz'])
|
await self.assertFindsClaims([], any_tag=['xyz'])
|
||||||
|
|
||||||
# all_tags
|
# all_tags
|
||||||
await self.assertFindsClaims([], all_tags=['abc', 'pqr'])
|
await self.assertFindsClaims([], all_tag=['abc', 'pqr'])
|
||||||
await self.assertFindsClaims([claim4, claim3, claim2, claim1], all_tags=['ABC'])
|
await self.assertFindsClaims([claim4, claim3, claim2, claim1], all_tag=['ABC'])
|
||||||
await self.assertFindsClaims([claim4, claim3], all_tags=['abc', 'ghi'])
|
await self.assertFindsClaims([claim4, claim3], all_tag=['abc', 'ghi'])
|
||||||
await self.assertFindsClaims([claim4, claim3], all_tags=['ghi'])
|
await self.assertFindsClaims([claim4, claim3], all_tag=['ghi'])
|
||||||
await self.assertFindsClaims([], all_tags=['ghi', 'xyz'])
|
await self.assertFindsClaims([], all_tag=['ghi', 'xyz'])
|
||||||
await self.assertFindsClaims([], all_tags=['xyz'])
|
await self.assertFindsClaims([], all_tag=['xyz'])
|
||||||
|
|
||||||
# not_tags
|
# not_tags
|
||||||
await self.assertFindsClaims([], not_tags=['abc', 'pqr'])
|
await self.assertFindsClaims([], not_tag=['abc', 'pqr'])
|
||||||
await self.assertFindsClaims([claim5], not_tags=['abC'])
|
await self.assertFindsClaims([claim5], not_tag=['abC'])
|
||||||
await self.assertFindsClaims([claim5], not_tags=['abc', 'ghi'])
|
await self.assertFindsClaims([claim5], not_tag=['abc', 'ghi'])
|
||||||
await self.assertFindsClaims([claim5, claim2, claim1], not_tags=['ghi'])
|
await self.assertFindsClaims([claim5, claim2, claim1], not_tag=['ghi'])
|
||||||
await self.assertFindsClaims([claim5, claim2, claim1], not_tags=['ghi', 'xyz'])
|
await self.assertFindsClaims([claim5, claim2, claim1], not_tag=['ghi', 'xyz'])
|
||||||
await self.assertFindsClaims([claim5, claim4, claim3, claim2, claim1], not_tags=['xyz'])
|
await self.assertFindsClaims([claim5, claim4, claim3, claim2, claim1], not_tag=['xyz'])
|
||||||
|
|
||||||
# combinations
|
# combinations
|
||||||
await self.assertFindsClaims([claim3], all_tags=['abc', 'ghi'], not_tags=['mno'])
|
await self.assertFindsClaims([claim3], all_tag=['abc', 'ghi'], not_tag=['mno'])
|
||||||
await self.assertFindsClaims([claim3], all_tags=['abc', 'ghi'], any_tags=['jkl'], not_tags=['mno'])
|
await self.assertFindsClaims([claim3], all_tag=['abc', 'ghi'], any_tag=['jkl'], not_tag=['mno'])
|
||||||
await self.assertFindsClaims([claim4, claim3, claim2], all_tags=['abc'], any_tags=['def', 'ghi'])
|
await self.assertFindsClaims([claim4, claim3, claim2], all_tags=['abc'], any_tag=['def', 'ghi'])
|
||||||
|
|
||||||
async def test_order_by(self):
|
async def test_order_by(self):
|
||||||
height = self.ledger.sync.network.remote_height
|
height = self.ledger.sync.network.remote_height
|
|
@ -150,6 +150,7 @@ class WalletCommands(CommandTestCase):
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@skip
|
||||||
class WalletEncryptionAndSynchronization(CommandTestCase):
|
class WalletEncryptionAndSynchronization(CommandTestCase):
|
||||||
|
|
||||||
SEED = (
|
SEED = (
|
||||||
|
|
Loading…
Add table
Reference in a new issue