@startuml title Node Initialization — Peer A (InitNode) participant "main (Peer A)" as MainA participant "Node A" as NodeA participant "libp2p (Peer A)" as libp2pA participant "ConnectionGater A" as GaterA participant "DB Peer A (oc-lib)" as DBA participant "NATS A" as NATSA participant "Indexer (shared)" as IndexerA participant "DHT A" as DHTA participant "StreamService A" as StreamA participant "PubSubService A" as PubSubA MainA -> NodeA: InitNode(isNode=true, isIndexer=false) NodeA -> NodeA: LoadKeyFromFilePrivate() → priv NodeA -> NodeA: LoadPSKFromFile() → psk NodeA -> GaterA: newOCConnectionGater(nil) NodeA -> libp2pA: New(\n PrivateNetwork(psk),\n Identity(priv),\n ListenAddr: tcp/4001,\n ConnectionGater(gater)\n) libp2pA --> NodeA: host A (PeerID_A) NodeA -> GaterA: gater.host = host A note over GaterA: InterceptSecured (inbound):\n1. DB lookup by peer_id\n → BLACKLIST : refuse\n → found : accept\n2. Not found → DHT sequential check\n (transport-error fallthrough only) NodeA -> libp2pA: SetStreamHandler(/opencloud/probe/1.0, HandleBandwidthProbe) NodeA -> libp2pA: SetStreamHandler(/opencloud/witness/1.0, HandleWitnessQuery) NodeA -> libp2pA: NewGossipSub(ctx, host) → ps (GossipSub) NodeA -> NodeA: buildRecord() closure\n→ signs fresh PeerRecord (expiry=now+2min)\n embedded in each heartbeat tick NodeA -> IndexerA: ConnectToIndexers(host, minIndexer=1, maxIndexer=5, buildRecord) note over IndexerA: Reads IndexerAddresses from config\nAdds seeds → Indexers Directory (IsSeed=true)\nLaunches SendHeartbeat goroutine (20s ticker) IndexerA -> DHTA: proactive DHT discovery (after 5s warmup)\ninitNodeDHT(h, seeds)\nDiscoverIndexersFromDHT → SelectByFillRate\n→ add to Indexers Directory + NudgeIt() NodeA -> NodeA: claimInfo(name, hostname) NodeA -> IndexerA: TempStream /opencloud/record/publish/1.0 NodeA -> IndexerA: stream.Encode(Signed PeerRecord A) IndexerA -> DHTA: PutValue("/node/"+DID_A, record) NodeA -> NodeA: StartGC(30s) NodeA -> StreamA: InitStream(ctx, host, PeerID_A, 1000, nodeA) StreamA -> StreamA: SetStreamHandler(resource/search, create, update,\n delete, planner, verify, considers) StreamA --> NodeA: StreamService A NodeA -> PubSubA: InitPubSub(ctx, host, ps, nodeA, streamA) PubSubA -> PubSubA: subscribeEvents(PB_SEARCH, timeout=-1) PubSubA --> NodeA: PubSubService A NodeA -> NodeA: SubscribeToSearch(ps, callback) note over NodeA: callback: if evt.From != self\n → GetPeerRecord(evt.From)\n → StreamService.SendResponse NodeA -> NATSA: ListenNATS(nodeA) note over NATSA: Subscribes:\nCREATE_RESOURCE → partner on-demand\nPROPALGATION_EVENT → resource propagation NodeA --> MainA: *Node A is ready note over NodeA,IndexerA: SendHeartbeat goroutine (permanent, 20s ticker):\nNode → Indexer : Heartbeat{name, PeerID, indexersBinded, need, challenges?, record}\nIndexer → Node : HeartbeatResponse{fillRate, challenges, suggestions, witnesses, suggestMigrate}\nScore updated (7 dimensions), pool managed autonomously @enduml