From c6f458e478f9ef2cf1d394972bfbc512862c6707 Mon Sep 17 00:00:00 2001 From: Mohamed Awnallah Date: Wed, 15 Oct 2025 20:26:20 +0000 Subject: [PATCH] lnd.go: sync headers before chain notifier startup to prevent premature rescan This commit addresses a regression where Neutrino rescanning starts from an outdated height (~100k blocks behind) instead of using the current synced height. Root Cause: In commit 16a8b623b, the initialization order was changed so that Chain Notifier starts before wallet syncing completes. This means the rescan begins using the stale height from BuildChainControl rather than the fully synced height. Old behavior (commit 1dfb5a0c2): 1. RPC server starts 2. Headers sync as part of daemon server 3. Chain Notifier starts after sync completes 4. Rescan begins from current (synced) height Current behavior (regression): 1. Chain Notifier starts in newServer (before RPC) 2. Wallet sync happens after RPC server starts 3. Rescan uses outdated height from BuildChainControl Solution: - Ensure headers are fully synced before starting the chain notifier, and after starting the RPC server. - Move chain notifier startup to its correct location after headers are fully synced. - Make sure the starting beat is lazily called after chain notifier started and before that starting beat result is used. --- server.go | 52 ++++++++++++---------------------------------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/server.go b/server.go index a2d36eb86..6d0b28f7e 100644 --- a/server.go +++ b/server.go @@ -733,17 +733,6 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr, quit: make(chan struct{}), } - // Start the low-level services once they are initialized. - // - // TODO(yy): break the server startup into four steps, - // 1. init the low-level services. - // 2. start the low-level services. - // 3. init the high-level services. - // 4. start the high-level services. - if err := s.startLowLevelServices(); err != nil { - return nil, err - } - currentHash, currentHeight, err := s.cc.ChainIO.GetBestBlock() if err != nil { return nil, err @@ -2125,41 +2114,12 @@ func (c cleaner) run() { } } -// startLowLevelServices starts the low-level services of the server. These -// services must be started successfully before running the main server. The -// services are, -// 1. the chain notifier. -// -// TODO(yy): identify and add more low-level services here. -func (s *server) startLowLevelServices() error { - var startErr error - - cleanup := cleaner{} - - cleanup = cleanup.add(s.cc.ChainNotifier.Stop) - if err := s.cc.ChainNotifier.Start(); err != nil { - startErr = err - } - - if startErr != nil { - cleanup.run() - } - - return startErr -} - // Start starts the main daemon server, all requested listeners, and any helper // goroutines. // NOTE: This function is safe for concurrent access. // //nolint:funlen func (s *server) Start(ctx context.Context) error { - // Get the current blockbeat. - beat, err := s.getStartingBeat() - if err != nil { - return err - } - var startErr error // If one sub system fails to start, the following code ensures that the @@ -2213,6 +2173,12 @@ func (s *server) Start(ctx context.Context) error { return } + cleanup = cleanup.add(s.cc.ChainNotifier.Stop) + if err := s.cc.ChainNotifier.Start(); err != nil { + startErr = err + return + } + cleanup = cleanup.add(s.cc.BestBlockTracker.Stop) if err := s.cc.BestBlockTracker.Start(); err != nil { startErr = err @@ -2247,6 +2213,12 @@ func (s *server) Start(ctx context.Context) error { } } + beat, err := s.getStartingBeat() + if err != nil { + startErr = err + return + } + cleanup = cleanup.add(s.txPublisher.Stop) if err := s.txPublisher.Start(beat); err != nil { startErr = err