github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/p2p/testing/p2p.go (about) 1 // Package testing includes useful utilities for mocking 2 // a beacon node's p2p service for unit tests. 3 package testing 4 5 import ( 6 "bytes" 7 "context" 8 "fmt" 9 "testing" 10 "time" 11 12 "github.com/ethereum/go-ethereum/p2p/enr" 13 bhost "github.com/libp2p/go-libp2p-blankhost" 14 core "github.com/libp2p/go-libp2p-core" 15 "github.com/libp2p/go-libp2p-core/control" 16 "github.com/libp2p/go-libp2p-core/host" 17 "github.com/libp2p/go-libp2p-core/network" 18 "github.com/libp2p/go-libp2p-core/peer" 19 "github.com/libp2p/go-libp2p-core/protocol" 20 pubsub "github.com/libp2p/go-libp2p-pubsub" 21 swarmt "github.com/libp2p/go-libp2p-swarm/testing" 22 "github.com/multiformats/go-multiaddr" 23 "github.com/prysmaticlabs/prysm/beacon-chain/p2p/encoder" 24 "github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers" 25 "github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers/scorers" 26 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 27 "github.com/prysmaticlabs/prysm/shared/interfaces" 28 "github.com/sirupsen/logrus" 29 "google.golang.org/protobuf/proto" 30 ) 31 32 // TestP2P represents a p2p implementation that can be used for testing. 33 type TestP2P struct { 34 t *testing.T 35 BHost host.Host 36 pubsub *pubsub.PubSub 37 joinedTopics map[string]*pubsub.Topic 38 BroadcastCalled bool 39 DelaySend bool 40 Digest [4]byte 41 peers *peers.Status 42 LocalMetadata interfaces.Metadata 43 } 44 45 // NewTestP2P initializes a new p2p test service. 46 func NewTestP2P(t *testing.T) *TestP2P { 47 ctx := context.Background() 48 h := bhost.NewBlankHost(swarmt.GenSwarm(t, ctx)) 49 ps, err := pubsub.NewFloodSub(ctx, h, 50 pubsub.WithMessageSigning(false), 51 pubsub.WithStrictSignatureVerification(false), 52 ) 53 if err != nil { 54 t.Fatal(err) 55 } 56 57 peerStatuses := peers.NewStatus(context.Background(), &peers.StatusConfig{ 58 PeerLimit: 30, 59 ScorerParams: &scorers.Config{ 60 BadResponsesScorerConfig: &scorers.BadResponsesScorerConfig{ 61 Threshold: 5, 62 }, 63 }, 64 }) 65 return &TestP2P{ 66 t: t, 67 BHost: h, 68 pubsub: ps, 69 joinedTopics: map[string]*pubsub.Topic{}, 70 peers: peerStatuses, 71 } 72 } 73 74 // Connect two test peers together. 75 func (p *TestP2P) Connect(b *TestP2P) { 76 if err := connect(p.BHost, b.BHost); err != nil { 77 p.t.Fatal(err) 78 } 79 } 80 81 func connect(a, b host.Host) error { 82 pinfo := b.Peerstore().PeerInfo(b.ID()) 83 return a.Connect(context.Background(), pinfo) 84 } 85 86 // ReceiveRPC simulates an incoming RPC. 87 func (p *TestP2P) ReceiveRPC(topic string, msg proto.Message) { 88 h := bhost.NewBlankHost(swarmt.GenSwarm(p.t, context.Background())) 89 if err := connect(h, p.BHost); err != nil { 90 p.t.Fatalf("Failed to connect two peers for RPC: %v", err) 91 } 92 s, err := h.NewStream(context.Background(), p.BHost.ID(), protocol.ID(topic+p.Encoding().ProtocolSuffix())) 93 if err != nil { 94 p.t.Fatalf("Failed to open stream %v", err) 95 } 96 defer func() { 97 if err := s.Close(); err != nil { 98 p.t.Log(err) 99 } 100 }() 101 102 n, err := p.Encoding().EncodeWithMaxLength(s, msg) 103 if err != nil { 104 _err := s.Reset() 105 _ = _err 106 p.t.Fatalf("Failed to encode message: %v", err) 107 } 108 109 p.t.Logf("Wrote %d bytes", n) 110 } 111 112 // ReceivePubSub simulates an incoming message over pubsub on a given topic. 113 func (p *TestP2P) ReceivePubSub(topic string, msg proto.Message) { 114 h := bhost.NewBlankHost(swarmt.GenSwarm(p.t, context.Background())) 115 ps, err := pubsub.NewFloodSub(context.Background(), h, 116 pubsub.WithMessageSigning(false), 117 pubsub.WithStrictSignatureVerification(false), 118 ) 119 if err != nil { 120 p.t.Fatalf("Failed to create flood sub: %v", err) 121 } 122 if err := connect(h, p.BHost); err != nil { 123 p.t.Fatalf("Failed to connect two peers for RPC: %v", err) 124 } 125 126 // PubSub requires some delay after connecting for the (*PubSub).processLoop method to 127 // pick up the newly connected peer. 128 time.Sleep(time.Millisecond * 100) 129 130 buf := new(bytes.Buffer) 131 if _, err := p.Encoding().EncodeGossip(buf, msg); err != nil { 132 p.t.Fatalf("Failed to encode message: %v", err) 133 } 134 digest, err := p.ForkDigest() 135 if err != nil { 136 p.t.Fatal(err) 137 } 138 topicHandle, err := ps.Join(fmt.Sprintf(topic, digest) + p.Encoding().ProtocolSuffix()) 139 if err != nil { 140 p.t.Fatal(err) 141 } 142 if err := topicHandle.Publish(context.TODO(), buf.Bytes()); err != nil { 143 p.t.Fatalf("Failed to publish message; %v", err) 144 } 145 } 146 147 // Broadcast a message. 148 func (p *TestP2P) Broadcast(_ context.Context, _ proto.Message) error { 149 p.BroadcastCalled = true 150 return nil 151 } 152 153 // BroadcastAttestation broadcasts an attestation. 154 func (p *TestP2P) BroadcastAttestation(_ context.Context, _ uint64, _ *ethpb.Attestation) error { 155 p.BroadcastCalled = true 156 return nil 157 } 158 159 // SetStreamHandler for RPC. 160 func (p *TestP2P) SetStreamHandler(topic string, handler network.StreamHandler) { 161 p.BHost.SetStreamHandler(protocol.ID(topic), handler) 162 } 163 164 // JoinTopic will join PubSub topic, if not already joined. 165 func (p *TestP2P) JoinTopic(topic string, opts ...pubsub.TopicOpt) (*pubsub.Topic, error) { 166 if _, ok := p.joinedTopics[topic]; !ok { 167 joinedTopic, err := p.pubsub.Join(topic, opts...) 168 if err != nil { 169 return nil, err 170 } 171 p.joinedTopics[topic] = joinedTopic 172 } 173 174 return p.joinedTopics[topic], nil 175 } 176 177 // PublishToTopic publishes message to previously joined topic. 178 func (p *TestP2P) PublishToTopic(ctx context.Context, topic string, data []byte, opts ...pubsub.PubOpt) error { 179 joinedTopic, err := p.JoinTopic(topic) 180 if err != nil { 181 return err 182 } 183 return joinedTopic.Publish(ctx, data, opts...) 184 } 185 186 // SubscribeToTopic joins (if necessary) and subscribes to PubSub topic. 187 func (p *TestP2P) SubscribeToTopic(topic string, opts ...pubsub.SubOpt) (*pubsub.Subscription, error) { 188 joinedTopic, err := p.JoinTopic(topic) 189 if err != nil { 190 return nil, err 191 } 192 return joinedTopic.Subscribe(opts...) 193 } 194 195 // LeaveTopic closes topic and removes corresponding handler from list of joined topics. 196 // This method will return error if there are outstanding event handlers or subscriptions. 197 func (p *TestP2P) LeaveTopic(topic string) error { 198 if t, ok := p.joinedTopics[topic]; ok { 199 if err := t.Close(); err != nil { 200 return err 201 } 202 delete(p.joinedTopics, topic) 203 } 204 return nil 205 } 206 207 // Encoding returns ssz encoding. 208 func (p *TestP2P) Encoding() encoder.NetworkEncoding { 209 return &encoder.SszNetworkEncoder{} 210 } 211 212 // PubSub returns reference underlying floodsub. This test library uses floodsub 213 // to ensure all connected peers receive the message. 214 func (p *TestP2P) PubSub() *pubsub.PubSub { 215 return p.pubsub 216 } 217 218 // Disconnect from a peer. 219 func (p *TestP2P) Disconnect(pid peer.ID) error { 220 return p.BHost.Network().ClosePeer(pid) 221 } 222 223 // PeerID returns the Peer ID of the local peer. 224 func (p *TestP2P) PeerID() peer.ID { 225 return p.BHost.ID() 226 } 227 228 // Host returns the libp2p host of the 229 // local peer. 230 func (p *TestP2P) Host() host.Host { 231 return p.BHost 232 } 233 234 // ENR returns the enr of the local peer. 235 func (p *TestP2P) ENR() *enr.Record { 236 return new(enr.Record) 237 } 238 239 // DiscoveryAddresses -- 240 func (p *TestP2P) DiscoveryAddresses() ([]multiaddr.Multiaddr, error) { 241 return nil, nil 242 } 243 244 // AddConnectionHandler handles the connection with a newly connected peer. 245 func (p *TestP2P) AddConnectionHandler(f, _ func(ctx context.Context, id peer.ID) error) { 246 p.BHost.Network().Notify(&network.NotifyBundle{ 247 ConnectedF: func(net network.Network, conn network.Conn) { 248 // Must be handled in a goroutine as this callback cannot be blocking. 249 go func() { 250 p.peers.Add(new(enr.Record), conn.RemotePeer(), conn.RemoteMultiaddr(), conn.Stat().Direction) 251 ctx := context.Background() 252 253 p.peers.SetConnectionState(conn.RemotePeer(), peers.PeerConnecting) 254 if err := f(ctx, conn.RemotePeer()); err != nil { 255 logrus.WithError(err).Error("Could not send succesful hello rpc request") 256 if err := p.Disconnect(conn.RemotePeer()); err != nil { 257 logrus.WithError(err).Errorf("Unable to close peer %s", conn.RemotePeer()) 258 } 259 p.peers.SetConnectionState(conn.RemotePeer(), peers.PeerDisconnected) 260 return 261 } 262 p.peers.SetConnectionState(conn.RemotePeer(), peers.PeerConnected) 263 }() 264 }, 265 }) 266 } 267 268 // AddDisconnectionHandler -- 269 func (p *TestP2P) AddDisconnectionHandler(f func(ctx context.Context, id peer.ID) error) { 270 p.BHost.Network().Notify(&network.NotifyBundle{ 271 DisconnectedF: func(net network.Network, conn network.Conn) { 272 // Must be handled in a goroutine as this callback cannot be blocking. 273 go func() { 274 p.peers.SetConnectionState(conn.RemotePeer(), peers.PeerDisconnecting) 275 if err := f(context.Background(), conn.RemotePeer()); err != nil { 276 logrus.WithError(err).Debug("Unable to invoke callback") 277 } 278 p.peers.SetConnectionState(conn.RemotePeer(), peers.PeerDisconnected) 279 }() 280 }, 281 }) 282 } 283 284 // Send a message to a specific peer. 285 func (p *TestP2P) Send(ctx context.Context, msg interface{}, topic string, pid peer.ID) (network.Stream, error) { 286 t := topic 287 if t == "" { 288 return nil, fmt.Errorf("protocol doesnt exist for proto message: %v", msg) 289 } 290 stream, err := p.BHost.NewStream(ctx, pid, core.ProtocolID(t+p.Encoding().ProtocolSuffix())) 291 if err != nil { 292 return nil, err 293 } 294 295 if topic != "/eth2/beacon_chain/req/metadata/1" { 296 if _, err := p.Encoding().EncodeWithMaxLength(stream, msg); err != nil { 297 _err := stream.Reset() 298 _ = _err 299 return nil, err 300 } 301 } 302 303 // Close stream for writing. 304 if err := stream.CloseWrite(); err != nil { 305 _err := stream.Reset() 306 _ = _err 307 return nil, err 308 } 309 // Delay returning the stream for testing purposes 310 if p.DelaySend { 311 time.Sleep(1 * time.Second) 312 } 313 314 return stream, nil 315 } 316 317 // Started always returns true. 318 func (p *TestP2P) Started() bool { 319 return true 320 } 321 322 // Peers returns the peer status. 323 func (p *TestP2P) Peers() *peers.Status { 324 return p.peers 325 } 326 327 // FindPeersWithSubnet mocks the p2p func. 328 func (p *TestP2P) FindPeersWithSubnet(_ context.Context, _ string, _, _ uint64) (bool, error) { 329 return false, nil 330 } 331 332 // RefreshENR mocks the p2p func. 333 func (p *TestP2P) RefreshENR() {} 334 335 // ForkDigest mocks the p2p func. 336 func (p *TestP2P) ForkDigest() ([4]byte, error) { 337 return p.Digest, nil 338 } 339 340 // Metadata mocks the peer's metadata. 341 func (p *TestP2P) Metadata() interfaces.Metadata { 342 return p.LocalMetadata.Copy() 343 } 344 345 // MetadataSeq mocks metadata sequence number. 346 func (p *TestP2P) MetadataSeq() uint64 { 347 return p.LocalMetadata.SequenceNumber() 348 } 349 350 // AddPingMethod mocks the p2p func. 351 func (p *TestP2P) AddPingMethod(_ func(ctx context.Context, id peer.ID) error) { 352 // no-op 353 } 354 355 // InterceptPeerDial . 356 func (p *TestP2P) InterceptPeerDial(peer.ID) (allow bool) { 357 return true 358 } 359 360 // InterceptAddrDial . 361 func (p *TestP2P) InterceptAddrDial(peer.ID, multiaddr.Multiaddr) (allow bool) { 362 return true 363 } 364 365 // InterceptAccept . 366 func (p *TestP2P) InterceptAccept(_ network.ConnMultiaddrs) (allow bool) { 367 return true 368 } 369 370 // InterceptSecured . 371 func (p *TestP2P) InterceptSecured(network.Direction, peer.ID, network.ConnMultiaddrs) (allow bool) { 372 return true 373 } 374 375 // InterceptUpgraded . 376 func (p *TestP2P) InterceptUpgraded(network.Conn) (allow bool, reason control.DisconnectReason) { 377 return true, 0 378 }