"net"
"testing"
+ "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anacrolix/dht/v2/krpc"
)
var (
- addrs = []net.Addr{
+ addrs6 = []net.Addr{
&net.TCPAddr{IP: net.IPv6loopback, Port: 4747},
&net.TCPAddr{IP: net.IPv6loopback, Port: 4748},
+ &net.TCPAddr{IP: net.IPv6loopback, Port: 4749},
+ &net.TCPAddr{IP: net.IPv6loopback, Port: 4750},
+ }
+ addrs4 = []net.Addr{
&net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 4747},
&net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 4748},
+ &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 4749},
+ &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 4750},
+ }
+ addrs = []net.Addr{
+ addrs6[0],
+ addrs6[1],
+ addrs4[0],
+ addrs4[1],
}
- f = pp.PexOutgoingConn
)
-func TestPexAdded(t *testing.T) {
- t.Run("noHold", func(t *testing.T) {
- s := new(pexState)
- s.Add(&PeerConn{remoteAddr: addrs[0], outgoing: true})
- targ := &pexState{
- ev: []pexEvent{
- pexEvent{pexAdd, addrs[0], pp.PexOutgoingConn},
- },
- nc: 1,
- }
- require.EqualValues(t, targ, s)
- })
- t.Run("belowTarg", func(t *testing.T) {
- s := &pexState{
- hold: []pexEvent{
- pexEvent{pexDrop, addrs[1], 0},
- },
- nc: 0,
- }
- s.Add(&PeerConn{remoteAddr: addrs[0]})
- targ := &pexState{
- hold: []pexEvent{
- pexEvent{pexDrop, addrs[1], 0},
- },
- ev: []pexEvent{
- pexEvent{pexAdd, addrs[0], 0},
- },
- nc: 1,
- }
- require.EqualValues(t, targ, s)
- })
- t.Run("aboveTarg", func(t *testing.T) {
- holdAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 4848}
- s := &pexState{
- hold: []pexEvent{
- pexEvent{pexDrop, holdAddr, 0},
- },
- nc: pexTargAdded,
- }
- s.Add(&PeerConn{remoteAddr: addrs[0]})
- targ := &pexState{
- hold: []pexEvent{},
- ev: []pexEvent{
- pexEvent{pexDrop, holdAddr, 0},
- pexEvent{pexAdd, addrs[0], 0},
- },
- nc: pexTargAdded + 1,
- }
- require.EqualValues(t, targ, s)
- })
-}
-
-func TestPexDropped(t *testing.T) {
- t.Run("belowTarg", func(t *testing.T) {
- s := &pexState{nc: 1}
- s.Drop(&PeerConn{remoteAddr: addrs[0], pex: pexConnState{Listed: true}})
- targ := &pexState{
- hold: []pexEvent{pexEvent{pexDrop, addrs[0], 0}},
- nc: 0,
- }
- require.EqualValues(t, targ, s)
- })
- t.Run("aboveTarg", func(t *testing.T) {
- s := &pexState{nc: pexTargAdded + 1}
- s.Drop(&PeerConn{remoteAddr: addrs[0], pex: pexConnState{Listed: true}})
- targ := &pexState{
- ev: []pexEvent{pexEvent{pexDrop, addrs[0], 0}},
- nc: pexTargAdded,
- }
- require.EqualValues(t, targ, s)
- })
- t.Run("aboveTargNotListed", func(t *testing.T) {
- s := &pexState{nc: pexTargAdded + 1}
- s.Drop(&PeerConn{remoteAddr: addrs[0], pex: pexConnState{Listed: false}})
- targ := &pexState{nc: pexTargAdded + 1}
- require.EqualValues(t, targ, s)
- })
-}
-
func TestPexReset(t *testing.T) {
- s := &pexState{
- hold: []pexEvent{pexEvent{pexDrop, addrs[0], 0}},
- ev: []pexEvent{pexEvent{pexAdd, addrs[1], 0}},
- nc: 1,
+ s := &pexState{}
+ conns := []PeerConn{
+ {Peer: Peer{RemoteAddr: addrs[0]}},
+ {Peer: Peer{RemoteAddr: addrs[1]}},
+ {Peer: Peer{RemoteAddr: addrs[2]}},
}
+ s.Add(&conns[0])
+ s.Add(&conns[1])
+ s.Drop(&conns[0])
s.Reset()
targ := new(pexState)
require.EqualValues(t, targ, s)
}
+func mustNodeAddr(addr net.Addr) krpc.NodeAddr {
+ ret, ok := nodeAddr(addr)
+ if !ok {
+ panic(addr)
+ }
+ return ret
+}
+
var testcases = []struct {
- name string
- in *pexState
- arg int
- targM *pp.PexMsg
- targS int
+ name string
+ in *pexState
+ targ pp.PexMsg
+ update func(*pexState)
+ targ1 pp.PexMsg
}{
{
- name: "empty",
- in: &pexState{},
- arg: 0,
- targM: &pp.PexMsg{},
- targS: 0,
+ name: "empty",
+ in: &pexState{},
+ targ: pp.PexMsg{},
+ },
+ {
+ name: "add0",
+ in: func() *pexState {
+ s := new(pexState)
+ nullAddr := &net.TCPAddr{}
+ s.Add(&PeerConn{Peer: Peer{RemoteAddr: nullAddr}})
+ return s
+ }(),
+ targ: pp.PexMsg{},
+ },
+ {
+ name: "drop0",
+ in: func() *pexState {
+ nullAddr := &net.TCPAddr{}
+ s := new(pexState)
+ s.Drop(&PeerConn{Peer: Peer{RemoteAddr: nullAddr}, pex: pexConnState{Listed: true}})
+ return s
+ }(),
+ targ: pp.PexMsg{},
},
{
name: "add4",
- in: &pexState{
- ev: []pexEvent{
- pexEvent{pexAdd, addrs[0], f},
- pexEvent{pexAdd, addrs[1], f},
- pexEvent{pexAdd, addrs[2], f},
- pexEvent{pexAdd, addrs[3], f},
- },
- },
- arg: 0,
- targM: &pp.PexMsg{
+ in: func() *pexState {
+ s := new(pexState)
+ s.Add(&PeerConn{Peer: Peer{RemoteAddr: addrs[0]}})
+ s.Add(&PeerConn{Peer: Peer{RemoteAddr: addrs[1], outgoing: true}})
+ s.Add(&PeerConn{Peer: Peer{RemoteAddr: addrs[2], outgoing: true}})
+ s.Add(&PeerConn{Peer: Peer{RemoteAddr: addrs[3]}})
+ return s
+ }(),
+ targ: pp.PexMsg{
Added: krpc.CompactIPv4NodeAddrs{
- nodeAddr(addrs[2]),
- nodeAddr(addrs[3]),
+ mustNodeAddr(addrs[2]),
+ mustNodeAddr(addrs[3]),
},
- AddedFlags: []pp.PexPeerFlags{f, f},
+ AddedFlags: []pp.PexPeerFlags{pp.PexOutgoingConn, 0},
Added6: krpc.CompactIPv6NodeAddrs{
- nodeAddr(addrs[0]),
- nodeAddr(addrs[1]),
+ mustNodeAddr(addrs[0]),
+ mustNodeAddr(addrs[1]),
},
- Added6Flags: []pp.PexPeerFlags{f, f},
+ Added6Flags: []pp.PexPeerFlags{0, pp.PexOutgoingConn},
},
- targS: 4,
},
{
name: "drop2",
- arg: 0,
- in: &pexState{
- ev: []pexEvent{
- pexEvent{pexDrop, addrs[0], f},
- pexEvent{pexDrop, addrs[2], f},
- },
- },
- targM: &pp.PexMsg{
+ in: func() *pexState {
+ s := &pexState{nc: pexTargAdded + 2}
+ s.Drop(&PeerConn{Peer: Peer{RemoteAddr: addrs[0]}, pex: pexConnState{Listed: true}})
+ s.Drop(&PeerConn{Peer: Peer{RemoteAddr: addrs[2]}, pex: pexConnState{Listed: true}})
+ return s
+ }(),
+ targ: pp.PexMsg{
Dropped: krpc.CompactIPv4NodeAddrs{
- nodeAddr(addrs[2]),
+ mustNodeAddr(addrs[2]),
},
Dropped6: krpc.CompactIPv6NodeAddrs{
- nodeAddr(addrs[0]),
+ mustNodeAddr(addrs[0]),
},
},
- targS: 2,
},
{
name: "add2drop1",
- arg: 0,
- in: &pexState{
- ev: []pexEvent{
- pexEvent{pexAdd, addrs[0], f},
- pexEvent{pexAdd, addrs[1], f},
- pexEvent{pexDrop, addrs[0], f},
- },
- },
- targM: &pp.PexMsg{
+ in: func() *pexState {
+ conns := []PeerConn{
+ {Peer: Peer{RemoteAddr: addrs[0]}},
+ {Peer: Peer{RemoteAddr: addrs[1]}},
+ {Peer: Peer{RemoteAddr: addrs[2]}},
+ }
+ s := &pexState{nc: pexTargAdded}
+ s.Add(&conns[0])
+ s.Add(&conns[1])
+ s.Drop(&conns[0])
+ s.Drop(&conns[2]) // to be ignored: it wasn't added
+ return s
+ }(),
+ targ: pp.PexMsg{
Added6: krpc.CompactIPv6NodeAddrs{
- nodeAddr(addrs[1]),
+ mustNodeAddr(addrs[1]),
},
- Added6Flags: []pp.PexPeerFlags{f},
+ Added6Flags: []pp.PexPeerFlags{0},
},
- targS: 3,
},
{
name: "delayed",
- arg: 0,
- in: &pexState{
- ev: []pexEvent{
- pexEvent{pexAdd, addrs[0], f},
- pexEvent{pexAdd, addrs[1], f},
- pexEvent{pexAdd, addrs[2], f},
+ in: func() *pexState {
+ conns := []PeerConn{
+ {Peer: Peer{RemoteAddr: addrs[0]}},
+ {Peer: Peer{RemoteAddr: addrs[1]}},
+ {Peer: Peer{RemoteAddr: addrs[2]}},
+ }
+ s := new(pexState)
+ s.Add(&conns[0])
+ s.Add(&conns[1])
+ s.Add(&conns[2])
+ s.Drop(&conns[0]) // on hold: s.nc < pexTargAdded
+ s.Drop(&conns[2])
+ s.Drop(&conns[1])
+ return s
+ }(),
+ targ: pp.PexMsg{
+ Added: krpc.CompactIPv4NodeAddrs{
+ mustNodeAddr(addrs[2]),
},
- hold: []pexEvent{
- pexEvent{pexDrop, addrs[0], f},
- pexEvent{pexDrop, addrs[2], f},
- pexEvent{pexDrop, addrs[1], f},
+ AddedFlags: []pp.PexPeerFlags{0},
+ Added6: krpc.CompactIPv6NodeAddrs{
+ mustNodeAddr(addrs[0]),
+ mustNodeAddr(addrs[1]),
},
+ Added6Flags: []pp.PexPeerFlags{0, 0},
},
- targM: &pp.PexMsg{
- Added: krpc.CompactIPv4NodeAddrs{
- nodeAddr(addrs[2]),
- },
- AddedFlags: []pp.PexPeerFlags{f},
+ },
+ {
+ name: "unheld",
+ in: func() *pexState {
+ conns := []PeerConn{
+ {Peer: Peer{RemoteAddr: addrs[0]}},
+ {Peer: Peer{RemoteAddr: addrs[1]}},
+ }
+ s := &pexState{nc: pexTargAdded - 1}
+ s.Add(&conns[0])
+ s.Drop(&conns[0]) // on hold: s.nc < pexTargAdded
+ s.Add(&conns[1]) // unholds the above
+ return s
+ }(),
+ targ: pp.PexMsg{
Added6: krpc.CompactIPv6NodeAddrs{
- nodeAddr(addrs[0]),
- nodeAddr(addrs[1]),
+ mustNodeAddr(addrs[1]),
},
- Added6Flags: []pp.PexPeerFlags{f, f},
+ Added6Flags: []pp.PexPeerFlags{0},
},
- targS: 3,
},
{
name: "followup",
- arg: 1,
- in: &pexState{
- ev: []pexEvent{
- pexEvent{pexAdd, addrs[0], f},
- pexEvent{pexAdd, addrs[1], f},
+ in: func() *pexState {
+ s := new(pexState)
+ s.Add(&PeerConn{Peer: Peer{RemoteAddr: addrs[0]}})
+ return s
+ }(),
+ targ: pp.PexMsg{
+ Added6: krpc.CompactIPv6NodeAddrs{
+ mustNodeAddr(addrs[0]),
},
+ Added6Flags: []pp.PexPeerFlags{0},
+ },
+ update: func(s *pexState) {
+ s.Add(&PeerConn{Peer: Peer{RemoteAddr: addrs[1]}})
},
- targM: &pp.PexMsg{
+ targ1: pp.PexMsg{
Added6: krpc.CompactIPv6NodeAddrs{
- nodeAddr(addrs[1]),
+ mustNodeAddr(addrs[1]),
},
- Added6Flags: []pp.PexPeerFlags{f},
+ Added6Flags: []pp.PexPeerFlags{0},
},
- targS: 2,
},
}
-func TestPexGenmsg(t *testing.T) {
+// Represents the contents of a PexMsg in a way that supports equivalence checking in tests. This is
+// necessary because pexMsgFactory uses maps and so ordering of the resultant PexMsg isn't
+// deterministic. Because the flags are in a different array, we can't just use testify's
+// ElementsMatch because the ordering *does* still matter between an added addr and its flags.
+type comparablePexMsg struct {
+ added, added6 []krpc.NodeAddr
+ addedFlags, added6Flags []pp.PexPeerFlags
+ dropped, dropped6 []krpc.NodeAddr
+}
+
+// Such Rust-inspired.
+func (me *comparablePexMsg) From(f pp.PexMsg) {
+ me.added = f.Added
+ me.addedFlags = f.AddedFlags
+ me.added6 = f.Added6
+ me.added6Flags = f.Added6Flags
+ me.dropped = f.Dropped
+ me.dropped6 = f.Dropped6
+}
+
+// For PexMsg created by pexMsgFactory, this is as good as it can get without using data structures
+// in pexMsgFactory that preserve insert ordering.
+func (actual comparablePexMsg) AssertEqual(t *testing.T, expected comparablePexMsg) {
+ assert.ElementsMatch(t, expected.added, actual.added)
+ assert.ElementsMatch(t, expected.addedFlags, actual.addedFlags)
+ assert.ElementsMatch(t, expected.added6, actual.added6)
+ assert.ElementsMatch(t, expected.added6Flags, actual.added6Flags)
+ assert.ElementsMatch(t, expected.dropped, actual.dropped)
+ assert.ElementsMatch(t, expected.dropped6, actual.dropped6)
+}
+
+func assertPexMsgsEqual(t *testing.T, expected, actual pp.PexMsg) {
+ var ec, ac comparablePexMsg
+ ec.From(expected)
+ ac.From(actual)
+ ac.AssertEqual(t, ec)
+}
+
+func TestPexGenmsg0(t *testing.T) {
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
- s := tc.in
- m, seen := s.Genmsg(tc.arg)
- require.EqualValues(t, tc.targM, m)
- require.EqualValues(t, tc.targS, seen)
+ s := *tc.in
+ m, last := s.Genmsg(nil)
+ assertPexMsgsEqual(t, tc.targ, m)
+ if tc.update != nil {
+ tc.update(&s)
+ m1, last := s.Genmsg(last)
+ assertPexMsgsEqual(t, tc.targ1, m1)
+ assert.NotNil(t, last)
+ }
})
}
}
+
+// generate 𝑛 distinct values of net.Addr
+func addrgen(n int) chan net.Addr {
+ c := make(chan net.Addr)
+ go func() {
+ defer close(c)
+ for i := 4747; i < 65535 && n > 0; i++ {
+ c <- &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: i}
+ n--
+ }
+ }()
+ return c
+}
+
+func TestPexInitialNoCutoff(t *testing.T) {
+ const n = 2 * pexMaxDelta
+ var s pexState
+
+ c := addrgen(n)
+ for addr := range c {
+ s.Add(&PeerConn{Peer: Peer{RemoteAddr: addr}})
+ }
+ m, _ := s.Genmsg(nil)
+
+ require.EqualValues(t, n, len(m.Added))
+ require.EqualValues(t, n, len(m.AddedFlags))
+ require.EqualValues(t, 0, len(m.Added6))
+ require.EqualValues(t, 0, len(m.Added6Flags))
+ require.EqualValues(t, 0, len(m.Dropped))
+ require.EqualValues(t, 0, len(m.Dropped6))
+}
+
+func benchmarkPexInitialN(b *testing.B, npeers int) {
+ for i := 0; i < b.N; i++ {
+ var s pexState
+ c := addrgen(npeers)
+ for addr := range c {
+ s.Add(&PeerConn{Peer: Peer{RemoteAddr: addr}})
+ s.Genmsg(nil)
+ }
+ }
+}
+
+// obtain at least 5 points, e.g. to plot a graph
+func BenchmarkPexInitial4(b *testing.B) { benchmarkPexInitialN(b, 4) }
+func BenchmarkPexInitial50(b *testing.B) { benchmarkPexInitialN(b, 50) }
+func BenchmarkPexInitial100(b *testing.B) { benchmarkPexInitialN(b, 100) }
+func BenchmarkPexInitial200(b *testing.B) { benchmarkPexInitialN(b, 200) }
+func BenchmarkPexInitial400(b *testing.B) { benchmarkPexInitialN(b, 400) }