github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/internal/p2pfixtures/fixtures.go (about) 1 package p2pfixtures 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "errors" 8 "fmt" 9 "net" 10 "testing" 11 "time" 12 13 addrutil "github.com/libp2p/go-addr-util" 14 pubsub "github.com/libp2p/go-libp2p-pubsub" 15 "github.com/libp2p/go-libp2p/core/host" 16 "github.com/libp2p/go-libp2p/core/network" 17 "github.com/libp2p/go-libp2p/core/peerstore" 18 "github.com/libp2p/go-libp2p/core/routing" 19 "github.com/multiformats/go-multiaddr" 20 manet "github.com/multiformats/go-multiaddr/net" 21 "github.com/onflow/crypto" 22 "github.com/rs/zerolog" 23 "github.com/stretchr/testify/require" 24 25 "github.com/onflow/flow-go/config" 26 "github.com/onflow/flow-go/model/flow" 27 "github.com/onflow/flow-go/module/id" 28 "github.com/onflow/flow-go/module/metrics" 29 flownet "github.com/onflow/flow-go/network" 30 "github.com/onflow/flow-go/network/channels" 31 "github.com/onflow/flow-go/network/internal/p2putils" 32 "github.com/onflow/flow-go/network/message" 33 "github.com/onflow/flow-go/network/p2p" 34 p2pbuilder "github.com/onflow/flow-go/network/p2p/builder" 35 p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" 36 p2pdht "github.com/onflow/flow-go/network/p2p/dht" 37 "github.com/onflow/flow-go/network/p2p/unicast/protocols" 38 "github.com/onflow/flow-go/network/p2p/utils" 39 "github.com/onflow/flow-go/utils/unittest" 40 ) 41 42 // NetworkingKeyFixtures is a test helper that generates a ECDSA flow key pair. 43 func NetworkingKeyFixtures(t *testing.T) crypto.PrivateKey { 44 seed := unittest.SeedFixture(48) 45 key, err := crypto.GeneratePrivateKey(crypto.ECDSASecp256k1, seed) 46 require.NoError(t, err) 47 return key 48 } 49 50 // SilentNodeFixture returns a TCP listener and a node which never replies 51 func SilentNodeFixture(t *testing.T) (net.Listener, flow.Identity) { 52 key := NetworkingKeyFixtures(t) 53 54 lst, err := net.Listen("tcp4", unittest.DefaultAddress) 55 require.NoError(t, err) 56 57 addr, err := manet.FromNetAddr(lst.Addr()) 58 require.NoError(t, err) 59 60 addrs := []multiaddr.Multiaddr{addr} 61 addrs, err = addrutil.ResolveUnspecifiedAddresses(addrs, nil) 62 require.NoError(t, err) 63 64 go acceptAndHang(t, lst) 65 66 ip, port, err := p2putils.IPPortFromMultiAddress(addrs...) 67 require.NoError(t, err) 68 69 identity := unittest.IdentityFixture(unittest.WithNetworkingKey(key.PublicKey()), unittest.WithAddress(ip+":"+port)) 70 return lst, *identity 71 } 72 73 func acceptAndHang(t *testing.T, l net.Listener) { 74 conns := make([]net.Conn, 0, 10) 75 for { 76 c, err := l.Accept() 77 if err != nil { 78 break 79 } 80 if c != nil { 81 conns = append(conns, c) 82 } 83 } 84 for _, c := range conns { 85 require.NoError(t, c.Close()) 86 } 87 } 88 89 type nodeOpt func(p2p.NodeBuilder) 90 91 func WithSubscriptionFilter(filter pubsub.SubscriptionFilter) nodeOpt { 92 return func(builder p2p.NodeBuilder) { 93 builder.SetSubscriptionFilter(filter) 94 } 95 } 96 97 // TODO: this should be replaced by node fixture: https://github.com/onflow/flow-go/blob/master/network/p2p/test/fixtures.go 98 func CreateNode(t *testing.T, networkKey crypto.PrivateKey, sporkID flow.Identifier, logger zerolog.Logger, nodeIds flow.IdentityList, opts ...nodeOpt) p2p.LibP2PNode { 99 idProvider := id.NewFixedIdentityProvider(nodeIds) 100 defaultFlowConfig, err := config.DefaultConfig() 101 require.NoError(t, err) 102 103 builder := p2pbuilder.NewNodeBuilder( 104 logger, 105 &defaultFlowConfig.NetworkConfig.GossipSub, 106 &p2pbuilderconfig.MetricsConfig{ 107 HeroCacheFactory: metrics.NewNoopHeroCacheMetricsFactory(), 108 Metrics: metrics.NewNoopCollector(), 109 }, 110 flownet.PrivateNetwork, 111 unittest.DefaultAddress, 112 networkKey, 113 sporkID, 114 idProvider, 115 &defaultFlowConfig.NetworkConfig.ResourceManager, 116 p2pbuilderconfig.PeerManagerDisableConfig(), 117 &p2p.DisallowListCacheConfig{ 118 MaxSize: uint32(1000), 119 Metrics: metrics.NewNoopCollector(), 120 }, 121 &p2pbuilderconfig.UnicastConfig{ 122 Unicast: defaultFlowConfig.NetworkConfig.Unicast, 123 }). 124 SetRoutingSystem(func(c context.Context, h host.Host) (routing.Routing, error) { 125 return p2pdht.NewDHT(c, h, protocols.FlowDHTProtocolID(sporkID), zerolog.Nop(), metrics.NewNoopCollector()) 126 }). 127 SetResourceManager(&network.NullResourceManager{}) 128 129 for _, opt := range opts { 130 opt(builder) 131 } 132 133 libp2pNode, err := builder.Build() 134 require.NoError(t, err) 135 136 return libp2pNode 137 } 138 139 // SubMustEventuallyStopReceivingAnyMessage checks that the subscription eventually stops receiving any messages within the given timeout by the context. 140 // This func uses the publish callback to continually publish messages to the subscription, this ensures that the subscription indeed stops receiving the messages. 141 func SubMustEventuallyStopReceivingAnyMessage(t *testing.T, ctx context.Context, sub p2p.Subscription, publish func(t *testing.T)) { 142 done := make(chan struct{}) 143 ticker := time.NewTicker(500 * time.Millisecond) 144 defer func() { 145 close(done) 146 ticker.Stop() 147 }() 148 149 go func() { 150 for { 151 select { 152 case <-done: 153 return 154 case <-ticker.C: 155 publish(t) 156 } 157 } 158 }() 159 160 // eventually we should stop receiving messages on the sub 161 require.Eventually(t, func() bool { 162 _, err := sub.Next(ctx) 163 return errors.Is(err, context.DeadlineExceeded) 164 }, 10*time.Second, 100*time.Millisecond) 165 166 // after we stop receiving messages on sub we should continue to not receiving messages 167 // despite messages continuing to be published 168 _, err := sub.Next(ctx) 169 require.Error(t, err) 170 require.ErrorIs(t, err, context.DeadlineExceeded) 171 } 172 173 // SubMustNeverReceiveAnyMessage checks that the subscription never receives any message within the given timeout by the context. 174 func SubMustNeverReceiveAnyMessage(t *testing.T, ctx context.Context, sub p2p.Subscription) { 175 timeouted := make(chan struct{}) 176 go func() { 177 _, err := sub.Next(ctx) 178 require.Error(t, err) 179 require.ErrorIs(t, err, context.DeadlineExceeded) 180 close(timeouted) 181 }() 182 183 // wait for the timeout, we choose the timeout to be long enough to make sure that 184 // on a happy path the timeout never happens, and short enough to make sure that 185 // the test doesn't take too long in case of a failure. 186 unittest.RequireCloseBefore(t, timeouted, 10*time.Second, "timeout did not happen on receiving expected pubsub message") 187 } 188 189 func SubsMustEventuallyStopReceivingAnyMessage(t *testing.T, ctx context.Context, subs []p2p.Subscription, send func(t *testing.T)) { 190 for _, sub := range subs { 191 SubMustEventuallyStopReceivingAnyMessage(t, ctx, sub, send) 192 } 193 } 194 195 // HasSubReceivedMessage checks that the subscription have received the given message within the given timeout by the context. 196 // It returns true if the subscription has received the message, false otherwise. 197 func HasSubReceivedMessage(t *testing.T, ctx context.Context, expectedMessage []byte, sub p2p.Subscription) bool { 198 received := make(chan struct{}) 199 go func() { 200 msg, err := sub.Next(ctx) 201 if err != nil { 202 require.ErrorIs(t, err, context.DeadlineExceeded) 203 return 204 } 205 if !bytes.Equal(expectedMessage, msg.Data) { 206 return 207 } 208 close(received) 209 }() 210 211 select { 212 case <-received: 213 return true 214 case <-ctx.Done(): 215 return false 216 } 217 } 218 219 // SubsMustNeverReceiveAnyMessage checks that all subscriptions never receive any message within the given timeout by the context. 220 func SubsMustNeverReceiveAnyMessage(t *testing.T, ctx context.Context, subs []p2p.Subscription) { 221 for _, sub := range subs { 222 SubMustNeverReceiveAnyMessage(t, ctx, sub) 223 } 224 } 225 226 // AddNodesToEachOthersPeerStore adds the dialing address of all nodes to the peer store of all other nodes. 227 // However, it does not connect them to each other. 228 func AddNodesToEachOthersPeerStore(t *testing.T, nodes []p2p.LibP2PNode, ids flow.IdentityList) { 229 for _, node := range nodes { 230 for i, other := range nodes { 231 if node == other { 232 continue 233 } 234 otherPInfo, err := utils.PeerAddressInfo(ids[i].IdentitySkeleton) 235 require.NoError(t, err) 236 node.Host().Peerstore().AddAddrs(otherPInfo.ID, otherPInfo.Addrs, peerstore.AddressTTL) 237 } 238 } 239 } 240 241 // EnsureNotConnected ensures that no connection exists from "from" nodes to "to" nodes. 242 func EnsureNotConnected(t *testing.T, ctx context.Context, from []p2p.LibP2PNode, to []p2p.LibP2PNode) { 243 for _, this := range from { 244 for _, other := range to { 245 if this == other { 246 require.Fail(t, "overlapping nodes in from and to lists") 247 } 248 thisId := this.ID() 249 // we intentionally do not check the error here, with libp2p v0.24 connection gating at the "InterceptSecured" level 250 // does not cause the nodes to complain about the connection being rejected at the dialer side. 251 // Hence, we instead check for any trace of the connection being established in the receiver side. 252 _ = this.Host().Connect(ctx, other.Host().Peerstore().PeerInfo(other.ID())) 253 // ensures that other node has never received a connection from this node. 254 require.Equal(t, network.NotConnected, other.Host().Network().Connectedness(thisId)) 255 require.Empty(t, other.Host().Network().ConnsToPeer(thisId)) 256 } 257 } 258 } 259 260 // EnsureMessageExchangeOverUnicast ensures that the given nodes exchange arbitrary messages on through unicasting (i.e., stream creation). 261 // It fails the test if any of the nodes does not receive the message from the other nodes. 262 // The "inbounds" parameter specifies the inbound channel of the nodes on which the messages are received. 263 // The "messageFactory" parameter specifies the function that creates unique messages to be sent. 264 func EnsureMessageExchangeOverUnicast(t *testing.T, ctx context.Context, nodes []p2p.LibP2PNode, inbounds []chan string, messageFactory func() string) { 265 for _, this := range nodes { 266 msg := messageFactory() 267 268 // send the message to all other nodes 269 for _, other := range nodes { 270 if this == other { 271 continue 272 } 273 err := this.OpenAndWriteOnStream(ctx, other.ID(), t.Name(), func(stream network.Stream) error { 274 rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream)) 275 _, err := rw.WriteString(msg) 276 require.NoError(t, err) 277 278 // Flush the stream 279 require.NoError(t, rw.Flush()) 280 281 return nil 282 }) 283 require.NoError(t, err) 284 285 } 286 287 // wait for the message to be received by all other nodes 288 for i, other := range nodes { 289 if this == other { 290 continue 291 } 292 293 select { 294 case rcv := <-inbounds[i]: 295 require.Equal(t, msg, rcv) 296 case <-time.After(3 * time.Second): 297 require.Fail(t, fmt.Sprintf("did not receive message from node %d", i)) 298 } 299 } 300 } 301 } 302 303 // EnsureNoStreamCreationBetweenGroups ensures that no stream is created between the given groups of nodes. 304 func EnsureNoStreamCreationBetweenGroups(t *testing.T, ctx context.Context, groupA []p2p.LibP2PNode, groupB []p2p.LibP2PNode, errorCheckers ...func(*testing.T, error)) { 305 // no stream from groupA -> groupB 306 EnsureNoStreamCreation(t, ctx, groupA, groupB, errorCheckers...) 307 // no stream from groupB -> groupA 308 EnsureNoStreamCreation(t, ctx, groupB, groupA, errorCheckers...) 309 } 310 311 // EnsureNoStreamCreation ensures that no stream is created "from" the given nodes "to" the given nodes. 312 func EnsureNoStreamCreation(t *testing.T, ctx context.Context, from []p2p.LibP2PNode, to []p2p.LibP2PNode, errorCheckers ...func(*testing.T, error)) { 313 for _, this := range from { 314 for _, other := range to { 315 if this == other { 316 // should not happen, unless the test is misconfigured. 317 require.Fail(t, "node is in both from and to lists") 318 } 319 320 // we intentionally do not check the error here, with libp2p v0.24 connection gating at the "InterceptSecured" level 321 // does not cause the nodes to complain about the connection being rejected at the dialer side. 322 // Hence, we instead check for any trace of the connection being established in the receiver side. 323 otherId := other.ID() 324 thisId := this.ID() 325 326 // closes all connections from other node to this node in order to isolate the connection attempt. 327 for _, conn := range other.Host().Network().ConnsToPeer(thisId) { 328 require.NoError(t, conn.Close()) 329 } 330 require.Empty(t, other.Host().Network().ConnsToPeer(thisId)) 331 332 err := this.OpenAndWriteOnStream(ctx, otherId, t.Name(), func(stream network.Stream) error { 333 // no-op as the stream is never created. 334 return nil 335 }) 336 // ensures that other node has never received a connection from this node. 337 require.Equal(t, network.NotConnected, other.Host().Network().Connectedness(thisId)) 338 // a stream is established on top of a connection, so if there is no connection, there should be no stream. 339 require.Empty(t, other.Host().Network().ConnsToPeer(thisId)) 340 // runs the error checkers if any. 341 for _, check := range errorCheckers { 342 check(t, err) 343 } 344 } 345 } 346 } 347 348 // EnsureStreamCreation ensures that a stream is created between each of the "from" nodes to each of the "to" nodes. 349 func EnsureStreamCreation(t *testing.T, ctx context.Context, from []p2p.LibP2PNode, to []p2p.LibP2PNode) { 350 for _, this := range from { 351 for _, other := range to { 352 if this == other { 353 // should not happen, unless the test is misconfigured. 354 require.Fail(t, "node is in both from and to lists") 355 } 356 // stream creation should pass without error 357 err := this.OpenAndWriteOnStream(ctx, other.ID(), t.Name(), func(stream network.Stream) error { 358 require.NotNil(t, stream) 359 return nil 360 }) 361 require.NoError(t, err) 362 } 363 } 364 } 365 366 // LongStringMessageFactoryFixture returns a function that creates a long unique string message. 367 func LongStringMessageFactoryFixture(t *testing.T) func() string { 368 return func() string { 369 msg := "this is an intentionally long MESSAGE to be bigger than buffer size of most of stream compressors" 370 require.Greater(t, len(msg), 10, "we must stress test with longer than 10 bytes messages") 371 return fmt.Sprintf("%s %d \n", msg, time.Now().UnixNano()) // add timestamp to make sure we don't send the same message twice 372 } 373 } 374 375 // MustEncodeEvent encodes and returns the given event and fails the test if it faces any issue while encoding. 376 func MustEncodeEvent(t *testing.T, v interface{}, channel channels.Channel) []byte { 377 bz, err := unittest.NetworkCodec().Encode(v) 378 require.NoError(t, err) 379 380 msg := message.Message{ 381 ChannelID: channel.String(), 382 Payload: bz, 383 } 384 data, err := msg.Marshal() 385 require.NoError(t, err) 386 387 return data 388 } 389 390 // SubMustReceiveMessage checks that the subscription have received the given message within the given timeout by the context. 391 func SubMustReceiveMessage(t *testing.T, ctx context.Context, expectedMessage []byte, sub p2p.Subscription) { 392 received := make(chan struct{}) 393 go func() { 394 msg, err := sub.Next(ctx) 395 require.NoError(t, err) 396 require.Equal(t, expectedMessage, msg.Data) 397 close(received) 398 }() 399 400 select { 401 case <-received: 402 return 403 case <-ctx.Done(): 404 require.Fail(t, "timeout on receiving expected pubsub message") 405 } 406 } 407 408 // SubsMustReceiveMessage checks that all subscriptions receive the given message within the given timeout by the context. 409 func SubsMustReceiveMessage(t *testing.T, ctx context.Context, expectedMessage []byte, subs []p2p.Subscription) { 410 for _, sub := range subs { 411 SubMustReceiveMessage(t, ctx, expectedMessage, sub) 412 } 413 }