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