# Setting up a ChannelManager

The ChannelManager is responsible for several tasks related to managing channel state. This includes keeping track of many channels, sending messages to appropriate channels, creating channels and more.

# Adding a ChannelManager

Adding a ChannelManager to your application should look something like this:

  • Rust
  • Kotlin
  • Swift
use lightning::ln::channelmanager;

let channel_manager = ChannelManager::new(
  &fee_estimator,
  &chain_monitor,
  &broadcaster,
  &router,
  &logger,
  &entropy_source,
  &node_signer,
  &signer_provider,
  user_config,
  chain_params,
  current_timestamp
);

There are a few dependencies needed to get this working. Let's walk through setting up each one so we can plug them into our ChannelManager.

# Initialize the FeeEstimator

What it's used for: estimating fees for on-chain transactions that LDK wants broadcasted.

  • Rust
  • Kotlin
  • Swift
struct YourFeeEstimator();

impl FeeEstimator for YourFeeEstimator {
  fn get_est_sat_per_1000_weight(
      &self, confirmation_target: ConfirmationTarget,
  ) -> u32 {
      match confirmation_target {
          ConfirmationTarget::Background => {
              // Fetch background feerate,
              // You can add the code here for this case
          }
          ConfirmationTarget::Normal => {
              // Fetch normal feerate (~6 blocks)
              // You can add the code here for this case
          }
          ConfirmationTarget::HighPriority => {
              // Fetch high priority feerate
              // You can add the code here for this case
          }
      }
  }
}

let fee_estimator = YourFeeEstimator();

Implementation notes:

  1. Fees must be returned in: satoshis per 1000 weight units
  2. Fees must be no smaller than 253 (equivalent to 1 satoshi/vbyte, rounded up)
  3. To reduce network traffic, you may want to cache fee results rather than retrieving fresh ones every time

Dependencies: none

References: Rust FeeEstimator docs (opens new window), Java/Kotlin FeeEstimator bindings (opens new window)

# Initialize the Router

What it's used for: Finds a Route for a payment between the given payer and a payee.

  • Rust
  • Kotlin
  • Swift
let router = DefaultRouter::new(
  network_graph.clone(),
  logger.clone(),
  keys_manager.get_secure_random_bytes(),
  scorer.clone(),
  ProbabilisticScoringFeeParameters::default()
)

Dependencies: P2PGossipSync, Logger, KeysManager, Scorer

References: Rust Router docs (opens new window), Java/Kotlin Router bindings (opens new window)

# Initialize the Logger

What it's used for: LDK logging

  • Rust
  • Kotlin
  • Swift
struct YourLogger();

impl Logger for YourLogger {
  fn log(&self, record: &Record) {
      let raw_log = record.args.to_string();
      let log = format!(
          "{} {:<5} [{}:{}] {}\n",
          OffsetDateTime::now_utc().format("%F %T"),
          record.level.to_string(),
          record.module_path,
          record.line,
          raw_log
      );
      // <insert code to print this log and/or write this log to disk>
  }
}

let logger = YourLogger();

Implementation notes: you'll likely want to write the logs to a file for debugging purposes.

Dependencies: none

References: Rust Logger docs (opens new window), Java/Kotlin Logger bindings (opens new window)

# Initialize the BroadcasterInterface

What it's used for: broadcasting various transactions to the bitcoin network

  • Rust
  • Kotlin
  • Swift
struct YourTxBroadcaster();

impl BroadcasterInterface for YourTxBroadcaster {
    fn broadcast_transactions(&self, txs: &[&Transaction]) {
        // <insert code to broadcast a list of transactions>
    }
}

let broadcaster = YourTxBroadcaster();

Dependencies: none

References: Rust BroadcasterInterface docs (opens new window), Java/Kotlin BroadcasterInterface bindings (opens new window)

# Initialize Persist

What it's used for: persisting ChannelMonitors, which contain crucial channel data, in a timely manner

  • Rust
  • Kotlin
  • Swift
struct YourPersister();

impl<ChannelSigner: Sign> Persist for YourPersister {
    fn persist_new_channel(
        &self, id: OutPoint, data: &ChannelMonitor<ChannelSigner>
    ) -> Result<(), ChannelMonitorUpdateErr> {
        // <insert code to persist the ChannelMonitor to disk and/or backups>
        // Note that monitor.encode() will get you the ChannelMonitor as a
        // Vec<u8>.
    }

  fn update_persisted_channel(
        &self,
        id: OutPoint,
        update: &ChannelMonitorUpdate,
        data: &ChannelMonitor<ChannelSigner>
    ) -> Result<(), ChannelMonitorUpdateErr> {
        // <insert code to persist either the ChannelMonitor or the
        //  ChannelMonitorUpdate to disk>
    }
}

