@@ -62,7 +62,7 @@ type WatcherConfig struct {
6262 PollInterval time.Duration
6363 ReadTimeout time.Duration
6464 StartLedger uint64
65- MaxPerPoll int
65+ MaxPerPoll int
6666}
6767
6868func (wc * WatcherConfig ) Create (
@@ -173,11 +173,8 @@ func (w *watcher) Run(ctx context.Context) error {
173173 w .logger = logger
174174
175175 if w .nextLedger == 0 {
176- seq , err := w .getLatestLedger (ctx )
176+ seq , err := w .getInitialLedger (ctx , logger )
177177 if err != nil {
178- stellarConnectionErrors .WithLabelValues (w .networkName , "initial_ledger" ).Inc ()
179- p2p .DefaultRegistry .AddErrorCount (w .chainID , 1 )
180- logger .Error ("failed to get latest ledger" , zap .Error (err ))
181178 return err
182179 }
183180 w .nextLedger = seq
@@ -214,6 +211,42 @@ func (w *watcher) Run(ctx context.Context) error {
214211 }
215212}
216213
214+ // getInitialLedger fetches the starting ledger, retrying with capped exponential
215+ // backoff. A transient RPC outage at startup (e.g. the Soroban RPC not yet
216+ // reachable when the guardian boots) must not kill the watcher, mirroring the
217+ // resilience of the poll loop. Returns an error only when the context is cancelled.
218+ func (w * watcher ) getInitialLedger (ctx context.Context , logger * zap.Logger ) (uint64 , error ) {
219+ backoff := w .pollInterval
220+ if backoff <= 0 {
221+ backoff = 700 * time .Millisecond
222+ }
223+ const maxBackoff = 60 * time .Second
224+
225+ for {
226+ seq , err := w .getLatestLedger (ctx )
227+ if err == nil {
228+ return seq , nil
229+ }
230+
231+ stellarConnectionErrors .WithLabelValues (w .networkName , "initial_ledger" ).Inc ()
232+ p2p .DefaultRegistry .AddErrorCount (w .chainID , 1 )
233+ logger .Warn ("failed to get latest ledger, retrying" , zap .Error (err ), zap .Duration ("backoff" , backoff ))
234+
235+ select {
236+ case <- ctx .Done ():
237+ return 0 , ctx .Err ()
238+ case <- time .After (backoff ):
239+ }
240+
241+ if backoff < maxBackoff {
242+ backoff *= 2
243+ if backoff > maxBackoff {
244+ backoff = maxBackoff
245+ }
246+ }
247+ }
248+ }
249+
217250// runReobservationHandler handles incoming reobservation requests. Returns an error only on fatal failure.
218251func (w * watcher ) runReobservationHandler (ctx context.Context ) error {
219252 logger := w .logger
0 commit comments