Discovery Neo Oclib

This commit is contained in:
mr
2026-05-27 16:17:00 +02:00
parent 7f951afd41
commit 6ce6e6fe7d
20 changed files with 1436 additions and 1133 deletions
+40 -2
View File
@@ -39,6 +39,8 @@ type PeerRecordPayload struct {
PubKey []byte `json:"public_key"`
ExpiryDate time.Time `json:"expiry_date"`
IsNano bool `json:"is_nano"`
// MasterID is the libp2p PeerID of this peer's MASTER, self-attested and signed.
MasterID string `json:"master_id,omitempty"`
// TTLSeconds is the publisher's declared lifetime for this record in seconds.
// 0 means "use the default (120 s)". Included in the signed payload so it
// cannot be altered by an intermediary.
@@ -105,6 +107,7 @@ func (pr *PeerRecord) ExtractPeer(ourkey string, key string, pubKey crypto.PubKe
NATSAddress: pr.NATSAddress,
WalletAddress: pr.WalletAddress,
Location: pr.Location,
MasterID: pr.MasterID,
}
if time.Now().UTC().After(pr.ExpiryDate) {
return pp.SELF == p.Relation, nil, errors.New("peer " + key + " is offline")
@@ -285,6 +288,20 @@ func (ix *IndexerService) initNodeHandler() {
}
cancel2()
}
// PendingContact: update inverted index — for each target peer in the list,
// record that hb.PeerID wants to contact it. Entries expire after 3 heartbeat
// intervals so stale callers are cleaned up automatically if they stop advertising.
if len(hb.PendingContact) > 0 {
expiry := time.Now().Add(3 * 20 * time.Second)
ix.pendingContactIndexMu.Lock()
for _, targetID := range hb.PendingContact {
if ix.pendingContactIndex[targetID] == nil {
ix.pendingContactIndex[targetID] = map[string]time.Time{}
}
ix.pendingContactIndex[targetID][hb.PeerID] = expiry
}
ix.pendingContactIndexMu.Unlock()
}
}
ix.Host.SetStreamHandler(common.ProtocolHeartbeat, ix.HandleHeartbeat)
ix.Host.SetStreamHandler(common.ProtocolPublish, ix.handleNodePublish)
@@ -351,7 +368,8 @@ func (ix *IndexerService) handleNodePublish(s network.Stream) {
}
continue
}
if _, err := rec.Verify(); err != nil {
pubKey, err := rec.Verify()
if err != nil {
ix.behavior.RecordBadSignature(remotePeer)
logger.Warn().Err(err).Str("peer", remotePeer.String()).Msg("bad signature on publish")
return
@@ -369,6 +387,26 @@ func (ix *IndexerService) handleNodePublish(s network.Stream) {
if err != nil {
return
}
// Chain of trust: PubKey → PeerID (libp2p invariant), then transport identity.
// This prevents a peer from publishing a record on behalf of someone else.
if derivedID, err := lpp.IDFromPublicKey(pubKey); err != nil || derivedID != pid {
ix.behavior.RecordBadSignature(remotePeer)
logger.Warn().Str("peer", remotePeer.String()).Msg("PubKey/PeerID mismatch on publish")
s.Reset()
return
}
if remotePeer != pid {
ix.behavior.RecordBadSignature(remotePeer)
logger.Warn().Str("remote", remotePeer.String()).Str("claimed", pid.String()).Msg("transport identity mismatch on publish")
s.Reset()
return
}
if rec.StreamAddress != "" && !strings.HasSuffix(rec.StreamAddress, "/p2p/"+rec.PeerID) {
ix.behavior.RecordBadSignature(remotePeer)
logger.Warn().Str("peer", remotePeer.String()).Msg("StreamAddress/PeerID mismatch on publish")
s.Reset()
return
}
ix.StreamMU.Lock()
defer ix.StreamMU.Unlock()
@@ -566,7 +604,7 @@ func (ix *IndexerService) handleIndirectProbe(s network.Stream) {
// Connect to target if not already connected.
ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second)
defer cancel()
if ix.Host.Network().Connectedness(req.Target.ID) != network.Connected {
if len(ix.Host.Network().ConnsToPeer(req.Target.ID)) == 0 {
if err := ix.Host.Connect(ctx, req.Target); err != nil {
respond(false, 0)
return
+24
View File
@@ -79,6 +79,11 @@ type IndexerService struct {
// eventQueue holds SWIM membership events to be piggybacked on responses
// (infection-style dissemination toward connected nodes).
eventQueue *common.MembershipEventQueue
// pendingContactIndex is an inverted index built from Heartbeat.PendingContact.
// Maps target peer ID → { caller peer ID → expiry time }.
// Returned in HeartbeatResponse.PendingCallers when the target reconnects.
pendingContactIndex map[string]map[string]time.Time
pendingContactIndexMu sync.Mutex
}
// NewIndexerService creates an IndexerService.
@@ -95,6 +100,7 @@ func NewIndexerService(h host.Host, ps *pubsub.PubSub, maxNode int) *IndexerServ
behavior: newNodeBehaviorTracker(),
deletedDIDs: make(map[string]time.Time),
eventQueue: &common.MembershipEventQueue{},
pendingContactIndex: map[string]map[string]time.Time{},
}
if ps == nil {
ps, err = pubsub.NewGossipSub(context.Background(), ix.Host)
@@ -408,6 +414,24 @@ func NewIndexerService(h host.Host, ps *pubsub.PubSub, maxNode int) *IndexerServ
resp.Incarnation = ix.incarnation.Load()
resp.MembershipEvents = ix.eventQueue.Drain(5)
// PendingCallers: look up who has undelivered messages for this node.
// Clean up expired entries at the same time.
ix.pendingContactIndexMu.Lock()
if callers, ok := ix.pendingContactIndex[remotePeer.String()]; ok {
now := time.Now()
for callerID, exp := range callers {
if now.Before(exp) {
resp.PendingCallers = append(resp.PendingCallers, callerID)
} else {
delete(callers, callerID)
}
}
if len(callers) == 0 {
delete(ix.pendingContactIndex, remotePeer.String())
}
}
ix.pendingContactIndexMu.Unlock()
return resp
}