Skip to content

Commit eccd4f1

Browse files
fix(p2p): handle pre-release versions in bee260 backward compatibility check (#5327)
1 parent 26530f1 commit eccd4f1

File tree

2 files changed

+180
-3
lines changed

2 files changed

+180
-3
lines changed

pkg/p2p/libp2p/libp2p.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -655,11 +655,13 @@ func (s *Service) handleIncoming(stream network.Stream) {
655655
return
656656
}
657657

658+
bee260Compat := s.bee260BackwardCompatibility(peerID)
659+
658660
i, err := s.handshakeService.Handle(
659661
s.ctx,
660662
handshakeStream,
661663
peerMultiaddrs,
662-
handshake.WithBee260Compatibility(s.bee260BackwardCompatibility(peerID)),
664+
handshake.WithBee260Compatibility(bee260Compat),
663665
)
664666
if err != nil {
665667
s.logger.Debug("stream handler: handshake: handle failed", "peer_id", peerID, "error", err)
@@ -1070,11 +1072,13 @@ func (s *Service) Connect(ctx context.Context, addrs []ma.Multiaddr) (address *b
10701072
return nil, fmt.Errorf("build peer multiaddrs: %w", err)
10711073
}
10721074

1075+
bee260Compat := s.bee260BackwardCompatibility(peerID)
1076+
10731077
i, err := s.handshakeService.Handshake(
10741078
s.ctx,
10751079
handshakeStream,
10761080
peerMultiaddrs,
1077-
handshake.WithBee260Compatibility(s.bee260BackwardCompatibility(peerID)),
1081+
handshake.WithBee260Compatibility(bee260Compat),
10781082
)
10791083
if err != nil {
10801084
_ = handshakeStream.Reset()
@@ -1484,7 +1488,15 @@ func (s *Service) bee260BackwardCompatibility(peerID libp2ppeer.ID) bool {
14841488
if err != nil {
14851489
return false
14861490
}
1487-
return v.LessThan(version270)
1491+
1492+
// Compare major.minor.patch only (ignore pre-release)
1493+
// This way 2.7.0-rc12 is treated as >= 2.7.0
1494+
vCore, err := semver.NewVersion(fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch))
1495+
if err != nil {
1496+
return false
1497+
}
1498+
result := vCore.LessThan(version270)
1499+
return result
14881500
}
14891501

14901502
// appendSpace adds a leading space character if the string is not empty.

pkg/p2p/libp2p/version_test.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright 2026 The Swarm Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package libp2p
6+
7+
import (
8+
"context"
9+
"testing"
10+
11+
"github.com/ethersphere/bee/v2/pkg/crypto"
12+
"github.com/ethersphere/bee/v2/pkg/log"
13+
"github.com/ethersphere/bee/v2/pkg/statestore/mock"
14+
"github.com/ethersphere/bee/v2/pkg/swarm"
15+
libp2ppeer "github.com/libp2p/go-libp2p/core/peer"
16+
)
17+
18+
func TestBee260BackwardCompatibility(t *testing.T) {
19+
t.Parallel()
20+
21+
tests := []struct {
22+
name string
23+
userAgent string
24+
want bool
25+
}{
26+
// Versions < 2.7.0 should require backward compatibility
27+
{
28+
name: "version 2.6.0",
29+
userAgent: "bee/2.6.0 go1.22.0 linux/amd64",
30+
want: true,
31+
},
32+
{
33+
name: "version 2.6.5",
34+
userAgent: "bee/2.6.5 go1.22.0 linux/amd64",
35+
want: true,
36+
},
37+
{
38+
name: "version 2.5.0",
39+
userAgent: "bee/2.5.0 go1.21.0 linux/amd64",
40+
want: true,
41+
},
42+
{
43+
name: "version 2.6.0-beta1",
44+
userAgent: "bee/2.6.0-beta1 go1.22.0 linux/amd64",
45+
want: true,
46+
},
47+
// Versions >= 2.7.0 should NOT require backward compatibility
48+
{
49+
name: "version 2.7.0",
50+
userAgent: "bee/2.7.0 go1.23.0 linux/amd64",
51+
want: false,
52+
},
53+
{
54+
name: "version 2.8.0",
55+
userAgent: "bee/2.8.0 go1.23.0 linux/amd64",
56+
want: false,
57+
},
58+
{
59+
name: "version 3.0.0",
60+
userAgent: "bee/3.0.0 go1.25.0 linux/amd64",
61+
want: false,
62+
},
63+
// Pre-release versions >= 2.7.0 should NOT require backward compatibility
64+
// This is the critical fix: 2.7.0-rcX should be treated as >= 2.7.0
65+
{
66+
name: "version 2.7.0-rc1",
67+
userAgent: "bee/2.7.0-rc1 go1.23.0 linux/amd64",
68+
want: false,
69+
},
70+
{
71+
name: "version 2.7.0-rc12",
72+
userAgent: "bee/2.7.0-rc12-b39629d5-dirty go1.25.6 linux/amd64",
73+
want: false,
74+
},
75+
{
76+
name: "version 2.7.0-beta1",
77+
userAgent: "bee/2.7.0-beta1 go1.23.0 linux/amd64",
78+
want: false,
79+
},
80+
{
81+
name: "version 2.8.0-rc1",
82+
userAgent: "bee/2.8.0-rc1 go1.24.0 linux/amd64",
83+
want: false,
84+
},
85+
{
86+
name: "version 2.9.0-beta1",
87+
userAgent: "bee/2.9.0-beta1 go1.24.0 linux/amd64",
88+
want: false,
89+
},
90+
// Edge cases that should return false (not requiring backward compat)
91+
{
92+
name: "empty user agent",
93+
userAgent: "",
94+
want: false,
95+
},
96+
{
97+
name: "malformed user agent missing space",
98+
userAgent: "bee/2.6.0",
99+
want: false,
100+
},
101+
{
102+
name: "non-bee user agent",
103+
userAgent: "other/1.0.0 go1.22.0 linux/amd64",
104+
want: false,
105+
},
106+
{
107+
name: "invalid version format",
108+
userAgent: "bee/invalid go1.22.0 linux/amd64",
109+
want: false,
110+
},
111+
{
112+
name: "default libp2p user agent",
113+
userAgent: "github.com/libp2p/go-libp2p",
114+
want: false,
115+
},
116+
}
117+
118+
for _, tc := range tests {
119+
t.Run(tc.name, func(t *testing.T) {
120+
t.Parallel()
121+
122+
// Create a service with minimal configuration
123+
ctx, cancel := context.WithCancel(context.Background())
124+
defer cancel()
125+
126+
swarmKey, err := crypto.GenerateSecp256k1Key()
127+
if err != nil {
128+
t.Fatal(err)
129+
}
130+
131+
overlay := swarm.RandAddress(t)
132+
addr := ":0"
133+
networkID := uint64(1)
134+
135+
statestore := mock.NewStateStore()
136+
defer statestore.Close()
137+
138+
s, err := New(ctx, crypto.NewDefaultSigner(swarmKey), networkID, overlay, addr, nil, statestore, nil, log.Noop, nil, Options{})
139+
if err != nil {
140+
t.Fatal(err)
141+
}
142+
defer s.Close()
143+
144+
// Create a random test peer ID - we only need any valid libp2p peer ID
145+
// The peerstore lookup will be mocked by setting the AgentVersion directly
146+
libp2pPeerID, err := libp2ppeer.Decode("16Uiu2HAm3g4hXfCWTDhPBq3KkqpV3wGkPVgMJY3Jt8gGTYWiTWNZ")
147+
if err != nil {
148+
t.Fatal(err)
149+
}
150+
151+
// Set the user agent in the peerstore if provided
152+
if tc.userAgent != "" {
153+
if err := s.host.Peerstore().Put(libp2pPeerID, "AgentVersion", tc.userAgent); err != nil {
154+
t.Fatal(err)
155+
}
156+
}
157+
158+
// Test the backward compatibility check
159+
got := s.bee260BackwardCompatibility(libp2pPeerID)
160+
if got != tc.want {
161+
t.Errorf("bee260BackwardCompatibility() = %v, want %v (userAgent: %q)", got, tc.want, tc.userAgent)
162+
}
163+
})
164+
}
165+
}

0 commit comments

Comments
 (0)