let persister = YourPersister();
  • Using LDK Sample Filesystem Persistence Crate in Rust
use lightning_persister::FilesystemPersister; // import LDK sample persist crate

let persister = FilesystemPersister::new(ldk_data_dir_path);

Implementation notes:

  • ChannelMonitors are objects which are capable of responding to on-chain events for a given channel. Thus, you will have one ChannelMonitor per channel. They are persisted in real-time and the Persist methods will block progress on sending or receiving payments until they return. You must ensure that ChannelMonitors are durably persisted to disk before returning or you may lose funds.
  • If you implement a custom persister, it's important to read the trait docs (linked in References) to make sure you satisfy the API requirements, particularly for update_persisted_channel

Dependencies: none

References: Rust Persister docs (opens new window), Java/Kotlin Persister bindings (opens new window)

# Start Background Processing

What it's used for: running tasks periodically in the background to keep LDK operational.

  • Rust
let background_processor = BackgroundProcessor::start(
  persister,
  Arc::clone(&invoice_payer),
  Arc::clone(&chain_monitor),
  Arc::clone(&channel_manager),
  Arc::clone(&net_graph_msg_handler),
  Arc::clone(&peer_manager),
  Arc::clone(&logger),
);

Dependencies: ChannelManager, ChainMonitor, PeerManager, Logger

References: Rust BackgroundProcessor::Start docs (opens new window)

# Regularly Broadcast Node Announcement

What it's used for: if you have 1 or more public channels, you may need to announce your node and its channels occasionally. LDK will automatically announce channels when they are created, but there are no guarantees you have connected peers at that time or that your peers will propagate such announcements. The broader node-announcement message is not automatically broadcast.

  • Rust
let mut interval = tokio::time::interval(Duration::from_secs(60));
loop {
	interval.tick().await;
	channel_manager.broadcast_node_announcement(
		[0; 3], // insert your node's RGB color
		node_alias,
		vec![ldk_announced_listen_addr],
	);
}

Dependencies: Peer Manager

References: PeerManager::broadcast_node_announcement docs (opens new window)

# Optional: Initialize the Transaction Filter

You must follow this step if: you are not providing full blocks to LDK, i.e. if you're using BIP 157/158 or Electrum as your chain backend

What it's used for: if you are not providing full blocks, LDK uses this object to tell you what transactions and outputs to watch for on-chain.

  • Rust
  • Kotlin
  • Swift
struct YourTxFilter();

impl Filter for YourTxFilter {
  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) ->
        Option<(usize, Transaction)> {
        // <insert code for you to watch for any transactions that spend this
        // output on-chain>
    }
}

let filter = YourTxFilter();

Implementation notes: see the Blockchain Data guide for more info

Dependencies: none

References: Rust Filter docs (opens new window), Java/Kotlin Filter bindings (opens new window)

# Initialize the ChainMonitor

What it's used for: tracking one or more ChannelMonitors and using them to monitor the chain for lighting transactions that are relevant to our node, and broadcasting transactions if need be.

  • Rust
  • Kotlin
  • Swift
let filter: Option<Box<dyn Filter>> = // leave this as None or insert the Filter trait object

let chain_monitor = ChainMonitor::new(filter, &broadcaster, &logger, &fee_estimator, &persister);

Implementation notes: Filter must be non-None if you're using Electrum or BIP 157/158 as your chain backend

Dependencies: FeeEstimator, Logger, BroadcasterInterface, Persist

Optional dependency: Filter

References: Rust ChainMonitor docs (opens new window), Java/Kotlin ChainMonitor bindings (opens new window)

# Initialize the KeysManager

What it's used for: providing keys for signing Lightning transactions

  • Rust
  • Kotlin
  • Swift
let keys_seed_path = format!("{}/keys_seed", ldk_data_dir.clone());

// If we're restarting and already have a key seed, read it from disk. Else,
// create a new one.
let keys_seed = if let Ok(seed) = fs::read(keys_seed_path.clone()) {
    assert_eq!(seed.len(), 32);
    let mut key = [0; 32];
    key.copy_from_slice(&seed);
    key
} else {
    let mut key = [0; 32];
    thread_rng().fill_bytes(&mut key);
    match File::create(keys_seed_path.clone()) {
        Ok(mut f) => {
            f.write_all(&key)
                .expect("Failed to write node keys seed to disk");
            f.sync_all().expect("Failed to sync node keys seed to disk");
        }
        Err(e) => {
            println!(
                "ERROR: Unable to create keys seed file {}: {}",
                keys_seed_path, e
            );
            return;
        }
    }
    key
};

