Discovery Nano the light version.

This commit is contained in:
mr
2026-04-29 07:41:00 +02:00
parent fa341494d9
commit 7f951afd41
34 changed files with 2961 additions and 1501 deletions

View File

@@ -10,7 +10,7 @@ clean:
rm -rf oc-discovery rm -rf oc-discovery
docker: docker:
DOCKER_BUILDKIT=1 docker build --build-arg CONF_NUM=1 -t oc-discovery_1 -f Dockerfile . DOCKER_BUILDKIT=1 docker build --build-arg CONF_NUM=5 -t oc-discovery_1 -f Dockerfile .
docker tag oc-discovery_1 opencloudregistry/oc-discovery_1:latest docker tag oc-discovery_1 opencloudregistry/oc-discovery_1:latest
DOCKER_BUILDKIT=1 docker build --build-arg CONF_NUM=2 -t oc-discovery_2 -f Dockerfile . DOCKER_BUILDKIT=1 docker build --build-arg CONF_NUM=2 -t oc-discovery_2 -f Dockerfile .
docker tag oc-discovery_2 opencloudregistry/oc-discovery_2:latest docker tag oc-discovery_2 opencloudregistry/oc-discovery_2:latest
@@ -18,6 +18,8 @@ docker:
docker tag oc-discovery_3 opencloudregistry/oc-discovery_3:latest docker tag oc-discovery_3 opencloudregistry/oc-discovery_3:latest
DOCKER_BUILDKIT=1 docker build --build-arg CONF_NUM=4 -t oc-discovery_4 -f Dockerfile . DOCKER_BUILDKIT=1 docker build --build-arg CONF_NUM=4 -t oc-discovery_4 -f Dockerfile .
docker tag oc-discovery_4 opencloudregistry/oc-discovery_4:latest docker tag oc-discovery_4 opencloudregistry/oc-discovery_4:latest
DOCKER_BUILDKIT=1 docker build --build-arg CONF_NUM=6 -t oc-discovery_6 -f Dockerfile .
docker tag oc-discovery_6 opencloudregistry/oc-discovery_6:latest
publish-kind: publish-kind:
kind load docker-image opencloudregistry/oc-discovery:latest --name opencloud kind load docker-image opencloudregistry/oc-discovery:latest --name opencloud
@@ -27,6 +29,7 @@ publish-registry:
docker push opencloudregistry/oc-discovery_2:latest docker push opencloudregistry/oc-discovery_2:latest
docker push opencloudregistry/oc-discovery_3:latest docker push opencloudregistry/oc-discovery_3:latest
docker push opencloudregistry/oc-discovery_4:latest docker push opencloudregistry/oc-discovery_4:latest
docker push opencloudregistry/oc-discovery_6:latest
all: docker publish-kind all: docker publish-kind

View File

