Skip to content

Blockchain Data

In this guide, we'll explore how to provide chain data to LDK upon startup and as new blocks are mined. This allows LDK to maintain channel state and monitor for on-chain channel activity.

LDK maintains channels with your node's peers during the course of node operation. When a new channel is opened, the ChannelManager will keep track of the channel's state and tell the ChainMonitor that a new channel should be watched. The ChainMonitor does so by maintaining a ChannelMonitor for each channel.

When a new block is mined, it is connected to the chain while other blocks may be disconnected if reorganized out. Transactions are confirmed or unconfirmed during this process. You are required to feed this activity to LDK which will process it by:

  • Updating channel state
  • Signaling back transactions to filter
  • Broadcasting transactions if necessary

Chain Activity

Initially, our node doesn't have any channels and hence has no data to monitor for on-chain. When a channel is opened with a peer, the ChannelManager creates a ChannelMonitor and passes it to the ChainMonitor to watch.

At this point, you need to feed LDK any chain data of interest so that it can respond accordingly. It supports receiving either full blocks or pre-filtered blocks using the chain::Listen interface. While block data can be sourced from anywhere, it is your responsibility to call the block_connected and block_disconnected methods on ChannelManager and ChainMonitor. This allows them to update channel state and respond to on-chain events, respectively.

LDK comes with a lightning-block-sync utility that handles polling a block source for the best chain tip, detecting chain forks, and notifying listeners when blocks are connected and disconnected. It can be configured to:

  • Poll a custom BlockSource
  • Notify ChannelManager and ChainMonitor of block events

It is your choice as to whether you use this utility or your own to feed the required chain data to LDK. If you choose to use it, you will need to implement the BlockSource interface or use one of the samples that it provides.

Note

Currently, lightning-block-sync is only available in Rust.

Block Source

Implementing the BlockSource interface requires defining methods for fetching headers, blocks, and the best block hash. As noted above, lightning-block-sync is only available in Rust.

rust
impl BlockSource for Blockchain {
	fn get_header<'a>(&'a mut self, header_hash: &'a BlockHash, _height: Option<u32>) -> AsyncBlockSourceResult<'a, BlockHeaderData> {
		// <insert code for fetching block headers>
	}

	// Note: `get_block` returns `BlockData` (it can yield either a full block
	// or just the header, supporting pre-filtered block sources).
	fn get_block<'a>(&'a mut self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, BlockData> {
		// <insert code for fetching block>
	}

	fn get_best_block<'a>(&'a mut self) -> AsyncBlockSourceResult<'a, (BlockHash, Option<u32>)> {
		// <insert code for fetching the best block hash>
	}
}

For instance, you may implement this interface by querying Bitcoin Core's JSON RPC interface, which happens to be a sample implementation provided by lightning-block-sync.

Let's walk through the use case where LDK receives full blocks.

Full Blocks

If your Lightning node is backed by a Bitcoin full node, the operation is straight forward: call the appropriate methods on ChannelManager and ChainMonitor as blocks are connected and disconnected. LDK will handle the rest!

So what happens? The ChannelManager examines the block's transactions and updates the internal channel state as needed. The ChainMonitor will detect any spends of the channel funding transaction or any pertinent transaction outputs, tracking them as necessary.

If necessary, LDK will broadcast a transaction on your behalf. More on that later. For now, let's look at the more interesting case of pre-filtered blocks.

Pre-filtered Blocks

For environments that are resource constrained, receiving and processing all transaction data may not be feasible. LDK handles this case by signaling back which transactions and outputs it is interested in. This information can then be used to filter blocks prior to sending them to your node.

For example, if your block source is an Electrum client, you can pass along this information to it. Or, if you are making use of a BIP 157 client, you can check if a block contains relevant transactions before fetching it.

So how does this work in practice? ChainMonitor is parameterized by an optional type that implements chain::Filter:

rust
impl chain::Filter for Blockchain {
	fn register_tx(&self, txid: &Txid, script_pubkey: &Script) {
		// <insert code for you to watch for this transaction on-chain>
	}

	fn register_output(&self, output: WatchedOutput) {
		// <insert code for you to watch for any transactions that spend this
		// output on-chain>
	}
}
kotlin
val txFilter = Filter.new_impl(object : Filter.FilterInterface {
	override fun register_tx(txid: ByteArray, scriptPubkey: ByteArray) {
		// <insert code for you to watch for this transaction on-chain>
	}

	override fun register_output(output: WatchedOutput) {
		// <insert code for you to watch for any transactions that spend this
		// output on-chain>
	}
})
typescript
import * as ldk from "lightningdevkit";

const txFilter = ldk.Filter.new_impl({
	register_tx(txid: Uint8Array, scriptPubkey: Uint8Array): void {
		// <insert code for you to watch for this transaction on-chain>
	},
	register_output(output: ldk.WatchedOutput): void {
		// <insert code for you to watch for any transactions that spend this output>
	},
} as ldk.FilterInterface);

When this is provided, ChainMonitor will call back to the filter as channels are opened and blocks connected. This gives the opportunity for the source to pre-filter blocks as desired.

Regardless, when a block is connected, its header must be processed by LDK.

Confirmed Transactions

Up until this point, we've explored how to notify LDK of chain activity using blocks. But what if you're sourcing chain activity from a place that doesn't provide a block-centric interface, like Electrum or Esplora?

LDK's ChannelManager and ChainMonitor implement a chain::Confirm interface to support this use case, analogous to the block-oriented chain::Listen interface which we've been using up until now. With this alternative approach, you still need to give LDK information about chain activity, but only for transactions of interest. To this end, you must call Confirm::transactions_confirmed when any transactions identified by chain::Filter's register_tx/register_output methods are confirmed.

You also need to notify LDK of any transactions with insufficient confirmations that have been reorganized out of the chain. Transactions that need to be monitored for such reorganization are returned by Confirm::get_relevant_txids. If any of these transactions become unconfirmed, you must call Confirm::transaction_unconfirmed.

Lastly, you must notify LDK whenever a new chain tip is available using the Confirm::best_block_updated method. See the documentation for a full picture of how this interface is intended to be used.

Note

Note that the described methods of Confirm must be called in accordance with the ordering requirements described in the Confirm documentation

Note

Note that the described methods of Confirm must be called both on the ChannelManager and the ChainMonitor.

Note

For Electrum and Esplora backends, LDK provides the lightning-transaction-sync crate, which drives the Confirm interface for you via its EsploraSyncClient and ElectrumSyncClient. You register interest through the Filter it exposes, then call sync with the ChannelManager and ChainMonitor.

Transaction Broadcasting

Inevitably, LDK will need to broadcast transactions on your behalf. As you notify it of blocks, it will determine if it should broadcast a transaction and do so using an implementation of BroadcasterInterface that you have provided.

And as those transactions or those from your peers are confirmed on-chain, they will be likewise processed when notified of a connected block. Thus, continuing the cycle.