let cur = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
let keys_manager = KeysManager::new(&keys_seed, cur.as_secs(), cur.subsec_nanos());

Implementation notes:

  • See the Key Management guide for more info
  • Note that you must write the key_seed you give to the KeysManager on startup to disk, and keep using it to initialize the KeysManager every time you restart. This key_seed is used to derive your node's secret key (which corresponds to its node pubkey) and all other secret key material.
  • The current time is part of the KeysManager's parameters because it is used to derive random numbers from the seed where required, to ensure all random generation is unique across restarts.

Dependencies: random bytes

References: Rust KeysManager docs (opens new window), Java/Kotlin KeysManager bindings (opens new window)

# Read ChannelMonitor state from disk

What it's used for: if LDK is restarting and has at least 1 channel, its ChannelMonitors will need to be (1) fed to the ChannelManager and (2) synced to chain.

  • Rust
  • Kotlin
  • Swift
// Use LDK's sample persister crate provided method
let mut channel_monitors =
  persister.read_channelmonitors(keys_manager.clone()).unwrap();

// If you are using Electrum or BIP 157/158, you must call load_outputs_to_watch
// on each ChannelMonitor to prepare for chain synchronization.
for chan_mon in channel_monitors.iter() {
    chan_mon.load_outputs_to_watch(&filter);
}

Dependencies: KeysManager

References: Rust load_outputs_to_watch docs (opens new window)

# Initialize the ChannelManager

What it's used for: managing channel state

  • Rust
  • Kotlin
  • Swift
let user_config = UserConfig::default();

/* RESTARTING */
let (channel_manager_blockhash, mut channel_manager) = {
    let channel_manager_file = fs::File::open(format!("{}/manager", ldk_data_dir.clone())).unwrap();

    // Use the `ChannelMonitors` we read from disk.
    let mut channel_monitor_mut_references = Vec::new();
    for (_, channel_monitor) in channel_monitors.iter_mut() {
        channel_monitor_mut_references.push(channel_monitor);
    }
    let read_args = ChannelManagerReadArgs::new(
        &keys_manager,
        &fee_estimator,
        &chain_monitor,
        &broadcaster,
        &logger,
        user_config,
        channel_monitor_mut_references,
    );
    <(BlockHash, ChannelManager)>::read(&mut channel_manager_file, read_args).unwrap()
};

/* FRESH CHANNELMANAGER */

let (channel_manager_blockhash, mut channel_manager) = {
    let best_blockhash = // insert the best blockhash you know of
    let best_chain_height = // insert the height corresponding to best_blockhash
    let chain_params = ChainParameters {
        network: Network::Testnet, // substitute this with your network
        best_block: BestBlock::new(best_blockhash, best_chain_height),
    };
    let fresh_channel_manager = ChannelManager::new(
        &fee_estimator,
        &chain_monitor,
        &broadcaster,
        &router,
        &logger,
        &entropy_source,
        &node_signer,
        &signer_provider,
        user_config,
        chain_params,
        current_timestamp
      );
    (best_blockhash, fresh_channel_manager)
};

Implementation notes: No methods should be called on ChannelManager until after the ChannelMonitors and ChannelManager are synced to the chain tip (next step).

Dependencies: KeysManager, FeeEstimator, ChainMonitor, BroadcasterInterface, Logger

References: Rust ChannelManager docs (opens new window), Java/Kotlin ChannelManager bindings (opens new window)

# Sync ChannelMonitors and ChannelManager to chain tip

What it's used for: this step is only necessary if you're restarting and have open channels. This step ensures that LDK channel state is up-to-date with the bitcoin blockchain

Example:

  • Rust
  • Kotlin
  • Swift
// Full Blocks or BIP 157/158

use lightning_block_sync::init;
use lightning_block_sync::poll;
use lightning_block_sync::UnboundedCache;

impl lightning_block_sync::BlockSource for YourChainBackend {
  fn get_header<'a>(
      &'a mut self, header_hash: &'a BlockHash, height_hint: Option<u32>,
  ) -> AsyncBlockSourceResult<'a, BlockHeaderData> {
      // <insert code to retrieve the header corresponding to header_hash>
  }

  fn get_block<'a>(
      &'a mut self, header_hash: &'a BlockHash,
  ) -> AsyncBlockSourceResult<'a, Block> {
      // <insert code to retrieve the block corresponding to header_hash>
  }

  fn get_best_block<'a>(&'a mut self) ->
      AsyncBlockSourceResult<(BlockHash, Option<u32>)> {
      // <insert code to retrieve your best-known block hash and height>
  }
}

