feat(gl-sdk): add log + node-event listener callbacks #705
feat(gl-sdk): add log + node-event listener callbacks #705
Conversation
cdecker
left a comment
There was a problem hiding this comment.
I'm not quite happy with encumbering what is supposed to be the simple interface (naked top-level functions) with things like logging and event subscriber handling. That's more advanced concerns and exactly a problem of naked functions: they have no context other than the global static context they could be configured, hence all possible variants must be expressible in the argument list, which adds complexity back in.
I'd say if you use the simple interface you do not get to play with these advanced features, if you want those you have to use the OOP interface, where we can use builders to incrementally expose details. That is also in tune with the semver logic: naked functions change their entire interface when you add optional arguments, whereas builders can add new setters without breaking existing code.
Can we split out the log handling, which I'd merge right away, and keep the event streaming discussion separate?
| fn flush(&self) {} | ||
| } | ||
|
|
||
| static INIT: Once = Once::new(); |
There was a problem hiding this comment.
Not quite sure if Once is the right type here. It should be a Mutex<LogListener>, since otherwise multiple set_logger calls are ignored, which I don't think anybody expects.
a5b00c2 to
11736e6
Compare
NodeState - Unified node_state() snapshot aggregating get_info, list_peer_channels, and list_funds into a single record with balances, capacity, utxos, and connected-channel-peer set. - Reserved UTXOs (in-flight PSBTs) excluded from on-chain balance. FundOutput now carries `reserved` from listfunds.outputs[].reserved. - Closing-channel double-count fixed. Onchain-state channels only contribute to pending_onchain_balance_msat when we initiated the close and our payout is still timelocked (DELAYED_OUTPUT_TO_US); otherwise the payout is already a wallet UTXO and would be counted twice. PeerChannel exposes `closer` (ChannelSide) and `status` to support this gate. - ChannelState::from_i32 unknown fallback now maps to a new ChannelState::Unknown variant rather than Onchain (which silently counted unmapped states as closing). Unknown is treated as neither open nor closing by balance math. gl-sdk-cli output formatter handles the new variant too. - Immature outputs (confirmed but timelocked, e.g. coinbase maturation) surface as immature_onchain_balance_msat instead of being silently dropped. OutputStatus match is exhaustive. - The three underlying RPCs run concurrently via tokio::join!. NodeState aggregate fields: - total_onchain_msat (confirmed + unconfirmed + immature) - total_balance_msat (everything the user owns) - spendable_balance_msat (send-screen gate) - max_chan_reserve_msat (protocol reserve locked across channels) - utxos: Vec<FundOutput> for coin-control UIs Other NodeState changes: - channels_balance_msat / max_payable_msat docstrings explicitly name their roles: home-screen display vs send-button gate. - num_active/pending/inactive_channels docstrings clarify which balance fields each count contributes to. - connected_peers renamed to connected_channel_peers (only lists peers we have a channel with); dedup via HashSet. - fees_collected_msat removed from NodeState (still on GetInfoResponse); it's a routing-node concern. Hex identifier surface - Every identifier (pubkey, payment hash, txid, preimage, funding txid, channel id) on public structs is now a lowercase hex String instead of Vec<u8>. Affected: NodeState, PeerChannel, FundOutput, FundChannel, GetInfoResponse, Peer, Invoice, Pay, Payment, SendResponse, OnchainSendResponse (txid only; raw tx stays bytes), InvoicePaidEvent, ParsedInvoice. - Kept as Vec<u8>: Credentials::load/save bytes, raw on-chain tx bytes, Peer.features bitfield, DeveloperCert/Signer constructor arguments. list_payments fixes - Unpaid (open) and expired invoices are dropped from list_payments. Only Paid invoices appear as Payment rows on the received side; list_invoices() still surfaces the full invoice list for callers that want to inspect open invoices directly. - Payment.destination is documented as always-None for PaymentType::Received: Lightning's privacy model does not reveal the sender's pubkey to the recipient, and the only pubkey derivable from a paid invoice is the payee (our own node), which is uninteresting per-row. PaymentStatus::Pending stays on the public type — it remains valid for in-flight sent payments. NAPI mirrors synced (Buffer→String everywhere the underlying field became hex), gl-sdk-cli output formatters simplified, NodeExtensions defaults adjusted, and Python + Kotlin tests updated to match.
Two callback-based listener APIs for mobile integrators. Logging - LogListener: apps implement on_log(LogEntry) to receive log messages from both gl-sdk (via tracing's log bridge) and gl-client (111 log::*! callsites flow through automatically). - LogEntry carries level, message, target module, and source file + line for easier triage of production issues. - LogLevel variants use Rust PascalCase (Error/Warn/Info/Debug/Trace) matching the ChannelState/OutputStatus/PaymentStatus convention; bindings still render as UPPER_SNAKE per uniffi convention. - set_logger(level, listener) returns Result<(), Error>. First call installs; subsequent calls fail with an explicit error instead of panicking or silently no-op'ing. Internal Once guard removed — log::set_boxed_logger already enforces at-most-once. - set_log_level(level) exported: callers can change the filter at runtime without reinstalling, driving a "verbose logs" UI toggle. Implemented by reading log::max_level() inside Log::enabled so the logger picks up global filter changes with no stored state. - Docstrings call out that on_log runs on the thread that emitted the log — implementations should be cheap and non-blocking. - Exposed to JS via a NAPI wrapper that bridges a ThreadsafeFunction to LogListener (napi4 feature enabled). Node events - NodeEventListener: apps implement on_event(NodeEvent) to receive live events. Current variant: InvoicePaid. Unknown server events are skipped silently so the public enum stays exhaustive for integrator match statements. - Replaces the pull-based NodeEventStream + next() surface entirely. NodeEventStream is gone. - Listener is strictly a connect-time concern: register / recover / connect / register_or_recover each gain an event_listener: Option<Box<dyn NodeEventListener>> parameter. The SDK installs it atomically during node bring-up, so events that fire during the first round of RPCs are not missed. - set_event_listener is pub(crate) — not exposed via uniffi or NAPI. Callers who need post-construction flexibility wire a mutable delegate inside their listener implementation (Flow/LiveData/ObservableObject pattern). - Single listener per Node. Drop for Node aborts the dispatch task so teardown is automatic. - InvoicePaidEvent exposes payment_hash and preimage as lowercase hex strings, matching the mobile-facing hex convention used elsewhere. gl-sdk-cli - StreamEvents subcommand removed along with its NodeEventOutput type and AtomicBool/ctrl-c scaffolding. The pull API it used is gone, and Node::new(creds) has no path to install a listener. Tests - Python test_logging.py verifies LogLevel variants, LogEntry shape, LogListener trait, and that set_log_level is callable. - Kotlin LoggingTest.kt installs the listener in @BeforeClass (handles the "already installed" error gracefully) and asserts that register_or_recover actually drives log entries through the listener with non-empty targets. - AuthApi / ListPayment / NodeOperations instrumented tests updated to pass null for the new event_listener argument.
Returns a pretty-printed JSON envelope { timestamp, node, sdk } where
the node section serializes getinfo/listpeerchannels/listfunds and the
sdk section carries version + node_state. Failed sub-calls are embedded
as { "error": "..." } instead of failing the dump. Adds serde derive on
the response types so each section is real nested JSON, queryable with
jq. Payment and invoice history are intentionally excluded to avoid
leaking preimages, payment hashes, bolt11 strings, and labels into
support dumps.
Two callback-based listener APIs for mobile integrators, both
following the Breez SDK shape.
Logging
from both gl-sdk (via tracing's log bridge) and gl-client (111
log::*! callsites flow through automatically).
for easier triage of production issues.
matching the ChannelState/OutputStatus/PaymentStatus convention;
bindings still render as UPPER_SNAKE per uniffi convention.
installs; subsequent calls are silent no-ops. Returns an error only
when another crate has already installed a log logger, replacing the
previous .expect("logger already set") panic.
LogListener (napi4 feature enabled).
Node events
events (InvoicePaid today; room for more).
NodeEventStream is gone.
register / recover / connect / register_or_recover each gain an
event_listener: Option<Box> parameter. The
SDK installs it atomically during node bring-up, so events that fire
during the first round of RPCs are not missed.
Callers who need post-construction flexibility wire a mutable delegate
inside their listener implementation (Flow/LiveData/ObservableObject
pattern). Matches Breez's EventListener shape.
teardown is automatic.
Tests
accepts a listener.
as a smoke test for cross-language log capture.