workaround for lnd rescan bug

This commit is contained in:
shocknet-justin 2025-08-27 23:27:49 -04:00
parent ce94b316fb
commit 3b8ec65c67
6 changed files with 92 additions and 2 deletions

View file

@ -8,6 +8,7 @@
#LND_ADDRESS=127.0.0.1:10009 #LND_ADDRESS=127.0.0.1:10009
#LND_CERT_PATH=~/.lnd/tls.cert #LND_CERT_PATH=~/.lnd/tls.cert
#LND_MACAROON_PATH=~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon #LND_MACAROON_PATH=~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon
#LND_LOG_DIR=~/.lnd/logs/bitcoin/mainnet/lnd.log
#BOOTSTRAP_PEER #BOOTSTRAP_PEER
# A trusted peer that will hold a node-level account until channel automation becomes affordable # A trusted peer that will hold a node-level account until channel automation becomes affordable

View file

@ -12,7 +12,7 @@ get_log_info() {
LOG_DIR="$USER_HOME/lightning_pub/logs" LOG_DIR="$USER_HOME/lightning_pub/logs"
DATA_DIR="$USER_HOME/lightning_pub/" DATA_DIR="$USER_HOME/lightning_pub/"
START_TIME=$(date +%s) 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 WAIT_INTERVAL=5 # Time to wait between checks in seconds
log "Checking wallet status... This may take a moment." log "Checking wallet status... This may take a moment."
@ -50,7 +50,24 @@ get_log_info() {
START_TIME=$(date +%s) START_TIME=$(date +%s)
while [ $(($(date +%s) - START_TIME)) -lt $MAX_WAIT_TIME ]; do 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) 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 if [ -n "$latest_entry" ]; then
# End the progress line cleanly
echo ""
break break
fi fi
sleep $WAIT_INTERVAL sleep $WAIT_INTERVAL

View file

@ -107,6 +107,7 @@ else
# Only start services if it was a fresh install or an upgrade. # Only start services if it was a fresh install or an upgrade.
if [ "$pub_upgrade_status" -eq 0 ] || [ "$pub_upgrade_status" -eq 100 ]; then if [ "$pub_upgrade_status" -eq 0 ] || [ "$pub_upgrade_status" -eq 100 ]; then
log "Starting services..." log "Starting services..."
log "Note: LND may take several minutes to sync block headers depending on network conditions."
touch /tmp/pub_install_timestamp touch /tmp/pub_install_timestamp
start_services $lnd_status $pub_upgrade_status || log_error "Failed to start services" 1 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 get_log_info || log_error "Failed to get log info" 1

View file

@ -17,9 +17,10 @@ export const LoadLndSettingsFromEnv = (): LndSettings => {
const lndAddr = process.env.LND_ADDRESS || "127.0.0.1:10009" const lndAddr = process.env.LND_ADDRESS || "127.0.0.1:10009"
const lndCertPath = process.env.LND_CERT_PATH || resolveHome("/.lnd/tls.cert") 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 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 feeRateBps = EnvCanBeInteger("OUTBOUND_MAX_FEE_BPS", 60)
const feeRateLimit = feeRateBps / 10000 const feeRateLimit = feeRateBps / 10000
const feeFixedLimit = EnvCanBeInteger("OUTBOUND_MAX_FEE_EXTRA_SATS", 100) const feeFixedLimit = EnvCanBeInteger("OUTBOUND_MAX_FEE_EXTRA_SATS", 100)
const mockLnd = EnvCanBeBoolean("MOCK_LND") const mockLnd = EnvCanBeBoolean("MOCK_LND")
return { mainNode: { lndAddr, lndCertPath, lndMacaroonPath }, feeRateLimit, feeFixedLimit, feeRateBps, mockLnd } return { mainNode: { lndAddr, lndCertPath, lndMacaroonPath }, lndLogDir, feeRateLimit, feeFixedLimit, feeRateBps, mockLnd }
} }

View file

@ -7,6 +7,7 @@ export type NodeSettings = {
} }
export type LndSettings = { export type LndSettings = {
mainNode: NodeSettings mainNode: NodeSettings
lndLogDir: string
feeRateLimit: number feeRateLimit: number
feeFixedLimit: number feeFixedLimit: number
feeRateBps: number feeRateBps: number
@ -15,6 +16,7 @@ export type LndSettings = {
otherNode?: NodeSettings otherNode?: NodeSettings
thirdNode?: NodeSettings thirdNode?: NodeSettings
} }
type TxOutput = { type TxOutput = {
hash: string hash: string
index: number index: number

View file

@ -95,8 +95,76 @@ export class Unlocker {
return { ln, pub: infoAfter.pub, action: 'unlocked' } return { ln, pub: infoAfter.pub, action: 'unlocked' }
} }
private waitForLndSync = async (timeoutSeconds: number): Promise<void> => {
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<void>((resolve, reject) => {
checkLog(resolve, reject);
});
}
InitFlow = async (lndCert: Buffer) => { InitFlow = async (lndCert: Buffer) => {
this.log("macaroon not found, creating wallet...") this.log("macaroon not found, creating wallet...")
await this.waitForLndSync(300); // Wait up to 5 minutes
const unlocker = this.GetUnlockerClient(lndCert) const unlocker = this.GetUnlockerClient(lndCert)
const { plaintextSeed, encryptedSeed } = await this.genSeed(unlocker) const { plaintextSeed, encryptedSeed } = await this.genSeed(unlocker)
return this.initWallet(lndCert, unlocker, { plaintextSeed, encryptedSeed }) return this.initWallet(lndCert, unlocker, { plaintextSeed, encryptedSeed })