ceremonyclient/go-libp2p-kad-dht/dht_test.go
2024-10-12 11:48:25 -07:00

2479 lines
63 KiB
Go

package dht
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"math/rand"
"runtime"
"sort"
"strings"
"sync"
"testing"
"time"
"github.com/libp2p/go-libp2p-kad-dht/internal/net"
"github.com/libp2p/go-libp2p-kad-dht/providers"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/event"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-libp2p/core/routing"
"github.com/libp2p/go-msgio"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
"github.com/multiformats/go-multihash"
"github.com/multiformats/go-multistream"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
test "github.com/libp2p/go-libp2p-kad-dht/internal/testing"
pb "github.com/libp2p/go-libp2p-kad-dht/pb"
u "github.com/ipfs/boxo/util"
"github.com/ipfs/go-cid"
detectrace "github.com/ipfs/go-detect-race"
kb "github.com/libp2p/go-libp2p-kbucket"
record "github.com/libp2p/go-libp2p-record"
bhost "github.com/libp2p/go-libp2p/p2p/host/basic"
swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
)
var testCaseCids []cid.Cid
func init() {
for i := 0; i < 100; i++ {
v := fmt.Sprintf("%d -- value", i)
var newCid cid.Cid
switch i % 3 {
case 0:
mhv := u.Hash([]byte(v))
newCid = cid.NewCidV0(mhv)
case 1:
mhv := u.Hash([]byte(v))
newCid = cid.NewCidV1(cid.DagCBOR, mhv)
case 2:
rawMh := make([]byte, 12)
binary.PutUvarint(rawMh, cid.Raw)
binary.PutUvarint(rawMh[1:], 10)
copy(rawMh[2:], []byte(v)[:10])
_, mhv, err := multihash.MHFromBytes(rawMh)
if err != nil {
panic(err)
}
newCid = cid.NewCidV1(cid.Raw, mhv)
}
testCaseCids = append(testCaseCids, newCid)
}
}
type blankValidator struct{}
func (blankValidator) Validate(_ string, _ []byte) error { return nil }
func (blankValidator) Select(_ string, _ [][]byte) (int, error) { return 0, nil }
type testAtomicPutValidator struct {
test.TestValidator
}
// selects the entry with the 'highest' last byte
func (testAtomicPutValidator) Select(_ string, bs [][]byte) (int, error) {
index := -1
max := uint8(0)
for i, b := range bs {
if bytes.Equal(b, []byte("valid")) {
if index == -1 {
index = i
}
continue
}
str := string(b)
n := str[len(str)-1]
if n > max {
max = n
index = i
}
}
if index == -1 {
return -1, errors.New("no rec found")
}
return index, nil
}
var testPrefix = ProtocolPrefix("/test")
func setupDHT(ctx context.Context, t *testing.T, client bool, options ...Option) *IpfsDHT {
baseOpts := []Option{
testPrefix,
NamespacedValidator("v", blankValidator{}),
DisableAutoRefresh(),
}
if client {
baseOpts = append(baseOpts, Mode(ModeClient))
} else {
baseOpts = append(baseOpts, Mode(ModeServer))
}
host, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
host.Start()
t.Cleanup(func() { host.Close() })
d, err := New(ctx, host, append(baseOpts, options...)...)
require.NoError(t, err)
t.Cleanup(func() { d.Close() })
return d
}
func setupDHTS(t *testing.T, ctx context.Context, n int, options ...Option) []*IpfsDHT {
addrs := make([]ma.Multiaddr, n)
dhts := make([]*IpfsDHT, n)
peers := make([]peer.ID, n)
sanityAddrsMap := make(map[string]struct{})
sanityPeersMap := make(map[string]struct{})
for i := 0; i < n; i++ {
dhts[i] = setupDHT(ctx, t, false, options...)
peers[i] = dhts[i].PeerID()
addrs[i] = dhts[i].host.Addrs()[0]
if _, lol := sanityAddrsMap[addrs[i].String()]; lol {
t.Fatal("While setting up DHTs address got duplicated.")
} else {
sanityAddrsMap[addrs[i].String()] = struct{}{}
}
if _, lol := sanityPeersMap[peers[i].String()]; lol {
t.Fatal("While setting up DHTs peerid got duplicated.")
} else {
sanityPeersMap[peers[i].String()] = struct{}{}
}
}
return dhts
}
func connectNoSync(t *testing.T, ctx context.Context, a, b *IpfsDHT) {
t.Helper()
idB := b.self
addrB := b.peerstore.Addrs(idB)
if len(addrB) == 0 {
t.Fatal("peers setup incorrectly: no local address")
}
if err := a.host.Connect(ctx, peer.AddrInfo{ID: idB, Addrs: addrB}); err != nil {
t.Fatal(err)
}
}
func wait(t *testing.T, ctx context.Context, a, b *IpfsDHT) {
t.Helper()
// loop until connection notification has been received.
// under high load, this may not happen as immediately as we would like.
for a.routingTable.Find(b.self) == "" {
select {
case <-ctx.Done():
t.Fatal(ctx.Err())
case <-time.After(time.Millisecond * 5):
}
}
}
func connect(t *testing.T, ctx context.Context, a, b *IpfsDHT) {
t.Helper()
connectNoSync(t, ctx, a, b)
wait(t, ctx, a, b)
wait(t, ctx, b, a)
}
func bootstrap(t *testing.T, ctx context.Context, dhts []*IpfsDHT) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
logger.Debugf("refreshing DHTs routing tables...")
// tried async. sequential fares much better. compare:
// 100 async https://gist.github.com/jbenet/56d12f0578d5f34810b2
// 100 sync https://gist.github.com/jbenet/6c59e7c15426e48aaedd
// probably because results compound
start := rand.Intn(len(dhts)) // randomize to decrease bias.
for i := range dhts {
dht := dhts[(start+i)%len(dhts)]
select {
case err := <-dht.RefreshRoutingTable():
if err != nil {
t.Error(err)
}
case <-ctx.Done():
return
}
}
}
// Check to make sure we always signal the RefreshRoutingTable channel.
func TestRefreshMultiple(t *testing.T) {
// TODO: What's with this test? How long should it take and why does RefreshRoutingTable not take a context?
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second)
defer cancel()
dhts := setupDHTS(t, ctx, 5)
defer func() {
for _, dht := range dhts {
dht.Close()
defer dht.host.Close()
}
}()
for _, dht := range dhts[1:] {
connect(t, ctx, dhts[0], dht)
}
a := dhts[0].RefreshRoutingTable()
time.Sleep(time.Nanosecond)
b := dhts[0].RefreshRoutingTable()
time.Sleep(time.Nanosecond)
c := dhts[0].RefreshRoutingTable()
// make sure that all of these eventually return
select {
case <-a:
case <-ctx.Done():
t.Fatal("first channel didn't signal")
}
select {
case <-b:
case <-ctx.Done():
t.Fatal("second channel didn't signal")
}
select {
case <-c:
case <-ctx.Done():
t.Fatal("third channel didn't signal")
}
}
func TestValueGetSet(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var dhts [5]*IpfsDHT
for i := range dhts {
dhts[i] = setupDHT(ctx, t, false)
defer dhts[i].Close()
defer dhts[i].host.Close()
}
connect(t, ctx, dhts[0], dhts[1])
t.Log("adding value on: ", dhts[0].self)
ctxT, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
err := dhts[0].PutValue(ctxT, "/v/hello", []byte("world"))
if err != nil {
t.Fatal(err)
}
t.Log("requesting value on dhts: ", dhts[1].self)
ctxT, cancel = context.WithTimeout(ctx, time.Second*2*60)
defer cancel()
val, err := dhts[1].GetValue(ctxT, "/v/hello")
if err != nil {
t.Fatal(err)
}
if string(val) != "world" {
t.Fatalf("Expected 'world' got '%s'", string(val))
}
connect(t, ctx, dhts[2], dhts[0])
connect(t, ctx, dhts[2], dhts[1])
t.Log("requesting value (offline) on dhts: ", dhts[2].self)
vala, err := dhts[2].GetValue(ctxT, "/v/hello", Quorum(0))
if err != nil {
t.Fatal(err)
}
if string(vala) != "world" {
t.Fatalf("Expected 'world' got '%s'", string(vala))
}
t.Log("requesting value (online) on dhts: ", dhts[2].self)
val, err = dhts[2].GetValue(ctxT, "/v/hello")
if err != nil {
t.Fatal(err)
}
if string(val) != "world" {
t.Fatalf("Expected 'world' got '%s'", string(val))
}
for _, d := range dhts[:3] {
connect(t, ctx, dhts[3], d)
}
connect(t, ctx, dhts[4], dhts[3])
t.Log("requesting value (requires peer routing) on dhts: ", dhts[4].self)
val, err = dhts[4].GetValue(ctxT, "/v/hello")
if err != nil {
t.Fatal(err)
}
if string(val) != "world" {
t.Fatalf("Expected 'world' got '%s'", string(val))
}
}
func TestValueSetInvalid(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dhtA := setupDHT(ctx, t, false)
dhtB := setupDHT(ctx, t, false)
defer dhtA.Close()
defer dhtB.Close()
defer dhtA.host.Close()
defer dhtB.host.Close()
dhtA.Validator.(record.NamespacedValidator)["v"] = test.TestValidator{}
dhtB.Validator.(record.NamespacedValidator)["v"] = blankValidator{}
connect(t, ctx, dhtA, dhtB)
testSetGet := func(val string, failset bool, exp string, experr error) {
t.Helper()
ctxT, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
err := dhtA.PutValue(ctxT, "/v/hello", []byte(val))
if failset {
if err == nil {
t.Error("expected set to fail")
}
} else {
if err != nil {
t.Error(err)
}
}
ctxT, cancel = context.WithTimeout(ctx, time.Second*2)
defer cancel()
valb, err := dhtB.GetValue(ctxT, "/v/hello")
if err != experr {
t.Errorf("Set/Get %v: Expected %v error but got %v", val, experr, err)
} else if err == nil && string(valb) != exp {
t.Errorf("Expected '%v' got '%s'", exp, string(valb))
}
}
// Expired records should not be set
testSetGet("expired", true, "", routing.ErrNotFound)
// Valid record should be returned
testSetGet("valid", false, "valid", nil)
// Newer record should supersede previous record
testSetGet("newer", false, "newer", nil)
// Attempt to set older record again should be ignored
testSetGet("valid", true, "newer", nil)
}
func TestContextShutDown(t *testing.T) {
t.Skip("This test is flaky, see https://github.com/libp2p/go-libp2p-kad-dht/issues/724.")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dht := setupDHT(ctx, t, false)
// context is alive
select {
case <-dht.Context().Done():
t.Fatal("context should not be done")
default:
}
// shut down dht
require.NoError(t, dht.Close())
// now context should be done
select {
case <-dht.Context().Done():
default:
t.Fatal("context should be done")
}
}
func TestSearchValue(t *testing.T) {
t.Skip("This test is flaky, see https://github.com/libp2p/go-libp2p-kad-dht/issues/723.")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dhtA := setupDHT(ctx, t, false)
dhtB := setupDHT(ctx, t, false)
defer dhtA.Close()
defer dhtB.Close()
defer dhtA.host.Close()
defer dhtB.host.Close()
connect(t, ctx, dhtA, dhtB)
dhtA.Validator.(record.NamespacedValidator)["v"] = test.TestValidator{}
dhtB.Validator.(record.NamespacedValidator)["v"] = test.TestValidator{}
ctxT, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
err := dhtA.PutValue(ctxT, "/v/hello", []byte("valid"))
if err != nil {
t.Error(err)
}
ctxT, cancel = context.WithTimeout(ctx, time.Second*2)
defer cancel()
valCh, err := dhtA.SearchValue(ctxT, "/v/hello", Quorum(0))
if err != nil {
t.Fatal(err)
}
select {
case v := <-valCh:
if string(v) != "valid" {
t.Errorf("expected 'valid', got '%s'", string(v))
}
case <-ctxT.Done():
t.Fatal(ctxT.Err())
}
err = dhtB.PutValue(ctxT, "/v/hello", []byte("newer"))
if err != nil {
t.Error(err)
}
select {
case v := <-valCh:
if string(v) != "newer" {
t.Errorf("expected 'newer', got '%s'", string(v))
}
case <-ctxT.Done():
t.Fatal(ctxT.Err())
}
}
func TestValueGetInvalid(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dhtA := setupDHT(ctx, t, false)
dhtB := setupDHT(ctx, t, false)
defer dhtA.Close()
defer dhtB.Close()
defer dhtA.host.Close()
defer dhtB.host.Close()
dhtA.Validator.(record.NamespacedValidator)["v"] = blankValidator{}
dhtB.Validator.(record.NamespacedValidator)["v"] = test.TestValidator{}
connect(t, ctx, dhtA, dhtB)
testSetGet := func(val string, exp string, experr error) {
t.Helper()
ctxT, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
err := dhtA.PutValue(ctxT, "/v/hello", []byte(val))
if err != nil {
t.Error(err)
}
ctxT, cancel = context.WithTimeout(ctx, time.Second*2)
defer cancel()
valb, err := dhtB.GetValue(ctxT, "/v/hello")
if err != experr {
t.Errorf("Set/Get %v: Expected '%v' error but got '%v'", val, experr, err)
} else if err == nil && string(valb) != exp {
t.Errorf("Expected '%v' got '%s'", exp, string(valb))
}
}
// Expired records should not be returned
testSetGet("expired", "", routing.ErrNotFound)
// Valid record should be returned
testSetGet("valid", "valid", nil)
// Newer record should supersede previous record
testSetGet("newer", "newer", nil)
// Attempt to set older record again should be ignored
testSetGet("valid", "newer", nil)
}
func TestProvides(t *testing.T) {
// t.Skip("skipping test to debug another")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dhts := setupDHTS(t, ctx, 4)
defer func() {
for i := 0; i < 4; i++ {
dhts[i].Close()
defer dhts[i].host.Close()
}
}()
connect(t, ctx, dhts[0], dhts[1])
connect(t, ctx, dhts[1], dhts[2])
connect(t, ctx, dhts[1], dhts[3])
for _, k := range testCaseCids {
logger.Debugf("announcing provider for %s", k)
if err := dhts[3].Provide(ctx, k, true); err != nil {
t.Fatal(err)
}
}
// what is this timeout for? was 60ms before.
time.Sleep(time.Millisecond * 6)
n := 0
for _, c := range testCaseCids {
n = (n + 1) % 3
logger.Debugf("getting providers for %s from %d", c, n)
ctxT, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
provchan := dhts[n].FindProvidersAsync(ctxT, c, 1)
select {
case prov := <-provchan:
if prov.ID == "" {
t.Fatal("Got back nil provider")
}
if prov.ID != dhts[3].self {
t.Fatal("Got back wrong provider")
}
if len(prov.Addrs) == 0 {
t.Fatal("Got no addresses back")
}
case <-ctxT.Done():
t.Fatal("Did not get a provider back.")
}
}
}
type testMessageSender struct {
sendRequest func(ctx context.Context, p peer.ID, pmes *pb.Message) (*pb.Message, error)
sendMessage func(ctx context.Context, p peer.ID, pmes *pb.Message) error
}
var _ pb.MessageSender = (*testMessageSender)(nil)
func (t testMessageSender) SendRequest(ctx context.Context, p peer.ID, pmes *pb.Message) (*pb.Message, error) {
return t.sendRequest(ctx, p, pmes)
}
func (t testMessageSender) SendMessage(ctx context.Context, p peer.ID, pmes *pb.Message) error {
return t.sendMessage(ctx, p, pmes)
}
func TestProvideAddressFilter(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dhts := setupDHTS(t, ctx, 2)
connect(t, ctx, dhts[0], dhts[1])
testMaddr, _ := ma.StringCast("/ip4/99.99.99.99/tcp/9999")
done := make(chan struct{})
impl := net.NewMessageSenderImpl(dhts[0].host, dhts[0].protocols)
tms := &testMessageSender{
sendMessage: func(ctx context.Context, p peer.ID, pmes *pb.Message) error {
defer close(done)
assert.Equal(t, pmes.Type, pb.Message_ADD_PROVIDER)
assert.Len(t, pmes.ProviderPeers[0].Addrs, 1)
assert.True(t, pmes.ProviderPeers[0].Addresses()[0].Equal(testMaddr))
return impl.SendMessage(ctx, p, pmes)
},
sendRequest: func(ctx context.Context, p peer.ID, pmes *pb.Message) (*pb.Message, error) {
return impl.SendRequest(ctx, p, pmes)
},
}
pm, err := pb.NewProtocolMessenger(tms)
require.NoError(t, err)
dhts[0].protoMessenger = pm
dhts[0].addrFilter = func(multiaddrs []ma.Multiaddr) []ma.Multiaddr {
return []ma.Multiaddr{testMaddr}
}
if err := dhts[0].Provide(ctx, testCaseCids[0], true); err != nil {
t.Fatal(err)
}
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatal("timeout")
}
}
type testProviderManager struct {
addProvider func(ctx context.Context, key []byte, prov peer.AddrInfo) error
getProviders func(ctx context.Context, key []byte) ([]peer.AddrInfo, error)
close func() error
}
var _ providers.ProviderStore = (*testProviderManager)(nil)
func (t *testProviderManager) AddProvider(ctx context.Context, key []byte, prov peer.AddrInfo) error {
return t.addProvider(ctx, key, prov)
}
func (t *testProviderManager) GetProviders(ctx context.Context, key []byte) ([]peer.AddrInfo, error) {
return t.getProviders(ctx, key)
}
func (t *testProviderManager) Close() error {
return t.close()
}
func TestHandleAddProviderAddressFilter(t *testing.T) {
ctx := context.Background()
d := setupDHT(ctx, t, false)
provider := setupDHT(ctx, t, false)
testMaddr, _ := ma.StringCast("/ip4/99.99.99.99/tcp/9999")
d.addrFilter = func(multiaddrs []ma.Multiaddr) []ma.Multiaddr {
return []ma.Multiaddr{testMaddr}
}
done := make(chan struct{})
d.providerStore = &testProviderManager{
addProvider: func(ctx context.Context, key []byte, prov peer.AddrInfo) error {
defer close(done)
assert.True(t, prov.Addrs[0].Equal(testMaddr))
return nil
},
close: func() error { return nil },
}
m1, _ := ma.StringCast("/ip4/55.55.55.55/tcp/5555")
m2, _ := ma.StringCast("/ip4/66.66.66.66/tcp/6666")
pmes := &pb.Message{
Type: pb.Message_ADD_PROVIDER,
Key: []byte("test-key"),
ProviderPeers: pb.RawPeerInfosToPBPeers([]peer.AddrInfo{{
ID: provider.self,
Addrs: []ma.Multiaddr{
m1,
m2,
},
}}),
}
_, err := d.handleAddProvider(ctx, provider.self, pmes)
require.NoError(t, err)
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatal("timeout")
}
}
func TestLocalProvides(t *testing.T) {
// t.Skip("skipping test to debug another")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dhts := setupDHTS(t, ctx, 4)
defer func() {
for i := 0; i < 4; i++ {
dhts[i].Close()
defer dhts[i].host.Close()
}
}()
connect(t, ctx, dhts[0], dhts[1])
connect(t, ctx, dhts[1], dhts[2])
connect(t, ctx, dhts[1], dhts[3])
for _, k := range testCaseCids {
logger.Debugf("announcing provider for %s", k)
if err := dhts[3].Provide(ctx, k, false); err != nil {
t.Fatal(err)
}
}
time.Sleep(time.Millisecond * 10)
for _, c := range testCaseCids {
for i := 0; i < 3; i++ {
provs, _ := dhts[i].ProviderStore().GetProviders(ctx, c.Hash())
if len(provs) > 0 {
t.Fatal("shouldnt know this")
}
}
}
}
func TestAddressFilterProvide(t *testing.T) {
// t.Skip("skipping test to debug another")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
testMaddr, _ := ma.StringCast("/ip4/99.99.99.99/tcp/9999")
d := setupDHT(ctx, t, false)
provider := setupDHT(ctx, t, false)
d.addrFilter = func(maddrs []ma.Multiaddr) []ma.Multiaddr {
return []ma.Multiaddr{
testMaddr,
}
}
_, err := d.handleAddProvider(ctx, provider.self, &pb.Message{
Type: pb.Message_ADD_PROVIDER,
Key: []byte("random-key"),
ProviderPeers: pb.PeerInfosToPBPeers(provider.host.Network(), []peer.AddrInfo{{
ID: provider.self,
Addrs: provider.host.Addrs(),
}}),
})
require.NoError(t, err)
// because of the identify protocol we add all
// addresses to the peerstore, although the addresses
// will be filtered in the above handleAddProvider call
d.peerstore.AddAddrs(provider.self, provider.host.Addrs(), time.Hour)
resp, err := d.handleGetProviders(ctx, d.self, &pb.Message{
Type: pb.Message_GET_PROVIDERS,
Key: []byte("random-key"),
})
require.NoError(t, err)
assert.True(t, resp.ProviderPeers[0].Addresses()[0].Equal(testMaddr))
assert.Len(t, resp.ProviderPeers[0].Addresses(), 1)
}
// if minPeers or avgPeers is 0, dont test for it.
func waitForWellFormedTables(t *testing.T, dhts []*IpfsDHT, minPeers, avgPeers int, timeout time.Duration) {
// test "well-formed-ness" (>= minPeers peers in every routing table)
t.Helper()
timeoutA := time.After(timeout)
for {
select {
case <-timeoutA:
t.Errorf("failed to reach well-formed routing tables after %s", timeout)
return
case <-time.After(5 * time.Millisecond):
if checkForWellFormedTablesOnce(t, dhts, minPeers, avgPeers) {
// succeeded
return
}
}
}
}
func checkForWellFormedTablesOnce(t *testing.T, dhts []*IpfsDHT, minPeers, avgPeers int) bool {
t.Helper()
totalPeers := 0
for _, dht := range dhts {
rtlen := dht.routingTable.Size()
totalPeers += rtlen
if minPeers > 0 && rtlen < minPeers {
// t.Logf("routing table for %s only has %d peers (should have >%d)", dht.self, rtlen, minPeers)
return false
}
}
actualAvgPeers := totalPeers / len(dhts)
t.Logf("avg rt size: %d", actualAvgPeers)
if avgPeers > 0 && actualAvgPeers < avgPeers {
t.Logf("avg rt size: %d < %d", actualAvgPeers, avgPeers)
return false
}
return true
}
func printRoutingTables(dhts []*IpfsDHT) {
// the routing tables should be full now. let's inspect them.
fmt.Printf("checking routing table of %d\n", len(dhts))
for _, dht := range dhts {
fmt.Printf("checking routing table of %s\n", dht.self)
dht.routingTable.Print()
fmt.Println("")
}
}
func TestRefresh(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
nDHTs := 30
dhts := setupDHTS(t, ctx, nDHTs)
defer func() {
for i := 0; i < nDHTs; i++ {
dhts[i].Close()
defer dhts[i].host.Close()
}
}()
t.Logf("connecting %d dhts in a ring", nDHTs)
for i := 0; i < nDHTs; i++ {
connect(t, ctx, dhts[i], dhts[(i+1)%len(dhts)])
}
<-time.After(100 * time.Millisecond)
// bootstrap a few times until we get good tables.
t.Logf("bootstrapping them so they find each other %d", nDHTs)
for {
bootstrap(t, ctx, dhts)
if checkForWellFormedTablesOnce(t, dhts, 7, 10) {
break
}
time.Sleep(time.Microsecond * 50)
}
if u.Debug {
// the routing tables should be full now. let's inspect them.
printRoutingTables(dhts)
}
}
func TestRefreshBelowMinRTThreshold(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
host, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
host.Start()
// enable auto bootstrap on A
dhtA, err := New(
ctx,
host,
testPrefix,
Mode(ModeServer),
NamespacedValidator("v", blankValidator{}),
)
if err != nil {
t.Fatal(err)
}
dhtB := setupDHT(ctx, t, false)
dhtC := setupDHT(ctx, t, false)
defer func() {
dhtA.Close()
dhtA.host.Close()
dhtB.Close()
dhtB.host.Close()
dhtC.Close()
dhtC.host.Close()
}()
connect(t, ctx, dhtA, dhtB)
connect(t, ctx, dhtB, dhtC)
// we ONLY init bootstrap on A
dhtA.RefreshRoutingTable()
// and wait for one round to complete i.e. A should be connected to both B & C
waitForWellFormedTables(t, []*IpfsDHT{dhtA}, 2, 2, 20*time.Second)
// now we create two new peers
dhtD := setupDHT(ctx, t, false)
dhtE := setupDHT(ctx, t, false)
// connect them to each other
connect(t, ctx, dhtD, dhtE)
defer func() {
dhtD.Close()
dhtD.host.Close()
dhtE.Close()
dhtE.host.Close()
}()
// and then, on connecting the peer D to A, the min RT threshold gets triggered on A which leads to a bootstrap.
// since the default bootstrap scan interval is 30 mins - 1 hour, we can be sure that if bootstrap happens,
// it is because of the min RT threshold getting triggered (since default min value is 4 & we only have 2 peers in the RT when D gets connected)
connect(t, ctx, dhtA, dhtD)
// and because of the above bootstrap, A also discovers E !
waitForWellFormedTables(t, []*IpfsDHT{dhtA}, 4, 4, 10*time.Second)
time.Sleep(100 * time.Millisecond)
assert.Equal(t, dhtE.self, dhtA.routingTable.Find(dhtE.self), "A's routing table should have peer E!")
}
func TestQueryWithEmptyRTShouldNotPanic(t *testing.T) {
ctx := context.Background()
d := setupDHT(ctx, t, false)
// TODO This swallows the error for now, should we change it ?
// FindProviders
ps, _ := d.FindProviders(ctx, testCaseCids[0])
require.Empty(t, ps)
// GetClosestPeers
pc, err := d.GetClosestPeers(ctx, "key")
require.Nil(t, pc)
require.Equal(t, kb.ErrLookupFailure, err)
// GetValue
best, err := d.GetValue(ctx, "key")
require.Empty(t, best)
require.Error(t, err)
// SearchValue
bchan, err := d.SearchValue(ctx, "key")
require.Empty(t, bchan)
require.NoError(t, err)
// Provide
err = d.Provide(ctx, testCaseCids[0], true)
require.Equal(t, kb.ErrLookupFailure, err)
}
func TestPeriodicRefresh(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
if runtime.GOOS == "windows" {
t.Skip("skipping due to #760")
}
if detectrace.WithRace() {
t.Skip("skipping due to race detector max goroutines")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
nDHTs := 30
dhts := setupDHTS(t, ctx, nDHTs)
defer func() {
for i := 0; i < nDHTs; i++ {
dhts[i].Close()
defer dhts[i].host.Close()
}
}()
t.Logf("dhts are not connected. %d", nDHTs)
for _, dht := range dhts {
rtlen := dht.routingTable.Size()
if rtlen > 0 {
t.Errorf("routing table for %s should have 0 peers. has %d", dht.self, rtlen)
}
}
for i := 0; i < nDHTs; i++ {
connect(t, ctx, dhts[i], dhts[(i+1)%len(dhts)])
}
t.Logf("DHTs are now connected to 1-2 others. %d", nDHTs)
for _, dht := range dhts {
rtlen := dht.routingTable.Size()
if rtlen > 2 {
t.Errorf("routing table for %s should have at most 2 peers. has %d", dht.self, rtlen)
}
}
if u.Debug {
printRoutingTables(dhts)
}
t.Logf("bootstrapping them so they find each other. %d", nDHTs)
var wg sync.WaitGroup
for _, dht := range dhts {
wg.Add(1)
go func(d *IpfsDHT) {
<-d.RefreshRoutingTable()
wg.Done()
}(dht)
}
wg.Wait()
// this is async, and we dont know when it's finished with one cycle, so keep checking
// until the routing tables look better, or some long timeout for the failure case.
waitForWellFormedTables(t, dhts, 7, 10, 20*time.Second)
if u.Debug {
printRoutingTables(dhts)
}
}
func TestProvidesMany(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skipping due to #760")
}
if detectrace.WithRace() {
t.Skip("skipping due to race detector max goroutines")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
nDHTs := 40
dhts := setupDHTS(t, ctx, nDHTs)
defer func() {
for i := 0; i < nDHTs; i++ {
dhts[i].Close()
defer dhts[i].host.Close()
}
}()
t.Logf("connecting %d dhts in a ring", nDHTs)
for i := 0; i < nDHTs; i++ {
connect(t, ctx, dhts[i], dhts[(i+1)%len(dhts)])
}
<-time.After(100 * time.Millisecond)
t.Logf("bootstrapping them so they find each other. %d", nDHTs)
ctxT, cancel := context.WithTimeout(ctx, 20*time.Second)
defer cancel()
bootstrap(t, ctxT, dhts)
if u.Debug {
// the routing tables should be full now. let's inspect them.
t.Logf("checking routing table of %d", nDHTs)
for _, dht := range dhts {
fmt.Printf("checking routing table of %s\n", dht.self)
dht.routingTable.Print()
fmt.Println("")
}
}
providers := make(map[cid.Cid]peer.ID)
d := 0
for _, c := range testCaseCids {
d = (d + 1) % len(dhts)
dht := dhts[d]
providers[c] = dht.self
t.Logf("announcing provider for %s", c)
if err := dht.Provide(ctx, c, true); err != nil {
t.Fatal(err)
}
}
// what is this timeout for? was 60ms before.
time.Sleep(time.Millisecond * 6)
errchan := make(chan error)
ctxT, cancel = context.WithTimeout(ctx, 5*time.Second)
defer cancel()
var wg sync.WaitGroup
getProvider := func(dht *IpfsDHT, k cid.Cid) {
defer wg.Done()
expected := providers[k]
provchan := dht.FindProvidersAsync(ctxT, k, 1)
select {
case prov := <-provchan:
actual := prov.ID
if actual == "" {
errchan <- fmt.Errorf("Got back nil provider (%s at %s)", k, dht.self)
} else if actual != expected {
errchan <- fmt.Errorf("Got back wrong provider (%s != %s) (%s at %s)",
expected, actual, k, dht.self)
}
case <-ctxT.Done():
errchan <- fmt.Errorf("Did not get a provider back (%s at %s)", k, dht.self)
}
}
for _, c := range testCaseCids {
// everyone should be able to find it...
for _, dht := range dhts {
logger.Debugf("getting providers for %s at %s", c, dht.self)
wg.Add(1)
go getProvider(dht, c)
}
}
// we need this because of printing errors
go func() {
wg.Wait()
close(errchan)
}()
for err := range errchan {
t.Error(err)
}
}
func TestProvidesAsync(t *testing.T) {
// t.Skip("skipping test to debug another")
if testing.Short() {
t.SkipNow()
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dhts := setupDHTS(t, ctx, 4)
defer func() {
for i := 0; i < 4; i++ {
dhts[i].Close()
defer dhts[i].host.Close()
}
}()
connect(t, ctx, dhts[0], dhts[1])
connect(t, ctx, dhts[1], dhts[2])
connect(t, ctx, dhts[1], dhts[3])
err := dhts[3].Provide(ctx, testCaseCids[0], true)
if err != nil {
t.Fatal(err)
}
time.Sleep(time.Millisecond * 60)
ctxT, cancel := context.WithTimeout(ctx, time.Millisecond*300)
defer cancel()
provs := dhts[0].FindProvidersAsync(ctxT, testCaseCids[0], 5)
select {
case p, ok := <-provs:
if !ok {
t.Fatal("Provider channel was closed...")
}
if p.ID == "" {
t.Fatal("Got back nil provider!")
}
if p.ID != dhts[3].self {
t.Fatalf("got a provider, but not the right one. %s", p)
}
case <-ctxT.Done():
t.Fatal("Didnt get back providers")
}
}
func TestLayeredGet(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dhts := setupDHTS(t, ctx, 4)
defer func() {
for i := 0; i < 4; i++ {
dhts[i].Close()
defer dhts[i].host.Close()
}
}()
connect(t, ctx, dhts[0], dhts[1])
connect(t, ctx, dhts[1], dhts[2])
connect(t, ctx, dhts[2], dhts[3])
err := dhts[3].PutValue(ctx, "/v/hello", []byte("world"))
if err != nil {
t.Fatal(err)
}
time.Sleep(time.Millisecond * 6)
ctxT, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
val, err := dhts[0].GetValue(ctxT, "/v/hello")
if err != nil {
t.Fatal(err)
}
if string(val) != "world" {
t.Error("got wrong value")
}
}
func TestUnfindablePeer(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dhts := setupDHTS(t, ctx, 4)
defer func() {
for i := 0; i < 4; i++ {
dhts[i].Close()
dhts[i].Host().Close()
}
}()
connect(t, ctx, dhts[0], dhts[1])
connect(t, ctx, dhts[1], dhts[2])
connect(t, ctx, dhts[2], dhts[3])
// Give DHT 1 a bad addr for DHT 2.
dhts[1].host.Peerstore().ClearAddrs(dhts[2].PeerID())
dhts[1].host.Peerstore().AddAddr(dhts[2].PeerID(), dhts[0].Host().Addrs()[0], time.Minute)
ctxT, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
_, err := dhts[0].FindPeer(ctxT, dhts[3].PeerID())
if err == nil {
t.Error("should have failed to find peer")
}
if ctxT.Err() != nil {
t.Error("FindPeer should have failed before context expired")
}
}
func TestFindPeer(t *testing.T) {
// t.Skip("skipping test to debug another")
if testing.Short() {
t.SkipNow()
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dhts := setupDHTS(t, ctx, 4)
defer func() {
for i := 0; i < 4; i++ {
dhts[i].Close()
dhts[i].host.Close()
}
}()
connect(t, ctx, dhts[0], dhts[1])
connect(t, ctx, dhts[1], dhts[2])
connect(t, ctx, dhts[1], dhts[3])
ctxT, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
p, err := dhts[0].FindPeer(ctxT, dhts[2].PeerID())
if err != nil {
t.Fatal(err)
}
if p.ID == "" {
t.Fatal("Failed to find peer.")
}
if p.ID != dhts[2].PeerID() {
t.Fatal("Didnt find expected peer.")
}
}
func TestFindPeerWithQueryFilter(t *testing.T) {
// t.Skip("skipping test to debug another")
if testing.Short() {
t.SkipNow()
}
if runtime.GOOS == "windows" {
t.Skip("skipping due to #760")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
filteredPeer, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
filteredPeer.Start()
defer filteredPeer.Close()
dhts := setupDHTS(t, ctx, 4, QueryFilter(func(_ interface{}, ai peer.AddrInfo) bool {
return ai.ID != filteredPeer.ID()
}))
defer func() {
for i := 0; i < 4; i++ {
dhts[i].Close()
dhts[i].host.Close()
}
}()
connect(t, ctx, dhts[0], dhts[1])
connect(t, ctx, dhts[1], dhts[2])
connect(t, ctx, dhts[1], dhts[3])
err = filteredPeer.Connect(ctx, peer.AddrInfo{
ID: dhts[2].host.ID(),
Addrs: dhts[2].host.Addrs(),
})
require.NoError(t, err)
require.Eventually(t, func() bool {
return len(dhts[2].host.Network().ConnsToPeer(filteredPeer.ID())) > 0
}, 5*time.Millisecond, time.Millisecond, "failed to connect to peer")
ctxT, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
p, err := dhts[0].FindPeer(ctxT, filteredPeer.ID())
require.NoError(t, err)
require.NotEmpty(t, p.ID, "Failed to find peer.")
require.Equal(t, filteredPeer.ID(), p.ID, "Didnt find expected peer.")
}
func TestConnectCollision(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
runTimes := 10
for rtime := 0; rtime < runTimes; rtime++ {
logger.Info("Running Time: ", rtime)
ctx, cancel := context.WithCancel(context.Background())
dhtA := setupDHT(ctx, t, false)
dhtB := setupDHT(ctx, t, false)
addrA := dhtA.peerstore.Addrs(dhtA.self)[0]
addrB := dhtB.peerstore.Addrs(dhtB.self)[0]
peerA := dhtA.self
peerB := dhtB.self
errs := make(chan error)
go func() {
dhtA.peerstore.AddAddr(peerB, addrB, peerstore.TempAddrTTL)
pi := peer.AddrInfo{ID: peerB}
err := dhtA.host.Connect(ctx, pi)
errs <- err
}()
go func() {
dhtB.peerstore.AddAddr(peerA, addrA, peerstore.TempAddrTTL)
pi := peer.AddrInfo{ID: peerA}
err := dhtB.host.Connect(ctx, pi)
errs <- err
}()
timeout := time.After(5 * time.Second)
select {
case e := <-errs:
if e != nil {
t.Fatal(e)
}
case <-timeout:
t.Fatal("Timeout received!")
}
select {
case e := <-errs:
if e != nil {
t.Fatal(e)
}
case <-timeout:
t.Fatal("Timeout received!")
}
dhtA.Close()
dhtB.Close()
dhtA.host.Close()
dhtB.host.Close()
cancel()
}
}
func TestBadProtoMessages(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
d := setupDHT(ctx, t, false)
nilrec := new(pb.Message)
if _, err := d.handlePutValue(ctx, "testpeer", nilrec); err == nil {
t.Fatal("should have errored on nil record")
}
}
func TestAtomicPut(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
d := setupDHT(ctx, t, false)
d.Validator = testAtomicPutValidator{}
// fnc to put a record
key := "testkey"
putRecord := func(value []byte) error {
rec := record.MakePutRecord(key, value)
pmes := pb.NewMessage(pb.Message_PUT_VALUE, rec.Key, 0)
pmes.Record = rec
_, err := d.handlePutValue(ctx, "testpeer", pmes)
return err
}
// put a valid record
if err := putRecord([]byte("valid")); err != nil {
t.Fatal("should not have errored on a valid record")
}
// simultaneous puts for old & new values
values := [][]byte{[]byte("newer1"), []byte("newer7"), []byte("newer3"), []byte("newer5")}
var wg sync.WaitGroup
for _, v := range values {
wg.Add(1)
go func(v []byte) {
defer wg.Done()
_ = putRecord(v) // we expect some of these to fail
}(v)
}
wg.Wait()
// get should return the newest value
pmes := pb.NewMessage(pb.Message_GET_VALUE, []byte(key), 0)
msg, err := d.handleGetValue(ctx, "testkey", pmes)
if err != nil {
t.Fatalf("should not have errored on final get, but got %+v", err)
}
if string(msg.GetRecord().Value) != "newer7" {
t.Fatalf("Expected 'newer7' got '%s'", string(msg.GetRecord().Value))
}
}
func TestClientModeConnect(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
a := setupDHT(ctx, t, false)
b := setupDHT(ctx, t, true)
connectNoSync(t, ctx, a, b)
c := testCaseCids[0]
p := peer.ID("TestPeer")
a.ProviderStore().AddProvider(ctx, c.Hash(), peer.AddrInfo{ID: p})
time.Sleep(time.Millisecond * 5) // just in case...
provs, err := b.FindProviders(ctx, c)
if err != nil {
t.Fatal(err)
}
if len(provs) == 0 {
t.Fatal("Expected to get a provider back")
}
if provs[0].ID != p {
t.Fatal("expected it to be our test peer")
}
if a.routingTable.Find(b.self) != "" {
t.Fatal("DHT clients should not be added to routing tables")
}
if b.routingTable.Find(a.self) == "" {
t.Fatal("DHT server should have been added to the dht client's routing table")
}
}
func TestInvalidServer(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
s0 := setupDHT(ctx, t, false, BucketSize(2)) // server
s1 := setupDHT(ctx, t, false, BucketSize(2)) // server
m0 := setupDHT(ctx, t, false, BucketSize(2)) // misbehabing server
m1 := setupDHT(ctx, t, false, BucketSize(2)) // misbehabing server
// make m0 and m1 advertise all dht server protocols, but hang on all requests
for _, proto := range s0.serverProtocols {
for _, m := range []*IpfsDHT{m0, m1} {
// Hang on every request.
m.host.SetStreamHandler(proto, func(s network.Stream) {
r := msgio.NewVarintReaderSize(s, network.MessageSizeMax)
msgbytes, err := r.ReadMsg()
if err != nil {
t.Fatal(err)
}
var req pb.Message
err = req.Unmarshal(msgbytes)
if err != nil {
t.Fatal(err)
}
// answer with an empty response message
resp := pb.NewMessage(req.GetType(), nil, req.GetClusterLevel())
// send out response msg
err = net.WriteMsg(s, resp)
if err != nil {
t.Fatal(err)
}
})
}
}
// connect s0 and m0
connectNoSync(t, ctx, s0, m0)
// add a provider (p) for a key (k) to s0
k := testCaseCids[0]
p := peer.ID("TestPeer")
s0.ProviderStore().AddProvider(ctx, k.Hash(), peer.AddrInfo{ID: p})
time.Sleep(time.Millisecond * 5) // just in case...
// find the provider for k from m0
provs, err := m0.FindProviders(ctx, k)
if err != nil {
t.Fatal(err)
}
if len(provs) == 0 {
t.Fatal("Expected to get a provider back")
}
if provs[0].ID != p {
t.Fatal("expected it to be our test peer")
}
// verify that m0 and s0 contain each other in their routing tables
if s0.routingTable.Find(m0.self) == "" {
// m0 is added to s0 routing table even though it is misbehaving, because
// s0's routing table is not well populated, so s0 isn't picky about who it adds.
t.Fatal("Misbehaving DHT servers should be added to routing table if not well populated")
}
if m0.routingTable.Find(s0.self) == "" {
t.Fatal("DHT server should have been added to the misbehaving server routing table")
}
// connect s0 to both s1 and m1
connectNoSync(t, ctx, s0, s1)
connectNoSync(t, ctx, s0, m1)
// s1 should be added to s0's routing table. Then, because s0's routing table
// contains more than bucketSize (2) entries, lookupCheck is enabled and m1
// shouldn't be added, because it fails the lookupCheck (hang on all requests).
if s0.routingTable.Find(s1.self) == "" {
t.Fatal("Well behaving DHT server should have been added to the server routing table")
}
if s0.routingTable.Find(m1.self) != "" {
t.Fatal("Misbehaving DHT servers should not be added to routing table if well populated")
}
}
func TestClientModeFindPeer(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
a := setupDHT(ctx, t, false)
b := setupDHT(ctx, t, true)
c := setupDHT(ctx, t, true)
connectNoSync(t, ctx, b, a)
connectNoSync(t, ctx, c, a)
// Can't use `connect` because b and c are only clients.
wait(t, ctx, b, a)
wait(t, ctx, c, a)
pi, err := c.FindPeer(ctx, b.self)
if err != nil {
t.Fatal(err)
}
if len(pi.Addrs) == 0 {
t.Fatal("should have found addresses for node b")
}
err = c.host.Connect(ctx, pi)
if err != nil {
t.Fatal(err)
}
}
func minInt(a, b int) int {
if a < b {
return a
}
return b
}
func TestFindPeerQueryMinimal(t *testing.T) {
testFindPeerQuery(t, 2, 22, 1)
}
func TestFindPeerQuery(t *testing.T) {
if detectrace.WithRace() {
t.Skip("skipping due to race detector max goroutines")
}
if testing.Short() {
t.Skip("skipping test in short mode")
}
testFindPeerQuery(t, 5, 40, 3)
}
// NOTE: You must have ATLEAST (minRTRefreshThreshold+1) test peers before using this.
func testFindPeerQuery(t *testing.T,
bootstrappers, // Number of nodes connected to the querying node
leafs, // Number of nodes that might be connected to from the bootstrappers
bootstrapConns int, // Number of bootstrappers each leaf should connect to.
) {
if runtime.GOOS == "windows" {
t.Skip("skipping due to #760")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dhts := setupDHTS(t, ctx, 1+bootstrappers+leafs, BucketSize(4))
defer func() {
for _, d := range dhts {
d.Close()
d.host.Close()
}
}()
t.Log("connecting")
mrand := rand.New(rand.NewSource(42))
guy := dhts[0]
others := dhts[1:]
for i := 0; i < leafs; i++ {
for _, v := range mrand.Perm(bootstrappers)[:bootstrapConns] {
connectNoSync(t, ctx, others[v], others[bootstrappers+i])
}
}
for i := 0; i < bootstrappers; i++ {
connectNoSync(t, ctx, guy, others[i])
}
t.Log("waiting for routing tables")
// give some time for things to settle down
waitForWellFormedTables(t, dhts, bootstrapConns, bootstrapConns, 5*time.Second)
t.Log("refreshing")
var wg sync.WaitGroup
for _, dht := range dhts {
wg.Add(1)
go func(d *IpfsDHT) {
<-d.RefreshRoutingTable()
wg.Done()
}(dht)
}
wg.Wait()
t.Log("waiting for routing tables again")
// Wait for refresh to work. At least one bucket should be full.
waitForWellFormedTables(t, dhts, 4, 0, 5*time.Second)
var peers []peer.ID
for _, d := range others {
peers = append(peers, d.PeerID())
}
t.Log("querying")
val := "foobar"
rtval := kb.ConvertKey(val)
outpeers, err := guy.GetClosestPeers(ctx, val)
require.NoError(t, err)
sort.Sort(peer.IDSlice(outpeers))
exp := kb.SortClosestPeers(peers, rtval)[:minInt(guy.bucketSize, len(peers))]
t.Logf("got %d peers", len(outpeers))
got := kb.SortClosestPeers(outpeers, rtval)
assert.EqualValues(t, exp, got)
}
func TestFindClosestPeers(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
nDHTs := 30
dhts := setupDHTS(t, ctx, nDHTs)
defer func() {
for i := 0; i < nDHTs; i++ {
dhts[i].Close()
defer dhts[i].host.Close()
}
}()
t.Logf("connecting %d dhts in a ring", nDHTs)
for i := 0; i < nDHTs; i++ {
connect(t, ctx, dhts[i], dhts[(i+1)%len(dhts)])
}
querier := dhts[1]
peers, err := querier.GetClosestPeers(ctx, "foo")
if err != nil {
t.Fatal(err)
}
if len(peers) < querier.beta {
t.Fatalf("got wrong number of peers (got %d, expected at least %d)", len(peers), querier.beta)
}
}
func TestFixLowPeers(t *testing.T) {
ctx := context.Background()
dhts := setupDHTS(t, ctx, minRTRefreshThreshold+5)
defer func() {
for _, d := range dhts {
d.Close()
d.Host().Close()
}
}()
mainD := dhts[0]
// connect it to everyone else
for _, d := range dhts[1:] {
mainD.peerstore.AddAddrs(d.self, d.peerstore.Addrs(d.self), peerstore.TempAddrTTL)
require.NoError(t, mainD.Host().Connect(ctx, peer.AddrInfo{ID: d.self}))
}
waitForWellFormedTables(t, []*IpfsDHT{mainD}, minRTRefreshThreshold, minRTRefreshThreshold+4, 5*time.Second)
// run a refresh on all of them
for _, d := range dhts {
err := <-d.RefreshRoutingTable()
require.NoError(t, err)
}
// now remove peers from RT so threshold gets hit
for _, d := range dhts[3:] {
mainD.routingTable.RemovePeer(d.self)
}
// but we will still get enough peers in the RT because of fix low Peers
waitForWellFormedTables(t, []*IpfsDHT{mainD}, minRTRefreshThreshold, minRTRefreshThreshold, 5*time.Second)
}
func TestProvideDisabled(t *testing.T) {
k := testCaseCids[0]
kHash := k.Hash()
for i := 0; i < 3; i++ {
enabledA := (i & 0x1) > 0
enabledB := (i & 0x2) > 0
t.Run(fmt.Sprintf("a=%v/b=%v", enabledA, enabledB), func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var optsA, optsB []Option
optsA = append(optsA, ProtocolPrefix("/provMaybeDisabled"))
optsB = append(optsB, ProtocolPrefix("/provMaybeDisabled"))
if !enabledA {
optsA = append(optsA, DisableProviders())
}
if !enabledB {
optsB = append(optsB, DisableProviders())
}
dhtA := setupDHT(ctx, t, false, optsA...)
dhtB := setupDHT(ctx, t, false, optsB...)
defer dhtA.Close()
defer dhtB.Close()
defer dhtA.host.Close()
defer dhtB.host.Close()
connect(t, ctx, dhtA, dhtB)
err := dhtB.Provide(ctx, k, true)
if enabledB {
if err != nil {
t.Fatal("put should have succeeded on node B", err)
}
} else {
if err != routing.ErrNotSupported {
t.Fatal("should not have put the value to node B", err)
}
_, err = dhtB.FindProviders(ctx, k)
if err != routing.ErrNotSupported {
t.Fatal("get should have failed on node B")
}
provs, _ := dhtB.ProviderStore().GetProviders(ctx, kHash)
if len(provs) != 0 {
t.Fatal("node B should not have found local providers")
}
}
provs, err := dhtA.FindProviders(ctx, k)
if enabledA {
if len(provs) != 0 {
t.Fatal("node A should not have found providers")
}
} else {
if err != routing.ErrNotSupported {
t.Fatal("node A should not have found providers")
}
}
provAddrs, _ := dhtA.ProviderStore().GetProviders(ctx, kHash)
if len(provAddrs) != 0 {
t.Fatal("node A should not have found local providers")
}
})
}
}
func TestHandleRemotePeerProtocolChanges(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
os := []Option{
testPrefix,
Mode(ModeServer),
NamespacedValidator("v", blankValidator{}),
DisableAutoRefresh(),
}
// start host 1 that speaks dht v1
hA, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
hA.Start()
defer hA.Close()
dhtA, err := New(ctx, hA, os...)
require.NoError(t, err)
defer dhtA.Close()
// start host 2 that also speaks dht v1
hB, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
hB.Start()
defer hB.Close()
dhtB, err := New(ctx, hB, os...)
require.NoError(t, err)
defer dhtB.Close()
connect(t, ctx, dhtA, dhtB)
// now assert both have each other in their RT
waitForWellFormedTables(t, []*IpfsDHT{dhtA, dhtB}, 1, 1, 10*time.Second)
// dhtB becomes a client
require.NoError(t, dhtB.setMode(modeClient))
// which means that dhtA should evict it from it's RT
waitForWellFormedTables(t, []*IpfsDHT{dhtA}, 0, 0, 10*time.Second)
// dhtB becomes a server
require.NoError(t, dhtB.setMode(modeServer))
// which means dhtA should have it in the RT again because of fixLowPeers
waitForWellFormedTables(t, []*IpfsDHT{dhtA}, 1, 1, 10*time.Second)
}
func TestGetSetPluggedProtocol(t *testing.T) {
t.Run("PutValue/GetValue - same protocol", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
os := []Option{
ProtocolPrefix("/esh"),
Mode(ModeServer),
NamespacedValidator("v", blankValidator{}),
DisableAutoRefresh(),
}
hA, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
hA.Start()
defer hA.Close()
dhtA, err := New(ctx, hA, os...)
require.NoError(t, err)
defer dhtA.Close()
hB, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
hB.Start()
defer hB.Close()
dhtB, err := New(ctx, hB, os...)
require.NoError(t, err)
defer dhtB.Close()
connect(t, ctx, dhtA, dhtB)
ctxT, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
err = dhtA.PutValue(ctxT, "/v/cat", []byte("meow"))
require.NoError(t, err)
value, err := dhtB.GetValue(ctxT, "/v/cat")
require.NoError(t, err)
require.Equal(t, "meow", string(value))
})
t.Run("DHT routing table for peer A won't contain B if A and B don't use same protocol", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
hA, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
hA.Start()
defer hA.Close()
dhtA, err := New(ctx, hA, []Option{
ProtocolPrefix("/esh"),
Mode(ModeServer),
NamespacedValidator("v", blankValidator{}),
DisableAutoRefresh(),
}...)
require.NoError(t, err)
defer dhtA.Close()
hB, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
hB.Start()
defer hB.Close()
dhtB, err := New(ctx, hB, []Option{
ProtocolPrefix("/lsr"),
Mode(ModeServer),
NamespacedValidator("v", blankValidator{}),
DisableAutoRefresh(),
}...)
require.NoError(t, err)
defer dhtB.Close()
connectNoSync(t, ctx, dhtA, dhtB)
// We don't expect connection notifications for A to reach B (or vice-versa), given
// that they've been configured with different protocols - but we'll give them a
// chance, anyhow.
time.Sleep(time.Second * 2)
err = dhtA.PutValue(ctx, "/v/cat", []byte("meow"))
if err == nil || !strings.Contains(err.Error(), "failed to find any peer in table") {
t.Fatalf("put should not have been able to find any peers in routing table, err:'%v'", err)
}
v, err := dhtB.GetValue(ctx, "/v/cat")
if v != nil || err != routing.ErrNotFound {
t.Fatalf("get should have failed from not being able to find the value, err: '%v'", err)
}
})
}
func TestPing(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ds := setupDHTS(t, ctx, 2)
ds[0].Host().Peerstore().AddAddrs(ds[1].PeerID(), ds[1].Host().Addrs(), peerstore.AddressTTL)
assert.NoError(t, ds[0].Ping(context.Background(), ds[1].PeerID()))
}
func TestClientModeAtInit(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
pinger := setupDHT(ctx, t, false)
client := setupDHT(ctx, t, true)
pinger.Host().Peerstore().AddAddrs(client.PeerID(), client.Host().Addrs(), peerstore.AddressTTL)
err := pinger.Ping(context.Background(), client.PeerID())
assert.True(t, errors.Is(err, multistream.ErrNotSupported[protocol.ID]{}))
}
func TestModeChange(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
clientOnly := setupDHT(ctx, t, true)
clientToServer := setupDHT(ctx, t, true)
clientOnly.Host().Peerstore().AddAddrs(clientToServer.PeerID(), clientToServer.Host().Addrs(), peerstore.AddressTTL)
err := clientOnly.Ping(ctx, clientToServer.PeerID())
assert.True(t, errors.Is(err, multistream.ErrNotSupported[protocol.ID]{}))
err = clientToServer.setMode(modeServer)
assert.Nil(t, err)
err = clientOnly.Ping(ctx, clientToServer.PeerID())
assert.Nil(t, err)
err = clientToServer.setMode(modeClient)
assert.Nil(t, err)
err = clientOnly.Ping(ctx, clientToServer.PeerID())
assert.NotNil(t, err)
}
func TestDynamicModeSwitching(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
prober := setupDHT(ctx, t, true) // our test harness
node := setupDHT(ctx, t, true, Mode(ModeAuto)) // the node under test
prober.Host().Peerstore().AddAddrs(node.PeerID(), node.Host().Addrs(), peerstore.AddressTTL)
if _, err := prober.Host().Network().DialPeer(ctx, node.PeerID()); err != nil {
t.Fatal(err)
}
emitter, err := node.host.EventBus().Emitter(new(event.EvtLocalReachabilityChanged))
if err != nil {
t.Fatal(err)
}
assertDHTClient := func() {
err = prober.Ping(ctx, node.PeerID())
assert.True(t, errors.Is(err, multistream.ErrNotSupported[protocol.ID]{}))
if l := len(prober.RoutingTable().ListPeers()); l != 0 {
t.Errorf("expected routing table length to be 0; instead is %d", l)
}
}
assertDHTServer := func() {
err = prober.Ping(ctx, node.PeerID())
assert.Nil(t, err)
// the node should be in the RT for the prober
// because the prober will call fixLowPeers when the node updates it's protocols
if l := len(prober.RoutingTable().ListPeers()); l != 1 {
t.Errorf("expected routing table length to be 1; instead is %d", l)
}
}
err = emitter.Emit(event.EvtLocalReachabilityChanged{Reachability: network.ReachabilityPrivate})
if err != nil {
t.Fatal(err)
}
time.Sleep(500 * time.Millisecond)
assertDHTClient()
err = emitter.Emit(event.EvtLocalReachabilityChanged{Reachability: network.ReachabilityPublic})
if err != nil {
t.Fatal(err)
}
time.Sleep(500 * time.Millisecond)
assertDHTServer()
err = emitter.Emit(event.EvtLocalReachabilityChanged{Reachability: network.ReachabilityUnknown})
if err != nil {
t.Fatal(err)
}
time.Sleep(500 * time.Millisecond)
assertDHTClient()
}
func TestInvalidKeys(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
nDHTs := 2
dhts := setupDHTS(t, ctx, nDHTs)
defer func() {
for i := 0; i < nDHTs; i++ {
dhts[i].Close()
defer dhts[i].host.Close()
}
}()
t.Logf("connecting %d dhts in a ring", nDHTs)
for i := 0; i < nDHTs; i++ {
connect(t, ctx, dhts[i], dhts[(i+1)%len(dhts)])
}
querier := dhts[0]
_, err := querier.GetClosestPeers(ctx, "")
if err == nil {
t.Fatal("get closest peers should have failed")
}
_, err = querier.FindProviders(ctx, cid.Cid{})
switch err {
case routing.ErrNotFound, routing.ErrNotSupported, kb.ErrLookupFailure:
t.Fatal("failed with the wrong error: ", err)
case nil:
t.Fatal("find providers should have failed")
}
_, err = querier.FindPeer(ctx, peer.ID(""))
if err != peer.ErrEmptyPeerID {
t.Fatal("expected to fail due to the empty peer ID")
}
_, err = querier.GetValue(ctx, "")
if err == nil {
t.Fatal("expected to have failed")
}
err = querier.PutValue(ctx, "", []byte("foobar"))
if err == nil {
t.Fatal("expected to have failed")
}
}
func TestV1ProtocolOverride(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
d1 := setupDHT(ctx, t, false, V1ProtocolOverride("/myproto"))
d2 := setupDHT(ctx, t, false, V1ProtocolOverride("/myproto"))
d3 := setupDHT(ctx, t, false, V1ProtocolOverride("/myproto2"))
d4 := setupDHT(ctx, t, false)
dhts := []*IpfsDHT{d1, d2, d3, d4}
for i, dout := range dhts {
for _, din := range dhts[i+1:] {
connectNoSync(t, ctx, dout, din)
}
}
wait(t, ctx, d1, d2)
wait(t, ctx, d2, d1)
time.Sleep(time.Second)
if d1.RoutingTable().Size() != 1 || d2.routingTable.Size() != 1 {
t.Fatal("should have one peer in the routing table")
}
if d3.RoutingTable().Size() > 0 || d4.RoutingTable().Size() > 0 {
t.Fatal("should have an empty routing table")
}
}
func TestRoutingFilter(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
nDHTs := 2
dhts := setupDHTS(t, ctx, nDHTs)
defer func() {
for i := 0; i < nDHTs; i++ {
dhts[i].Close()
defer dhts[i].host.Close()
}
}()
dhts[0].routingTablePeerFilter = PublicRoutingTableFilter
connectNoSync(t, ctx, dhts[0], dhts[1])
wait(t, ctx, dhts[1], dhts[0])
select {
case <-ctx.Done():
t.Fatal(ctx.Err())
case <-time.After(time.Millisecond * 200):
}
}
func TestBootStrapWhenRTIsEmpty(t *testing.T) {
if detectrace.WithRace() {
t.Skip("skipping timing dependent test when race detector is running")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// create three boostrap peers each of which is connected to 1 other peer.
nBootStraps := 3
bootstrappers := setupDHTS(t, ctx, nBootStraps)
defer func() {
for i := 0; i < nBootStraps; i++ {
bootstrappers[i].Close()
defer bootstrappers[i].host.Close()
}
}()
bootstrapcons := setupDHTS(t, ctx, nBootStraps)
defer func() {
for i := 0; i < nBootStraps; i++ {
bootstrapcons[i].Close()
defer bootstrapcons[i].host.Close()
}
}()
for i := 0; i < nBootStraps; i++ {
connect(t, ctx, bootstrappers[i], bootstrapcons[i])
}
// convert the bootstrap addresses to a p2p address
bootstrapAddrs := make([]peer.AddrInfo, nBootStraps)
for i := 0; i < nBootStraps; i++ {
b := peer.AddrInfo{
ID: bootstrappers[i].self,
Addrs: bootstrappers[i].host.Addrs(),
}
bootstrapAddrs[i] = b
}
{
// ----------------
// We will initialize a DHT with 1 bootstrapper, connect it to another DHT,
// then remove the latter from the Routing Table
// This should add the bootstrap peer and the peer that the bootstrap peer is conencted to
// to it's Routing Table.
// AutoRefresh needs to be enabled for this.
h1, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
h1.Start()
dht1, err := New(
ctx,
h1,
testPrefix,
NamespacedValidator("v", blankValidator{}),
Mode(ModeServer),
BootstrapPeers(bootstrapAddrs[0]),
)
require.NoError(t, err)
dht2 := setupDHT(ctx, t, false)
defer func() {
dht1.host.Close()
dht2.host.Close()
dht1.Close()
dht2.Close()
}()
connect(t, ctx, dht1, dht2)
require.NoError(t, dht2.Close())
require.NoError(t, dht2.host.Close())
require.NoError(t, dht1.host.Network().ClosePeer(dht2.self))
dht1.routingTable.RemovePeer(dht2.self)
require.NotContains(t, dht2.self, dht1.routingTable.ListPeers())
require.Eventually(t, func() bool {
return dht1.routingTable.Size() == 2 && dht1.routingTable.Find(bootstrappers[0].self) != "" &&
dht1.routingTable.Find(bootstrapcons[0].self) != ""
}, 5*time.Second, 500*time.Millisecond)
}
{
// ----------------
// We will initialize a DHT with 2 bootstrappers, connect it to another DHT,
// then remove the DHT handler from the other DHT which should make the first DHT's
// routing table empty.
// This should add the bootstrap peers and the peer thats the bootstrap peers are connected to
// to it's Routing Table.
// AutoRefresh needs to be enabled for this.
h1, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
h1.Start()
dht1, err := New(
ctx,
h1,
testPrefix,
NamespacedValidator("v", blankValidator{}),
Mode(ModeServer),
BootstrapPeers(bootstrapAddrs[1], bootstrapAddrs[2]),
)
require.NoError(t, err)
dht2 := setupDHT(ctx, t, false)
connect(t, ctx, dht1, dht2)
defer func() {
dht1.host.Close()
dht2.host.Close()
dht1.Close()
dht2.Close()
}()
connect(t, ctx, dht1, dht2)
require.NoError(t, dht2.setMode(modeClient))
require.Eventually(t, func() bool {
rt := dht1.routingTable
return rt.Size() == 4 && rt.Find(bootstrappers[1].self) != "" &&
rt.Find(bootstrappers[2].self) != "" && rt.Find(bootstrapcons[1].self) != "" && rt.Find(bootstrapcons[2].self) != ""
}, 5*time.Second, 500*time.Millisecond)
}
}
func TestBootstrapPeersFunc(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var lock sync.Mutex
bootstrapFuncA := func() []peer.AddrInfo {
return []peer.AddrInfo{}
}
dhtA := setupDHT(ctx, t, false, BootstrapPeersFunc(bootstrapFuncA))
bootstrapPeersB := []peer.AddrInfo{}
bootstrapFuncB := func() []peer.AddrInfo {
lock.Lock()
defer lock.Unlock()
return bootstrapPeersB
}
dhtB := setupDHT(ctx, t, false, BootstrapPeersFunc(bootstrapFuncB))
require.Equal(t, 0, len(dhtB.host.Network().Peers()))
addrA := peer.AddrInfo{
ID: dhtA.self,
Addrs: dhtA.host.Addrs(),
}
lock.Lock()
bootstrapPeersB = []peer.AddrInfo{addrA}
lock.Unlock()
dhtB.fixLowPeers()
require.NotEqual(t, 0, len(dhtB.host.Network().Peers()))
}
func TestPreconnectedNodes(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
opts := []Option{
testPrefix,
DisableAutoRefresh(),
Mode(ModeServer),
}
// Create hosts
h1, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
h1.Start()
defer h1.Close()
h2, err := bhost.NewHost(swarmt.GenSwarm(t, swarmt.OptDisableReuseport), new(bhost.HostOpts))
require.NoError(t, err)
h2.Start()
defer h2.Close()
// Setup first DHT
d1, err := New(ctx, h1, opts...)
require.NoError(t, err)
defer d1.Close()
// Connect the first host to the second
err = h1.Connect(ctx, peer.AddrInfo{ID: h2.ID(), Addrs: h2.Addrs()})
require.NoError(t, err)
// Wait until we know identify has completed by checking for supported protocols
// TODO: Is this needed? Could we do h2.Connect(h1) and that would wait for identify to complete.
require.Eventually(t, func() bool {
h1Protos, err := h2.Peerstore().SupportsProtocols(h1.ID(), d1.protocols...)
require.NoError(t, err)
return len(h1Protos) > 0
}, 10*time.Second, time.Millisecond)
// Setup the second DHT
d2, err := New(ctx, h2, opts...)
require.NoError(t, err)
defer h2.Close()
connect(t, ctx, d1, d2)
// See if it works
peers, err := d2.GetClosestPeers(ctx, "testkey")
require.NoError(t, err)
require.Equal(t, len(peers), 1, "why is there more than one peer?")
require.Equal(t, h1.ID(), peers[0], "could not find peer")
}
func TestAddrFilter(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
pub1, _ := ma.StringCast("/ip4/1.2.3.1/tcp/123")
pub2, _ := ma.StringCast("/ip4/160.160.160.160/tcp/1600")
pub3, _ := ma.StringCast("/ip6/2001::10/tcp/123")
priv1, _ := ma.StringCast("/ip4/192.168.1.100/tcp/123")
priv2, _ := ma.StringCast("/ip4/172.16.10.10/tcp/123")
priv3, _ := ma.StringCast("/ip4/10.10.10.10/tcp/123")
priv4, _ := ma.StringCast("/ip6/fc00::10/tcp/123")
l1, _ := ma.StringCast("/ip4/127.0.0.100/tcp/123")
l2, _ := ma.StringCast("/ip6/::1/tcp/123")
// generate a bunch of addresses
publicAddrs := []ma.Multiaddr{
pub1,
pub2,
pub3,
}
privAddrs := []ma.Multiaddr{
priv1,
priv2,
priv3,
priv4,
}
loopbackAddrs := []ma.Multiaddr{
l1,
l2,
}
allAddrs := append(publicAddrs, privAddrs...)
allAddrs = append(allAddrs, loopbackAddrs...)
// generate different address filters
acceptAllFilter := AddressFilter(func(addrs []ma.Multiaddr) []ma.Multiaddr {
return addrs
})
rejectAllFilter := AddressFilter(func(addrs []ma.Multiaddr) []ma.Multiaddr {
return []ma.Multiaddr{}
})
publicIpFilter := AddressFilter(func(addrs []ma.Multiaddr) []ma.Multiaddr {
return ma.FilterAddrs(addrs, func(a ma.Multiaddr) bool { is, err := manet.IsPublicAddr(a); return is && err == nil })
})
localIpFilter := AddressFilter(func(addrs []ma.Multiaddr) []ma.Multiaddr {
return ma.FilterAddrs(addrs, func(a ma.Multiaddr) bool { return !manet.IsIPLoopback(a) })
})
// generate peerid for "remote" peer
_, pub, err := crypto.GenerateKeyPair(
crypto.Ed25519, // Select your key type. Ed25519 are nice short
-1, // Select key length when possible (i.e. RSA).
)
require.NoError(t, err)
peerid, err := peer.IDFromPublicKey(pub)
require.NoError(t, err)
// DHT accepting all addresses
d0 := setupDHT(ctx, t, false, acceptAllFilter)
// peerstore should only contain self
require.Equal(t, 1, d0.host.Peerstore().Peers().Len())
d0.maybeAddAddrs(peerid, allAddrs, time.Minute)
require.Equal(t, 2, d0.host.Peerstore().Peers().Len())
for _, a := range allAddrs {
// check that the peerstore contains all addresses of the remote peer
require.Contains(t, d0.host.Peerstore().Addrs(peerid), a)
}
// DHT rejecting all addresses
d1 := setupDHT(ctx, t, false, rejectAllFilter)
d1.maybeAddAddrs(peerid, allAddrs, time.Minute)
// remote peer should not be added to peerstore (all addresses rejected)
require.Equal(t, 1, d1.host.Peerstore().Peers().Len())
// DHT accepting only public addresses
d2 := setupDHT(ctx, t, false, publicIpFilter)
d2.maybeAddAddrs(peerid, allAddrs, time.Minute)
for _, a := range publicAddrs {
// check that the peerstore contains only public addresses of the remote peer
require.Contains(t, d2.host.Peerstore().Addrs(peerid), a)
}
require.Equal(t, len(publicAddrs), len(d2.host.Peerstore().Addrs(peerid)))
// DHT accepting only non-loopback addresses
d3 := setupDHT(ctx, t, false, localIpFilter)
d3.maybeAddAddrs(peerid, allAddrs, time.Minute)
for _, a := range publicAddrs {
// check that the peerstore contains only non-loopback addresses of the remote peer
require.Contains(t, d3.host.Peerstore().Addrs(peerid), a)
}
for _, a := range privAddrs {
// check that the peerstore contains only non-loopback addresses of the remote peer
require.Contains(t, d3.host.Peerstore().Addrs(peerid), a)
}
require.Equal(t, len(publicAddrs)+len(privAddrs), len(d3.host.Peerstore().Addrs(peerid)))
}