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.
This commit is contained in:
Mohamed Awnallah
2025-10-15 20:26:20 +00:00
parent 6ade31d05c
commit c6f458e478

View File

@@ -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