github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/p2p/profile_service_test.go (about) 1 package p2p 2 3 import ( 4 "context" 5 "fmt" 6 "sort" 7 "sync" 8 "testing" 9 "time" 10 11 peer "github.com/libp2p/go-libp2p-core/peer" 12 "github.com/qri-io/qri/event" 13 p2ptest "github.com/qri-io/qri/p2p/test" 14 "github.com/qri-io/qri/profile" 15 ) 16 17 // Hello fellow devs. Let my pain be your comfort and plz use my notes here 18 // as a guide on how to not pull out your hair when testing the p2p connections 19 // things to note: 20 // - once a libp2p connection has been established, the QriProfileService will 21 // check if that remote peers speaks qri. If it does, it will protect that 22 // connection, add it to a list of current qri connections, and ask that peer 23 // for it's qri profile. It will then emit a `event.ETP2PQriPeerConnected` event 24 // - if a libp2p connection has been broken, aka disconnected, the QriProfile- 25 // Service will remove that connection from the current qri connections and 26 // emit a `event.ETP2PQriPeerDisconnected` event 27 // - we can use these events to coordinate on 28 // - QriNodes have a Discovery process that the peers use to find each other 29 // in the "wild". This is why you may see connections to peers that you haven't 30 // explicitly connected to. When this is happening locally, it may happen so 31 // quickly that the connections can collide causing errors. Unless you are 32 // specifically testing that the peers can find themselves on their own, it 33 // may be best to disable Discovery by calling `node.Discovery.Close()` 34 // - when coordinating on events, ensure that you are using a mutex lock to 35 // prevent any race conditions 36 // - use channels to wait for all connections or disconnections to occur. a 37 // closed channel never blocks! 38 39 func TestQriProfileService(t *testing.T) { 40 ctx := context.Background() 41 numNodes := 5 // number of nodes we want (besides the main test node) in this test 42 // create a network of connected nodes 43 factory := p2ptest.NewTestNodeFactory(NewTestableQriNode) 44 testPeers, err := p2ptest.NewTestNetwork(ctx, factory, numNodes) 45 if err != nil { 46 t.Fatalf("error creating network: %s", err.Error()) 47 } 48 49 // Convert from test nodes to non-test nodes. 50 nodes := make([]*QriNode, len(testPeers)) 51 for i, node := range testPeers { 52 nodes[i] = node.(*QriNode) 53 // closing the discovery process prevents situations where another peer finds 54 // our node in the wild and attempts to connect to it while we are trying 55 // to connect to that node at the same time. This causes a dial failure. 56 nodes[i].Discovery.Close() 57 } 58 59 expectedPeers := peer.IDSlice{} 60 for _, node := range nodes { 61 expectedPeers = append(expectedPeers, node.host.ID()) 62 } 63 sort.Sort(peer.IDSlice(expectedPeers)) 64 t.Logf("expected nodes: %v", expectedPeers) 65 66 // set up bus & listener for `ETP2PQriPeerConnected` events 67 // this is how we will be sure that all the nodes have exchanged qri profiles 68 // before moving forward 69 bus := event.NewBus(ctx) 70 qriPeerConnWaitCh := make(chan struct{}) 71 connectedPeersMu := sync.Mutex{} 72 connectedPeers := peer.IDSlice{} 73 74 disconnectedPeers := peer.IDSlice{} 75 disconnectedPeersMu := sync.Mutex{} 76 disconnectsCh := make(chan struct{}) 77 78 node := &QriNode{} 79 80 unexpectedPeers := peer.IDSlice{} 81 82 watchP2PQriEvents := func(_ context.Context, e event.Event) error { 83 pro, ok := e.Payload.(*profile.Profile) 84 if !ok { 85 t.Error("payload for event.ETP2PQriPeerConnected not a *profile.Profile as expected") 86 return fmt.Errorf("payload for event.ETP2PQriPeerConnected not a *profile.Profile as expected") 87 } 88 if pro == nil { 89 t.Error("error: event payload is a nil profile") 90 return fmt.Errorf("err: event payload is a nil profile") 91 } 92 pid := pro.PeerIDs[0] 93 expectedPeer := false 94 for _, id := range expectedPeers { 95 if pid == id { 96 expectedPeer = true 97 break 98 } 99 } 100 if !expectedPeer { 101 t.Logf("peer %q event occurred, but not an expected peer", pid) 102 unexpectedPeers = append(unexpectedPeers, pid) 103 return nil 104 } 105 if e.Type == event.ETP2PQriPeerConnected { 106 connectedPeersMu.Lock() 107 defer connectedPeersMu.Unlock() 108 t.Log("Qri Peer Connected: ", pid) 109 for _, id := range connectedPeers { 110 if pid == id { 111 t.Logf("peer %q has already been connected", pid) 112 return nil 113 } 114 } 115 connectedPeers = append(connectedPeers, pid) 116 if len(connectedPeers) == numNodes { 117 close(qriPeerConnWaitCh) 118 } 119 } 120 if e.Type == event.ETP2PQriPeerDisconnected { 121 disconnectedPeersMu.Lock() 122 defer disconnectedPeersMu.Unlock() 123 t.Log("Qri Peer Disconnected: ", pid) 124 for _, id := range disconnectedPeers { 125 if pid == id { 126 t.Logf("peer %q has already been disconnected", pid) 127 return nil 128 } 129 } 130 disconnectedPeers = append(disconnectedPeers, pid) 131 if len(disconnectedPeers) == numNodes { 132 close(disconnectsCh) 133 } 134 } 135 return nil 136 } 137 bus.SubscribeTypes(watchP2PQriEvents, event.ETP2PQriPeerConnected, event.ETP2PQriPeerDisconnected) 138 139 // create a new, disconnected node 140 testnode, err := p2ptest.NewNodeWithBus(ctx, factory, bus) 141 if err != nil { 142 t.Fatal(err) 143 } 144 node = testnode.(*QriNode) 145 // closing the discovery process prevents situations where another peer finds 146 // our node in the wild and attempts to connect to it while we are trying 147 // to connect to that node at the same time. This causes a dial failure. 148 node.Discovery.Close() 149 defer node.GoOffline() 150 t.Log("node id: ", node.host.ID()) 151 152 connectedPeers = node.qis.ConnectedQriPeers() 153 if len(connectedPeers) != 0 { 154 t.Errorf("expected 0 peers to be connected to the isolated node, but got %d connected qri peers instead", len(connectedPeers)) 155 } 156 157 // explicitly connect the main test node to each other node in the network 158 for _, groupNode := range nodes { 159 addrInfo := peer.AddrInfo{ 160 ID: groupNode.Host().ID(), 161 Addrs: groupNode.Host().Addrs(), 162 } 163 err := node.Host().Connect(context.Background(), addrInfo) 164 if err != nil { 165 t.Logf("error connecting to peer %q: %s", groupNode.Host().ID(), err) 166 } 167 } 168 169 // wait for all nodes to upgrade to qri peers 170 <-qriPeerConnWaitCh 171 172 // get a list of connected peers, according to the QriProfileService 173 connectedPeers = node.qis.ConnectedQriPeers() 174 sort.Sort(peer.IDSlice(connectedPeers)) 175 176 // ensure the lists are the same 177 if len(connectedPeers) == 0 { 178 t.Errorf("error exchange qri identities: expected number of connected peers to be %d, got %d", len(expectedPeers), len(connectedPeers)) 179 } 180 181 if len(unexpectedPeers) != 0 { 182 t.Errorf("unexpected peers found: %v", unexpectedPeers) 183 for _, pid := range unexpectedPeers { 184 protocols, err := node.host.Peerstore().GetProtocols(pid) 185 if err != nil { 186 t.Errorf("error getting peer %q protocols: %s", pid, err) 187 } else { 188 t.Logf("peer %q speaks protocols: %v", pid, protocols) 189 } 190 } 191 } 192 193 if len(expectedPeers) != len(connectedPeers) { 194 t.Errorf("expected list of connected peers different then the given list of connected peers: \n expected: %v\n got: %v", expectedPeers, connectedPeers) 195 for _, peer := range connectedPeers { 196 pro, err := node.Repo.Profiles().PeerProfile(ctx, peer) 197 if err != nil { 198 t.Errorf("error getting peer %q profile: %s", peer, err) 199 } 200 t.Logf("%s, %v", peer, pro) 201 } 202 return 203 } 204 different := false 205 for i, pid := range expectedPeers { 206 if pid != connectedPeers[i] { 207 different = true 208 break 209 } 210 } 211 if different { 212 t.Errorf("expected list of connected peers different then the given list of connected peers: \n expected: %v\n got: %v", expectedPeers, connectedPeers) 213 } 214 215 // check that each connection is protected, and the we have a profile for each 216 for _, id := range connectedPeers { 217 if protected := node.host.ConnManager().IsProtected(id, qriSupportKey); !protected { 218 t.Errorf("expected peer %q to have a protected connection, but it does not", id) 219 } 220 pro := node.qis.ConnectedPeerProfile(id) 221 if pro == nil { 222 t.Errorf("expected to have peer %q's qri profile, but wasn't able to receive it", id) 223 } 224 } 225 226 // disconnect each node 227 for _, node := range nodes { 228 node.GoOffline() 229 } 230 231 <-disconnectsCh 232 // get a list of connected qri peers according to the QriProfileService 233 connectedPeers = node.qis.ConnectedQriPeers() 234 235 if len(connectedPeers) != 0 { 236 t.Errorf("error with catching disconnects, expected 0 remaining connections, got %d to peers %v", len(connectedPeers), connectedPeers) 237 } 238 } 239 240 func TestDiscoveryConnection(t *testing.T) { 241 t.Skip("enable this test to see if nodes will find each other on your machine using their Discovery methods without getting orders to explicitily connect") 242 ctx := context.Background() 243 // create a network of connected nodes 244 factory := p2ptest.NewTestNodeFactory(NewTestableQriNode) 245 // create a new, disconnected node 246 busA := event.NewBus(ctx) 247 248 watchP2PQriEvents := func(_ context.Context, e event.Event) error { 249 pro, ok := e.Payload.(*profile.Profile) 250 if !ok { 251 t.Error("payload for event.ETP2PQriPeerConnected not a *profile.Profile as expected") 252 return fmt.Errorf("payload for event.ETP2PQriPeerConnected not a *profile.Profile as expected") 253 } 254 pid := pro.PeerIDs[0] 255 if e.Type == event.ETP2PQriPeerConnected { 256 t.Log("Qri Peer Connected: ", pid) 257 } 258 if e.Type == event.ETP2PQriPeerDisconnected { 259 t.Log("Qri Peer Disconnected: ", pid) 260 } 261 return nil 262 } 263 busA.SubscribeTypes(watchP2PQriEvents, event.ETP2PQriPeerConnected, event.ETP2PQriPeerDisconnected) 264 265 testNodeA, err := p2ptest.NewNodeWithBus(ctx, factory, busA) 266 if err != nil { 267 t.Error(err.Error()) 268 return 269 } 270 nodeA := testNodeA.(*QriNode) 271 272 testNodeB, err := p2ptest.NewNodeWithBus(ctx, factory, event.NilBus) 273 if err != nil { 274 t.Error(err.Error()) 275 return 276 } 277 nodeB := testNodeB.(*QriNode) 278 279 time.Sleep(time.Second * 30) 280 281 t.Errorf("nodeA's connections: %v", nodeA.ConnectedQriPeerIDs()) 282 t.Errorf("nodeB's connections: %v", nodeB.ConnectedQriPeerIDs()) 283 nodeA.GoOffline() 284 nodeB.GoOffline() 285 }