Repository navigation
Conversation
|
👋 I see @tankyleo was un-assigned. |
6e39d0d to
86e88ce
Compare
86e88ce to
3de4777
Compare
3de4777 to
9692ff4
Compare
|
🔔 1st Reminder Hey @tnull! This PR has been waiting for your review. |
388d7d2 to
fdf6710
Compare
|
@febyeji @randomlogin Please let me know when you consider merging of the two PRs completed and this is ready for a first round of review! |
22cb2c2 to
817e764
Compare
| } | ||
|
|
||
| /// Broadcast a package of transactions via the P2P network. | ||
| pub(crate) async fn process_broadcast_package(&self, package: Vec<Transaction>) { |
There was a problem hiding this comment.
bip157 version 0.5.0 includes a Package type that may be used here.
@tnull do I understand correctly that Vec<Transaction> is used so the traits are forward compatible for arbitrary package lengths? My crate only supports packages of size 1 and 2, as per the current Bitcoin Core policy.
There was a problem hiding this comment.
Hmm, well, kinda. LDK's trait uses a &[&Transaction] I believe. Generally we prefer to leave any transaction/package validation to Core via. the chain source. Given bip157 is the chain source, it should be fine to use from_vec and log an error if that fails for whatever reason.
8a03367 to
187b18d
Compare
|
I've started to test it in the wild (on mutinynet with ldk-server) and encountered this problem:
Working on that. I'll update the PR with new commits with all the suggestions. |
d394d66 to
10e5c05
Compare
10e5c05 to
74cfbc2
Compare
* Add CBF chain source stubs for starting Add stub methods/functions, add basic build and start of the CBF chain source as well as basic struct containing the fields which undoubtedtly are needed. * Add waiting for gossip propagation in tests Previously tests assumed that the chain source of the lightning node and is node which mines. This is not the case with CBF chain source which needs to wait until after mining a new block a new tips propagates to it. `wait_for_block` is made to return a new height and a new function `wait_for_node_tip` is added which waits until the given height is processed (returned via `status.best_block` ) on a given node. * Populate revealed spks for CBF Ask wallet for revealed spks, register them. Implement `Listen` trait ans add register_script method as well as implementation of registered scripts/outputs. --------- Co-authored-by: Yeji Han <yeji.hvn@gmail.com>
- Implement CBF requester shutdown and mark runtime status stopped on clean exit. - Route generic chain source stop calls to the CBF backend. AI-assisted-by: OpenAI Codex
- Stop runtime-dependent chain sources before waiting for non-cancellable background tasks. - Allow the CBF requester shutdown to unblock the node run loop during Node::stop.
- Check CBF runtime status before publishing a rebuilt requester after restart backoff. - Shut down the newly built requester and exit the restart loop if stop() ran during the backoff.
* Add CBF chain source stubs for starting * Add waiting for gossip propagation in tests * Populate revealed spks for CBF * Implement `process_kyoto_events` and `ChainOp` Co-authored-by: febyeji <yeji.han@sf.snu.ac.kr>
Co-authored-by: febyeji <yeji.han@sf.snu.ac.kr>
Also added env var for the CBF tests, also waiting for tx gossip for broadcast in some of the tests.
* bump kyoto version * Add `synced_to_tip` to CbfSyncState Previously we did not track the `FiltersSynced` kyoto event, so we could not tell when we had applied all blocks up to the tip. For example, when we stop and restart the node, kyoto's tip is 0 at the instant of start (it does not persist its chain), so our applied height trivially matches kyoto's tip and we would falsely conclude we had reached it. That is only actually true once we have received `FiltersSynced`. * Add lookahead addresses to `list_revealed_scripts`. Now the function is called `list_watched_scripts`. * Add timeout to block fetch attempts. Previously stalled fetch would hang indefinitely. --------- Co-authored-by: Alexander Shevtsov <randomlogin76@gmail.com>
After the new kyoto release (v0.6.3) `Indexedfilter` event has a `header` field which is used directly (previously we fetched header as an additional action). Also renamed import of kyoto `Event` into `KyotoEvent` for readability.
The fee-rate cache update moved into a free function that takes `network` directly, so the client no longer reads its `config` field. This tripped `dead_code` under `-D warnings` in CI. Remove the field and the argument threaded through `start`/`new`. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Node might go offline on a reorgnaized block, which would make stall syncing after a restart. This commits adds a preprocessing step which runs once before the events from kyoto are processed. During the preprocessing step we check whether we have forked and disconnect blocks accordingly. Still there might be a problem if several of chain monitors diverged and live on different forks, but other chain sources (bitcoind) ignore that and that perhaps requires a fix upstream.
74cfbc2 to
77d4698
Compare
A CBF node has no trusted node to ask for the chain tip, so a wallet created for the first time started from genesis and had to download every filter on the way up. On a first start, run a short-lived headers-only kyoto node up to the tip, validate the best header through the same helper the bitcoind sources use, and walk back `REORG_SAFETY_BLOCKS` from it. The wallet and the channel manager begin from that block instead of genesis. The walk back is needed because kyoto cannot report a reorg at or below its start checkpoint, and on the first start `resume_checkpoint` has nothing lower to step to. `ChainSource::fetch_stable_tip` is only called by the builder when the CBF source is configured and no tip is known yet. Also move to bip157 0.8.0: `Client::warn_rx` is now a bounded receiver, and `data_dir` no longer exists, so the unused call is removed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Upstream now syncs Lightning state before peers or event processing start, going through `ChainSource::initialize_wallets` for every backend. The CBF source has no separate Lightning sync: `ChainSource::start` already spawns the kyoto node, which feeds all listeners. Hold the node start until the CBF source has caught up to the network tip and applied its blocks, via `wait_until_synced`. This replaces the `todo!()` placeholder left while rebasing onto upstream. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
77d4698 to
fa7e9aa
Compare
CBF has no mempool, so the wallet never sees a transaction it has just broadcast until a block confirms it. Until then BDK's coin selection still treats the inputs as spendable, and a second send could double-spend them. On CBF, apply the transaction as unconfirmed ourselves when we send it (`send_to_address`), replace it (`bump_fee_rbf`, which also evicts the replaced transaction) or fund a channel with it. The funding case runs from the event handler after the channel facts are recorded, so the payment this creates is classified. This replaces the earlier approach that hooked into the now-removed broadcast-time classification; transactions produced by LDK itself (closes, sweeps, anchors) are not applied this way. CBF also has no mempool to tell us a transaction was evicted. Fall back to a stale-broadcast deadline of 336h, matching Bitcoin Core's mempool expiry, for outbound non-funding payments, and mark a dropped payment failed instead of re-creating it as pending. `update_payment_store` takes a reference time for this: the connecting block's own time rather than the wall clock, so replaying many blocks after being offline does not skew the comparison. Both behaviours apply to CBF only. `apply_mempool_txs` becomes available under `chain-cbf` as well as `chain-bitcoind`. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
`chain-cbf` pulled in both `chain-esplora` and `chain-electrum` only because the optional external fee sources need their clients. Make each source depend on its own feature instead, so CBF can be built with either one, both or neither (in which case fee rates are derived from recent blocks). `CbfFeeSourceConfig::Esplora` and `::Electrum`, the matching `FeeSource` variants and the code that uses them are gated on `chain-esplora` and `chain-electrum` respectively. The "at least one chain source feature" check now also counts `chain-cbf`. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1c81591 to
ec136be
Compare
This PR adds a new compact block filter chain source (via kyoto) to ldk-node. Let's go through its features.
Design notes
Contrary to other chain sources, we use a push-based model: we don't proactively request data, but rather listen
for events and process them.
Kyoto
Kyoto is a standalone node which comes in the form of a library. It manages peers and provides users with compact
block filters as well as the ability to request blocks on demand.
Getting data from kyoto is done via a channel which emits events (e.g.
IndexedFilterwhich means we received a newcompact block filter).
Operation
In broad terms we operate in the following way:
There is no concept of "active syncing": when the chain source runs, it always processes new events and applies them.
Method
sync_walletwaits until the application of all blocks up to the known tip.Node aliveness
As we are responsible for always having an operational node, we have its runtime status, and if it goes down by any
reason, we try to restart the kyoto node. If something went wrong and after
MAX_RESTART_RETRIES=5the node still died,we give up.
Scripts
CBFChainSource tracks watched scripts provided from the onchain wallet as well as the lightning wallet.
We need to track two types of scripts: ones provided from the onchain wallet (e.g. to fund the lightning node) and
"LDK-scripts", exposed and needed to be tracked during channel operation (e.g. channel force-close transactions). The
former scripts are revealed via bdk wallet. We don't maintain a CBFChainSource-related copy of them, rather we just ask
bdk wallet each time we need them. This is done to have a single source of truth.
Thus,
registered_scriptsare LDK-revealed scripts.Block applicator
This entity applies blocks to the state of the node. (Perhaps its title can be changed to something more concise.)
It runs as its own background task, decoupled from kyoto event processing via an unbounded channel of
ChainOps:ConnectFull— filter matched, apply the downloaded full block (block_connected)ConnectFiltered— filter didn't match, apply header only (filtered_block_connectedwith no transactions)Disconnect— reorg, disconnect back to the fork pointSynced— kyoto reportedFiltersSynced, i.e. we caught up to the network tipFailed— an unrecoverable error occurred while producing ops (surfaces tosync_walletswaiters)For applying blocks we reuse bitcoind's
ChainListener, which fans each connect/disconnect out to the onchainwallet, channel manager, chain monitor and output sweeper.
process_kyoto_eventsis a single sequential loop: for a matched filter it awaits the full block fetch (withretries/timeout) inline, and for an unmatched one it awaits the header fetch inline, before moving on to the next
kyoto event — this is what ensures ops are produced in order. The block applicator then just consumes those ops
in order and applies them. As a safety net it also tracks the next expected height itself and skips any
out-of-sequence op (e.g. duplicates, or stale ops produced before a reorg); a
Disconnectrewinds the expectedheight to the fork point.
Besides applying blocks, the applicator is the single writer of
CbfSyncState: it advancesapplied_tip/synced_to_tip(whichsync_walletswaits on) and persists the node's wallet-sync timestamps once the tip isreached. When the native kyoto fee source is used, it also opportunistically caches the coinbase-derived fee rate
of every full block it applies, so the fee estimator doesn't have to download those blocks again.
There is room for improvement: we could download full blocks in parallel (e.g. using
https://docs.rs/futures/latest/futures/stream/struct.FuturesOrdered.html), which would speed up initial sync.
We could do without this additional entity, by just processing ops in place, but we stick to this design for
clarity.
Fee source
As we don't have mempool with CBF chain source, we cannot properly estimate the transaction fees.
There are three options for a fee source provider (
FeeSource):Using esplora or electrum just for the fees is privacy-preserving and acceptable.
Using native extrapolation is a fallback method, because it has two flaws:
Also, recently fetched blocks (when we need to download a full block anyway) are cached so they won't be downloaded
twice during a fee update.
Reorganizations
On reorgs kyoto emits events about disconnected blocks and we disconnect them accordingly.
CbfSyncState
We need that struct to distinguish between last block that kyoto has and the last block we have applied locally, these
may be different blocks. Also used to understand whether we processed everything up to kyoto's tip.
Testing
We tried to adhere to already existing tests, as they cover the vital node operation scenarios. Though some of the tests
are not meant for kyoto (for example tests related to mempool, 0-conf channels, RBF).
As we want to test exclusively the new chain source in this PR, we've added an environment variable overriding the
random chain source choice, it can be used on the following way:
Other available options:
esplora,electrum,bitcoind-rpc,bitcoind-rest.Also please note sometimes in test scenarios kyoto bans bitcoind because of this bug:
bitcoin/bitcoin#35354.
Previously tests assumed that the chain source and the mining node are the same entity, i.e. there is no gossip delay.
This is not the case with the kyoto node, that's why one might experience a race condition due to gossip propagation
(we do something, like mine a block with a needed transaction, then it is correctly shown in esplora/electrum/bitcoind
and we try to invoke some action, but the new block has not yet propagated to the kyoto node); the situation is
worsened by the fact that GitHub's CI runners are slower than local machines, and a simple time-based wait does not
work well to circumvent the propagation delay. That's why many functions in the tests have additional waiting for
propagation.
Tests not comptible with the compact block filter node:
History
We switched to bitcoind's sync model (
poll_and_update_listenervssync_onchain_walletandsync_lightning_wallet).We tried to follow the patterns for already existing chain sources, more precisely we had distinct
sync_onchain_walletand
sync_lightning_wallet(dictated by transaction-based syncing sources) functions however it is suboptimal, becausewe might fetch the same block twice.