@startuml hb_last_indexer title Protection last-indexer → reconnectToSeeds → retryUntilSeedResponds participant "Node A" as NodeA participant "Indexer LAST\n(seul restant)" as IL participant "Seed Indexer\n(config)" as SEED participant "DHT" as DHT note over NodeA: Pool = 1 indexeur (LAST)\nIsSeed=false, score bas depuis longtemps == Tentative d'éviction par score == NodeA -> NodeA: score < minScore\nAND TotalOnline ≥ 2×interval\nAND !IsSeed\nAND len(pool) > 1 ← FAUX : pool == 1 note over NodeA: Garde active : len(pool) == 1\n→ éviction par score BLOQUÉE\nLAST reste dans le pool == Panne réseau (heartbeat fail) == NodeA -> IL: stream.Encode(Heartbeat{...}) IL -->x NodeA: timeout NodeA -> NodeA: HeartbeatFailure → evictPeer(LAST)\npoolSize = 0 NodeA -> NodeA: reconnectToSeeds()\n→ parse IndexerAddresses (conf)\n→ SetAddr + SetScore(IsSeed=true) pour chaque seed alt seeds ajoutés (IndexerAddresses non vide) NodeA -> NodeA: NudgeIt() → tick immédiat NodeA -> SEED: Heartbeat{...} (via SendHeartbeat nudge) SEED --> NodeA: HeartbeatResponse{fillRate, ...} note over NodeA: Pool rétabli via seeds.\nDHT proactive discovery reprend. else IndexerAddresses vide NodeA -> NodeA: go retryUntilSeedResponds() note over NodeA: panic immédiat :\n"pool is empty and no seed indexers configured"\n→ arrêt du processus end == retryUntilSeedResponds (si seeds non répondants) == loop backoff exponentiel (10s → 20s → ... → 5min) NodeA -> NodeA: time.Sleep(backoff) NodeA -> NodeA: len(Indexers.GetAddrs()) > 0?\n→ oui : retour (quelqu'un a refillé) NodeA -> NodeA: reconnectToSeeds() alt pool > 0 après reconnect NodeA -> NodeA: NudgeIt()\nDHT.Bootstrap(ctx, 15s) note over NodeA: Sortie de la boucle.\nHeartbeat normal reprend. end end @enduml