@startuml title Native — ConnectToNatives + Consensus (Pair A bootstrap) participant "Node A" as NodeA participant "Native #1 (primary)" as Native1 participant "Native #2" as Native2 participant "Native #N" as NativeN participant "DHT Kademlia" as DHT note over NodeA: NativeIndexerAddresses configuré\nAppelé pendant InitNode → ConnectToIndexers NodeA -> NodeA: Parse NativeIndexerAddresses → StaticNatives NodeA -> Native1: SendHeartbeat /opencloud/heartbeat/1.0 (20s tick) NodeA -> Native2: SendHeartbeat /opencloud/heartbeat/1.0 (20s tick) ' Étape 1 : récupérer un pool initial NodeA -> Native1: Connect + NewStream /opencloud/native/indexers/1.0 NodeA -> Native1: json.Encode(GetIndexersRequest{Count: maxIndexer}) Native1 -> Native1: reachableLiveIndexers() note over Native1: Filtre liveIndexers par TTL\nping chaque candidat (PeerIsAlive) alt Aucun indexer connu par Native1 Native1 -> Native1: selfDelegate(NodeA.PeerID, resp) note over Native1: IsSelfFallback=true\nIndexers=[native1 addr] Native1 -> NodeA: GetIndexersResponse{IsSelfFallback:true, Indexers:[native1]} NodeA -> NodeA: StaticIndexers[native1] = native1 note over NodeA: Pas de consensus — native1 utilisé directement comme indexeur else Indexers disponibles Native1 -> NodeA: GetIndexersResponse{Indexers:[Addr_IndexerA, Addr_IndexerB, ...]} ' Étape 2 : consensus note over NodeA: clientSideConsensus(candidates) par Requêtes consensus parallèles NodeA -> Native1: NewStream /opencloud/native/consensus/1.0 NodeA -> Native1: ConsensusRequest{Candidates:[Addr_A, Addr_B]} Native1 -> Native1: Croiser avec liveIndexers propres Native1 -> NodeA: ConsensusResponse{Trusted:[Addr_A, Addr_B], Suggestions:[]} else NodeA -> Native2: NewStream /opencloud/native/consensus/1.0 NodeA -> Native2: ConsensusRequest{Candidates:[Addr_A, Addr_B]} Native2 -> Native2: Croiser avec liveIndexers propres Native2 -> NodeA: ConsensusResponse{Trusted:[Addr_A], Suggestions:[Addr_C]} else NodeA -> NativeN: NewStream /opencloud/native/consensus/1.0 NodeA -> NativeN: ConsensusRequest{Candidates:[Addr_A, Addr_B]} NativeN -> NativeN: Croiser avec liveIndexers propres NativeN -> NodeA: ConsensusResponse{Trusted:[Addr_A, Addr_B], Suggestions:[]} end par note over NodeA: Aggrège les votes (timeout 4s)\nAddr_A → 3/3 votes → confirmé ✓\nAddr_B → 2/3 votes → confirmé ✓ alt confirmed < maxIndexer && suggestions disponibles note over NodeA: Round 2 — rechallenge avec suggestions NodeA -> NodeA: clientSideConsensus(confirmed + sample(suggestions)) end NodeA -> NodeA: StaticIndexers = adresses confirmées à majorité end @enduml