wip misc
This commit is contained in:
parent
6a33d86bfe
commit
fef09c1773
6 changed files with 143 additions and 71 deletions
28
lbry/conf.py
28
lbry/conf.py
|
@ -10,7 +10,7 @@ import yaml
|
||||||
from appdirs import user_data_dir, user_config_dir
|
from appdirs import user_data_dir, user_config_dir
|
||||||
from lbry.error import InvalidCurrencyError
|
from lbry.error import InvalidCurrencyError
|
||||||
from lbry.dht import constants
|
from lbry.dht import constants
|
||||||
from lbry.wallet.coinselection import STRATEGIES
|
from lbry.wallet.coinselection import COIN_SELECTION_STRATEGIES
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -382,8 +382,12 @@ class BaseConfig:
|
||||||
self.environment = {} # from environment variables
|
self.environment = {} # from environment variables
|
||||||
self.persisted = {} # from config file
|
self.persisted = {} # from config file
|
||||||
self._updating_config = False
|
self._updating_config = False
|
||||||
|
self.set(**kwargs)
|
||||||
|
|
||||||
|
def set(self, **kwargs):
|
||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
return self
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def update_config(self):
|
def update_config(self):
|
||||||
|
@ -603,6 +607,17 @@ class Config(CLIConfig):
|
||||||
# blockchain
|
# blockchain
|
||||||
blockchain_name = String("Blockchain name - lbrycrd_main, lbrycrd_regtest, or lbrycrd_testnet", 'lbrycrd_main')
|
blockchain_name = String("Blockchain name - lbrycrd_main, lbrycrd_regtest, or lbrycrd_testnet", 'lbrycrd_main')
|
||||||
|
|
||||||
|
spv_address_filters = Toggle(
|
||||||
|
"Generate Golomb-Rice coding filters for blocks and transactions. Enables "
|
||||||
|
"light client to synchronize with a full node.",
|
||||||
|
True
|
||||||
|
)
|
||||||
|
|
||||||
|
lbrycrd_dir = Path(
|
||||||
|
"Directory containing lbrycrd data.",
|
||||||
|
previous_names=['lbrycrd_dir'], metavar='DIR'
|
||||||
|
)
|
||||||
|
|
||||||
# daemon
|
# daemon
|
||||||
save_files = Toggle("Save downloaded files when calling `get` by default", True)
|
save_files = Toggle("Save downloaded files when calling `get` by default", True)
|
||||||
components_to_skip = Strings("components which will be skipped during start-up of daemon", [])
|
components_to_skip = Strings("components which will be skipped during start-up of daemon", [])
|
||||||
|
@ -620,7 +635,7 @@ class Config(CLIConfig):
|
||||||
|
|
||||||
coin_selection_strategy = StringChoice(
|
coin_selection_strategy = StringChoice(
|
||||||
"Strategy to use when selecting UTXOs for a transaction",
|
"Strategy to use when selecting UTXOs for a transaction",
|
||||||
STRATEGIES, "standard")
|
COIN_SELECTION_STRATEGIES, "standard")
|
||||||
|
|
||||||
save_resolved_claims = Toggle(
|
save_resolved_claims = Toggle(
|
||||||
"Save content claims to the database when they are resolved to keep file_list up to date, "
|
"Save content claims to the database when they are resolved to keep file_list up to date, "
|
||||||
|
@ -639,6 +654,15 @@ class Config(CLIConfig):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.set_default_paths()
|
self.set_default_paths()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def with_same_dir(cls, same_dir):
|
||||||
|
return cls(
|
||||||
|
data_dir=same_dir,
|
||||||
|
download_dir=same_dir,
|
||||||
|
wallet_dir=same_dir,
|
||||||
|
lbrycrd_dir=same_dir,
|
||||||
|
)
|
||||||
|
|
||||||
def set_default_paths(self):
|
def set_default_paths(self):
|
||||||
if 'darwin' in sys.platform.lower():
|
if 'darwin' in sys.platform.lower():
|
||||||
get_directories = get_darwin_directories
|
get_directories = get_darwin_directories
|
||||||
|
|
|
@ -2,26 +2,3 @@ NULL_HASH32 = b'\x00'*32
|
||||||
|
|
||||||
CENT = 1000000
|
CENT = 1000000
|
||||||
COIN = 100*CENT
|
COIN = 100*CENT
|
||||||
|
|
||||||
TIMEOUT = 30.0
|
|
||||||
|
|
||||||
TXO_TYPES = {
|
|
||||||
"other": 0,
|
|
||||||
"stream": 1,
|
|
||||||
"channel": 2,
|
|
||||||
"support": 3,
|
|
||||||
"purchase": 4,
|
|
||||||
"collection": 5,
|
|
||||||
"repost": 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
CLAIM_TYPE_NAMES = [
|
|
||||||
'stream',
|
|
||||||
'channel',
|
|
||||||
'collection',
|
|
||||||
'repost',
|
|
||||||
]
|
|
||||||
|
|
||||||
CLAIM_TYPES = [
|
|
||||||
TXO_TYPES[name] for name in CLAIM_TYPE_NAMES
|
|
||||||
]
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ from coincurve import PublicKey, PrivateKey as _PrivateKey
|
||||||
|
|
||||||
from lbry.crypto.hash import hmac_sha512, hash160, double_sha256
|
from lbry.crypto.hash import hmac_sha512, hash160, double_sha256
|
||||||
from lbry.crypto.base58 import Base58
|
from lbry.crypto.base58 import Base58
|
||||||
from .util import cachedproperty
|
from lbry.utils import cachedproperty
|
||||||
|
|
||||||
|
|
||||||
class DerivationError(Exception):
|
class DerivationError(Exception):
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import threading
|
import threading
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BroadcastSubscription:
|
class BroadcastSubscription:
|
||||||
|
|
||||||
def __init__(self, controller, on_data, on_error, on_done):
|
def __init__(self, controller: 'EventController', on_data, on_error, on_done):
|
||||||
self._controller = controller
|
self._controller = controller
|
||||||
self._previous = self._next = None
|
self._previous = self._next = None
|
||||||
self._on_data = on_data
|
self._on_data = on_data
|
||||||
|
@ -45,10 +49,10 @@ class BroadcastSubscription:
|
||||||
self.is_closed = True
|
self.is_closed = True
|
||||||
|
|
||||||
|
|
||||||
class StreamController:
|
class EventController:
|
||||||
|
|
||||||
def __init__(self, merge_repeated_events=False):
|
def __init__(self, merge_repeated_events=False):
|
||||||
self.stream = Stream(self)
|
self.stream = EventStream(self)
|
||||||
self._first_subscription = None
|
self._first_subscription = None
|
||||||
self._last_subscription = None
|
self._last_subscription = None
|
||||||
self._last_event = None
|
self._last_event = None
|
||||||
|
@ -66,30 +70,25 @@ class StreamController:
|
||||||
next_sub = next_sub._next
|
next_sub = next_sub._next
|
||||||
yield subscription
|
yield subscription
|
||||||
|
|
||||||
def _notify_and_ensure_future(self, notify):
|
async def _notify(self, notify, event):
|
||||||
tasks = []
|
try:
|
||||||
for subscription in self._iterate_subscriptions:
|
maybe_coroutine = notify(event)
|
||||||
maybe_coroutine = notify(subscription)
|
|
||||||
if asyncio.iscoroutine(maybe_coroutine):
|
if asyncio.iscoroutine(maybe_coroutine):
|
||||||
tasks.append(maybe_coroutine)
|
await maybe_coroutine
|
||||||
if tasks:
|
except Exception as e:
|
||||||
return asyncio.ensure_future(asyncio.wait(tasks))
|
log.exception(e)
|
||||||
else:
|
raise
|
||||||
f = asyncio.get_event_loop().create_future()
|
|
||||||
f.set_result(None)
|
|
||||||
return f
|
|
||||||
|
|
||||||
def add(self, event):
|
async def add(self, event):
|
||||||
skip = self._merge_repeated and event == self._last_event
|
if self._merge_repeated and event == self._last_event:
|
||||||
|
return
|
||||||
self._last_event = event
|
self._last_event = event
|
||||||
return self._notify_and_ensure_future(
|
for subscription in self._iterate_subscriptions:
|
||||||
lambda subscription: None if skip else subscription._add(event)
|
await self._notify(subscription._add, event)
|
||||||
)
|
|
||||||
|
|
||||||
def add_error(self, exception):
|
async def add_error(self, exception):
|
||||||
return self._notify_and_ensure_future(
|
for subscription in self._iterate_subscriptions:
|
||||||
lambda subscription: subscription._add_error(exception)
|
await self._notify(subscription._add_error, exception)
|
||||||
)
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
for subscription in self._iterate_subscriptions:
|
for subscription in self._iterate_subscriptions:
|
||||||
|
@ -121,16 +120,16 @@ class StreamController:
|
||||||
return subscription
|
return subscription
|
||||||
|
|
||||||
|
|
||||||
class Stream:
|
class EventStream:
|
||||||
|
|
||||||
def __init__(self, controller):
|
def __init__(self, controller):
|
||||||
self._controller = controller
|
self._controller = controller
|
||||||
|
|
||||||
def listen(self, on_data, on_error=None, on_done=None):
|
def listen(self, on_data, on_error=None, on_done=None) -> BroadcastSubscription:
|
||||||
return self._controller._listen(on_data, on_error, on_done)
|
return self._controller._listen(on_data, on_error, on_done)
|
||||||
|
|
||||||
def where(self, condition) -> asyncio.Future:
|
def where(self, condition) -> asyncio.Future:
|
||||||
future = asyncio.get_event_loop().create_future()
|
future = asyncio.get_running_loop().create_future()
|
||||||
|
|
||||||
def where_test(value):
|
def where_test(value):
|
||||||
if condition(value):
|
if condition(value):
|
||||||
|
@ -144,7 +143,7 @@ class Stream:
|
||||||
return future
|
return future
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def first(self):
|
def first(self) -> asyncio.Future:
|
||||||
future = asyncio.get_event_loop().create_future()
|
future = asyncio.get_event_loop().create_future()
|
||||||
subscription = self.listen(
|
subscription = self.listen(
|
||||||
lambda value: not future.done() and self._cancel_and_callback(subscription, future, value),
|
lambda value: not future.done() and self._cancel_and_callback(subscription, future, value),
|
||||||
|
@ -165,12 +164,12 @@ class Stream:
|
||||||
|
|
||||||
class EventQueuePublisher(threading.Thread):
|
class EventQueuePublisher(threading.Thread):
|
||||||
|
|
||||||
STOP = object()
|
STOP = 'STOP'
|
||||||
|
|
||||||
def __init__(self, queue: multiprocessing.Queue, stream_controller: StreamController):
|
def __init__(self, queue: multiprocessing.Queue, event_controller: EventController):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.stream_controller = stream_controller
|
self.event_controller = event_controller
|
||||||
self.loop = asyncio.get_running_loop()
|
self.loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
@ -178,7 +177,9 @@ class EventQueuePublisher(threading.Thread):
|
||||||
msg = self.queue.get()
|
msg = self.queue.get()
|
||||||
if msg == self.STOP:
|
if msg == self.STOP:
|
||||||
return
|
return
|
||||||
self.loop.call_soon_threadsafe(self.stream_controller.add, msg)
|
asyncio.run_coroutine_threadsafe(
|
||||||
|
self.event_controller.add(msg), self.loop
|
||||||
|
)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.queue.put(self.STOP)
|
self.queue.put(self.STOP)
|
||||||
|
|
|
@ -14,13 +14,18 @@ from time import time
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from lbry.wallet import WalletManager, Wallet, Ledger, Account, Transaction
|
from lbry.blockchain.ledger import Ledger
|
||||||
|
from lbry.blockchain.transaction import Transaction, Input, Output
|
||||||
|
from lbry.blockchain.util import satoshis_to_coins
|
||||||
|
from lbry.constants import CENT, NULL_HASH32
|
||||||
|
from lbry.wallet.wallet import Wallet, Account
|
||||||
|
from lbry.wallet.manager import WalletManager
|
||||||
from lbry.conf import Config
|
from lbry.conf import Config
|
||||||
from lbry.wallet.util import satoshis_to_coins
|
from lbry.blockchain.lbrycrd import Lbrycrd
|
||||||
from lbry.wallet.orchstr8 import Conductor
|
from lbry.service.full_node import FullNode
|
||||||
from lbry.wallet.orchstr8.node import BlockchainNode, WalletNode
|
from lbry.service.daemon import Daemon
|
||||||
|
|
||||||
from lbry.extras.daemon.daemon import Daemon, jsonrpc_dumps_pretty
|
from lbry.extras.daemon.daemon import jsonrpc_dumps_pretty
|
||||||
from lbry.extras.daemon.components import Component, WalletComponent
|
from lbry.extras.daemon.components import Component, WalletComponent
|
||||||
from lbry.extras.daemon.components import (
|
from lbry.extras.daemon.components import (
|
||||||
DHT_COMPONENT, HASH_ANNOUNCER_COMPONENT, PEER_PROTOCOL_SERVER_COMPONENT,
|
DHT_COMPONENT, HASH_ANNOUNCER_COMPONENT, PEER_PROTOCOL_SERVER_COMPONENT,
|
||||||
|
@ -36,6 +41,28 @@ from lbry.stream.reflector.server import ReflectorServer
|
||||||
from lbry.blob_exchange.server import BlobServer
|
from lbry.blob_exchange.server import BlobServer
|
||||||
|
|
||||||
|
|
||||||
|
def get_output(amount=CENT, pubkey_hash=NULL_HASH32, height=-2):
|
||||||
|
return Transaction(height=height) \
|
||||||
|
.add_outputs([Output.pay_pubkey_hash(amount, pubkey_hash)]) \
|
||||||
|
.outputs[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_input(amount=CENT, pubkey_hash=NULL_HASH32):
|
||||||
|
return Input.spend(get_output(amount, pubkey_hash))
|
||||||
|
|
||||||
|
|
||||||
|
def get_transaction(txo=None):
|
||||||
|
return Transaction() \
|
||||||
|
.add_inputs([get_input()]) \
|
||||||
|
.add_outputs([txo or Output.pay_pubkey_hash(CENT, NULL_HASH32)])
|
||||||
|
|
||||||
|
|
||||||
|
def get_claim_transaction(claim_name, claim=b''):
|
||||||
|
return get_transaction(
|
||||||
|
Output.pay_claim_name_pubkey_hash(CENT, claim_name, claim, NULL_HASH32)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ColorHandler(logging.StreamHandler):
|
class ColorHandler(logging.StreamHandler):
|
||||||
|
|
||||||
level_color = {
|
level_color = {
|
||||||
|
@ -327,6 +354,32 @@ class CommandTestCase(IntegrationTestCase):
|
||||||
self.reflector = None
|
self.reflector = None
|
||||||
|
|
||||||
async def asyncSetUp(self):
|
async def asyncSetUp(self):
|
||||||
|
self.chain = Lbrycrd.temp_regtest()
|
||||||
|
self.ledger = self.chain.ledger
|
||||||
|
await self.chain.ensure()
|
||||||
|
self.addCleanup(self.chain.stop)
|
||||||
|
await self.chain.start('-rpcworkqueue=128')
|
||||||
|
|
||||||
|
self.block_expected = 0
|
||||||
|
await self.generate(200, wait=False)
|
||||||
|
|
||||||
|
self.chain.ledger.conf.spv_address_filters = False
|
||||||
|
self.service = FullNode(
|
||||||
|
self.ledger, f'sqlite:///{self.chain.data_dir}/full_node.db', Lbrycrd(self.ledger)
|
||||||
|
)
|
||||||
|
self.daemon = Daemon(self.service)
|
||||||
|
self.api = self.daemon.api
|
||||||
|
self.addCleanup(self.daemon.stop)
|
||||||
|
await self.daemon.start()
|
||||||
|
|
||||||
|
self.wallet = self.service.wallet_manager.default_wallet
|
||||||
|
self.account = self.wallet.accounts[0]
|
||||||
|
addresses = await self.account.ensure_address_gap()
|
||||||
|
|
||||||
|
await self.chain.send_to_address(addresses[0], '10.0')
|
||||||
|
await self.generate(5)
|
||||||
|
|
||||||
|
async def XasyncSetUp(self):
|
||||||
await super().asyncSetUp()
|
await super().asyncSetUp()
|
||||||
|
|
||||||
logging.getLogger('lbry.blob_exchange').setLevel(self.VERBOSITY)
|
logging.getLogger('lbry.blob_exchange').setLevel(self.VERBOSITY)
|
||||||
|
@ -435,10 +488,15 @@ class CommandTestCase(IntegrationTestCase):
|
||||||
addresses.add(txo['address'])
|
addresses.add(txo['address'])
|
||||||
return list(addresses)
|
return list(addresses)
|
||||||
|
|
||||||
async def generate(self, blocks):
|
def is_expected_block(self, b):
|
||||||
|
return self.block_expected == b.height
|
||||||
|
|
||||||
|
async def generate(self, blocks, wait=True):
|
||||||
""" Ask lbrycrd to generate some blocks and wait until ledger has them. """
|
""" Ask lbrycrd to generate some blocks and wait until ledger has them. """
|
||||||
await self.blockchain.generate(blocks)
|
await self.chain.generate(blocks)
|
||||||
await self.ledger.on_header.where(self.blockchain.is_expected_block)
|
self.block_expected += blocks
|
||||||
|
if wait:
|
||||||
|
await self.service.sync.on_block.where(self.is_expected_block)
|
||||||
|
|
||||||
async def blockchain_claim_name(self, name: str, value: str, amount: str, confirm=True):
|
async def blockchain_claim_name(self, name: str, value: str, amount: str, confirm=True):
|
||||||
txid = await self.blockchain._cli_cmnd('claimname', name, value, amount)
|
txid = await self.blockchain._cli_cmnd('claimname', name, value, amount)
|
||||||
|
@ -454,18 +512,18 @@ class CommandTestCase(IntegrationTestCase):
|
||||||
|
|
||||||
async def out(self, awaitable):
|
async def out(self, awaitable):
|
||||||
""" Serializes lbrynet API results to JSON then loads and returns it as dictionary. """
|
""" Serializes lbrynet API results to JSON then loads and returns it as dictionary. """
|
||||||
return json.loads(jsonrpc_dumps_pretty(await awaitable, ledger=self.ledger))['result']
|
return json.loads(jsonrpc_dumps_pretty(await awaitable, service=self.service))['result']
|
||||||
|
|
||||||
def sout(self, value):
|
def sout(self, value):
|
||||||
""" Synchronous version of `out` method. """
|
""" Synchronous version of `out` method. """
|
||||||
return json.loads(jsonrpc_dumps_pretty(value, ledger=self.ledger))['result']
|
return json.loads(jsonrpc_dumps_pretty(value, service=self.service))['result']
|
||||||
|
|
||||||
async def confirm_and_render(self, awaitable, confirm) -> Transaction:
|
async def confirm_and_render(self, awaitable, confirm) -> Transaction:
|
||||||
tx = await awaitable
|
tx = await awaitable
|
||||||
if confirm:
|
if confirm:
|
||||||
await self.ledger.wait(tx)
|
await self.service.wait(tx)
|
||||||
await self.generate(1)
|
await self.generate(1)
|
||||||
await self.ledger.wait(tx, self.blockchain.block_expected)
|
await self.service.wait(tx)
|
||||||
return self.sout(tx)
|
return self.sout(tx)
|
||||||
|
|
||||||
def create_upload_file(self, data, prefix=None, suffix=None):
|
def create_upload_file(self, data, prefix=None, suffix=None):
|
||||||
|
@ -519,7 +577,7 @@ class CommandTestCase(IntegrationTestCase):
|
||||||
|
|
||||||
async def channel_create(self, name='@arena', bid='1.0', confirm=True, **kwargs):
|
async def channel_create(self, name='@arena', bid='1.0', confirm=True, **kwargs):
|
||||||
return await self.confirm_and_render(
|
return await self.confirm_and_render(
|
||||||
self.daemon.jsonrpc_channel_create(name, bid, **kwargs), confirm
|
self.api.channel_create(name, bid, **kwargs), confirm
|
||||||
)
|
)
|
||||||
|
|
||||||
async def channel_update(self, claim_id, confirm=True, **kwargs):
|
async def channel_update(self, claim_id, confirm=True, **kwargs):
|
||||||
|
@ -582,7 +640,7 @@ class CommandTestCase(IntegrationTestCase):
|
||||||
return (await self.out(self.daemon.jsonrpc_resolve(uri, **kwargs)))[uri]
|
return (await self.out(self.daemon.jsonrpc_resolve(uri, **kwargs)))[uri]
|
||||||
|
|
||||||
async def claim_search(self, **kwargs):
|
async def claim_search(self, **kwargs):
|
||||||
return (await self.out(self.daemon.jsonrpc_claim_search(**kwargs)))['items']
|
return (await self.out(self.api.claim_search(**kwargs)))['items']
|
||||||
|
|
||||||
async def file_list(self, *args, **kwargs):
|
async def file_list(self, *args, **kwargs):
|
||||||
return (await self.out(self.daemon.jsonrpc_file_list(*args, **kwargs)))['items']
|
return (await self.out(self.daemon.jsonrpc_file_list(*args, **kwargs)))['items']
|
||||||
|
|
|
@ -282,3 +282,15 @@ async def get_external_ip() -> typing.Optional[str]: # used if upnp is disabled
|
||||||
def is_running_from_bundle():
|
def is_running_from_bundle():
|
||||||
# see https://pyinstaller.readthedocs.io/en/stable/runtime-information.html
|
# see https://pyinstaller.readthedocs.io/en/stable/runtime-information.html
|
||||||
return getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS')
|
return getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS')
|
||||||
|
|
||||||
|
|
||||||
|
class cachedproperty:
|
||||||
|
|
||||||
|
def __init__(self, f):
|
||||||
|
self.f = f
|
||||||
|
|
||||||
|
def __get__(self, obj, objtype):
|
||||||
|
obj = obj or objtype
|
||||||
|
value = self.f(obj)
|
||||||
|
setattr(obj, self.f.__name__, value)
|
||||||
|
return value
|
||||||
|
|
Loading…
Reference in a new issue