package indexer import ( "context" "oc-discovery/conf" "oc-discovery/daemons/node/common" "strings" "sync" oclib "cloud.o-forge.io/core/oc-lib" dht "github.com/libp2p/go-libp2p-kad-dht" pubsub "github.com/libp2p/go-libp2p-pubsub" record "github.com/libp2p/go-libp2p-record" "github.com/libp2p/go-libp2p/core/host" pp "github.com/libp2p/go-libp2p/core/peer" ) // IndexerService manages the indexer node's state: stream records, DHT, pubsub. type IndexerService struct { *common.LongLivedStreamRecordedService[PeerRecord] PS *pubsub.PubSub DHT *dht.IpfsDHT isStrictIndexer bool mu sync.RWMutex IsNative bool Native *NativeState // non-nil when IsNative == true nameIndex *nameIndexState } // NewIndexerService creates an IndexerService. // If ps is nil, this is a strict indexer (no pre-existing gossip sub from a node). func NewIndexerService(h host.Host, ps *pubsub.PubSub, maxNode int, isNative bool) *IndexerService { logger := oclib.GetLogger() logger.Info().Msg("open indexer mode...") var err error ix := &IndexerService{ LongLivedStreamRecordedService: common.NewStreamRecordedService[PeerRecord](h, maxNode), isStrictIndexer: ps == nil, IsNative: isNative, } if ps == nil { ps, err = pubsub.NewGossipSub(context.Background(), ix.Host) if err != nil { panic(err) // can't run your indexer without a propagation pubsub } } ix.PS = ps if ix.isStrictIndexer && !isNative { logger.Info().Msg("connect to indexers as strict indexer...") common.ConnectToIndexers(h, conf.GetConfig().MinIndexer, conf.GetConfig().MaxIndexer, ix.Host.ID()) logger.Info().Msg("subscribe to decentralized search flow as strict indexer...") go ix.SubscribeToSearch(ix.PS, nil) } if !isNative { logger.Info().Msg("init distributed name index...") ix.initNameIndex(ps) ix.LongLivedStreamRecordedService.AfterDelete = func(pid pp.ID, name, did string) { ix.publishNameEvent(NameIndexDelete, name, pid.String(), did) } } // Parse bootstrap peers from configured native/indexer addresses so that the // DHT can find its routing table entries even in a fresh deployment. var bootstrapPeers []pp.AddrInfo for _, addrStr := range strings.Split(conf.GetConfig().NativeIndexerAddresses+","+conf.GetConfig().IndexerAddresses, ",") { addrStr = strings.TrimSpace(addrStr) if addrStr == "" { continue } if ad, err := pp.AddrInfoFromString(addrStr); err == nil { bootstrapPeers = append(bootstrapPeers, *ad) } } dhtOpts := []dht.Option{ dht.Mode(dht.ModeServer), dht.ProtocolPrefix("oc"), // đŸ”„ rĂ©seau privĂ© dht.Validator(record.NamespacedValidator{ "node": PeerRecordValidator{}, "indexer": IndexerRecordValidator{}, // for native indexer registry "name": DefaultValidator{}, "pid": DefaultValidator{}, }), } if len(bootstrapPeers) > 0 { dhtOpts = append(dhtOpts, dht.BootstrapPeers(bootstrapPeers...)) } if ix.DHT, err = dht.New(context.Background(), ix.Host, dhtOpts...); err != nil { logger.Info().Msg(err.Error()) return nil } // InitNative must happen after DHT is ready if isNative { ix.InitNative() } else { ix.initNodeHandler() // Register with configured natives so this indexer appears in their cache. // Pass a fill rate provider so the native can route new nodes to less-loaded indexers. if nativeAddrs := conf.GetConfig().NativeIndexerAddresses; nativeAddrs != "" { fillRateFn := func() float64 { ix.StreamMU.RLock() n := len(ix.StreamRecords[common.ProtocolHeartbeat]) ix.StreamMU.RUnlock() maxN := ix.MaxNodesConn() if maxN <= 0 { return 0 } rate := float64(n) / float64(maxN) if rate > 1 { rate = 1 } return rate } common.StartNativeRegistration(ix.Host, nativeAddrs, fillRateFn) } } return ix } func (ix *IndexerService) Close() { if ix.Native != nil && ix.Native.cancel != nil { ix.Native.cancel() } // Explicitly deregister from natives on clean shutdown so they evict this // indexer immediately rather than waiting for TTL expiry (~90 s). if !ix.IsNative { if nativeAddrs := conf.GetConfig().NativeIndexerAddresses; nativeAddrs != "" { common.UnregisterFromNative(ix.Host, nativeAddrs) } } ix.DHT.Close() ix.PS.UnregisterTopicValidator(common.TopicPubSubSearch) if ix.nameIndex != nil { ix.PS.UnregisterTopicValidator(TopicNameIndex) } for _, s := range ix.StreamRecords { for _, ss := range s { ss.HeartbeatStream.Stream.Close() } } }