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  }