add listeners
This commit is contained in:
+114
-22
@@ -37,21 +37,27 @@ class Task:
|
|||||||
created_at: datetime
|
created_at: datetime
|
||||||
task: asyncio.Task
|
task: asyncio.Task
|
||||||
invoice_queue: asyncio.Queue[Payment] | None = None
|
invoice_queue: asyncio.Queue[Payment] | None = None
|
||||||
onchain_queue: asyncio.Queue[OnchainAddressEvent] | None = None
|
onchain_address_queue: asyncio.Queue[OnchainAddressEvent] | None = None
|
||||||
|
onchain_tx_queue: asyncio.Queue[OnchainTxEvent] | None = None
|
||||||
|
block_queue: asyncio.Queue[BlockInfo] | None = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coro: Coroutine,
|
coro: Coroutine,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
invoice_queue: asyncio.Queue | None = None,
|
invoice_queue: asyncio.Queue | None = None,
|
||||||
onchain_queue: asyncio.Queue | None = None,
|
onchain_address_queue: asyncio.Queue | None = None,
|
||||||
|
onchain_tx_queue: asyncio.Queue | None = None,
|
||||||
|
block_queue: asyncio.Queue | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.coro = coro
|
self.coro = coro
|
||||||
self.name = name or f"task_{uuid.uuid4()}"
|
self.name = name or f"task_{uuid.uuid4()}"
|
||||||
self.created_at = datetime.now(timezone.utc)
|
self.created_at = datetime.now(timezone.utc)
|
||||||
self.task = asyncio.create_task(self.coro, name=self.name)
|
self.task = asyncio.create_task(self.coro, name=self.name)
|
||||||
self.invoice_queue = invoice_queue
|
self.invoice_queue = invoice_queue
|
||||||
self.onchain_queue = onchain_queue
|
self.onchain_address_queue = onchain_address_queue
|
||||||
|
self.onchain_tx_queue = onchain_tx_queue
|
||||||
|
self.block_queue = block_queue
|
||||||
|
|
||||||
|
|
||||||
class TaskManager:
|
class TaskManager:
|
||||||
@@ -101,7 +107,9 @@ class TaskManager:
|
|||||||
coro: Coroutine,
|
coro: Coroutine,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
invoice_queue: asyncio.Queue | None = None,
|
invoice_queue: asyncio.Queue | None = None,
|
||||||
onchain_queue: asyncio.Queue | None = None,
|
onchain_address_queue: asyncio.Queue | None = None,
|
||||||
|
onchain_tx_queue: asyncio.Queue | None = None,
|
||||||
|
block_queue: asyncio.Queue | None = None,
|
||||||
) -> Task:
|
) -> Task:
|
||||||
"""Create a task. If a task with the same name exists, it will be cancelled."""
|
"""Create a task. If a task with the same name exists, it will be cancelled."""
|
||||||
if name:
|
if name:
|
||||||
@@ -112,7 +120,9 @@ class TaskManager:
|
|||||||
coro=coro,
|
coro=coro,
|
||||||
name=name,
|
name=name,
|
||||||
invoice_queue=invoice_queue,
|
invoice_queue=invoice_queue,
|
||||||
onchain_queue=onchain_queue,
|
onchain_address_queue=onchain_address_queue,
|
||||||
|
onchain_tx_queue=onchain_tx_queue,
|
||||||
|
block_queue=block_queue,
|
||||||
)
|
)
|
||||||
self.tasks.append(task)
|
self.tasks.append(task)
|
||||||
return task
|
return task
|
||||||
@@ -121,7 +131,9 @@ class TaskManager:
|
|||||||
self,
|
self,
|
||||||
func: Callable[[], Coroutine],
|
func: Callable[[], Coroutine],
|
||||||
invoice_queue: asyncio.Queue | None = None,
|
invoice_queue: asyncio.Queue | None = None,
|
||||||
onchain_queue: asyncio.Queue | None = None,
|
onchain_address_queue: asyncio.Queue | None = None,
|
||||||
|
onchain_tx_queue: asyncio.Queue | None = None,
|
||||||
|
block_queue: asyncio.Queue | None = None,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
interval: int = 0,
|
interval: int = 0,
|
||||||
) -> Task:
|
) -> Task:
|
||||||
@@ -137,7 +149,9 @@ class TaskManager:
|
|||||||
coro=wrapper(),
|
coro=wrapper(),
|
||||||
name=name or func.__name__,
|
name=name or func.__name__,
|
||||||
invoice_queue=invoice_queue,
|
invoice_queue=invoice_queue,
|
||||||
onchain_queue=onchain_queue,
|
onchain_address_queue=onchain_address_queue,
|
||||||
|
onchain_tx_queue=onchain_tx_queue,
|
||||||
|
block_queue=block_queue,
|
||||||
)
|
)
|
||||||
|
|
||||||
def register_invoice_listener(
|
def register_invoice_listener(
|
||||||
@@ -166,12 +180,49 @@ class TaskManager:
|
|||||||
Register a callback for onchain address events dispatched by track_address.
|
Register a callback for onchain address events dispatched by track_address.
|
||||||
Will call the provided coroutine with an OnchainAddressEvent on each update.
|
Will call the provided coroutine with an OnchainAddressEvent on each update.
|
||||||
"""
|
"""
|
||||||
name = f"{name or uuid.uuid4()}_onchain_listener"
|
name = f"{name or uuid.uuid4()}_onchain_address_listener"
|
||||||
queue: asyncio.Queue[OnchainAddressEvent] = asyncio.Queue()
|
queue: asyncio.Queue[OnchainAddressEvent] = asyncio.Queue()
|
||||||
return self.create_permanent_task(
|
return self.create_permanent_task(
|
||||||
self._onchain_listener_worker(func, queue),
|
self._onchain_address_listener_worker(func, queue),
|
||||||
name=name,
|
name=name,
|
||||||
onchain_queue=queue,
|
onchain_address_queue=queue,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register_onchain_tx_listener(
|
||||||
|
self,
|
||||||
|
func: Callable[[OnchainTxEvent], Coroutine],
|
||||||
|
name: str | None = None,
|
||||||
|
) -> Task:
|
||||||
|
"""
|
||||||
|
Register a callback for onchain transaction events dispatched for any
|
||||||
|
transaction currently tracked via register_ws_tx_queue.
|
||||||
|
Will call the provided coroutine with an OnchainTxEvent on each update.
|
||||||
|
"""
|
||||||
|
name = f"{name or uuid.uuid4()}_onchain_tx_listener"
|
||||||
|
queue: asyncio.Queue[OnchainTxEvent] = asyncio.Queue()
|
||||||
|
return self.create_permanent_task(
|
||||||
|
self._onchain_tx_listener_worker(func, queue),
|
||||||
|
name=name,
|
||||||
|
onchain_tx_queue=queue,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register_block_listener(
|
||||||
|
self,
|
||||||
|
func: Callable[[BlockInfo], Coroutine],
|
||||||
|
name: str | None = None,
|
||||||
|
) -> Task:
|
||||||
|
"""
|
||||||
|
Register a callback for new block events dispatched while the shared
|
||||||
|
block tracker is running (i.e. while a websocket or other consumer has
|
||||||
|
requested block updates via register_ws_block_queue).
|
||||||
|
Will call the provided coroutine with a BlockInfo on each new block.
|
||||||
|
"""
|
||||||
|
name = f"{name or uuid.uuid4()}_block_listener"
|
||||||
|
queue: asyncio.Queue[BlockInfo] = asyncio.Queue()
|
||||||
|
return self.create_permanent_task(
|
||||||
|
self._block_listener_worker(func, queue),
|
||||||
|
name=name,
|
||||||
|
block_queue=queue,
|
||||||
)
|
)
|
||||||
|
|
||||||
def track_address(self, address: str) -> None:
|
def track_address(self, address: str) -> None:
|
||||||
@@ -258,7 +309,10 @@ class TaskManager:
|
|||||||
tracker.register_queue(queue)
|
tracker.register_queue(queue)
|
||||||
if was_empty:
|
if was_empty:
|
||||||
self.create_task(
|
self.create_task(
|
||||||
tracker.run(lambda: tracker.has_queues() and settings.lnbits_running),
|
tracker.run(
|
||||||
|
self._dispatch_block_event,
|
||||||
|
lambda: tracker.has_queues() and settings.lnbits_running,
|
||||||
|
),
|
||||||
name="block_tracker",
|
name="block_tracker",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -296,7 +350,11 @@ class TaskManager:
|
|||||||
logger.debug(f"Task Manager: task `{task.name}` is done.")
|
logger.debug(f"Task Manager: task `{task.name}` is done.")
|
||||||
self.cancel_task(task)
|
self.cancel_task(task)
|
||||||
invoice_listeners = sum(1 for task in self.tasks if task.invoice_queue)
|
invoice_listeners = sum(1 for task in self.tasks if task.invoice_queue)
|
||||||
onchain_listeners = sum(1 for task in self.tasks if task.onchain_queue)
|
onchain_listeners = sum(
|
||||||
|
1
|
||||||
|
for task in self.tasks
|
||||||
|
if task.onchain_address_queue or task.onchain_tx_queue or task.block_queue
|
||||||
|
)
|
||||||
other_tasks = len(self.tasks) - invoice_listeners - onchain_listeners
|
other_tasks = len(self.tasks) - invoice_listeners - onchain_listeners
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Task Manager: {other_tasks} tasks, "
|
f"Task Manager: {other_tasks} tasks, "
|
||||||
@@ -334,7 +392,7 @@ class TaskManager:
|
|||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def _onchain_listener_worker(
|
def _onchain_address_listener_worker(
|
||||||
self,
|
self,
|
||||||
func: Callable[[OnchainAddressEvent], Coroutine],
|
func: Callable[[OnchainAddressEvent], Coroutine],
|
||||||
queue: asyncio.Queue[OnchainAddressEvent],
|
queue: asyncio.Queue[OnchainAddressEvent],
|
||||||
@@ -345,6 +403,28 @@ class TaskManager:
|
|||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
def _onchain_tx_listener_worker(
|
||||||
|
self,
|
||||||
|
func: Callable[[OnchainTxEvent], Coroutine],
|
||||||
|
queue: asyncio.Queue[OnchainTxEvent],
|
||||||
|
) -> Callable:
|
||||||
|
async def wrapper() -> None:
|
||||||
|
event: OnchainTxEvent = await queue.get()
|
||||||
|
await func(event)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
def _block_listener_worker(
|
||||||
|
self,
|
||||||
|
func: Callable[[BlockInfo], Coroutine],
|
||||||
|
queue: asyncio.Queue[BlockInfo],
|
||||||
|
) -> Callable:
|
||||||
|
async def wrapper() -> None:
|
||||||
|
event: BlockInfo = await queue.get()
|
||||||
|
await func(event)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
def _invoice_dispatcher(self, payment: Payment) -> None:
|
def _invoice_dispatcher(self, payment: Payment) -> None:
|
||||||
"""Dispatches a payment to all registered invoice listeners."""
|
"""Dispatches a payment to all registered invoice listeners."""
|
||||||
for task in self.tasks:
|
for task in self.tasks:
|
||||||
@@ -359,8 +439,26 @@ class TaskManager:
|
|||||||
Per-address WS queue fan-out is handled by AddressTracker itself.
|
Per-address WS queue fan-out is handled by AddressTracker itself.
|
||||||
"""
|
"""
|
||||||
for task in self.tasks:
|
for task in self.tasks:
|
||||||
if task.onchain_queue:
|
if task.onchain_address_queue:
|
||||||
task.onchain_queue.put_nowait(event)
|
task.onchain_address_queue.put_nowait(event)
|
||||||
|
|
||||||
|
async def _dispatch_onchain_tx_event(self, event: OnchainTxEvent) -> None:
|
||||||
|
"""Dispatches an onchain tx event to registered listeners.
|
||||||
|
|
||||||
|
Per-tx WS queue fan-out is handled by TransactionTracker itself.
|
||||||
|
"""
|
||||||
|
for task in self.tasks:
|
||||||
|
if task.onchain_tx_queue:
|
||||||
|
task.onchain_tx_queue.put_nowait(event)
|
||||||
|
|
||||||
|
async def _dispatch_block_event(self, event: BlockInfo) -> None:
|
||||||
|
"""Dispatches a new block event to registered listeners.
|
||||||
|
|
||||||
|
Per-connection WS queue fan-out is handled by BlockTracker itself.
|
||||||
|
"""
|
||||||
|
for task in self.tasks:
|
||||||
|
if task.block_queue:
|
||||||
|
task.block_queue.put_nowait(event)
|
||||||
|
|
||||||
async def _invoice_listener_consumer(self) -> None:
|
async def _invoice_listener_consumer(self) -> None:
|
||||||
payment = await self.invoice_queue.get()
|
payment = await self.invoice_queue.get()
|
||||||
@@ -384,18 +482,12 @@ class TaskManager:
|
|||||||
async def _transaction_tracker_dispatch(
|
async def _transaction_tracker_dispatch(
|
||||||
self, txid: str, tracker: "TransactionTracker"
|
self, txid: str, tracker: "TransactionTracker"
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Runs a shared tracker whose events are fanned out to its own
|
|
||||||
registered queues; no separate callback dispatch is needed."""
|
|
||||||
await tracker.track(
|
await tracker.track(
|
||||||
txid,
|
txid,
|
||||||
self._noop_tx_callback,
|
self._dispatch_onchain_tx_event,
|
||||||
lambda: tracker.has_queues() and settings.lnbits_running,
|
lambda: tracker.has_queues() and settings.lnbits_running,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def _noop_tx_callback(_: OnchainTxEvent) -> None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar("T", bound=BaseModel)
|
T = TypeVar("T", bound=BaseModel)
|
||||||
|
|
||||||
|
|||||||
@@ -1115,10 +1115,14 @@ class BlockTracker:
|
|||||||
def has_queues(self) -> bool:
|
def has_queues(self) -> bool:
|
||||||
return bool(self._queues)
|
return bool(self._queues)
|
||||||
|
|
||||||
async def run(self, is_active: Callable[[], bool]) -> None:
|
async def run(
|
||||||
|
self,
|
||||||
|
callback: Callable[[BlockInfo], Coroutine[Any, Any, None]],
|
||||||
|
is_active: Callable[[], bool],
|
||||||
|
) -> None:
|
||||||
while is_active():
|
while is_active():
|
||||||
try:
|
try:
|
||||||
await self._run_once(is_active)
|
await self._run_once(callback, is_active)
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
raise
|
raise
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -1127,15 +1131,20 @@ class BlockTracker:
|
|||||||
logger.warning(f"BlockTracker: {exc!s}, retrying in 5s")
|
logger.warning(f"BlockTracker: {exc!s}, retrying in 5s")
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
async def _run_once(self, is_active: Callable[[], bool]) -> None:
|
async def _run_once(
|
||||||
|
self,
|
||||||
|
callback: Callable[[BlockInfo], Coroutine[Any, Any, None]],
|
||||||
|
is_active: Callable[[], bool],
|
||||||
|
) -> None:
|
||||||
async with ElectrumClient(self.url) as client:
|
async with ElectrumClient(self.url) as client:
|
||||||
|
|
||||||
async def on_header(params: list[Any]) -> None:
|
async def on_header(params: list[Any]) -> None:
|
||||||
h = params[0]
|
h = params[0]
|
||||||
self._dispatch(parse_block_header(h["hex"], h["height"]))
|
event = parse_block_header(h["hex"], h["height"])
|
||||||
|
await self._dispatch(event, callback)
|
||||||
|
|
||||||
tip = await client.subscribe_headers(on_header)
|
tip = await client.subscribe_headers(on_header)
|
||||||
self._dispatch(parse_block_header(tip.hex, tip.height))
|
await self._dispatch(parse_block_header(tip.hex, tip.height), callback)
|
||||||
|
|
||||||
while is_active():
|
while is_active():
|
||||||
try:
|
try:
|
||||||
@@ -1144,6 +1153,11 @@ class BlockTracker:
|
|||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _dispatch(self, event: BlockInfo) -> None:
|
async def _dispatch(
|
||||||
|
self,
|
||||||
|
event: BlockInfo,
|
||||||
|
callback: Callable[[BlockInfo], Coroutine[Any, Any, None]],
|
||||||
|
) -> None:
|
||||||
for q in list(self._queues):
|
for q in list(self._queues):
|
||||||
q.put_nowait(event)
|
q.put_nowait(event)
|
||||||
|
await callback(event)
|
||||||
|
|||||||
Reference in New Issue
Block a user