Skip to content

dns bootstrap#7

Merged
dzdidi merged 6 commits intomainfrom
feat/dns-bootstrap
Feb 23, 2026
Merged

dns bootstrap#7
dzdidi merged 6 commits intomainfrom
feat/dns-bootstrap

Conversation

@dzdidi
Copy link
Collaborator

@dzdidi dzdidi commented Feb 20, 2026

DNS bootstrap instead of manual

Blocked by / Based on #6

Adds DNS-based peer discovery (BOLT-10) so the node can bootstrap outbound connections automatically from well-known DNS seeds instead of relying solely on manual peer configuration.

Changes:

  • Introduces a new dns_bootstrap module to query SRV records, decode bech32 pubkeys from hostnames, and resolve A/AAAA records into connectable peers.
  • Wires optional dns_bootstrap configuration through config.json → NodeConfig → LdkUserInfo and starts a periodic bootstrap task in start_ldk.
  • Adds hickory-resolver dependency to perform async DNS lookups.

Signed-off-by: dzdidi <dzdidi@protonmail.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds DNS-based peer discovery (BOLT-0010) so the node can bootstrap outbound connections automatically from well-known DNS seeds instead of relying solely on manual peer configuration.

Changes:

  • Introduces a new dns_bootstrap module to query SRV records, decode bech32 pubkeys from hostnames, and resolve A/AAAA records into connectable peers.
  • Wires optional dns_bootstrap configuration through config.jsonNodeConfigLdkUserInfo and starts a periodic bootstrap task in start_ldk.
  • Adds hickory-resolver dependency to perform async DNS lookups.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/main.rs Starts periodic DNS bootstrap task to discover/connect peers.
src/dns_bootstrap/bootstrapper.rs Core bootstrap logic: SRV query → pubkey decode → host resolve → peer list.
src/dns_bootstrap/query.rs DNS resolver wrapper for SRV + IP lookups with timeouts.
src/dns_bootstrap/pubkey.rs Decodes node pubkeys from bech32 DNS labels; includes unit tests.
src/dns_bootstrap/config.rs Defines DnsBootstrapConfig + default seed list.
src/dns_bootstrap/mod.rs Module exports and error type definitions.
src/config.rs Adds dns_bootstrap to NodeConfig and config help output.
src/cli.rs Extends LdkUserInfo to carry dns_bootstrap config.
Cargo.toml Adds hickory-resolver dependency.
Cargo.lock Lockfile updates for new DNS dependency graph.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 1282 to 1335
println!(
"DNS bootstrap enabled: {} seeds, target {} peers, interval {}s",
bootstrapper.config().seeds.len(),
num_peers,
interval_secs,
);

tokio::spawn(async move {
// Initial delay to let networking and gossip start.
tokio::time::sleep(Duration::from_secs(5)).await;

let mut interval =
tokio::time::interval(Duration::from_secs(interval_secs));
loop {
interval.tick().await;

if stop_bootstrap.load(Ordering::Acquire) {
return;
}

// Build ignore set from currently connected peers.
let ignore: std::collections::HashSet<
lightning::routing::gossip::NodeId,
> = bootstrap_pm
.list_peers()
.iter()
.map(|p| {
lightning::routing::gossip::NodeId::from_pubkey(
&p.counterparty_node_id,
)
})
.collect();

match bootstrapper.sample_node_addrs(num_peers, &ignore).await {
Ok(peers) => {
println!(
"[dns_bootstrap] Discovered {} peers",
peers.len()
);
for peer in peers {
let _ = cli::do_connect_peer(
peer.pubkey,
peer.addr,
Arc::clone(&bootstrap_pm),
)
.await;
}
},
Err(e) => {
eprintln!("[dns_bootstrap] Bootstrap failed: {}", e);
},
}
}
});
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

interval_secs comes from config and can be set to 0, but tokio::time::interval(Duration::from_secs(0)) panics. Add validation (e.g., reject 0 with a clear error) before spawning the task, or clamp to a minimum of 1s so misconfiguration can’t crash the bootstrap task.

