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
to your application should look something like this:
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
);
import org.ldk.batteries.ChannelManagerConstructor
val channelManagerConstructor = ChannelManagerConstructor(
Network.LDKNetwork_Regtest,
userConfig,
latestBlockHash,
latestBlockHeight,
keysManager.as_EntropySource(),
keysManager.as_NodeSigner(),
keysManager.as_SignerProvider(),
feeEstimator,
chainMonitor,
router,
scoringParams,
routerWrapper, // optional
txBroadcaster,
logger
);
import LightningDevKit
let channelManagerConstructionParameters = ChannelManagerConstructionParameters(
config: userConfig,
entropySource: keysManager.asEntropySource(),
nodeSigner: keysManager.asNodeSigner(),
signerProvider: keysManager.asSignerProvider(),
feeEstimator: feeEstimator,
chainMonitor: chainMonitor,
txBroadcaster: broadcaster,
logger: logger,
enableP2PGossip: true,
scorer: scorer
)
let channelManagerConstructor = ChannelManagerConstructor(
network: network,
currentBlockchainTipHash: latestBlockHash,
currentBlockchainTipHeight: latestBlockHeight,
netGraph: netGraph,
params: channelManagerConstructionParameters
)
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
.
What it's used for: estimating fees for on-chain transactions that LDK wants broadcasted.
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();
object YourFeeEstimator : FeeEstimatorInterface {
override fun get_est_sat_per_1000_weight(confirmationTarget: ConfirmationTarget?): Int {
if (confirmationTarget == ConfirmationTarget.LDKConfirmationTarget_Background) {
// <insert code to retrieve a background feerate>
}
if (confirmationTarget == ConfirmationTarget.LDKConfirmationTarget_Normal) {
// <insert code to retrieve a normal (i.e. within ~6 blocks) feerate>
}
if (confirmationTarget == ConfirmationTarget.LDKConfirmationTarget_HighPriority) {
// <insert code to retrieve a high-priority feerate>
}
// return default fee rate
}
}
val feeEstimator: FeeEstimator = FeeEstimator.new_impl(YourFeeEstimator)
class MyFeeEstimator: FeeEstimator {
override func getEstSatPer1000Weight(confirmationTarget: Bindings.ConfirmationTarget) -> UInt32 {
if confirmationTarget == .Background {
// Fetch Background Feerate
} else if confirmationTarget == .Normal {
// Fetch Normal Feerate (~6 blocks)
} else if confirmationTarget == .HighPriority {
// Fetch High Feerate
}
// Fetch Default Feerate
}
}
let feeEstimator = MyFeeEstimator()
Implementation notes:
- Fees must be returned in: satoshis per 1000 weight units
- Fees must be no smaller than 253 (equivalent to 1 satoshi/vbyte, rounded up)
- To reduce network traffic, you may want to cache fee results rather than retrieving fresh ones every time
Dependencies: none
References: Rust FeeEstimator
docs, Java/Kotlin FeeEstimator
bindings
What it's used for: Finds a Route for a payment between the given payer and a payee.
let router = DefaultRouter::new(
network_graph.clone(),
logger.clone(),
keys_manager.get_secure_random_bytes(),
scorer.clone(),
ProbabilisticScoringFeeParameters::default()
)
val networkGraph = NetworkGraph.of(Network.LDKNetwork_Regtest, logger)
let netGraph = NetworkGraph(network: .Regtest, logger: logger)
Dependencies: P2PGossipSync
, Logger
, KeysManager
, Scorer
References: Rust Router
docs, Java/Kotlin Router
bindings
What it's used for: LDK logging
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();
object YourLogger : LoggerInterface {
override fun log(record: Record?) {
// <insert code to print this log and/or write this log to a file>
}
}
val logger: Logger = Logger.new_impl(YourLogger)
class MyLogger: Logger {
override func log(record: Bindings.Record) {
// Print and/or write the log to a file
}
}
let logger = MyLogger()
Implementation notes: you'll likely want to write the logs to a file for debugging purposes.
Dependencies: none
References: Rust Logger
docs, Java/Kotlin Logger
bindings
What it's used for: broadcasting various transactions to the bitcoin network
struct YourTxBroadcaster();
impl BroadcasterInterface for YourTxBroadcaster {
fn broadcast_transactions(&self, txs: &[&Transaction]) {
// <insert code to broadcast a list of transactions>
}
}
let broadcaster = YourTxBroadcaster();
object YourTxBroadcaster: BroadcasterInterface.BroadcasterInterfaceInterface {
override fun broadcast_transactions(txs: Array<out ByteArray>??) {
// <insert code to broadcast a list of transactions>
}
}
val txBroadcaster: BroadcasterInterface = BroadcasterInterface.new_impl(YourTxBroadcaster)
class YourTxBroacaster: BroadcasterInterface {
override func broadcastTransactions(txs: [[UInt8]]) {
// Insert code to broadcast a list of transactions
}
}
let broadcaster = YourTxBroacaster()
Dependencies: none
References: Rust BroadcasterInterface
docs, Java/Kotlin BroadcasterInterface
bindings
What it's used for: persisting ChannelMonitor
s, which contain crucial channel data, in a timely manner
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();
object YourPersister: Persist.PersistInterface {
override fun persist_new_channel(
id: OutPoint?, data: ChannelMonitor?, updateId: MonitorUpdateId?
): Result_NoneChannelMonitorUpdateErrZ? {
// <insert code to write these bytes to disk, keyed by `id`>
}
override fun update_persisted_channel(
id: OutPoint?, update: ChannelMonitorUpdate?, data: ChannelMonitor?,
updateId: MonitorUpdateId
): Result_NoneChannelMonitorUpdateErrZ? {
// <insert code to update the `ChannelMonitor`'s file on disk with these
// new bytes, keyed by `id`>
}
}
val persister: Persist = Persist.new_impl(YourPersister)
class MyPersister: Persist {
override func persistNewChannel(channelId: OutPoint, data: ChannelMonitor, updateId: MonitorUpdateId) -> Bindings.ChannelMonitorUpdateStatus {
// Insert the code to persist the ChannelMonitor to disk
}
override func updatePersistedChannel(channelId: OutPoint, update: ChannelMonitorUpdate, data: ChannelMonitor, updateId: MonitorUpdateId) -> ChannelMonitorUpdateStatus {
// Insert the code to persist either ChannelMonitor or ChannelMonitorUpdate to disk
}
}
let persister = MyPersister()
use lightning_persister::FilesystemPersister; // import LDK sample persist crate
let persister = FilesystemPersister::new(ldk_data_dir_path);
Implementation notes:
ChannelMonitor
s are objects which are capable of responding to on-chain events for a given channel. Thus, you will have oneChannelMonitor
per channel. They are persisted in real-time and thePersist
methods will block progress on sending or receiving payments until they return. You must ensure thatChannelMonitor
s 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, Java/Kotlin Persister
bindings
What it's used for: running tasks periodically in the background to keep LDK operational.
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
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.
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
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.
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();
object YourTxFilter : Filter.FilterInterface {
override fun register_tx(txid: ByteArray, script_pubkey: 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>
}
}
val txFilter: Filter = Filter.new_impl(YourTxFilter)
class MyFilter: Filter {
override func registerTx(txid: [UInt8]?, scriptPubkey: [UInt8]) {
// Insert code to watch this transaction
}
override func registerOutput(output: Bindings.WatchedOutput) {
// Insert code to watch for any transaction that spend this output
}
}
let filter = MyFilter()
Implementation notes: see the Blockchain Data guide for more info
Dependencies: none
References: Rust Filter
docs, Java/Kotlin Filter
bindings
What it's used for: tracking one or more ChannelMonitor
s and using them to monitor the chain for lighting transactions that are relevant to our node, and broadcasting transactions if need be.
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);
val filter : Filter = // leave this as `null` or insert the Filter object.
val chainMonitor = ChainMonitor.of(filter, txBroadcaster, logger, feeEstimator, persister)
let filter: Filter = // leave this as `nil` or insert the Filter object.
let chainMonitor = ChainMonitor(
chainSource: filter,
broadcaster: broadcaster,
logger: logger,
feeest: feeEstimator,
persister: 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, Java/Kotlin ChainMonitor
bindings
What it's used for: providing keys for signing Lightning transactions
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());
val keySeed = ByteArray(32)
// <insert code to fill key_seed with random bytes OR if restarting, reload the
// seed from disk>
val keysManager = KeysManager.of(
keySeed,
System.currentTimeMillis() / 1000,
(System.currentTimeMillis() * 1000).toInt()
)
let seed = // Insert code to create seed with random bytes or if restarting, reload the seed from disk
let timestampSeconds = UInt64(NSDate().timeIntervalSince1970)
let timestampNanos = UInt32.init(truncating: NSNumber(value: timestampSeconds * 1000 * 1000))
let keysManager = KeysManager(seed: seed, startingTimeSecs: timestampSeconds, startingTimeNanos: timestampNanos)
Implementation notes:
- See the Key Management guide for more info
- Note that you must write the
key_seed
you give to theKeysManager
on startup to disk, and keep using it to initialize theKeysManager
every time you restart. Thiskey_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, Java/Kotlin KeysManager
bindings
What it's used for: if LDK is restarting and has at least 1 channel, its ChannelMonitor
s will need to be (1) fed to the ChannelManager
and (2) synced to chain.
// 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);
}
// Initialize the hashmap where we'll store the `ChannelMonitor`s read from disk.
// This hashmap will later be given to the `ChannelManager` on initialization.
var channelMonitors = arrayOf<ByteArray>();
val channelMonitorList = ArrayList<ByteArray>()
channelMonitorFiles.iterator().forEach {
val channelMonitorBytes = it.hexStringToByteArray();
channelMonitorList.add(channelMonitorBytes);
}
channelMonitors = channelMonitorList.toTypedArray();
// Initialize the array where we'll store the `ChannelMonitor`s read from disk.
// This array will later be given to the `ChannelManagerConstructor` on initialization.
var serializedChannelMonitors: [[UInt8]] = []
let allChannels = // Insert code to get a list of persisted channels
for channel in allChannels {
let channelData = try Data(contentsOf: channel)
let channelBytes = [UInt8](channelData)
serializedChannelMonitors.append(channelBytes)
}
Dependencies: KeysManager
References: Rust load_outputs_to_watch
docs
What it's used for: managing channel state
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)
};
if (serializedChannelManager != null && serializedChannelManager.isNotEmpty()) {
// Loading from disk (restarting)
val channelManagerConstructor = ChannelManagerConstructor(
serializedChannelManager,
serializedChannelMonitors,
userConfig,
keysManager!!.inner.as_EntropySource(),
keysManager!!.inner.as_NodeSigner(),
SignerProvider.new_impl(keysManager!!.signerProvider),
feeEstimator,
chainMonitor,
txFilter,
networkGraph!!.write(),
ProbabilisticScoringDecayParameters.with_default(),
ProbabilisticScoringFeeParameters.with_default(),
scorer!!.write(),
null,
txBroadcaster,
logger
)
} else {
// Fresh start
val channelManagerConstructor = ChannelManagerConstructor(
Network.LDKNetwork_Regtest,
userConfig,
latestBlockHash.toByteArray(),
latestBlockHeight,
keysManager!!.inner.as_EntropySource(),
keysManager!!.inner.as_NodeSigner(),
SignerProvider.new_impl(keysManager!!.signerProvider),
feeEstimator,
chainMonitor,
networkGraph,
ProbabilisticScoringDecayParameters.with_default(),
ProbabilisticScoringFeeParameters.with_default(),
null,
txBroadcaster,
logger
)
}
if serializedChannelManager != nil && !serializedChannelManager!.isEmpty {
do {
let channelManagerConstructor = try ChannelManagerConstructor(
channelManagerSerialized: serializedChannelManager!,
channelMonitorsSerialized: serializedChannelMonitors,
networkGraph: NetworkGraphArgument.instance(netGraph),
filter: filter,
params: channelManagerConstructionParameters
)
} catch {
let channelManagerConstructor = ChannelManagerConstructor(
network: network,
currentBlockchainTipHash: latestBlockHash!,
currentBlockchainTipHeight: latestBlockHeight!,
netGraph: netGraph,
params: channelManagerConstructionParameters
)
}
} else {
let channelManagerConstructor = ChannelManagerConstructor(
network: network,
currentBlockchainTipHash: latestBlockHash!,
currentBlockchainTipHeight: latestBlockHeight!,
netGraph: netGraph,
params: channelManagerConstructionParameters
)
}
Implementation notes: No methods should be called on ChannelManager
until
after the ChannelMonitor
s and ChannelManager
are synced to the chain tip (next step).
Dependencies: KeysManager
, FeeEstimator
, ChainMonitor
, BroadcasterInterface
, Logger
References: Rust ChannelManager
docs, Java/Kotlin ChannelManager
bindings
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.
There are 2 main options for synchronizing to chain on startup:
You can use LDK's lightning-block-sync crate. This provides utilities for syncing LDK via a block-based interface.
Example:
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(),
);
::: tip Full block syncing in mobile environments
Block syncing for mobile clients tends to present several challenges due to resource contraints and network limitiations typically associated with mobile devices. It requires a full node and usually fetches blocks over RPC.
Compact block filters (CBFs) are an alternative approach to syncing the blockchain that addresses some of the challenges associated with mobile clients. Please start a discussion if you would like us to expose lightning-block-sync
in our bindings.
:::
Alternatively, you can use LDK's lightning-transaction-sync
crate. This provides utilities for syncing LDK via the transaction-based Confirm
interface.
Example:
let tx_sync = Arc::new(EsploraSyncClient::new(
esplora_server_url,
Arc::clone(&some_logger),
));
let chain_monitor = Arc::new(ChainMonitor::new(
Some(Arc::clone(&tx_sync)),
Arc::clone(&some_broadcaster),
Arc::clone(&some_logger),
Arc::clone(&some_fee_estimator),
Arc::clone(&some_persister),
));
let channel_manager = Arc::new(ChannelManager::new(
Arc::clone(&some_fee_estimator),
Arc::clone(&chain_monitor),
Arc::clone(&some_broadcaster),
Arc::clone(&some_router),
Arc::clone(&some_logger),
Arc::clone(&some_entropy_source),
Arc::clone(&some_node_signer),
Arc::clone(&some_signer_provider),
user_config,
chain_params,
));
let confirmables = vec![
&*channel_manager as &(dyn Confirm + Sync + Send),
&*chain_monitor as &(dyn Confirm + Sync + Send),
];
tx_sync.sync(confirmables).unwrap();
// Note: This example calls the Confirm interface directly. The lightning-transaction-sync crate will
// be available in the next bindings release.
// Retrieve transaction IDs to check the chain for un-confirmation.
val relevantTxIdsFromChannelManager: Array<ByteArray> = channelManager .as_Confirm().get_relevant_txids()
val relevantTxIdsFromChannelManager: Array<ByteArray> = chainMonitor.as_Confirm().get_relevant_txids()
val relevantTxIds = relevantTxIdsFromChannelManager + relevantTxIdsFromChainMonitor
val unconfirmedTxs: Array<ByteArray> = // <insert code to find out from your chain source
// if any of relevant_txids have been reorged out
// of the chain>
for (txid in unconfirmedTxs) {
channelManager .transaction_unconfirmed(txid)
chainMonitor.transaction_unconfirmed(txid)
}
// Retrieve transactions and outputs that were registered through the `Filter`
// interface.
// If any of these txs/outputs were confirmed on-chain, then:
val header: Array<ByteArray> = // insert block header from the block with confirmed tx/output
val height: Int = // insert block height of `header`
val txIndex: Long = // insert tx index in block
val serializedTx: Array<ByteArray> = // insert tx hex as byte array
val tx: TwoTuple_usizeTransactionZ = TwoTuple_usizeTransactionZ.of(txIndex, serializedTx);
// Marshall all TwoTuples you built right above into an array
val txList = arrayOf<TwoTuple_usizeTransactionZ>(TwoTuple_usizeTransactionZ.of(tx.., ..));
channelManager.transactions_confirmed(header, height, txList);
chainMonitor.transactions_confirmed(header, height, txList);
val bestHeader: Array<ByteArray> = // <insert code to get your best known header>
val bestHeight: Int = // <insert code to get your best known block height>
channelManager.update_best_block(bestHeader, bestHeight);
chainMonitor.update_best_block(bestHeader, bestHeight);
// Finally, tell LDK that chain sync is complete. This will also spawn several
// background threads to handle networking and event processing.
channelManagerConstructor.chain_sync_completed(customEventHandler);
// Note: This example calls the Confirm interface directly. The lightning-transaction-sync crate will
// be available in the next bindings release.
// Retrieve transaction IDs to check the chain for un-confirmation.
let relevantTxIds1 = channelManager?.asConfirm().getRelevantTxids() ?? []
let relevantTxIds2 = chainMonitor?.asConfirm().getRelevantTxids() ?? []
var relevantTxIds: [[UInt8]] = [[UInt8]]()
for tx in relevantTxIds1 {
relevantTxIds.append(tx.0)
}
for tx in relevantTxIds2 {
relevantTxIds.append(tx.0)
}
var unconfirmedTx: [[UInt8]] = // Insert code to find out from your chain source
// if any of relevantTxIds have been reorged out
// of the chain
for txid in unconfirmedTx {
channelManager.asConfirm().transactionUnconfirmed(txid: txid)
chainMonitor.asConfirm().transactionUnconfirmed(txid: txid)
}
// Retrieve transactions and outputs that were registered through the `Filter` interface.
var confirmedTx: [[UInt8]] = // Insert code to find out from your chain source
// if any of the `Filter` txs/outputs were confirmed
// on-chain
for txid in confirmedTx {
let header: [UInt8] = // Insert code to fetch header
let height: UInt32 = // Insert code to fetch height of the header
let tx: [UInt8] = // Insert code to fetch tx
let txIndex: UInt = // Insert code to fetch tx index
var twoTuple: [(UInt, [UInt8])] = []
twoTuple.append((UInt, [UInt8])(txIndex, tx))
channelManager.asConfirm().transactionsConfirmed(header: header, txdata: twoTuple, height: height)
chainMonitor.asConfirm().transactionsConfirmed(header: header, txdata: twoTuple, height: height)
}
let bestHeader = // Insert code to fetch best header
let bestHeight = // Insert code to fetch best height
channelManager.asConfirm().bestBlockUpdated(header: bestHeader, height: bestHeight)
chainMonitor.asConfirm().bestBlockUpdated(header: bestHeader, height: bestHeight)
// Finally, tell LDK that chain sync is complete. This will also spawn several
// background threads to handle networking and event processing.
channelManagerConstructor.chainSyncCompleted(persister: channelManagerPersister)
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
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(),
));
val genesisBlock : BestBlock = BestBlock.from_genesis(Network.LDKNetwork_Testnet)
val genesisBlockHash : String = byteArrayToHex(genesisBlock.block_hash())
val serializedNetworkGraph = // Read network graph bytes from file
val networkGraph : NetworkGraph = NetworkGraph.read(serializedNetworkGraph, logger)
val p2pGossip : P2PGossipSync = P2PGossipSync.of(networkGraph, Option_AccessZ.none(), logger)
// If Network Graph exists, then read from disk
let serializedNetworkGraph = // Read Network Graph bytes from file
let readResult = NetworkGraph.read(ser: serializedNetworkGraph, arg: logger)
if readResult.isOk() {
netGraph = readResult.getValue()!
}
// If Network Graph does not exist, create a new one
let netGraph = NetworkGraph(network: network, logger: logger)
// Initialise RGS
let rgs = RapidGossipSync(networkGraph: netGraph, logger: logger)
if let lastSync = netGraph.getLastRapidGossipSyncTimestamp(), let snapshot = getSnapshot(lastSyncTimeStamp: lastSync) {
let timestampSeconds = UInt64(NSDate().timeIntervalSince1970)
let res = rgs.updateNetworkGraphNoStd(updateData: snapshot, currentTimeUnix: timestampSeconds)
if res.isOk() {
print("RGS updated")
}
} else if let snapshot = getSnapshot(lastSyncTimeStamp: 0) { // Use lastSyncTimeStamp as 0 for first Sync
let timestampSeconds = UInt64(NSDate().timeIntervalSince1970)
let res = rgs.updateNetworkGraphNoStd(updateData: snapshot, currentTimeUnix: timestampSeconds)
if res.isOk() {
print("RGS initialized for the first time")
}
}
// Get current snapshot from the RGS Server
func getSnapshot(lastSyncTimeStamp: UInt32) -> [UInt8]? {
// Use LDK's RGS Server or use your own Server
let url: URL = URL(string: "https://rapidsync.lightningdevkit.org/snapshot/\(lastSyncTimeStamp)")!
let data = // Use the url to get the data
if let data = data {
return [UInt8](data)
}
return nil
}
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, Access
docs, Java/Kotlin P2PGossipSync
bindings, Rust RapidGossipSync
docs, Java/Kotlin RapidGossipSync
bindings
What it's used for: to find a suitable payment path to reach the destination.
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),
)));
if (scorerFile.exists()) {
val scorerReaderResult = ProbabilisticScorer.read(scorerFile.readBytes(), ProbabilisticScoringDecayParameters.with_default(), networkGraph, logger)
if (scorerReaderResult.is_ok) {
val probabilisticScorer = (scorerReaderResult as Result_ProbabilisticScorerDecodeErrorZ.Result_ProbabilisticScorerDecodeErrorZ_OK).res
scorer = MultiThreadedLockableScore.of(probabilisticScorer.as_Score())
Log.i(LDKTAG, "LDK: Probabilistic Scorer loaded and running")
} else {
Log.i(LDKTAG, "LDK: Couldn't load Probabilistic Scorer")
val decayParams = ProbabilisticScoringDecayParameters.with_default()
val probabilisticScorer = ProbabilisticScorer.of(decayParams, networkGraph, logger)
scorer = MultiThreadedLockableScore.of(probabilisticScorer.as_Score())
Log.i(LDKTAG, "LDK: Creating a new Probabilistic Scorer")
}
} else {
val decayParams = ProbabilisticScoringDecayParameters.with_default()
val probabilisticScorer = ProbabilisticScorer.of(decayParams, networkGraph, logger)
scorer = MultiThreadedLockableScore.of(probabilisticScorer.as_Score())
}
// If Scorer exists, then read from disk
let serializedScorer = // Read Scorer bytes from file
let decayParams = ProbabilisticScoringDecayParameters.initWithDefault()
let serializedProbabilisticScorer = ProbabilisticScorer.read(
ser: serializedScorer,
argA: decayParams,
argB: netGraph,
argC: logger
)
if let res = serializedProbabilisticScorer.getValue() {
let probabilisticScorer = res
let score = probabilisticScorer.asScore()
// Scorer loaded
let scorer = MultiThreadedLockableScore(score: score)
}
// If Scorer does not exist, create a new one
let decayParams = ProbabilisticScoringDecayParameters.initWithDefault()
let probabilisticScorer = ProbabilisticScorer(
decayParams: decayParams,
networkGraph: netGraph,
logger: logger
)
let score = probabilisticScorer.asScore()
// Scorer loaded
let scorer = MultiThreadedLockableScore(score: score)
Dependencies: NetworkGraph
References: Rust ProbabilisticScorer
docs, Java/Kotlin ProbabilisticScorer
docs