oc-discovery -> conf
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"oc-discovery/daemons/node/common"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
oclib "cloud.o-forge.io/core/oc-lib"
|
||||
@@ -30,9 +31,9 @@ type dhtCacheEntry struct {
|
||||
// SuggestMigrate to a small batch at a time; peers that don't migrate within
|
||||
// offloadGracePeriod are moved to alreadyTried so a new batch can be picked.
|
||||
type offloadState struct {
|
||||
inBatch map[pp.ID]time.Time // peer → time added to current batch
|
||||
inBatch map[pp.ID]time.Time // peer → time added to current batch
|
||||
alreadyTried map[pp.ID]struct{} // peers proposed to that didn't migrate
|
||||
mu sync.Mutex
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -64,9 +65,20 @@ type IndexerService struct {
|
||||
pendingSearchesMu sync.Mutex
|
||||
// behavior tracks per-node compliance (heartbeat rate, publish/get volume,
|
||||
// identity consistency, signature failures).
|
||||
behavior *NodeBehaviorTracker
|
||||
behavior *NodeBehaviorTracker
|
||||
// connGuard limits new-connection bursts to protect public indexers.
|
||||
connGuard *ConnectionRateGuard
|
||||
// deletedDIDs tracks recently tombstoned DIDs to prevent AfterHeartbeat
|
||||
// from republishing records that were explicitly deleted by the peer.
|
||||
// Entries are cleared automatically after tombstoneTTL.
|
||||
deletedDIDs map[string]time.Time
|
||||
deletedDIDsMu sync.RWMutex
|
||||
// SWIM incarnation: incremented when a connecting node signals suspicion via
|
||||
// SuspectedIncarnation. The new value is broadcast back so nodes can clear
|
||||
// their suspect state (refutation mechanism).
|
||||
incarnation atomic.Uint64
|
||||
// eventQueue holds SWIM membership events to be piggybacked on responses
|
||||
// (infection-style dissemination toward connected nodes).
|
||||
eventQueue *common.MembershipEventQueue
|
||||
}
|
||||
|
||||
// NewIndexerService creates an IndexerService.
|
||||
@@ -81,7 +93,8 @@ func NewIndexerService(h host.Host, ps *pubsub.PubSub, maxNode int) *IndexerServ
|
||||
referencedNodes: map[pp.ID]PeerRecord{},
|
||||
pendingSearches: map[string]chan []common.SearchHit{},
|
||||
behavior: newNodeBehaviorTracker(),
|
||||
connGuard: newConnectionRateGuard(),
|
||||
deletedDIDs: make(map[string]time.Time),
|
||||
eventQueue: &common.MembershipEventQueue{},
|
||||
}
|
||||
if ps == nil {
|
||||
ps, err = pubsub.NewGossipSub(context.Background(), ix.Host)
|
||||
@@ -96,6 +109,21 @@ func NewIndexerService(h host.Host, ps *pubsub.PubSub, maxNode int) *IndexerServ
|
||||
common.ConnectToIndexers(h, conf.GetConfig().MinIndexer, conf.GetConfig().MaxIndexer*2)
|
||||
logger.Info().Msg("subscribe to decentralized search flow as strict indexer...")
|
||||
go ix.SubscribeToSearch(ix.PS, nil)
|
||||
ix.AllowInbound = func(remotePeer pp.ID, isNew bool) error {
|
||||
/*if ix.behavior.IsBanned(remotePeer) {
|
||||
return errors.New("peer is banned")
|
||||
}*/
|
||||
if isNew {
|
||||
// DB blacklist check: blocks reconnection after EvictPeer + blacklist.
|
||||
/*if !ix.isPeerKnown(remotePeer) {
|
||||
return errors.New("peer is blacklisted or unknown")
|
||||
}*/
|
||||
if !ix.ConnGuard.Allow() {
|
||||
return errors.New("connection rate limit exceeded, retry later")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
ix.LongLivedStreamRecordedService.AfterDelete = func(pid pp.ID, name, did string) {
|
||||
@@ -106,16 +134,7 @@ func NewIndexerService(h host.Host, ps *pubsub.PubSub, maxNode int) *IndexerServ
|
||||
|
||||
// AllowInbound: fired once per stream open, before any heartbeat is decoded.
|
||||
// 1. Reject peers that are currently banned (behavioral strikes).
|
||||
// 2. For genuinely new connections, apply the burst guard.
|
||||
ix.AllowInbound = func(remotePeer pp.ID, isNew bool) error {
|
||||
if ix.behavior.IsBanned(remotePeer) {
|
||||
return errors.New("peer is banned")
|
||||
}
|
||||
if isNew && !ix.connGuard.Allow() {
|
||||
return errors.New("connection rate limit exceeded, retry later")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// 2. For genuinely new connections, check the DB blacklist and apply the burst guard.
|
||||
|
||||
// ValidateHeartbeat: fired on every heartbeat tick for an established stream.
|
||||
// Checks heartbeat cadence — rejects if the node is sending too fast.
|
||||
@@ -162,7 +181,11 @@ func NewIndexerService(h host.Host, ps *pubsub.PubSub, maxNode int) *IndexerServ
|
||||
|
||||
// Build and send a HeartbeatResponse after each received node heartbeat.
|
||||
// Raw metrics only — no pre-cooked score. Node computes the score itself.
|
||||
ix.BuildHeartbeatResponse = func(remotePeer pp.ID, need int, challenges []string, challengeDID string, referent bool, rawRecord json.RawMessage) *common.HeartbeatResponse {
|
||||
ix.BuildHeartbeatResponse = func(remotePeer pp.ID, hb *common.Heartbeat) *common.HeartbeatResponse {
|
||||
logger := oclib.GetLogger()
|
||||
need, challenges, challengeDID, referent, rawRecord :=
|
||||
hb.Need, hb.Challenges, hb.ChallengeDID, hb.Referent, hb.Record
|
||||
|
||||
ix.StreamMU.RLock()
|
||||
peerCount := len(ix.StreamRecords[common.ProtocolHeartbeat])
|
||||
// Collect lastSeen per active peer for challenge responses.
|
||||
@@ -197,6 +220,31 @@ func NewIndexerService(h host.Host, ps *pubsub.PubSub, maxNode int) *IndexerServ
|
||||
// Update referent designation: node marks its best-scored indexer with Referent=true.
|
||||
ix.updateReferent(remotePeer, remotePeerRecord, referent)
|
||||
|
||||
// SWIM refutation: if the node signals our current incarnation as suspected,
|
||||
// increment it and broadcast an alive event so other nodes can clear suspicion.
|
||||
inc := ix.incarnation.Load()
|
||||
if hb.SuspectedIncarnation != nil && *hb.SuspectedIncarnation == inc {
|
||||
inc = ix.incarnation.Add(1)
|
||||
logger.Info().
|
||||
Str("suspected_by", remotePeer.String()).
|
||||
Uint64("new_incarnation", inc).
|
||||
Msg("[swim] refuting suspicion — incarnation incremented")
|
||||
ix.eventQueue.Add(common.MemberEvent{
|
||||
Type: common.MemberAlive,
|
||||
PeerID: ix.Host.ID().String(),
|
||||
Incarnation: inc,
|
||||
HopsLeft: common.InitialEventHops,
|
||||
})
|
||||
}
|
||||
|
||||
// Relay incoming SWIM events from the node into our event queue so they
|
||||
// propagate to other connected nodes (infection-style forwarding).
|
||||
for _, ev := range hb.MembershipEvents {
|
||||
if ev.HopsLeft > 0 {
|
||||
ix.eventQueue.Add(ev)
|
||||
}
|
||||
}
|
||||
|
||||
maxN := ix.MaxNodesConn()
|
||||
fillRate := 0.0
|
||||
if maxN > 0 {
|
||||
@@ -356,6 +404,10 @@ func NewIndexerService(h host.Host, ps *pubsub.PubSub, maxNode int) *IndexerServ
|
||||
}()
|
||||
}
|
||||
|
||||
// Attach SWIM incarnation and piggybacked membership events.
|
||||
resp.Incarnation = ix.incarnation.Load()
|
||||
resp.MembershipEvents = ix.eventQueue.Drain(5)
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
@@ -489,6 +541,23 @@ func (ix *IndexerService) startDHTProvide(fillRateFn func() float64) {
|
||||
}()
|
||||
}
|
||||
|
||||
// EvictPeer immediately closes the heartbeat stream of a peer and removes it
|
||||
// from the active stream records. Used when a peer is auto-blacklisted.
|
||||
func (ix *IndexerService) EvictPeer(peerID string) {
|
||||
pid, err := pp.Decode(peerID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ix.StreamMU.Lock()
|
||||
defer ix.StreamMU.Unlock()
|
||||
if rec, ok := ix.StreamRecords[common.ProtocolHeartbeat][pid]; ok {
|
||||
if rec.HeartbeatStream != nil && rec.HeartbeatStream.Stream != nil {
|
||||
rec.HeartbeatStream.Stream.Reset()
|
||||
}
|
||||
delete(ix.StreamRecords[common.ProtocolHeartbeat], pid)
|
||||
}
|
||||
}
|
||||
|
||||
func (ix *IndexerService) Close() {
|
||||
if ix.dhtProvideCancel != nil {
|
||||
ix.dhtProvideCancel()
|
||||
|
||||
Reference in New Issue
Block a user