let block_source = YourChainBackend::new();

let mut chain_listener_channel_monitors = Vec::new();
let mut cache = UnboundedCache::new();
let mut chain_tip: Option<poll::ValidatedBlockHeader> = None;
let mut chain_listeners = vec![(
  channel_manager_blockhash,
  &mut channel_manager as &mut dyn chain::Listen,
)];

for (blockhash, channel_monitor) in channel_monitors.drain(..) {
  let outpoint = channel_monitor.get_funding_txo().0;
  chain_listener_channel_monitors.push((
      blockhash,
      (
          channel_monitor,
          &broadcaster,
          &fee_estimator,
          &logger,
      ),
      outpoint,
  ));
}

for monitor_listener_info in chain_listener_channel_monitors.iter_mut() {
  chain_listeners.push((
      monitor_listener_info.0,
      &mut monitor_listener_info.1 as &mut dyn chain::Listen,
  ));
}

// Save the chain tip to be used in future steps
chain_tip = Some(
  init::synchronize_listeners(
      &mut block_source,
      Network::Testnet,
      &mut cache,
      chain_listeners,
  )
  .await
  .unwrap(),
);


Implementation notes:

There are 2 main options for synchronizing to chain on startup:

Full Blocks or BIP 157/158

If you are connecting full blocks or using BIP 157/158, then it is recommended to use LDK's lightning_block_sync crate as in the example above: the high-level steps that must be done for both ChannelManager and each ChannelMonitor are as follows:

  1. Get the last blockhash that each object saw.
    • Receive the latest block hash when through deserializtion (opens new window) of the ChannelManager via read()
    • Each ChannelMonitor's is in channel_manager.channel_monitors, as the 2nd element in each tuple
  2. For each object, if its latest known blockhash has been reorged out of the chain, then disconnect blocks using channel_manager.as_Listen().block_disconnected(..) or channel_monitor.block_disconnected(..) until you reach the last common ancestor with the main chain.
  3. For each object, reconnect blocks starting from the common ancestor until it gets to your best known chain tip using channel_manager.as_Listen().block_connected(..) and/or channel_monitor.block_connected(..).
  4. Call channel_manager.chain_sync_completed(..) to complete the initial sync process.

Electrum/Esplora

Alternatively, you can use LDK's lightning-transaction-sync crate. This provides utilities for syncing LDK via the transaction-based Confirm interface.

# Optional: Initialize P2PGossipSync or RapidGossipSync

You must follow this step if: you need LDK to provide routes for sending payments (i.e. you are not providing your own routes)

What it's used for: generating routes to send payments over

  • Rust
  • Kotlin
  • Swift
let genesis = genesis_block(Network::Testnet).header.block_hash();
let network_graph_path = format!("{}/network_graph", ldk_data_dir.clone());
let network_graph = Arc::new(disk::read_network(Path::new(&network_graph_path), genesis, logger.clone()));
let gossip_sync = Arc::new(P2PGossipSync::new(
  Arc::clone(&network_graph),
  None::<Arc<dyn chain::Access + Send + Sync>>,
  logger.clone(),
));

Implementation notes: this struct is not required if you are providing your own routes. It will be used internally in ChannelManager to build a NetworkGraph. Network options include: Mainnet,Regtest,Testnet,Signet

Dependencies: Logger

Optional dependency: Access, a source of chain information. Recommended to be able to verify channels before adding them to the internal network graph.

References: Rust P2PGossipSync docs (opens new window), Access docs (opens new window), Java/Kotlin P2PGossipSync bindings (opens new window), Rust RapidGossipSync docs (opens new window), Java/Kotlin RapidGossipSync bindings (opens new window)

# Optional: Initialize Probabilistic Scorer

What it's used for: to find a suitable payment path to reach the destination.

  • Rust
  • Kotlin
  • Swift
let network_graph_path = format!("{}/network_graph", ldk_data_dir.clone());
let network_graph = Arc::new(disk::read_network(Path::new(&network_graph_path), args.network, logger.clone()));

let scorer_path = format!("{}/scorer", ldk_data_dir.clone());
let scorer = Arc::new(RwLock::new(disk::read_scorer(
    Path::new(&scorer_path),
    Arc::clone(&network_graph),
    Arc::clone(&logger),
)));

Dependencies: NetworkGraph

References: Rust ProbabilisticScorer docs (opens new window), Java/Kotlin ProbabilisticScorer docs (opens new window)

Last Updated: 11/18/2024, 6:18:24 PM