Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
343 changes: 338 additions & 5 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@ chrono = { version = "0.4", default-features = false, features = ["clock"] }
rand = "0.4"
serde_json = { version = "1.0" }
serde = { version = "1.0", features = ["derive"] }
toml = { version = "0.8" }
tokio = { version = "1", features = [ "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time", "io-std" ] }
reqwest = { version = "0.11", features = ["tokio-native-tls", "stream"] }
futures-util = "0.3"

# DNS bootstrap (BOLT-0010)
hickory-resolver = { version = "0.24", features = ["tokio-runtime"] }

[profile.release]
panic = "abort"

Expand Down
76 changes: 45 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,49 +12,57 @@ cd ldk-sample
cargo run <ldk_storage_directory_path>
```
The only CLI argument is the storage directory path. All configuration is read from
`<ldk_storage_directory_path>/.ldk/config.json`.
`<ldk_storage_directory_path>/.ldk/config.toml`.

Use `config.example.json` in the repo root as a template for your config file.
Use `config.example.toml` in the repo root as a template for your config file.

## Configuration

Config is loaded from `<storage_dir>/.ldk/config.json` and strictly validated. Unknown
Config is loaded from `<storage_dir>/.ldk/config.toml` and strictly validated. Unknown
fields cause an error (`deny_unknown_fields`).

Required sections: `bitcoind`.
Optional sections: `ldk`, `rapid_gossip_sync`, and `probing` (probing is disabled if
missing).
Optional sections: `ldk`, `rapid_gossip_sync`, `probing` (probing is disabled if
missing), and `dns_bootstrap` (enabled by default with sensible defaults).

Network options: `mainnet`, `testnet`, `regtest`, `signet` (default is `testnet`).

### Example config

```json
{
"bitcoind": {
"rpc_host": "127.0.0.1",
"rpc_port": 8332,
"rpc_username": "your_rpc_user",
"rpc_password": "your_rpc_password"
},
"network": "testnet",
"ldk": {
"peer_listening_port": 9735,
"announced_node_name": "MyLDKNode",
"announced_listen_addr": []
},
"rapid_gossip_sync": {
"enabled": true,
"url": "https://rapidsync.lightningdevkit.org/snapshot/",
"interval_hours": 6
},
"probing": {
"interval_sec": 300,
"peers": ["02abc123...@1.2.3.4:9735"],
"amount_msats": [1000, 10000, 100000, 1000000],
"timeout_sec": 60
}
}
```toml
network = "testnet"

[bitcoind]
rpc_host = "127.0.0.1"
rpc_port = 8332
rpc_username = "your_rpc_user"
rpc_password = "your_rpc_password"

# [ldk]
# peer_listening_port = 9735
# announced_node_name = "MyLDKNode"
# announced_listen_addr = []

[rapid_gossip_sync]
enabled = true
url = "https://rapidsync.lightningdevkit.org/snapshot/"
interval_hours = 6

[probing]
interval_sec = 300
peers = ["02abc123...@1.2.3.4:9735"]
amount_msats = [1000, 10000, 100000, 1000000]
timeout_sec = 60

probe_delay_sec = 1
peer_delay_sec = 2

[dns_bootstrap]
enabled = true
seeds = [ "nodes.lightning.wiki", "lseed.bitcoinstats.com" ]
timeout_secs = 30
num_peers = 10
interval_secs = 300
```

### Key options
Expand All @@ -80,6 +88,12 @@ fails, the node falls back to P2P gossip sync. `url` defaults to
Optional. If omitted, probing is disabled. Configure probe interval, peers, probe
amounts in millisatoshis, and timeout.

`dns_bootstrap`:
Optional. Enabled by default. Discovers peers via DNS SRV lookups per BOLT-0010.
`seeds` defaults to `nodes.lightning.wiki` and `lseed.bitcoinstats.com`.
`timeout_secs` defaults to 30, `num_peers` defaults to 10, `interval_secs`
defaults to 300. Set `enabled` to `false` to disable.

## License

Licensed under either:
Expand Down
29 changes: 0 additions & 29 deletions config.example.json

This file was deleted.

27 changes: 27 additions & 0 deletions config.example.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[bitcoind]
rpc_host = "127.0.0.1"
rpc_port = 8332
rpc_username = "your_rpc_user"
rpc_password = "your_rpc_password"

network = "testnet"

[ldk]
peer_listening_port = 9735
announced_node_name = "MyLDKNode"
announced_listen_addr = []

[rapid_gossip_sync]
enabled = true
url = "https://rapidsync.lightningdevkit.org/snapshot/"
interval_hours = 6

[probing]
interval_sec = 300
peers = [
"02abc123...@1.2.3.4:9735"
]
amount_msats = [1000, 10000, 100000, 1000000]
timeout_sec = 60
probe_delay_sec = 1
peer_delay_sec = 2
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dns_bootstrap configuration section is missing from this example file. According to the config.rs changes (lines 249-254), the example should include a [dns_bootstrap] section with fields: enabled, seeds, timeout_secs, num_peers, and interval_secs. This omission makes the example incomplete and inconsistent with the documentation in config.rs.

