From 3b8ec65c67730b1f9d28ef14d88aa24f62d62889 Mon Sep 17 00:00:00 2001 From: shocknet-justin Date: Wed, 27 Aug 2025 23:27:49 -0400 Subject: [PATCH] workaround for lnd rescan bug --- env.example | 1 + scripts/extract_nprofile.sh | 19 +++++++++- scripts/install.sh | 1 + src/services/lnd/index.ts | 3 +- src/services/lnd/settings.ts | 2 ++ src/services/main/unlocker.ts | 68 +++++++++++++++++++++++++++++++++++ 6 files changed, 92 insertions(+), 2 deletions(-) diff --git a/env.example b/env.example index 83c3a411..0614df42 100644 --- a/env.example +++ b/env.example @@ -8,6 +8,7 @@ #LND_ADDRESS=127.0.0.1:10009 #LND_CERT_PATH=~/.lnd/tls.cert #LND_MACAROON_PATH=~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon +#LND_LOG_DIR=~/.lnd/logs/bitcoin/mainnet/lnd.log #BOOTSTRAP_PEER # A trusted peer that will hold a node-level account until channel automation becomes affordable diff --git a/scripts/extract_nprofile.sh b/scripts/extract_nprofile.sh index fd5d2fe1..74b8add5 100644 --- a/scripts/extract_nprofile.sh +++ b/scripts/extract_nprofile.sh @@ -12,7 +12,7 @@ get_log_info() { LOG_DIR="$USER_HOME/lightning_pub/logs" DATA_DIR="$USER_HOME/lightning_pub/" START_TIME=$(date +%s) - MAX_WAIT_TIME=120 # Maximum wait time in seconds + MAX_WAIT_TIME=360 # Maximum wait time in seconds (6 minutes) WAIT_INTERVAL=5 # Time to wait between checks in seconds log "Checking wallet status... This may take a moment." @@ -50,7 +50,24 @@ get_log_info() { START_TIME=$(date +%s) while [ $(($(date +%s) - START_TIME)) -lt $MAX_WAIT_TIME ]; do latest_entry=$(grep -E "unlocker >> (the wallet is already unlocked|created wallet with pub:|unlocked wallet with pub)" "$latest_unlocker_log" | tail -n 1) + + # Show dynamic header sync progress if available from unlocker logs + progress_line=$(grep -E "LND header sync [0-9]+% \(height=" "$latest_unlocker_log" | tail -n 1) + if [ -n "$progress_line" ]; then + percent=$(echo "$progress_line" | sed -E 's/.*LND header sync ([0-9]+)%.*/\1/') + if [[ "$percent" =~ ^[0-9]+$ ]]; then + bar_len=30 + filled=$((percent*bar_len/100)) + if [ $filled -gt $bar_len ]; then filled=$bar_len; fi + empty=$((bar_len-filled)) + filled_bar=$(printf '%*s' "$filled" | tr ' ' '#') + empty_bar=$(printf '%*s' "$empty" | tr ' ' ' ') + echo -ne "Header sync: [${filled_bar}${empty_bar}] ${percent}%\r" + fi + fi if [ -n "$latest_entry" ]; then + # End the progress line cleanly + echo "" break fi sleep $WAIT_INTERVAL diff --git a/scripts/install.sh b/scripts/install.sh index 7ab4587a..8666e4f9 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -107,6 +107,7 @@ else # Only start services if it was a fresh install or an upgrade. if [ "$pub_upgrade_status" -eq 0 ] || [ "$pub_upgrade_status" -eq 100 ]; then log "Starting services..." + log "Note: LND may take several minutes to sync block headers depending on network conditions." touch /tmp/pub_install_timestamp start_services $lnd_status $pub_upgrade_status || log_error "Failed to start services" 1 get_log_info || log_error "Failed to get log info" 1 diff --git a/src/services/lnd/index.ts b/src/services/lnd/index.ts index 2525503c..8b1f2d00 100644 --- a/src/services/lnd/index.ts +++ b/src/services/lnd/index.ts @@ -17,9 +17,10 @@ export const LoadLndSettingsFromEnv = (): LndSettings => { const lndAddr = process.env.LND_ADDRESS || "127.0.0.1:10009" const lndCertPath = process.env.LND_CERT_PATH || resolveHome("/.lnd/tls.cert") const lndMacaroonPath = process.env.LND_MACAROON_PATH || resolveHome("/.lnd/data/chain/bitcoin/mainnet/admin.macaroon") + const lndLogDir = process.env.LND_LOG_DIR || resolveHome("/.lnd/logs/bitcoin/mainnet/lnd.log") const feeRateBps = EnvCanBeInteger("OUTBOUND_MAX_FEE_BPS", 60) const feeRateLimit = feeRateBps / 10000 const feeFixedLimit = EnvCanBeInteger("OUTBOUND_MAX_FEE_EXTRA_SATS", 100) const mockLnd = EnvCanBeBoolean("MOCK_LND") - return { mainNode: { lndAddr, lndCertPath, lndMacaroonPath }, feeRateLimit, feeFixedLimit, feeRateBps, mockLnd } + return { mainNode: { lndAddr, lndCertPath, lndMacaroonPath }, lndLogDir, feeRateLimit, feeFixedLimit, feeRateBps, mockLnd } } diff --git a/src/services/lnd/settings.ts b/src/services/lnd/settings.ts index f777e09d..1ced39ee 100644 --- a/src/services/lnd/settings.ts +++ b/src/services/lnd/settings.ts @@ -7,6 +7,7 @@ export type NodeSettings = { } export type LndSettings = { mainNode: NodeSettings + lndLogDir: string feeRateLimit: number feeFixedLimit: number feeRateBps: number @@ -15,6 +16,7 @@ export type LndSettings = { otherNode?: NodeSettings thirdNode?: NodeSettings } + type TxOutput = { hash: string index: number diff --git a/src/services/main/unlocker.ts b/src/services/main/unlocker.ts index 2d0d623d..b104ffd3 100644 --- a/src/services/main/unlocker.ts +++ b/src/services/main/unlocker.ts @@ -95,8 +95,76 @@ export class Unlocker { return { ln, pub: infoAfter.pub, action: 'unlocked' } } + private waitForLndSync = async (timeoutSeconds: number): Promise => { + const lndLogPath = this.settings.lndSettings.lndLogDir; + if (this.settings.lndSettings.mockLnd) { + this.log("MOCK_LND set, skipping header sync wait."); + return; + } + + let targetHeight = 0; + this.log(`Waiting for LND to sync headers (timeout: ${timeoutSeconds}s). Log: ${lndLogPath} (discovering target height...)`); + + let lastPercentReported = -1; + const startTime = Date.now(); + const checkLog = async (resolve: () => void, reject: (reason?: any) => void) => { + const elapsedTime = (Date.now() - startTime) / 1000; + if (elapsedTime > timeoutSeconds) { + return reject(new Error("Timed out waiting for LND to sync.")); + } + + try { + if (fs.existsSync(lndLogPath)) { + const logContent = fs.readFileSync(lndLogPath, 'utf8'); + + if (logContent.includes("Fully caught up with cfheaders")) { + this.log("LND sync complete."); + return resolve(); + } + + // If target height isn't known yet, try to derive it from the log + if (targetHeight === 0) { + const targetMatch = [...logContent.matchAll(/Syncing to block height\s+(\d+)\s+from peer/gi)]; + if (targetMatch.length > 0) { + const lastTarget = targetMatch[targetMatch.length - 1]; + const parsedTarget = Number.parseInt(lastTarget[1], 10); + if (!Number.isNaN(parsedTarget) && parsedTarget > 0) { + targetHeight = parsedTarget; + this.log(`Detected target header height: ${targetHeight}`); + } + } + } + + // Parse latest reported height: look for "height=NNNN" occurrences + const matches = [...logContent.matchAll(/height=(\d+)/g)]; + if (matches.length > 0) { + const lastMatch = matches[matches.length - 1]; + const currentHeight = Number.parseInt(lastMatch[1], 10); + if (!Number.isNaN(currentHeight) && currentHeight > 0 && targetHeight > 0) { + const percent = Math.min(99, Math.max(0, Math.floor((Math.min(currentHeight, targetHeight) * 100) / targetHeight))); + // Report only on first run and on >=5% increments to reduce noise + if (lastPercentReported === -1 || percent >= lastPercentReported + 5) { + this.log(`LND header sync ${percent}% (height=${currentHeight}/${targetHeight})`); + lastPercentReported = percent; + } + } + } + } + } catch (err) { + // Log file might not exist yet, which is fine. + } + + setTimeout(() => checkLog(resolve, reject), 3000); + }; + + return new Promise((resolve, reject) => { + checkLog(resolve, reject); + }); + } + InitFlow = async (lndCert: Buffer) => { this.log("macaroon not found, creating wallet...") + await this.waitForLndSync(300); // Wait up to 5 minutes const unlocker = this.GetUnlockerClient(lndCert) const { plaintextSeed, encryptedSeed } = await this.genSeed(unlocker) return this.initWallet(lndCert, unlocker, { plaintextSeed, encryptedSeed })