diff --git a/dashboards/grafana/BlossomSub.json b/dashboards/grafana/BlossomSub.json index 9bb4532..400e769 100644 --- a/dashboards/grafana/BlossomSub.json +++ b/dashboards/grafana/BlossomSub.json @@ -656,7 +656,8 @@ "value": null } ] - } + }, + "unit": "short" }, "overrides": [] }, @@ -735,7 +736,7 @@ "type": "prometheus", "uid": "${datasource}" }, - "description": "The number of message IDs in IHAVE control messages which have been successfully sent to a remote peer.", + "description": "The number of message IDs in IHAVE control messages which have been sent to a remote peer.", "fieldConfig": { "defaults": { "color": { @@ -783,7 +784,8 @@ "value": null } ] - } + }, + "unit": "short" }, "overrides": [] }, @@ -956,7 +958,8 @@ "value": null } ] - } + }, + "unit": "short" }, "overrides": [] }, @@ -1083,7 +1086,8 @@ "value": null } ] - } + }, + "unit": "short" }, "overrides": [] }, @@ -1383,7 +1387,8 @@ "value": null } ] - } + }, + "unit": "short" }, "overrides": [] }, @@ -1502,13 +1507,525 @@ ], "type": "timeseries" }, + { + "datasource": { + "default": false, + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "The number of message IDs provided in IDONTWANT control messages sent to remote peers.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 47 + }, + "id": 19, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "min", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Name", + "sortDesc": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, rate(blossomsub_idontwant_messages_bucket{job=~\"$job\", instance=~\"$host\", direction=\"send\"}[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "P99", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, rate(blossomsub_idontwant_messages_bucket{job=~\"$job\", instance=~\"$host\", direction=\"send\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "P95", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, rate(blossomsub_idontwant_messages_bucket{job=~\"$job\", instance=~\"$host\", direction=\"send\"}[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "P50", + "range": true, + "refId": "C" + } + ], + "title": "Sent IDONTWANT message count histogram", + "type": "timeseries" + }, + { + "datasource": { + "default": false, + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "The number of message IDs provided in IDONTWANT control messages received from remote peers.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 47 + }, + "id": 20, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "min", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Name", + "sortDesc": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, rate(blossomsub_idontwant_messages_bucket{job=~\"$job\", instance=~\"$host\", direction=\"recv\"}[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "P99", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, rate(blossomsub_idontwant_messages_bucket{job=~\"$job\", instance=~\"$host\", direction=\"recv\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "P95", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, rate(blossomsub_idontwant_messages_bucket{job=~\"$job\", instance=~\"$host\", direction=\"recv\"}[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "P50", + "range": true, + "refId": "C" + } + ], + "title": "Received IDONTWANT message count histogram", + "type": "timeseries" + }, + { + "datasource": { + "default": false, + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "The number of message IDs provided in IDONTWANT control messages which have not been sent to a remote peer due to an error (usually a full queue).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 56 + }, + "id": 21, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "min", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Name", + "sortDesc": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, rate(blossomsub_idontwant_messages_bucket{job=~\"$job\", instance=~\"$host\", direction=\"drop\"}[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "P99", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, rate(blossomsub_idontwant_messages_bucket{job=~\"$job\", instance=~\"$host\", direction=\"drop\"}[$__rate_interval]))", + "instant": false, + "legendFormat": "P95", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, rate(blossomsub_idontwant_messages_bucket{job=~\"$job\", instance=~\"$host\", direction=\"drop\"}[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "P50", + "range": true, + "refId": "C" + } + ], + "title": "Dropped IDONTWANT message count histogram", + "type": "timeseries" + }, + { + "datasource": { + "default": false, + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "pps" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 56 + }, + "id": 22, + "options": { + "legend": { + "calcs": [ + "lastNotNull", + "min", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "sortBy": "Name", + "sortDesc": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(blossomsub_idontwant_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"drop\"}[$__rate_interval])", + "hide": false, + "instant": false, + "legendFormat": "Dropped", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "rate(blossomsub_idontwant_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"recv\"}[$__rate_interval])", + "hide": false, + "instant": false, + "legendFormat": "Received", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(blossomsub_idontwant_messages_count{job=~\"$job\", instance=~\"$host\", direction=\"send\"}[$__rate_interval])", + "hide": false, + "instant": false, + "legendFormat": "Sent", + "range": true, + "refId": "C" + } + ], + "title": "IDONTWANT message rates", + "type": "timeseries" + }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 47 + "y": 65 }, "id": 8, "panels": [], @@ -1576,7 +2093,7 @@ "h": 9, "w": 24, "x": 0, - "y": 48 + "y": 66 }, "id": 11, "options": { @@ -1749,7 +2266,7 @@ "h": 9, "w": 12, "x": 0, - "y": 57 + "y": 75 }, "id": 9, "options": { @@ -1922,7 +2439,7 @@ "h": 9, "w": 12, "x": 12, - "y": 57 + "y": 75 }, "id": 10, "options": { @@ -2039,7 +2556,7 @@ "h": 1, "w": 24, "x": 0, - "y": 66 + "y": 84 }, "id": 6, "panels": [], @@ -2108,7 +2625,7 @@ "h": 9, "w": 24, "x": 0, - "y": 67 + "y": 85 }, "id": 7, "options": { @@ -2251,6 +2768,6 @@ "timezone": "browser", "title": "BlossomSub", "uid": "ee47pcfax962ob", - "version": 39, + "version": 45, "weekStart": "" } \ No newline at end of file diff --git a/go-libp2p-blossomsub/blossomsub.go b/go-libp2p-blossomsub/blossomsub.go index d16f7e9..997f2a7 100644 --- a/go-libp2p-blossomsub/blossomsub.go +++ b/go-libp2p-blossomsub/blossomsub.go @@ -8,7 +8,6 @@ import ( "math/rand" "slices" "sort" - "sync" "time" pb "source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/pb" @@ -26,6 +25,9 @@ import ( const ( // BlossomSubID_v2 is the protocol ID for version 2.0.0 of the BlossomSub protocol. BlossomSubID_v2 = protocol.ID("/blossomsub/2.0.0") + + // BlossomSubID_v21 is the protocol ID for version 2.1.0 of the BlossomSub protocol. + BlossomSubID_v21 = protocol.ID("/blossomsub/2.1.0") ) // Defines the default BlossomSub parameters. @@ -56,7 +58,10 @@ var ( BlossomSubGraftFloodThreshold = 10 * time.Second BlossomSubMaxIHaveLength = 5000 BlossomSubMaxIHaveMessages = 10 + BlossomSubMaxIDontWantMessages = 5000 BlossomSubIWantFollowupTime = 3 * time.Second + BlossomSubIDontWantMessageThreshold = 1024 // 1KB + BlossomSubIDontWantMessageTTL = 60 // 60 heartbeats / 42 seconds ) // BlossomSubParams defines all the BlossomSub specific parameters. @@ -195,10 +200,21 @@ type BlossomSubParams struct { // MaxIHaveMessages is the maximum number of IHAVE messages to accept from a peer within a heartbeat. MaxIHaveMessages int + // MaxIDontWantMessages is the maximum number of IDONTWANT messages to accept from a peer within a heartbeat. + MaxIDontWantMessages int + // Time to wait for a message requested through IWANT following an IHAVE advertisement. // If the message is not received within this window, a broken promise is declared and // the router may apply bahavioural penalties. IWantFollowupTime time.Duration + + // IDONTWANT is only sent for messages larger than the threshold. This should be greater than + // D_high * the size of the message id. Otherwise, the attacker can do the amplication attack by sending + // small messages while the receiver replies back with larger IDONTWANT messages. + IDontWantMessageThreshold int + + // IDONTWANT is cleared when it's older than the TTL. + IDontWantMessageTTL int } // NewBlossomSub returns a new PubSub object using the default BlossomSubRouter as the router. @@ -215,31 +231,47 @@ func NewBlossomSubWithRouter(ctx context.Context, h host.Host, rt PubSubRouter, // NewBlossomSubRouter returns a new BlossomSubRouter with custom parameters. func NewBlossomSubRouter(h host.Host, params BlossomSubParams, network uint8) *BlossomSubRouter { + protos, feature := BlossomSubDefaultProtocols, BlossomSubDefaultFeatures if network != 0 { - BlossomSubDefaultProtocols[0] = protocol.ID( - string(BlossomSubID_v2) + fmt.Sprintf("-network-%d", network), - ) + protos = append(protos[:0:0], BlossomSubDefaultProtocols...) + for i, p := range protos { + protos[i] = protocol.ID(fmt.Sprintf("%s-network-%d", p, network)) + } + feature = func(f BlossomSubFeature, proto protocol.ID) bool { + switch f { + case BlossomSubFeatureMesh: + return proto == protos[0] || proto == protos[1] + case BlossomSubFeaturePX: + return proto == protos[0] || proto == protos[1] + case BlossomSubFeatureIdontwant: + return proto == protos[0] + default: + return false + } + } } return &BlossomSubRouter{ - peers: make(map[peer.ID]protocol.ID), - mesh: make(map[string]map[peer.ID]struct{}), - fanout: make(map[string]map[peer.ID]struct{}), - lastpub: make(map[string]int64), - gossip: make(map[peer.ID][]*pb.ControlIHave), - control: make(map[peer.ID]*pb.ControlMessage), - cab: pstoremem.NewAddrBook(), - backoff: make(map[string]map[peer.ID]time.Time), - peerhave: make(map[peer.ID]int), - iasked: make(map[peer.ID]int), - outbound: make(map[peer.ID]bool), - connect: make(chan connectInfo, params.MaxPendingConnections), - mcache: NewMessageCache(params.HistoryGossip, params.HistoryLength), - protos: BlossomSubDefaultProtocols, - feature: BlossomSubDefaultFeatures, - tagTracer: newTagTracer(h.ConnManager()), - params: params, - network: network, + peers: make(map[peer.ID]protocol.ID), + mesh: make(map[string]map[peer.ID]struct{}), + fanout: make(map[string]map[peer.ID]struct{}), + lastpub: make(map[string]int64), + gossip: make(map[peer.ID][]*pb.ControlIHave), + control: make(map[peer.ID]*pb.ControlMessage), + cab: pstoremem.NewAddrBook(), + backoff: make(map[string]map[peer.ID]time.Time), + peerhave: make(map[peer.ID]int), + peerdontwant: make(map[peer.ID]int), + unwanted: make(map[peer.ID]map[string]int), + iasked: make(map[peer.ID]int), + outbound: make(map[peer.ID]bool), + connect: make(chan connectInfo, params.MaxPendingConnections), + mcache: NewMessageCache(params.HistoryGossip, params.HistoryLength), + protos: protos, + feature: feature, + tagTracer: newTagTracer(h.ConnManager()), + params: params, + network: network, } } @@ -247,23 +279,25 @@ func NewBlossomSubRouter(h host.Host, params BlossomSubParams, network uint8) *B func DefaultBlossomSubRouter(h host.Host) *BlossomSubRouter { params := DefaultBlossomSubParams() return &BlossomSubRouter{ - peers: make(map[peer.ID]protocol.ID), - mesh: make(map[string]map[peer.ID]struct{}), - fanout: make(map[string]map[peer.ID]struct{}), - lastpub: make(map[string]int64), - gossip: make(map[peer.ID][]*pb.ControlIHave), - control: make(map[peer.ID]*pb.ControlMessage), - backoff: make(map[string]map[peer.ID]time.Time), - peerhave: make(map[peer.ID]int), - iasked: make(map[peer.ID]int), - outbound: make(map[peer.ID]bool), - connect: make(chan connectInfo, params.MaxPendingConnections), - cab: pstoremem.NewAddrBook(), - mcache: NewMessageCache(params.HistoryGossip, params.HistoryLength), - protos: BlossomSubDefaultProtocols, - feature: BlossomSubDefaultFeatures, - tagTracer: newTagTracer(h.ConnManager()), - params: params, + peers: make(map[peer.ID]protocol.ID), + mesh: make(map[string]map[peer.ID]struct{}), + fanout: make(map[string]map[peer.ID]struct{}), + lastpub: make(map[string]int64), + gossip: make(map[peer.ID][]*pb.ControlIHave), + control: make(map[peer.ID]*pb.ControlMessage), + backoff: make(map[string]map[peer.ID]time.Time), + peerhave: make(map[peer.ID]int), + peerdontwant: make(map[peer.ID]int), + unwanted: make(map[peer.ID]map[string]int), + iasked: make(map[peer.ID]int), + outbound: make(map[peer.ID]bool), + connect: make(chan connectInfo, params.MaxPendingConnections), + cab: pstoremem.NewAddrBook(), + mcache: NewMessageCache(params.HistoryGossip, params.HistoryLength), + protos: BlossomSubDefaultProtocols, + feature: BlossomSubDefaultFeatures, + tagTracer: newTagTracer(h.ConnManager()), + params: params, } } @@ -296,7 +330,10 @@ func DefaultBlossomSubParams() BlossomSubParams { GraftFloodThreshold: BlossomSubGraftFloodThreshold, MaxIHaveLength: BlossomSubMaxIHaveLength, MaxIHaveMessages: BlossomSubMaxIHaveMessages, + MaxIDontWantMessages: BlossomSubMaxIDontWantMessages, IWantFollowupTime: BlossomSubIWantFollowupTime, + IDontWantMessageThreshold: BlossomSubIDontWantMessageThreshold, + IDontWantMessageTTL: BlossomSubIDontWantMessageTTL, SlowHeartbeatWarning: 0.1, } } @@ -445,22 +482,23 @@ func WithBlossomSubParams(cfg BlossomSubParams) Option { // is the fanout map. Fanout peer lists are expired if we don't publish any // messages to their bitmask for BlossomSubFanoutTTL. type BlossomSubRouter struct { - p *PubSub - peers map[peer.ID]protocol.ID // peer protocols - direct map[peer.ID]struct{} // direct peers - mesh map[string]map[peer.ID]struct{} // bitmask meshes - fanout map[string]map[peer.ID]struct{} // bitmask fanout - lastpub map[string]int64 // last publish time for fanout bitmasks - gossip map[peer.ID][]*pb.ControlIHave // pending gossip - control map[peer.ID]*pb.ControlMessage // pending control messages - peerhave map[peer.ID]int // number of IHAVEs received from peer in the last heartbeat - iasked map[peer.ID]int // number of messages we have asked from peer in the last heartbeat - outbound map[peer.ID]bool // connection direction cache, marks peers with outbound connections - backoff map[string]map[peer.ID]time.Time // prune backoff - connect chan connectInfo // px connection requests - cab peerstore.AddrBook - meshMx sync.RWMutex - network uint8 + p *PubSub + peers map[peer.ID]protocol.ID // peer protocols + direct map[peer.ID]struct{} // direct peers + mesh map[string]map[peer.ID]struct{} // bitmask meshes + fanout map[string]map[peer.ID]struct{} // bitmask fanout + lastpub map[string]int64 // last publish time for fanout bitmasks + gossip map[peer.ID][]*pb.ControlIHave // pending gossip + control map[peer.ID]*pb.ControlMessage // pending control messages + peerhave map[peer.ID]int // number of IHAVEs received from peer in the last heartbeat + peerdontwant map[peer.ID]int // number of IDONTWANTs received from peer in the last heartbeat + unwanted map[peer.ID]map[string]int // TTL of the message ids peers don't want + iasked map[peer.ID]int // number of messages we have asked from peer in the last heartbeat + outbound map[peer.ID]bool // connection direction cache, marks peers with outbound connections + backoff map[string]map[peer.ID]time.Time // prune backoff + connect chan connectInfo // px connection requests + cab peerstore.AddrBook + network uint8 protos []protocol.ID feature BlossomSubFeatureTest @@ -633,32 +671,25 @@ loop: func (bs *BlossomSubRouter) RemovePeer(p peer.ID) { log.Debugf("PEERDOWN: Remove disconnected peer %s", p) - masks := make([][]byte, 0) - bs.meshMx.Lock() for bitmask, peers := range bs.mesh { if _, ok := peers[p]; !ok { continue } - masks = append(masks, []byte(bitmask)) - } - bs.meshMx.Unlock() - for _, bitmask := range masks { log.Debugf("PEERDOWN: Pruning peer %s from bitmask %s", p, bitmask) - bs.tracer.Prune(p, bitmask) + bs.tracer.Prune(p, []byte(bitmask)) } bs.tracer.RemovePeer(p) delete(bs.peers, p) - bs.meshMx.Lock() for _, peers := range bs.mesh { delete(peers, p) } - bs.meshMx.Unlock() for _, peers := range bs.fanout { delete(peers, p) } delete(bs.gossip, p) delete(bs.control, p) delete(bs.outbound, p) + delete(bs.unwanted, p) } func (bs *BlossomSubRouter) EnoughPeers(bitmask []byte, suggested int) bool { @@ -676,10 +707,8 @@ func (bs *BlossomSubRouter) EnoughPeers(bitmask []byte, suggested int) bool { } } - bs.meshMx.RLock() // BlossomSub peers bsPeers = len(bs.mesh[string(bitmask)]) - bs.meshMx.RUnlock() if suggested == 0 { suggested = bs.params.Dlo @@ -709,6 +738,52 @@ func (bs *BlossomSubRouter) AcceptFrom(p peer.ID) AcceptStatus { return bs.gate.AcceptFrom(p) } +// PreValidation sends the IDONTWANT control messages to all the mesh +// peers. They need to be sent right before the validation because they +// should be seen by the peers as soon as possible. +func (bs *BlossomSubRouter) PreValidation(msgs []*Message) { + slicedMessages := make(map[string][]*Message, len(msgs)) + for _, msg := range msgs { + if len(msg.GetData()) < bs.params.IDontWantMessageThreshold { + continue + } + for _, bitmask := range SliceBitmask(msg.GetBitmask()) { + bitmask := string(bitmask) + slicedMessages[bitmask] = append(slicedMessages[bitmask], msg) + } + } + toSend := make(map[peer.ID]map[*Message]struct{}, len(slicedMessages)) + for bitmask, msgs := range slicedMessages { + // send IDONTWANT to all the mesh peers + for p := range bs.mesh[bitmask] { + // send to only peers that support IDONTWANT + if !bs.feature(BlossomSubFeatureIdontwant, bs.peers[p]) { + continue + } + for _, msg := range msgs { + if msg.ReceivedFrom == p { + continue + } + if toSend[p] == nil { + toSend[p] = make(map[*Message]struct{}, len(msgs)) + } + toSend[p][msg] = struct{}{} + } + } + } + for p, msgs := range toSend { + mids := make([][]byte, 0, len(msgs)) + for msg := range msgs { + mids = append(mids, bs.p.idGen.ID(msg)) + } + // shuffle the messages got from the RPC envelope + shuffleBytes(mids) + idontwant := []*pb.ControlIDontWant{{MessageIDs: mids}} + out := rpcWithControl(nil, nil, nil, nil, nil, idontwant) + bs.sendRPC(p, out, true) + } +} + func (bs *BlossomSubRouter) HandleRPC(rpc *RPC) { ctl := rpc.GetControl() if ctl == nil { @@ -719,13 +794,14 @@ func (bs *BlossomSubRouter) HandleRPC(rpc *RPC) { ihave := bs.handleIWant(rpc.from, ctl) prune := bs.handleGraft(rpc.from, ctl) bs.handlePrune(rpc.from, ctl) + bs.handleIDontWant(rpc.from, ctl) if len(iwant) == 0 && len(ihave) == 0 && len(prune) == 0 { return } - out := rpcWithControl(ihave, nil, iwant, nil, prune) - bs.sendRPC(rpc.from, out) + out := rpcWithControl(ihave, nil, iwant, nil, prune, nil) + bs.sendRPC(rpc.from, out, false) } func (bs *BlossomSubRouter) handleIHave(p peer.ID, ctl *pb.ControlMessage) []*pb.ControlIWant { @@ -751,9 +827,7 @@ func (bs *BlossomSubRouter) handleIHave(p peer.ID, ctl *pb.ControlMessage) []*pb iwant := make(map[string]struct{}) for _, ihave := range ctl.GetIhave() { bitmask := ihave.GetBitmask() - bs.meshMx.RLock() _, ok := bs.mesh[string(bitmask)] - bs.meshMx.RUnlock() if !ok { continue } @@ -859,9 +933,7 @@ func (bs *BlossomSubRouter) handleGraft(p peer.ID, ctl *pb.ControlMessage) []*pb continue } - bs.meshMx.RLock() peers, ok := bs.mesh[string(bitmask)] - bs.meshMx.RUnlock() if !ok { // don't do PX when there is an unknown bitmask to avoid leaking our peers doPX = false @@ -950,9 +1022,7 @@ func (bs *BlossomSubRouter) handlePrune(p peer.ID, ctl *pb.ControlMessage) { for _, prune := range ctl.GetPrune() { bitmask := prune.GetBitmask() - bs.meshMx.RLock() peers, ok := bs.mesh[string(bitmask)] - bs.meshMx.RUnlock() if !ok { continue } @@ -984,6 +1054,26 @@ func (bs *BlossomSubRouter) handlePrune(p peer.ID, ctl *pb.ControlMessage) { } } +func (bs *BlossomSubRouter) handleIDontWant(p peer.ID, ctl *pb.ControlMessage) { + if bs.unwanted[p] == nil { + bs.unwanted[p] = make(map[string]int) + } + + // IDONTWANT flood protection + if bs.peerdontwant[p] >= bs.params.MaxIDontWantMessages { + log.Debugf("IDONWANT: peer %s has advertised too many times (%d) within this heartbeat interval; ignoring", p, bs.peerdontwant[p]) + return + } + bs.peerdontwant[p]++ + + // Remember all the unwanted message ids + for _, idontwant := range ctl.GetIdontwant() { + for _, mid := range idontwant.GetMessageIDs() { + bs.unwanted[p][string(mid)] = bs.params.IDontWantMessageTTL + } + } +} + func (bs *BlossomSubRouter) addBackoff(p peer.ID, bitmask []byte, isUnsubscribe bool) { backoff := bs.params.PruneBackoff if isUnsubscribe { @@ -1137,9 +1227,7 @@ func (bs *BlossomSubRouter) Publish(msg *Message) { } // BlossomSub peers - bs.meshMx.RLock() gmap, ok := bs.mesh[string(bitmask)] - bs.meshMx.RUnlock() if !ok { // we are not in the mesh for bitmask, use fanout peers gmap, ok = bs.fanout[string(bitmask)] @@ -1170,14 +1258,19 @@ func (bs *BlossomSubRouter) Publish(msg *Message) { continue } - bs.sendRPC(pid, out) + mid := bs.p.idGen.ID(msg) + // Check if it has already received an IDONTWANT for the message. + // If so, don't send it to the peer + if _, ok := bs.unwanted[pid][string(mid)]; ok { + continue + } + + bs.sendRPC(pid, out, false) } } func (bs *BlossomSubRouter) Join(bitmask []byte) { - bs.meshMx.RLock() gmap, ok := bs.mesh[string(bitmask)] - bs.meshMx.RUnlock() if ok { return } @@ -1212,9 +1305,7 @@ func (bs *BlossomSubRouter) Join(bitmask []byte) { } } - bs.meshMx.Lock() bs.mesh[string(bitmask)] = gmap - bs.meshMx.Unlock() delete(bs.fanout, string(bitmask)) delete(bs.lastpub, string(bitmask)) } else { @@ -1226,9 +1317,7 @@ func (bs *BlossomSubRouter) Join(bitmask []byte) { return !direct && !doBackOff && bs.score.Score(p) >= 0 }) gmap = peerListToMap(peers) - bs.meshMx.Lock() bs.mesh[string(bitmask)] = gmap - bs.meshMx.Unlock() } for p := range gmap { @@ -1239,9 +1328,7 @@ func (bs *BlossomSubRouter) Join(bitmask []byte) { } func (bs *BlossomSubRouter) Leave(bitmask []byte) { - bs.meshMx.RLock() gmap, ok := bs.mesh[string(bitmask)] - bs.meshMx.RUnlock() if !ok { return } @@ -1249,9 +1336,7 @@ func (bs *BlossomSubRouter) Leave(bitmask []byte) { log.Debugf("LEAVE %s", bitmask) bs.tracer.Leave(bitmask) - bs.meshMx.Lock() delete(bs.mesh, string(bitmask)) - bs.meshMx.Unlock() for p := range gmap { log.Debugf("LEAVE: Remove mesh link to %s in %s", p, bitmask) @@ -1266,17 +1351,17 @@ func (bs *BlossomSubRouter) Leave(bitmask []byte) { func (bs *BlossomSubRouter) sendGraft(p peer.ID, bitmask []byte) { graft := []*pb.ControlGraft{{Bitmask: bitmask}} - out := rpcWithControl(nil, nil, nil, graft, nil) - bs.sendRPC(p, out) + out := rpcWithControl(nil, nil, nil, graft, nil, nil) + bs.sendRPC(p, out, false) } func (bs *BlossomSubRouter) sendPrune(p peer.ID, bitmask []byte, isUnsubscribe bool) { prune := []*pb.ControlPrune{bs.makePrune(p, bitmask, bs.doPX, isUnsubscribe)} - out := rpcWithControl(nil, nil, nil, nil, prune) - bs.sendRPC(p, out) + out := rpcWithControl(nil, nil, nil, nil, prune, nil) + bs.sendRPC(p, out, false) } -func (bs *BlossomSubRouter) sendRPC(p peer.ID, out *RPC) { +func (bs *BlossomSubRouter) sendRPC(p peer.ID, out *RPC, fast bool) { // do we own the RPC? own := false @@ -1300,16 +1385,14 @@ func (bs *BlossomSubRouter) sendRPC(p peer.ID, out *RPC) { delete(bs.gossip, p) } - bs.p.peersMx.RLock() mch, ok := bs.p.peers[p] - bs.p.peersMx.RUnlock() if !ok { return } // If we're below the max message size, go ahead and send if out.Size() < bs.p.maxMessageSize { - bs.doSendRPC(out, p, mch) + bs.doSendRPC(out, p, mch, fast) return } @@ -1322,7 +1405,7 @@ func (bs *BlossomSubRouter) sendRPC(p peer.ID, out *RPC) { bs.doDropRPC(out, p, fmt.Sprintf("Dropping oversized RPC. Size: %d, limit: %d. (Over by %d bytes)", rpc.Size(), bs.p.maxMessageSize, rpc.Size()-bs.p.maxMessageSize)) continue } - bs.doSendRPC(rpc, p, mch) + bs.doSendRPC(rpc, p, mch, fast) } } @@ -1336,13 +1419,12 @@ func (bs *BlossomSubRouter) doDropRPC(rpc *RPC, p peer.ID, reason string) { } } -func (bs *BlossomSubRouter) doSendRPC(rpc *RPC, p peer.ID, mch chan *RPC) { - select { - case mch <- rpc: - bs.tracer.SendRPC(rpc, p) - default: +func (bs *BlossomSubRouter) doSendRPC(rpc *RPC, p peer.ID, q *rpcQueue, fast bool) { + if err := q.TryPush(bs.p.ctx, rpc, fast); err != nil { bs.doDropRPC(rpc, p, "queue full") + return } + bs.tracer.SendRPC(rpc, p) } // appendOrMergeRPC appends the given RPCs to the slice, merging them if possible. @@ -1517,6 +1599,9 @@ func (bs *BlossomSubRouter) heartbeat() { // clean up iasked counters bs.clearIHaveCounters() + // clean up IDONTWANT counters + bs.clearIDontWantCounters() + // apply IWANT request penalties bs.applyIwantPenalties() @@ -1535,7 +1620,6 @@ func (bs *BlossomSubRouter) heartbeat() { } // maintain the mesh for bitmasks we have joined - bs.meshMx.Lock() for bitmask, peers := range bs.mesh { bitmask := []byte(bitmask) prunePeer := func(p peer.ID) { @@ -1718,7 +1802,6 @@ func (bs *BlossomSubRouter) heartbeat() { } } } - bs.meshMx.Unlock() // expire fanout for bitmasks we haven't published to in a while now := time.Now().UnixNano() @@ -1771,14 +1854,21 @@ func (bs *BlossomSubRouter) heartbeat() { } func (bs *BlossomSubRouter) clearIHaveCounters() { - if len(bs.peerhave) > 0 { - // throw away the old map and make a new one - bs.peerhave = make(map[peer.ID]int) - } + clear(bs.peerhave) + clear(bs.iasked) +} - if len(bs.iasked) > 0 { - // throw away the old map and make a new one - bs.iasked = make(map[peer.ID]int) +func (bs *BlossomSubRouter) clearIDontWantCounters() { + clear(bs.peerdontwant) + + // decrement TTLs of all the IDONTWANTs and delete it from the cache when it reaches zero + for _, mids := range bs.unwanted { + for mid := range mids { + mids[mid]-- + if mids[mid] == 0 { + delete(mids, mid) + } + } } } @@ -1856,8 +1946,8 @@ func (bs *BlossomSubRouter) sendGraftPrune(tograft, toprune map[peer.ID][][]byte } } - out := rpcWithControl(nil, nil, nil, graft, prune) - bs.sendRPC(p, out) + out := rpcWithControl(nil, nil, nil, graft, prune, nil) + bs.sendRPC(p, out, false) } for p, bitmasks := range toprune { @@ -1866,8 +1956,8 @@ func (bs *BlossomSubRouter) sendGraftPrune(tograft, toprune map[peer.ID][][]byte prune = append(prune, bs.makePrune(p, bitmask, bs.doPX && !noPX[p], false)) } - out := rpcWithControl(nil, nil, nil, nil, prune) - bs.sendRPC(p, out) + out := rpcWithControl(nil, nil, nil, nil, prune, nil) + bs.sendRPC(p, out, false) } } @@ -1929,15 +2019,15 @@ func (bs *BlossomSubRouter) flush() { // send gossip first, which will also piggyback pending control for p, ihave := range bs.gossip { delete(bs.gossip, p) - out := rpcWithControl(nil, ihave, nil, nil, nil) - bs.sendRPC(p, out) + out := rpcWithControl(nil, ihave, nil, nil, nil, nil) + bs.sendRPC(p, out, false) } // send the remaining control messages that wasn't merged with gossip for p, ctl := range bs.control { delete(bs.control, p) - out := rpcWithControl(nil, nil, nil, ctl.Graft, ctl.Prune) - bs.sendRPC(p, out) + out := rpcWithControl(nil, nil, nil, ctl.Graft, ctl.Prune, nil) + bs.sendRPC(p, out, false) } } @@ -1973,9 +2063,7 @@ func (bs *BlossomSubRouter) piggybackControl(p peer.ID, out *RPC, ctl *pb.Contro for _, graft := range ctl.GetGraft() { bitmask := graft.GetBitmask() - bs.meshMx.RLock() peers, ok := bs.mesh[string(bitmask)] - bs.meshMx.RUnlock() if !ok { continue } @@ -1987,9 +2075,7 @@ func (bs *BlossomSubRouter) piggybackControl(p peer.ID, out *RPC, ctl *pb.Contro for _, prune := range ctl.GetPrune() { bitmask := prune.GetBitmask() - bs.meshMx.RLock() peers, ok := bs.mesh[string(bitmask)] - bs.meshMx.RUnlock() if !ok { toprune = append(toprune, prune) continue diff --git a/go-libp2p-blossomsub/blossomsub_feat.go b/go-libp2p-blossomsub/blossomsub_feat.go index e6c0e37..5926c2d 100644 --- a/go-libp2p-blossomsub/blossomsub_feat.go +++ b/go-libp2p-blossomsub/blossomsub_feat.go @@ -18,18 +18,22 @@ const ( BlossomSubFeatureMesh = iota // Protocol supports Peer eXchange on prune -- BlossomSub-v2 compatible BlossomSubFeaturePX + // Protocol supports IDONTWANT -- BlossomSub-v2.1 compatible + BlossomSubFeatureIdontwant ) // BlossomSubDefaultProtocols is the default BlossomSub router protocol list -var BlossomSubDefaultProtocols = []protocol.ID{BlossomSubID_v2} +var BlossomSubDefaultProtocols = []protocol.ID{BlossomSubID_v21, BlossomSubID_v2} // BlossomSubDefaultFeatures is the feature test function for the default BlossomSub protocols func BlossomSubDefaultFeatures(feat BlossomSubFeature, proto protocol.ID) bool { switch feat { case BlossomSubFeatureMesh: - return proto == BlossomSubID_v2 + return proto == BlossomSubID_v21 || proto == BlossomSubID_v2 case BlossomSubFeaturePX: - return proto == BlossomSubID_v2 + return proto == BlossomSubID_v21 || proto == BlossomSubID_v2 + case BlossomSubFeatureIdontwant: + return proto == BlossomSubID_v21 default: return false } diff --git a/go-libp2p-blossomsub/blossomsub_feat_test.go b/go-libp2p-blossomsub/blossomsub_feat_test.go index f6098ce..f762980 100644 --- a/go-libp2p-blossomsub/blossomsub_feat_test.go +++ b/go-libp2p-blossomsub/blossomsub_feat_test.go @@ -15,10 +15,23 @@ func TestDefaultBlossomSubFeatures(t *testing.T) { if !BlossomSubDefaultFeatures(BlossomSubFeatureMesh, BlossomSubID_v2) { t.Fatal("BlossomSub-v2.0 should support Mesh") } + if !BlossomSubDefaultFeatures(BlossomSubFeatureMesh, BlossomSubID_v21) { + t.Fatal("BlossomSub-v2.1 should support Mesh") + } if !BlossomSubDefaultFeatures(BlossomSubFeaturePX, BlossomSubID_v2) { t.Fatal("BlossomSub-v2.0 should support PX") } + if !BlossomSubDefaultFeatures(BlossomSubFeaturePX, BlossomSubID_v21) { + t.Fatal("BlossomSub-v2.0 should support PX") + } + + if BlossomSubDefaultFeatures(BlossomSubFeatureIdontwant, BlossomSubID_v2) { + t.Fatal("BlossomSub-v2.0 should not support IDONTWANT") + } + if !BlossomSubDefaultFeatures(BlossomSubFeatureIdontwant, BlossomSubID_v21) { + t.Fatal("BlossomSub-v2.1 should support IDONTWANT") + } } func TestBlossomSubCustomProtocols(t *testing.T) { diff --git a/go-libp2p-blossomsub/blossomsub_spam_test.go b/go-libp2p-blossomsub/blossomsub_spam_test.go index dd22681..6dbc3c2 100644 --- a/go-libp2p-blossomsub/blossomsub_spam_test.go +++ b/go-libp2p-blossomsub/blossomsub_spam_test.go @@ -1,9 +1,11 @@ package blossomsub import ( + "bytes" "context" + "crypto/rand" + "crypto/sha256" "fmt" - "math/rand" "strconv" "sync" "testing" @@ -12,6 +14,7 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" "github.com/libp2p/go-msgio" "google.golang.org/protobuf/proto" @@ -122,7 +125,7 @@ func TestBlossomSubAttackSpamIWANT(t *testing.T) { // being spammy) iwantlst := [][]byte{DefaultMsgIdFn(msg)} iwant := []*pb.ControlIWant{{MessageIDs: iwantlst}} - orpc := rpcWithControl(nil, nil, iwant, nil, nil) + orpc := rpcWithControl(nil, nil, iwant, nil, nil, nil) writeMsg(orpc.RPC) } }) @@ -216,7 +219,7 @@ func TestBlossomSubAttackSpamIHAVE(t *testing.T) { for i := 0; i < 3*BlossomSubMaxIHaveLength; i++ { ihavelst := [][]byte{[]byte("someid" + strconv.Itoa(i))} ihave := []*pb.ControlIHave{{Bitmask: sub.Bitmask, MessageIDs: ihavelst}} - orpc := rpcWithControl(nil, ihave, nil, nil, nil) + orpc := rpcWithControl(nil, ihave, nil, nil, nil, nil) writeMsg(orpc.RPC) } @@ -246,7 +249,7 @@ func TestBlossomSubAttackSpamIHAVE(t *testing.T) { for i := 0; i < 3*BlossomSubMaxIHaveLength; i++ { ihavelst := [][]byte{[]byte("someid" + strconv.Itoa(i+100))} ihave := []*pb.ControlIHave{{Bitmask: sub.Bitmask, MessageIDs: ihavelst}} - orpc := rpcWithControl(nil, ihave, nil, nil, nil) + orpc := rpcWithControl(nil, ihave, nil, nil, nil, nil) writeMsg(orpc.RPC) } @@ -775,16 +778,151 @@ func TestBlossomSubAttackInvalidMessageSpam(t *testing.T) { <-ctx.Done() } +// Test that when BlossomSub receives too many IDONTWANT messages from a peer +func TestBlossomSubAttackSpamIDONTWANT(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getDefaultHosts(t, 3) + + msgID := func(pmsg *pb.Message) []byte { + mid := sha256.Sum256(pmsg.GetData()) + return mid[:] + } + + psubs := make([]*PubSub, 2) + psubs[0] = getBlossomSub(ctx, hosts[0], WithMessageIdFn(msgID)) + psubs[1] = getBlossomSub(ctx, hosts[1], WithMessageIdFn(msgID)) + + bitmask := []byte{0x20, 0x00, 0x00} + for _, ps := range psubs { + _, err := ps.Subscribe(bitmask) + if err != nil { + t.Fatal(err) + } + } + + // Wait a bit after the last message before checking the result + msgWaitMax := time.Second + BlossomSubHeartbeatInterval + msgTimer := time.NewTimer(msgWaitMax) + + // Checks we received some messages + var midsMu sync.RWMutex + var expMid []byte + var actMids [][]byte + checkMsgs := func() { + midsMu.RLock() + defer midsMu.RUnlock() + if len(actMids) == 0 { + t.Fatalf("Expected some messages when the maximum number of IDONTWANTs is reached") + } + if !bytes.Equal(actMids[0], expMid) { + t.Fatalf("The expected message is incorrect") + } + if len(actMids) > 1 { + t.Fatalf("The spam prevention should be reset after the heartbeat") + } + } + + // Wait for the timer to expire + go func() { + select { + case <-msgTimer.C: + checkMsgs() + cancel() + return + case <-ctx.Done(): + checkMsgs() + } + }() + + newMockBS(ctx, t, hosts[2], func(writeMsg func(*pb.RPC), irpc *pb.RPC) { + // Each time the host receives a message + midsMu.Lock() + for _, msg := range irpc.GetPublish() { + actMids = append(actMids, msgID(msg)) + } + midsMu.Unlock() + // When the middle peer connects it will send us its subscriptions + for _, sub := range irpc.GetSubscriptions() { + if sub.GetSubscribe() { + // Reply by subcribing to the bitmask and grafting to the middle peer + writeMsg(&pb.RPC{ + Subscriptions: []*pb.RPC_SubOpts{{Subscribe: sub.Subscribe, Bitmask: sub.Bitmask}}, + Control: &pb.ControlMessage{Graft: []*pb.ControlGraft{{Bitmask: sub.Bitmask}}}, + }) + + go func() { + // Wait for a short interval to make sure the middle peer + // received and processed the subscribe + graft + time.Sleep(100 * time.Millisecond) + + // Generate a message and send IDONTWANT to the middle peer + data := make([]byte, 16) + var mid []byte + for i := 0; i < 1+BlossomSubMaxIDontWantMessages; i++ { + rand.Read(data) + mid = msgID(&pb.Message{Data: data}) + writeMsg(&pb.RPC{ + Control: &pb.ControlMessage{Idontwant: []*pb.ControlIDontWant{{MessageIDs: [][]byte{mid}}}}, + }) + } + // The host should receives this message id because the maximum was reached + midsMu.Lock() + expMid = mid + midsMu.Unlock() + + // Wait for a short interval to make sure the middle peer + // received and processed the IDONTWANTs + time.Sleep(100 * time.Millisecond) + + // Publish the message from the first peer + if err := psubs[0].Publish(ctx, bitmask, data); err != nil { + t.Error(err) + return // cannot call t.Fatal in a non-test goroutine + } + + // Wait for the next heartbeat so that the prevention will be reset + select { + case <-ctx.Done(): + return + case <-time.After(BlossomSubHeartbeatInterval): + } + + // Test IDONTWANT again to see that it now works again + rand.Read(data) + mid = msgID(&pb.Message{Data: data}) + writeMsg(&pb.RPC{ + Control: &pb.ControlMessage{Idontwant: []*pb.ControlIDontWant{{MessageIDs: [][]byte{mid}}}}, + }) + time.Sleep(100 * time.Millisecond) + if err := psubs[0].Publish(ctx, bitmask, data); err != nil { + t.Error(err) + return // cannot call t.Fatal in a non-test goroutine + } + }() + } + } + }) + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + + <-ctx.Done() +} + type MockBSOnRead func(writeMsg func(*pb.RPC), irpc *pb.RPC) func newMockBS(ctx context.Context, t *testing.T, attacker host.Host, onReadMsg MockBSOnRead) { + newMockBSWithVersion(ctx, t, attacker, BlossomSubID_v21, onReadMsg) +} + +func newMockBSWithVersion(ctx context.Context, t *testing.T, attacker host.Host, blossomSubID protocol.ID, onReadMsg MockBSOnRead) { // Listen on the BlossomSub protocol - const BlossomSubID = BlossomSubID_v2 const maxMessageSize = 1024 * 1024 - attacker.SetStreamHandler(BlossomSubID, func(stream network.Stream) { + attacker.SetStreamHandler(blossomSubID, func(stream network.Stream) { // When an incoming stream is opened, set up an outgoing stream p := stream.Conn().RemotePeer() - ostream, err := attacker.NewStream(ctx, p, BlossomSubID) + ostream, err := attacker.NewStream(ctx, p, blossomSubID) if err != nil { t.Fatal(err) } diff --git a/go-libp2p-blossomsub/blossomsub_test.go b/go-libp2p-blossomsub/blossomsub_test.go index 77f4911..dd52b38 100644 --- a/go-libp2p-blossomsub/blossomsub_test.go +++ b/go-libp2p-blossomsub/blossomsub_test.go @@ -3,10 +3,14 @@ package blossomsub import ( "bytes" "context" + crand "crypto/rand" + "crypto/sha256" "errors" "fmt" + "maps" "math/rand" "slices" + "sort" "sync" "sync/atomic" "testing" @@ -41,7 +45,7 @@ func assertPeerLists(t *testing.T, bitmask []byte, hosts []host.Host, ps *PubSub func checkMessageRouting(t *testing.T, ctx context.Context, bitmasks []*Bitmask, subs [][]*Subscription) { for _, p := range bitmasks { data := make([]byte, 16) - rand.Read(data) + crand.Read(data) err := p.Publish(ctx, p.bitmask, data) if err != nil { t.Fatal(err) @@ -2258,10 +2262,14 @@ func TestBlossomSubJoinBitmask(t *testing.T) { router0 := psubs[0].rt.(*BlossomSubRouter) // Add in backoff for peer. - peerMap := make(map[peer.ID]time.Time) - peerMap[h[1].ID()] = time.Now().Add(router0.params.UnsubscribeBackoff) - - router0.backoff[string([]byte{0x00, 0x00, 0x81, 0x00})] = peerMap + ran := make(chan struct{}) + router0.p.eval <- func() { + defer close(ran) + peerMap := make(map[peer.ID]time.Time) + peerMap[h[1].ID()] = time.Now().Add(router0.params.UnsubscribeBackoff) + router0.backoff[string([]byte{0x00, 0x00, 0x81, 0x00})] = peerMap + } + <-ran // Join all peers var subs []*Subscription @@ -2275,13 +2283,17 @@ func TestBlossomSubJoinBitmask(t *testing.T) { time.Sleep(time.Second) - router0.meshMx.RLock() - meshMap := router0.mesh[string([]byte{0x00, 0x00, 0x81, 0x00})] - router0.meshMx.RUnlock() + ran = make(chan struct{}) + var meshMap map[peer.ID]struct{} + router0.p.eval <- func() { + defer close(ran) + meshMap = maps.Clone(router0.mesh[string([]byte{0x00, 0x00, 0x81, 0x00})]) + } + <-ran + if len(meshMap) != 1 { t.Fatalf("Unexpect peer included in the mesh") } - _, ok := meshMap[h[1].ID()] if ok { t.Fatalf("Peer that was to be backed off is included in the mesh") @@ -2497,7 +2509,7 @@ func TestBlossomSubRPCFragmentation(t *testing.T) { msgSize := 20000 for i := 0; i < nMessages; i++ { msg := make([]byte, msgSize) - rand.Read(msg) + crand.Read(msg) b[0].Publish(ctx, []byte{0x00, 0x00, 0x81, 0x00}, msg) time.Sleep(20 * time.Millisecond) } @@ -2610,7 +2622,7 @@ func (iwe *iwantEverything) handleStream(s network.Stream) { } } - msg := rpcWithControl(nil, nil, iwants, nil, prunes) + msg := rpcWithControl(nil, nil, iwants, nil, prunes, nil) out, err := proto.Marshal(msg) if err != nil { @@ -2635,7 +2647,7 @@ func TestFragmentRPCFunction(t *testing.T) { mkMsg := func(size int) *pb.Message { msg := &pb.Message{} msg.Data = make([]byte, size-4) // subtract the protobuf overhead, so msg.Size() returns requested size - rand.Read(msg.Data) + crand.Read(msg.Data) return msg } @@ -2737,7 +2749,7 @@ func TestFragmentRPCFunction(t *testing.T) { messageIds := make([][]byte, msgsPerBitmask) for m := 0; m < msgsPerBitmask; m++ { mid := make([]byte, messageIdSize) - rand.Read(mid) + crand.Read(mid) messageIds[m] = mid } rpc.Control.Ihave[i] = &pb.ControlIHave{MessageIDs: messageIds} @@ -2754,7 +2766,7 @@ func TestFragmentRPCFunction(t *testing.T) { // Test the pathological case where a single gossip message ID exceeds the limit. rpc.Reset() giantIdBytes := make([]byte, limit*2) - rand.Read(giantIdBytes) + crand.Read(giantIdBytes) rpc.Control = &pb.ControlMessage{ Iwant: []*pb.ControlIWant{ {MessageIDs: [][]byte{[]byte("hello"), giantIdBytes}}, @@ -2780,6 +2792,595 @@ func TestFragmentRPCFunction(t *testing.T) { } } +func TestBlossomSubIdontwantSend(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getDefaultHosts(t, 3) + + msgID := func(pmsg *pb.Message) []byte { + mid := sha256.Sum256(pmsg.Data) + return mid[:] + } + + var validated atomic.Bool + validate := func(context.Context, peer.ID, *Message) bool { + time.Sleep(100 * time.Millisecond) + validated.Store(true) + return true + } + + params := DefaultBlossomSubParams() + params.IDontWantMessageThreshold = 16 + + psubs := make([]*PubSub, 2) + psubs[0] = getBlossomSub(ctx, hosts[0], + WithBlossomSubParams(params), + WithMessageIdFn(msgID)) + psubs[1] = getBlossomSub(ctx, hosts[1], + WithBlossomSubParams(params), + WithMessageIdFn(msgID), + WithDefaultValidator(validate)) + + bitmask := []byte{0x20, 0x00, 0x00} + for _, ps := range psubs { + _, err := ps.Subscribe(bitmask) + if err != nil { + t.Fatal(err) + } + } + + var expMids [][]byte + var actMids [][]byte + + // Used to publish a message with random data + publishMsg := func() { + data := make([]byte, 16) + crand.Read(data) + m := &pb.Message{Data: data} + expMids = append(expMids, msgID(m)) + + if err := psubs[0].Publish(ctx, bitmask, data); err != nil { + t.Fatal(err) + } + } + + // Wait a bit after the last message before checking we got the right messages + msgWaitMax := time.Second + msgTimer := time.NewTimer(msgWaitMax) + + // Checks we received the right IDONTWANT messages + checkMsgs := func() { + sort.Slice(actMids, func(i, j int) bool { + return bytes.Compare(actMids[i], actMids[j]) < 0 + }) + sort.Slice(expMids, func(i, j int) bool { + return bytes.Compare(expMids[i], expMids[j]) < 0 + }) + + if len(actMids) != len(expMids) { + t.Fatalf("Expected %d IDONTWANT messages, got %d", len(expMids), len(actMids)) + } + for i, expMid := range expMids { + actMid := actMids[i] + if !bytes.Equal(expMid, actMid) { + t.Fatalf("Expected the id of %x in the %d'th IDONTWANT messages, got %x", expMid, i+1, actMid) + } + } + } + + // Wait for the timer to expire + go func() { + select { + case <-msgTimer.C: + checkMsgs() + cancel() + return + case <-ctx.Done(): + checkMsgs() + } + }() + + newMockBS(ctx, t, hosts[2], func(writeMsg func(*pb.RPC), irpc *pb.RPC) { + // When the middle peer connects it will send us its subscriptions + for _, sub := range irpc.GetSubscriptions() { + if sub.GetSubscribe() { + // Reply by subcribing to the bitmask and grafting to the middle peer + writeMsg(&pb.RPC{ + Subscriptions: []*pb.RPC_SubOpts{{Subscribe: sub.Subscribe, Bitmask: sub.Bitmask}}, + Control: &pb.ControlMessage{Graft: []*pb.ControlGraft{{Bitmask: sub.Bitmask}}}, + }) + + go func() { + // Wait for a short interval to make sure the middle peer + // received and processed the subscribe + graft + time.Sleep(100 * time.Millisecond) + + // Publish messages from the first peer + for i := 0; i < 10; i++ { + publishMsg() + } + }() + } + } + + // Each time the middle peer sends an IDONTWANT message + for _, idonthave := range irpc.GetControl().GetIdontwant() { + // If true, it means that, when we get IDONTWANT, the middle peer has done validation + // already, which should not be the case + if validated.Load() { + t.Fatalf("IDONTWANT should be sent before doing validation") + } + for _, mid := range idonthave.GetMessageIDs() { + // Add the message to the list and reset the timer + actMids = append(actMids, mid) + msgTimer.Reset(msgWaitMax) + } + } + }) + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + + <-ctx.Done() +} + +func TestBlossomSubIdontwantReceive(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getDefaultHosts(t, 3) + + msgID := func(pmsg *pb.Message) []byte { + mid := sha256.Sum256(pmsg.Data) + return mid[:] + } + + psubs := make([]*PubSub, 2) + psubs[0] = getBlossomSub(ctx, hosts[0], WithMessageIdFn(msgID)) + psubs[1] = getBlossomSub(ctx, hosts[1], WithMessageIdFn(msgID)) + + bitmask := []byte{0x20, 0x00, 0x00} + for _, ps := range psubs { + _, err := ps.Subscribe(bitmask) + if err != nil { + t.Fatal(err) + } + } + + // Wait a bit after the last message before checking the result + msgWaitMax := time.Second + msgTimer := time.NewTimer(msgWaitMax) + + // Checks we received no messages + received := false + checkMsgs := func() { + if received { + t.Fatalf("Expected no messages received after IDONWANT") + } + } + + // Wait for the timer to expire + go func() { + select { + case <-msgTimer.C: + checkMsgs() + cancel() + return + case <-ctx.Done(): + checkMsgs() + } + }() + + newMockBS(ctx, t, hosts[2], func(writeMsg func(*pb.RPC), irpc *pb.RPC) { + // Check if it receives any message + if len(irpc.GetPublish()) > 0 { + received = true + } + // When the middle peer connects it will send us its subscriptions + for _, sub := range irpc.GetSubscriptions() { + if sub.GetSubscribe() { + // Reply by subcribing to the bitmask and grafting to the middle peer + writeMsg(&pb.RPC{ + Subscriptions: []*pb.RPC_SubOpts{{Subscribe: sub.Subscribe, Bitmask: sub.Bitmask}}, + Control: &pb.ControlMessage{Graft: []*pb.ControlGraft{{Bitmask: sub.Bitmask}}}, + }) + + go func() { + // Wait for a short interval to make sure the middle peer + // received and processed the subscribe + graft + time.Sleep(100 * time.Millisecond) + + // Generate a message and send IDONTWANT to the middle peer + data := make([]byte, 16) + crand.Read(data) + mid := msgID(&pb.Message{Data: data}) + writeMsg(&pb.RPC{ + Control: &pb.ControlMessage{Idontwant: []*pb.ControlIDontWant{{MessageIDs: [][]byte{mid}}}}, + }) + + // Wait for a short interval to make sure the middle peer + // received and processed the IDONTWANTs + time.Sleep(100 * time.Millisecond) + + // Publish the message from the first peer + if err := psubs[0].Publish(ctx, bitmask, data); err != nil { + t.Error(err) + return // cannot call t.Fatal in a non-test goroutine + } + }() + } + } + }) + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + + <-ctx.Done() +} + +// Test that non-mesh peers will not get IDONTWANT +func TestBlossomSubIdontwantNonMesh(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getDefaultHosts(t, 3) + + params := DefaultBlossomSubParams() + params.IDontWantMessageThreshold = 16 + psubs := getBlossomSubs(ctx, hosts[:2], WithBlossomSubParams(params)) + + bitmask := []byte{0x20, 0x00, 0x00} + for _, ps := range psubs { + _, err := ps.Subscribe(bitmask) + if err != nil { + t.Fatal(err) + } + } + + // Used to publish a message with random data + publishMsg := func() { + data := make([]byte, 16) + crand.Read(data) + + if err := psubs[0].Publish(ctx, bitmask, data); err != nil { + t.Fatal(err) + } + } + + // Wait a bit after the last message before checking we got the right messages + msgWaitMax := time.Second + msgTimer := time.NewTimer(msgWaitMax) + received := false + + // Checks if we received any IDONTWANT + checkMsgs := func() { + if received { + t.Fatalf("No IDONTWANT is expected") + } + } + + // Wait for the timer to expire + go func() { + select { + case <-msgTimer.C: + checkMsgs() + cancel() + return + case <-ctx.Done(): + checkMsgs() + } + }() + + newMockBS(ctx, t, hosts[2], func(writeMsg func(*pb.RPC), irpc *pb.RPC) { + // When the middle peer connects it will send us its subscriptions + for _, sub := range irpc.GetSubscriptions() { + if sub.GetSubscribe() { + // Reply by subcribing to the bitmask and pruning to the middle peer to make sure + // that it's not in the mesh + writeMsg(&pb.RPC{ + Subscriptions: []*pb.RPC_SubOpts{{Subscribe: sub.Subscribe, Bitmask: sub.Bitmask}}, + Control: &pb.ControlMessage{Prune: []*pb.ControlPrune{{Bitmask: sub.Bitmask}}}, + }) + + go func() { + // Wait for a short interval to make sure the middle peer + // received and processed the subscribe + time.Sleep(100 * time.Millisecond) + + // Publish messages from the first peer + for i := 0; i < 10; i++ { + publishMsg() + } + }() + } + } + + // Each time the middle peer sends an IDONTWANT message + for range irpc.GetControl().GetIdontwant() { + received = true + } + }) + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + + <-ctx.Done() +} + +// Test that peers with incompatible versions will not get IDONTWANT +func TestBlossomSubIdontwantIncompat(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getDefaultHosts(t, 3) + + params := DefaultBlossomSubParams() + params.IDontWantMessageThreshold = 16 + psubs := getBlossomSubs(ctx, hosts[:2], WithBlossomSubParams(params)) + + bitmask := []byte{0x20, 0x00, 0x00} + for _, ps := range psubs { + _, err := ps.Subscribe(bitmask) + if err != nil { + t.Fatal(err) + } + } + + // Used to publish a message with random data + publishMsg := func() { + data := make([]byte, 16) + crand.Read(data) + + if err := psubs[0].Publish(ctx, bitmask, data); err != nil { + t.Fatal(err) + } + } + + // Wait a bit after the last message before checking we got the right messages + msgWaitMax := time.Second + msgTimer := time.NewTimer(msgWaitMax) + received := false + + // Checks if we received any IDONTWANT + checkMsgs := func() { + if received { + t.Fatalf("No IDONTWANT is expected") + } + } + + // Wait for the timer to expire + go func() { + select { + case <-msgTimer.C: + checkMsgs() + cancel() + return + case <-ctx.Done(): + checkMsgs() + } + }() + + // Use the old BlossomSub version + newMockBSWithVersion(ctx, t, hosts[2], BlossomSubID_v2, func(writeMsg func(*pb.RPC), irpc *pb.RPC) { + // When the middle peer connects it will send us its subscriptions + for _, sub := range irpc.GetSubscriptions() { + if sub.GetSubscribe() { + // Reply by subcribing to the bitmask and grafting to the middle peer + writeMsg(&pb.RPC{ + Subscriptions: []*pb.RPC_SubOpts{{Subscribe: sub.Subscribe, Bitmask: sub.Bitmask}}, + Control: &pb.ControlMessage{Graft: []*pb.ControlGraft{{Bitmask: sub.Bitmask}}}, + }) + + go func() { + // Wait for a short interval to make sure the middle peer + // received and processed the subscribe + graft + time.Sleep(100 * time.Millisecond) + + // Publish messages from the first peer + for i := 0; i < 10; i++ { + publishMsg() + } + }() + } + } + + // Each time the middle peer sends an IDONTWANT message + for range irpc.GetControl().GetIdontwant() { + received = true + } + }) + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + + <-ctx.Done() +} + +// Test that IDONTWANT will not be sent for small messages +func TestBlossomSubIdontwantSmallMessage(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getDefaultHosts(t, 3) + + params := DefaultBlossomSubParams() + params.IDontWantMessageThreshold = 16 + psubs := getBlossomSubs(ctx, hosts[:2], WithBlossomSubParams(params)) + + bitmask := []byte{0x20, 0x00, 0x00} + for _, ps := range psubs { + _, err := ps.Subscribe(bitmask) + if err != nil { + t.Fatal(err) + } + } + + // Used to publish a message with random data + publishMsg := func() { + data := make([]byte, 8) + crand.Read(data) + + if err := psubs[0].Publish(ctx, bitmask, data); err != nil { + t.Fatal(err) + } + } + + // Wait a bit after the last message before checking we got the right messages + msgWaitMax := time.Second + msgTimer := time.NewTimer(msgWaitMax) + received := false + + // Checks if we received any IDONTWANT + checkMsgs := func() { + if received { + t.Fatalf("No IDONTWANT is expected") + } + } + + // Wait for the timer to expire + go func() { + select { + case <-msgTimer.C: + checkMsgs() + cancel() + return + case <-ctx.Done(): + checkMsgs() + } + }() + + newMockBS(ctx, t, hosts[2], func(writeMsg func(*pb.RPC), irpc *pb.RPC) { + // When the middle peer connects it will send us its subscriptions + for _, sub := range irpc.GetSubscriptions() { + if sub.GetSubscribe() { + // Reply by subcribing to the bitmask and pruning to the middle peer to make sure + // that it's not in the mesh + writeMsg(&pb.RPC{ + Subscriptions: []*pb.RPC_SubOpts{{Subscribe: sub.Subscribe, Bitmask: sub.Bitmask}}, + Control: &pb.ControlMessage{Graft: []*pb.ControlGraft{{Bitmask: sub.Bitmask}}}, + }) + + go func() { + // Wait for a short interval to make sure the middle peer + // received and processed the subscribe + time.Sleep(100 * time.Millisecond) + + // Publish messages from the first peer + for i := 0; i < 10; i++ { + publishMsg() + } + }() + } + } + + // Each time the middle peer sends an IDONTWANT message + for range irpc.GetControl().GetIdontwant() { + received = true + } + }) + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + + <-ctx.Done() +} + +// Test that IDONTWANT will cleared when it's old enough +func TestBlossomSubIdontwantClear(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + hosts := getDefaultHosts(t, 3) + + msgID := func(pmsg *pb.Message) []byte { + mid := sha256.Sum256(pmsg.Data) + return mid[:] + } + + params := DefaultBlossomSubParams() + params.IDontWantMessageTTL = 3 + + psubs := make([]*PubSub, 2) + psubs[0] = getBlossomSub(ctx, hosts[0], WithMessageIdFn(msgID), WithBlossomSubParams(params)) + psubs[1] = getBlossomSub(ctx, hosts[1], WithMessageIdFn(msgID), WithBlossomSubParams(params)) + + bitmask := []byte{0x20, 0x00, 0x00} + for _, ps := range psubs { + _, err := ps.Subscribe(bitmask) + if err != nil { + t.Fatal(err) + } + } + + // Wait a bit after the last message before checking the result + msgWaitMax := 5 * time.Second + msgTimer := time.NewTimer(msgWaitMax) + + // Checks we received some message after the IDONTWANT is cleared + var received atomic.Bool + checkMsgs := func() { + if !received.Load() { + t.Fatalf("Expected some message after the IDONTWANT is cleared") + } + } + + // Wait for the timer to expire + go func() { + select { + case <-msgTimer.C: + checkMsgs() + cancel() + return + case <-ctx.Done(): + checkMsgs() + } + }() + + newMockBS(ctx, t, hosts[2], func(writeMsg func(*pb.RPC), irpc *pb.RPC) { + // Check if it receives any message + if len(irpc.GetPublish()) > 0 { + received.Store(true) + } + // When the middle peer connects it will send us its subscriptions + for _, sub := range irpc.GetSubscriptions() { + if sub.GetSubscribe() { + // Reply by subcribing to the bitmask and grafting to the middle peer + writeMsg(&pb.RPC{ + Subscriptions: []*pb.RPC_SubOpts{{Subscribe: sub.Subscribe, Bitmask: sub.Bitmask}}, + Control: &pb.ControlMessage{Graft: []*pb.ControlGraft{{Bitmask: sub.Bitmask}}}, + }) + + go func() { + // Wait for a short interval to make sure the middle peer + // received and processed the subscribe + graft + time.Sleep(100 * time.Millisecond) + + // Generate a message and send IDONTWANT to the middle peer + data := make([]byte, 16) + crand.Read(data) + mid := msgID(&pb.Message{Data: data}) + writeMsg(&pb.RPC{ + Control: &pb.ControlMessage{Idontwant: []*pb.ControlIDontWant{{MessageIDs: [][]byte{mid}}}}, + }) + + // Wait for a short interval to make sure the middle peer + // received and processed the IDONTWANTs + time.Sleep(100 * time.Millisecond) + + // Wait for 4 heartbeats to make sure the IDONTWANT is cleared + time.Sleep(4 * time.Second) + + // Publish the message from the first peer + if err := psubs[0].Publish(ctx, bitmask, data); err != nil { + t.Error(err) + return // cannot call t.Fatal in a non-test goroutine + } + }() + } + } + }) + + connect(t, hosts[0], hosts[1]) + connect(t, hosts[1], hosts[2]) + + <-ctx.Done() +} + func TestBloomRouting(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -2933,7 +3534,7 @@ func TestBloomPropagationOverSubTreeTopology(t *testing.T) { for _, p := range bitmasks { data := make([]byte, 32) - rand.Read(data) + crand.Read(data) err := p[0].Publish(ctx, []byte{0x10, 0x10, 0x10, 0x00}, data) if err != nil { t.Fatal(err) diff --git a/go-libp2p-blossomsub/comm.go b/go-libp2p-blossomsub/comm.go index 8c847d7..b09e05f 100644 --- a/go-libp2p-blossomsub/comm.go +++ b/go-libp2p-blossomsub/comm.go @@ -126,7 +126,7 @@ func (p *PubSub) notifyPeerDead(pid peer.ID) { } } -func (p *PubSub) handleNewPeer(ctx context.Context, pid peer.ID, outgoing <-chan *RPC) { +func (p *PubSub) handleNewPeer(ctx context.Context, pid peer.ID, q *rpcQueue) { s, err := p.host.NewStream(p.ctx, pid, p.rt.Protocols()...) if err != nil { log.Debug("opening new stream to peer: ", err, pid) @@ -139,7 +139,7 @@ func (p *PubSub) handleNewPeer(ctx context.Context, pid peer.ID, outgoing <-chan return } - go p.handleSendingMessages(ctx, s, outgoing) + go p.handleSendingMessages(ctx, s, q) go p.handlePeerDead(s) select { case p.newPeerStream <- s: @@ -147,10 +147,10 @@ func (p *PubSub) handleNewPeer(ctx context.Context, pid peer.ID, outgoing <-chan } } -func (p *PubSub) handleNewPeerWithBackoff(ctx context.Context, pid peer.ID, backoff time.Duration, outgoing <-chan *RPC) { +func (p *PubSub) handleNewPeerWithBackoff(ctx context.Context, pid peer.ID, backoff time.Duration, q *rpcQueue) { select { case <-time.After(backoff): - p.handleNewPeer(ctx, pid, outgoing) + p.handleNewPeer(ctx, pid, q) case <-ctx.Done(): return } @@ -168,39 +168,29 @@ func (p *PubSub) handlePeerDead(s network.Stream) { p.notifyPeerDead(pid) } -func (p *PubSub) handleSendingMessages(ctx context.Context, s network.Stream, outgoing <-chan *RPC) { +func (p *PubSub) handleSendingMessages(ctx context.Context, s network.Stream, q *rpcQueue) { + writeRPC := func(rpc *RPC) error { + size := uint64(rpc.Size()) + buf := pool.Get(varint.UvarintSize(size) + int(size)) + defer pool.Put(buf) + n := binary.PutUvarint(buf, size) + _, err := rpc.MarshalTo(buf[n:]) + if err != nil { + return err + } + _, err = s.Write(buf) + return err + } + defer s.Close() + defer s.Reset() for { - select { - case rpc, ok := <-outgoing: - if !ok { - s.Close() - return - } - - size := uint64(rpc.Size()) - - buf := pool.Get(varint.UvarintSize(size) + int(size)) - - n := binary.PutUvarint(buf, size) - _, err := rpc.MarshalTo(buf[n:]) - if err != nil { - s.Reset() - log.Debugf("writing message to %s: %s", s.Conn().RemotePeer(), err) - s.Close() - pool.Put(buf) - return - } - - _, err = s.Write(buf) - pool.Put(buf) - if err != nil { - s.Reset() - log.Debugf("writing message to %s: %s", s.Conn().RemotePeer(), err) - s.Close() - return - } - case <-ctx.Done(): - s.Close() + rpc, err := q.Pop(ctx) + if err != nil { + log.Debugf("pop RPC from queue: %s", err) + return + } + if err := writeRPC(rpc); err != nil { + log.Debugf("writing message to %s: %s", s.Conn().RemotePeer(), err) return } } @@ -222,15 +212,17 @@ func rpcWithControl(msgs []*pb.Message, ihave []*pb.ControlIHave, iwant []*pb.ControlIWant, graft []*pb.ControlGraft, - prune []*pb.ControlPrune) *RPC { + prune []*pb.ControlPrune, + idontwant []*pb.ControlIDontWant) *RPC { return &RPC{ RPC: &pb.RPC{ Publish: msgs, Control: &pb.ControlMessage{ - Ihave: ihave, - Iwant: iwant, - Graft: graft, - Prune: prune, + Ihave: ihave, + Iwant: iwant, + Graft: graft, + Prune: prune, + Idontwant: idontwant, }, }, } diff --git a/go-libp2p-blossomsub/pb/rpc.pb.go b/go-libp2p-blossomsub/pb/rpc.pb.go index 2ba0dac..37603cc 100644 --- a/go-libp2p-blossomsub/pb/rpc.pb.go +++ b/go-libp2p-blossomsub/pb/rpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 -// protoc v3.21.12 +// protoc-gen-go v1.34.1 +// protoc v5.27.0 // source: rpc.proto package pb @@ -175,10 +175,11 @@ type ControlMessage struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Ihave []*ControlIHave `protobuf:"bytes,1,rep,name=ihave,proto3" json:"ihave,omitempty"` - Iwant []*ControlIWant `protobuf:"bytes,2,rep,name=iwant,proto3" json:"iwant,omitempty"` - Graft []*ControlGraft `protobuf:"bytes,3,rep,name=graft,proto3" json:"graft,omitempty"` - Prune []*ControlPrune `protobuf:"bytes,4,rep,name=prune,proto3" json:"prune,omitempty"` + Ihave []*ControlIHave `protobuf:"bytes,1,rep,name=ihave,proto3" json:"ihave,omitempty"` + Iwant []*ControlIWant `protobuf:"bytes,2,rep,name=iwant,proto3" json:"iwant,omitempty"` + Graft []*ControlGraft `protobuf:"bytes,3,rep,name=graft,proto3" json:"graft,omitempty"` + Prune []*ControlPrune `protobuf:"bytes,4,rep,name=prune,proto3" json:"prune,omitempty"` + Idontwant []*ControlIDontWant `protobuf:"bytes,5,rep,name=idontwant,proto3" json:"idontwant,omitempty"` } func (x *ControlMessage) Reset() { @@ -241,6 +242,13 @@ func (x *ControlMessage) GetPrune() []*ControlPrune { return nil } +func (x *ControlMessage) GetIdontwant() []*ControlIDontWant { + if x != nil { + return x.Idontwant + } + return nil +} + type ControlIHave struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -453,6 +461,53 @@ func (x *ControlPrune) GetBackoff() uint64 { return 0 } +type ControlIDontWant struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MessageIDs [][]byte `protobuf:"bytes,1,rep,name=messageIDs,proto3" json:"messageIDs,omitempty"` +} + +func (x *ControlIDontWant) Reset() { + *x = ControlIDontWant{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ControlIDontWant) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ControlIDontWant) ProtoMessage() {} + +func (x *ControlIDontWant) ProtoReflect() protoreflect.Message { + mi := &file_rpc_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ControlIDontWant.ProtoReflect.Descriptor instead. +func (*ControlIDontWant) Descriptor() ([]byte, []int) { + return file_rpc_proto_rawDescGZIP(), []int{7} +} + +func (x *ControlIDontWant) GetMessageIDs() [][]byte { + if x != nil { + return x.MessageIDs + } + return nil +} + type PeerInfo struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -465,7 +520,7 @@ type PeerInfo struct { func (x *PeerInfo) Reset() { *x = PeerInfo{} if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[7] + mi := &file_rpc_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -478,7 +533,7 @@ func (x *PeerInfo) String() string { func (*PeerInfo) ProtoMessage() {} func (x *PeerInfo) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[7] + mi := &file_rpc_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -491,7 +546,7 @@ func (x *PeerInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerInfo.ProtoReflect.Descriptor instead. func (*PeerInfo) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{7} + return file_rpc_proto_rawDescGZIP(), []int{8} } func (x *PeerInfo) GetPeerID() []byte { @@ -520,7 +575,7 @@ type RPC_SubOpts struct { func (x *RPC_SubOpts) Reset() { *x = RPC_SubOpts{} if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[8] + mi := &file_rpc_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -533,7 +588,7 @@ func (x *RPC_SubOpts) String() string { func (*RPC_SubOpts) ProtoMessage() {} func (x *RPC_SubOpts) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[8] + mi := &file_rpc_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -592,7 +647,7 @@ var file_rpc_proto_rawDesc = []byte{ 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xdc, 0x01, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x9b, 0x02, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x31, 0x0a, 0x05, 0x69, 0x68, 0x61, 0x76, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, @@ -606,37 +661,44 @@ var file_rpc_proto_rawDesc = []byte{ 0x66, 0x74, 0x12, 0x31, 0x0a, 0x05, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x52, 0x05, - 0x70, 0x72, 0x75, 0x6e, 0x65, 0x22, 0x48, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x49, 0x48, 0x61, 0x76, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x12, - 0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x22, - 0x2e, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x49, 0x57, 0x61, 0x6e, 0x74, 0x12, + 0x70, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x3d, 0x0a, 0x09, 0x69, 0x64, 0x6f, 0x6e, 0x74, 0x77, 0x61, + 0x6e, 0x74, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, + 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x49, 0x44, 0x6f, 0x6e, 0x74, 0x57, 0x61, 0x6e, 0x74, 0x52, 0x09, 0x69, 0x64, 0x6f, 0x6e, 0x74, + 0x77, 0x61, 0x6e, 0x74, 0x22, 0x48, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x49, + 0x48, 0x61, 0x76, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x12, 0x1e, + 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0c, 0x52, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x22, 0x2e, + 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x49, 0x57, 0x61, 0x6e, 0x74, 0x12, 0x1e, + 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0c, 0x52, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x22, 0x28, + 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x47, 0x72, 0x61, 0x66, 0x74, 0x12, 0x18, + 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x22, 0x71, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, + 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, + 0x73, 0x6b, 0x12, 0x2d, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, + 0x62, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, + 0x73, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x22, 0x32, 0x0a, 0x10, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x49, 0x44, 0x6f, 0x6e, 0x74, 0x57, 0x61, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x22, - 0x28, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x47, 0x72, 0x61, 0x66, 0x74, 0x12, - 0x18, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x22, 0x71, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x69, 0x74, - 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, - 0x61, 0x73, 0x6b, 0x12, 0x2d, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, - 0x70, 0x62, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x70, 0x65, 0x65, - 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x22, 0x78, 0x0a, 0x08, - 0x50, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a, 0x06, 0x70, 0x65, 0x65, 0x72, - 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, - 0x49, 0x44, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x10, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, - 0x65, 0x65, 0x72, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, - 0x01, 0x52, 0x10, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x49, - 0x44, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, - 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x42, 0x43, 0x5a, 0x41, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x6d, 0x6f, 0x6e, 0x6f, 0x72, - 0x65, 0x70, 0x6f, 0x2f, 0x67, 0x6f, 0x2d, 0x6c, 0x69, 0x62, 0x70, 0x32, 0x70, 0x2d, 0x62, 0x6c, - 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x78, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a, 0x06, 0x70, + 0x65, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x70, + 0x65, 0x65, 0x72, 0x49, 0x44, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x10, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x48, 0x01, 0x52, 0x10, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x70, 0x65, + 0x65, 0x72, 0x49, 0x44, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, + 0x65, 0x65, 0x72, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x42, 0x43, 0x5a, 0x41, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x6d, 0x6f, + 0x6e, 0x6f, 0x72, 0x65, 0x70, 0x6f, 0x2f, 0x67, 0x6f, 0x2d, 0x6c, 0x69, 0x62, 0x70, 0x32, 0x70, + 0x2d, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2f, 0x70, 0x62, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -651,32 +713,34 @@ func file_rpc_proto_rawDescGZIP() []byte { return file_rpc_proto_rawDescData } -var file_rpc_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_rpc_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_rpc_proto_goTypes = []interface{}{ - (*RPC)(nil), // 0: blossomsub.pb.RPC - (*Message)(nil), // 1: blossomsub.pb.Message - (*ControlMessage)(nil), // 2: blossomsub.pb.ControlMessage - (*ControlIHave)(nil), // 3: blossomsub.pb.ControlIHave - (*ControlIWant)(nil), // 4: blossomsub.pb.ControlIWant - (*ControlGraft)(nil), // 5: blossomsub.pb.ControlGraft - (*ControlPrune)(nil), // 6: blossomsub.pb.ControlPrune - (*PeerInfo)(nil), // 7: blossomsub.pb.PeerInfo - (*RPC_SubOpts)(nil), // 8: blossomsub.pb.RPC.SubOpts + (*RPC)(nil), // 0: blossomsub.pb.RPC + (*Message)(nil), // 1: blossomsub.pb.Message + (*ControlMessage)(nil), // 2: blossomsub.pb.ControlMessage + (*ControlIHave)(nil), // 3: blossomsub.pb.ControlIHave + (*ControlIWant)(nil), // 4: blossomsub.pb.ControlIWant + (*ControlGraft)(nil), // 5: blossomsub.pb.ControlGraft + (*ControlPrune)(nil), // 6: blossomsub.pb.ControlPrune + (*ControlIDontWant)(nil), // 7: blossomsub.pb.ControlIDontWant + (*PeerInfo)(nil), // 8: blossomsub.pb.PeerInfo + (*RPC_SubOpts)(nil), // 9: blossomsub.pb.RPC.SubOpts } var file_rpc_proto_depIdxs = []int32{ - 8, // 0: blossomsub.pb.RPC.subscriptions:type_name -> blossomsub.pb.RPC.SubOpts + 9, // 0: blossomsub.pb.RPC.subscriptions:type_name -> blossomsub.pb.RPC.SubOpts 1, // 1: blossomsub.pb.RPC.publish:type_name -> blossomsub.pb.Message 2, // 2: blossomsub.pb.RPC.control:type_name -> blossomsub.pb.ControlMessage 3, // 3: blossomsub.pb.ControlMessage.ihave:type_name -> blossomsub.pb.ControlIHave 4, // 4: blossomsub.pb.ControlMessage.iwant:type_name -> blossomsub.pb.ControlIWant 5, // 5: blossomsub.pb.ControlMessage.graft:type_name -> blossomsub.pb.ControlGraft 6, // 6: blossomsub.pb.ControlMessage.prune:type_name -> blossomsub.pb.ControlPrune - 7, // 7: blossomsub.pb.ControlPrune.peers:type_name -> blossomsub.pb.PeerInfo - 8, // [8:8] is the sub-list for method output_type - 8, // [8:8] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + 7, // 7: blossomsub.pb.ControlMessage.idontwant:type_name -> blossomsub.pb.ControlIDontWant + 8, // 8: blossomsub.pb.ControlPrune.peers:type_name -> blossomsub.pb.PeerInfo + 9, // [9:9] is the sub-list for method output_type + 9, // [9:9] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name } func init() { file_rpc_proto_init() } @@ -770,7 +834,7 @@ func file_rpc_proto_init() { } } file_rpc_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeerInfo); i { + switch v := v.(*ControlIDontWant); i { case 0: return &v.state case 1: @@ -782,6 +846,18 @@ func file_rpc_proto_init() { } } file_rpc_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PeerInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RPC_SubOpts); i { case 0: return &v.state @@ -794,14 +870,14 @@ func file_rpc_proto_init() { } } } - file_rpc_proto_msgTypes[7].OneofWrappers = []interface{}{} + file_rpc_proto_msgTypes[8].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_rpc_proto_rawDesc, NumEnums: 0, - NumMessages: 9, + NumMessages: 10, NumExtensions: 0, NumServices: 0, }, diff --git a/go-libp2p-blossomsub/pb/rpc.proto b/go-libp2p-blossomsub/pb/rpc.proto index ee4b695..d3d4ce1 100644 --- a/go-libp2p-blossomsub/pb/rpc.proto +++ b/go-libp2p-blossomsub/pb/rpc.proto @@ -30,6 +30,7 @@ message ControlMessage { repeated ControlIWant iwant = 2; repeated ControlGraft graft = 3; repeated ControlPrune prune = 4; + repeated ControlIDontWant idontwant = 5; } message ControlIHave { @@ -51,6 +52,10 @@ message ControlPrune { uint64 backoff = 3; } +message ControlIDontWant { + repeated bytes messageIDs = 1; +} + message PeerInfo { optional bytes peerID = 1; optional bytes signedPeerRecord = 2; diff --git a/go-libp2p-blossomsub/pb/trace.pb.go b/go-libp2p-blossomsub/pb/trace.pb.go index e7ed0b0..8867ed3 100644 --- a/go-libp2p-blossomsub/pb/trace.pb.go +++ b/go-libp2p-blossomsub/pb/trace.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 -// protoc v3.21.12 +// protoc-gen-go v1.34.1 +// protoc v5.27.0 // source: trace.proto package pb @@ -1288,10 +1288,11 @@ type TraceEvent_ControlMeta struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Ihave []*TraceEvent_ControlIHaveMeta `protobuf:"bytes,1,rep,name=ihave,proto3" json:"ihave,omitempty"` - Iwant []*TraceEvent_ControlIWantMeta `protobuf:"bytes,2,rep,name=iwant,proto3" json:"iwant,omitempty"` - Graft []*TraceEvent_ControlGraftMeta `protobuf:"bytes,3,rep,name=graft,proto3" json:"graft,omitempty"` - Prune []*TraceEvent_ControlPruneMeta `protobuf:"bytes,4,rep,name=prune,proto3" json:"prune,omitempty"` + Ihave []*TraceEvent_ControlIHaveMeta `protobuf:"bytes,1,rep,name=ihave,proto3" json:"ihave,omitempty"` + Iwant []*TraceEvent_ControlIWantMeta `protobuf:"bytes,2,rep,name=iwant,proto3" json:"iwant,omitempty"` + Graft []*TraceEvent_ControlGraftMeta `protobuf:"bytes,3,rep,name=graft,proto3" json:"graft,omitempty"` + Prune []*TraceEvent_ControlPruneMeta `protobuf:"bytes,4,rep,name=prune,proto3" json:"prune,omitempty"` + Idontwant []*TraceEvent_ControlIDontWantMeta `protobuf:"bytes,5,rep,name=idontwant,proto3" json:"idontwant,omitempty"` } func (x *TraceEvent_ControlMeta) Reset() { @@ -1354,6 +1355,13 @@ func (x *TraceEvent_ControlMeta) GetPrune() []*TraceEvent_ControlPruneMeta { return nil } +func (x *TraceEvent_ControlMeta) GetIdontwant() []*TraceEvent_ControlIDontWantMeta { + if x != nil { + return x.Idontwant + } + return nil +} + type TraceEvent_ControlIHaveMeta struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1558,11 +1566,58 @@ func (x *TraceEvent_ControlPruneMeta) GetPeers() [][]byte { return nil } +type TraceEvent_ControlIDontWantMeta struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MessageIDs [][]byte `protobuf:"bytes,1,rep,name=messageIDs,proto3" json:"messageIDs,omitempty"` +} + +func (x *TraceEvent_ControlIDontWantMeta) Reset() { + *x = TraceEvent_ControlIDontWantMeta{} + if protoimpl.UnsafeEnabled { + mi := &file_trace_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TraceEvent_ControlIDontWantMeta) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TraceEvent_ControlIDontWantMeta) ProtoMessage() {} + +func (x *TraceEvent_ControlIDontWantMeta) ProtoReflect() protoreflect.Message { + mi := &file_trace_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TraceEvent_ControlIDontWantMeta.ProtoReflect.Descriptor instead. +func (*TraceEvent_ControlIDontWantMeta) Descriptor() ([]byte, []int) { + return file_trace_proto_rawDescGZIP(), []int{0, 22} +} + +func (x *TraceEvent_ControlIDontWantMeta) GetMessageIDs() [][]byte { + if x != nil { + return x.MessageIDs + } + return nil +} + var File_trace_proto protoreflect.FileDescriptor var file_trace_proto_rawDesc = []byte{ 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x62, - 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x22, 0xca, 0x21, 0x0a, + 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x22, 0xd0, 0x22, 0x0a, 0x0a, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x37, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, @@ -1766,7 +1821,7 @@ var file_trace_proto_rawDesc = []byte{ 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, - 0x95, 0x02, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x12, + 0xe3, 0x02, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x40, 0x0a, 0x05, 0x69, 0x68, 0x61, 0x76, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, @@ -1783,64 +1838,72 @@ var file_trace_proto_rawDesc = []byte{ 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x4d, 0x65, 0x74, 0x61, - 0x52, 0x05, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x1a, 0x5d, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x49, 0x48, 0x61, 0x76, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x07, 0x62, + 0x52, 0x05, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x69, 0x64, 0x6f, 0x6e, 0x74, + 0x77, 0x61, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x62, 0x6c, 0x6f, + 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x49, 0x44, 0x6f, + 0x6e, 0x74, 0x57, 0x61, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x09, 0x69, 0x64, 0x6f, 0x6e, + 0x74, 0x77, 0x61, 0x6e, 0x74, 0x1a, 0x5d, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x49, 0x48, 0x61, 0x76, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, + 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x62, 0x69, + 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, + 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0x32, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x49, + 0x57, 0x61, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x1a, 0x3d, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x47, 0x72, 0x61, 0x66, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x07, + 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, + 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, + 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0x53, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, - 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, - 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0x32, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x49, 0x57, 0x61, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x44, 0x73, 0x1a, 0x3d, 0x0a, 0x10, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x47, 0x72, 0x61, 0x66, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, - 0x0a, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, - 0x00, 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, - 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0x53, 0x0a, 0x10, 0x43, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, - 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, - 0x52, 0x07, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, - 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x70, 0x65, 0x65, - 0x72, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x22, 0xea, - 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x55, 0x42, 0x4c, 0x49, - 0x53, 0x48, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, - 0x52, 0x45, 0x4a, 0x45, 0x43, 0x54, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x01, - 0x12, 0x15, 0x0a, 0x11, 0x44, 0x55, 0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x45, 0x5f, 0x4d, 0x45, - 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x45, 0x4c, 0x49, 0x56, - 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, - 0x41, 0x44, 0x44, 0x5f, 0x50, 0x45, 0x45, 0x52, 0x10, 0x04, 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x45, - 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x50, 0x45, 0x45, 0x52, 0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x52, - 0x45, 0x43, 0x56, 0x5f, 0x52, 0x50, 0x43, 0x10, 0x06, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x45, 0x4e, - 0x44, 0x5f, 0x52, 0x50, 0x43, 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x52, 0x4f, 0x50, 0x5f, - 0x52, 0x50, 0x43, 0x10, 0x08, 0x12, 0x08, 0x0a, 0x04, 0x4a, 0x4f, 0x49, 0x4e, 0x10, 0x09, 0x12, - 0x09, 0x0a, 0x05, 0x4c, 0x45, 0x41, 0x56, 0x45, 0x10, 0x0a, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x52, - 0x41, 0x46, 0x54, 0x10, 0x0b, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x10, 0x0c, - 0x12, 0x19, 0x0a, 0x15, 0x55, 0x4e, 0x44, 0x45, 0x4c, 0x49, 0x56, 0x45, 0x52, 0x41, 0x42, 0x4c, - 0x45, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x0d, 0x42, 0x07, 0x0a, 0x05, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x49, 0x44, 0x42, - 0x0c, 0x0a, 0x0a, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x11, 0x0a, - 0x0f, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x64, 0x65, 0x6c, 0x69, - 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x61, - 0x64, 0x64, 0x50, 0x65, 0x65, 0x72, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x76, - 0x65, 0x50, 0x65, 0x65, 0x72, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x72, 0x65, 0x63, 0x76, 0x52, 0x50, - 0x43, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x52, 0x50, 0x43, 0x42, 0x0a, 0x0a, - 0x08, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x52, 0x50, 0x43, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6a, 0x6f, - 0x69, 0x6e, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x42, 0x08, 0x0a, 0x06, - 0x5f, 0x67, 0x72, 0x61, 0x66, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x70, 0x72, 0x75, 0x6e, 0x65, - 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x75, 0x6e, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x61, 0x62, - 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x42, 0x0a, 0x0f, 0x54, 0x72, 0x61, - 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x2f, 0x0a, 0x05, - 0x62, 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x62, 0x6c, - 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, - 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x42, 0x43, 0x5a, - 0x41, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, - 0x75, 0x6d, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x71, 0x75, 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, - 0x6d, 0x2f, 0x6d, 0x6f, 0x6e, 0x6f, 0x72, 0x65, 0x70, 0x6f, 0x2f, 0x67, 0x6f, 0x2d, 0x6c, 0x69, - 0x62, 0x70, 0x32, 0x70, 0x2d, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2f, - 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x65, + 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, + 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x1a, 0x36, 0x0a, 0x14, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x49, 0x44, 0x6f, 0x6e, 0x74, 0x57, 0x61, 0x6e, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, + 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x49, 0x44, 0x73, 0x22, 0xea, 0x01, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, + 0x0f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, + 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x45, 0x4a, 0x45, 0x43, 0x54, 0x5f, 0x4d, 0x45, 0x53, + 0x53, 0x41, 0x47, 0x45, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x55, 0x50, 0x4c, 0x49, 0x43, + 0x41, 0x54, 0x45, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x02, 0x12, 0x13, 0x0a, + 0x0f, 0x44, 0x45, 0x4c, 0x49, 0x56, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, + 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x44, 0x44, 0x5f, 0x50, 0x45, 0x45, 0x52, 0x10, 0x04, + 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x45, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x50, 0x45, 0x45, 0x52, 0x10, + 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x43, 0x56, 0x5f, 0x52, 0x50, 0x43, 0x10, 0x06, 0x12, + 0x0c, 0x0a, 0x08, 0x53, 0x45, 0x4e, 0x44, 0x5f, 0x52, 0x50, 0x43, 0x10, 0x07, 0x12, 0x0c, 0x0a, + 0x08, 0x44, 0x52, 0x4f, 0x50, 0x5f, 0x52, 0x50, 0x43, 0x10, 0x08, 0x12, 0x08, 0x0a, 0x04, 0x4a, + 0x4f, 0x49, 0x4e, 0x10, 0x09, 0x12, 0x09, 0x0a, 0x05, 0x4c, 0x45, 0x41, 0x56, 0x45, 0x10, 0x0a, + 0x12, 0x09, 0x0a, 0x05, 0x47, 0x52, 0x41, 0x46, 0x54, 0x10, 0x0b, 0x12, 0x09, 0x0a, 0x05, 0x50, + 0x52, 0x55, 0x4e, 0x45, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x55, 0x4e, 0x44, 0x45, 0x4c, 0x49, + 0x56, 0x45, 0x52, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, + 0x0d, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x70, + 0x65, 0x65, 0x72, 0x49, 0x44, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x72, 0x65, 0x6a, 0x65, 0x63, + 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x64, 0x75, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x11, 0x0a, + 0x0f, 0x5f, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x61, 0x64, 0x64, 0x50, 0x65, 0x65, 0x72, 0x42, 0x0d, 0x0a, 0x0b, + 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x65, 0x65, 0x72, 0x42, 0x0a, 0x0a, 0x08, 0x5f, + 0x72, 0x65, 0x63, 0x76, 0x52, 0x50, 0x43, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x73, 0x65, 0x6e, 0x64, + 0x52, 0x50, 0x43, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x52, 0x50, 0x43, 0x42, + 0x07, 0x0a, 0x05, 0x5f, 0x6a, 0x6f, 0x69, 0x6e, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x65, 0x61, + 0x76, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x67, 0x72, 0x61, 0x66, 0x74, 0x42, 0x08, 0x0a, 0x06, + 0x5f, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x75, 0x6e, 0x64, 0x65, 0x6c, + 0x69, 0x76, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0x42, 0x0a, 0x0f, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x12, 0x2f, 0x0a, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x62, 0x6c, 0x6f, 0x73, 0x73, 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2e, 0x70, + 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x62, 0x61, + 0x74, 0x63, 0x68, 0x42, 0x43, 0x5a, 0x41, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x71, 0x75, + 0x69, 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x71, 0x75, 0x69, + 0x6c, 0x69, 0x62, 0x72, 0x69, 0x75, 0x6d, 0x2f, 0x6d, 0x6f, 0x6e, 0x6f, 0x72, 0x65, 0x70, 0x6f, + 0x2f, 0x67, 0x6f, 0x2d, 0x6c, 0x69, 0x62, 0x70, 0x32, 0x70, 0x2d, 0x62, 0x6c, 0x6f, 0x73, 0x73, + 0x6f, 0x6d, 0x73, 0x75, 0x62, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1856,7 +1919,7 @@ func file_trace_proto_rawDescGZIP() []byte { } var file_trace_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_trace_proto_msgTypes = make([]protoimpl.MessageInfo, 24) +var file_trace_proto_msgTypes = make([]protoimpl.MessageInfo, 25) var file_trace_proto_goTypes = []interface{}{ (TraceEvent_Type)(0), // 0: blossomsub.pb.TraceEvent.Type (*TraceEvent)(nil), // 1: blossomsub.pb.TraceEvent @@ -1883,6 +1946,7 @@ var file_trace_proto_goTypes = []interface{}{ (*TraceEvent_ControlIWantMeta)(nil), // 22: blossomsub.pb.TraceEvent.ControlIWantMeta (*TraceEvent_ControlGraftMeta)(nil), // 23: blossomsub.pb.TraceEvent.ControlGraftMeta (*TraceEvent_ControlPruneMeta)(nil), // 24: blossomsub.pb.TraceEvent.ControlPruneMeta + (*TraceEvent_ControlIDontWantMeta)(nil), // 25: blossomsub.pb.TraceEvent.ControlIDontWantMeta } var file_trace_proto_depIdxs = []int32{ 0, // 0: blossomsub.pb.TraceEvent.type:type_name -> blossomsub.pb.TraceEvent.Type @@ -1911,11 +1975,12 @@ var file_trace_proto_depIdxs = []int32{ 22, // 23: blossomsub.pb.TraceEvent.ControlMeta.iwant:type_name -> blossomsub.pb.TraceEvent.ControlIWantMeta 23, // 24: blossomsub.pb.TraceEvent.ControlMeta.graft:type_name -> blossomsub.pb.TraceEvent.ControlGraftMeta 24, // 25: blossomsub.pb.TraceEvent.ControlMeta.prune:type_name -> blossomsub.pb.TraceEvent.ControlPruneMeta - 26, // [26:26] is the sub-list for method output_type - 26, // [26:26] is the sub-list for method input_type - 26, // [26:26] is the sub-list for extension type_name - 26, // [26:26] is the sub-list for extension extendee - 0, // [0:26] is the sub-list for field type_name + 25, // 26: blossomsub.pb.TraceEvent.ControlMeta.idontwant:type_name -> blossomsub.pb.TraceEvent.ControlIDontWantMeta + 27, // [27:27] is the sub-list for method output_type + 27, // [27:27] is the sub-list for method input_type + 27, // [27:27] is the sub-list for extension type_name + 27, // [27:27] is the sub-list for extension extendee + 0, // [0:27] is the sub-list for field type_name } func init() { file_trace_proto_init() } @@ -2212,6 +2277,18 @@ func file_trace_proto_init() { return nil } } + file_trace_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TraceEvent_ControlIDontWantMeta); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_trace_proto_msgTypes[0].OneofWrappers = []interface{}{} file_trace_proto_msgTypes[2].OneofWrappers = []interface{}{} @@ -2240,7 +2317,7 @@ func file_trace_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_trace_proto_rawDesc, NumEnums: 1, - NumMessages: 24, + NumMessages: 25, NumExtensions: 0, NumServices: 0, }, diff --git a/go-libp2p-blossomsub/pb/trace.proto b/go-libp2p-blossomsub/pb/trace.proto index 7e84226..ec3bace 100644 --- a/go-libp2p-blossomsub/pb/trace.proto +++ b/go-libp2p-blossomsub/pb/trace.proto @@ -134,6 +134,7 @@ message TraceEvent { repeated ControlIWantMeta iwant = 2; repeated ControlGraftMeta graft = 3; repeated ControlPruneMeta prune = 4; + repeated ControlIDontWantMeta idontwant = 5; } message ControlIHaveMeta { @@ -153,6 +154,10 @@ message TraceEvent { optional bytes bitmask = 1; repeated bytes peers = 2; } + + message ControlIDontWantMeta { + repeated bytes messageIDs = 1; + } } message TraceEventBatch { diff --git a/go-libp2p-blossomsub/pubsub.go b/go-libp2p-blossomsub/pubsub.go index f968b5f..feabe17 100644 --- a/go-libp2p-blossomsub/pubsub.go +++ b/go-libp2p-blossomsub/pubsub.go @@ -150,8 +150,7 @@ type PubSub struct { blacklist Blacklist blacklistPeer chan peer.ID - peers map[peer.ID]chan *RPC - peersMx sync.RWMutex + peers map[peer.ID]*rpcQueue inboundStreamsMx sync.Mutex inboundStreams map[peer.ID]network.Stream @@ -203,11 +202,14 @@ type PubSubRouter interface { // EnoughPeers returns whether the router needs more peers before it's ready to publish new records. // Suggested (if greater than 0) is a suggested number of peers that the router should need. EnoughPeers(bitmask []byte, suggested int) bool - // AcceptFrom is invoked on any incoming message before pushing it to the validation pipeline + // AcceptFrom is invoked on any RPC envelope before pushing it to the validation pipeline // or processing control information. // Allows routers with internal scoring to vet peers before committing any processing resources // to the message and implement an effective graylist and react to validation queue overload. AcceptFrom(peer.ID) AcceptStatus + // PreValidation is invoked on messages in the RPC envelope right before pushing it to + // the validation pipeline + PreValidation([]*Message) // HandleRPC is invoked to process control messages in the RPC envelope. // It is invoked after subscriptions and payload messages have been processed. HandleRPC(*RPC) @@ -292,7 +294,7 @@ func NewPubSub(ctx context.Context, h host.Host, rt PubSubRouter, opts ...Option mySubs: make(map[string]map[*Subscription]struct{}), myRelays: make(map[string]int), bitmasks: make(map[string]map[peer.ID]struct{}), - peers: make(map[peer.ID]chan *RPC), + peers: make(map[peer.ID]*rpcQueue), inboundStreams: make(map[peer.ID]network.Stream), blacklist: NewMapBlacklist(), blacklistPeer: make(chan peer.ID), @@ -566,13 +568,11 @@ func WithAppSpecificRpcInspector(inspector func(peer.ID, *RPC) error) Option { // processLoop handles all inputs arriving on the channels func (p *PubSub) processLoop(ctx context.Context) { defer func() { - p.peersMx.Lock() // Clean up go routines. - for _, ch := range p.peers { - close(ch) + for _, q := range p.peers { + _ = q.Close() } p.peers = nil - p.peersMx.Unlock() p.bitmasks = nil p.seenMessages.Done() }() @@ -585,9 +585,7 @@ func (p *PubSub) processLoop(ctx context.Context) { case s := <-p.newPeerStream: pid := s.Conn().RemotePeer() - p.peersMx.RLock() - ch, ok := p.peers[pid] - p.peersMx.RUnlock() + q, ok := p.peers[pid] if !ok { log.Warn("new stream for unknown peer: ", pid) s.Reset() @@ -596,10 +594,8 @@ func (p *PubSub) processLoop(ctx context.Context) { if p.blacklist.Contains(pid) { log.Warn("closing stream for blacklisted peer: ", pid) - close(ch) - p.peersMx.Lock() + _ = q.Close() delete(p.peers, pid) - p.peersMx.Unlock() s.Reset() continue } @@ -607,9 +603,7 @@ func (p *PubSub) processLoop(ctx context.Context) { p.rt.AddPeer(pid, s.Protocol()) case pid := <-p.newPeerError: - p.peersMx.Lock() delete(p.peers, pid) - p.peersMx.Unlock() case <-p.peerDead: p.handleDeadPeers() @@ -659,14 +653,10 @@ func (p *PubSub) processLoop(ctx context.Context) { log.Infof("Blacklisting peer %s", pid) p.blacklist.Add(pid) - p.peersMx.RLock() - ch, ok := p.peers[pid] - p.peersMx.RUnlock() + q, ok := p.peers[pid] if ok { - close(ch) - p.peersMx.Lock() + _ = q.Close() delete(p.peers, pid) - p.peersMx.Unlock() for t, tmap := range p.bitmasks { if _, ok := tmap[pid]; ok { delete(tmap, pid) @@ -695,7 +685,6 @@ peerloop: } var peerset []peer.ID - p.peersMx.RLock() for p := range p.peers { _, ok := tmap[p] if !ok { @@ -703,7 +692,6 @@ peerloop: } peerset = append(peerset, p) } - p.peersMx.RUnlock() if len(peers) == 0 { peers = peerset @@ -743,25 +731,24 @@ func (p *PubSub) handlePendingPeers() { continue } - p.peersMx.RLock() if _, ok := p.peers[pid]; ok { - p.peersMx.RUnlock() log.Debug("already have connection to peer: ", pid) continue } - p.peersMx.RUnlock() if p.blacklist.Contains(pid) { log.Warn("ignoring connection from blacklisted peer: ", pid) continue } - messages := make(chan *RPC, p.peerOutboundQueueSize) - messages <- p.getHelloPacket() - go p.handleNewPeer(p.ctx, pid, messages) - p.peersMx.Lock() - p.peers[pid] = messages - p.peersMx.Unlock() + q := newRPCQueue(p.peerOutboundQueueSize, p.peerOutboundQueueSize) + if err := q.Push(p.ctx, p.getHelloPacket(), true); err != nil { + log.Debug("error sending hello packet to new peer: ", err) + _ = q.Close() + continue + } + go p.handleNewPeer(p.ctx, pid, q) + p.peers[pid] = q } } @@ -778,17 +765,13 @@ func (p *PubSub) handleDeadPeers() { p.peerDeadPrioLk.Unlock() for pid := range deadPeers { - p.peersMx.RLock() - ch, ok := p.peers[pid] - p.peersMx.RUnlock() + q, ok := p.peers[pid] if !ok { continue } - close(ch) - p.peersMx.Lock() + _ = q.Close() delete(p.peers, pid) - p.peersMx.Unlock() for t, tmap := range p.bitmasks { if _, ok := tmap[pid]; ok { @@ -809,12 +792,14 @@ func (p *PubSub) handleDeadPeers() { // still connected, must be a duplicate connection being closed. // we respawn the writer as we need to ensure there is a stream active log.Debugf("peer declared dead but still connected; respawning writer: %s", pid) - messages := make(chan *RPC, p.peerOutboundQueueSize) - messages <- p.getHelloPacket() - p.peersMx.Lock() - p.peers[pid] = messages - p.peersMx.Unlock() - go p.handleNewPeerWithBackoff(p.ctx, pid, backoffDelay, messages) + q := newRPCQueue(p.peerOutboundQueueSize, p.peerOutboundQueueSize) + if err := q.Push(p.ctx, p.getHelloPacket(), true); err != nil { + log.Debug("error sending hello packet to new peer: ", err) + _ = q.Close() + continue + } + p.peers[pid] = q + go p.handleNewPeerWithBackoff(p.ctx, pid, backoffDelay, q) } } } @@ -977,18 +962,15 @@ func (p *PubSub) announce(bitmask []byte, sub bool) { } out := rpcWithSubs(subopt) - p.peersMx.RLock() - for pid, peer := range p.peers { - select { - case peer <- out: - p.tracer.SendRPC(out, pid) - default: + for pid, q := range p.peers { + if err := q.TryPush(p.ctx, out, false); err != nil { log.Infof("Can't send announce message to peer %s: queue full; scheduling retry", pid) p.tracer.DropRPC(out, pid) go p.announceRetry(pid, bitmask, sub) + continue } + p.tracer.SendRPC(out, pid) } - p.peersMx.RUnlock() } func (p *PubSub) announceRetry(pid peer.ID, bitmask []byte, sub bool) { @@ -1012,9 +994,7 @@ func (p *PubSub) announceRetry(pid peer.ID, bitmask []byte, sub bool) { } func (p *PubSub) doAnnounceRetry(pid peer.ID, bitmask []byte, sub bool) { - p.peersMx.RLock() - peer, ok := p.peers[pid] - p.peersMx.RUnlock() + q, ok := p.peers[pid] if !ok { return } @@ -1025,14 +1005,13 @@ func (p *PubSub) doAnnounceRetry(pid peer.ID, bitmask []byte, sub bool) { } out := rpcWithSubs(subopt) - select { - case peer <- out: - p.tracer.SendRPC(out, pid) - default: + if err := q.TryPush(p.ctx, out, false); err != nil { log.Infof("Can't send announce message to peer %s: queue full; scheduling retry", pid) p.tracer.DropRPC(out, pid) go p.announceRetry(pid, bitmask, sub) + return } + p.tracer.SendRPC(out, pid) } // notifySubs sends a given message to all corresponding subscribers. @@ -1172,13 +1151,21 @@ func (p *PubSub) handleIncomingRPC(rpc *RPC) { p.tracer.ThrottlePeer(rpc.from) case AcceptAll: + var toPush []*Message for _, pmsg := range rpc.GetPublish() { if !(p.subscribedToMsg(pmsg) || p.canRelayMsg(pmsg)) { log.Debug("received message in bitmask we didn't subscribe to; ignoring message") continue } - p.pushMsg(&Message{pmsg, []byte{}, rpc.from, nil, false}) + msg := &Message{pmsg, []byte{}, rpc.from, nil, false} + if p.shouldPush(msg) { + toPush = append(toPush, msg) + } + } + p.rt.PreValidation(toPush) + for _, msg := range toPush { + p.pushMsg(msg) } } @@ -1197,27 +1184,28 @@ func DefaultPeerFilter(pid peer.ID, bitmask []byte) bool { return true } -// pushMsg pushes a message performing validation as necessary -func (p *PubSub) pushMsg(msg *Message) { +// shouldPush filters a message before validating and pushing it +// It returns true if the message can be further validated and pushed +func (p *PubSub) shouldPush(msg *Message) bool { src := msg.ReceivedFrom // reject messages from blacklisted peers if p.blacklist.Contains(src) { log.Debugf("dropping message from blacklisted peer %s", src) p.tracer.RejectMessage(msg, RejectBlacklstedPeer) - return + return false } // even if they are forwarded by good peers if p.blacklist.Contains(msg.GetFrom()) { log.Debugf("dropping message from blacklisted source %s", src) p.tracer.RejectMessage(msg, RejectBlacklistedSource) - return + return false } err := p.checkSigningPolicy(msg) if err != nil { log.Debugf("dropping message from %s: %s", src, err) - return + return false } // reject messages claiming to be from ourselves but not locally published @@ -1225,16 +1213,24 @@ func (p *PubSub) pushMsg(msg *Message) { if peer.ID(msg.GetFrom()) == self && src != self { log.Debugf("dropping message claiming to be from self but forwarded from %s", src) p.tracer.RejectMessage(msg, RejectSelfOrigin) - return + return false } // have we already seen and validated this message? id := p.idGen.ID(msg) if p.seenMessage(id) { p.tracer.DuplicateMessage(msg) - return + return false } + return true +} + +// pushMsg pushes a message performing validation as necessary +func (p *PubSub) pushMsg(msg *Message) { + src := msg.ReceivedFrom + id := p.idGen.ID(msg) + if !p.val.Push(src, msg) { return } diff --git a/go-libp2p-blossomsub/pubsub_test.go b/go-libp2p-blossomsub/pubsub_test.go index 4f1edea..ffec9af 100644 --- a/go-libp2p-blossomsub/pubsub_test.go +++ b/go-libp2p-blossomsub/pubsub_test.go @@ -24,7 +24,12 @@ func TestPubSubRemovesBlacklistedPeer(t *testing.T) { // Bad peer is blacklisted after it has connected. // Calling p.BlacklistPeer directly does the right thing but we should also clean // up the peer if it has been added the the blacklist by another means. - bl.Add(hosts[0].ID()) + ran := make(chan struct{}) + psubs1.eval <- func() { + defer close(ran) + bl.Add(hosts[0].ID()) + } + <-ran bitmasks, err := psubs0.Join([]byte{0x01, 0x00}) if err != nil { t.Fatal(err) diff --git a/go-libp2p-blossomsub/rpc_queue.go b/go-libp2p-blossomsub/rpc_queue.go new file mode 100644 index 0000000..071e021 --- /dev/null +++ b/go-libp2p-blossomsub/rpc_queue.go @@ -0,0 +1,94 @@ +package blossomsub + +import ( + "context" + "errors" +) + +var ErrQueueFull = errors.New("queue full") + +// rpcQueue is a queue of RPCs with two priority levels: fast and slow. +// Fast RPCs are processed before slow RPCs. +type rpcQueue struct { + ctx context.Context + cancel context.CancelFunc + fastQueue chan *RPC + slowQueue chan *RPC +} + +// Close closes the queue. +func (q *rpcQueue) Close() error { + q.cancel() + return nil +} + +// TryPush tries to push an RPC to the queue. +// Returns ErrQueueFull if the queue is full, or the context error if the context is done. +func (q *rpcQueue) TryPush(ctx context.Context, rpc *RPC, fast bool) error { + ch := q.slowQueue + if fast { + ch = q.fastQueue + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-q.ctx.Done(): + return q.ctx.Err() + case ch <- rpc: + return nil + default: + return ErrQueueFull + } +} + +// Push pushes an RPC to the queue. +// Returns the context error if the context is done. +func (q *rpcQueue) Push(ctx context.Context, rpc *RPC, fast bool) error { + ch := q.slowQueue + if fast { + ch = q.fastQueue + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-q.ctx.Done(): + return q.ctx.Err() + case ch <- rpc: + return nil + } +} + +// Pop pops an RPC from the queue. +// Returns the RPC or the context error. +func (q *rpcQueue) Pop(ctx context.Context) (*RPC, error) { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-q.ctx.Done(): + return nil, q.ctx.Err() + case rpc := <-q.fastQueue: + return rpc, nil + default: + } + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-q.ctx.Done(): + return nil, q.ctx.Err() + case rpc := <-q.fastQueue: + return rpc, nil + case rpc := <-q.slowQueue: + return rpc, nil + } +} + +// newRPCQueue creates a new RPC queue. +func newRPCQueue(fastSize, slowSize int) *rpcQueue { + ctx, cancel := context.WithCancel(context.Background()) + return &rpcQueue{ + ctx: ctx, + cancel: cancel, + fastQueue: make(chan *RPC, fastSize), + slowQueue: make(chan *RPC, slowSize), + } +} diff --git a/go-libp2p-blossomsub/rpc_queue_test.go b/go-libp2p-blossomsub/rpc_queue_test.go new file mode 100644 index 0000000..f98d526 --- /dev/null +++ b/go-libp2p-blossomsub/rpc_queue_test.go @@ -0,0 +1,81 @@ +package blossomsub + +import ( + "context" + "errors" + "testing" +) + +func TestRPCQueue(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + q := newRPCQueue(32, 32) + defer q.Close() + + rpcs := []*RPC{ + {from: "a"}, + {from: "b"}, + {from: "c"}, + {from: "d"}, + } + + for i, tc := range []struct { + fast bool + rpc *RPC + }{ + {true, rpcs[0]}, + {false, rpcs[1]}, + {true, rpcs[2]}, + {false, rpcs[3]}, + } { + if err := q.TryPush(ctx, tc.rpc, tc.fast); err != nil { + t.Fatal(i, "unexpected error:", err) + } + } + for i, tc := range []struct { + rpc *RPC + }{ + {rpcs[0]}, + {rpcs[2]}, + {rpcs[1]}, + {rpcs[3]}, + } { + rpc, err := q.Pop(ctx) + if err != nil { + t.Fatal(i, "unexpected error:", err) + } + if rpc != tc.rpc { + t.Fatal(i, "expected rpc", string(tc.rpc.from), "got", string(rpc.from)) + } + } + + q = newRPCQueue(0, 0) + defer q.Close() + + type result struct { + rpc *RPC + err error + } + res := make(chan result, 1) + go func() { + rpc, err := q.Pop(ctx) + res <- result{rpc, err} + }() + if err := q.Push(ctx, rpcs[0], false); err != nil { + t.Fatal("unexpected error:", err) + } + r := <-res + if r.err != nil { + t.Fatal("unexpected error:", r.err) + } + if r.rpc != rpcs[0] { + t.Fatal("expected rpc", string(rpcs[0].from), "got", string(r.rpc.from)) + } + + if err := q.TryPush(ctx, rpcs[0], false); !errors.Is(err, ErrQueueFull) { + t.Fatal("expected ErrQueueFull, got", err) + } +} diff --git a/go-libp2p-blossomsub/trace.go b/go-libp2p-blossomsub/trace.go index 61a9206..0e5e616 100644 --- a/go-libp2p-blossomsub/trace.go +++ b/go-libp2p-blossomsub/trace.go @@ -420,11 +420,23 @@ func (t *pubsubTracer) traceRPCMeta(rpc *RPC) *pb.TraceEvent_RPCMeta { }) } + var idontwant []*pb.TraceEvent_ControlIDontWantMeta + for _, ctl := range rpc.Control.Idontwant { + var mids [][]byte + for _, mid := range ctl.MessageIDs { + mids = append(mids, []byte(mid)) + } + idontwant = append(idontwant, &pb.TraceEvent_ControlIDontWantMeta{ + MessageIDs: mids, + }) + } + rpcMeta.Control = &pb.TraceEvent_ControlMeta{ - Ihave: ihave, - Iwant: iwant, - Graft: graft, - Prune: prune, + Ihave: ihave, + Iwant: iwant, + Graft: graft, + Prune: prune, + Idontwant: idontwant, } } diff --git a/node/config/p2p.go b/node/config/p2p.go index efddceb..0c4fa7e 100644 --- a/node/config/p2p.go +++ b/node/config/p2p.go @@ -30,7 +30,10 @@ type P2PConfig struct { GraftFloodThreshold time.Duration `yaml:"graftFloodThreshold"` MaxIHaveLength int `yaml:"maxIHaveLength"` MaxIHaveMessages int `yaml:"maxIHaveMessages"` + MaxIDontWantMessages int `yaml:"maxIDontWantMessages"` IWantFollowupTime time.Duration `yaml:"iWantFollowupTime"` + IDontWantMessageThreshold int `yaml:"iDontWantMessageThreshold"` + IDontWantMessageTTL int `yaml:"iDontWantMessageTTL"` BootstrapPeers []string `yaml:"bootstrapPeers"` ListenMultiaddr string `yaml:"listenMultiaddr"` PeerPrivKey string `yaml:"peerPrivKey"` diff --git a/node/internal/observability/blossomsub.go b/node/internal/observability/blossomsub.go index 5404670..44ecc89 100644 --- a/node/internal/observability/blossomsub.go +++ b/node/internal/observability/blossomsub.go @@ -32,6 +32,7 @@ type blossomSubRawTracer struct { undeliverableMessageTotal *prometheus.CounterVec iHaveMessageHistogram *prometheus.HistogramVec iWantMessageHistogram *prometheus.HistogramVec + iDontWantMessageHistogram *prometheus.HistogramVec } func (b *blossomSubRawTracer) observeControl(control *pb.ControlMessage, direction string) { @@ -43,6 +44,9 @@ func (b *blossomSubRawTracer) observeControl(control *pb.ControlMessage, directi for _, iWant := range control.GetIwant() { b.iWantMessageHistogram.WithLabelValues(labels...).Observe(float64(len(iWant.GetMessageIDs()))) } + for _, iDontWant := range control.GetIdontwant() { + b.iDontWantMessageHistogram.WithLabelValues(labels...).Observe(float64(len(iDontWant.GetMessageIDs()))) + } } var _ blossomsub.RawTracer = (*blossomSubRawTracer)(nil) @@ -146,6 +150,7 @@ func (b *blossomSubRawTracer) Describe(ch chan<- *prometheus.Desc) { b.undeliverableMessageTotal.Describe(ch) b.iHaveMessageHistogram.Describe(ch) b.iWantMessageHistogram.Describe(ch) + b.iDontWantMessageHistogram.Describe(ch) } // Collect implements prometheus.Collector. @@ -167,6 +172,7 @@ func (b *blossomSubRawTracer) Collect(ch chan<- prometheus.Metric) { b.undeliverableMessageTotal.Collect(ch) b.iHaveMessageHistogram.Collect(ch) b.iWantMessageHistogram.Collect(ch) + b.iDontWantMessageHistogram.Collect(ch) } type BlossomSubRawTracer interface { @@ -309,6 +315,15 @@ func NewBlossomSubRawTracer() BlossomSubRawTracer { }, []string{"direction"}, ), + iDontWantMessageHistogram: prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: blossomSubNamespace, + Name: "idontwant_messages", + Help: "Histogram of the number of messages in an IDontWant message.", + Buckets: prometheus.ExponentialBuckets(1, 2, 14), + }, + []string{"direction"}, + ), } return b } diff --git a/node/p2p/blossomsub.go b/node/p2p/blossomsub.go index 16145db..e1a8bbe 100644 --- a/node/p2p/blossomsub.go +++ b/node/p2p/blossomsub.go @@ -1033,9 +1033,18 @@ func withDefaults(p2pConfig *config.P2PConfig) *config.P2PConfig { if p2pConfig.MaxIHaveMessages == 0 { p2pConfig.MaxIHaveMessages = blossomsub.BlossomSubMaxIHaveMessages } + if p2pConfig.MaxIDontWantMessages == 0 { + p2pConfig.MaxIDontWantMessages = blossomsub.BlossomSubMaxIDontWantMessages + } if p2pConfig.IWantFollowupTime == 0 { p2pConfig.IWantFollowupTime = blossomsub.BlossomSubIWantFollowupTime } + if p2pConfig.IDontWantMessageThreshold == 0 { + p2pConfig.IDontWantMessageThreshold = blossomsub.BlossomSubIDontWantMessageThreshold + } + if p2pConfig.IDontWantMessageTTL == 0 { + p2pConfig.IDontWantMessageTTL = blossomsub.BlossomSubIDontWantMessageTTL + } if p2pConfig.LowWatermarkConnections == 0 { p2pConfig.LowWatermarkConnections = defaultLowWatermarkConnections } @@ -1099,7 +1108,10 @@ func toBlossomSubParams(p2pConfig *config.P2PConfig) blossomsub.BlossomSubParams GraftFloodThreshold: p2pConfig.GraftFloodThreshold, MaxIHaveLength: p2pConfig.MaxIHaveLength, MaxIHaveMessages: p2pConfig.MaxIHaveMessages, + MaxIDontWantMessages: p2pConfig.MaxIDontWantMessages, IWantFollowupTime: p2pConfig.IWantFollowupTime, + IDontWantMessageThreshold: p2pConfig.IDontWantMessageThreshold, + IDontWantMessageTTL: p2pConfig.IDontWantMessageTTL, SlowHeartbeatWarning: 0.1, } }