@@ -3,14 +3,15 @@ package conf
import "sync" import "sync"
type Config struct { type Config struct {
Name string Name string
Hostname string Hostname string
PSKPath string PSKPath string
PublicKeyPath string PublicKeyPath string
PrivateKeyPath string PrivateKeyPath string
NodeEndpointPort int64 NodeEndpointPort int64
IndexerAddresses string IndexerAddresses string
NanoIDS string
PeerIDS string // TO REMOVE PeerIDS string // TO REMOVE
NodeMode string NodeMode string

View File

@@ -92,10 +92,6 @@ type SearchQuery struct {
// SearchPeerResult is sent by a responding indexer to the emitting indexer // SearchPeerResult is sent by a responding indexer to the emitting indexer
// via ProtocolSearchPeerResponse, and forwarded by the emitting indexer to // via ProtocolSearchPeerResponse, and forwarded by the emitting indexer to
// the node on the open ProtocolSearchPeer stream. // the node on the open ProtocolSearchPeer stream.
type SearchPeerResult struct {
QueryID string `json:"query_id"`
Records []SearchHit `json:"records"`
}
// SearchHit is a single peer found during distributed search. // SearchHit is a single peer found during distributed search.
type SearchHit struct { type SearchHit struct {

View File

@@ -203,6 +203,9 @@ func waitResults[T interface{}](topic *pubsub.Topic, s *LongLivedPubSubService,
if errors.Is(err, context.DeadlineExceeded) { if errors.Is(err, context.DeadlineExceeded) {
// timeout hit, no message before deadline kill subsciption. // timeout hit, no message before deadline kill subsciption.
s.PubsubMu.Lock() s.PubsubMu.Lock()
if s.LongLivedPubSubs[proto] != nil {
s.LongLivedPubSubs[proto].Close()
}
delete(s.LongLivedPubSubs, proto) delete(s.LongLivedPubSubs, proto)
s.PubsubMu.Unlock() s.PubsubMu.Unlock()
return return
@@ -214,6 +217,5 @@ func waitResults[T interface{}](topic *pubsub.Topic, s *LongLivedPubSubService,
continue continue
} }
f(ctx, evt, fmt.Sprintf("%v", proto)) f(ctx, evt, fmt.Sprintf("%v", proto))
fmt.Println("DEADLOCK ?")
} }
} }

View File

@@ -101,6 +101,10 @@ func (ix *LongLivedStreamRecordedService[T]) gc() {
evicted = append(evicted, gcEntry{pid, name, did}) evicted = append(evicted, gcEntry{pid, name, did})
for _, sstreams := range ix.StreamRecords { for _, sstreams := range ix.StreamRecords {
if sstreams[pid] != nil { if sstreams[pid] != nil {
if sstreams[pid].HeartbeatStream != nil && sstreams[pid].HeartbeatStream.Stream != nil {
sstreams[pid].HeartbeatStream.Stream.Close()
}
delete(sstreams, pid) delete(sstreams, pid)
} }
} }

View File

@@ -184,12 +184,15 @@ func TempStream(h host.Host, ad pp.AddrInfo, proto protocol.ID, did string, stre
} }
ctxTTL, cancelTTL := context.WithTimeout(context.Background(), expiry) ctxTTL, cancelTTL := context.WithTimeout(context.Background(), expiry)
defer cancelTTL() defer cancelTTL()
if h.Network().Connectedness(ad.ID) != network.Connected { if h.Network().Connectedness(ad.ID) != network.Connected {
if err := h.Connect(ctxTTL, ad); err != nil { if err := h.Connect(ctxTTL, ad); err != nil {
fmt.Println("Connectedness", ad.ID, err)
return streams, err return streams, err
} }
} }
fmt.Println("PROTO", streams[proto])
if streams[proto] != nil && streams[proto][ad.ID] != nil { if streams[proto] != nil && streams[proto][ad.ID] != nil {
return streams, nil return streams, nil
} else if s, err := h.NewStream(ctxTTL, ad.ID, proto); err == nil { } else if s, err := h.NewStream(ctxTTL, ad.ID, proto); err == nil {
@@ -200,6 +203,9 @@ func TempStream(h host.Host, ad pp.AddrInfo, proto protocol.ID, did string, stre
mu.Unlock() mu.Unlock()
time.AfterFunc(expiry, func() { time.AfterFunc(expiry, func() {
mu.Lock() mu.Lock()
if streams[proto] != nil && streams[proto][ad.ID] != nil && streams[proto][ad.ID].Stream != nil {
streams[proto][ad.ID].Stream.Close()
}
delete(streams[proto], ad.ID) delete(streams[proto], ad.ID)
mu.Unlock() mu.Unlock()
}) })
@@ -212,6 +218,7 @@ func TempStream(h host.Host, ad pp.AddrInfo, proto protocol.ID, did string, stre
mu.Unlock() mu.Unlock()
return streams, nil return streams, nil
} else { } else {
fmt.Println("ERRER", err)
return streams, err return streams, err
} }
} }

View File

@@ -33,10 +33,12 @@ const maxTTLSeconds = 86400 // 24h
const tombstoneTTL = 10 * time.Minute const tombstoneTTL = 10 * time.Minute
type PeerRecordPayload struct { type PeerRecordPayload struct {
ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
DID string `json:"did"` DID string `json:"did"`
PubKey []byte `json:"pub_key"` PubKey []byte `json:"public_key"`
ExpiryDate time.Time `json:"expiry_date"` ExpiryDate time.Time `json:"expiry_date"`
IsNano bool `json:"is_nano"`
// TTLSeconds is the publisher's declared lifetime for this record in seconds. // 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 // 0 means "use the default (120 s)". Included in the signed payload so it
// cannot be altered by an intermediary. // cannot be altered by an intermediary.
@@ -45,6 +47,8 @@ type PeerRecordPayload struct {
type PeerRecord struct { type PeerRecord struct {
PeerRecordPayload PeerRecordPayload
CreationDate time.Time `json:"creation_date"`
UpdateDate time.Time `json:"update_date"`
PeerID string `json:"peer_id"` PeerID string `json:"peer_id"`
APIUrl string `json:"api_url"` APIUrl string `json:"api_url"`
StreamAddress string `json:"stream_address"` StreamAddress string `json:"stream_address"`
@@ -184,7 +188,7 @@ func (ix *IndexerService) isPeerKnown(pid lpp.ID) bool {
And: map[string][]dbs.Filter{ And: map[string][]dbs.Filter{
"peer_id": {{Operator: dbs.EQUAL.String(), Value: pid.String()}}, "peer_id": {{Operator: dbs.EQUAL.String(), Value: pid.String()}},
}, },
}, pid.String(), false) }, pid.String(), false, 0, 1)
for _, item := range results.Data { for _, item := range results.Data {
p, ok := item.(*pp.Peer) p, ok := item.(*pp.Peer)
if !ok || p.PeerID != pid.String() { if !ok || p.PeerID != pid.String() {

View File

@@ -3,6 +3,7 @@ package indexer
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"strings" "strings"
"time" "time"
@@ -10,8 +11,8 @@ import (
"oc-discovery/daemons/node/common" "oc-discovery/daemons/node/common"
oclib "cloud.o-forge.io/core/oc-lib" oclib "cloud.o-forge.io/core/oc-lib"
pp "github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/network"
pp "github.com/libp2p/go-libp2p/core/peer"
) )
const TopicSearchPeer = "oc-search-peer" const TopicSearchPeer = "oc-search-peer"
@@ -46,31 +47,34 @@ func (ix *IndexerService) updateReferent(pid pp.ID, rec PeerRecord, referent boo
// searchReferenced looks up nodes in referencedNodes matching the query. // searchReferenced looks up nodes in referencedNodes matching the query.
// Matches on peerID (exact), DID (exact), or name (case-insensitive contains). // Matches on peerID (exact), DID (exact), or name (case-insensitive contains).
func (ix *IndexerService) searchReferenced(peerID, did, name string) []common.SearchHit { func (ix *IndexerService) searchReferenced(peerID, did, name string) []PeerRecord {
ix.referencedNodesMu.RLock() ix.referencedNodesMu.RLock()
defer ix.referencedNodesMu.RUnlock() defer ix.referencedNodesMu.RUnlock()
nameLow := strings.ToLower(name) nameLow := strings.ToLower(name)
var hits []common.SearchHit var hits []PeerRecord
for pid, rec := range ix.referencedNodes { for pid, rec := range ix.referencedNodes {
pidStr := pid.String() pidStr := pid.String()
matchPeerID := peerID != "" && pidStr == peerID matchPeerID := peerID != "" && pidStr == peerID
matchDID := did != "" && rec.DID == did matchDID := did != "" && rec.DID == did
matchName := name != "" && strings.Contains(strings.ToLower(rec.Name), nameLow) matchName := name != "" && strings.Contains(strings.ToLower(rec.Name), nameLow)
if matchPeerID || matchDID || matchName { if matchPeerID || matchDID || matchName {
hits = append(hits, common.SearchHit{ rec.ID = rec.DID
PeerID: pidStr, hits = append(hits, rec)
DID: rec.DID,
Name: rec.Name,
})
} }
} }
return hits return hits
} }
type SearchPeerResult struct {
QueryID string `json:"query_id"`
Records []PeerRecord `json:"records"`
}
// handleSearchPeer is the ProtocolSearchPeer handler. // handleSearchPeer is the ProtocolSearchPeer handler.
// The node opens this stream, sends a SearchPeerRequest, and reads results // The node opens this stream, sends a SearchPeerRequest, and reads results
// as they stream in. The stream stays open until timeout or node closes it. // as they stream in. The stream stays open until timeout or node closes it.
func (ix *IndexerService) handleSearchPeer(s network.Stream) { func (ix *IndexerService) handleSearchPeer(s network.Stream) {
fmt.Println("handleSearchPeer")
logger := oclib.GetLogger() logger := oclib.GetLogger()
defer s.Reset() defer s.Reset()
@@ -78,7 +82,7 @@ func (ix *IndexerService) handleSearchPeer(s network.Stream) {
logger.Warn().Str("peer", s.Conn().RemotePeer().String()).Msg("[search] unknown peer, rejecting stream") logger.Warn().Str("peer", s.Conn().RemotePeer().String()).Msg("[search] unknown peer, rejecting stream")
return return
} }
fmt.Println("SearchPeerRequest")
var req common.SearchPeerRequest var req common.SearchPeerRequest
if err := json.NewDecoder(s).Decode(&req); err != nil || req.QueryID == "" { if err := json.NewDecoder(s).Decode(&req); err != nil || req.QueryID == "" {
return return
@@ -94,7 +98,7 @@ func (ix *IndexerService) handleSearchPeer(s network.Stream) {
}() }()
defer streamCancel() defer streamCancel()
resultCh := make(chan []common.SearchHit, 16) resultCh := make(chan []PeerRecord, 16)
ix.pendingSearchesMu.Lock() ix.pendingSearchesMu.Lock()
ix.pendingSearches[req.QueryID] = resultCh ix.pendingSearches[req.QueryID] = resultCh
ix.pendingSearchesMu.Unlock() ix.pendingSearchesMu.Unlock()
@@ -106,9 +110,10 @@ func (ix *IndexerService) handleSearchPeer(s network.Stream) {
// Check own referencedNodes immediately. // Check own referencedNodes immediately.
if hits := ix.searchReferenced(req.PeerID, req.DID, req.Name); len(hits) > 0 { if hits := ix.searchReferenced(req.PeerID, req.DID, req.Name); len(hits) > 0 {
fmt.Println("hits", hits)
resultCh <- hits resultCh <- hits
} }
fmt.Println("publishSearchQuery")
// Broadcast search on GossipSub so other indexers can respond. // Broadcast search on GossipSub so other indexers can respond.
ix.publishSearchQuery(req.QueryID, req.PeerID, req.DID, req.Name) ix.publishSearchQuery(req.QueryID, req.PeerID, req.DID, req.Name)
@@ -119,7 +124,8 @@ func (ix *IndexerService) handleSearchPeer(s network.Stream) {
for { for {
select { select {
case hits := <-resultCh: case hits := <-resultCh:
if err := enc.Encode(common.SearchPeerResult{QueryID: req.QueryID, Records: hits}); err != nil { fmt.Println("resultCh hits", hits)
if err := enc.Encode(SearchPeerResult{QueryID: req.QueryID, Records: hits}); err != nil {
logger.Debug().Err(err).Msg("[search] stream write failed") logger.Debug().Err(err).Msg("[search] stream write failed")
return return
} }
@@ -145,13 +151,15 @@ func (ix *IndexerService) handleSearchPeer(s network.Stream) {
// Another indexer opens this stream to deliver hits for a pending queryID. // Another indexer opens this stream to deliver hits for a pending queryID.
func (ix *IndexerService) handleSearchPeerResponse(s network.Stream) { func (ix *IndexerService) handleSearchPeerResponse(s network.Stream) {
defer s.Reset() defer s.Reset()
var result common.SearchPeerResult fmt.Println("RECEIVED SEARCH")
var result SearchPeerResult
if err := json.NewDecoder(s).Decode(&result); err != nil || result.QueryID == "" { if err := json.NewDecoder(s).Decode(&result); err != nil || result.QueryID == "" {
return return
} }
ix.pendingSearchesMu.Lock() ix.pendingSearchesMu.Lock()
ch := ix.pendingSearches[result.QueryID] ch := ix.pendingSearches[result.QueryID]
ix.pendingSearchesMu.Unlock() ix.pendingSearchesMu.Unlock()
fmt.Println("RECEIVED", result.QueryID, ix.pendingSearches[result.QueryID])
if ch != nil { if ch != nil {
select { select {
case ch <- result.Records: case ch <- result.Records:
@@ -213,21 +221,28 @@ func (ix *IndexerService) onSearchQuery(q common.SearchQuery) {
if q.EmitterID == ix.Host.ID().String() { if q.EmitterID == ix.Host.ID().String() {
return return
} }
fmt.Println("ON SEARCH QUERY")
hits := ix.searchReferenced(q.PeerID, q.DID, q.Name) hits := ix.searchReferenced(q.PeerID, q.DID, q.Name)
fmt.Println("ON SEARCH QUERY HITS", hits)
if len(hits) == 0 { if len(hits) == 0 {
return return
} }
emitterID, err := pp.Decode(q.EmitterID) emitterID, err := pp.Decode(q.EmitterID)
if err != nil { if err != nil {
fmt.Println("ON SEARCH QUERY err DECODE", err)
return return
} }
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
s, err := ix.Host.NewStream(ctx, emitterID, common.ProtocolSearchPeerResponse) s, err := ix.Host.NewStream(ctx, emitterID, common.ProtocolSearchPeerResponse)
if err != nil { if err != nil {
fmt.Println("ON SEARCH QUERY err NewStream", emitterID, err)
return return
} }
defer s.Reset() fmt.Println("ON ", emitterID)
defer s.Close()
s.SetDeadline(time.Now().Add(5 * time.Second)) s.SetDeadline(time.Now().Add(5 * time.Second))
json.NewEncoder(s).Encode(common.SearchPeerResult{QueryID: q.QueryID, Records: hits}) err = json.NewEncoder(s).Encode(SearchPeerResult{QueryID: q.QueryID, Records: hits})
fmt.Println("SEARCH ERR", err)
s.CloseWrite()
} }

View File

@@ -61,7 +61,7 @@ type IndexerService struct {
referencedNodes map[pp.ID]PeerRecord referencedNodes map[pp.ID]PeerRecord
referencedNodesMu sync.RWMutex referencedNodesMu sync.RWMutex
// pendingSearches maps queryID → result channel for in-flight searches. // pendingSearches maps queryID → result channel for in-flight searches.
pendingSearches map[string]chan []common.SearchHit pendingSearches map[string]chan []PeerRecord
pendingSearchesMu sync.Mutex pendingSearchesMu sync.Mutex
// behavior tracks per-node compliance (heartbeat rate, publish/get volume, // behavior tracks per-node compliance (heartbeat rate, publish/get volume,
// identity consistency, signature failures). // identity consistency, signature failures).
@@ -91,7 +91,7 @@ func NewIndexerService(h host.Host, ps *pubsub.PubSub, maxNode int) *IndexerServ
LongLivedStreamRecordedService: common.NewStreamRecordedService[PeerRecord](h, maxNode), LongLivedStreamRecordedService: common.NewStreamRecordedService[PeerRecord](h, maxNode),
isStrictIndexer: ps == nil, isStrictIndexer: ps == nil,
referencedNodes: map[pp.ID]PeerRecord{}, referencedNodes: map[pp.ID]PeerRecord{},
pendingSearches: map[string]chan []common.SearchHit{}, pendingSearches: map[string]chan []PeerRecord{},
behavior: newNodeBehaviorTracker(), behavior: newNodeBehaviorTracker(),
deletedDIDs: make(map[string]time.Time), deletedDIDs: make(map[string]time.Time),
eventQueue: &common.MembershipEventQueue{}, eventQueue: &common.MembershipEventQueue{},

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"oc-discovery/daemons/node/common" "oc-discovery/daemons/node/indexer"
"oc-discovery/daemons/node/stream" "oc-discovery/daemons/node/stream"
"slices" "slices"
@@ -29,6 +29,11 @@ func ListenNATS(n *Node) {
tools.PEER_BEHAVIOR_EVENT: func(resp tools.NATSResponse) { //nolint:typecheck tools.PEER_BEHAVIOR_EVENT: func(resp tools.NATSResponse) { //nolint:typecheck
handlePeerBehaviorEvent(n, resp) handlePeerBehaviorEvent(n, resp)
}, },
// PEER_OBSERVE_EVENT is sent by oc-peer to start or stop observations
// for a list of peer IDs, or to trigger a close-all.
tools.PEER_OBSERVE_EVENT: func(resp tools.NATSResponse) {
n.StreamService.HandleObserveNATSCommand(resp)
},
tools.PROPALGATION_EVENT: func(resp tools.NATSResponse) { tools.PROPALGATION_EVENT: func(resp tools.NATSResponse) {
if resp.FromApp == config.GetAppName() { if resp.FromApp == config.GetAppName() {
return return
@@ -134,6 +139,21 @@ func ListenNATS(n *Node) {
} }
n.StreamService.Mu.Unlock() n.StreamService.Mu.Unlock()
} }
case tools.PB_OBSERVE:
print("PROPALGATE OBSERVE")
handleObserveEvent(n, propalgation)
case tools.PB_OBSERVE_CLOSE:
print("PROPALGATE CLOSE")
handleObserveCloseEvent(n, propalgation)
case tools.PB_PROPAGATE:
// Another oc-discovery forwarded a heartbeat batch.
// Re-emit on PEER_OBSERVE_RESPONSE_EVENT so the local oc-peer sees it.
tools.NewNATSCaller().SetNATSPub(tools.PEER_OBSERVE_RESPONSE_EVENT, tools.NATSResponse{
FromApp: resp.FromApp,
Datatype: tools.PEER,
Method: int(tools.PEER_OBSERVE_RESPONSE_EVENT),
Payload: propalgation.Payload,
})
case tools.PB_CLOSE_SEARCH: case tools.PB_CLOSE_SEARCH:
if propalgation.DataType == int(tools.PEER) { if propalgation.DataType == int(tools.PEER) {
n.peerSearches.Cancel(resp.User) n.peerSearches.Cancel(resp.User)
@@ -141,16 +161,18 @@ func ListenNATS(n *Node) {
n.StreamService.ResourceSearches.Cancel(resp.User) n.StreamService.ResourceSearches.Cancel(resp.User)
} }
case tools.PB_SEARCH: case tools.PB_SEARCH:
fmt.Println("PROPALGATE PEER")
if propalgation.DataType == int(tools.PEER) { if propalgation.DataType == int(tools.PEER) {
m := map[string]interface{}{} m := map[string]interface{}{}
if err := json.Unmarshal(propalgation.Payload, &m); err == nil { if err := json.Unmarshal(propalgation.Payload, &m); err == nil {
needle := fmt.Sprintf("%v", m["search"]) needle := fmt.Sprintf("%v", m["search"])
userKey := resp.User userKey := resp.User
go n.SearchPeerRecord(userKey, needle, func(hit common.SearchHit) { go n.SearchPeerRecord(userKey, needle, func(hit indexer.PeerRecord) {
if b, err := json.Marshal(hit); err == nil { if b, err := json.Marshal(hit); err == nil {
tools.NewNATSCaller().SetNATSPub(tools.SEARCH_EVENT, tools.NATSResponse{ tools.NewNATSCaller().SetNATSPub(tools.SEARCH_EVENT, tools.NATSResponse{
FromApp: "oc-discovery", FromApp: "oc-discovery",
Datatype: tools.DataType(tools.PEER), Datatype: tools.DataType(tools.PEER),
User: userKey,
Method: int(tools.SEARCH_EVENT), Method: int(tools.SEARCH_EVENT),
Payload: b, Payload: b,
}) })
@@ -240,3 +262,37 @@ func handlePeerBehaviorEvent(n *Node, resp tools.NATSResponse) {
}) })
} }
} }
// handleObserveEvent processes a PB_OBSERVE PropalgationMessage from another
// oc-discovery node, starting observation for the listed peers.
func handleObserveEvent(n *Node, p tools.PropalgationMessage) {
var cmd stream.ObserveCommand
if err := json.Unmarshal(p.Payload, &cmd); err != nil {
fmt.Println("handleObserveEvent: unmarshal error:", err)
return
}
for _, sp := range cmd.Peers {
if err := n.StreamService.OpenObserveStream(sp); err != nil {
fmt.Println("handleObserveEvent: OpenObserveStream failed for", sp.PeerID, ":", err)
}
}
}
// handleObserveCloseEvent processes a PB_OBSERVE_CLOSE PropalgationMessage from
// another oc-discovery node, stopping observation for the listed peer IDs.
func handleObserveCloseEvent(n *Node, p tools.PropalgationMessage) {
var cmd stream.ObserveCommand
if err := json.Unmarshal(p.Payload, &cmd); err != nil {
fmt.Println("handleObserveCloseEvent: unmarshal error:", err)
return
}
if cmd.CloseAll {
n.StreamService.CloseAllObserves()
return
}
for _, peerID := range cmd.PeerIDs {
if err := n.StreamService.CloseObserveStream(peerID); err != nil {
fmt.Println("handleObserveCloseEvent: CloseObserveStream failed for", peerID, ":", err)
}
}
}

View File

@@ -113,6 +113,7 @@ func InitNode(isNode bool, isIndexer bool) (*Node, error) {
if ttl <= 0 { if ttl <= 0 {
ttl = indexer.DefaultTTLSeconds * time.Second ttl = indexer.DefaultTTLSeconds * time.Second
} }
fresh.UpdateDate = time.Now().UTC()
fresh.PeerRecordPayload.ExpiryDate = time.Now().UTC().Add(ttl) fresh.PeerRecordPayload.ExpiryDate = time.Now().UTC().Add(ttl)
payload, _ := json.Marshal(fresh.PeerRecordPayload) payload, _ := json.Marshal(fresh.PeerRecordPayload)
fresh.Signature, err = priv.Sign(payload) fresh.Signature, err = priv.Sign(payload)
@@ -141,7 +142,7 @@ func InitNode(isNode bool, isIndexer bool) (*Node, error) {
And: map[string][]dbs.Filter{ And: map[string][]dbs.Filter{
"peer_id": {{Operator: dbs.EQUAL.String(), Value: pid.String()}}, "peer_id": {{Operator: dbs.EQUAL.String(), Value: pid.String()}},
}, },
}, pid.String(), false) }, pid.String(), false, 0, 1)
for _, item := range results.Data { for _, item := range results.Data {
p, ok := item.(*peer.Peer) p, ok := item.(*peer.Peer)
if !ok || p.PeerID != pid.String() { if !ok || p.PeerID != pid.String() {
@@ -228,7 +229,7 @@ func (d *Node) isPeerKnown(pid pp.ID) bool {
And: map[string][]dbs.Filter{ And: map[string][]dbs.Filter{
"peer_id": {{Operator: dbs.EQUAL.String(), Value: pid.String()}}, "peer_id": {{Operator: dbs.EQUAL.String(), Value: pid.String()}},
}, },
}, pid.String(), false) }, pid.String(), false, 0, 1)
for _, item := range results.Data { for _, item := range results.Data {
p, ok := item.(*peer.Peer) p, ok := item.(*peer.Peer)
if !ok || p.PeerID != pid.String() { if !ok || p.PeerID != pid.String() {
@@ -267,15 +268,8 @@ func (d *Node) publishPeerRecord(
if ttl <= 0 { if ttl <= 0 {
ttl = indexer.DefaultTTLSeconds * time.Second ttl = indexer.DefaultTTLSeconds * time.Second
} }
base := indexer.PeerRecordPayload{ rec.ExpiryDate = time.Now().UTC().Add(ttl)
Name: rec.Name, payload, _ := json.Marshal(rec.PeerRecordPayload)
DID: rec.DID,
PubKey: rec.PubKey,
TTLSeconds: rec.TTLSeconds,
ExpiryDate: time.Now().UTC().Add(ttl),
}
payload, _ := json.Marshal(base)
rec.PeerRecordPayload = base
rec.Signature, err = priv.Sign(payload) rec.Signature, err = priv.Sign(payload)
if err := json.NewEncoder(stream.Stream).Encode(&rec); err != nil { // then publish on stream if err := json.NewEncoder(stream.Stream).Encode(&rec); err != nil { // then publish on stream
return err return err
@@ -288,7 +282,7 @@ func (d *Node) publishPeerRecord(
// A new call for the same userKey cancels any previous search. // A new call for the same userKey cancels any previous search.
// Results are pushed to onResult as they arrive; the function returns when // Results are pushed to onResult as they arrive; the function returns when
// the stream closes (idle timeout, explicit cancel, or indexer unreachable). // the stream closes (idle timeout, explicit cancel, or indexer unreachable).
func (d *Node) SearchPeerRecord(userKey, needle string, onResult func(common.SearchHit)) { func (d *Node) SearchPeerRecord(userKey, needle string, onResult func(indexer.PeerRecord)) {
logger := oclib.GetLogger() logger := oclib.GetLogger()
idleTimeout := common.SearchIdleTimeout() idleTimeout := common.SearchIdleTimeout()
@@ -306,7 +300,7 @@ func (d *Node) SearchPeerRecord(userKey, needle string, onResult func(common.Sea
} else { } else {
req.Name = needle req.Name = needle
} }
fmt.Println("PROPALGATE PEER", needle, common.Indexers.GetAddrs())
for _, ad := range common.Indexers.GetAddrs() { for _, ad := range common.Indexers.GetAddrs() {
if ad.Info == nil { if ad.Info == nil {
continue continue
@@ -330,7 +324,7 @@ func (d *Node) SearchPeerRecord(userKey, needle string, onResult func(common.Sea
seen := map[string]struct{}{} seen := map[string]struct{}{}
dec := json.NewDecoder(s) dec := json.NewDecoder(s)
for { for {
var result common.SearchPeerResult var result indexer.SearchPeerResult
if err := dec.Decode(&result); err != nil { if err := dec.Decode(&result); err != nil {
break break
} }
@@ -416,7 +410,7 @@ func (d *Node) claimInfo(
And: map[string][]dbs.Filter{ // search by name if no filters are provided And: map[string][]dbs.Filter{ // search by name if no filters are provided
"peer_id": {{Operator: dbs.EQUAL.String(), Value: d.Host.ID().String()}}, "peer_id": {{Operator: dbs.EQUAL.String(), Value: d.Host.ID().String()}},
}, },
}, "", false) }, "", false, 0, 1)
if len(peers.Data) > 0 { if len(peers.Data) > 0 {
did = peers.Data[0].GetID() // if already existing set up did as made did = peers.Data[0].GetID() // if already existing set up did as made
} }
@@ -435,9 +429,11 @@ func (d *Node) claimInfo(
now := time.Now().UTC() now := time.Now().UTC()
pRec := indexer.PeerRecordPayload{ pRec := indexer.PeerRecordPayload{
Name: name, Name: name,
DID: did, // REAL PEER ID DID: did, // REAL PEER ID
PubKey: pubBytes, PubKey: pubBytes,
IsNano: oclib.GetConfig().IsNano,
TTLSeconds: indexer.DefaultTTLSeconds, TTLSeconds: indexer.DefaultTTLSeconds,
ExpiryDate: now.Add(indexer.DefaultTTLSeconds * time.Second), ExpiryDate: now.Add(indexer.DefaultTTLSeconds * time.Second),
} }
@@ -447,6 +443,8 @@ func (d *Node) claimInfo(
rec := &indexer.PeerRecord{ rec := &indexer.PeerRecord{
PeerRecordPayload: pRec, PeerRecordPayload: pRec,
} }
rec.CreationDate = time.Now().UTC()
rec.UpdateDate = time.Now().UTC()
rec.Signature, err = priv.Sign(payload) rec.Signature, err = priv.Sign(payload)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -27,8 +27,9 @@ func (ps *PubSubService) SearchPublishEvent(
return ps.StreamService.PublishesCommon(dt, user, groups, nil, b, stream.ProtocolSearchResource) //if partners focus only them*/ return ps.StreamService.PublishesCommon(dt, user, groups, nil, b, stream.ProtocolSearchResource) //if partners focus only them*/
case "partner": // define Search Strategy case "partner": // define Search Strategy
return ps.StreamService.PublishesCommon(dt, user, groups, &dbs.Filters{ // filter by like name, short_description, description, owner, url if no filters are provided return ps.StreamService.PublishesCommon(dt, user, groups, &dbs.Filters{ // filter by like name, short_description, description, owner, url if no filters are provided
And: map[string][]dbs.Filter{ Or: map[string][]dbs.Filter{
"relation": {{Operator: dbs.EQUAL.String(), Value: peer.PARTNER}}, "relation": {{Operator: dbs.EQUAL.String(), Value: peer.PARTNER}},
"is_nano": {{Operator: dbs.EQUAL.String(), Value: true}},
}, },
}, b, stream.ProtocolSearchResource) }, b, stream.ProtocolSearchResource)
case "all": // Gossip PubSub case "all": // Gossip PubSub

View File

@@ -0,0 +1,362 @@
package stream
// dnt_cache.go — Disconnection Network Tolerance cache for outbound stream requests.
//
// When a stream write fails because the remote peer is unreachable, the request
// is saved here and retried on the next tick. Two levels are defined:
//
// - dntCritical : retry indefinitely (create / update / delete resource).
// - dntModerate : up to dntMaxModerateRetries retries, then abandon.
//
// Pubsub messages and search streams are explicitly excluded.
// Streams initiated from the indexer side are never enqueued here.
//
// # Crash-resilient persistence
//
// Critical entries are written to an encrypted file (AES-256-GCM) so they
// survive a node crash/restart. The AES key is derived deterministically from
// the node's Ed25519 private key via HKDF-SHA256 — no extra secret to manage.
// Moderate entries are intentionally not persisted: their retry budget is small
// enough that re-loading them after a restart would be misleading.
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/json"
"io"
"os"
"path/filepath"
"sync"
"time"
oclib "cloud.o-forge.io/core/oc-lib"
"cloud.o-forge.io/core/oc-lib/tools"
"golang.org/x/crypto/hkdf"
"oc-discovery/conf"
pp "github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
)
type dntLevel int
const (
dntCritical dntLevel = iota // retry until the message is delivered
dntModerate // retry up to dntMaxModerateRetries times
)
const dntMaxModerateRetries = 3
const dntRetryInterval = 15 * time.Second
// dntProtocols maps each stream protocol to its DNT level.
// Protocols absent from this map receive no caching (e.g. ProtocolSearchResource).
var dntProtocols = map[protocol.ID]dntLevel{
// Critical — data mutations that must eventually be delivered.
ProtocolCreateResource: dntCritical,
ProtocolUpdateResource: dntCritical,
ProtocolDeleteResource: dntCritical,
// Moderate — confirmations / config / planner: 3 retries before abandon.
ProtocolVerifyResource: dntModerate,
ProtocolSendPlanner: dntModerate,
ProtocolConsidersResource: dntModerate,
ProtocolMinioConfigResource: dntModerate,
ProtocolAdmiraltyConfigResource: dntModerate,
}
// dntEntryJSON is the on-disk representation of a dntEntry.
// pp.AddrInfo and protocol.ID don't have built-in JSON tags so we flatten them.
type dntEntryJSON struct {
DID string `json:"did"`
Addr pp.AddrInfo `json:"addr"`
DT *tools.DataType `json:"dt,omitempty"`
User string `json:"user"`
Payload []byte `json:"payload"`
Proto protocol.ID `json:"proto"`
Retries int `json:"retries"`
AddedAt time.Time `json:"added_at"`
}
type dntEntry struct {
did string
addr pp.AddrInfo
dt *tools.DataType
user string
payload []byte
proto protocol.ID
retries int
addedAt time.Time
}
func (e *dntEntry) toJSON() dntEntryJSON {
return dntEntryJSON{
DID: e.did,
Addr: e.addr,
DT: e.dt,
User: e.user,
Payload: e.payload,
Proto: e.proto,
Retries: e.retries,
AddedAt: e.addedAt,
}
}
func entryFromJSON(j dntEntryJSON) *dntEntry {
return &dntEntry{
did: j.DID,
addr: j.Addr,
dt: j.DT,
user: j.User,
payload: j.Payload,
proto: j.Proto,
retries: j.Retries,
addedAt: j.AddedAt,
}
}
type dntCache struct {
mu sync.Mutex
entries []*dntEntry
// aesKey is the derived AES-256 key used for on-disk encryption.
// Nil when key derivation failed: persistence is disabled but the in-memory
// cache continues to function normally.
aesKey []byte
}
// newDNTCache initialises the cache, derives the encryption key, and restores
// any critical entries that were persisted before the last crash.
func newDNTCache() *dntCache {
log := oclib.GetLogger()
c := &dntCache{}
key, err := deriveDNTKey()
if err != nil {
log.Warn().Err(err).Msg("[dnt] key derivation failed — persistence disabled")
} else {
c.aesKey = key
c.loadFromDisk()
}
return c
}
// enqueue adds an entry to the cache and persists critical entries to disk.
func (c *dntCache) enqueue(e *dntEntry) {
c.mu.Lock()
c.entries = append(c.entries, e)
c.mu.Unlock()
if dntProtocols[e.proto] == dntCritical {
go c.persistToDisk()
}
}
// drain atomically removes and returns all current entries.
func (c *dntCache) drain() []*dntEntry {
c.mu.Lock()
defer c.mu.Unlock()
out := c.entries
c.entries = nil
return out
}
// requeue puts entries back at the head of the list, preserving any new
// entries added while the retry loop was running.
func (c *dntCache) requeue(entries []*dntEntry) {
if len(entries) == 0 {
return
}
c.mu.Lock()
defer c.mu.Unlock()
c.entries = append(entries, c.entries...)
}
// ── Persistence ──────────────────────────────────────────────────────────────
// dntCachePath returns the path of the on-disk cache file, placed next to the
// node's private key so it lives on the same persistent volume.
func dntCachePath() string {
return filepath.Join(filepath.Dir(conf.GetConfig().PrivateKeyPath), "dnt_cache.bin")
}
// deriveDNTKey derives a 32-byte AES key from the node's Ed25519 private key
// using HKDF-SHA256. The derivation is deterministic: the same key is always
// produced from the same private key, so no symmetric secret needs storing.
func deriveDNTKey() ([]byte, error) {
priv, err := tools.LoadKeyFromFilePrivate()
if err != nil {
return nil, err
}
// Raw() on a libp2p Ed25519 private key returns the 64-byte representation
// (32-byte seed || 32-byte public key). We use the full 64 bytes as IKM.
raw, err := priv.Raw()
if err != nil {
return nil, err
}
reader := hkdf.New(sha256.New, raw, nil, []byte("oc-discovery/dnt-cache/v1"))
key := make([]byte, 32)
if _, err := io.ReadFull(reader, key); err != nil {
return nil, err
}
return key, nil
}
// persistToDisk encrypts all current critical entries and writes them to disk.
// Non-critical entries are deliberately excluded — they are not worth restoring
// after a restart given their limited retry budget.
func (c *dntCache) persistToDisk() {
if c.aesKey == nil {
return
}
log := oclib.GetLogger()
c.mu.Lock()
var toSave []dntEntryJSON
for _, e := range c.entries {
if dntProtocols[e.proto] == dntCritical {
toSave = append(toSave, e.toJSON())
}
}
c.mu.Unlock()
plaintext, err := json.Marshal(toSave)
if err != nil {
return
}
block, err := aes.NewCipher(c.aesKey)
if err != nil {
return
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return
}
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
path := dntCachePath()
tmp := path + ".tmp"
if err := os.WriteFile(tmp, ciphertext, 0600); err != nil {
log.Warn().Err(err).Msg("[dnt] failed to write cache file")
return
}
if err := os.Rename(tmp, path); err != nil {
log.Warn().Err(err).Msg("[dnt] failed to rename cache file")
_ = os.Remove(tmp)
}
}
// loadFromDisk decrypts the on-disk cache and re-enqueues only critical entries.
// Errors (missing file, decryption failure) are non-fatal: the cache simply
// starts empty, which is safe.
func (c *dntCache) loadFromDisk() {
if c.aesKey == nil {
return
}
log := oclib.GetLogger()
path := dntCachePath()
data, err := os.ReadFile(path)
if err != nil {
if !os.IsNotExist(err) {
log.Warn().Err(err).Msg("[dnt] failed to read cache file")
}
return
}
block, err := aes.NewCipher(c.aesKey)
if err != nil {
return
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return
}
if len(data) < gcm.NonceSize() {
log.Warn().Msg("[dnt] cache file too short, ignoring")
return
}
nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
log.Warn().Err(err).Msg("[dnt] cache file decryption failed (key mismatch?), ignoring")
return
}
var saved []dntEntryJSON
if err := json.Unmarshal(plaintext, &saved); err != nil {
log.Warn().Err(err).Msg("[dnt] cache file unmarshal failed, ignoring")
return
}
count := 0
for _, j := range saved {
// Only restore critical entries — moderate entries are intentionally
// not persisted, but this guard defends against format changes.
if dntProtocols[j.Proto] != dntCritical {
continue
}
c.entries = append(c.entries, entryFromJSON(j))
count++
}
if count > 0 {
log.Info().Int("count", count).Msg("[dnt] restored critical entries from disk")
}
}
// ── Retry loop ────────────────────────────────────────────────────────────────
// startDNTLoop runs the background retry goroutine. Call once after init.
func (s *StreamService) startDNTLoop() {
logger := oclib.GetLogger()
ticker := time.NewTicker(dntRetryInterval)
defer ticker.Stop()
for range ticker.C {
entries := s.dnt.drain()
if len(entries) == 0 {
continue
}
var keep []*dntEntry
for _, e := range entries {
_, err := s.write(e.did, &e.addr, e.dt, e.user, e.payload, e.proto)
if err == nil {
level := dntProtocols[e.proto]
if level == dntCritical {
logger.Info().
Str("proto", string(e.proto)).
Str("peer", e.did).
Msg("[dnt] critical message delivered after retry")
} else {
logger.Info().
Str("proto", string(e.proto)).
Str("peer", e.did).
Int("retries", e.retries).
Msg("[dnt] moderate message delivered after retry")
}
continue
}
level := dntProtocols[e.proto]
switch level {
case dntCritical:
keep = append(keep, e)
case dntModerate:
e.retries++
if e.retries < dntMaxModerateRetries {
keep = append(keep, e)
} else {
logger.Warn().
Str("proto", string(e.proto)).
Str("peer", e.did).
Int("retries", e.retries).
Msg("[dnt] moderate message abandoned after max retries")
}
}
}
s.dnt.requeue(keep)
// Persist after each tick so the on-disk file reflects the current
// state (entries delivered are removed, new ones from concurrent
// enqueues are included).
go s.dnt.persistToDisk()
}
}

View File

@@ -14,14 +14,23 @@ import (
"cloud.o-forge.io/core/oc-lib/models/peer" "cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/models/resources" "cloud.o-forge.io/core/oc-lib/models/resources"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
"github.com/libp2p/go-libp2p/core/network"
) )
type Verify struct { type Verify struct {
IsVerified bool `json:"is_verified"` IsVerified bool `json:"is_verified"`
} }
func (ps *StreamService) handleEvent(protocol string, evt *common.Event) error { func (ps *StreamService) handleEvent(protocol string, evt *common.Event, s network.Stream) error {
fmt.Println("handleEvent") fmt.Println("handleEvent", protocol)
// Heartbeat received on an outgoing ProtocolObserve stream.
if protocol == ProtocolObserve {
return ps.handleIncomingObserve(s)
}
if protocol == observeHBEventType {
return ps.handleObserveHeartbeat(evt)
}
ps.handleEventFromPartner(evt, protocol) ps.handleEventFromPartner(evt, protocol)
/*if protocol == ProtocolVerifyResource { /*if protocol == ProtocolVerifyResource {
if evt.DataType == -1 { if evt.DataType == -1 {
@@ -159,7 +168,7 @@ func (ps *StreamService) handleEventFromPartner(evt *common.Event, protocol stri
And: map[string][]dbs.Filter{ And: map[string][]dbs.Filter{
"peer_id": {{Operator: dbs.EQUAL.String(), Value: evt.From}}, "peer_id": {{Operator: dbs.EQUAL.String(), Value: evt.From}},
}, },
}, evt.From, false) }, evt.From, false, 0, 1)
if len(peers.Data) > 0 { if len(peers.Data) > 0 {
p := peers.Data[0].(*peer.Peer) p := peers.Data[0].(*peer.Peer)
ps.SendResponse(p, evt, fmt.Sprintf("%v", search)) ps.SendResponse(p, evt, fmt.Sprintf("%v", search))
@@ -212,7 +221,7 @@ func (abs *StreamService) SendResponse(p *peer.Peer, event *common.Event, search
} else { } else {
for _, dt := range dts { for _, dt := range dts {
access := oclib.NewRequestAdmin(oclib.LibDataEnum(dt), nil) access := oclib.NewRequestAdmin(oclib.LibDataEnum(dt), nil)
searched := access.Search(abs.FilterPeer(self.GetID(), event.Groups, search), "", false) searched := access.Search(abs.FilterPeer(self.GetID(), event.Groups, search), "", false, 0, 0)
for _, ss := range searched.Data { for _, ss := range searched.Data {
if j, err := json.Marshal(ss); err == nil { if j, err := json.Marshal(ss); err == nil {
abs.PublishCommon(&dt, event.User, event.Groups, p.PeerID, ProtocolSearchResource, j) abs.PublishCommon(&dt, event.User, event.Groups, p.PeerID, ProtocolSearchResource, j)

View File

@@ -0,0 +1,552 @@
package stream
import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"time"
"oc-discovery/daemons/node/common"
oclib "cloud.o-forge.io/core/oc-lib"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/libp2p/go-libp2p/core/network"
pp "github.com/libp2p/go-libp2p/core/peer"
)
// ProtocolObserve is the libp2p protocol for peer connectivity observation.
// The requesting oc-discovery opens a stream to the remote oc-discovery and
// sends an ObserveRequest. The remote side keeps the stream open and writes
// ObserveHeartbeat events back every observeHBInterval seconds.
const ProtocolObserve = "/opencloud/peer/observe/1.0"
// observeHBEventType is used as the common.Event.Type for heartbeat responses.
const observeHBEventType = "/opencloud/peer/observe/heartbeat"
const observeHBInterval = 30 * time.Second
const observeDrainDuration = 30 * time.Second
// observeBatchWindow is the accumulation window before a heartbeat batch is
// flushed to NATS. All peer heartbeats received within this window are grouped
// into a single PEER_OBSERVE_RESPONSE_EVENT, reducing NATS traffic.
const observeBatchWindow = 2 * time.Second
// ObserveRequest is the first (and only) message sent by the observing side
// when opening a ProtocolObserve stream.
type ObserveRequest struct {
// Close, when true, asks the remote side to stop the heartbeat goroutine
// and remove the observer from its cache. Used for graceful teardown.
Close bool `json:"close,omitempty"`
}
// ObserveHeartbeat is sent by the observed side every observeHBInterval.
type ObserveHeartbeat struct {
State string `json:"state"` // always "online" when actively emitted
}
// ShallowPeer is the minimal peer representation sent by oc-peer in a
// PEER_OBSERVE_EVENT. StreamAddress lets oc-discovery connect without a DB
// lookup; Address carries the NATSAddress (unused here, forwarded as-is).
type ShallowPeer struct {
ID string `json:"id"`
PeerID string `json:"peer_id"`
Address string `json:"address"`
StreamAddress string `json:"stream_address"`
}
// ObserveCommand is the payload carried by a PEER_OBSERVE_EVENT NATS message
// (from oc-peer).
//
// Observe → User + Peers populated
// Close → User + PeerIDs + Close=true
// CloseAll → CloseAll=true (User optional)
type ObserveCommand struct {
User string `json:"user"`
Peers []ShallowPeer `json:"peers,omitempty"`
PeerIDs []string `json:"peer_ids,omitempty"`
Close bool `json:"close,omitempty"`
CloseAll bool `json:"close_all,omitempty"`
}
// ── observe cache (observed side) ────────────────────────────────────────────
// observeCache tracks running heartbeat goroutines keyed by the observing
// peer's libp2p PeerID string. It is used exclusively on the OBSERVED side.
type observeCache struct {
mu sync.Mutex
cancels map[string]context.CancelFunc
}
func newObserveCache() *observeCache {
return &observeCache{cancels: map[string]context.CancelFunc{}}
}
func (c *observeCache) set(pid string, cancel context.CancelFunc) {
c.mu.Lock()
defer c.mu.Unlock()
if old, ok := c.cancels[pid]; ok {
old() // cancel previous goroutine if any
}
c.cancels[pid] = cancel
}
func (c *observeCache) cancel(pid string) {
c.mu.Lock()
defer c.mu.Unlock()
if fn, ok := c.cancels[pid]; ok {
fn()
delete(c.cancels, pid)
}
}
func (c *observeCache) cancelAll() {
c.mu.Lock()
defer c.mu.Unlock()
for _, fn := range c.cancels {
fn()
}
c.cancels = map[string]context.CancelFunc{}
}
func (c *observeCache) delete(pid string) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.cancels, pid)
}
// ── heartbeat batcher (observing side) ───────────────────────────────────────
// heartbeatBatcher accumulates peer_ids from incoming heartbeats over
// observeBatchWindow, then flushes them in a single NATS call.
// Using a map as the backing store deduplicates multiple heartbeats from the
// same peer within the same window (should not happen, but is harmless).
type heartbeatBatcher struct {
mu sync.Mutex
ids map[string]struct{}
timer *time.Timer
flush func(peerIDs []string)
}
func newHeartbeatBatcher(flush func([]string)) *heartbeatBatcher {
return &heartbeatBatcher{
ids: make(map[string]struct{}),
flush: flush,
}
}
// add records peerID in the current batch and arms the flush timer if needed.
func (b *heartbeatBatcher) add(peerID string) {
b.mu.Lock()
defer b.mu.Unlock()
b.ids[peerID] = struct{}{}
if b.timer == nil {
b.timer = time.AfterFunc(observeBatchWindow, b.fire)
}
}
// fire is called by the timer; it drains the batch and invokes flush.
func (b *heartbeatBatcher) fire() {
b.mu.Lock()
ids := make([]string, 0, len(b.ids))
for id := range b.ids {
ids = append(ids, id)
}
b.ids = make(map[string]struct{})
b.timer = nil
b.mu.Unlock()
if len(ids) > 0 {
b.flush(ids)
}
}
// flushObserveBatch is the flush function wired into the heartbeatBatcher.
// It emits two NATS messages:
// - PEER_OBSERVE_RESPONSE_EVENT → consumed by oc-peer (direct channel)
// - PROPALGATION_EVENT / PB_PROPAGATE → consumed by other oc-discovery nodes
func flushObserveBatch(peerIDs []string) {
payload, err := json.Marshal(map[string]interface{}{
"peer_ids": peerIDs,
"state": "online",
})
if err != nil {
return
}
// Direct notification to oc-peer.
tools.NewNATSCaller().SetNATSPub(tools.PEER_OBSERVE_RESPONSE_EVENT, tools.NATSResponse{
FromApp: "oc-discovery",
Datatype: tools.PEER,
Method: int(tools.PEER_OBSERVE_RESPONSE_EVENT),
Payload: payload,
})
// Broadcast to other oc-discovery nodes so they can forward to their
// local oc-peer if needed.
propPayload, err := json.Marshal(tools.PropalgationMessage{
DataType: int(tools.PEER),
Action: tools.PB_PROPAGATE,
Payload: payload,
})
if err != nil {
return
}
tools.NewNATSCaller().SetNATSPub(tools.PROPALGATION_EVENT, tools.NATSResponse{
FromApp: "oc-discovery",
Datatype: tools.PEER,
Method: int(tools.PROPALGATION_EVENT),
Payload: propPayload,
})
}
// ── incoming observe handler (observed side) ──────────────────────────────────
// handleIncomingObserve is registered as the ProtocolObserve stream handler.
// It is called when a remote peer opens an observe stream to us.
// The function reads the request, validates it, then starts (or stops) the
// heartbeat goroutine and returns immediately — the goroutine owns the stream.
func (s *StreamService) handleIncomingObserve(rawStream network.Stream) error {
remotePeerID := rawStream.Conn().RemotePeer().String()
addr := rawStream.Conn().RemoteMultiaddr().String()
ad, err := pp.AddrInfoFromString(addr + "/p2p/" + remotePeerID)
if err != nil {
fmt.Println("qndlqnl EERR", addr, err)
return err
}
log := oclib.GetLogger()
// Drain mode: reject any new observations for 30 s after a close-all.
s.drainMu.RLock()
draining := !s.drainUntil.IsZero() && time.Now().Before(s.drainUntil)
s.drainMu.RUnlock()
if draining {
rawStream.Close()
fmt.Println("Draining")
return errors.New("Draining")
}
// Read the observe request (with a generous deadline to avoid hangs).
// Guard: the requesting peer must not be blacklisted or be ourself.
did := ""
access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil)
res := access.Search(&dbs.Filters{
And: map[string][]dbs.Filter{
"peer_id": {{Operator: dbs.EQUAL.String(), Value: remotePeerID}},
},
}, "", false, 0, 1)
if len(res.Data) > 0 {
p := res.Data[0].(*peer.Peer)
did = p.GetID()
if p.Relation == peer.BLACKLIST { // || p.Relation == peer.SELF
rawStream.Close()
fmt.Println("CLOSE blacklist or self")
return errors.New("can't exploit blacklist or self")
}
}
// Replace any existing heartbeat goroutine for this observer.
ctx, cancel := context.WithCancel(context.Background())
s.observeCache.set(remotePeerID, cancel)
fmt.Println("LOOP OBSERVE")
go func() {
defer rawStream.Close()
defer cancel()
defer s.observeCache.delete(remotePeerID)
ticker := time.NewTicker(observeHBInterval)
defer ticker.Stop()
hbPayload, _ := json.Marshal(ObserveHeartbeat{State: "online"})
evt := common.NewEvent(observeHBEventType, s.Host.ID().String(), nil, "", hbPayload)
if evt == nil {
return
}
if s.Streams, err = common.TempStream(s.Host, *ad, ProtocolObserve, did, s.Streams, protocols, &s.Mu); err == nil {
stream := s.Streams[ProtocolObserve][ad.ID]
if err := json.NewEncoder(stream.Stream).Encode(evt); err != nil {
// Moderate connectivity event: the observer is unreachable.
// The deferred calls above purge this observer from the cache.
fmt.Println("LOOP EVT ERR", err)
log.Info().
Str("observer", remotePeerID).
Err(err).
Msg("[observe] heartbeat write failed — moderate connectivity event, purging observer from cache")
return
}
}
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
rawStream.SetWriteDeadline(time.Now().Add(5 * time.Second))
fmt.Println("LOOP EVT", evt)
var err error
if s.Streams, err = common.TempStream(s.Host, *ad, ProtocolObserve, did, s.Streams, protocols, &s.Mu); err == nil {
stream := s.Streams[ProtocolObserve][ad.ID]
if err := json.NewEncoder(stream.Stream).Encode(evt); err != nil {
// Moderate connectivity event: the observer is unreachable.
// The deferred calls above purge this observer from the cache.
fmt.Println("LOOP EVT ERR", err)
log.Info().
Str("observer", remotePeerID).
Err(err).
Msg("[observe] heartbeat write failed — moderate connectivity event, purging observer from cache")
return
}
}
rawStream.SetWriteDeadline(time.Time{})
}
}
}()
return nil
}
// ── heartbeat receiver (observing side) ───────────────────────────────────────
// handleObserveHeartbeat is called by readLoop when a heartbeat event arrives
// on an outgoing ProtocolObserve stream. It queues the peer_id in the batch
// accumulator; the batcher flushes to NATS after observeBatchWindow.
func (ps *StreamService) handleObserveHeartbeat(evt *common.Event) error {
// ps.hbBatcher.add(evt.From)
flushObserveBatch([]string{evt.From})
return nil
}
// ── user→peer index (ref-counted observe management) ─────────────────────────
// userPeerIndex tracks which users are observing which peers.
// A libp2p observe stream is kept open as long as at least one user watches
// the peer; it is closed only when the last user stops.
type userPeerIndex struct {
mu sync.Mutex
index map[string]map[string]struct{} // user → set of peer_id strings
}
func newUserPeerIndex() *userPeerIndex {
return &userPeerIndex{index: map[string]map[string]struct{}{}}
}
// add registers user as an observer of peerID.
// Returns true if peerID was not yet observed by any user (first observer).
func (u *userPeerIndex) add(user, peerID string) (isFirst bool) {
u.mu.Lock()
defer u.mu.Unlock()
// Count total observers for peerID across all users before adding.
total := 0
for _, peers := range u.index {
if _, ok := peers[peerID]; ok {
total++
}
}
if u.index[user] == nil {
u.index[user] = map[string]struct{}{}
}
u.index[user][peerID] = struct{}{}
return total == 0
}
// remove unregisters user from peerID.
// Returns true if no user is observing peerID anymore (last observer removed).
func (u *userPeerIndex) remove(user, peerID string) (isLast bool) {
u.mu.Lock()
defer u.mu.Unlock()
delete(u.index[user], peerID)
if len(u.index[user]) == 0 {
delete(u.index, user)
}
for _, peers := range u.index {
if _, ok := peers[peerID]; ok {
return false
}
}
return true
}
// removeUser removes all entries for user and returns the peer_ids that now
// have no remaining observers (i.e., those whose streams should be closed).
func (u *userPeerIndex) removeUser(user string) []string {
u.mu.Lock()
defer u.mu.Unlock()
watched := u.index[user]
delete(u.index, user)
var orphans []string
for peerID := range watched {
found := false
for _, peers := range u.index {
if _, ok := peers[peerID]; ok {
found = true
break
}
}
if !found {
orphans = append(orphans, peerID)
}
}
return orphans
}
// ── NATS command handler (observing side) ─────────────────────────────────────
// HandleObserveNATSCommand processes a PEER_OBSERVE_EVENT received from oc-peer.
func (ps *StreamService) HandleObserveNATSCommand(resp tools.NATSResponse) {
log := oclib.GetLogger()
var cmd ObserveCommand
if err := json.Unmarshal(resp.Payload, &cmd); err != nil {
log.Warn().Err(err).Msg("[observe] failed to unmarshal ObserveCommand")
return
}
if cmd.CloseAll {
log.Info().Msg("[observe] close-all received via NATS")
ps.CloseAllObserves()
return
}
if cmd.Close {
for _, peerID := range cmd.PeerIDs {
if isLast := ps.observeUsers.remove(cmd.User, peerID); isLast {
if err := ps.closeObserveStream(peerID); err != nil {
log.Warn().Str("peer", peerID).Err(err).Msg("[observe] closeObserveStream failed")
}
}
}
return
}
// Observe: open streams for any new peer, using the address from the payload.
for _, p := range cmd.Peers {
if isFirst := ps.observeUsers.add(cmd.User, p.PeerID); isFirst {
if err := ps.openObserveStream(p); err != nil {
// Roll back the index entry so the next NATS command can retry.
ps.observeUsers.remove(cmd.User, p.PeerID)
log.Warn().Str("peer", p.PeerID).Err(err).Msg("[observe] openObserveStream failed")
}
}
}
}
// ── outgoing observe management (observing side) ──────────────────────────────
// OpenObserveStream is the exported variant for inter-discovery propagation
// (no user context available). It bypasses the user index and opens the stream
// directly if not already open.
func (ps *StreamService) OpenObserveStream(p ShallowPeer) error {
return ps.openObserveStream(p)
}
// CloseObserveStream is the exported variant for inter-discovery propagation.
func (ps *StreamService) CloseObserveStream(toPeerID string) error {
return ps.closeObserveStream(toPeerID)
}
// openObserveStream opens a ProtocolObserve stream to p.
// Uses p.StreamAddress directly; falls back to DB then DHT lookup if empty.
func (ps *StreamService) openObserveStream(p ShallowPeer) error {
streamAddr := p.StreamAddress
fmt.Println("STREAM OBS", streamAddr)
access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil)
res := access.Search(&dbs.Filters{
And: map[string][]dbs.Filter{
"peer_id": {{Operator: dbs.EQUAL.String(), Value: p.PeerID}},
},
}, "", false, 0, 1)
if streamAddr == "" {
// Fallback: DB then DHT.
if len(res.Data) > 0 {
streamAddr = res.Data[0].(*peer.Peer).StreamAddress
} else if peers, err := ps.Node.GetPeerRecord(context.Background(), p.PeerID); err == nil && len(peers) > 0 {
streamAddr = peers[0].StreamAddress
}
}
if len(res.Data) > 0 && res.Data[0].(*peer.Peer).Relation == peer.SELF {
return errors.New("Can't send to self")
}
fmt.Println("STREAM OBS SSS", streamAddr)
if streamAddr == "" {
return nil // can't resolve address — silently skip
}
decodedID, err := pp.Decode(p.PeerID)
if err != nil {
return err
}
// If a stream already exists, reuse it.
ps.Mu.RLock()
_, alreadyOpen := ps.Streams[ProtocolObserve][decodedID]
ps.Mu.RUnlock()
if alreadyOpen {
return nil
}
ad, err := pp.AddrInfoFromString(streamAddr)
if err != nil {
return err
}
fmt.Println("TempStream OBSERVE", ad)
if ps.Streams, err = common.TempStream(ps.Host, *ad, ProtocolObserve, p.ID, ps.Streams, protocols, &ps.Mu); err == nil {
rawStream := ps.Streams[ProtocolObserve][ad.ID]
if hbPayload, err := json.Marshal(ObserveRequest{Close: false}); err == nil {
if err := json.NewEncoder(rawStream.Stream).Encode(common.NewEvent(ProtocolObserve, ps.Host.ID().String(), nil, "", hbPayload)); err != nil {
fmt.Println("ERR")
rawStream.Stream.Close()
return err
}
s := &common.Stream{
Stream: rawStream.Stream,
Expiry: time.Now().Add(365 * 24 * time.Hour),
}
ps.Mu.Lock()
if ps.Streams[ProtocolObserve] == nil {
ps.Streams[ProtocolObserve] = map[pp.ID]*common.Stream{}
}
ps.Streams[ProtocolObserve][ad.ID] = s
ps.Mu.Unlock()
go ps.readLoop(s, ad.ID, ProtocolObserve, &common.ProtocolInfo{PersistantStream: true})
}
} else {
return err
}
return nil
}
// closeObserveStream closes the ProtocolObserve stream to toPeerID and notifies
// the remote side.
func (ps *StreamService) closeObserveStream(toPeerID string) error {
decodedID, err := pp.Decode(toPeerID)
if err != nil {
return err
}
ps.Mu.Lock()
if ps.Streams[ProtocolObserve] != nil {
if s, ok := ps.Streams[ProtocolObserve][decodedID]; ok {
_ = json.NewEncoder(s.Stream).Encode(ObserveRequest{Close: true})
s.Stream.Close()
delete(ps.Streams[ProtocolObserve], decodedID)
}
}
ps.Mu.Unlock()
return nil
}
// CloseAllObserves closes every outgoing ProtocolObserve stream, clears the
// user index, and enters drain mode for observeDrainDuration.
func (ps *StreamService) CloseAllObserves() {
ps.Mu.Lock()
for _, s := range ps.Streams[ProtocolObserve] {
_ = json.NewEncoder(s.Stream).Encode(ObserveRequest{Close: true})
s.Stream.Close()
}
delete(ps.Streams, ProtocolObserve)
ps.Mu.Unlock()
// Reset user index so stale ref-counts don't block future opens.
ps.observeUsers = newUserPeerIndex()
ps.drainMu.Lock()
ps.drainUntil = time.Now().Add(observeDrainDuration)
ps.drainMu.Unlock()
}

View File

@@ -6,6 +6,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"oc-discovery/daemons/node/common" "oc-discovery/daemons/node/common"
"strings"
"time"
oclib "cloud.o-forge.io/core/oc-lib" oclib "cloud.o-forge.io/core/oc-lib"
"cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/dbs"
@@ -19,9 +21,9 @@ func (ps *StreamService) PublishesCommon(dt *tools.DataType, user string, groups
access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil) access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil)
var p oclib.LibDataShallow var p oclib.LibDataShallow
if filter == nil { if filter == nil {
p = access.LoadAll(false) p = access.LoadAll(false, 0, 10000)
} else { } else {
p = access.Search(filter, "", false) p = access.Search(filter, "", false, 0, 10000)
} }
for _, pes := range p.Data { for _, pes := range p.Data {
for _, proto := range protos { for _, proto := range protos {
@@ -45,7 +47,7 @@ func (ps *StreamService) PublishCommon(dt *tools.DataType, user string, groups [
And: map[string][]dbs.Filter{ // search by name if no filters are provided And: map[string][]dbs.Filter{ // search by name if no filters are provided
"peer_id": {{Operator: dbs.EQUAL.String(), Value: toPeerID}}, "peer_id": {{Operator: dbs.EQUAL.String(), Value: toPeerID}},
}, },
}, toPeerID, false) }, toPeerID, false, 0, 1)
var pe *peer.Peer var pe *peer.Peer
if len(p.Data) > 0 && p.Data[0].(*peer.Peer).Relation != peer.BLACKLIST { if len(p.Data) > 0 && p.Data[0].(*peer.Peer).Relation != peer.BLACKLIST {
pe = p.Data[0].(*peer.Peer) pe = p.Data[0].(*peer.Peer)
@@ -57,13 +59,36 @@ func (ps *StreamService) PublishCommon(dt *tools.DataType, user string, groups [
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ps.write(toPeerID, ad, dt, user, resource, proto) stream, err := ps.write(toPeerID, ad, dt, user, resource, proto)
if err != nil {
if _, ok := dntProtocols[proto]; ok {
ps.dnt.enqueue(&dntEntry{
did: toPeerID,
addr: *ad,
dt: dt,
user: user,
payload: resource,
proto: proto,
addedAt: time.Now().UTC(),
})
}
return nil, err
}
return stream, nil
} }
return nil, errors.New("peer unvalid " + toPeerID) return nil, errors.New("peer unvalid " + toPeerID)
} }
func (ps *StreamService) ToPartnerPublishEvent( func (ps *StreamService) ToPartnerPublishEvent(
ctx context.Context, action tools.PubSubAction, dt *tools.DataType, user string, groups []string, payload []byte) error { ctx context.Context, action tools.PubSubAction, dt *tools.DataType, user string, groups []string, payload []byte) error {
var proto protocol.ID
proto = ProtocolCreateResource
switch action {
case tools.PB_DELETE:
proto = ProtocolDeleteResource
case tools.PB_UPDATE:
proto = ProtocolUpdateResource
}
if *dt == tools.PEER { if *dt == tools.PEER {
var p peer.Peer var p peer.Peer
if err := json.Unmarshal(payload, &p); err != nil { if err := json.Unmarshal(payload, &p); err != nil {
@@ -87,25 +112,30 @@ func (ps *StreamService) ToPartnerPublishEvent(
} }
} }
var per peer.Peer
if err := json.Unmarshal(payload, &per); err == nil && !strings.Contains(per.Relation.String(), "master") && !strings.Contains(per.Relation.String(), "nano") {
for _, rel := range []peer.PeerRelation{peer.MASTER, peer.NANO} {
ps.PublishesCommon(dt, user, groups, &dbs.Filters{
And: map[string][]dbs.Filter{
"relation": {{Operator: dbs.EQUAL.String(), Value: rel}},
},
}, payload, proto)
}
}
return nil return nil
} }
ks := []protocol.ID{} ks := []protocol.ID{}
for k := range protocolsPartners { for k := range protocolsPartners {
ks = append(ks, k) ks = append(ks, k)
} }
var proto protocol.ID for _, rel := range []peer.PeerRelation{peer.PARTNER, peer.MASTER, peer.NANO} {
proto = ProtocolCreateResource ps.PublishesCommon(dt, user, groups, &dbs.Filters{
switch action { And: map[string][]dbs.Filter{
case tools.PB_DELETE: "relation": {{Operator: dbs.EQUAL.String(), Value: rel}},
proto = ProtocolDeleteResource },
case tools.PB_UPDATE: }, payload, proto)
proto = ProtocolUpdateResource
} }
ps.PublishesCommon(dt, user, groups, &dbs.Filters{ // filter by like name, short_description, description, owner, url if no filters are provided
And: map[string][]dbs.Filter{
"relation": {{Operator: dbs.EQUAL.String(), Value: peer.PARTNER}},
},
}, payload, proto)
return nil return nil
} }
@@ -129,7 +159,6 @@ func (s *StreamService) write(
if s.Streams, err = common.TempStream(s.Host, *peerID, proto, did, s.Streams, pts, &s.Mu); err != nil { if s.Streams, err = common.TempStream(s.Host, *peerID, proto, did, s.Streams, pts, &s.Mu); err != nil {
fmt.Println("TempStream", err) fmt.Println("TempStream", err)
return nil, errors.New("no stream available for protocol " + fmt.Sprintf("%v", proto) + " from PID " + peerID.ID.String()) return nil, errors.New("no stream available for protocol " + fmt.Sprintf("%v", proto) + " from PID " + peerID.ID.String())
} }
stream := s.Streams[proto][peerID.ID] stream := s.Streams[proto][peerID.ID]

View File

@@ -12,6 +12,7 @@ import (
"time" "time"
oclib "cloud.o-forge.io/core/oc-lib" oclib "cloud.o-forge.io/core/oc-lib"
"cloud.o-forge.io/core/oc-lib/config"
"cloud.o-forge.io/core/oc-lib/dbs" "cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/peer" "cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
@@ -42,6 +43,7 @@ var protocols = map[protocol.ID]*common.ProtocolInfo{
ProtocolVerifyResource: {WaitResponse: true, TTL: 1 * time.Minute}, ProtocolVerifyResource: {WaitResponse: true, TTL: 1 * time.Minute},
ProtocolMinioConfigResource: {WaitResponse: true, TTL: 1 * time.Minute}, ProtocolMinioConfigResource: {WaitResponse: true, TTL: 1 * time.Minute},
ProtocolAdmiraltyConfigResource: {WaitResponse: true, TTL: 1 * time.Minute}, ProtocolAdmiraltyConfigResource: {WaitResponse: true, TTL: 1 * time.Minute},
ProtocolObserve: {WaitResponse: true, TTL: 1 * time.Minute},
} }
var protocolsPartners = map[protocol.ID]*common.ProtocolInfo{ var protocolsPartners = map[protocol.ID]*common.ProtocolInfo{
@@ -61,6 +63,21 @@ type StreamService struct {
// IsPeerKnown, when set, is called at stream open for every inbound protocol. // IsPeerKnown, when set, is called at stream open for every inbound protocol.
// Return false to reset the stream immediately. Left nil until wired by the node. // Return false to reset the stream immediately. Left nil until wired by the node.
IsPeerKnown func(pid pp.ID) bool IsPeerKnown func(pid pp.ID) bool
// dnt is the Disconnection Network Tolerance cache for outbound streams.
dnt *dntCache
// observeCache tracks running heartbeat goroutines on the OBSERVED side.
observeCache *observeCache
// hbBatcher accumulates incoming heartbeats (observing side) and flushes
// them as a single NATS batch after observeBatchWindow.
hbBatcher *heartbeatBatcher
// drainUntil / drainMu implement the startup drain window: for 30 s after a
// close-all, incoming ProtocolObserve requests are rejected so stale heartbeats
// from a previous run cannot mix with fresh observations.
drainUntil time.Time
drainMu sync.RWMutex
// observeUsers tracks which users are observing which peers so streams are
// closed only when the last observer for a peer disconnects.
observeUsers *userPeerIndex
} }
func InitStream(ctx context.Context, h host.Host, key pp.ID, maxNode int, node common.DiscoveryPeer) (*StreamService, error) { func InitStream(ctx context.Context, h host.Host, key pp.ID, maxNode int, node common.DiscoveryPeer) (*StreamService, error) {
@@ -72,31 +89,60 @@ func InitStream(ctx context.Context, h host.Host, key pp.ID, maxNode int, node c
Streams: common.ProtocolStream{}, Streams: common.ProtocolStream{},
maxNodesConn: maxNode, maxNodesConn: maxNode,
ResourceSearches: common.NewSearchTracker(), ResourceSearches: common.NewSearchTracker(),
dnt: newDNTCache(),
observeCache: newObserveCache(),
observeUsers: newUserPeerIndex(),
} }
service.hbBatcher = newHeartbeatBatcher(flushObserveBatch)
for proto := range protocols { for proto := range protocols {
service.Host.SetStreamHandler(proto, service.gate(service.HandleResponse)) service.Host.SetStreamHandler(proto, service.gate(service.HandleResponse))
} }
// ProtocolObserve uses a dedicated handler (bidirectional, long-lived).
logger.Info().Msg("connect to partners...") logger.Info().Msg("connect to partners...")
service.connectToPartners() // we set up a stream service.connectToPartners() // we set up a stream
go service.StartGC(8 * time.Second) go service.StartGC(8 * time.Second)
go service.startDNTLoop()
return service, nil return service, nil
} }
// gate wraps a stream handler with IsPeerKnown validation.
// If the peer is unknown the entire connection is closed and the handler is not called.
// IsPeerKnown is read at stream-open time so it works even when set after InitStream.
func (s *StreamService) gatePrivilege(h func(network.Stream)) func(network.Stream) {
return func(stream network.Stream) {
if config.GetConfig().IsNano {
d := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil).Search(&dbs.Filters{
And: map[string][]dbs.Filter{
"relation": {{Operator: dbs.EQUAL.String(), Value: peer.MASTER}},
},
}, "", false, 0, 1)
if len(d.Data) == 0 {
return
}
}
s.knowingGate(stream, h)
}
}
// gate wraps a stream handler with IsPeerKnown validation. // gate wraps a stream handler with IsPeerKnown validation.
// If the peer is unknown the entire connection is closed and the handler is not called. // If the peer is unknown the entire connection is closed and the handler is not called.
// IsPeerKnown is read at stream-open time so it works even when set after InitStream. // IsPeerKnown is read at stream-open time so it works even when set after InitStream.
func (s *StreamService) gate(h func(network.Stream)) func(network.Stream) { func (s *StreamService) gate(h func(network.Stream)) func(network.Stream) {
return func(stream network.Stream) { return func(stream network.Stream) {
if s.IsPeerKnown != nil && !s.IsPeerKnown(stream.Conn().RemotePeer()) { s.knowingGate(stream, h)
logger := oclib.GetLogger()
logger.Warn().Str("peer", stream.Conn().RemotePeer().String()).Msg("[stream] unknown peer, closing connection")
stream.Conn().Close()
return
}
h(stream)
} }
} }
func (s *StreamService) knowingGate(stream network.Stream, h func(network.Stream)) {
if s.IsPeerKnown != nil && !s.IsPeerKnown(stream.Conn().RemotePeer()) {
logger := oclib.GetLogger()
logger.Warn().Str("peer", stream.Conn().RemotePeer().String()).Msg("[stream] unknown peer, closing connection")
stream.Conn().Close()
return
}
h(stream)
}
func (s *StreamService) HandleResponse(stream network.Stream) { func (s *StreamService) HandleResponse(stream network.Stream) {
s.Mu.Lock() s.Mu.Lock()
defer s.Mu.Unlock() defer s.Mu.Unlock()
@@ -137,13 +183,27 @@ func (s *StreamService) connectToPartners() error {
go s.readLoop(s.Streams[proto][ss.Conn().RemotePeer()], ss.Conn().RemotePeer(), proto, info) go s.readLoop(s.Streams[proto][ss.Conn().RemotePeer()], ss.Conn().RemotePeer(), proto, info)
} }
logger.Info().Msg("SetStreamHandler " + string(proto)) logger.Info().Msg("SetStreamHandler " + string(proto))
s.Host.SetStreamHandler(proto, s.gate(f)) s.Host.SetStreamHandler(proto, s.gatePrivilege(f))
} }
return nil return nil
} }
func (s *StreamService) searchPeer(search string) ([]*peer.Peer, error) { func (s *StreamService) searchPeer(search string) ([]*peer.Peer, error) {
ps := []*peer.Peer{} ps := []*peer.Peer{}
if conf.GetConfig().NanoIDS != "" {
for _, peerID := range strings.Split(conf.GetConfig().NanoIDS, ",") {
ppID := strings.Split(peerID, "/")
ps = append(ps, &peer.Peer{
AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(),
Name: ppID[1],
},
PeerID: ppID[len(ppID)-1],
StreamAddress: peerID,
Relation: peer.NANO,
})
}
}
if conf.GetConfig().PeerIDS != "" { if conf.GetConfig().PeerIDS != "" {
for _, peerID := range strings.Split(conf.GetConfig().PeerIDS, ",") { for _, peerID := range strings.Split(conf.GetConfig().PeerIDS, ",") {
ppID := strings.Split(peerID, "/") ppID := strings.Split(peerID, "/")
@@ -159,7 +219,7 @@ func (s *StreamService) searchPeer(search string) ([]*peer.Peer, error) {
} }
} }
access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil) access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil)
peers := access.Search(nil, search, false) peers := access.Search(nil, search, false, 0, 0)
for _, p := range peers.Data { for _, p := range peers.Data {
ps = append(ps, p.(*peer.Peer)) ps = append(ps, p.(*peer.Peer))
} }
@@ -230,7 +290,7 @@ func (ps *StreamService) readLoop(s *common.Stream, id pp.ID, proto protocol.ID,
} }
continue continue
} }
ps.handleEvent(evt.Type, &evt) ps.handleEvent(evt.Type, &evt, s.Stream)
if protocolInfo.WaitResponse && !protocolInfo.PersistantStream { if protocolInfo.WaitResponse && !protocolInfo.PersistantStream {
break break
} }

View File

@@ -4,5 +4,5 @@
"NATS_URL": "nats://nats:4222", "NATS_URL": "nats://nats:4222",
"NODE_MODE": "indexer", "NODE_MODE": "indexer",
"NODE_ENDPOINT_PORT": 4002, "NODE_ENDPOINT_PORT": 4002,
"INDEXER_ADDRESSES": "/ip4/172.40.0.1/tcp/4001/p2p/12D3KooWGn3j4XqTSrjJDGGpTQERdDV5TPZdhQp87rAUnvQssvQu" "INDEXER_ADDRESSES": "/ip4/172.40.0.5/tcp/4005/p2p/12D3KooWGn3j4XqTSrjJDGGpTQERdDV5TPZdhQp87rAUnvQssvQu"
} }

View File

@@ -1,9 +1,9 @@
{ {
"MONGO_URL":"mongodb://mongo:27017/", "MONGO_URL":"mongodb://mongo2:27017/",
"MONGO_DATABASE":"DC_myDC", "MONGO_DATABASE":"DC_myDC",
"NATS_URL": "nats://nats:4222", "NATS_URL": "nats://nats2:4222",
"NODE_MODE": "node", "NODE_MODE": "node",
"NODE_ENDPOINT_PORT": 4004, "NODE_ENDPOINT_PORT": 4004,
"NAME": "opencloud-demo-2", "NAME": "opencloud-demo-2",
"INDEXER_ADDRESSES": "/ip4/172.40.0.1/tcp/4001/p2p/12D3KooWGn3j4XqTSrjJDGGpTQERdDV5TPZdhQp87rAUnvQssvQu" "INDEXER_ADDRESSES": "/ip4/172.40.0.5/tcp/4005/p2p/12D3KooWGn3j4XqTSrjJDGGpTQERdDV5TPZdhQp87rAUnvQssvQu"
} }

View File

@@ -2,5 +2,6 @@
"MONGO_URL":"mongodb://mongo:27017/", "MONGO_URL":"mongodb://mongo:27017/",
"MONGO_DATABASE":"DC_myDC", "MONGO_DATABASE":"DC_myDC",
"NATS_URL": "nats://nats:4222", "NATS_URL": "nats://nats:4222",
"NODE_MODE": "indexer" "NODE_MODE": "indexer",
"NODE_ENDPOINT_PORT": 4005
} }

9
docker_discovery6.json Normal file
View File

@@ -0,0 +1,9 @@
{
"MONGO_URL":"mongodb://mongo3:27017/",
"MONGO_DATABASE":"DC_myDC",
"NATS_URL": "nats://nats3:4222",
"NODE_MODE": "node",
"NODE_ENDPOINT_PORT": 4006,
"NAME": "opencloud-demo-3",
"INDEXER_ADDRESSES": "/ip4/172.40.0.5/tcp/4005/p2p/12D3KooWGn3j4XqTSrjJDGGpTQERdDV5TPZdhQp87rAUnvQssvQu"
}

View File

@@ -0,0 +1,472 @@
# Comparaison entre les Systèmes Existants et la Proposition d'Architecture Décentralisée Souveraine
**Catégorie** : Systèmes distribués — Revue comparative et positionnement architectural
**Domaine d'application** : Systèmes embarqués, contexte spatial, réseaux hostiles, marchés de ressources décentralisés
**Version** : 1.1 — Mars 2026
---
## Résumé
Ce document présente une analyse comparative systématique entre les principaux systèmes décentralisés existants — de la découverte P2P aux blockchains — et la proposition d'architecture pour un réseau décentralisé souverain opérant dans un contexte embarqué et hostile (désignée ci-après "la proposition"). Le contexte de référence est celui du projet GARDEN (Generic Architecture for Resilient Decentralized Execution Networks), qui requiert une combinaison de propriétés rarement satisfaites simultanément par un système unique : découverte décentralisée de pairs, souveraineté des données, confidentialité transactionnelle, tolérance aux disruptions réseau (DTN), et gestion de consortiums à niveaux de confiance hétérogènes.
L'analyse montre qu'aucun système existant ne répond simultanément à l'ensemble de ces exigences. IPFS/libp2p offre une découverte décentralisée mature mais sans confidentialité native. Hyperledger Fabric offre une blockchain de consortium avec canaux privés mais suppose une connectivité continue. Secret Network offre des smart contracts confidentiels mais repose sur une dépendance matérielle (Intel SGX). Les systèmes de marketplace décentralisée (Golem, Akash) fournissent des mécanismes de marché mais n'abordent pas la tolérance DTN ni la gestion de la confiance relative.
La proposition se distingue en intégrant des mécanismes issus de plusieurs domaines de la littérature : scoring comportemental multidimensionnel inspiré des protocoles SWIM [Das et al., 2002], architecture blockchain hybride combinant blockchain permissionnée intra-consortium et règlement confidentiel inter-consortium, et adaptations DTN dérivées de la RFC 4838 [Cerf et al., 2007]. Cette synthèse architecturale constitue une contribution originale par rapport à l'existant.
Un point structurant de la proposition, absent de la majorité des systèmes existants, est la **séparation explicite entre le système de découverte et d'échange pair-à-pair** (profil AP, haute disponibilité, cohérence éventuelle) et le **système de règlement monétaire** (profil CP, cohérence forte, finalité déterministe). Ces deux sous-systèmes ont des prérequis mutuellement incompatibles : leur fusion dans un protocole unique dégraderait les garanties des deux. Le tableau comparatif intègre ce critère de séparation.
Les divergences détaillées entre la proposition et un système de référence en cours de développement — avec criticité, options de développement, et feuille de route — sont documentées dans le fichier `NOTE_DIVERGENCE_SYSTEME_REFERENCE.md`.
---
## 1. Critères de Comparaison
Les critères suivants sont retenus pour l'analyse comparative. Chaque critère est justifié par rapport aux exigences du contexte GARDEN.
| Critère | Définition | Justification GARDEN |
|---|---|---|
| **Décentralisation** | Absence de point unique de contrôle ou de défaillance | Contexte hostile : tout point centralisé est une cible prioritaire |
| **Confidentialité** | Opacité du contenu ET des métadonnées pour les observateurs non autorisés | Watchers passifs, acteurs adversariaux dans le réseau |
| **Résistance Sybil** | Capacité à résister à la création massive d'identités fictives [Douceur, 2002] | Réseau potentiellement infiltré par des adversaires |
| **Tolérance DTN** | Opérabilité sous connectivité intermittente, haute latence, partitions prolongées [Fall, 2003] | Contrainte structurelle spatiale et embarquée |
| **Scalabilité** | Passage à l'échelle sans dégradation prohibitive des performances | Multi-cluster, multi-organisation |
| **Finalité des transactions** | Délai et certitude de l'irréversibilité des transactions monétaires | Prévention du double-spend dans les contextes DTN |
| **Smart contracts** | Capacité d'automatiser le règlement conditionnel à des événements vérifiables | Marché de ressources : paiement automatique à la livraison attestée |
| **Séparation découverte / règlement** | Architecture explicitement distincte entre la couche de découverte P2P (AP) et la couche de règlement monétaire (CP) | Prérequis mutuellement incompatibles — toute fusion dégrade les garanties des deux |
| **Gestion de consortiums** | Mécanismes d'admission, révocation, et niveaux de confiance différenciés | Coalitions multi-organisations à confiance hétérogène |
| **Souveraineté** | Contrôle par le créateur du cycle de vie de ses données et identités | Exigence fondamentale du contexte spatial/militaire |
| **Maturité** | Disponibilité en production, documentation, écosystème | Faisabilité d'implémentation dans un délai raisonnable |
| **Adaptabilité embarquée** | Fonctionnalité sous contraintes énergétiques et de bande passante sévères | Nœuds spatiaux à ressources limitées |
---
## 2. Systèmes de Découverte et Stockage P2P
### 2.1 IPFS / Libp2p (Benet, 2014)
**Principe** : IPFS (InterPlanetary File System) est un système de fichiers distribué adressé par contenu. Chaque bloc de données est identifié par son hash cryptographique (Content Identifier, CID), permettant une vérification d'intégrité intrinsèque et une déduplication automatique. La couche de découverte repose sur une DHT Kademlia publique [Maymounkov & Mazières, 2002] implémentée dans libp2p.
**Forces** :
- Architecture décentralisée mature, déployée à très large échelle
- Vérification d'intégrité native par adressage de contenu
- Écosystème libp2p modulaire (transports multiples : TCP, QUIC, WebTransport, Bluetooth)
- Comunauté active, documentation extensive
- Support natif pour les connexions multi-transport et le NAT traversal
**Faiblesses** :
- Aucune confidentialité native : les CIDs dans la DHT publique révèlent quels contenus sont recherchés et partagés
- Aucun contrôle d'accès à la couche de découverte : tout nœud peut rejoindre la DHT publique
- Résistance Sybil non résolue dans la DHT publique
- Pas de mécanisme de confiance ou de scoring des pairs
- Latences de découverte incompatibles avec les contraintes DTN sévères
**Compatibilité GARDEN** : Inadapté en l'état. La DHT publique IPFS est une source de fuite d'information massive dans un contexte hostile. Le fait qu'un ensemble de nœuds recherche des ressources de type spécifique révèle des informations stratégiques à tout observateur participant à la DHT. Des couches additionnelles de confidentialité (chiffrement du contenu, utilisation d'une DHT privée avec espace de noms isolé) peuvent rendre libp2p utilisable comme couche de transport, mais IPFS dans sa forme standard est inadapté.
### 2.2 Filecoin (Protocol Labs, 2017)
**Principe** : Filecoin est un marché de stockage décentralisé construit sur IPFS. Les fournisseurs de stockage s'engagent à stocker des données en soumettant des preuves cryptographiques : Proof of Replication (PoRep) prouve qu'une copie unique d'un fichier est bien stockée, Proof of Spacetime (PoSt) prouve que le stockage est maintenu dans le temps. Le marché de stockage est réglé via des smart contracts sur la chaîne Filecoin.
**Forces** :
- Incentives cryptoéconomiques alignant les comportements des fournisseurs avec les intérêts des utilisateurs
- Preuves cryptographiques vérifiables de l'exécution du stockage
- Décentralisé avec un marché libre de capacité
**Faiblesses** :
- Transactions on-chain sur Filecoin entièrement publiques : les accords de stockage révèlent qui stocke quoi pour qui
- Latences élevées incompatibles avec les workloads temps-réel
- Complexité des preuves cryptographiques : charge de calcul significative pour les nœuds
- Pas de gestion de confiance relative entre pairs ; pas de consortium
**Compatibilité GARDEN** : Le modèle de preuves cryptographiques de consommation de ressources est conceptuellement pertinent et constitue une inspiration pour la couche d'attestation de la proposition. Cependant, la transparence des transactions Filecoin on-chain et l'absence de confidentialité native rendent ce système inadapté au contexte hostile.
### 2.3 BitTorrent DHT (Loewenstern & Norberg, 2008)
**Principe** : Le protocole BitTorrent DHT (BEP 5) implémente une DHT Kademlia pour la découverte de pairs partageant des torrents. Chaque nœud maintient une table de routage de pairs proches dans l'espace XOR de Kademlia, permettant de localiser des pairs ayant annoncé un torrent spécifique.
**Forces** :
- Système très mature, déployé à des centaines de millions de nœuds
- Résilience exceptionnelle démontrée à grande échelle
- Protocole simple et bien documenté
**Faiblesses** :
- Aucune authentification des nœuds : toute clé publique peut être annoncée par n'importe qui
- Aucune confidentialité : les requêtes DHT révèlent quels contenus sont recherchés
- Aucun mécanisme de confiance ou de réputation
- Résistance Sybil nulle
**Compatibilité GARDEN** : Inadapté. BitTorrent DHT constitue une référence historique utile pour comprendre les propriétés de scalabilité et de résilience des DHT Kademlia, mais ses propriétés de sécurité sont incompatibles avec tout contexte hostile.
### 2.4 Tor (Dingledine, Mathewson & Syverson, 2004)
**Principe** : Tor (The Onion Router) est un réseau d'anonymisation par routage en oignon : chaque message est chiffré en plusieurs couches et routé à travers trois nœuds relais (entry guard, middle relay, exit node), de sorte qu'aucun relais unique ne connaît à la fois la source et la destination d'un message. Les services cachés (hidden services, .onion) permettent d'opérer des serveurs sans révéler leur adresse IP.
**Forces** :
- Confidentialité de transport forte, résistance démontrée contre les observateurs passifs non globaux
- Résistance à la surveillance ciblée sur les liaisons intermédiaires
- Écosystème bien documenté, client libre largement déployé
**Faiblesses** :
- Latences élevées (100500ms en moyenne) introduites par le routage multi-sauts
- Vulnérable aux attaques par analyse de trafic global (corrélation temporelle) [Murdoch & Danezis, 2005]
- Pas de découverte décentralisée de pairs : les directory authorities constituent un point centralisé
- Pas de transactions monétaires ni de smart contracts
- Connectivité TCP continue requise : incompatible avec les contraintes DTN
**Compatibilité GARDEN** : Pertinent comme couche de transport pour masquer les métadonnées de communication entre nœuds, en particulier sur les liens exposés à des observateurs passifs. Cependant, Tor ne constitue pas une infrastructure complète pour un système de découverte et de marché de ressources.
### 2.5 I2P (Invisible Internet Project)
**Principe** : I2P est un réseau mixnet utilisant le routage en ail (garlic routing), une variante du routage en oignon où plusieurs messages sont agrégés dans une même "gousse d'ail" pour réduire la corrélation temporelle. I2P est plus décentralisé que Tor (pas de directory authorities) et optimisé pour la communication bidirectionnelle (trafic interne au réseau).
**Forces** :
- Plus décentralisé que Tor (distributed network database)
- Meilleure résistance à l'analyse de trafic pour le trafic bidirectionnel
- Réseau auto-suffisant sans dépendance aux directory authorities
**Faiblesses** :
- Écosystème et communauté plus petits que Tor
- Performances de latence comparables à Tor
- Documentation moins développée
- Pas adapté aux contraintes DTN
**Compatibilité GARDEN** : Alternative crédible à Tor pour la couche de transport anonymisant, avec l'avantage d'une moindre dépendance à des points centralisés. Comme Tor, ne constitue pas une infrastructure complète.
---
## 3. Systèmes de Computation et Marketplace Décentralisée
### 3.1 Golem (Golem Network, 2016)
**Principe** : Golem est une marketplace de compute décentralisée basée sur Ethereum. Les fournisseurs de ressources (requestors) soumettent des tâches de calcul définies dans un format standardisé ; les prestataires (providers) les exécutent et reçoivent un paiement en tokens GLM. La vérification du bon accomplissement repose sur des challenges cryptographiques sur les résultats.
**Forces** :
- Marché libre de ressources de calcul
- Preuve de travail challengeable pour vérification des résultats
- Décentralisé, pas d'opérateur central
**Faiblesses** :
- Transactions Ethereum entièrement publiques : qui paie qui, pour quel type de tâche, avec quel montant
- Latences du réseau Ethereum (finalité probabiliste) incompatibles avec les workloads temps-réel
- Pas de support DTN
- Pas de gestion de confiance relative ni de consortiums
- Pas de confidentialité des tâches exécutées
**Compatibilité GARDEN** : Insuffisant pour un contexte hostile. Le modèle de marketplace de compute est conceptuellement pertinent, mais l'absence de confidentialité et la dépendance à Ethereum public rendent ce système inadapté.
### 3.2 Akash Network
**Principe** : Akash Network est un marché de déploiement Kubernetes décentralisé basé sur le SDK Cosmos. Les fournisseurs de compute publient leurs offres de ressources Kubernetes ; les utilisateurs soumettent des manifestes de déploiement standardisés (SDL : Stack Definition Language) ; un mécanisme d'enchère inverse attribue les déploiements aux fournisseurs les moins coûteux.
**Forces** :
- Architecturalement compatible avec Kubernetes (outil standard d'orchestration de conteneurs)
- Finalité rapide via Tendermint BFT (~6 secondes)
- Marché de déploiement décentralisé avec enchères transparentes
- Écosystème Cosmos avec interopérabilité IBC
**Faiblesses** :
- Transactions publiques sur la chaîne Akash : les déploiements sont entièrement visibles
- Modèle de confiance binaire (fournisseur certifié / non certifié)
- Pas de confidentialité native des workloads déployés
- Pas de support DTN
- Gouvernance de confiance limitée : pas de gestion de consortiums à niveaux de confiance
**Compatibilité GARDEN** : Architecturalement proche de ce que GARDEN requiert (Kubernetes décentralisé), mais manque les couches de confidentialité et de gestion de confiance relative indispensables. L'interopérabilité Cosmos constitue un point de compatibilité potentiel.
### 3.3 iExec (2017)
**Principe** : iExec est une marketplace de compute décentralisée intégrant des Trusted Execution Environments (TEE) Intel SGX pour l'exécution confidentielle des tâches. Les smart contracts Ethereum gèrent le cycle de vie des tâches et les paiements en tokens RLC. Le TEE garantit que ni le fournisseur de compute ni aucun observateur ne peut accéder au contenu des données traitées.
**Forces** :
- Exécution confidentielle native via Intel SGX : le fournisseur de compute ne voit pas les données traitées
- Marketplace décentralisée avec attestations TEE
- Smart contracts Ethereum pour l'automatisation du paiement
**Faiblesses** :
- Dépendance critique à Intel SGX : vulnérabilités de canal latéral documentées (Spectre-NG, SGAxe, LVI) [Costan & Devadas, 2016]
- Transactions de marketplace publiques sur Ethereum L1
- Pas de support DTN
- Gouvernance centralisée du réseau TEE
**Compatibilité GARDEN** : Le modèle TEE pour l'exécution confidentielle est pertinent pour le contexte GARDEN, particulièrement pour l'attestation de consommation de ressources (verrou oracle). Cependant, la dépendance à Intel SGX constitue une dépendance matérielle problématique pour des nœuds embarqués spatiaux dont la chaîne d'approvisionnement peut être compromise.
### 3.4 Ocean Protocol (2019)
**Principe** : Ocean Protocol est un marché décentralisé de données basé sur Ethereum. Le mécanisme "Compute-to-Data" permet à un fournisseur de données de proposer l'accès à ses données pour du calcul sans jamais les transférer : le calcul s'exécute au plus près des données, et seul le résultat est retourné au demandeur.
**Forces** :
- Modèle Compute-to-Data respectant la souveraineté du fournisseur de données
- Tokenisation des actifs de données (datatokens ERC-20)
- Décentralisé, écosystème actif
**Faiblesses** :
- Transactions de marketplace publiques sur Ethereum
- Confiance basée sur des smart contracts audités, pas sur des preuves cryptographiques d'exécution
- Pas de support DTN
- Pas de gestion de consortiums à confiance différenciée
**Compatibilité GARDEN** : Le modèle Compute-to-Data constitue une approche intéressante pour préserver la souveraineté des données dans un marché décentralisé. Cependant, la transparence Ethereum et l'absence de gestion de la confiance relative limitent son applicabilité directe.
---
## 4. Systèmes Blockchain
### 4.1 Bitcoin (Nakamoto, 2008)
**Principe** : Bitcoin est le premier système de règlement monétaire pair-à-pair sans tiers de confiance. La sécurité repose sur une preuve de travail (Proof of Work) permettant d'atteindre un consensus sur l'ordre des transactions sans autorité centrale. La finalité est probabiliste : une transaction est considérée irréversible après ~6 confirmations (~1 heure).
**Forces** :
- Sécurité éprouvée sur 15+ ans d'opération continue sans compromission protocolaire
- Décentralisation maximale (aucun opérateur central)
- Résistance à la censure démontrée
**Faiblesses** :
- Pas de smart contracts expressifs (Script très limité)
- Latence finale (~1 heure) incompatible avec les contraintes opérationnelles
- Transactions pseudonymes traçables par analyse de graphe [Meiklejohn et al., 2013]
- Proof of Work : consommation énergétique prohibitive pour contexte embarqué
- Pas de gestion de consortiums
**Compatibilité GARDEN** : Inadapté pour le règlement programmatique et la confidentialité. Valeur de référence théorique uniquement.
### 4.2 Ethereum (Buterin, 2014 ; Wood, 2014)
**Principe** : Ethereum est la première plateforme de smart contracts à vocation généraliste. La machine virtuelle EVM (Ethereum Virtual Machine) exécute des programmes Turing-complets encodés dans des transactions on-chain. La migration vers Proof of Stake (The Merge, 2022) a réduit la consommation énergétique de ~99.95%.
**Forces** :
- EVM : plateforme de smart contracts la plus mature, écosystème DeFi massif
- Large écosystème de développeurs et d'outils
- L2 émergents (ZK-rollups, Optimistic rollups) améliorant scalabilité et potentiellement confidentialité
**Faiblesses** :
- Transparence totale : toutes les transactions et les états des smart contracts sont publics
- Phénomène MEV (Miner/Maximal Extractable Value) créant des distorsions de marché [Daian et al., 2020]
- Finalité probabiliste sur L1 (~12s pour la finalité slot, ~15 minutes pour finalité économique forte)
- Pas de gestion native des consortiums
- L2 ZK confidentiels encore en développement actif
**Compatibilité GARDEN** : Inadapté en L1 seul. Les L2 ZK confidentiels (Aztec) constituent un axe de développement prometteur mais dont la maturité est insuffisante pour un déploiement opérationnel en 2026. Pertinent comme substrate de référence pour l'écosystème EVM.
### 4.3 Hyperledger Fabric (Androulaki et al., 2018)
**Principe** : Hyperledger Fabric est une blockchain permissionnée conçue pour les consortiums d'entreprises. Son architecture distingue les orderers (responsables de l'ordre des transactions), les peers (exécutant les chaincode et maintenant le ledger), et les MSP (Membership Service Providers, responsables de l'identité des membres). Les canaux privés permettent à des sous-groupes de membres d'effectuer des transactions confidentielles vis-à-vis du reste du réseau.
**Forces** :
- Canaux privés : confidentialité native pour les sous-consortiums
- Finalité déterministe : pas de forks possibles avec Raft ou PBFT
- Modular consensus : le mécanisme de consensus est paramétrable selon les besoins
- Chaincode (smart contracts) en langages standards (Go, Java, Node.js)
- Gouvernance fine via les MSP
**Faiblesses** :
- Permissioned : requiert de faire confiance aux opérateurs des MSP (pas de trustless)
- Gouvernance centralisée par essence : il faut faire confiance à la structure d'administration
- Pas de connectivité intermittente supportée nativement : TCP continu requis entre peers et orderers
- Complexité d'administration élevée
**Compatibilité GARDEN** : Très adapté pour les consortiums fermés (intra-consortium dans la proposition). La finalité déterministe et les canaux privés en font le candidat le plus mature pour le règlement intra-consortium. La dépendance à une connectivité TCP continue constitue le principal gap avec les contraintes DTN.
### 4.4 Cosmos / IBC (Kwon & Buchman, 2016)
**Principe** : Cosmos est un écosystème de blockchains souveraines et interopérables. Chaque "zone" est une blockchain indépendante avec son propre consensus (généralement Tendermint BFT), sa propre gouvernance et ses propres smart contracts (CosmWasm). Les zones communiquent via le protocole IBC (Inter-Blockchain Communication), permettant des transferts de tokens et de messages entre blockchains hétérogènes.
**Forces** :
- Zones souveraines : chaque consortium peut opérer sa propre blockchain avec sa propre gouvernance
- Finalité rapide : Tendermint BFT offre une finalité déterministe en ~6 secondes
- IBC : interopérabilité standardisée entre zones
- CosmWasm : smart contracts en Rust avec sécurité mémoire accrue
- Écosystème large et actif
**Faiblesses** :
- Pas de confidentialité native : les transactions IBC sont visibles dans les logs des deux zones impliquées
- Modèle de confiance limité entre zones : pas de mécanisme standardisé pour la confiance relative
- Complexité d'administration d'une zone Cosmos
**Compatibilité GARDEN** : Excellente base pour une architecture multi-consortium. Chaque consortium peut opérer sa propre zone avec sa gouvernance, et les échanges inter-consortium transitent via IBC. L'absence de confidentialité native est compensable par des solutions applicatives (chiffrement des données avant soumission on-chain) ou par l'utilisation de zones confidentielles (Secret Network, zone Cosmos).
### 4.5 Secret Network
**Principe** : Secret Network est une blockchain basée sur Cosmos utilisant des enclaves TEE Intel SGX pour l'exécution confidentielle des smart contracts. Les inputs des transactions, les états des smart contracts, et les outputs sont chiffrés pour les validateurs eux-mêmes : seule la logique du contrat est publique. Les développeurs écrivent des "secret contracts" en CosmWasm avec des annotations de confidentialité.
**Forces** :
- Smart contracts confidentiels natifs : aucun validateur ne peut accéder aux données en clair
- Compatibilité Cosmos : interopérabilité via IBC avec l'écosystème Cosmos
- Cas d'usage variés : DeFi privé, DAO confidentielles, bridges confidentiels
- Finalité Tendermint (~6 secondes)
**Faiblesses** :
- Dépendance critique à Intel SGX : vulnérabilités de canal latéral potentielles [Costan & Devadas, 2016]
- Pas de garantie de confidentialité si SGX est compromis
- Complexité de développement supérieure aux smart contracts standard
- Écosystème plus petit que Ethereum ou Cosmos
**Compatibilité GARDEN** : Bon candidat pour le règlement inter-consortium confidentiel dans la proposition. La dépendance à Intel SGX constitue une limitation pour les nœuds embarqués spatiaux dont la chaîne d'approvisionnement matérielle peut être compromise ou dont la disponibilité de matériel certifié SGX est incertaine.
### 4.6 Zcash (Ben-Sasson et al., 2014 ; Hopwood et al., 2016)
**Principe** : Zcash est une blockchain de paiements confidentiels utilisant les zk-SNARKs (Zero-Knowledge Succinct Non-interactive ARguments of Knowledge) pour masquer cryptographiquement les montants transférés et les adresses des parties dans les transactions "shielded". Contrairement aux solutions TEE, la confidentialité repose uniquement sur des primitives cryptographiques, sans dépendance matérielle.
**Forces** :
- Confidentialité cryptographique pure, sans dépendance matérielle
- zk-SNARKs vérifiables par n'importe quel nœud en O(1) de calcul
- Technologie ZK mature, référence académique et industrielle
- Pas de PoW sur la nouvelle architecture (Sapling, Orchard)
**Faiblesses** :
- Pas de smart contracts complexes : le langage Script de Zcash est très limité
- Les pools shielded sont sous-utilisés (majorité des transactions non-shielded), réduisant l'anonymat par effet d'ensemble
- Gouvernance fortement centralisée par l'Electric Coin Company
- Pas adapté pour automatiser le règlement conditionnel d'un marché de ressources
**Compatibilité GARDEN** : Excellent pour les paiements confidentiels simples entre parties connues. L'absence de smart contracts expressifs limite l'automatisation du règlement dans un marché de ressources complexe. Pertinent comme couche de paiement pour des cas d'usage spécifiques ne requérant pas de logique contractuelle.
### 4.7 Oasis Network (Oasis Labs, 2020)
**Principe** : Oasis Network combine TEE et blockchain en proposant des "ParaTimes" — des environnements d'exécution parallèles avec des niveaux de confidentialité configurables. Le Sapphire ParaTime offre un EVM confidentiel (les états des smart contracts sont chiffrés dans SGX), permettant d'exécuter des smart contracts Solidity avec confidentialité des données internes.
**Forces** :
- EVM confidentiel : compatibilité avec l'écosystème Ethereum + confidentialité
- Architecture modulaire (multiple ParaTimes avec différents niveaux de sécurité)
- Compatibilité Cosmos-adjacent
- Séparation consensus et exécution : scalabilité accrue
**Faiblesses** :
- Dépendance à Intel SGX pour les ParaTimes confidentiels
- Écosystème plus jeune que Secret Network ou Ethereum
- Complexité d'architecture (multiple ParaTimes à comprendre et gérer)
**Compatibilité GARDEN** : Bon compromis entre smart contracts expressifs (EVM) et confidentialité (TEE). La dépendance SGX est la même limitation que pour Secret Network. La compatibilité EVM facilite le portage de smart contracts existants.
---
## 5. Tableau de Comparaison Synthétique
| Système | Décentralisation | Confidentialité | Sybil-résistance | Tolérance DTN | Finalité | Smart contracts | Séparation découverte/règlement | Consortium | Maturité | Embarqué |
|---|---|---|---|---|---|---|---|---|---|---|
| IPFS / Libp2p | ✓✓ | ✗ | ✗ | △ | N/A | N/A | ✗ (découverte seule) | ✗ | ✓✓ | △ |
| Filecoin | ✓✓ | ✗ | △ | ✗ | △ | △ | △ (discovery + storage couplés) | ✗ | ✓ | ✗ |
| BitTorrent DHT | ✓✓ | ✗ | ✗ | △ | N/A | N/A | ✗ (découverte seule) | ✗ | ✓✓ | △ |
| Tor | ✓ | ✓✓ | △ | ✗ | N/A | N/A | N/A (transport uniquement) | ✗ | ✓✓ | ✗ |
| I2P | ✓✓ | ✓✓ | △ | ✗ | N/A | N/A | N/A (transport uniquement) | ✗ | ✓ | ✗ |
| Golem | ✓ | ✗ | △ | ✗ | △ | △ | ✗ (couplé Ethereum) | ✗ | ✓ | ✗ |
| Akash Network | ✓ | ✗ | △ | ✗ | ✓✓ | △ | ✗ (couplé Cosmos) | △ | ✓ | △ |
| iExec | ✓ | ✓ (TEE) | △ | ✗ | △ | △ | ✗ (couplé Ethereum) | ✗ | ✓ | ✗ |
| Ocean Protocol | ✓ | △ | △ | ✗ | △ | △ | ✗ (couplé Ethereum) | ✗ | ✓ | ✗ |
| Bitcoin | ✓✓ | ✗ | ✓✓ | ✗ | ✗ | ✗ | N/A (règlement seul) | ✗ | ✓✓ | ✗ |
| Ethereum L1 | ✓✓ | ✗ | ✓ | ✗ | △ | ✓✓ | N/A (règlement seul) | ✗ | ✓✓ | ✗ |
| Ethereum L2 ZK | ✓ | ✓ | ✓ | ✗ | △ | ✓ | N/A (règlement seul) | ✗ | △ | ✗ |
| Hyperledger Fabric | △ | ✓✓ | ✓✓ | ✗ | ✓✓ | ✓✓ | N/A (règlement seul) | ✓✓ | ✓✓ | △ |
| Cosmos SDK + IBC | ✓✓ | △ | ✓ | ✗ | ✓✓ | ✓ | N/A (règlement seul) | ✓✓ | ✓✓ | △ |
| Secret Network | ✓ | ✓✓ | ✓ | ✗ | ✓✓ | ✓✓ | N/A (règlement seul) | ✓ | ✓ | △ |
| Zcash | ✓ | ✓✓ | ✓ | ✗ | △ | ✗ | N/A (règlement seul) | ✗ | ✓✓ | ✗ |
| Oasis Network | ✓ | ✓✓ | ✓ | ✗ | ✓✓ | ✓✓ | N/A (règlement seul) | △ | ✓ | △ |
| **Proposition** | **✓✓** | **✓✓** | **✓** | **✓✓** | **✓✓** | **✓✓** | **✓✓ (3 plans distincts)** | **✓✓** | **△** | **✓✓** |
*Notation : ✓✓ excellent, ✓ bon, △ partiel ou conditionnel, ✗ insuffisant, N/A non applicable*
> **Lecture de la colonne "Séparation découverte/règlement"** : Les systèmes notés N/A n'implémentent qu'une seule des deux fonctions (découverte ou règlement), sans prétendre à l'autre. Les systèmes notés ✗ couplent les deux dans un même protocole ou une même chaîne, créant des compromis défavorables. Seule la proposition formalise explicitement la séparation en trois plans indépendants avec des profils CAP distincts.
---
## 6. Positionnement de la Proposition par Rapport à l'Existant
### 6.1 L'Absence d'une Solution Intégrée dans l'État de l'Art
L'analyse comparative révèle une lacune structurelle dans l'état de l'art : **aucun système existant ne combine simultanément l'ensemble des propriétés requises par le contexte GARDEN**. Les systèmes actuels se spécialisent sur un sous-ensemble de propriétés, laissant des gaps critiques selon leur domaine de conception.
Les systèmes de découverte P2P (IPFS, libp2p, BitTorrent DHT) excellent en décentralisation et en scalabilité mais ignorent la confidentialité et la gestion de confiance. Les réseaux d'anonymisation (Tor, I2P) offrent une excellente confidentialité de transport mais ne fournissent ni découverte décentralisée, ni transactions monétaires, ni tolérance DTN. Les blockchains de consortium (Hyperledger Fabric) offrent confidentialité intra-consortium et finalité déterministe mais supposent une connectivité continue et ne s'intègrent pas avec une couche de découverte P2P. Les blockchains confidentielles (Secret Network, Oasis) offrent des smart contracts confidentiels mais présentent des dépendances matérielles problématiques et ne traitent pas la couche de découverte.
### 6.2 La Proposition comme Synthèse Architecturale
La proposition se distingue de tous les systèmes existants par sa nature de **synthèse architecturale multi-couche**, combinant :
1. **Couche de découverte** : DHT Kademlia privée (inspiration libp2p) + scoring comportemental multidimensionnel (absent de tous les systèmes existants) + adaptation DTN (absent de tous les systèmes existants) + gossip épidémique SWIM-adjacent [Das et al., 2002].
2. **Couche de données** : enregistrements signés + chiffrement de contenu bout-en-bout + attestations ZK (inspiration Filecoin PoSt, mais avec confidentialité cryptographique) + Compute-to-Data (inspiration Ocean Protocol, mais avec preuves ZK).
3. **Couche de règlement** : architecture hybride intra/inter-consortium (Hyperledger Fabric + Secret Network / Cosmos + Secret Network) + gestion de consortiums à niveaux de confiance différenciés (absent de tous les systèmes existants comme propriété native).
### 6.3 La Tolérance DTN : Le Gap le Plus Universel
La tolérance aux réseaux DTN est la propriété la plus universellement absente des systèmes existants. Tous les systèmes analysés supposent implicitement ou explicitement une connectivité TCP continue :
- Les DHT Kademlia supposent une disponibilité suffisante des pairs pour maintenir les tables de routage
- Les blockchains supposent une connectivité entre validateurs pour atteindre le quorum de consensus
- Les systèmes de marketplace supposent la disponibilité en temps quasi-réel des parties contractantes
La proposition adresse ce gap en adaptant chaque couche aux contraintes DTN : TTL adaptatifs, stockage-et-retransmission, signature différée, heartbeats asynchrones accumulés. Cette adaptation systématique est, à notre connaissance, absente de tout système de marché de ressources décentralisé existant.
---
## Références Bibliographiques
**[Androulaki et al., 2018]** Androulaki, E., et al. (2018). Hyperledger Fabric: a distributed operating system for permissioned blockchains. In *Proceedings of the 13th EuroSys Conference*, article 30.
**[Ben-Sasson et al., 2014]** Ben-Sasson, E., Chiesa, A., Garman, C., Green, M., Miers, I., Tromer, E., & Virza, M. (2014). Zerocash: Decentralized Anonymous Payments from Bitcoin. In *Proceedings of the 2014 IEEE S&P*, pp. 459474.
**[Benet, 2014]** Benet, J. (2014). IPFS - Content Addressed, Versioned, P2P File System. *arXiv preprint* arXiv:1407.3561.
**[Brewer, 2000]** Brewer, E. A. (2000). Towards robust distributed systems. In *Proceedings of PODC 2000*, pp. 7.
**[Buterin, 2014]** Buterin, V. (2014). *A Next-Generation Smart Contract and Decentralized Application Platform*. Ethereum Whitepaper. https://ethereum.org/whitepaper
**[Cerf et al., 2007]** Cerf, V., et al. (2007). Delay-Tolerant Networking Architecture. *RFC 4838*, IETF.
**[Costan & Devadas, 2016]** Costan, V., & Devadas, S. (2016). Intel SGX Explained. *IACR Cryptology ePrint Archive*, Report 2016/086.
**[Daian et al., 2020]** Daian, P., et al. (2020). Flash Boys 2.0. In *Proceedings of the 2020 IEEE S&P*, pp. 910927.
**[Das et al., 2002]** Das, A., Gupta, I., & Motivala, A. (2002). SWIM: Scalable Weakly-consistent Infection-style Process Group Membership Protocol. In *Proceedings of DSN 2002*, pp. 303312.
**[Dingledine, Mathewson & Syverson, 2004]** Dingledine, R., Mathewson, N., & Syverson, P. (2004). Tor: The Second-Generation Onion Router. In *Proceedings of the 13th USENIX Security Symposium*, pp. 303320.
**[Douceur, 2002]** Douceur, J. R. (2002). The Sybil Attack. In *Proceedings of IPTPS 2002*, LNCS 2429, pp. 251260.
**[Fall, 2003]** Fall, K. (2003). A delay-tolerant network architecture for challenged internets. In *Proceedings of SIGCOMM 2003*, pp. 2734.
**[Fischer, Lynch & Paterson, 1985]** Fischer, M. J., Lynch, N. A., & Paterson, M. S. (1985). Impossibility of distributed consensus with one faulty process. *JACM*, 32(2), 374382.
**[Gilbert & Lynch, 2002]** Gilbert, S., & Lynch, N. (2002). Brewer's conjecture and the feasibility of consistent, available, partition-tolerant web services. *ACM SIGACT News*, 33(2), 5159.
**[Goldwasser, Micali & Rackoff, 1989]** Goldwasser, S., Micali, S., & Rackoff, C. (1989). The knowledge complexity of interactive proof systems. *SIAM Journal on Computing*, 18(1), 186208.
**[Golem Network, 2016]** Golem Network (2016). *Golem: A decentralized computation network*. Whitepaper. https://golem.network/golem_whitepaper.pdf
**[Groth, 2016]** Groth, J. (2016). On the size of pairing-based non-interactive arguments. In *Proceedings of EUROCRYPT 2016*, LNCS 9666, pp. 305326.
**[Heilman et al., 2015]** Heilman, E., Kendler, A., Zohar, A., & Goldberg, S. (2015). Eclipse Attacks on Bitcoin's Peer-to-Peer Network. In *Proceedings of the 24th USENIX Security Symposium*, pp. 129144.
**[Hopwood et al., 2016]** Hopwood, D., Bowe, S., Hornby, T., & Wilcox, N. (2016). *Zcash Protocol Specification*. Electric Coin Company.
**[Kwon & Buchman, 2016]** Kwon, J., & Buchman, E. (2016). *Cosmos: A Network of Distributed Ledgers*. Whitepaper.
**[Lamport, Shostak & Pease, 1982]** Lamport, L., Shostak, R., & Pease, M. (1982). The Byzantine Generals Problem. *ACM TOPLAS*, 4(3), 382401.
**[Loewenstern & Norberg, 2008]** Loewenstern, A., & Norberg, A. (2008). *DHT Protocol (BEP 5)*. BitTorrent Enhancement Proposal.
**[Luu et al., 2016]** Luu, L., Chu, D.-H., Olickel, H., Saxena, P., & Hobor, A. (2016). Making Smart Contracts Smarter. In *Proceedings of CCS 2016*, pp. 254269.
**[Maymounkov & Mazières, 2002]** Maymounkov, P., & Mazières, D. (2002). Kademlia: A Peer-to-Peer Information System Based on the XOR Metric. In *Proceedings of IPTPS 2002*, LNCS 2429, pp. 5365.
**[Meiklejohn et al., 2013]** Meiklejohn, S., et al. (2013). A Fistful of Bitcoins. In *Proceedings of IMC 2013*, pp. 127140.
**[Murdoch & Danezis, 2005]** Murdoch, S. J., & Danezis, G. (2005). Low-Cost Traffic Analysis of Tor. In *Proceedings of the 2005 IEEE S&P*, pp. 183195.
**[Nakamoto, 2008]** Nakamoto, S. (2008). Bitcoin: A Peer-to-Peer Electronic Cash System. https://bitcoin.org/bitcoin.pdf
**[Oasis Labs, 2020]** Oasis Network (2020). *Oasis Network Primer*. https://oasisprotocol.org/primer
**[Perrin, 2018]** Perrin, T. (2018). *The Noise Protocol Framework*. https://noiseprotocol.org/noise.pdf
**[Protocol Labs, 2017]** Protocol Labs (2017). *Filecoin: A Decentralized Storage Network*. Whitepaper.
**[Stoica et al., 2003]** Stoica, I., et al. (2003). Chord: A scalable peer-to-peer lookup protocol for internet applications. *IEEE/ACM Transactions on Networking*, 11(1), 1732.
**[Sun et al., 2017]** Sun, S.-F., Au, M. H., Liu, J. K., & Yuen, T. H. (2017). RingCT 2.0. In *Proceedings of ESORICS 2017*, LNCS 10493, pp. 456474.
**[Szabo, 1997]** Szabo, N. (1997). Formalizing and securing relationships on public networks. *First Monday*, 2(9).
**[W3C, 2022]** W3C (2022). *Decentralized Identifiers (DIDs) v1.0*. https://www.w3.org/TR/did-core/
**[Wood, 2014]** Wood, G. (2014). *Ethereum: A Secure Decentralised Generalised Transaction Ledger*. Ethereum Yellow Paper.
**[Wood, 2016]** Wood, G. (2016). *Polkadot: Vision for a Heterogeneous Multi-Chain Framework*. Whitepaper.

File diff suppressed because it is too large Load Diff

View File

@@ -1,362 +0,0 @@
================================================================================
OC-DISCOVERY : ARCHITECTURE CIBLE — RÉSEAU DHT SANS NATIFS
Vision d'évolution long terme, issue d'une analyse comparative
================================================================================
Rédigé à partir de l'analyse de l'architecture actuelle et de la discussion
comparative avec Tapestry, Kademlia, EigenTrust et les systèmes de réputation
distribués.
Référence : DECENTRALIZED_SYSTEMS_COMPARISON.txt §9
================================================================================
1. MOTIVATION
================================================================================
L'architecture actuelle (node → indexer → native indexer) est robuste et bien
adaptée à une phase précoce du réseau. Ses limites à l'échelle sont :
- Pool de natives statique au démarrage → dépendance à la configuration
- Cache local des natives = point de défaillance unique (perte = pool vide)
- Consensus inter-natives bloquant (~7s) déclenché à chaque bootstrap node
- État O(N indexers) par native → croît linéairement avec le réseau
- Nœuds privilégiés structurellement → SPOFs relatifs
La cible décrite ici supprime la notion de native indexer en tant que tier
architectural. Le réseau devient plat : indexers et nodes sont des acteurs
de même nature, différenciés uniquement par leur rôle volontaire.
================================================================================
2. PRINCIPES FONDAMENTAUX
================================================================================
P1. Aucun nœud n'est structurellement privilégié.
P2. La confiance est un produit du temps et de la vérification, pas d'un arbitre.
P3. Les claims d'un acteur sont vérifiables indépendamment par tout pair.
P4. La réputation émerge du comportement collectif, pas d'un signalement central.
P5. La DHT est une infrastructure neutre — elle stocke des faits, pas des jugements.
P6. La configuration statique n'existe plus au runtime — seulement au bootstrap.
================================================================================
3. RÔLES
================================================================================
3.1 Node
--------
Consommateur du réseau. Démarre, sélectionne un pool d'indexers via DHT,
heartbeat ses indexers, accumule des scores localement. Ne publie rien en
routine. Participe aux challenges de consensus à la demande.
3.2 Indexer
-----------
Acteur volontaire. S'inscrit dans la DHT à la naissance, maintient son record,
sert le trafic des nodes (heartbeat, Publish, Get). Déclare ses métriques dans
chaque réponse heartbeat. Maintient un score agrégé depuis ses nodes connectés.
Différence avec l'actuel : l'indexer n'a plus de lien avec une native.
Il est autonome. Son existence dans le réseau est prouvée par son record DHT
et par les nodes qui le contactent directement.
3.3 Nœud DHT infrastructure (ex-native)
----------------------------------------
N'importe quel nœud suffisamment stable peut maintenir la DHT sans être un
indexer. C'est une configuration, pas un type architectural : `dht_mode: server`.
Ces nœuds maintiennent les k-buckets Kademlia et stockent les records des
indexers. Ils ne connaissent pas le trafic node↔indexer et ne l'orchestrent pas.
================================================================================
4. BOOTSTRAP D'UN NODE
================================================================================
4.1 Entrée dans le réseau
-------------------------
Le node démarre avec 1 à 3 adresses de nœuds DHT connus (bootstrap peers).
Ce sont les seules informations statiques nécessaires. Ces peers n'ont pas de
rôle sémantique — ils servent uniquement à entrer dans l'overlay DHT.
4.2 Découverte du pool d'indexers
----------------------------------
Node → DHT.FindProviders(hash("/opencloud/indexers"))
→ reçoit une liste de N candidats avec leurs records
Sélection du pool initial :
1. Filtre latence : ping < seuil → proximité réseau réelle
2. Filtre fill rate : préférer les indexers moins chargés
3. Tirage pondéré : probabilité ∝ (1 - fill_rate), courbe w(F) = F×(1-F)
indexer à 20% charge → très probable
indexer à 80% charge → peu probable
4. Filtre diversité : subnet /24 différent pour chaque entrée du pool
Aucun consensus nécessaire à cette étape. Le node démarre avec une tolérance
basse (voir §7) — il accepte des indexers imparfaits et les évalue au fil du temps.
================================================================================
5. REGISTRATION D'UN INDEXER DANS LA DHT
================================================================================
À la naissance, l'indexer publie son record DHT :
clé : hash("/opencloud/indexers") ← clé fixe, connue de tous
valeur: {
multiaddr : <adresse réseau>,
region : <subnet /24>,
capacity : <maxNodesConn>,
fill_rate : <float 0-1>, ← auto-déclaré, vérifiable
peer_count : <int>, ← auto-déclaré, vérifiable
peers : [hash(nodeID1), ...], ← liste hashée des nodes connectés
born_at : <timestamp>,
sig : <signature clé indexer>, ← non-forgeable (PSK context)
}
Le record est rafraîchi toutes les ~60s (avant expiration du TTL).
Si l'indexer tombe : TTL expire → disparaît de la DHT automatiquement.
La peer list est hashée pour la confidentialité mais reste vérifiable :
un challenger peut demander directement à un node s'il est connecté à cet indexer.
================================================================================
6. PROTOCOLE HEARTBEAT — QUESTION ET RÉPONSE
================================================================================
Le heartbeat devient bidirectionnel : le node pose des questions, l'indexer
répond avec ses déclarations courantes.
6.1 Structure
-------------
Node → Indexer :
{
ts : now,
challenge : <optionnel, voir §8>
}
Indexer → Node :
{
ts : now,
fill_rate : 0.42,
peer_count : 87,
cached_score : 0.74, ← score agrégé depuis tous ses nodes connectés
challenge_response : {...} ← si challenge présent dans la requête
}
Le heartbeat normal (sans challenge) est quasi-identique à l'actuel en poids.
Le cached_score indexer est mis à jour progressivement par les feedbacks reçus.
6.2 Le cached_score de l'indexer
---------------------------------
L'indexer agrège les scores que ses nodes connectés lui communiquent
(implicitement via le fait qu'ils restent connectés, ou explicitement lors
d'un consensus). Ce score lui donne une vision de sa propre qualité réseau.
Un node peut comparer son score local de l'indexer avec le cached_score déclaré.
Une forte divergence est un signal d'alerte.
Score local node : 0.40 ← cet indexer est médiocre pour moi
Cached score : 0.91 ← il se prétend excellent globalement
→ déclenche un challenge de vérification
================================================================================
7. MODÈLE DE CONFIANCE PROGRESSIVE
================================================================================
7.1 Cycle de vie d'un node
---------------------------
Naissance
→ tolérance basse : accepte presque n'importe quel indexer du DHT
→ switching cost faible : peu de contexte accumulé
→ minScore ≈ 20% (dynamicMinScore existant, conservé)
Quelques heures
→ uptime s'accumule sur chaque indexer connu
→ scores se stabilisent
→ seuil de remplacement qui monte progressivement
Long terme (jours)
→ pool stable, confiance élevée sur les indexers connus
→ switching coûteux mais déclenché sur déception franche
→ minScore ≈ 80% (maturité)
7.2 Modèle sous-jacent : beta distribution implicite
------------------------------------------------------
α = succès cumulés (heartbeats OK, probes OK, challenges réussis)
β = échecs cumulés (timeouts, probes échoués, challenges ratés)
confiance = α / (α + β)
Nouveau indexer : α=0, β=0 → prior neutre, tolérance basse
Après 10 jours : α élevé → confiance stable, seuil de switch élevé
Déception franche : β monte → confiance chute → switch déclenché
7.3 Ce que "décevoir" signifie
--------------------------------
Heartbeat rate → trop de timeouts → fiabilité en baisse
Bandwidth probe → chute sous déclaré → dégradation ou mensonge
Fill rate réel → supérieur au déclaré → indexer surchargé ou malhonnête
Challenge échoué → peer déclaré absent du réseau → claim invalide
Latence → dérive progressive → qualité réseau dégradée
Cached_score gonflé → divergence forte avec score local → suspicion
================================================================================
8. VÉRIFICATION DES CLAIMS — TROIS COUCHES
================================================================================
8.1 Couche 1 : passive (chaque heartbeat, 60s)
-----------------------------------------------
Mesures automatiques, zéro coût supplémentaire.
- RTT du heartbeat → latence directe
- fill_rate déclaré → tiny payload dans la réponse
- peer_count déclaré → tiny payload
- cached_score indexer → comparé au score local
8.2 Couche 2 : sampling actif (1 heartbeat sur N)
--------------------------------------------------
Vérifications périodiques, asynchrones, légères.
Tous les 5 HB (~5min) : spot-check 1 peer aléatoire (voir §8.4)
Tous les 10 HB (~10min): vérification diversité subnet (lookups DHT légers)
Tous les 15 HB (~15min): bandwidth probe (transfert réel, protocole dédié)
8.3 Couche 3 : consensus (événementiel)
-----------------------------------------
Déclenché sur : admission d'un nouvel indexer dans le pool, ou suspicion détectée.
Node sélectionne une claim vérifiable de l'indexer cible X
Node vérifie lui-même
Node demande à ses indexers de confiance : "vérifiez cette claim sur X"
Chaque indexer vérifie indépendamment
Convergence des résultats → X est honnête → admission
Divergence → X est suspect → rejet ou probation
Le consensus est léger : quelques contacts out-of-band, pas de round bloquant.
Il n'est pas continu — il est événementiel.
8.4 Vérification out-of-band (pas de DHT writes par les nodes)
----------------------------------------------------------------
Les nodes ne publient PAS de contact records continus dans la DHT.
Cela éviterait N×M records à rafraîchir (coût DHT élevé à l'échelle).
À la place, lors d'un challenge :
Challenger sélectionne 2-3 peers dans la peer list déclarée par X
→ contacte ces peers directement : "es-tu connecté à indexer X ?"
→ réponse directe (out-of-band, pas via DHT)
→ vérification sans écriture DHT
L'indexer ne peut pas faire répondre "oui" à des peers qui ne lui sont pas
connectés. La vérification est non-falsifiable et sans coût DHT.
8.5 Pourquoi X ne peut pas tricher
------------------------------------
X ne peut pas coordonner des réponses différentes vers des challengers
simultanés. Chaque challenger contacte indépendamment les mêmes peers.
Si X ment sur sa peer list :
- Challenger A contacte peer P → "non, pas connecté à X"
- Challenger B contacte peer P → "non, pas connecté à X"
- Consensus : X ment → score chute chez tous les challengers
- Effet réseau : progressivement, X perd ses connections
- Peer list DHT se vide → claims futures encore moins crédibles
================================================================================
9. EFFET RÉSEAU SANS SIGNALEMENT CENTRAL
================================================================================
Un node qui pénalise un indexer n'envoie aucun "rapport" à quiconque.
Ses actions locales produisent l'effet réseau par agrégation :
Node baisse le score de X → X reçoit moins de trafic de ce node
Node switche vers Y → X perd un client
Node refuse les challenges X → X ne peut plus participer aux consensus
Si 200 nodes font pareil :
X perd la majorité de ses connections
Sa peer list DHT se vide (peers contactés directement disent "non")
Son cached_score s'effondre (peu de nodes restent)
Les nouveaux nodes qui voient X dans la DHT obtiennent des challenges échoués
X est naturellement exclu sans aucune décision centrale
Inversement, un indexer honnête voit ses scores monter sur tous ses nodes
connectés, sa peer list se densifier, ses challenges réussis systématiquement.
Sa réputation est un produit observable et vérifiable.
================================================================================
10. RÉSUMÉ DE L'ARCHITECTURE
================================================================================
DHT → annuaire neutre, vérité des records indexers
maintenu par tout nœud stable (dht_mode: server)
Indexer → acteur volontaire, s'inscrit, maintient ses claims,
sert le trafic, accumule son propre score agrégé
Node → consommateur, score passif + sampling + consensus léger,
confiance progressive, switching adaptatif
Heartbeat → métronome 60s + vecteur de déclarations légères + challenge optionnel
Consensus → événementiel, multi-challengers indépendants,
vérification out-of-band sur claims DHT
Confiance → beta implicite, progressive, switching cost croissant avec l'âge
Réputation → émerge du comportement collectif, aucun arbitre central
Bootstrap → 1-3 peers DHT connus → seule configuration statique nécessaire
================================================================================
11. TRAJECTOIRE DE MIGRATION
================================================================================
Phase 1 (actuel)
Natives statiques, pool indexers dynamique, consensus inter-natives
→ robuste, adapté à la phase précoce
Phase 2 (intermédiaire)
Pool de natives dynamique via DHT (bootstrap + gossip)
Même protocole natif, juste la découverte devient dynamique
→ supprime la dépendance à la configuration statique des natives
→ voir DECENTRALIZED_SYSTEMS_COMPARISON.txt §9.2
Phase 3 (cible)
Architecture décrite dans ce document
Natives disparaissent en tant que tier architectural
DHT = infrastructure, indexers = acteurs autonomes
Scoring et consensus entièrement côté node
→ aucun nœud privilégié, scalabilité O(log N)
La migration Phase 2 → Phase 3 est une refonte du plan de contrôle.
Le plan de données (heartbeat node↔indexer, Publish, Get) est inchangé.
Les primitives libp2p (Kademlia DHT, GossipSub) sont déjà présentes.
================================================================================
12. PROPRIÉTÉS DU SYSTÈME CIBLE
================================================================================
Scalabilité O(log N) — routage DHT Kademlia
Résilience Pas de SPOF structurel, TTL = seule source de vérité
Confiance Progressive, vérifiable, émergente
Sybil resistance PSK — seuls les nœuds avec la clé peuvent publier
Cold start Tolérance basse initiale, montée progressive (existant)
Honnêteté Claims vérifiables out-of-band, non-falsifiables
Décentralisation Aucun nœud ne connaît l'état global complet
================================================================================

View File

@@ -0,0 +1,406 @@
# Note de Divergence : Proposition Architecturale vs État du Système de Référence
**Type** : Note de travail interne — analyse des écarts et feuille de route
**Version** : 1.0 — Mars 2026
**Relation** : Ce document est le complément opérationnel du document `PROPOSITION_ARCHITECTURE_MINIMALE.md` et de `COMPARAISON_SYSTEMES_ET_PROPOSITION.md`. Il ne constitue pas une étude scientifique indépendante mais un outil d'aide à la décision pour l'équipe de développement.
> **Convention** : Le terme "système de référence" désigne le système P2P de découverte et d'échange de ressources actuellement en développement, dont l'architecture de découverte est partiellement alignée avec la proposition mais qui n'implémente pas encore les couches de règlement monétaire, de confidentialité renforcée, ni les adaptations DTN. La "proposition" renvoie au document `PROPOSITION_ARCHITECTURE_MINIMALE.md`.
---
## Résumé Exécutif
| # | Gap | Criticité | Effort estimé | Priorité |
|---|---|---|---|---|
| G1 | Couche blockchain de règlement monétaire absente | 🔴 Critique | Élevé (612 mois) | P0 |
| G2 | Records DHT non chiffrés (confidentialité payload) | 🔴 Critique (contexte hostile) | Faible (24 semaines) | P1 |
| G3 | Absence de tolérance DTN | 🔴 Critique (contexte spatial) | Moyen (24 mois) | P1 |
| G4 | Gestion de consortiums binaire (absence du niveau 2) | 🟠 Significatif | Moyen (13 mois) | P2 |
| G5 | Résistance Sybil uniquement organisationnelle | 🟠 Significatif (réseau ouvert) | Dépend de G1 | P3 |
| G6 | Attestation ZK de consommation absente | 🟠 Significatif | Élevé (48 mois) | P2 |
| G7 | Identité DID non implémentée | 🟡 Partiel | Moyen (23 mois) | P3 |
| G8 | Scoring multidimensionnel | ✅ Convergence forte | — | — |
| G9 | Gossip / SWIM membership events | ✅ Convergence forte | — | — |
**Lecture** : Les gaps G1, G2, G3 sont bloquants pour un déploiement en contexte hostile ou spatial. G4 et G6 sont nécessaires pour la marketplace de ressources. G5 et G7 sont des améliorations importantes mais non bloquantes à court terme pour les déploiements en réseau fermé.
---
## Contexte : Deux Systèmes à Prérequis Distincts
La proposition distingue formellement deux sous-systèmes aux prérequis mutuellement incompatibles s'ils étaient fusionnés :
### Système de Découverte et d'Échange entre Pairs
**Rôle** : localiser les pairs, évaluer leur qualité, maintenir le réseau de voisinage, propager les enregistrements de ressources.
**Prérequis** :
- Haute disponibilité (profil AP du théorème CAP)
- Tolérance aux partitions et à la connectivité intermittente
- Faible latence de découverte (secondes, pas minutes)
- Cohérence éventuelle acceptable
- Protocoles légers, binaires, compacts
**Ce que le système de référence implémente** : ce sous-système est substantiellement développé. La DHT Kademlia, le scoring multidimensionnel, les heartbeats bidirectionnels, le gossip d'événements d'appartenance, et la ConnectionGater sont en place et alignés avec la proposition.
### Système de Règlement Monétaire et Transactionnel
**Rôle** : régler les transactions financières résultant de la consommation de ressources marketplace (compute, stockage, données, workflows), de manière automatisée et confidentielle.
**Prérequis** :
- Cohérence forte (profil CP — prévention double-spend)
- Finalité déterministe ou quasi-déterministe
- Confidentialité des montants et des parties
- Smart contracts pour l'automatisation du paiement conditionnel
- Gouvernance de consortium (admission, révocation de validateurs)
- Faible empreinte énergétique (PoW exclu)
**Ce que le système de référence implémente** : ce sous-système est entièrement absent. C'est le gap le plus critique.
> **Principe fondamental** : ces deux systèmes NE DOIVENT PAS partager le même protocole. La découverte optimise pour la disponibilité — la blockchain optimise pour la cohérence. Fusionner ces couches dégrade les garanties des deux.
---
## G1 — Couche Blockchain de Règlement Monétaire Absente
**Criticité** : 🔴 Critique — bloquant pour la marketplace économique
### État actuel du système de référence
Le système de référence ne dispose d'aucune couche de règlement monétaire. Il peut découvrir des ressources, coordonner leur utilisation, et propager des records de présence, mais ne peut pas :
- Automatiser le paiement entre fournisseur et consommateur de ressources
- Émettre un token ou représenter une unité de valeur
- Exécuter un contrat conditionnel à l'attestation d'une livraison de ressource
- Régler des transactions entre organisations différentes sans accord hors-bande
### Impact opérationnel
Sans couche de règlement, la marketplace de ressources (compute, storage, data, workflows en rent/buy) ne peut fonctionner qu'avec des accords commerciaux hors-bande — ce qui annule le bénéfice de la décentralisation pour l'aspect économique. Un fournisseur de compute ne peut pas être payé automatiquement à la livraison d'un résultat vérifié.
### Options de développement
**Option A — Hyperledger Fabric (recommandée pour phase 1)**
- Blockchain permissionnée, consortium fermé
- Finalité déterministe (Raft ou PBFT), canaux privés natifs
- Chaincode Go/Node.js mature, CouchDB state store
- Aucun token public requis : jeton interne au consortium
- **Avantage** : maturité maximale, documentation, écosystème d'entreprise
- **Inconvénient** : pas trustless pour les échanges inter-consortium
- **Effort** : 36 mois pour une intégration fonctionnelle
**Option B — Cosmos SDK + zone permissionnée (recommandée si interopérabilité inter-consortium est prioritaire)**
- Chaîne souveraine Cosmos, consensus Tendermint BFT (~6s finalité)
- IBC pour les échanges inter-zone (inter-consortium)
- CosmWasm pour les smart contracts
- **Avantage** : interopérabilité native avec d'autres zones Cosmos
- **Inconvénient** : pas de confidentialité native inter-zone, complexity opérationnelle plus élevée que Fabric
- **Effort** : 48 mois pour une intégration fonctionnelle
**Option C — Secret Network + Cosmos (recommandée pour inter-consortium confidentiel)**
- Smart contracts chiffrés via TEE (Intel SGX)
- Compatible Cosmos (IBC), finalité Tendermint
- **Avantage** : confidentialité des smart contracts native
- **Inconvénient** : dépendance SGX (side-channels documentés), écosystème plus restreint
- **Usage recommandé** : couche inter-consortium après phase 1 avec Fabric intra-consortium
**Option D — Oasis Network (alternative à Secret pour environnements embarqués)**
- ParaTime confidentielle + EVM (Sapphire)
- Compatible Cosmos
- **Avantage** : EVM mature + confidentialité, architecture ParaTime flexible
- **Inconvénient** : dépendance TEE, écosystème plus jeune
### Recommandation de feuille de route pour G1
```
Phase 1 (priorité immédiate) :
→ Hyperledger Fabric intra-consortium
→ Smart contracts de paiement conditionnel basiques (attestation → paiement)
→ Interface entre le système de découverte et Fabric (événements de consommation)
Phase 2 (612 mois) :
→ Cosmos SDK zone permissionnée pour l'interopérabilité inter-consortium
→ IBC configuré entre les zones des différents consortiums
Phase 3 (1224 mois) :
→ Secret Network ou Oasis pour le règlement confidentiel inter-consortium
→ ZK-proofs d'attestation de consommation (voir G6)
```
### Dépendances
- G6 (attestation ZK) dépend de G1 : impossible d'implémenter la preuve de consommation sans smart contract récepteur
- G5 (résistance Sybil cryptoéconomique) dépend de G1 : le stake on-chain requiert une blockchain
---
## G2 — Records DHT Non Chiffrés (Confidentialité du Payload)
**Criticité** : 🔴 Critique en contexte hostile — 🟡 Mineur en réseau privé fermé
### État actuel du système de référence
Les enregistrements de présence (PeerRecords) dans la DHT sont signés (authentification et intégrité garanties) mais non chiffrés. Le contenu — nom du pair, URL d'API, adresses réseau, clé publique, ressources annoncées — est lisible par tout nœud participant à la DHT ou observant le trafic réseau.
### Impact opérationnel
Dans un contexte hostile avec des watchers passifs :
- Un adversaire peut reconstruire la liste complète des participants actifs
- Les patterns d'activité (fréquence de renouvellement des records) révèlent des informations opérationnelles
- Les URLs d'API et adresses réseau permettent le ciblage d'acteurs spécifiques
- La corrélation des records dans le temps permet de reconstruire des graphes d'activité
### Options de développement
**Option A — Chiffrement symétrique par clé de consortium (recommandée)**
- La clé de déchiffrement est dérivée de la PSK du consortium
- Le payload du PeerRecord est chiffré avec AES-256-GCM avant insertion dans la DHT
- Seuls les membres partageant la PSK peuvent déchiffrer les records
- La DHT agit comme système de stockage aveugle
- **Effort** : 24 semaines (modification du format du PeerRecord + handlers d'encodage/décodage)
**Option B — Chiffrement asymétrique par liste d'accès (plus flexible, plus complexe)**
- Chaque record est chiffré avec les clés publiques des membres autorisés
- Permet un contrôle d'accès granulaire par record
- **Inconvénient** : taille des records augmente avec le nombre de destinataires, complexité opérationnelle
**Option C — Mode mixte : metadata publiques, payload chiffré**
- Seul le payload sensible est chiffré ; les métadonnées de routage restent en clair
- Compromis entre découvrabilité et confidentialité
### Recommandation
Option A pour les déploiements en contexte hostile. Activable par flag de configuration du réseau (backward-compatible avec les déploiements existants en réseau ouvert). Le chiffrement est transparent pour la couche DHT : la DHT stocke des opaque blobs sans connaître leur structure.
---
## G3 — Absence de Tolérance DTN
**Criticité** : 🔴 Critique pour le contexte spatial/embarqué
### État actuel du système de référence
Le système de référence est conçu pour une connectivité TCP continue :
- Heartbeats toutes les ~20 secondes — impossibles dans un contexte DTN
- Un pair absent pendant quelques intervalles est marqué suspect puis évincé
- Les scores d'uptime sont dégradés artificiellement pour les pairs à connectivité intermittente
- Aucun mécanisme de stockage-et-retransmission pour les messages non livrables
- Les TTL des PeerRecords (~2 minutes) sont inadaptés à des cycles de déconnexion de plusieurs heures
### Impact opérationnel en contexte spatial
Un satellite LEO passe hors de couverture sol toutes les 90 minutes. Avec les paramètres actuels :
- Chaque passage en dehors de couverture déclenche une série de faux positifs de détection de panne
- Le satellite est systématiquement évincé de tous les pools de ses pairs lors de chaque interruption
- À la reconnexion, une reconnexion complète (DHT bootstrap, re-heartbeat, re-scoring) est nécessaire — coûteuse en bande passante et en temps
### Options de développement
**Option A — Profil de connectivité par pair (recommandée, implémentation minimale)**
- Chaque pair déclare un profil de connectivité (`CONTINUOUS | INTERMITTENT | ORBITAL`)
- Les seuils de détection de panne sont paramétrés par profil
- Un pair `INTERMITTENT` peut être absent N heures sans déclencher de suspect
- **Effort** : 36 semaines (extension du PeerRecord + modification du scoring uptime)
**Option B — TTL adaptatifs selon profil orbital (complémentaire)**
- Les PeerRecords d'un pair orbital ont un TTL égal à (durée max d'absence prévisible + marge)
- Le pair renouvelle ses records juste avant chaque période de déconnexion prévue
- **Effort** : 12 semaines
**Option C — Heartbeats batch signés (pour le contexte DTN strict)**
- Pendant les périodes de déconnexion, les heartbeats sont signés localement avec timestamp
- À la reconnexion, ils sont soumis en batch avec validation de la séquence temporelle
- Permet une reconstruction de l'historique d'uptime sans connexion continue
- **Effort** : 48 semaines
**Option D — Store-and-forward pour les messages critiques**
- Les messages de mise à jour non livrables sont stockés localement et retransmis lors de la prochaine fenêtre
- Aligné avec RFC 4838 [Cerf et al., 2007]
- **Effort** : 24 mois (infrastructure significative)
### Recommandation
Implémenter A + B en priorité (effort faible, impact immédiat). C et D constituent une roadmap long terme pour un support DTN complet.
---
## G4 — Gestion de Consortiums Binaire (Absence du Niveau 2)
**Criticité** : 🟠 Significatif pour les coalitions multi-organisations
### État actuel du système de référence
Le système de référence implémente un modèle binaire : une PSK est présente ou absente. Un pair soit appartient au consortium (PSK correcte → confiance élevée), soit est un inconnu (pas de PSK → confiance nulle). Il n'existe pas de niveau intermédiaire formalisé pour les pairs d'organisations alliées qui ne partageraient pas la PSK principale.
### Impact opérationnel
Dans une coalition GARDEN multi-organisationnelle :
- Deux organisations alliées souhaitant interopérer doivent soit partager leur PSK principale (sécurité dégradée), soit se traiter mutuellement comme des inconnus (fonctionnalité dégradée)
- Aucun mécanisme ne permet de dire "ce pair est de confiance modérée, il peut consommer des ressources publiques mais pas les ressources critiques"
### Option de développement
**Attestation de niveau 2 (recommandée)**
- Un pair de niveau 1 (PSK) émet une attestation signée pour un pair externe
- L'attestation contient : clé publique du pair, période de validité, droits d'accès (périmètre restreint)
- Les pairs recevant une attestation valide signée par un membre de niveau 1 leur accordent un score initial de 40/100 (niveau 2)
- Les attestations sont révocables via tombstone signé
- **Effort** : 48 semaines (nouveau type de record + handlers d'admission)
---
## G5 — Résistance Sybil Uniquement Organisationnelle
**Criticité** : 🟠 Significatif pour les déploiements en réseau ouvert ou hostile — 🟢 Acceptable pour les consortiums fermés
### État actuel du système de référence
La PSK organisationnelle constitue une barrière d'entrée sociale efficace pour les réseaux fermés. Elle n'est pas cryptoéconomique (pas de coût financier d'entrée) mais organisationnelle (la PSK doit être obtenue auprès d'un membre existant).
### Impact
Pour les déploiements en réseau fermé (consortium PSK), ce gap n'est pas critique. Pour les déploiements en réseau ouvert ou dans des contextes où la PSK pourrait être compromise/partagée massivement, la résistance Sybil devient insuffisante.
### Option de développement
- **Stake on-chain** (dépend de G1) : exiger un dépôt de tokens sur la blockchain comme condition d'admission. Coûteux à implémenter sans G1.
- **Proof of Work sur le NodeID** (S/Kademlia style [Baumgart & Meinert, 2007]) : coût computationnel modéré à la création d'identité. Indépendant de G1. **Effort** : 23 semaines.
---
## G6 — Attestation ZK de Consommation de Ressource Absente
**Criticité** : 🟠 Significatif — nécessaire pour un marché de ressources décentralisé crédible
### État actuel du système de référence
Aucun mécanisme ne permet de prouver cryptographiquement qu'une ressource a été réellement consommée (compute exécuté, données livrées, stockage maintenu) sans révéler le contenu ni l'identité de l'initiateur. Le règlement des transactions repose entièrement sur la confiance organisationnelle.
### Options de développement
**Option A — TEE attestations (Intel SGX / ARM TrustZone)**
- Le nœud fournisseur exécute le workload dans une enclave sécurisée
- L'enclave génère une attestation signée par le microprocesseur prouvant que le code s'est exécuté dans un environnement sécurisé
- L'attestation est soumise au smart contract comme preuve de livraison
- **Avantage** : relativement mature (iExec utilise cette approche)
- **Inconvénient** : dépendance matérielle, vulnérabilités SGX documentées [Costan & Devadas, 2016]
- **Effort** : 36 mois
**Option B — ZK-proof d'exécution (approche cryptographique pure)**
- Un ZKP prouve la bonne exécution d'un programme sur des entrées données
- Pas de dépendance matérielle — sécurité cryptographique pure
- **Inconvénient** : génération du proof encore coûteuse pour des programmes complexes (zkEVM, zkVM)
- **Horizon** : viable à moyen terme (23 ans) pour des workloads standards
**Option C — Challenge-response statistique (approche hybride à court terme)**
- Le consommateur soumet un échantillon aléatoire de résultats intermédiaires vérifiables
- Le fournisseur prouve avoir exécuté le calcul en répondant correctement aux challenges
- Moins fort cryptographiquement mais praticable immédiatement
- **Effort** : 46 semaines
### Recommandation
Option C comme solution court terme (praticable sans G1 finalisé). Option A comme solution moyen terme (dépend de la disponibilité TEE sur les nœuds cibles). Option B comme horizon long terme.
---
## G7 — Identité DID Non Implémentée
**Criticité** : 🟡 Partiel — identités auto-certifiées présentes, DID formelles absentes
### État actuel du système de référence
Le système de référence utilise des identités auto-certifiées (PeerID = hash de la clé publique Ed25519) — aligné avec le principe de base de la proposition. Cependant, les DID W3C [W3C, 2022] formelles ne sont pas implémentées, ce qui limite l'interopérabilité avec les écosystèmes tiers qui utilisent ce standard.
### Impact
Pour les déploiements intra-consortium, ce gap est mineur. Pour l'interopérabilité avec des systèmes tiers (identity federations, portabilité des identités entre déploiements), l'absence de DID formelles est un frein.
### Option de développement
- Wrapper DID W3C sur les identités PeerID existantes : publier les clés publiques des pairs sous format DID document dans la DHT. **Effort** : 24 semaines.
---
## G8 et G9 — Points de Convergence Forts
### G8 — Scoring Multidimensionnel ✅
Le système de référence implémente un scoring à 7+ dimensions aligné avec la proposition :
- Ratio d'uptime (gap-aware : les absences courtes ne pénalisent pas)
- Latence normalisée (probe RTT)
- Précision des challenges (bandwidth + identity challenges)
- Diversité réseau (/24 subnet diversity)
- Taux de remplissage
- Cohérence des témoins (witness queries)
- Seuil d'éviction dynamique selon l'âge (20% → 80% sur 24h)
**Évaluation** : c'est la contribution la plus mature et la plus originale du système de référence. L'alignement avec la proposition est substantiel et constitue une base solide.
### G9 — Gossip / SWIM Membership Events ✅
Le système de référence implémente une propagation épidémique infection-style pour les événements d'appartenance :
- États suspects (SuspectedAt) avec incarnation numérotée
- Refutation automatique (un pair se défendant incrémente son incarnation)
- HopsLeft décroissant pour limiter la propagation des événements périmés
- Déduplication par (PeerID, Incarnation)
**Évaluation** : convergence forte avec les principes SWIM [Das et al., 2002]. L'implémentation est un atout majeur pour la résistance aux faux positifs de détection de panne.
---
## Matrice des Dépendances
```
G1 (blockchain)
├── G5 (stake Sybil) — G5 dépend de G1 pour la version cryptoéconomique
└── G6 (attestation ZK) — G6 (option A/B) dépend de G1 pour le smart contract récepteur
G2 (chiffrement records) — indépendant, priorité P1
G3 (DTN) — indépendant, priorité P1
G4 (consortiums niveau 2) — indépendant de G1 (attestation = records signés, pas blockchain)
G7 (DID) — indépendant
```
---
## Roadmap de Convergence Recommandée
### Phase 0 — Fondations (immédiat, effort faible)
| Action | Gap | Durée estimée |
|---|---|---|
| Chiffrement optionnel payload DHT (clé dérivée de la PSK) | G2 | 24 semaines |
| Profil de connectivité par pair (CONTINUOUS/INTERMITTENT/ORBITAL) + TTL adaptatifs | G3 (partiel) | 36 semaines |
| Mécanisme d'attestation de niveau 2 (confiance intermédiaire) | G4 | 48 semaines |
### Phase 1 — Règlement Monétaire Intra-Consortium (36 mois)
| Action | Gap | Durée estimée |
|---|---|---|
| Intégration Hyperledger Fabric | G1 | 36 mois |
| Smart contracts de paiement conditionnel basiques | G1 | Inclus |
| Interface système de découverte ↔ Fabric (événements de consommation) | G1 | Inclus |
| Challenge-response statistique pour attestation de livraison | G6 (option C) | 46 semaines |
### Phase 2 — Règlement Inter-Consortium et Confidentialité (612 mois)
| Action | Gap | Durée estimée |
|---|---|---|
| Cosmos SDK zone permissionnée pour interopérabilité inter-consortium | G1 | 48 mois |
| Heartbeats batch signés (DTN complet) | G3 (complet) | 48 semaines |
| Proof of Work sur NodeID (résistance Sybil légère) | G5 | 23 semaines |
| Wrapper DID W3C | G7 | 24 semaines |
### Phase 3 — Confidentialité Transactionnelle Avancée (1224 mois)
| Action | Gap | Durée estimée |
|---|---|---|
| Secret Network ou Oasis pour règlement confidentiel inter-consortium | G1 | 48 mois |
| TEE attestations pour preuve de consommation | G6 (option A) | 36 mois |
| Store-and-forward DTN complet (RFC 4838) | G3 (complet) | 24 mois |
---
## Synthèse
Le système de référence constitue une base solide pour le plan de découverte (G8, G9 convergents). Le scoring comportemental multidimensionnel et les mécanismes SWIM sont des contributions de qualité, correctement alignées avec la proposition.
Le gap structurel majeur est l'absence du plan de règlement monétaire (G1). Sans cette couche, le système peut coordonner l'utilisation de ressources mais ne peut pas les monétiser, limitant son périmètre à des usages intra-organisationnels sans incitation économique automatisée.
Les gaps de confidentialité (G2, G3) sont adressables à court terme avec un effort relativement faible et constituent des priorités immédiates pour les déploiements en contexte hostile ou spatial.
La feuille de route recommandée priorise : confidentialité immédiate (G2) → tolérance DTN partielle (G3) → règlement monétaire intra-consortium (G1, phase 1) → interopérabilité inter-consortium (G1, phase 2) → confidentialité transactionnelle avancée (G1, phase 3).
---
## Références
**[Baumgart & Meinert, 2007]** Baumgart, I., & Meinert, S. (2007). S/Kademlia: A practicable approach towards secure key-based routing. *ICPADS 2007*, pp. 18.
**[Cerf et al., 2007]** Cerf, V., et al. (2007). Delay-Tolerant Networking Architecture. *RFC 4838*, IETF.
**[Costan & Devadas, 2016]** Costan, V., & Devadas, S. (2016). Intel SGX Explained. *IACR Cryptology ePrint Archive*, Report 2016/086.
**[Das et al., 2002]** Das, A., Gupta, I., & Motivala, A. (2002). SWIM: Scalable Weakly-consistent Infection-style Process Group Membership Protocol. *DSN 2002*, pp. 303312.
**[Douceur, 2002]** Douceur, J. R. (2002). The Sybil Attack. *IPTPS 2002*, LNCS 2429, pp. 251260.
**[Kwon & Buchman, 2016]** Kwon, J., & Buchman, E. (2016). *Cosmos: A Network of Distributed Ledgers*. Whitepaper.
**[Oasis Labs, 2020]** Oasis Network (2020). *Oasis Network Primer*. https://oasisprotocol.org/primer
**[W3C, 2022]** W3C (2022). *Decentralized Identifiers (DIDs) v1.0*. https://www.w3.org/TR/did-core/

View File

@@ -0,0 +1,463 @@
# Proposition d'Architecture Minimale pour un Réseau Décentralisé Souverain dans un Contexte Embarqué et Hostile
**Catégorie** : Architecture des systèmes distribués — Proposition de conception
**Domaine d'application** : Systèmes embarqués, contexte spatial, réseaux DTN, environnements hostiles
**Version** : 1.0 — Mars 2026
---
## Résumé
Ce document présente une proposition d'architecture minimale pour un réseau distribué décentralisé répondant aux exigences de souveraineté, de confidentialité, et de tolérance aux disruptions réseau dans des environnements hostiles tels que les systèmes spatiaux embarqués ou les réseaux opérant en présence d'adversaires actifs. Le contexte cible — désigné GARDEN (Generic Architecture for Resilient Decentralized Execution Networks) — impose des contraintes qui rendent inadaptée toute solution standard : connectivité intermittente (paradigme DTN [Fall, 2003 ; Cerf et al., 2007]), présence d'acteurs à niveaux de confiance hétérogènes incluant des adversaires d'État, contraintes énergétiques et de bande passante sévères, et exigence de souveraineté absolue des données sur les acteurs externes.
L'architecture proposée repose sur une séparation stricte en trois plans indépendants : le plan de découverte de pairs, le plan de transactions de ressources, et le plan de règlement monétaire. Cette séparation n'est pas un choix stylistique mais une nécessité de sécurité : chaque plan présente des exigences de disponibilité, de cohérence et de confidentialité fondamentalement différentes, et leur fusion dans un protocole unique crée des couplages qui dégradent les garanties de sécurité de chacun.
La proposition couvre en détail les sept couches fonctionnelles de cette architecture : le transport chiffré (Noise Protocol Framework [Perrin, 2018]), la découverte DHT avec scoring comportemental multidimensionnel, la gestion des enregistrements de ressources avec chiffrement bout-en-bout, l'attestation de consommation par preuves à divulgation nulle (ZK-proofs [Groth, 2016]), le règlement monétaire par blockchain confidentielle (analyse comparative de dix solutions), et la gouvernance des consortiums à confiance relative.
Huit verrous conceptuels et technologiques sont identifiés et analysés : identité sans autorité centrale (TOFU), bootstrap trustless, résistance Sybil sans coût cryptoéconomique, cohérence CAP dans un réseau AP, détection de panne sans oracle (FLP), confidentialité des transactions on-chain, transactions intermittentes en contexte DTN, et oracle problem pour les preuves de consommation réelle. Pour chaque verrou, l'état de l'art des solutions disponibles est présenté avec leurs limites résiduelles.
---
## 1. Principes Directeurs Non-Négociables
Les principes suivants constituent les invariants de l'architecture proposée. Tout compromis sur ces principes dégrade structurellement la sécurité du système dans le contexte hostile cible.
### Principe 1 — Souveraineté par Défaut
Aucune donnée ne doit quitter le périmètre de confiance d'un acteur sans son consentement explicite. Ce principe s'applique à toutes les couches : les enregistrements de présence dans la DHT, les transactions de ressources, et le règlement monétaire. La souveraineté s'entend au sens technique (contrôle cryptographique du cycle de vie des données) et opérationnel (possibilité de révocation et d'expiration contrôlée).
### Principe 2 — Confiance Relative et Continue (Non-Binaire)
Le modèle de confiance ne peut être binaire dans un réseau hostile. Chaque pair se voit associer un score de confiance multidimensionnel calculé sur la base de comportements observés (disponibilité historique, précision des challenges, cohérence des données annoncées, diversité réseau). Ce score évolue dans le temps et détermine dynamiquement les interactions autorisées. L'impossibilité de distinguer panne et lenteur (FLP [Fischer, Lynch & Paterson, 1985]) implique que ce score est probabiliste par nature.
### Principe 3 — Disponibilité Prioritaire pour la Découverte, Cohérence Prioritaire pour le Règlement
La couche de découverte adopte délibérément le profil AP du théorème CAP [Brewer, 2000 ; Gilbert & Lynch, 2002] : elle doit rester opérationnelle même en cas de partitionnement, au prix d'une cohérence éventuellement relâchée. La couche de règlement monétaire adopte le profil CP : la cohérence forte est requise pour la prévention du double-spend, au prix d'une indisponibilité temporaire lors des partitions.
### Principe 4 — Opérabilité en Mode Complètement Autonome
Un nœud doit pouvoir opérer — prendre des décisions, gérer ses ressources locales, maintenir un état cohérent — sans aucune connexion réseau pendant des durées pouvant atteindre plusieurs jours. Les protocoles de resynchronisation après reconnexion doivent être déterministes, convergents, et capables de gérer les conflits d'état accumulés pendant la période d'isolation.
### Principe 5 — Opacité des Flux (Confidentialité des Métadonnées)
Le chiffrement de contenu est nécessaire mais insuffisant. L'architecture doit minimiser les informations révélées par les métadonnées de trafic : fréquence des échanges, volume, topologie des connexions. Des techniques de padding temporel, de trafic fictif calibré, et de routage multi-sauts doivent être employées sur les liens exposés à des observateurs potentiellement adversariaux.
### Principe 6 — Minimisation de la Surface d'Attaque
Chaque couche de l'architecture ne doit exposer que les fonctionnalités strictement nécessaires à son rôle. La couche de découverte ne doit pas avoir accès aux clés de chiffrement des transactions ; la couche de règlement ne doit pas avoir connaissance de la topologie du réseau de découverte. Cette isolation minimise l'impact d'une compromission partielle.
---
## 2. Séparation des Couches : Architecture en Trois Plans
L'architecture proposée repose sur une séparation stricte en trois plans fonctionnels indépendants. Cette séparation est motivée par des exigences de sécurité mutuellement incompatibles si ces plans étaient fusionnés.
### Plan de Découverte
**Objectif** : permettre à un nœud de localiser d'autres nœuds offrant des ressources compatibles, d'évaluer leur qualité, et de maintenir un réseau de voisinage robuste.
**Exigences** : haute disponibilité (AP), tolérance aux partitions, faible latence de découverte, résistance aux manipulations de routage. La cohérence n'est pas critique : une vue légèrement obsolète du réseau est acceptable.
**Ce qui ne doit PAS figurer dans ce plan** : le contenu des transactions, les identités réelles des acteurs, les montants échangés, ou tout élément permettant d'inférer des stratégies opérationnelles.
### Plan de Données et Ressources
**Objectif** : permettre à des acteurs de décrire, offrir, découvrir et transacter des ressources (compute, stockage, données, workflows) de manière sécurisée et souveraine.
**Exigences** : intégrité forte (enregistrements signés), confidentialité du contenu (chiffrement bout-en-bout), souveraineté du cycle de vie (TTL contrôlé par le créateur), attestation de consommation vérifiable sans révélation du contenu.
**Ce qui ne doit PAS figurer dans ce plan** : le règlement monétaire (séparation de la couche applicative et de la couche financière), ni les détails de routage de la couche de découverte.
### Plan de Règlement Monétaire
**Objectif** : régler les transactions monétaires résultant de la consommation de ressources, de manière automatisée, confidentielle, et résistante à la censure.
**Exigences** : finalité déterministe ou quasi-déterministe, confidentialité des montants et des parties, smart contracts pour l'automatisation du règlement, gouvernance de consortium, résistance à la censure par des validateurs adversariaux.
**Pourquoi ces trois plans doivent rester indépendants** :
La couche de découverte optimise pour la disponibilité et la performance de routage — des propriétés antagonistes avec la confidentialité forte. Fusionner les couches de découverte et de données exposerait les patterns d'accès aux ressources à tout observateur participant à la DHT. La couche de règlement monétaire requiert une cohérence forte et une finalité déterministe — des propriétés incompatibles avec le profil AP de la découverte. Un seul protocole ne peut satisfaire simultanément ces exigences contradictoires.
Analogie : le réseau téléphonique (couche de transport) ne gère pas le réseau bancaire (couche de règlement), même si les deux transportent des informations liées à des transactions économiques.
---
## 3. Couche de Découverte P2P
### 3.1 DHT Kademlia avec Espace de Noms Privé
La DHT Kademlia [Maymounkov & Mazières, 2002] constitue la base la plus adaptée pour la couche de découverte décentralisée. Son routage XOR en O(log n) sauts, sa robustesse au churn, et son implémentation dans de nombreux frameworks (libp2p, notamment) en font le choix le plus mature pour les réseaux P2P décentralisés à grande échelle.
Pour un réseau privé ou semi-privé, l'espace de noms DHT doit être isolé de la DHT globale publique. Cette isolation est réalisée par une clé de réseau (network key) distincte, empêchant la découverte croisée avec des nœuds appartenant à d'autres réseaux. S/Kademlia [Baumgart & Meinert, 2007] étend Kademlia avec des contraintes cryptographiques sur la génération des identifiants de nœuds (preuve de travail légère sur le NodeID) et des signatures sur les messages de routage, réduisant significativement les vecteurs d'empoisonnement.
Il n'existe pas d'alternative réaliste à Kademlia pour un réseau P2P décentralisé à cette échelle : Chord [Stoica et al., 2003] est plus simple mais moins robuste au churn ; Pastry et Tapestry présentent des propriétés similaires mais un écosystème d'implémentation plus réduit.
### 3.2 Transport Chiffré Multi-Protocole
Le Noise Protocol Framework [Perrin, 2018] fournit la primitive de transport sécurisé. Le pattern Noise_XX permet une authentification mutuelle des parties par échange de clés publiques, sans infrastructure PKI centralisée. La légèreté du framework (implémentation en quelques centaines de lignes, sans dépendance à une bibliothèque X.509) le rend particulièrement adapté aux contraintes embarquées.
Pour les réseaux de niveau de confiance élevée (consortium fermé), une clé pré-partagée (PSK) peut être incorporée dans le handshake Noise (pattern Noise_XXpsk), offrant une couche d'authentification supplémentaire qui garantit qu'un nœud non-membre du consortium ne peut même pas établir une connexion, réduisant drastiquement la surface d'attaque Sybil.
Le support multi-protocole (TCP, QUIC, WebTransport selon la disponibilité du lien) permet d'adapter le transport aux contraintes du lien physique, en particulier dans les environnements DTN où TCP peut être remplacé par des protocoles de couche bundle [Cerf et al., 2007].
### 3.3 Bootstrap Minimaliste
Le problème du premier contact est inévitable dans tout réseau décentralisé. La proposition retient une approche à trois niveaux :
- **Seeds codés en dur** : un ensemble minimal de nœuds de bootstrap dont les adresses et clés publiques sont incluses dans la distribution logicielle. Ces seeds sont diversifiés géographiquement et organisationnellement pour éviter un point de défaillance unique. Analogie directe avec les DNS seeds de Bitcoin et les directory authorities de Tor.
- **DHT locale** : les nœuds maintiennent localement une table persistante de leurs derniers pairs connus, permettant de bootstrapper sans accès aux seeds dans les reconnections ultérieures.
- **Résolution hors-bande** : pour les déploiements embarqués à haute sécurité, un mécanisme de distribution des seeds par canal hors-bande (support physique sécurisé, canal radio dédié) peut compléter les seeds réseau.
### 3.4 Scoring Comportemental Multidimensionnel
Le scoring comportemental constitue le mécanisme primaire d'évaluation de la qualité et de la fiabilité des pairs. La proposition retient une formule à sept dimensions, chacune justifiée par un vecteur de menace distinct :
**Dimension 1 — Ratio d'uptime (poids : 0.20)** : mesure la disponibilité historique du pair, en tenant compte des fenêtres de contact prévues. Un pair disponible 95% du temps dans ses fenêtres de contact prévues mérite une évaluation haute. Cette dimension pénalise les pairs instables ou éphémères (vecteur anti-Sybil par coût temporel).
**Dimension 2 — Latence normalisée (poids : 0.15)** : mesure le temps de réponse aux requêtes, normalisé par la latence attendue selon le type de lien. Cette dimension pénalise les pairs surchargés ou géographiquement distants sans que cela constitue un signal de compromission.
**Dimension 3 — Précision des challenges (poids : 0.25)** : des challenges cryptographiques périodiques (écho de données DHT, challenge de contenu connu) permettent de vérifier que le pair héberge réellement les données qu'il annonce. Un pair falsifiant sa capacité de stockage ou de traitement échouera sur ces challenges. C'est la dimension la plus résistante à la manipulation car elle requiert une réponse correcte à une question que l'adversaire ne peut anticiper.
**Dimension 4 — Diversité réseau (poids : 0.15)** : mesure la diversité des sous-réseaux IP des pairs que ce nœud connaît, favorisant les pairs bien connectés à des régions réseau variées. Cette dimension pénalise implicitement les clusters de nœuds contrôlés par un même acteur (colluseurs sur le même réseau).
**Dimension 5 — Taux de remplissage inverse (poids : 0.10)** : dans un contexte de marché de ressources, un pair offrant des ressources très chargées est moins utile qu'un pair offrant des ressources disponibles. Cette dimension oriente naturellement les nouveaux clients vers les pairs les moins sollicités, équilibrant la charge du réseau.
**Dimension 6 — Cohérence des témoins (poids : 0.10)** : les observations d'un pair par des témoins tiers (autres pairs qui l'ont récemment contacté) sont corrélées avec les observations directes. Une incohérence forte entre les témoignages et les observations directes signale une possible compromission ou une collusion localisée.
**Dimension 7 — Fiabilité DHT (poids : 0.05)** : mesure la probabilité que les enregistrements stockés par ce pair soient restitués correctement à des requêtes ultérieures. Cette dimension détecte les pairs qui acceptent des enregistrements sans les stocker (comportement parasite) ou qui altèrent les enregistrements confiés.
**Score global** :
```
Score = Σ(poids_i × dimension_i) × 100 ∈ [0, 100]
```
### 3.5 Seuil d'Éviction Dynamique selon l'Âge
Les nouveaux pairs ne disposent pas encore d'un historique comportemental suffisant pour être évalués équitablement. Un seuil d'éviction fixe pénaliserait systématiquement les nœuds récents, créant un réseau figé favorisant les anciens membres. La proposition retient un seuil dynamique selon l'âge du pair :
```
Seuil_min(age) = min(Seuil_max, Seuil_base + Seuil_pente × age_en_jours)
```
Typiquement : `Seuil_min(age) = min(80, 20 + 60 × age/24h)`, démarrant à 20% (très permissif) et atteignant 80% après 24 heures de présence continue. Ce profil permet à un nouveau pair légitime de s'établir progressivement tout en évinçant rapidement les pairs manifestement défaillants ou malveillants.
### 3.6 Protection Contre l'Isolement Total
Un invariant critique de sécurité : **le dernier pair actif d'un nœud ne peut jamais être évincé pour raison de score insuffisant seul**. Si un nœud ne dispose que d'un unique pair actif, le maintien de cette connexion, même avec un pair de score médiocre, est préférable à l'isolement complet qui rendrait le nœud aveugle et muet. L'éviction du dernier pair ne peut être déclenchée que par une preuve positive de comportement malveillant (challenge échoué, signature invalide) — non par un score passant sous un seuil.
### 3.7 Vérification Multi-Canal
La vérification de l'identité et de la qualité d'un pair par un unique canal crée une surface d'attaque exploitable par un adversaire capable de simuler parfaitement le comportement attendu sur ce canal. La proposition retient une architecture de vérification à trois canaux orthogonaux :
- **Challenge d'identité** : vérification cryptographique que la clé publique annoncée correspond bien au pair contacté.
- **Challenge DHT** : vérification que le pair stocke réellement les enregistrements qu'il annonce héberger, via des requêtes sur des clés dont la valeur est connue de l'observateur.
- **Witness query** : interrogation de pairs tiers sur leur expérience récente avec le pair évalué.
La vérification simultanément cohérente sur ces trois canaux orthogonaux requiert un niveau de sophistication adversariale croissant de manière exponentielle avec le nombre de canaux vérifiés, rendant la tromperie coordonnée prohibitivement coûteuse.
### 3.8 Gossip Sub pour la Propagation d'Événements
Les événements d'appartenance (arrivée, départ, mise à jour de score significative) sont propagés par un mécanisme de gossip sub — diffusion épidémique structurée de type infection-style [Das et al., 2002]. Ce mécanisme garantit une convergence en O(log n) étapes avec haute probabilité, sans coordination centrale. La propagation épidémique est robuste au churn et tolère l'absence temporaire de nœuds.
Pour les environnements hostiles, le gossip peut être limité aux paires de pairs avec une clé de chiffrement commune (intra-consortium), empêchant la fuite d'informations topologiques vers des observateurs extérieurs.
### 3.9 Adaptation DTN/Spatial
Les adaptations suivantes sont indispensables pour le contexte DTN :
- **Stockage-et-retransmission** : les messages de découverte non livrables (pair inaccessible) sont stockés localement et retransmis lors des prochaines fenêtres de contact, selon le modèle de la RFC 4838 [Cerf et al., 2007].
- **TTL adaptatifs** : le TTL des enregistrements DHT est paramétré en fonction du délai de propagation prévisible dans le réseau. Pour un satellite LEO avec une fenêtre de contact de 15 minutes toutes les 90 minutes, les TTL doivent être d'au moins 90 minutes pour survivre à un cycle orbital complet.
- **Heartbeats asynchrones** : en l'absence de connectivité continue, les heartbeats sont accumulés localement et émis en batch lors des fenêtres de contact, avec des timestamps signés permettant la reconstitution de l'historique d'uptime.
- **State snapshots** : l'état complet du réseau de voisinage est sérialisé périodiquement sur le stockage local, permettant une reprise sans perte après une déconnexion prolongée.
---
## 4. Couche de Données et Ressources
### 4.1 Records Signés avec Expiration Courte
Chaque enregistrement dans la DHT porte la signature de son créateur (clé privée ECDSA ou Ed25519) et un timestamp d'expiration. L'expiration courte (TTL typiquement entre 5 minutes et quelques heures selon le type de ressource) garantit l'auto-invalidation des enregistrements obsolètes sans mécanisme de suppression explicite. Le créateur est responsable du renouvellement périodique de ses enregistrements actifs.
Pour le contexte DTN, les TTL sont adaptés au profil de connectivité prévisible : un nœud satellite qui passe en dehors de la couverture sol pendant 2 heures doit avoir des TTL d'au moins 2 heures pour ses enregistrements critiques.
### 4.2 Tombstones Signés pour la Révocation Propre
La suppression explicite d'un enregistrement avant son expiration naturelle passe par un tombstone signé — un enregistrement spécial portant la clé de l'enregistrement révoqué, le timestamp de révocation, et la signature du créateur. Les tombstones sont propagés par gossip et stockés temporairement dans la DHT pour prévenir la réapparition de l'enregistrement révoqué (replay attack).
La durée de vie d'un tombstone doit être supérieure à la durée de vie maximale de l'enregistrement qu'il révoque, pour garantir que les nœuds qui n'ont pas encore vu la révocation ne réacceptent pas un replay de l'enregistrement original.
### 4.3 Index Secondaires dans la DHT
Pour permettre la découverte de ressources selon plusieurs critères (par type de ressource, par zone géographique, par niveau de confiance minimal requis), des index secondaires sont maintenus dans la DHT sous des clés dérivées des attributs d'indexation. Un enregistrement principal décrivant une ressource de compute est par exemple indexé sous `hash("compute")`, `hash("zone:LEO-1")`, et `hash("trust:consortium-A")`.
### 4.4 Chiffrement de Contenu Bout-en-Bout
Pour les données sensibles, la DHT ne doit stocker que des enveloppes chiffrées. Le payload de l'enregistrement est chiffré avec une clé symétrique connue uniquement des parties autorisées (clé de consortium ou clé dérivée d'un échange Diffie-Hellman). La DHT agit comme un système de stockage aveugle, incapable d'inférer le contenu des enregistrements qu'elle héberge.
Ce principe garantit que même un adversaire contrôlant une région de la DHT ne peut accéder au contenu des enregistrements, seulement à leurs métadonnées (clé DHT, créateur, expiration).
### 4.5 Attestation de Consommation par Zero-Knowledge Proofs
Le problème de l'oracle (comment prouver qu'une ressource a été réellement consommée sans révéler ni le contenu ni l'identité de l'initiateur) est adressé par les preuves à divulgation nulle de connaissance (ZK-proofs). Goldwasser, Micali et Rackoff [Goldwasser et al., 1989] ont formalisé ce paradigme ; les constructions modernes basées sur des arguments non-interactifs à divulgation nulle (zk-SNARKs [Groth, 2016]) permettent de prouver la bonne exécution d'un calcul en O(1) de vérification, quelle que soit la complexité du calcul.
Pour un marché de ressources, une attestation ZK permet à un consommateur de prouver à un smart contract qu'il a reçu et vérifié un résultat de calcul conforme à sa spécification, sans révéler ni le résultat lui-même ni les paramètres d'entrée.
### 4.6 Pub/Sub pour les Événements Applicatifs
Les événements applicatifs (disponibilité d'une ressource, completion d'un workflow, alerte de capacité) sont propagés via un mécanisme pub/sub indépendant de la couche de découverte. Ce mécanisme doit être léger, tolérant aux déconnexions temporaires (accumulation de messages pendant les périodes hors-ligne), et ne pas exposer la topologie du réseau de souscription.
---
## 5. Couche de Règlement Monétaire et Blockchain
### 5.1 Positionnement Transactionnel
La blockchain n'est pas la couche de découverte, ni la couche de données. Elle intervient exclusivement pour le règlement monétaire des transactions de ressources, après que ces ressources ont été consommées et attestées. Ce positionnement est fondamental : confondre la blockchain avec l'infrastructure de découverte ou de données créerait des couplages qui dégradent à la fois les performances (latence blockchain incompatible avec la découverte en temps réel) et la confidentialité (toute donnée sur la blockchain est potentiellement publique).
La blockchain règle les transactions APRÈS leur exécution, sur la base d'attestations cryptographiques de consommation. Elle n't pas besoin de connaître le contenu des ressources échangées, ni les identités des parties au-delà de leurs adresses blockchain (qui peuvent être pseudonymes ou chiffrées selon la solution choisie).
### 5.2 Exigences Non-Négociables pour la Blockchain Éligible
Les exigences suivantes sont dérivées des contraintes du contexte cible et constituent des critères d'exclusion pour les solutions inadaptées :
| Exigence | Justification |
|---|---|
| Confidentialité des transactions (montants et parties) | Tout observateur externe ne doit pouvoir inférer ni le volume des échanges, ni les partenariats entre acteurs |
| Souveraineté (pas de fuite vers observateur externe) | Même les métadonnées de la blockchain ne doivent pas révéler d'informations stratégiques |
| Finalité rapide (< 30s idéalement, < 5 min acceptable) | Compatibilité avec les fenêtres de contact DTN limitées |
| Smart contracts pour automatisation du règlement | Automatisation du paiement conditionnel à l'attestation de consommation [Szabo, 1997 ; Wood, 2014] |
| Résistance à la censure | Un validateur adversarial ne doit pas pouvoir bloquer unilatéralement une transaction |
| Gouvernabilité de consortium | Admission, révocation, rotation des validateurs sans redémarrage du réseau |
| Faible empreinte énergétique | Contrainte stricte pour les nœuds embarqués (Proof of Work exclu) |
### 5.3 Analyse des Blockchains Éligibles
| Blockchain | Confidentialité | Finalité | Smart Contracts | Consortium | Énergie | Maturité | Contexte GARDEN |
|---|---|---|---|---|---|---|---|
| Ethereum (L1) | (transparence totale) | (12s, probabiliste) | ✓✓ (EVM, très mature) | (public) | (PoS OK, mais L1 lourd) | ✓✓ | Inadapté seul |
| Ethereum L2 ZK (Aztec, zkSync) | ✓✓ (ZK-rollup) | (L2 fast, L1 proof lente) | (en développement) | | | (jeune) | Prometteur, encore immature |
| Aztec Network | ✓✓ (ZK natif chiffré) | | (Noir language) | | | (en développement actif) | Très intéressant pour confidentialité, maturité insuffisante 2026 |
| Secret Network | ✓✓ (TEE chiffrés) | (Tendermint ~6s) | ✓✓ (CosmWasm confidentiel) | (Cosmos zones) | ✓✓ | | Bon candidat consortium |
| Oasis Network | ✓✓ (TEE + ParaTime) | (Tendermint) | ✓✓ (EVM confidentiel) | | ✓✓ | | Très adapté embarqué + smart contracts |
| Zcash | ✓✓ (ZK-SNARKs, shielded) | (PoW, ~75s) | (Script limité) | (public) | (PoW) | ✓✓ | Excellent paiements confidentiels, insuffisant smart contracts |
| Monero | ✓✓ (RingCT, Ring Sig) | (PoW, ~2min) | | (public) | (PoW) | ✓✓ | Idem Zcash, moins adapté |
| Aleo | ✓✓ (ZK natif, Leo language) | (PoS, ~15s) | (ZK-native execution) | | ✓✓ | (mainnet récent) | Très prometteur, écosystème jeune |
| Hyperledger Fabric | ✓✓ (canaux privés) | ✓✓ (déterministe, Raft/PBFT) | ✓✓ (chaincode mature) | ✓✓ (MSP, canaux) | ✓✓ | ✓✓ | Idéal consortium fermé |
| Cosmos SDK + IBC | (dépend de la zone) | ✓✓ (Tendermint ~6s) | (CosmWasm) | ✓✓ (zones souveraines) | ✓✓ | ✓✓ | Excellente base multi-consortium |
| Polkadot / Substrate | | (GRANDPA ~12s) | (ink!, EVM via frontier) | ✓✓ (parachains) | ✓✓ | | Adapté multi-consortium, complexe |
**Analyse détaillée des blockchains les plus pertinentes** :
**Hyperledger Fabric** [Androulaki et al., 2018] est une blockchain permissionnée conçue pour les consortiums d'entreprises. Son architecture modulaire (orderers, peers, MSP) permet une gouvernance fine des membres du consortium. Les canaux privés permettent à un sous-ensemble de membres d'effectuer des transactions invisibles aux autres membres. La finalité est déterministe (pas de forks possibles avec Raft ou PBFT), propriété critique pour le contexte DTN la réconciliation de forks est difficile. Son principal défaut est l'absence de résistance trustless : la confiance dans les MSP (Membership Service Providers) est requise, ce qui convient aux consortiums fermés mais rend la solution inadaptée aux échanges inter-consortium entre adversaires potentiels.
**Cosmos SDK + IBC** [Kwon & Buchman, 2016] offre une architecture de zones souveraines interconnectées via le protocole IBC (Inter-Blockchain Communication). Chaque zone peut être configurée indépendamment (algorithme de consensus, gouvernance, smart contracts), permettant des niveaux de sécurité différenciés selon le contexte. Le consensus Tendermint BFT [Buchman, 2016] offre une finalité déterministe en environ 6 secondes compatible avec les contraintes DTN raisonnables. L'absence de confidentialité native est le principal défaut : les transactions IBC sont visibles dans les logs des deux zones impliquées.
**Secret Network** utilise des enclaves TEE (Trusted Execution Environment) Intel SGX pour exécuter les smart contracts dans un environnement chiffré. Les entrées, les sorties, et l'état des smart contracts sont chiffrés pour les validateurs eux-mêmes seul le code est public. Cette propriété est exceptionnelle pour un contexte hostile. La principale limitation est la dépendance à Intel SGX, une technologie matérielle présentant des vulnérabilités de canal latéral documentées [Costan & Devadas, 2016]. La compatibilité Cosmos offre une interopérabilité avec un écosystème large.
**Zcash** [Ben-Sasson et al., 2014 ; Hopwood et al., 2016] utilise les zk-SNARKs pour permettre des transactions shielded dont les montants et les adresses sont cryptographiquement cachés. C'est la solution la plus mature pour les paiements confidentiels purs, sans dépendance matérielle (contrairement aux TEE). Sa limitation principale est l'absence de smart contracts expressifs : le langage Script de Zcash ne permet pas d'automatiser le règlement conditionnel requis pour un marché de ressources complexe.
**Oasis Network** [Oasis Labs, 2020] combine TEE et blockchain pour offrir des "ParaThreads" confidentiels : des environnements d'exécution chiffrés les calculs se déroulent à l'abri des validateurs. L'EVM confidentiel (Sapphire ParaTime) permet d'exécuter des smart contracts Solidity avec confidentialité des états internes. La compatibilité avec l'écosystème Cosmos et l'EVM en fait un candidat particulièrement polyvalent.
### 5.4 Recommandation Architecturale
La proposition retient une architecture hybride à deux niveaux :
**Niveau 1 — Intra-consortium** : Hyperledger Fabric ou une zone Cosmos permissionnée. La gouvernance est assurée par le consortium via les MSP (Fabric) ou la gouvernance on-chain (Cosmos). Les transactions intra-consortium bénéficient de canaux privés (Fabric) ou d'une confidentialité applicative. La finalité déterministe garantit l'absence de forks. Ce niveau traite 95%+ des transactions dans un déploiement opérationnel normal.
**Niveau 2 — Inter-consortium et règlement public** : Secret Network ou Oasis Network pour les transactions entre acteurs de consortiums distincts, bénéficiant de smart contracts confidentiels. Les attestations de consommation de ressources sont soumises on-chain comme ZK-proofs hors-chaîne, permettant de prouver la bonne exécution sans révéler le contenu des calculs [Groth, 2016].
**Gouvernance** : chaque consortium maintient une structure de gouvernance multi-sig avec seuil de quorum configurable. Les décisions d'admission et d'exclusion de membres requièrent un quorum configurable (typiquement 2/3) pour résister aux attaques de corruption minoritaire.
---
## 6. Verrous Conceptuels et Technologiques
### Verrou 1 — Identité sans Autorité Centrale (TOFU)
**Problème** : Comment établir qu'une clé publique appartient bien à l'acteur qu'elle prétend représenter, sans autorité de certification centrale ? Le paradigme TOFU (Trust On First Use) accepte la clé lors du premier contact et avertit en cas de changement solution pragmatique mais vulnérable à l'interception du premier contact.
**Solutions** : Les identifiants décentralisés (DID) définis par la spécification W3C [W3C, 2022] permettent à des acteurs de créer des identités auto-certifiées ancrées dans une blockchain ou un réseau de confiance distribué, sans dépendance à une autorité centrale. Les identifiants libp2p (PeerID dérivé de la clé publique) offrent une auto-certification primitive.
**Challenge résiduel** : L'ancrage d'une identité DID dans une blockchain publique révèle l'existence de l'identité à tout observateur. Pour un contexte hostile, des identités éphémères rotatoires avec des mécanismes de reconnaissance hors-bande constituent une alternative, au prix d'une complexité opérationnelle plus élevée.
### Verrou 2 — Bootstrap Trustless
**Problème** : Le premier contact avec le réseau requiert de connaître au moins un pair de confiance. Ces seeds constituent un ancre de confiance qui ne peut être supprimée elle peut seulement être diversifiée et distribuée pour réduire le risque de compromission partielle.
**Solutions** : Diversification des seeds (géographique, organisationnelle, technologique). Distribution des seeds par canal hors-bande pour les environnements à haute sécurité. DHT locale persistante pour les reconnexions.
**Challenge résiduel** : Aucun système sans seed codés en dur ou sans canal hors-bande de confiance ne peut bootstrapper de manière véritablement trustless. Les seeds constituent nécessairement une ancre de confiance minimale.
### Verrou 3 — Résistance Sybil sans Coût Cryptoéconomique
**Problème** : Douceur [Douceur, 2002] a démontré que la résistance Sybil est impossible sans coût d'entrée ou autorité centrale. Pour les réseaux ouverts en environnement hostile, ni la PSK organisationnelle ni le scoring comportemental ne constituent des solutions complètes : la PSK est efficace mais requiert une coordination hors-bande pour chaque nouvel entrant ; le scoring peut être manipulé sur le long terme par un adversaire patient.
**Solutions** : PSK organisationnelle pour les réseaux fermés. Scoring comportemental multidimensionnel comme couche de défense secondaire. Pour les réseaux ouverts, un dépôt cryptoéconomique (stake on-chain) comme coût d'entrée minimum.
**Challenge résiduel** : Les réseaux entièrement ouverts et hostiles sans coût d'entrée cryptoéconomique restent vulnérables à des attaques Sybil patient et bien financées.
### Verrou 4 — Cohérence sans Consensus Fort (CAP)
**Problème** : Le théorème CAP [Brewer, 2000 ; Gilbert & Lynch, 2002] impose un choix entre cohérence et disponibilité lors d'un partitionnement. Pour la couche de découverte, choisir CP rendrait le système indisponible lors des partitions DTN fréquentes.
**Solution** : Choix délibéré du profil AP pour la découverte (cohérence éventuelle, disponibilité prioritaire), CP pour le règlement blockchain (cohérence forte, disponibilité conditionnelle au quorum). Les conflits d'état lors de la réunification sont résolus par Last-Write-Wins ou par vecteurs d'horloge selon la criticité des enregistrements.
**Challenge résiduel** : La cohérence éventuelle autorise des fenêtres temporaires d'état incohérent qui peuvent être exploitées par un adversaire contrôlant l'instant de la réunification.
### Verrou 5 — Détection de Panne sans Oracle (FLP)
**Problème** : Fischer, Lynch et Paterson [Fischer, Lynch & Paterson, 1985] ont démontré qu'en présence d'asynchronisme, aucun algorithme déterministe ne peut distinguer un pair défaillant d'un pair lent. Toute détection de panne est donc probabiliste et sujette à des faux positifs.
**Solution** : Le protocole SWIM [Das et al., 2002] offre une approche infection-style avec gestion des suspicions (un pair suspecté d'être défaillant n'est pas immédiatement exclu mais marqué comme suspect, et la confirmation de sa panne requiert la convergence de plusieurs observations indépendantes). Cette approche est adaptée aux réseaux DTN les faux positifs de détection de panne sont fréquents.
**Challenge résiduel** : Dans les réseaux DTN à très haute latence, les délais de confirmation de panne peuvent atteindre plusieurs cycles orbitaux, pendant lesquels un pair défaillant (ou malveillant) reste actif dans les tables de voisinage.
### Verrou 6 — Confidentialité des Transactions sur Chaîne Publique
**Problème** : La transparence des blockchains publiques est structurellement antagoniste à la confidentialité opérationnelle. Même les blockchains pseudonymes permettent la reconstruction partielle des graphes de transactions [Meiklejohn et al., 2013].
**Solutions** : ZK-SNARKs [Ben-Sasson et al., 2014] pour masquer cryptographiquement les montants et les adresses. TEE (Trusted Execution Environment) pour l'exécution confidentielle des smart contracts [Oasis Labs, 2020]. Blockchains permissionnées avec canaux privés [Androulaki et al., 2018].
**Challenge résiduel** : Les solutions ZK sont encore en cours de maturation pour les smart contracts complexes. Les solutions TEE présentent des dépendances matérielles avec des vulnérabilités de canal latéral documentées. Les blockchains permissionnées requièrent une confiance dans les opérateurs du réseau.
### Verrou 7 — DTN et Transactions Intermittentes
**Problème** : Les blockchains classiques supposent une connectivité continue entre validateurs. Dans un réseau DTN, un validateur peut être absent pendant plusieurs heures, bloquant l'atteinte du quorum et donc le traitement des transactions.
**Solutions** :
- **State channels** : deux parties pré-engagent des fonds dans un canal off-chain et échangent des signatures hors-chaîne pendant une période prolongée, ne soumettant le résultat final on-chain qu'à la clôture du canal. Cette approche tolère des périodes de déconnexion arbitrairement longues.
- **Optimistic rollups** : les transactions sont soumises on-chain avec une fenêtre de contestation. En l'absence de contestation, la transaction est finalisée. Adapté aux environnements à faible fraude mais à connectivité intermittente.
- **Signature différée** : les transactions sont signées localement avec un timestamp, accumulées pendant la période hors-ligne, et soumises en batch lors de la prochaine fenêtre de connexion. Le smart contract vérifie la validité temporelle des signatures.
**Challenge résiduel** : Les state channels requièrent une connexion initiale pour l'engagement des fonds. Les optimistic rollups introduisent des fenêtres de contestation de plusieurs heures compatibles DTN mais augmentant la latence de finalité effective.
### Verrou 8 — Oracle et Preuve de Consommation Réelle
**Problème** : Szabo [Szabo, 1997] a identifié la difficulté fondamentale d'ancrer des contrats sur des événements du monde réel. Un smart contract de règlement de ressources doit vérifier qu'un calcul s'est réellement exécuté, que des données ont été réellement livrées mais un smart contract est exécuté dans un environnement blockchain isolé du monde extérieur.
**Solutions** :
- **TEE attestations** : un calcul exécuté dans une enclave Intel SGX ou ARM TrustZone génère une attestation cryptographique signée par le microprocesseur lui-même, prouvant que le code spécifié s'est exécuté dans un environnement sécurisé.
- **ZK-proof d'exécution** : un ZKP prouve la bonne exécution d'un programme sur des entrées données sans révéler ni les entrées ni les sorties. Des systèmes comme zkEVM et les ZKVM expérimentaux permettent cette approche pour des programmes arbitraires [Groth, 2016].
- **Challenge-response** : le consommateur soumet une preuve interactive (échantillonnage aléatoire de résultats vérifiables) que le fournisseur ne peut produire qu'en ayant réellement exécuté le calcul.
**Challenge résiduel** : Les TEE présentent des vulnérabilités de canal latéral [Costan & Devadas, 2016]. Les ZK-proofs d'exécution générique restent coûteux à générer pour des programmes complexes.
---
## 7. Gestion des Consortiums et Confiance Relative
### 7.1 Modèle de Confiance Relative (Non-Binaire)
La confiance accordée à un pair est formalisée comme un vecteur multidimensionnel de scores, agrégé en un score scalaire dans [0, 100] :
```
Confiance(pair, t) = f(uptime_ratio(t), challenge_precision(t),
witness_coherence(t), age(pair),
diversity_contribution(t))
```
Ce score est mis à jour à chaque interaction et décroît graduellement en l'absence d'observations récentes (décroissance exponentielle avec demi-vie paramétrable). Un pair absent depuis longtemps n'est pas nécessairement malveillant son score se dégrade pour refléter l'incertitude croissante sur son état.
Un modèle binaire échoue parce qu'il ignore cette incertitude temporelle : un pair qui obtient le statut "de confiance" le conserve indéfiniment, même si son comportement récent est suspect. Le modèle continu à décroissance temporelle force une réévaluation permanente.
### 7.2 Architecture de Consortium à Trois Niveaux
**Niveau 1 — Consortium PSK** : Pairs partageant une clé pré-partagée (Pre-Shared Key) distribuée par voie organisationnelle sécurisée. La PSK est incorporée dans le handshake Noise, garantissant que seuls les membres du consortium peuvent établir des connexions dans cet espace. La confiance a priori est élevée (score initial : 70/100). Ce niveau correspond aux pairs appartenant à la même organisation ou unité opérationnelle.
**Niveau 2 — Consortium à Attestation** : Pairs d'organisations alliées disposant d'une attestation signée par un membre de niveau 1. L'attestation contient la clé publique du pair, sa période de validité, et les ressources auxquelles il est autorisé à accéder. La confiance a priori est modérée (score initial : 40/100). Ce niveau correspond aux partenaires de coalition.
**Niveau 3 — Réseau Ouvert** : Pairs sans attestation ni PSK. La confiance initiale est nulle (score : 0/100) et ne peut augmenter que par accumulation d'observations comportementales positives sur une période prolongée. L'accès aux ressources critiques est restreint jusqu'à ce que le score dépasse un seuil configurable.
### 7.3 Révocation et Propagation de Méfiance
La révocation d'un pair est propagée par un tombstone signé par un membre de niveau 1, diffusé par gossip épidémique [Das et al., 2002]. Tous les pairs recevant ce tombstone mettent immédiatement le score du pair révoqué à zéro et le placent sur une liste noire temporaire. La liste noire est elle-même signée et distribuée avec un TTL configurable, permettant une réhabilitation éventuelle après révocation temporaire.
Pour les situations de compromission active (clé privée extraite), le protocole de révocation d'urgence est initié simultanément par plusieurs membres de niveau 1 pour éviter qu'un adversaire contrôlant un unique membre puisse bloquer la révocation.
### 7.4 Consortiums Dynamiques
L'admission d'un nouveau membre est effectuée sans redémarrage du réseau : la nouvelle attestation est diffusée via gossip, et les membres existants commencent à accepter des connexions du nouveau membre dès réception de l'attestation valide. La rotation des PSK suit un calendrier prédéfini avec une fenêtre de grâce permettant la transition progressive (ancienne et nouvelle PSK acceptées simultanément pendant une fenêtre configurable de 24 à 72 heures).
---
## 8. Adaptations Indispensables pour le Contexte Spatial/Embarqué
### 8.1 Protocoles DTN-Compatibles
Tous les protocoles de la couche de découverte doivent être adaptés pour tolérer des latences aller-retour de plusieurs minutes et des interruptions de connectivité de plusieurs heures. Les timeouts TCP standard (généralement inférieurs à quelques minutes) sont remplacés par des délais configurables compatibles avec les orbites prévisibles. Les messages critiques sont acquittés avec retransmission exponentielle.
### 8.2 Minimisation des Échanges Verbeux
Les protocoles verbeux (abondance de handshakes, de messages de keepalive, de synchronisations de tables de routage) sont remplacés par des protocoles binaires compacts (Protocol Buffers, CBOR) avec compression différentielle. La fréquence des messages de maintenance est adaptée dynamiquement à la bande passante disponible : sur un lien satellite à 64 kbps, le heartbeat périodique est espacé de plusieurs minutes, non de quelques secondes.
### 8.3 Signature Différée et Soumission Batch
Dans les environnements DTN, les transactions sont signées localement avec un timestamp et accumulées dans une file de persistance locale. Lors des fenêtres de connexion disponibles, elles sont soumises en batch à la couche de règlement. Le smart contract vérifie que le timestamp de signature se situe dans une fenêtre acceptable et que le nonce est unique (prévention des replays).
### 8.4 Module de Sécurité Matérielle
La clé privée principale de chaque nœud est hébergée dans un module de sécurité matérielle (HSM) ou une enclave sécurisée (ARM TrustZone, RISC-V Keystone) rendant son extraction physique difficile même en cas de capture du nœud. Les opérations cryptographiques (signature, déchiffrement) sont déléguées au module HSM sans que la clé privée ne quitte jamais l'enclave.
### 8.5 Mode Autonome Complet
Chaque nœud doit être capable d'opérer en mode complètement autonome (zéro connexion réseau) pendant des durées pouvant atteindre plusieurs jours, en maintenant un état cohérent de ses ressources locales, en traitant les requêtes locales, et en accumulant les transactions en attente de règlement. La resynchronisation après reconnexion est déterministe et complète, résolvant les conflits d'état par un mécanisme de résolution configuré à l'avance.
---
## Références Bibliographiques
**[Androulaki et al., 2018]** Androulaki, E., Barger, A., Bortnikov, V., Cachin, C., Christidis, K., De Caro, A., Enyeart, D., Ferris, C., Laventman, G., Manevich, Y., Muralidharan, S., Murthy, C., Nguyen, B., Sethi, M., Singh, G., Smith, K., Sorniotti, A., Stathakopoulou, C., Vukolic, M., Cocco, S. W., & Yellick, J. (2018). Hyperledger Fabric: a distributed operating system for permissioned blockchains. In *Proceedings of the 13th EuroSys Conference*, article 30.
**[Baumgart & Meinert, 2007]** Baumgart, I., & Meinert, S. (2007). S/Kademlia: A practicable approach towards secure key-based routing. In *Proceedings of the 2007 International Conference on Parallel and Distributed Systems (ICPADS)*, pp. 18.
**[Ben-Sasson et al., 2014]** Ben-Sasson, E., Chiesa, A., Garman, C., Green, M., Miers, I., Tromer, E., & Virza, M. (2014). Zerocash: Decentralized Anonymous Payments from Bitcoin. In *Proceedings of the 2014 IEEE Symposium on Security and Privacy (S&P)*, pp. 459474.
**[Brewer, 2000]** Brewer, E. A. (2000). Towards robust distributed systems. In *Proceedings of the 19th Annual ACM Symposium on Principles of Distributed Computing (PODC)*, pp. 7.
**[Buchman, 2016]** Buchman, E. (2016). *Tendermint: Byzantine Fault Tolerance in the Age of Blockchains*. M.Sc. Thesis, University of Guelph.
**[Cerf et al., 2007]** Cerf, V., Burleigh, S., Hooke, A., Torgerson, L., Durst, R., Scott, K., Fall, K., & Weiss, H. (2007). Delay-Tolerant Networking Architecture. *RFC 4838*, IETF.
**[Costan & Devadas, 2016]** Costan, V., & Devadas, S. (2016). Intel SGX Explained. *IACR Cryptology ePrint Archive*, Report 2016/086.
**[Daian et al., 2020]** Daian, P., Goldfeder, S., Kell, T., Li, Y., Zhao, X., Bentov, I., Breidenbach, L., & Juels, A. (2020). Flash Boys 2.0: Frontrunning in Decentralized Exchanges, Miner Extractable Value, and Consensus Instability. In *Proceedings of the 2020 IEEE Symposium on Security and Privacy (S&P)*, pp. 910927.
**[Das et al., 2002]** Das, A., Gupta, I., & Motivala, A. (2002). SWIM: Scalable Weakly-consistent Infection-style Process Group Membership Protocol. In *Proceedings of the 2002 International Conference on Dependable Systems and Networks (DSN)*, pp. 303312.
**[Douceur, 2002]** Douceur, J. R. (2002). The Sybil Attack. In *Proceedings of the 1st International Workshop on Peer-to-Peer Systems (IPTPS)*, LNCS 2429, pp. 251260.
**[Fall, 2003]** Fall, K. (2003). A delay-tolerant network architecture for challenged internets. In *Proceedings of SIGCOMM 2003*, pp. 2734.
**[Fischer, Lynch & Paterson, 1985]** Fischer, M. J., Lynch, N. A., & Paterson, M. S. (1985). Impossibility of distributed consensus with one faulty process. *Journal of the ACM (JACM)*, 32(2), 374382.
**[Gilbert & Lynch, 2002]** Gilbert, S., & Lynch, N. (2002). Brewer's conjecture and the feasibility of consistent, available, partition-tolerant web services. *ACM SIGACT News*, 33(2), 5159.
**[Goldwasser, Micali & Rackoff, 1989]** Goldwasser, S., Micali, S., & Rackoff, C. (1989). The knowledge complexity of interactive proof systems. *SIAM Journal on Computing*, 18(1), 186208.
**[Groth, 2016]** Groth, J. (2016). On the size of pairing-based non-interactive arguments. In *Proceedings of the 35th Annual International Conference on the Theory and Applications of Cryptographic Techniques (EUROCRYPT 2016)*, LNCS 9666, pp. 305326.
**[Hopwood et al., 2016]** Hopwood, D., Bowe, S., Hornby, T., & Wilcox, N. (2016). *Zcash Protocol Specification*. Electric Coin Company. https://zips.z.cash/protocol/protocol.pdf
**[Kwon & Buchman, 2016]** Kwon, J., & Buchman, E. (2016). *Cosmos: A Network of Distributed Ledgers*. Whitepaper. https://v1.cosmos.network/resources/whitepaper
**[Maymounkov & Mazières, 2002]** Maymounkov, P., & Mazières, D. (2002). Kademlia: A Peer-to-Peer Information System Based on the XOR Metric. In *Proceedings of IPTPS 2002*, LNCS 2429, pp. 5365.
**[Meiklejohn et al., 2013]** Meiklejohn, S., Pomarole, M., Jordan, G., Levchenko, K., McCoy, D., Voelker, G. M., & Savage, S. (2013). A Fistful of Bitcoins: Characterizing Payments Among Men with No Names. In *Proceedings of IMC 2013*, pp. 127140.
**[Nakamoto, 2008]** Nakamoto, S. (2008). Bitcoin: A Peer-to-Peer Electronic Cash System. https://bitcoin.org/bitcoin.pdf
**[Oasis Labs, 2020]** Oasis Network (2020). *Oasis Network Primer*. Oasis Labs. https://oasisprotocol.org/primer
**[Perrin, 2018]** Perrin, T. (2018). *The Noise Protocol Framework*. https://noiseprotocol.org/noise.pdf
**[Stoica et al., 2003]** Stoica, I., Morris, R., Liben-Nowell, D., Karger, D. R., Kaashoek, M. F., Dabek, F., & Balakrishnan, H. (2003). Chord: A scalable peer-to-peer lookup protocol for internet applications. *IEEE/ACM Transactions on Networking*, 11(1), 1732.
**[Sun et al., 2017]** Sun, S.-F., Au, M. H., Liu, J. K., & Yuen, T. H. (2017). RingCT 2.0: A Compact Accumulator-Based (Linkable Ring Signature) Protocol for Blockchain Cryptocurrency Monero. In *Proceedings of ESORICS 2017*, LNCS 10493, pp. 456474.
**[Szabo, 1997]** Szabo, N. (1997). Formalizing and securing relationships on public networks. *First Monday*, 2(9).
**[W3C, 2022]** W3C (2022). *Decentralized Identifiers (DIDs) v1.0*. W3C Recommendation. https://www.w3.org/TR/did-core/
**[Wood, 2014]** Wood, G. (2014). *Ethereum: A Secure Decentralised Generalised Transaction Ledger*. Ethereum Yellow Paper. https://ethereum.github.io/yellowpaper/paper.pdf
**[Wood, 2016]** Wood, G. (2016). *Polkadot: Vision for a Heterogeneous Multi-Chain Framework*. Whitepaper. https://polkadot.network/PolkaDotPaper.pdf

View File

@@ -0,0 +1,410 @@
# Analyse des Risques d'une Découverte de Pair Sans Confiance et des Transactions de Données et Monétaires dans un Réseau Décentralisé
**Catégorie** : Sécurité des systèmes distribués — Analyse de risques
**Domaine d'application** : Systèmes embarqués, contexte spatial, réseaux tolérants aux disruptions (DTN)
**Version** : 1.0 — Mars 2026
---
## Résumé
Les réseaux pair-à-pair décentralisés opérant dans des environnements hostiles posent des défis de sécurité fondamentalement différents de ceux rencontrés dans les infrastructures classiques. Ce document analyse les risques inhérents à deux problématiques interdépendantes : d'une part, la découverte de pairs en l'absence d'autorité centrale de confiance, et d'autre part, la conduite de transactions portant sur des données et des actifs monétaires dans un réseau potentiellement infiltré, surveillé, ou partiellement contrôlé par des adversaires.
Le périmètre de cette analyse est délimité par le contexte des systèmes distribués embarqués et spatiaux opérant selon le paradigme DTN (Delay/Disruption Tolerant Networking), caractérisés par des latences élevées, une connectivité intermittente, et la coprésence d'acteurs aux niveaux de confiance hétérogènes.
Le modèle de menace retenu distingue deux classes d'adversaires : l'adversaire rationnel à ressources limitées, dont les actions sont guidées par le rapport coût/bénéfice, et l'adversaire disposant de ressources quasi-illimitées, capable de conduire des attaques passives prolongées (watchers) ou actives (injection, manipulation). Dans ce second cas, les garanties probabilistes offertes par les protocoles cryptographiques standards peuvent être insuffisantes sur des horizons temporels longs.
L'analyse couvre trois couches distinctes : la couche réseau et transport, la couche de découverte P2P, et la couche de transactions (données et monétaires). Pour chacune, nous identifions les vecteurs d'attaque connus de la littérature, leur criticité relative selon le contexte d'utilisation (réseau privé vs réseau public hostile), et les mécanismes de mitigation disponibles avec leurs limites résiduelles. Une synthèse tabulaire conclut l'analyse et permet d'orienter les choix architecturaux vers les risques les plus critiques.
La conclusion principale de cette étude est que la confiance ne peut, dans ce contexte, être que probabiliste et relative — jamais binaire ni absolue — et que toute architecture ignorant cette réalité est structurellement vulnérable sur le long terme.
---
## 1. Contexte et Modèle de Menace
### 1.1 Le Problème Fondamental : Confiance sans Autorité Centrale
La question de la coordination entre agents sans autorité centrale de confiance est l'un des problèmes fondamentaux de l'informatique distribuée. Lamport, Shostak et Pease [Lamport et al., 1982] ont formalisé ce problème sous la forme du "problème des généraux byzantins" : comment un ensemble de processus peut-il atteindre un consensus lorsque certains d'entre eux peuvent se comporter de manière arbitrairement malveillante ? Leur résultat fondamental établit qu'un consensus fiable est possible si et seulement si la proportion de processus défaillants est strictement inférieure à un tiers de l'ensemble.
Ce résultat théorique contraint directement toute architecture distribuée opérant dans un environnement hostile : même en supposant que les mécanismes cryptographiques sont parfaits, le nombre de nœuds compromis ou malveillants que le système peut tolérer est borné par la proportion d'un tiers. Au-delà de ce seuil, aucun algorithme de consensus déterministe ne peut garantir la cohérence.
Par ailleurs, le résultat FLP [Fischer, Lynch & Paterson, 1985] démontre l'impossibilité d'un algorithme de consensus déterministe tolérant même une seule panne dans un système purement asynchrone. Ce théorème impose des compromis entre disponibilité, cohérence et tolérance aux pannes que toute architecture doit assumer explicitement, sous peine de créer des zones d'ombre dans les garanties de sécurité.
### 1.2 Contexte Embarqué, Spatial et Hostile : Spécificités DTN
Les réseaux de communication spatiaux et les réseaux embarqués militaires ou industriels partagent des caractéristiques qui les distinguent radicalement des réseaux terrestres filaires sur lesquels la majorité des protocoles P2P ont été conçus.
Fall [Fall, 2003] a posé les bases du paradigme DTN (Delay-Tolerant Networking), conçu pour des environnements où les chemins bout-en-bout n'existent pas en permanence, où les latences peuvent s'étendre de secondes à plusieurs heures (communications interplanétaires), et où les protocoles de transport classiques fondés sur TCP/IP s'avèrent inutilisables. La RFC 4838 [Cerf et al., 2007] formalise l'architecture DTN dans le cadre de l'IETF, en introduisant le concept de couche "bundle" qui stocke les messages et les retransmet lors des fenêtres de contact.
Dans ce contexte, les hypothèses habituelles des protocoles P2P s'effondrent :
- L'hypothèse de connectivité partielle mais permanente disparaît ;
- Les timeouts TCP standard deviennent inutilisables ;
- Les heartbeats périodiques ne peuvent plus être garantis ;
- La détection de panne par timeout devient ambiguë (panne ou simple absence de connectivité ?).
Ces contraintes ne sont pas seulement techniques : elles créent des fenêtres d'opportunité pour des adversaires capables d'exploiter les périodes de déconnexion pour injecter de faux états ou corrompre des enregistrements en l'absence de réfutation possible.
### 1.3 Modèle d'Adversaire : Rationnel à Ressources Limitées vs Adversaire d'État
La littérature distingue classiquement deux grandes classes d'adversaires dans les protocoles cryptographiques :
**L'adversaire rationnel à ressources limitées** est un acteur dont le comportement est guidé par l'optimisation d'un objectif économique. Il attaquera si le bénéfice attendu dépasse le coût de l'attaque. Ce modèle est pertinent pour les attaques opportunistes sur les réseaux P2P publics.
**L'adversaire à ressources illimitées** peut financer des attaques dont le coût dépasse le bénéfice apparent, dans une perspective stratégique à long terme. Il peut maintenir une présence passive dans le réseau pendant des mois ou des années sans se manifester activement (watchers), accumulant des informations permettant ultérieurement de dé-anonymiser des participants ou de reconstruire des stratégies opérationnelles.
Dans un contexte spatial ou militaire embarqué, c'est ce second modèle qui prévaut. Les hypothèses de sécurité doivent donc être dimensionnées pour un adversaire capable de :
- Capturer physiquement des nœuds et en extraire les secrets ;
- Maintenir une écoute passive à long terme sur tous les canaux visibles ;
- Contrôler silencieusement des nœuds compromis pendant des semaines (Stealthy Byzantine [Amir et al., 2011]) ;
- Analyser les métadonnées de trafic même en présence de chiffrement de contenu.
### 1.4 Confiance Relative : Modèle Continu vs Binaire
Le modèle de confiance binaire (pair de confiance / pair non-fiable) est inadapté aux environnements complexes où les acteurs peuvent être partiellement compromis, temporairement indisponibles, ou simplement peu fiables sans être nécessairement malveillants. La notion de confiance doit être modélisée comme un continuum probabiliste.
Le théorème FLP [Fischer, Lynch & Paterson, 1985] établit qu'en présence d'asynchronisme, un observateur ne peut distinguer de manière déterministe un nœud défaillant d'un nœud simplement lent. Cela implique que tout jugement de confiance sur un pair distant est nécessairement probabiliste : il repose sur une accumulation d'observations cohérentes sur le temps, non sur une preuve déterministe.
Ce constat motive l'adoption de modèles de confiance à scores continus, intégrant des dimensions comportementales (disponibilité historique, précision des réponses, cohérence des données annoncées) plutôt que des jugements binaires susceptibles d'être manipulés par un adversaire capable de jouer sur les délais d'observation.
### 1.5 Propriétés de Sécurité Visées
Pour ce contexte, les propriétés de sécurité cibles sont les suivantes :
- **Authenticité** : chaque pair peut prouver son identité sans tiers de confiance (identité auto-certifiée).
- **Intégrité** : les données transmises ou stockées ne peuvent être altérées sans détection.
- **Disponibilité** : le système continue de fonctionner en mode dégradé en cas d'indisponibilité partielle.
- **Confidentialité** : le contenu des échanges est opaque pour les observateurs non autorisés.
- **Souveraineté** : les données appartiennent à leur créateur, qui contrôle leur cycle de vie.
- **Non-traçabilité** : l'existence même des échanges doit, dans le cas le plus hostile, être dissimulée.
Ces propriétés sont partiellement antagonistes. La disponibilité et la confidentialité entrent souvent en tension : un système qui chiffre toutes ses communications peut voir sa disponibilité dégradée par l'overhead cryptographique ; un système qui optimise la découverte de pairs maximise la visibilité des participants.
---
## 2. Risques de la Couche Réseau et Transport
### 2.1 Man-in-the-Middle et Attaques Actives
L'attaque MitM (Man-in-the-Middle) consiste pour un adversaire actif à s'interposer entre deux pairs communicants, en se faisant passer pour chacun d'eux auprès de l'autre. Sans authentification mutuelle des parties, cette attaque est triviale sur tout réseau P2P non protégé.
Le Noise Protocol Framework [Perrin, 2018] offre une réponse adaptée : il fournit des primitives de handshake cryptographique permettant l'authentification mutuelle et l'établissement de canaux chiffrés sans infrastructure PKI centralisée. Le pattern Noise_XX permet une authentification mutuelle des clés publiques sans nécessiter une autorité de certification. Sa légèreté en fait un candidat adapté aux contraintes embarquées.
La principale limite résiduelle est celle du premier contact : si un pair ne connaît pas a priori la clé publique de son interlocuteur, le premier échange est vulnérable à un MitM qui substituerait sa propre clé (problème TOFU : Trust On First Use).
### 2.2 Eclipse Attacks — Isolation d'un Pair du Réseau
Une attaque par éclipse (eclipse attack) vise à monopoliser l'ensemble des connexions entrantes et sortantes d'un pair cible, de sorte que toutes les informations que ce pair reçoit du réseau passent par des nœuds contrôlés par l'adversaire [Heilman et al., 2015]. Une fois isolé, le pair peut être manipulé : l'adversaire lui présente une vue falsifiée du réseau, peut censurer ses messages sortants, ou lui soumettre de fausses transactions.
Heilman et al. démontrent que cette attaque est réalisable sur Bitcoin avec un nombre limité de connexions adversariales, en exploitant la structure de la table de routage. Pour les réseaux P2P plus généraux, la résistance à cette attaque passe par la diversification des sources de pairs (bootstrap multi-seeds, connexions aléatoires dans des sous-espaces distincts du graphe).
### 2.3 Partitionnement Réseau et Problème CAP
Le théorème CAP, formulé par Brewer [Brewer, 2000] et formalisé par Gilbert et Lynch [Gilbert & Lynch, 2002], établit qu'un système distribué ne peut simultanément garantir la cohérence (Consistency), la disponibilité (Availability) et la tolérance au partitionnement réseau (Partition tolerance). Face à un partitionnement, tout système doit choisir entre cohérence et disponibilité.
Dans un contexte DTN, les partitions réseau ne sont pas des événements exceptionnels : elles sont la norme opérationnelle. Un système de découverte de pairs en contexte spatial doit délibérément favoriser la disponibilité (AP) sur la cohérence forte : un nœud doit pouvoir continuer à opérer même s'il ne peut pas vérifier en temps réel l'état de ses pairs. La cohérence forte est réservée aux opérations critiques (règlement de transactions monétaires), où elle doit être assurée par un mécanisme de consensus distinct.
### 2.4 Traffic Analysis par Acteurs Passifs — Le Watcher Problem
Le chiffrement de contenu ne suffit pas à protéger contre un adversaire passif capable d'analyser les métadonnées de trafic. Murdoch et Danezis [Murdoch & Danezis, 2005] ont démontré que même sur le réseau Tor, l'analyse des patterns de trafic (timing, volume, fréquence) permet de corréler des flux anonymisés avec leurs sources avec une précision significative.
Dans un réseau de découverte P2P, les métadonnées révèlent des informations critiques même en l'absence d'accès au contenu :
- La fréquence des heartbeats révèle les cycles opérationnels d'un nœud ;
- Le volume des échanges avec certains pairs révèle des relations privilégiées ;
- Les patterns de connexion/déconnexion révèlent les horaires d'activité ;
- L'évolution du nombre de pairs connus révèle des phases d'expansion ou de repli.
Sun et al. [Sun et al., 2015] ont quantifié l'efficacité de ces attaques sur les réseaux Bitcoin, montrant que l'identification de l'émetteur d'une transaction est possible avec une probabilité élevée à partir de l'analyse des patterns de diffusion, même sans accès au contenu chiffré.
### 2.5 Spécificités DTN/Spatial : Latences, Connectivité Intermittente, Impossibilité TCP
Les protocoles P2P conçus pour des réseaux terrestres supposent des connexions TCP stables, des latences de l'ordre de la milliseconde à la centaine de milliseconde, et une disponibilité continue des pairs actifs. Aucune de ces hypothèses ne tient dans un contexte spatial :
- Les communications Terre-orbite basse (LEO) introduisent des latences de 20 à 600 ms selon l'élévation ;
- Les fenêtres de visibilité entre satellites imposent des périodes de contact discontinues ;
- L'énergie disponible contraint le temps de transmission, forçant des modes de veille.
Ces contraintes rendent TCP inutilisable dans sa forme standard : les mécanismes de retransmission, de contrôle de flux et de gestion des timeouts sont dimensionnés pour des latences sub-secondes. Les protocoles de couche bundle définis dans la RFC 4838 [Cerf et al., 2007] offrent une alternative, mais leur adoption dans les systèmes P2P est embryonnaire.
---
## 3. Risques de la Couche de Découverte P2P (Sans Confiance)
### 3.1 Sybil Attack : Création Massive d'Identités
L'attaque Sybil, formalisée par Douceur [Douceur, 2002], consiste pour un adversaire à créer un grand nombre d'identités pseudonymes dans un réseau P2P, lui permettant d'acquérir une influence disproportionnée sur les mécanismes de consensus, de routage ou de réputation.
Le résultat fondamental de Douceur est que cette attaque est **insoluble sans coût d'entrée ou autorité centrale**. En l'absence d'un mécanisme rendant la création d'identités coûteuse (preuve de travail, preuve d'enjeu, dépôt cryptoéconomique) ou d'une autorité centrale capable de vérifier les identités, aucun système P2P ne peut garantir une résistance Sybil complète.
Dans un contexte embarqué à ressources limitées, les mécanismes de preuve de travail sont énergétiquement prohibitifs. Les solutions alternatives incluent les clés pré-partagées (PSK) organisationnelles pour les réseaux fermés, et le scoring comportemental pour pénaliser les pairs nouveaux ou peu fiables — sans résoudre complètement le problème pour les réseaux ouverts.
### 3.2 Routing Table Poisoning
Le DHT Kademlia [Maymounkov & Mazières, 2002] organise les pairs dans un espace d'adressage XOR, permettant la découverte de ressources en O(log n) sauts. Cependant, sa table de routage est vulnérable à l'empoisonnement : un adversaire peut insérer de fausses entrées dans la table d'un pair cible, redirigeant le trafic vers des nœuds malveillants.
Baumgart et Meinert [Baumgart & Meinert, 2007] ont proposé S/Kademlia comme extension sécurisée de Kademlia, ajoutant des contraintes cryptographiques sur la génération des identifiants de nœuds et des signatures sur les messages de routage. S/Kademlia réduit significativement la facilité d'empoisonnement des tables de routage, mais n'élimine pas complètement le risque pour un adversaire disposant de suffisamment de nœuds.
### 3.3 Attaque d'Éclipse sur DHT
Singh et al. [Singh et al., 2006] ont démontré des attaques d'éclipse spécifiques aux DHT : en contrôlant un sous-ensemble de nœuds strategiquement positionnés dans l'espace d'adressage, un adversaire peut isoler une région de la DHT, interceptant toutes les requêtes de découverte portant sur des identifiants tombant dans cette région.
Cette attaque est particulièrement dangereuse pour les systèmes où la DHT est utilisée non seulement pour la découverte mais aussi pour le stockage de données : les enregistrements stockés dans la région éclipsée peuvent être supprimés, falsifiés, ou rendus inaccessibles sans détection immédiate.
### 3.4 Bootstrap Poisoning : Compromission des Seeds d'Amorçage
Tout réseau P2P doit résoudre le problème du premier contact : un nœud rejoignant le réseau doit connaître au moins un pair existant pour s'y connecter. Les seeds d'amorçage (bootstrap nodes) constituent donc un point de défaillance unique ou une cible prioritaire pour un adversaire.
Bitcoin résout partiellement ce problème via des DNS seeds distribués opérés par plusieurs entités indépendantes. Tor utilise des directory authorities dont les clés publiques sont codées en dur dans le client. Dans un réseau embarqué hostile, ces approches présentent des limitations : les DNS seeds peuvent être censurés, et les clés publiques codées en dur peuvent être compromises si un nœud est capturé physiquement.
Un adversaire capable de contrôler les seeds d'amorçage peut orienter tous les nouveaux nœuds vers une partition malveillante du réseau, les isolant efficacement du réseau légitime.
### 3.5 Churn Excessif et Instabilité du Réseau
Rhea et al. [Rhea et al., 2004] ont étudié l'impact du churn (arrivées et départs fréquents de nœuds) sur la stabilité des DHT. Un churn élevé dégrade les performances de routage, fragmente les tables de routage, et peut rendre certaines régions de la DHT inaccessibles temporairement.
Dans un contexte DTN, le churn n'est pas exceptionnel mais structurel. Les nœuds apparaissent et disparaissent selon les fenêtres de contact, créant un churn permanent. Les mécanismes de maintenance des DHT (requêtes périodiques de vérification des voisins, repopulation des k-buckets) doivent être adaptés pour ne pas consommer une bande passante prohibitive dans ce contexte.
Un adversaire peut exploiter le churn en injectant massivement des nœuds éphémères : ces nœuds saturent les tables de routage, déplacent les nœuds légitimes, puis disparaissent avant d'être pénalisés par les mécanismes de scoring.
### 3.6 Collusion entre Pairs et Faux Consensus de Scoring
Les systèmes de réputation décentralisés sont vulnérables aux attaques de collusion : un groupe de nœuds malveillants peut s'attribuer mutuellement de bons scores, se recommander mutuellement, et dégrader les scores des nœuds légitimes par des témoignages falsifiés.
Cette attaque est particulièrement insidieuse car elle est difficile à distinguer d'un comportement légitime sans information hors-bande sur la topologie réelle du réseau. Les mécanismes de témoin croisé (un tiers indépendant observe le comportement du pair) réduisent ce risque mais ne l'éliminent pas si l'adversaire contrôle suffisamment de nœuds pour couvrir tous les angles d'observation.
### 3.7 Injection de Faux Enregistrements de Présence
Dans un contexte hostile, un adversaire peut injecter de faux enregistrements de présence dans la DHT, annonçant l'existence de ressources ou de nœuds fictifs. Cette pollution de la couche de découverte a plusieurs effets négatifs :
- Elle gaspille la bande passante des pairs qui tentent de contacter des nœuds inexistants ;
- Elle peut masquer la découverte de ressources légitimes en saturant les réponses de fausses entrées ;
- Elle peut servir de leurre pour identifier quels nœuds recherchent quels types de ressources (surveillance active via pot de miel).
---
## 4. Risques des Transactions de Données
### 4.1 Data Poisoning : Altération Silencieuse des Enregistrements
La DHT Kademlia stocke les enregistrements à proximité des nœuds dont l'identifiant est proche de la clé de l'enregistrement. Un adversaire contrôlant des nœuds dans une région de l'espace d'adressage peut altérer silencieusement les enregistrements qui lui sont confiés, substituant des données falsifiées aux données légitimes.
La mitigation standard repose sur les enregistrements signés : chaque enregistrement porte la signature de son créateur, permettant de détecter toute altération. Cependant, cette protection suppose que la clé publique du créateur est connue et authentique — ce qui ramène au problème de la distribution des clés publiques.
### 4.2 Replay Attacks sur les Records Signés
Un adversaire peut capturer un enregistrement signé valide et le rejouer ultérieurement, même après que son auteur a révoqué ou remplacé cet enregistrement. En l'absence de mécanisme de nonce ou de timestamp vérifié cryptographiquement, les enregistrements capturés restent valides indéfiniment du point de vue de leur signature.
La protection standard combine les timestamps et les numéros de séquence dans la donnée signée. Cependant, dans un réseau DTN où les horloges ne sont pas nécessairement synchronisées, la vérification des timestamps introduit des contraintes supplémentaires.
### 4.3 Attaque de Tombstone Malveillante
Un tombstone est un enregistrement spécial signalant la suppression ou la révocation d'un enregistrement précédent. Un adversaire en possession de la clé privée d'un pair compromis peut émettre de faux tombstones pour invalider des enregistrements légitimes, effaçant effectivement la présence d'un nœud ou la disponibilité d'une ressource du point de vue du réseau.
Cette attaque est particulièrement grave dans les réseaux à haute durée de vie où les enregistrements ne se périmissent pas rapidement : un tombstone malveillant peut effacer des années d'historique légitime.
### 4.4 Souveraineté de la Donnée : Contrôle du Cycle de Vie
La question de qui contrôle l'expiration et la révocation d'un enregistrement est non triviale dans un réseau décentralisé. Dans les architectures DHT standard, les enregistrements expirent naturellement après un TTL fixe, mais leur renouvellement est à la charge du créateur. Si le créateur est temporairement indisponible (contexte DTN), ses enregistrements expirent, le rendant invisible du réseau.
Inversement, si les enregistrements sont trop durables, un pair compromis ou supprimé reste visible dans la DHT pendant une durée excessive. L'équilibre entre durabilité et fraîcheur des enregistrements constitue un paramètre critique dont les valeurs optimales dépendent du profil de connectivité des nœuds.
### 4.5 Inférence par Analyse de Présence
Même en supposant que le contenu des enregistrements est chiffré, l'analyse des patterns de présence révèle des informations sensibles. La fréquence des heartbeats d'un nœud révèle son cycle opérationnel. L'apparition simultanée de plusieurs nœuds révèle une coordination. La corrélation des moments d'apparition et de disparition avec des événements externes peut permettre de reconstruire des plans opérationnels.
Ce risque s'applique même dans un réseau entièrement chiffré : les métadonnées de trafic (qui contacte qui, quand, avec quelle fréquence) constituent une source d'information indépendante du contenu.
### 4.6 Linkabilité : Corrélation d'Identités Pseudonymes
Dans les réseaux P2P utilisant des identités pseudonymes (clés publiques sans identité réelle associée), la corrélation de sessions successives peut permettre de relier différentes identités pseudonymes à un même acteur physique. Si un nœud utilise systématiquement les mêmes sous-ensembles de pairs pour se connecter, cette structure relationnelle constitue une empreinte distinctive.
La linkabilité est particulièrement problématique dans les contextes où un acteur doit interagir successivement avec des pairs dont certains peuvent être adversariaux.
---
## 5. Risques des Transactions Monétaires Décentralisées
### 5.1 Double-Spend dans un Réseau AP
Nakamoto [Nakamoto, 2008] a proposé le premier mécanisme pratique de prévention du double-spend dans un réseau pair-à-pair sans autorité centrale, en s'appuyant sur une chaîne de blocs et une preuve de travail. Ce mécanisme offre une finalité probabiliste : une transaction devient de plus en plus difficile à annuler à mesure que des blocs s'accumulent au-dessus d'elle.
Dans un réseau AP (disponibilité prioritaire sur cohérence), deux nœuds partitionnés peuvent chacun valider indépendamment une transaction utilisant le même actif, créant un conflit qui ne pourra être résolu qu'à la réunification du réseau. Les protocoles classiques de consensus blockchain (PoW, PoS) supposent une connectivité suffisante pour que les messages de consensus atteignent une majorité de validateurs en temps raisonnable — hypothèse violée en contexte DTN.
### 5.2 Front-Running et MEV
Daian et al. [Daian et al., 2020] ont documenté le phénomène de Miner Extractable Value (MEV) : les validateurs de transactions (mineurs ou stakers) peuvent réordonner, insérer ou censurer des transactions au sein d'un bloc pour extraire une valeur maximale, au détriment des utilisateurs ordinaires. Dans un marché de ressources décentralisé, ce phénomène peut prendre la forme d'enchères truquées, de saisie prioritaire de ressources convoitées, ou de censure de transactions concurrentes.
Le MEV n'est pas une attaque dans le sens traditionnel du terme : c'est une exploitation parfaitement légale des règles du protocole par les validateurs. Sa prévention requiert des mécanismes de protection de l'ordre des transactions (commit-reveal schemes, batched auctions à ordonnancement aléatoire).
### 5.3 Oracle Problem
Szabo [Szabo, 1997] a introduit la notion de smart contract — un contrat auto-exécutant dont les termes sont encodés directement dans du code informatique. Un smart contract de règlement de ressources doit typiquement se déclencher lorsqu'une ressource a été effectivement consommée. Mais comment un smart contract, exécuté dans un environnement blockchain fermé, peut-il vérifier un événement du monde réel (exécution d'un calcul, livraison d'une donnée) ?
Ce problème d'oracle est fondamentalement non résolu. Les solutions existantes (oracles décentralisés comme Chainlink, attestations TEE) réduisent la surface de confiance requise mais ne l'éliminent pas.
### 5.4 Smart Contract Bugs et Reentrancy
L'exploit du DAO en 2016 a démontré de manière spectaculaire les risques des smart contracts : un bug de reentrancy dans le contrat a permis à un attaquant de drainer environ 60 millions de dollars. Luu et al. [Luu et al., 2016] ont documenté systématiquement les classes de vulnérabilités dans les smart contracts Ethereum, incluant la reentrancy, les problèmes de timestamp, les conditions de course, et les débordements arithmétiques.
Dans un contexte de marché de ressources distribuées, les smart contracts gèrent potentiellement des actifs de valeur significative. La vérification formelle de ces contrats, quoique possible pour des contrats simples, reste hors de portée pour des contrats complexes.
### 5.5 Confidentialité des Transactions et Transparence Blockchain
La transparence totale des blockchains publiques comme Ethereum ou Bitcoin est antagoniste à la souveraineté des données. Meiklejohn et al. [Meiklejohn et al., 2013] ont démontré que les transactions Bitcoin, supposément pseudonymes, peuvent être reliées à des entités réelles avec un taux de succès élevé par analyse de graphe des transactions, corrélation avec des événements externes, et utilisation des adresses de réutilisation.
Dans un contexte opérationnel hostile, la simple visibilité des volumes de transactions entre acteurs peut révéler des informations stratégiques critiques : qui paie qui, combien, à quelle fréquence, pour quel type de ressource.
### 5.6 Finality et Latence : Inadaptation aux Environnements DTN
Les blockchains à preuve de travail offrent une finalité probabiliste : une transaction confirmée par 6 blocs sur Bitcoin est considérée pratiquement irréversible, mais ce processus prend environ une heure dans des conditions normales. Les blockchains à consensus BFT (Tendermint, HotStuff) offrent une finalité déterministe en quelques secondes, mais supposent une connectivité suffisante entre les validateurs.
Dans un réseau DTN à connectivité intermittente, un validateur peut être absent pendant plusieurs heures. Si sa participation est requise pour atteindre le quorum, les transactions sont bloquées pendant toute la durée de son absence.
---
## 6. Risques Spécifiques au Contexte Spatial et Embarqué
### 6.1 Connectivité Intermittente et Impossibilité de Heartbeat Continu
Les systèmes de détection de panne par heartbeat périodique supposent une connectivité continue entre les nœuds surveillés et les observateurs. Dans un contexte DTN, un nœud peut être legitimimement injoignable pendant plusieurs heures sans être défaillant. Un mécanisme de détection de panne naïf basé sur des timeouts courts produira des faux positifs massifs, dégradant la qualité du réseau de voisinage.
La distinction entre "nœud absent (normal)" et "nœud défaillant ou malveillant" requiert une connaissance du calendrier de contact prévisible — information qui peut elle-même être sensible et non publiable dans un réseau hostile.
### 6.2 Bande Passante Limitée
Les liens de communication satellites présentent des débits asymétriques et limités. Les protocoles P2P verbeux (abondance de messages de maintien de connexion, de propagation de rumeurs, de synchronisation de tables de routage) peuvent consommer une fraction substantielle de la bande passante disponible, laissant peu de capacité pour le trafic applicatif.
Les algorithmes de consensus blockchain (PoW en particulier) sont particulièrement inadaptés : la propagation de blocs volumineux sur des liens à bande passante limitée peut prendre plusieurs minutes, dégradant les performances et créant des conditions propices aux forks.
### 6.3 Contraintes Énergétiques
Les nœuds embarqués spatiaux opèrent sur des bilans énergétiques stricts. Le Proof of Work, qui requiert une dépense énergétique continue proportionnelle à la sécurité offerte, est structurellement incompatible avec ces contraintes. Les algorithmes de consensus alternatifs (Proof of Stake, BFT) présentent des profils énergétiques compatibles mais requièrent une disponibilité réseau plus élevée.
### 6.4 Isolation Planifiée et Mode Dégradé
Dans les opérations spatiales, des périodes d'isolation planifiée (passage dans l'ombre, maintenance silence radio, orbite en dehors de la couverture sol) font partie du profil opérationnel normal. Le réseau doit être conçu pour fonctionner en mode complètement autonome pendant ces périodes, sans dégradation irréversible de l'état du réseau.
Cette contrainte impose que tout état critique soit répliqué localement sur le nœud, que les décisions opérationnelles ne requièrent pas de consensus réseau en temps réel, et que la resynchronisation après reconnexion soit déterministe et complète.
### 6.5 Attaque Physique : Extraction des Secrets
Un adversaire physiquement présent peut capturer un nœud embarqué et en extraire les secrets cryptographiques (clés privées, PSK, identifiants de réseau). Cette attaque compromet non seulement le nœud capturé mais potentiellement l'ensemble du réseau si les clés partagées ne sont pas renouvelées rapidement.
La mitigation passe par des modules de sécurité matériels (HSM, Secure Enclave) rendant l'extraction difficile, des mécanismes de rotation régulière des clés, et des procédures de révocation réactives permettant d'invalider rapidement les clés d'un nœud capturé.
### 6.6 Compromission de Longue Durée : Stealthy Byzantine
Amir et al. [Amir et al., 2011] ont documenté les "stealthy Byzantine attacks" : un nœud compromis peut se comporter normalement pendant une longue période pour éviter la détection, puis activer un comportement malveillant coordonné au moment le plus opportun. Ce mode d'attaque est particulièrement dangereux car les mécanismes de réputation basés sur l'historique comportemental accordent une haute confiance aux nœuds de longue date, précisément les cibles préférées de ce type de compromission.
La seule mitigation partielle est la vérification périodique de la cohérence des comportements sur des dimensions difficiles à simuler (précision des challenges cryptographiques, cohérence des données annoncées avec des sources indépendantes), combinée à une surveillance croisée par des témoins multiples.
---
## 7. Gestion de la Confiance Relative et des Consortiums
### 7.1 Pourquoi la Confiance Binaire Échoue dans un Réseau Hostile
Un modèle de confiance binaire associe à chaque pair un état discret : "de confiance" ou "non-fiable". Ce modèle échoue dans les environnements complexes pour plusieurs raisons :
Premièrement, un pair peut être partiellement compromis — fiable pour certaines opérations mais non pour d'autres. Deuxièmement, la confiance est temporelle : un pair fiable hier peut être compromis aujourd'hui. Troisièmement, le modèle binaire est vulnérable aux promotions frauduleuses : un adversaire peut se comporter parfaitement pendant une longue période pour atteindre le statut "de confiance", puis exploiter ce statut.
Un modèle continu à score multidimensionnel offre une résistance bien supérieure en rendant la manipulation plus coûteuse et plus facile à détecter : altérer simultanément plusieurs dimensions comportementales de manière cohérente requiert une sophistication bien supérieure à la simple patience.
### 7.2 Trust on First Use (TOFU) et ses Limites
Le paradigme TOFU accepte la clé publique d'un pair lors du premier contact et la considère ensuite comme authentique. Cette approche est pragmatique mais présente une vulnérabilité critique au premier contact : un adversaire capable d'intercepter cette première connexion peut substituer sa propre clé.
SSH utilise TOFU couplé à une alerte en cas de changement ultérieur de la clé. Cette approche est raisonnable pour les réseaux à topologie relativement stable, mais problématique dans les réseaux où les nœuds sont fréquemment remplacés ou réinitialisés.
### 7.3 Consortiums à Confiance Choisie : Modèles de Gouvernance
Les consortiums permettent de définir explicitement des sous-groupes de pairs pour lesquels une confiance a priori plus élevée est accordée, sur la base d'une relation organisationnelle hors-bande. Dans un contexte spatial ou militaire, ces consortiums correspondent typiquement à des coalitions ou à des unités opérationnelles.
La gouvernance de ces consortiums soulève des questions : qui peut admettre de nouveaux membres ? Selon quel mécanisme ? Quel quorum est requis pour l'exclusion d'un membre ? Ces décisions de gouvernance ne peuvent elles-mêmes être prises de manière décentralisée que si un mécanisme de consensus approprié existe pour les supporters.
### 7.4 Révocation de Confiance Sans Autorité Centrale : Le Problème Non Résolu
La révocation d'un membre d'un consortium sans autorité centrale est un problème partiellement ouvert. Les Certificate Revocation Lists (CRL) et l'OCSP des PKI traditionnelles supposent une autorité de révocation centrale. Dans un réseau décentralisé, la révocation doit être propagée de manière épidémique — mais une propagation épidémique peut être bloquée si l'adversaire contrôle les canaux de communication des nœuds informés de la révocation.
### 7.5 Propagation Épidémique des Signaux de Méfiance
Das, Gupta et Motivala [Das et al., 2002] ont proposé le protocole SWIM (Scalable Weakly-consistent Infection-style Process Group Membership) pour la détection de panne distribuée. Le principe de base — infection-style propagation — peut être étendu à la propagation de signaux de méfiance : un nœud ayant observé un comportement suspect diffuse cette information à un sous-ensemble aléatoire de ses pairs, qui la propagent à leur tour.
Cette approche offre une convergence probabiliste en O(log n) étapes, mais est vulnérable si l'adversaire contrôle une fraction significative des canaux de propagation ou peut identifier et bloquer sélectivement les messages de méfiance le concernant.
---
## 8. Synthèse et Classification des Risques
Le tableau suivant synthétise les risques identifiés, leur criticité selon le contexte, et les mitigations disponibles :
| Risque | Couche | Criticité (réseau privé) | Criticité (réseau hostile) | Mitigation principale | Limite résiduelle |
|---|---|---|---|---|---|
| MitM actif | Transport | Élevée | Critique | Noise Protocol Framework (mutual auth) | Vulnérabilité TOFU au premier contact |
| Eclipse attack | Réseau | Moyenne | Élevée | Diversification des connexions, multi-seeds | Adversaire contrôlant un ISP |
| Partitionnement réseau | Réseau | Faible | Élevée | Choix AP délibéré, cohérence relaxée | Forks d'état lors de la réunification |
| Traffic analysis | Transport | Faible | Critique | Padding, traffic shaping, mixnets | Coût prohibitif à grande échelle |
| Sybil attack | Découverte | Faible (PSK) | Élevée | PSK organisationnelle + scoring comportemental | Réseau ouvert hostile non résolu |
| Routing table poisoning | Découverte | Moyenne | Élevée | S/Kademlia (signatures sur messages) | Adversaire avec fraction > 1/3 |
| Eclipse sur DHT | Découverte | Moyenne | Élevée | Distribution des seeds, diversité des voisins | Compromis performance/sécurité |
| Bootstrap poisoning | Découverte | Faible | Critique | Multi-seeds distribués, clés codées en dur | Seeds = ancre de confiance unique |
| Churn adversarial | Découverte | Faible | Moyenne | Rate limiting, délai de grâce | Difficile à distinguer du churn normal |
| Collusion scoring | Découverte | Faible | Élevée | Témoin croisé, challenges multidimensionnels | Insoluble si adversaire > 1/3 |
| Data poisoning | Données | Élevée | Critique | Records signés | Distribution des clés publiques |
| Replay attack | Données | Moyenne | Élevée | Nonce + timestamp dans payload signé | Synchronisation d'horloge en DTN |
| Tombstone malveillant | Données | Moyenne | Élevée | Signature + timestamp de révocation | Compromission de clé privée |
| Inférence par présence | Données | Faible | Critique | Padding temporel, faux heartbeats | Coût en bande passante |
| Linkabilité | Données | Faible | Élevée | Rotation d'identité, padding | Difficulté opérationnelle |
| Double-spend | Monétaire | Élevée | Élevée | Consensus BFT (finalité déterministe) | Indisponibilité des validateurs |
| MEV / Front-running | Monétaire | Moyenne | Élevée | Commit-reveal, batch auctions | Résistance imparfaite |
| Oracle problem | Monétaire | Élevée | Élevée | TEE attestations, ZK-proofs | Surface de confiance matérielle |
| Smart contract bug | Monétaire | Élevée | Élevée | Vérification formelle, audits | Impossibilité de vérification complète |
| Transparence blockchain | Monétaire | Moyenne | Critique | ZK-SNARKs, TEE confidential contracts | Maturité des solutions (ZK) |
| Stealthy Byzantine | Tous | Faible | Élevée | Scoring multidimensionnel + témoins | Détection possible seulement a posteriori |
| Capture physique | Tous | Faible | Critique | HSM, rotation régulière des clés | Fenêtre de compromission avant révocation |
---
## Références Bibliographiques
**[Amir et al., 2011]** Amir, Y., Coan, B., Kirsch, J., & Lane, J. (2011). Prime: Byzantine Replication Under Attack. *IEEE Transactions on Dependable and Secure Computing*, 8(4), 564577.
**[Baumgart & Meinert, 2007]** Baumgart, I., & Meinert, S. (2007). S/Kademlia: A practicable approach towards secure key-based routing. In *Proceedings of the 2007 International Conference on Parallel and Distributed Systems (ICPADS)*, pp. 18.
**[Brewer, 2000]** Brewer, E. A. (2000). Towards robust distributed systems. In *Proceedings of the 19th Annual ACM Symposium on Principles of Distributed Computing (PODC)*, pp. 7. (Keynote address).
**[Cerf et al., 2007]** Cerf, V., Burleigh, S., Hooke, A., Torgerson, L., Durst, R., Scott, K., Fall, K., & Weiss, H. (2007). Delay-Tolerant Networking Architecture. *RFC 4838*, IETF.
**[Daian et al., 2020]** Daian, P., Goldfeder, S., Kell, T., Li, Y., Zhao, X., Bentov, I., Breidenbach, L., & Juels, A. (2020). Flash Boys 2.0: Frontrunning in Decentralized Exchanges, Miner Extractable Value, and Consensus Instability. In *Proceedings of the 2020 IEEE Symposium on Security and Privacy (S&P)*, pp. 910927.
**[Das et al., 2002]** Das, A., Gupta, I., & Motivala, A. (2002). SWIM: Scalable Weakly-consistent Infection-style Process Group Membership Protocol. In *Proceedings of the 2002 International Conference on Dependable Systems and Networks (DSN)*, pp. 303312.
**[Douceur, 2002]** Douceur, J. R. (2002). The Sybil Attack. In *Proceedings of the 1st International Workshop on Peer-to-Peer Systems (IPTPS)*, LNCS 2429, pp. 251260.
**[Fall, 2003]** Fall, K. (2003). A delay-tolerant network architecture for challenged internets. In *Proceedings of the 2003 Conference on Applications, Technologies, Architectures, and Protocols for Computer Communications (SIGCOMM)*, pp. 2734.
**[Fischer, Lynch & Paterson, 1985]** Fischer, M. J., Lynch, N. A., & Paterson, M. S. (1985). Impossibility of distributed consensus with one faulty process. *Journal of the ACM (JACM)*, 32(2), 374382.
**[Gilbert & Lynch, 2002]** Gilbert, S., & Lynch, N. (2002). Brewer's conjecture and the feasibility of consistent, available, partition-tolerant web services. *ACM SIGACT News*, 33(2), 5159.
**[Heilman et al., 2015]** Heilman, E., Kendler, A., Zohar, A., & Goldberg, S. (2015). Eclipse Attacks on Bitcoin's Peer-to-Peer Network. In *Proceedings of the 24th USENIX Security Symposium*, pp. 129144.
**[Lamport, Shostak & Pease, 1982]** Lamport, L., Shostak, R., & Pease, M. (1982). The Byzantine Generals Problem. *ACM Transactions on Programming Languages and Systems (TOPLAS)*, 4(3), 382401.
**[Luu et al., 2016]** Luu, L., Chu, D.-H., Olickel, H., Saxena, P., & Hobor, A. (2016). Making Smart Contracts Smarter. In *Proceedings of the 2016 ACM SIGSAC Conference on Computer and Communications Security (CCS)*, pp. 254269.
**[Maymounkov & Mazières, 2002]** Maymounkov, P., & Mazières, D. (2002). Kademlia: A Peer-to-Peer Information System Based on the XOR Metric. In *Proceedings of the 1st International Workshop on Peer-to-Peer Systems (IPTPS)*, LNCS 2429, pp. 5365.
**[Meiklejohn et al., 2013]** Meiklejohn, S., Pomarole, M., Jordan, G., Levchenko, K., McCoy, D., Voelker, G. M., & Savage, S. (2013). A Fistful of Bitcoins: Characterizing Payments Among Men with No Names. In *Proceedings of the 2013 Internet Measurement Conference (IMC)*, pp. 127140.
**[Murdoch & Danezis, 2005]** Murdoch, S. J., & Danezis, G. (2005). Low-Cost Traffic Analysis of Tor. In *Proceedings of the 2005 IEEE Symposium on Security and Privacy (S&P)*, pp. 183195.
**[Nakamoto, 2008]** Nakamoto, S. (2008). Bitcoin: A Peer-to-Peer Electronic Cash System. *Whitepaper*. https://bitcoin.org/bitcoin.pdf
**[Rhea et al., 2004]** Rhea, S., Geels, D., Roscoe, T., & Kubiatowicz, J. (2004). Handling Churn in a DHT. In *Proceedings of the 2004 USENIX Annual Technical Conference*, pp. 127140.
**[Singh et al., 2006]** Singh, A., Castro, M., Druschel, P., & Rowstron, A. (2006). Defending against Eclipse attacks on overlay networks. In *Proceedings of the 11th Workshop on ACM SIGOPS European Workshop*, article 21.
**[Stoica et al., 2003]** Stoica, I., Morris, R., Liben-Nowell, D., Karger, D. R., Kaashoek, M. F., Dabek, F., & Balakrishnan, H. (2003). Chord: A scalable peer-to-peer lookup protocol for internet applications. *IEEE/ACM Transactions on Networking*, 11(1), 1732.
**[Sun et al., 2015]** Sun, Y., Edmundson, A., Vanbever, L., Li, O., Rexford, J., Chiang, M., & Mittal, P. (2015). RAPTOR: Routing Attacks on Privacy in Tor. In *Proceedings of the 24th USENIX Security Symposium*, pp. 271286.
**[Szabo, 1997]** Szabo, N. (1997). Formalizing and securing relationships on public networks. *First Monday*, 2(9).

6
go.mod
View File

@@ -3,7 +3,7 @@ module oc-discovery
go 1.25.0 go 1.25.0
require ( require (
cloud.o-forge.io/core/oc-lib v0.0.0-20260331181901-f3b5a54545ee cloud.o-forge.io/core/oc-lib v0.0.0-20260423081613-747368c79a13
github.com/ipfs/go-cid v0.6.0 github.com/ipfs/go-cid v0.6.0
github.com/libp2p/go-libp2p v0.47.0 github.com/libp2p/go-libp2p v0.47.0
github.com/libp2p/go-libp2p-record v0.3.1 github.com/libp2p/go-libp2p-record v0.3.1
@@ -167,11 +167,11 @@ require (
github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.mongodb.org/mongo-driver v1.17.4 // indirect go.mongodb.org/mongo-driver v1.17.4 // indirect
golang.org/x/crypto v0.47.0 // indirect golang.org/x/crypto v0.47.0
golang.org/x/net v0.49.0 // indirect golang.org/x/net v0.49.0 // indirect
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect golang.org/x/text v0.33.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

18
go.sum
View File

@@ -1,19 +1,5 @@
cloud.o-forge.io/core/oc-lib v0.0.0-20260304145747-e03a0d3dd0aa h1:1wCpI4dwN1pj6MlpJ7/WifhHVHmCE4RU+9klwqgo/bk= cloud.o-forge.io/core/oc-lib v0.0.0-20260423081613-747368c79a13 h1:Qnz5wSliRDl218nK4aCJdPL+rNp55A72UDyUIFDswEQ=
cloud.o-forge.io/core/oc-lib v0.0.0-20260304145747-e03a0d3dd0aa/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA= cloud.o-forge.io/core/oc-lib v0.0.0-20260423081613-747368c79a13/go.mod h1:JynnOb3eMr9VZW1mHq+Vsl3tzx6gPhPsGKpQD/dtEBc=
cloud.o-forge.io/core/oc-lib v0.0.0-20260311072518-933b7147e908 h1:1jz3xI/u2FzCG8phY7ShqADrmCj0mlrdjbdNUosSwgs=
cloud.o-forge.io/core/oc-lib v0.0.0-20260311072518-933b7147e908/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
cloud.o-forge.io/core/oc-lib v0.0.0-20260312073634-2c9c42dd516a h1:oCkb9l/Cvn0x6iicxIydrjfCNU+UHhKuklFgfzDa174=
cloud.o-forge.io/core/oc-lib v0.0.0-20260312073634-2c9c42dd516a/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
cloud.o-forge.io/core/oc-lib v0.0.0-20260312141150-a335c905b3a2 h1:DuB6SDThFVJVQ0iI0pZnBqtCE0uW+SNI7R7ndKixu2k=
cloud.o-forge.io/core/oc-lib v0.0.0-20260312141150-a335c905b3a2/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
cloud.o-forge.io/core/oc-lib v0.0.0-20260318143822-5976795d4406 h1:FN1EtRWn228JprAbnY5K863Fzj+SzMqQtKRtwvECbLw=
cloud.o-forge.io/core/oc-lib v0.0.0-20260318143822-5976795d4406/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
cloud.o-forge.io/core/oc-lib v0.0.0-20260325092016-4580200e8057 h1:pR+lZzcCWZ0kke2r2xXa7OpdbLpPW3gZSWZ8gGHh274=
cloud.o-forge.io/core/oc-lib v0.0.0-20260325092016-4580200e8057/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
cloud.o-forge.io/core/oc-lib v0.0.0-20260331144112-c0722483b86c h1:wTIridvhud8zwMsMkwxgrQ+j+6UAo2IHDr3N80AA6zc=
cloud.o-forge.io/core/oc-lib v0.0.0-20260331144112-c0722483b86c/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
cloud.o-forge.io/core/oc-lib v0.0.0-20260331181901-f3b5a54545ee h1:iJ1kgMbBOBIHwS4jHOVB5zFqOd7J9ZlweQBuchnmvT0=
cloud.o-forge.io/core/oc-lib v0.0.0-20260331181901-f3b5a54545ee/go.mod h1:+ENuvBfZdESSvecoqGY/wSvRlT3vinEolxKgwbOhUpA=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=

View File

@@ -28,7 +28,7 @@ func main() {
conf.GetConfig().NodeEndpointPort = o.GetInt64Default("NODE_ENDPOINT_PORT", 4001) conf.GetConfig().NodeEndpointPort = o.GetInt64Default("NODE_ENDPOINT_PORT", 4001)
conf.GetConfig().IndexerAddresses = o.GetStringDefault("INDEXER_ADDRESSES", "") conf.GetConfig().IndexerAddresses = o.GetStringDefault("INDEXER_ADDRESSES", "")
conf.GetConfig().NanoIDS = o.GetStringDefault("NANO_IDS", "")
conf.GetConfig().PeerIDS = o.GetStringDefault("PEER_IDS", "") conf.GetConfig().PeerIDS = o.GetStringDefault("PEER_IDS", "")
conf.GetConfig().NodeMode = o.GetStringDefault("NODE_MODE", "node") conf.GetConfig().NodeMode = o.GetStringDefault("NODE_MODE", "node")

3
pem/private6.pem Normal file
View File

@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIDo9ZgsqkIxu4Zhk4WY1xa4va1yO3Z6RuXU4K5+amwxE
-----END PRIVATE KEY-----

3
pem/public6.pem Normal file
View File

@@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAGp7zNNHz0fgb71WWJUGilpiONKIMk24MjCx6jJ7CTp8=
-----END PUBLIC KEY-----