This commit is contained in:
mr
2026-03-09 14:57:41 +01:00
parent 3751ec554d
commit 83cef6e6f6
47 changed files with 2704 additions and 1034 deletions

View File

@@ -1,62 +1,70 @@
@startuml
title Native — ConnectToNatives + Consensus (Pair A bootstrap)
@startuml native_get_consensus
title Native — ConnectToNatives : fetch pool + Phase 1 + Phase 2
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
participant "Node / Indexer\\n(appelant)" as Caller
participant "Native A" as NA
participant "Native B" as NB
participant "Indexer A\\n(stable voter)" as IA
note over NodeA: NativeIndexerAddresses configuré\nAppelé pendant InitNode → ConnectToIndexers
note over Caller: NativeIndexerAddresses configured\\nConnectToNatives() called from ConnectToIndexers
NodeA -> NodeA: Parse NativeIndexerAddresses → StaticNatives
NodeA -> Native1: SendHeartbeat /opencloud/heartbeat/1.0 (20s tick)
NodeA -> Native2: SendHeartbeat /opencloud/heartbeat/1.0 (20s tick)
== Step 1 : heartbeat to the native mesh (nativeHeartbeatOnce) ==
Caller -> NA: SendHeartbeat /opencloud/heartbeat/1.0
Caller -> NB: SendHeartbeat /opencloud/heartbeat/1.0
' Étape 1 : récupérer un pool initial
NodeA -> Native1: Connect + NewStream /opencloud/native/indexers/1.0
NodeA -> Native1: json.Encode(GetIndexersRequest{Count: maxIndexer})
== Step 2 : parrallel fetch pool (timeout 6s) ==
par fetchIndexersFromNative — parallel
Caller -> NA: NewStream /opencloud/native/indexers/1.0\\nGetIndexersRequest{Count: maxIndexer, From: PeerID}
NA -> NA: reachableLiveIndexers()\\ntri par w(F) = fillRate×(1fillRate) desc
NA --> Caller: GetIndexersResponse{Indexers:[IA,IB], FillRates:{IA:0.3,IB:0.6}}
else
Caller -> NB: NewStream /opencloud/native/indexers/1.0
NB -> NB: reachableLiveIndexers()
NB --> Caller: GetIndexersResponse{Indexers:[IA,IB], FillRates:{IA:0.3,IB:0.6}}
end par
Native1 -> Native1: reachableLiveIndexers()
note over Native1: Filtre liveIndexers par TTL\nping chaque candidat (PeerIsAlive)
note over Caller: Fusion → candidates=[IA,IB]\\nisFallback=false
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:[]}
alt isFallback=true (native give themself as Fallback indexer)
note over Caller: resolvePool : avoid consensus\\nadmittedAt = Now (zero)\\nStaticIndexers = {native_addr}
else isFallback=false → Phase 1 + Phase 2
== Phase 1 — clientSideConsensus (timeout 3s/natif, 4s total) ==
par Parralel Consensus
Caller -> NA: NewStream /opencloud/native/consensus/1.0\\nConsensusRequest{Candidates:[IA,IB]}
NA -> NA: compare with clean liveIndexers
NA --> Caller: ConsensusResponse{Trusted:[IA,IB], 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:[]}
Caller -> NB: NewStream /opencloud/native/consensus/1.0
NB --> Caller: ConsensusResponse{Trusted:[IA], Suggestions:[IC]}
end par
note over NodeA: Aggrège les votes (timeout 4s)\nAddr_A → 3/3 votesconfirmé ✓\nAddr_B → 2/3 votes → confirmé ✓
note over Caller: IA → 2/2 votes → confirmed ✓\\nIB → 1/2 vote → refusé ✗\\nIC → suggestion → round 2 if confirmed < maxIndexer
alt confirmed < maxIndexer && suggestions disponibles
note over NodeA: Round 2 — rechallenge avec suggestions
NodeA -> NodeA: clientSideConsensus(confirmed + sample(suggestions))
alt confirmed < maxIndexer && available suggestions
note over Caller: Round 2 — rechallenge with confirmed + sample(suggestions)\\nclientSideConsensus([IA, IC])
end
NodeA -> NodeA: StaticIndexers = adresses confirmées à majorité
note over Caller: admittedAt = time.Now()
== Phase 2 — indexerLivenessVote (timeout 3s/votant, 4s total) ==
note over Caller: Search for stable voters in Subscribed Indexers\\nAdmittedAt != zero && age >= MinStableAge (2min)
alt Stable Voters are available
par Phase 2 parrallel
Caller -> IA: NewStream /opencloud/indexer/consensus/1.0\\nIndexerConsensusRequest{Candidates:[IA]}
IA -> IA: StreamRecords[ProtocolHB][candidate]\\ntime.Since(LastSeen) <= 120s && LastScore >= 30.0
IA --> Caller: IndexerConsensusResponse{Alive:[IA]}
end par
note over Caller: alive IA confirmed per quorum > 0.5\\npool = {IA}
else No voters are stable (startup)
note over Caller: Phase 1 keep directly\\n(no indexer reaches MinStableAge)
end
== Replacement pool ==
Caller -> Caller: replaceStaticIndexers(pool, admittedAt)\\nStaticIndexerMeta[IA].AdmittedAt = admittedAt
end
== Étape 3 : heartbeat to indexers pool (ConnectToIndexers) ==
Caller -> Caller: SendHeartbeat /opencloud/heartbeat/1.0\\nvers StaticIndexers
@enduml