Suggested change
peer_delay_sec = 2
peer_delay_sec = 2
[dns_bootstrap]
enabled = true
seeds = [
"seed1.example.com",
"seed2.example.com"
]
timeout_secs = 10
num_peers = 3
interval_secs = 600

Copilot uses AI. Check for mistakes.
2 changes: 1 addition & 1 deletion src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub(crate) fn parse_startup_args() -> Result<LdkUserInfo, ()> {
println!("Usage: {} <ldk_storage_directory_path>", args[0]);
println!();
println!(
"The config.json file should be located at <ldk_storage_directory_path>/.ldk/config.json",
"The config.toml file should be located at <ldk_storage_directory_path>/.ldk/config.toml",
);
crate::config::print_config_help();
return Err(());
Expand Down
16 changes: 4 additions & 12 deletions src/bitcoind_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,7 @@ impl BitcoindClient {
pub async fn create_raw_transaction(&self, outputs: Vec<HashMap<String, f64>>) -> RawTx {
let outputs_json = serde_json::json!(outputs);
self.bitcoind_rpc_client
.call_method::<RawTx>(
"createrawtransaction",
&[serde_json::json!([]), outputs_json],
)
.call_method::<RawTx>("createrawtransaction", &[serde_json::json!([]), outputs_json])
.await
.unwrap()
}
Expand Down Expand Up @@ -282,10 +279,7 @@ impl BitcoindClient {
let tx_hex_json = serde_json::json!(tx_hex);
let rpc_client = self.get_new_rpc_client();
async move {
rpc_client
.call_method("signrawtransactionwithwallet", &[tx_hex_json])
.await
.unwrap()
rpc_client.call_method("signrawtransactionwithwallet", &[tx_hex_json]).await.unwrap()
Comment on lines -285 to +282
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would be nice pick a formatting tool and setup a rule so AI always runs the formatter before commits, so that formatting changes are "right from the start" and don't change arbitrarily with each PR, unless actual code changes warrant new formatting 👍🏻

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

}
}

Expand All @@ -309,9 +303,7 @@ impl BitcoindClient {

pub fn list_unspent(&self) -> impl Future<Output = ListUnspentResponse> {
let rpc_client = self.get_new_rpc_client();
async move {
rpc_client.call_method::<ListUnspentResponse>("listunspent", &[]).await.unwrap()
}
async move { rpc_client.call_method::<ListUnspentResponse>("listunspent", &[]).await.unwrap() }
}
}

Expand Down Expand Up @@ -392,7 +384,7 @@ impl WalletSource for BitcoindClient {
value,
script_pubkey: utxo.address.script_pubkey(),
},
satisfaction_weight: WITNESS_SCALE_FACTOR as u64 +
satisfaction_weight: WITNESS_SCALE_FACTOR as u64 +
1 /* witness items */ + 1 /* schnorr sig len */ + 64, /* schnorr sig */
})
.ok()
Expand Down
33 changes: 16 additions & 17 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use std::time::Duration;

use tokio::io::{AsyncBufReadExt, BufReader};

/// Probing configuration passed from config.json
/// Probing configuration passed from config.toml
#[derive(Clone)]
pub(crate) struct ProbingConfig {
pub(crate) interval_sec: u64,
Expand All @@ -60,6 +60,7 @@ pub(crate) struct LdkUserInfo {
pub(crate) rapid_gossip_sync_url: Option<String>,
pub(crate) rapid_gossip_sync_interval_hours: u64,
pub(crate) probing: Option<ProbingConfig>,
pub(crate) dns_bootstrap: Option<crate::dns_bootstrap::DnsBootstrapConfig>,
}

pub(crate) async fn poll_for_user_input(
Expand Down Expand Up @@ -322,10 +323,10 @@ pub(crate) async fn poll_for_user_input(
&invoice,
user_provided_amt,
&outbound_payments,
&fs_store,
)
.await
},
&fs_store,
)
.await
},
Err(e) => {
println!("ERROR: invalid invoice: {:?}", e);
},
Expand Down Expand Up @@ -360,15 +361,15 @@ pub(crate) async fn poll_for_user_input(
continue;
},
};
keysend(
&channel_manager,
dest_pubkey,
amt_msat,
&*keys_manager,
&outbound_payments,
&fs_store,
)
.await;
keysend(
&channel_manager,
dest_pubkey,
amt_msat,
&*keys_manager,
&outbound_payments,
&fs_store,
)
.await;
},
"getoffer" => {
let offer_builder = channel_manager.create_offer_builder();
Expand Down Expand Up @@ -1025,9 +1026,7 @@ pub(crate) fn parse_peer_info(

let pubkey = hex_utils::to_compressed_pubkey(pubkey.unwrap());
if pubkey.is_none() {
return Err(std::io::Error::other(
"ERROR: unable to parse given pubkey for node",
));
return Err(std::io::Error::other("ERROR: unable to parse given pubkey for node"));
}

Ok((pubkey.unwrap(), peer_addr.unwrap().unwrap()))
Expand Down
Loading