Suggested change
println!(
"DNS bootstrap enabled: {} seeds, target {} peers, interval {}s",
bootstrapper.config().seeds.len(),
num_peers,
interval_secs,
);
tokio::spawn(async move {
// Initial delay to let networking and gossip start.
tokio::time::sleep(Duration::from_secs(5)).await;
let mut interval =
tokio::time::interval(Duration::from_secs(interval_secs));
loop {
interval.tick().await;
if stop_bootstrap.load(Ordering::Acquire) {
return;
}
// Build ignore set from currently connected peers.
let ignore: std::collections::HashSet<
lightning::routing::gossip::NodeId,
> = bootstrap_pm
.list_peers()
.iter()
.map(|p| {
lightning::routing::gossip::NodeId::from_pubkey(
&p.counterparty_node_id,
)
})
.collect();
match bootstrapper.sample_node_addrs(num_peers, &ignore).await {
Ok(peers) => {
println!(
"[dns_bootstrap] Discovered {} peers",
peers.len()
);
for peer in peers {
let _ = cli::do_connect_peer(
peer.pubkey,
peer.addr,
Arc::clone(&bootstrap_pm),
)
.await;
}
},
Err(e) => {
eprintln!("[dns_bootstrap] Bootstrap failed: {}", e);
},
}
}
});
if interval_secs == 0 {
eprintln!(
"[dns_bootstrap] Invalid configuration: interval_secs must be greater than 0; DNS bootstrap disabled."
);
} else {
println!(
"DNS bootstrap enabled: {} seeds, target {} peers, interval {}s",
bootstrapper.config().seeds.len(),
num_peers,
interval_secs,
);
tokio::spawn(async move {
// Initial delay to let networking and gossip start.
tokio::time::sleep(Duration::from_secs(5)).await;
let mut interval =
tokio::time::interval(Duration::from_secs(interval_secs));
loop {
interval.tick().await;
if stop_bootstrap.load(Ordering::Acquire) {
return;
}
// Build ignore set from currently connected peers.
let ignore: std::collections::HashSet<
lightning::routing::gossip::NodeId,
> = bootstrap_pm
.list_peers()
.iter()
.map(|p| {
lightning::routing::gossip::NodeId::from_pubkey(
&p.counterparty_node_id,
)
})
.collect();
match bootstrapper.sample_node_addrs(num_peers, &ignore).await {
Ok(peers) => {
println!(
"[dns_bootstrap] Discovered {} peers",
peers.len()
);
for peer in peers {
let _ = cli::do_connect_peer(
peer.pubkey,
peer.addr,
Arc::clone(&bootstrap_pm),
)
.await;
}
},
Err(e) => {
eprintln!("[dns_bootstrap] Bootstrap failed: {}", e);
},
}
}
});
}

Copilot uses AI. Check for mistakes.
Signed-off-by: dzdidi <dzdidi@protonmail.com>
@dzdidi dzdidi force-pushed the feat/dns-bootstrap branch from 5de4407 to 5b96479 Compare February 20, 2026 17:41
Signed-off-by: dzdidi <dzdidi@protonmail.com>
Signed-off-by: dzdidi <dzdidi@protonmail.com>
Signed-off-by: dzdidi <dzdidi@protonmail.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 16 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Signed-off-by: dzdidi <dzdidi@protonmail.com>
@dzdidi dzdidi requested a review from ovitrif February 23, 2026 09:47
Copy link

@ovitrif ovitrif left a comment

Choose a reason for hiding this comment

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

PR looks good 🙌🏻

Reiterating the niceness we could've achieved with a PR which formats according to your rules, so then all additional PRs cover actual changes and make reviewing much easier due to not having to "vouch" for a lot of changes that are hard to understand and keep all in head at once time, for the final PR approval 🙃

Ultimately, GitHub's "hide whitespaces" did help a lot with reviewing and keeping track of what's necessary to read as they're legit changes; but there's still some changes seen, like for example when formatting of arguments was changed to list on same line instead of a line for each arg.

IDK the rust ecosystem so good, is Clippy the standard for these formatting enforcement concerns?!

Comment on lines -285 to +282
rpc_client
.call_method("signrawtransactionwithwallet", &[tx_hex_json])
.await
.unwrap()
rpc_client.call_method("signrawtransactionwithwallet", &[tx_hex_json]).await.unwrap()
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!

Base automatically changed from feat/delayed-probes to main February 23, 2026 17:03
@dzdidi
Copy link
Collaborator Author

dzdidi commented Feb 23, 2026

@ovitrif , I usually do both rust fmt and rust clippy. Missed on this one. But this is also a forked codebase so some things where inherited :(

@dzdidi dzdidi merged commit 32da1ad into main Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants