Discovery Neo Oclib
This commit is contained in:
@@ -66,6 +66,10 @@ type Heartbeat struct {
|
||||
// MembershipEvents carries SWIM events piggybacked on this heartbeat.
|
||||
// Events are forwarded infection-style until HopsLeft reaches 0.
|
||||
MembershipEvents []MemberEvent `json:"membership_events,omitempty"`
|
||||
// PendingContact lists peer IDs for which this node has undelivered critical
|
||||
// DTN entries. Indexers maintain an inverted index so those peers can
|
||||
// discover who is waiting for them when they reconnect.
|
||||
PendingContact []string `json:"pending_contact,omitempty"`
|
||||
}
|
||||
|
||||
// SearchPeerRequest is sent by a node to an indexer via ProtocolSearchPeer.
|
||||
@@ -134,6 +138,10 @@ type HeartbeatResponse struct {
|
||||
// MembershipEvents carries SWIM events piggybacked on this response.
|
||||
// The node should forward them to its other indexers (infection-style).
|
||||
MembershipEvents []MemberEvent `json:"membership_events,omitempty"`
|
||||
// PendingCallers lists peer IDs that have undelivered critical DTN messages
|
||||
// for the receiving node, as recorded by this indexer. On receipt the node
|
||||
// should initiate contact with each caller so it can flush its DTN cache.
|
||||
PendingCallers []string `json:"pending_callers,omitempty"`
|
||||
}
|
||||
|
||||
// ComputeIndexerScore computes a composite quality score [0, 100] for the connecting peer.
|
||||
|
||||
@@ -29,7 +29,7 @@ var retryRunning atomic.Bool
|
||||
// peer has at least 3 chances to respond or refute the suspicion signal.
|
||||
const suspectTimeout = 3 * RecommendedHeartbeatInterval
|
||||
|
||||
func ConnectToIndexers(h host.Host, minIndexer int, maxIndexer int, recordFn ...func() json.RawMessage) error {
|
||||
func ConnectToIndexers(h host.Host, minIndexer int, maxIndexer int, hooks ...HeartbeatHooks) error {
|
||||
TimeWatcher = time.Now().UTC()
|
||||
logger := oclib.GetLogger()
|
||||
|
||||
@@ -71,7 +71,7 @@ func ConnectToIndexers(h host.Host, minIndexer int, maxIndexer int, recordFn ...
|
||||
// Start long-lived heartbeat to seed indexers. The single goroutine follows
|
||||
// all subsequent StaticIndexers changes.
|
||||
SendHeartbeat(context.Background(), ProtocolHeartbeat, conf.GetConfig().Name,
|
||||
h, Indexers, 20*time.Second, maxIndexer, recordFn...)
|
||||
h, Indexers, 20*time.Second, maxIndexer, hooks...)
|
||||
|
||||
// Watch for inbound connections: if a peer connects to us and our pool has
|
||||
// room, probe it first to confirm it supports ProtocolHeartbeat (i.e. it is
|
||||
@@ -270,17 +270,29 @@ func handleSuggestions(d *Directory, from string, suggestions []pp.AddrInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
// HeartbeatHooks carries optional callbacks injected into the heartbeat loop.
|
||||
type HeartbeatHooks struct {
|
||||
// RecordFn returns a fresh signed PeerRecord for embedding in each heartbeat.
|
||||
RecordFn func() json.RawMessage
|
||||
// PendingContactFn returns the list of peer IDs for which the caller has
|
||||
// undelivered critical DTN entries. Called on every tick.
|
||||
PendingContactFn func() []string
|
||||
// OnPendingCallers is invoked when an indexer response contains peer IDs
|
||||
// that have undelivered messages for us. The caller should initiate contact
|
||||
// with each of them so they can flush their DTN cache.
|
||||
OnPendingCallers func(callerPeerIDs []string)
|
||||
}
|
||||
|
||||
// SendHeartbeat starts a goroutine that sends periodic heartbeats to peers.
|
||||
// recordFn, when provided, is called on each tick and its output is embedded in
|
||||
// the heartbeat as a fresh signed PeerRecord so the receiving indexer can
|
||||
// republish it to the DHT without an extra round-trip.
|
||||
// Pass no recordFn (or nil) for indexer→indexer / native heartbeats.
|
||||
func SendHeartbeat(ctx context.Context, proto protocol.ID, name string, h host.Host, directory *Directory, interval time.Duration, maxPool int, recordFn ...func() json.RawMessage) {
|
||||
// hooks.RecordFn, when set, is called on each tick and its output is embedded
|
||||
// in the heartbeat as a fresh signed PeerRecord.
|
||||
// Pass an empty HeartbeatHooks (or none) for indexer→indexer / native heartbeats.
|
||||
func SendHeartbeat(ctx context.Context, proto protocol.ID, name string, h host.Host, directory *Directory, interval time.Duration, maxPool int, hooks ...HeartbeatHooks) {
|
||||
logger := oclib.GetLogger()
|
||||
isIndexerHB := directory == Indexers
|
||||
var recFn func() json.RawMessage
|
||||
if len(recordFn) > 0 {
|
||||
recFn = recordFn[0]
|
||||
var hk HeartbeatHooks
|
||||
if len(hooks) > 0 {
|
||||
hk = hooks[0]
|
||||
}
|
||||
go func() {
|
||||
logger.Info().Str("proto", string(proto)).Int("peers", len(directory.Addrs)).Msg("heartbeat started")
|
||||
@@ -306,8 +318,11 @@ func SendHeartbeat(ctx context.Context, proto protocol.ID, name string, h host.H
|
||||
IndexersBinded: addrs,
|
||||
Need: need,
|
||||
}
|
||||
if recFn != nil {
|
||||
baseHB.Record = recFn()
|
||||
if hk.RecordFn != nil {
|
||||
baseHB.Record = hk.RecordFn()
|
||||
}
|
||||
if hk.PendingContactFn != nil {
|
||||
baseHB.PendingContact = hk.PendingContactFn()
|
||||
}
|
||||
// Piggyback SWIM membership events on every outgoing heartbeat batch.
|
||||
// All peers in the pool receive the same events this tick.
|
||||
@@ -550,6 +565,12 @@ func SendHeartbeat(ctx context.Context, proto protocol.ID, name string, h host.H
|
||||
handleSuggestions(directory, ai.Info.ID.String(), resp.Suggestions)
|
||||
}
|
||||
|
||||
// PendingCallers: peers that have undelivered DTN messages for us.
|
||||
// Signal the DTN layer so it can flush immediately when it reaches them.
|
||||
if resp != nil && len(resp.PendingCallers) > 0 && hk.OnPendingCallers != nil {
|
||||
hk.OnPendingCallers(resp.PendingCallers)
|
||||
}
|
||||
|
||||
// Handle SuggestMigrate: indexer is overloaded and wants us to move.
|
||||
if resp != nil && resp.SuggestMigrate && isIndexerHB {
|
||||
nonSeedCount := 0
|
||||
|
||||
@@ -261,7 +261,10 @@ func (ix *LongLivedStreamRecordedService[T]) HandleHeartbeat(s network.Stream) {
|
||||
}
|
||||
|
||||
func CheckHeartbeat(h host.Host, s network.Stream, dec *json.Decoder, streams map[pp.ID]HeartBeatStreamed, lock *sync.RWMutex, maxNodes int) (*pp.ID, *Heartbeat, error) {
|
||||
if len(h.Network().Peers()) >= maxNodes {
|
||||
// Use the heartbeat stream count, not h.Network().Peers(), which includes
|
||||
// upstream indexer connections, short-lived protocol streams (publish/get/probe),
|
||||
// and zombie libp2p connections whose heartbeat stream has already been GC'd.
|
||||
if len(streams) >= maxNodes {
|
||||
return nil, nil, fmt.Errorf("too many connections, try another indexer")
|
||||
}
|
||||
var hb Heartbeat
|
||||
@@ -285,9 +288,11 @@ func CheckHeartbeat(h host.Host, s network.Stream, dec *json.Decoder, streams ma
|
||||
// E: measure the indexer's own subnet diversity, not the node's view.
|
||||
diversity := getOwnDiversityRate(h)
|
||||
// fillRate: fraction of indexer capacity used — higher = more peers trust this indexer.
|
||||
// Use heartbeat stream count (same as fill rate reported to nodes), not
|
||||
// h.Network().Peers() which inflates the count with upstream/probe connections.
|
||||
fillRate := 0.0
|
||||
if maxNodes > 0 {
|
||||
fillRate = float64(len(h.Network().Peers())) / float64(maxNodes)
|
||||
fillRate = float64(len(streams)) / float64(maxNodes)
|
||||
if fillRate > 1 {
|
||||
fillRate = 1
|
||||
}
|
||||
|
||||
@@ -184,11 +184,10 @@ func TempStream(h host.Host, ad pp.AddrInfo, proto protocol.ID, did string, stre
|
||||
}
|
||||
ctxTTL, cancelTTL := context.WithTimeout(context.Background(), expiry)
|
||||
defer cancelTTL()
|
||||
|
||||
if h.Network().Connectedness(ad.ID) != network.Connected {
|
||||
fmt.Println(ad.ID, len(h.Network().ConnsToPeer(ad.ID)))
|
||||
if len(h.Network().ConnsToPeer(ad.ID)) == 0 {
|
||||
if err := h.Connect(ctxTTL, ad); err != nil {
|
||||
fmt.Println("Connectedness", ad.ID, err)
|
||||
|
||||
return streams, err
|
||||
}
|
||||
}
|
||||
@@ -233,7 +232,8 @@ func sendHeartbeat(ctx context.Context, h host.Host, proto protocol.ID, p *pp.Ad
|
||||
pss, exists := streams[p.ID]
|
||||
ctxTTL, cancel := context.WithTimeout(ctx, 3*interval)
|
||||
defer cancel()
|
||||
if h.Network().Connectedness(p.ID) != network.Connected {
|
||||
fmt.Println(p.ID, len(h.Network().ConnsToPeer(p.ID)))
|
||||
if len(h.Network().ConnsToPeer(p.ID)) == 0 {
|
||||
if err := h.Connect(ctxTTL, *p); err != nil {
|
||||
logger.Err(err)
|
||||
return nil, 0, err
|
||||
|
||||
@@ -3,12 +3,12 @@ package common
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
oclib "cloud.o-forge.io/core/oc-lib"
|
||||
"github.com/libp2p/go-libp2p/core/host"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
pp "github.com/libp2p/go-libp2p/core/peer"
|
||||
)
|
||||
|
||||
@@ -153,7 +153,8 @@ func TriggerConsensus(h host.Host, remaining []pp.AddrInfo, need int) {
|
||||
func probeIndexer(h host.Host, ai pp.AddrInfo) (*HeartbeatResponse, time.Duration, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
|
||||
defer cancel()
|
||||
if h.Network().Connectedness(ai.ID) != network.Connected {
|
||||
fmt.Println(ai.ID, len(h.Network().ConnsToPeer(ai.ID)))
|
||||
if len(h.Network().ConnsToPeer(ai.ID)) == 0 {
|
||||
if err := h.Connect(ctx, ai); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user