fix(lnd): give the rotation's verify step its own deadline

The mint wait and the post-rotation verify shared one 15-minute budget. A
rotation that legitimately spent 14 of those minutes waiting for LND to mint a
fresh macaroon — normal on a loaded node, where opening channel.db/graph.db/
wallet.db alone has been measured at 2m38s — then had 60 seconds to confirm the
node identity and channel census came back, and would report FAILURE on a wallet
that was completely healthy.

That is the most alarming possible way to be wrong about someone's Lightning
node: it names a backup directory and tells them to investigate before retrying,
at the exact moment nothing is actually broken. Each wait now gets its own
budget. Waiting longer costs nothing here — the failure this step exists to catch
(changed identity, missing channels) is not time-sensitive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-08 08:14:48 -04:00
co-authored by Claude Opus 5
parent 45c2925bdd
commit a9cefb8326
+15 -5
View File
@@ -616,10 +616,11 @@ async fn run_rotation(
.await
.with_context(|| format!("unlocking the wallet — old material is in {backup}"))?;
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(MACAROON_WAIT_SECS);
let mint_deadline =
std::time::Instant::now() + std::time::Duration::from_secs(MACAROON_WAIT_SECS);
let admin_path = format!("{LND_MAINNET_DIR}/admin.macaroon");
let mut new_digest = None;
while std::time::Instant::now() < deadline {
while std::time::Instant::now() < mint_deadline {
if let Some(d) = digest_as_root(&admin_path).await {
new_digest = Some(d);
break;
@@ -644,10 +645,19 @@ async fn run_rotation(
});
// 6. Verify the things that must NOT have changed.
//
// Its own budget, deliberately not the mint deadline. Sharing one would mean
// a rotation that legitimately spent 14 of its 15 minutes waiting for LND to
// mint gets 1 minute to prove the channels came back, and then reports
// FAILURE on a node that is perfectly healthy — the most alarming possible
// way to be wrong about someone's Lightning wallet.
with_progress(|p| p.set("verify", StepState::Running, None));
let (pubkey_after, census_after) = wait_for_serving(deadline).await.with_context(|| {
format!("verifying the node after rotation — old material is in {backup}")
})?;
let verify_deadline =
std::time::Instant::now() + std::time::Duration::from_secs(MACAROON_WAIT_SECS);
let (pubkey_after, census_after) =
wait_for_serving(verify_deadline).await.with_context(|| {
format!("verifying the node after rotation — old material is in {backup}")
})?;
with_progress(|p| p.channels_after = Some(census_after.channels_open));
if pubkey_after != pubkey_before {
with_progress(|p| p.set("verify", StepState::Failed, None));