github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/p2p/broadcaster_test.go (about)

     1  package p2p
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"reflect"
     8  	"sync"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/ethereum/go-ethereum/p2p/discover"
    13  	"github.com/libp2p/go-libp2p-core/host"
    14  	pubsub "github.com/libp2p/go-libp2p-pubsub"
    15  	"github.com/prysmaticlabs/go-bitfield"
    16  	"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
    17  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers"
    18  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers/scorers"
    19  	p2ptest "github.com/prysmaticlabs/prysm/beacon-chain/p2p/testing"
    20  	pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
    21  	eth "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    22  	testpb "github.com/prysmaticlabs/prysm/proto/testing"
    23  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    24  	"github.com/prysmaticlabs/prysm/shared/interfaces"
    25  	"github.com/prysmaticlabs/prysm/shared/testutil"
    26  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    27  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    28  	"google.golang.org/protobuf/proto"
    29  )
    30  
    31  func TestService_Broadcast(t *testing.T) {
    32  	p1 := p2ptest.NewTestP2P(t)
    33  	p2 := p2ptest.NewTestP2P(t)
    34  	p1.Connect(p2)
    35  	if len(p1.BHost.Network().Peers()) == 0 {
    36  		t.Fatal("No peers")
    37  	}
    38  
    39  	p := &Service{
    40  		host:                  p1.BHost,
    41  		pubsub:                p1.PubSub(),
    42  		joinedTopics:          map[string]*pubsub.Topic{},
    43  		cfg:                   &Config{},
    44  		genesisTime:           time.Now(),
    45  		genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
    46  	}
    47  
    48  	msg := &pb.Fork{
    49  		Epoch:           55,
    50  		CurrentVersion:  []byte("fooo"),
    51  		PreviousVersion: []byte("barr"),
    52  	}
    53  
    54  	topic := "/eth2/%x/testing"
    55  	// Set a test gossip mapping for testpb.TestSimpleMessage.
    56  	GossipTypeMapping[reflect.TypeOf(msg)] = topic
    57  	digest, err := p.forkDigest()
    58  	require.NoError(t, err)
    59  	topic = fmt.Sprintf(topic, digest)
    60  
    61  	// External peer subscribes to the topic.
    62  	topic += p.Encoding().ProtocolSuffix()
    63  	sub, err := p2.SubscribeToTopic(topic)
    64  	require.NoError(t, err)
    65  
    66  	time.Sleep(50 * time.Millisecond) // libp2p fails without this delay...
    67  
    68  	// Async listen for the pubsub, must be before the broadcast.
    69  	var wg sync.WaitGroup
    70  	wg.Add(1)
    71  	go func(tt *testing.T) {
    72  		defer wg.Done()
    73  		ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    74  		defer cancel()
    75  
    76  		incomingMessage, err := sub.Next(ctx)
    77  		require.NoError(t, err)
    78  
    79  		result := &pb.Fork{}
    80  		require.NoError(t, p.Encoding().DecodeGossip(incomingMessage.Data, result))
    81  		if !proto.Equal(result, msg) {
    82  			tt.Errorf("Did not receive expected message, got %+v, wanted %+v", result, msg)
    83  		}
    84  	}(t)
    85  
    86  	// Broadcast to peers and wait.
    87  	require.NoError(t, p.Broadcast(context.Background(), msg))
    88  	if testutil.WaitTimeout(&wg, 1*time.Second) {
    89  		t.Error("Failed to receive pubsub within 1s")
    90  	}
    91  }
    92  
    93  func TestService_Broadcast_ReturnsErr_TopicNotMapped(t *testing.T) {
    94  	p := Service{
    95  		genesisTime:           time.Now(),
    96  		genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
    97  	}
    98  	assert.ErrorContains(t, ErrMessageNotMapped.Error(), p.Broadcast(context.Background(), &testpb.AddressBook{}))
    99  }
   100  
   101  func TestService_Attestation_Subnet(t *testing.T) {
   102  	if gtm := GossipTypeMapping[reflect.TypeOf(&eth.Attestation{})]; gtm != AttestationSubnetTopicFormat {
   103  		t.Errorf("Constant is out of date. Wanted %s, got %s", AttestationSubnetTopicFormat, gtm)
   104  	}
   105  
   106  	tests := []struct {
   107  		att   *eth.Attestation
   108  		topic string
   109  	}{
   110  		{
   111  			att: &eth.Attestation{
   112  				Data: &eth.AttestationData{
   113  					CommitteeIndex: 0,
   114  					Slot:           2,
   115  				},
   116  			},
   117  			topic: "/eth2/00000000/beacon_attestation_2",
   118  		},
   119  		{
   120  			att: &eth.Attestation{
   121  				Data: &eth.AttestationData{
   122  					CommitteeIndex: 11,
   123  					Slot:           10,
   124  				},
   125  			},
   126  			topic: "/eth2/00000000/beacon_attestation_21",
   127  		},
   128  		{
   129  			att: &eth.Attestation{
   130  				Data: &eth.AttestationData{
   131  					CommitteeIndex: 55,
   132  					Slot:           529,
   133  				},
   134  			},
   135  			topic: "/eth2/00000000/beacon_attestation_8",
   136  		},
   137  	}
   138  	for _, tt := range tests {
   139  		subnet := helpers.ComputeSubnetFromCommitteeAndSlot(100, tt.att.Data.CommitteeIndex, tt.att.Data.Slot)
   140  		assert.Equal(t, tt.topic, attestationToTopic(subnet, [4]byte{} /* fork digest */), "Wrong topic")
   141  	}
   142  }
   143  
   144  func TestService_BroadcastAttestation(t *testing.T) {
   145  	p1 := p2ptest.NewTestP2P(t)
   146  	p2 := p2ptest.NewTestP2P(t)
   147  	p1.Connect(p2)
   148  	if len(p1.BHost.Network().Peers()) == 0 {
   149  		t.Fatal("No peers")
   150  	}
   151  
   152  	p := &Service{
   153  		host:                  p1.BHost,
   154  		pubsub:                p1.PubSub(),
   155  		joinedTopics:          map[string]*pubsub.Topic{},
   156  		cfg:                   &Config{},
   157  		genesisTime:           time.Now(),
   158  		genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
   159  		subnetsLock:           make(map[uint64]*sync.RWMutex),
   160  		subnetsLockLock:       sync.Mutex{},
   161  		peers: peers.NewStatus(context.Background(), &peers.StatusConfig{
   162  			ScorerParams: &scorers.Config{},
   163  		}),
   164  	}
   165  
   166  	msg := testutil.HydrateAttestation(&eth.Attestation{AggregationBits: bitfield.NewBitlist(7)})
   167  	subnet := uint64(5)
   168  
   169  	topic := AttestationSubnetTopicFormat
   170  	GossipTypeMapping[reflect.TypeOf(msg)] = topic
   171  	digest, err := p.forkDigest()
   172  	require.NoError(t, err)
   173  	topic = fmt.Sprintf(topic, digest, subnet)
   174  
   175  	// External peer subscribes to the topic.
   176  	topic += p.Encoding().ProtocolSuffix()
   177  	sub, err := p2.SubscribeToTopic(topic)
   178  	require.NoError(t, err)
   179  
   180  	time.Sleep(50 * time.Millisecond) // libp2p fails without this delay...
   181  
   182  	// Async listen for the pubsub, must be before the broadcast.
   183  	var wg sync.WaitGroup
   184  	wg.Add(1)
   185  	go func(tt *testing.T) {
   186  		defer wg.Done()
   187  		ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
   188  		defer cancel()
   189  
   190  		incomingMessage, err := sub.Next(ctx)
   191  		require.NoError(t, err)
   192  
   193  		result := &eth.Attestation{}
   194  		require.NoError(t, p.Encoding().DecodeGossip(incomingMessage.Data, result))
   195  		if !proto.Equal(result, msg) {
   196  			tt.Errorf("Did not receive expected message, got %+v, wanted %+v", result, msg)
   197  		}
   198  	}(t)
   199  
   200  	// Broadcast to peers and wait.
   201  	require.NoError(t, p.BroadcastAttestation(context.Background(), subnet, msg))
   202  	if testutil.WaitTimeout(&wg, 1*time.Second) {
   203  		t.Error("Failed to receive pubsub within 1s")
   204  	}
   205  }
   206  
   207  func TestService_BroadcastAttestationWithDiscoveryAttempts(t *testing.T) {
   208  	// Setup bootnode.
   209  	cfg := &Config{}
   210  	port := 2000
   211  	cfg.UDPPort = uint(port)
   212  	_, pkey := createAddrAndPrivKey(t)
   213  	ipAddr := net.ParseIP("127.0.0.1")
   214  	genesisTime := time.Now()
   215  	genesisValidatorsRoot := make([]byte, 32)
   216  	s := &Service{
   217  		cfg:                   cfg,
   218  		genesisTime:           genesisTime,
   219  		genesisValidatorsRoot: genesisValidatorsRoot,
   220  	}
   221  	bootListener, err := s.createListener(ipAddr, pkey)
   222  	require.NoError(t, err)
   223  	defer bootListener.Close()
   224  
   225  	// Use shorter period for testing.
   226  	currentPeriod := pollingPeriod
   227  	pollingPeriod = 1 * time.Second
   228  	defer func() {
   229  		pollingPeriod = currentPeriod
   230  	}()
   231  
   232  	bootNode := bootListener.Self()
   233  	subnet := uint64(5)
   234  
   235  	var listeners []*discover.UDPv5
   236  	var hosts []host.Host
   237  	// setup other nodes.
   238  	cfg = &Config{
   239  		BootstrapNodeAddr:   []string{bootNode.String()},
   240  		Discv5BootStrapAddr: []string{bootNode.String()},
   241  		MaxPeers:            30,
   242  	}
   243  	// Setup 2 different hosts
   244  	for i := 1; i <= 2; i++ {
   245  		h, pkey, ipAddr := createHost(t, port+i)
   246  		cfg.UDPPort = uint(port + i)
   247  		cfg.TCPPort = uint(port + i)
   248  		s := &Service{
   249  			cfg:                   cfg,
   250  			genesisTime:           genesisTime,
   251  			genesisValidatorsRoot: genesisValidatorsRoot,
   252  		}
   253  		listener, err := s.startDiscoveryV5(ipAddr, pkey)
   254  		// Set for 2nd peer
   255  		if i == 2 {
   256  			s.dv5Listener = listener
   257  			s.metaData = interfaces.WrappedMetadataV0(new(pb.MetaDataV0))
   258  			bitV := bitfield.NewBitvector64()
   259  			bitV.SetBitAt(subnet, true)
   260  			s.updateSubnetRecordWithMetadata(bitV)
   261  		}
   262  		assert.NoError(t, err, "Could not start discovery for node")
   263  		listeners = append(listeners, listener)
   264  		hosts = append(hosts, h)
   265  	}
   266  	defer func() {
   267  		// Close down all peers.
   268  		for _, listener := range listeners {
   269  			listener.Close()
   270  		}
   271  	}()
   272  
   273  	// close peers upon exit of test
   274  	defer func() {
   275  		for _, h := range hosts {
   276  			if err := h.Close(); err != nil {
   277  				t.Log(err)
   278  			}
   279  		}
   280  	}()
   281  
   282  	ps1, err := pubsub.NewFloodSub(context.Background(), hosts[0],
   283  		pubsub.WithMessageSigning(false),
   284  		pubsub.WithStrictSignatureVerification(false),
   285  	)
   286  	require.NoError(t, err)
   287  
   288  	ps2, err := pubsub.NewFloodSub(context.Background(), hosts[1],
   289  		pubsub.WithMessageSigning(false),
   290  		pubsub.WithStrictSignatureVerification(false),
   291  	)
   292  	require.NoError(t, err)
   293  	p := &Service{
   294  		host:                  hosts[0],
   295  		ctx:                   context.Background(),
   296  		pubsub:                ps1,
   297  		dv5Listener:           listeners[0],
   298  		joinedTopics:          map[string]*pubsub.Topic{},
   299  		cfg:                   &Config{},
   300  		genesisTime:           time.Now(),
   301  		genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
   302  		subnetsLock:           make(map[uint64]*sync.RWMutex),
   303  		subnetsLockLock:       sync.Mutex{},
   304  		peers: peers.NewStatus(context.Background(), &peers.StatusConfig{
   305  			ScorerParams: &scorers.Config{},
   306  		}),
   307  	}
   308  
   309  	p2 := &Service{
   310  		host:                  hosts[1],
   311  		ctx:                   context.Background(),
   312  		pubsub:                ps2,
   313  		dv5Listener:           listeners[1],
   314  		joinedTopics:          map[string]*pubsub.Topic{},
   315  		cfg:                   &Config{},
   316  		genesisTime:           time.Now(),
   317  		genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
   318  		subnetsLock:           make(map[uint64]*sync.RWMutex),
   319  		subnetsLockLock:       sync.Mutex{},
   320  		peers: peers.NewStatus(context.Background(), &peers.StatusConfig{
   321  			ScorerParams: &scorers.Config{},
   322  		}),
   323  	}
   324  
   325  	msg := testutil.HydrateAttestation(&eth.Attestation{AggregationBits: bitfield.NewBitlist(7)})
   326  	topic := AttestationSubnetTopicFormat
   327  	GossipTypeMapping[reflect.TypeOf(msg)] = topic
   328  	digest, err := p.forkDigest()
   329  	require.NoError(t, err)
   330  	topic = fmt.Sprintf(topic, digest, subnet)
   331  
   332  	// External peer subscribes to the topic.
   333  	topic += p.Encoding().ProtocolSuffix()
   334  	// We dont use our internal subscribe method
   335  	// due to using floodsub over here.
   336  	tpHandle, err := p2.JoinTopic(topic)
   337  	require.NoError(t, err)
   338  	sub, err := tpHandle.Subscribe()
   339  	require.NoError(t, err)
   340  
   341  	time.Sleep(50 * time.Millisecond) // libp2p fails without this delay...
   342  
   343  	// Async listen for the pubsub, must be before the broadcast.
   344  	var wg sync.WaitGroup
   345  	wg.Add(1)
   346  	go func(tt *testing.T) {
   347  		defer wg.Done()
   348  		ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
   349  		defer cancel()
   350  
   351  		incomingMessage, err := sub.Next(ctx)
   352  		require.NoError(t, err)
   353  
   354  		result := &eth.Attestation{}
   355  		require.NoError(t, p.Encoding().DecodeGossip(incomingMessage.Data, result))
   356  		if !proto.Equal(result, msg) {
   357  			tt.Errorf("Did not receive expected message, got %+v, wanted %+v", result, msg)
   358  		}
   359  	}(t)
   360  
   361  	// Broadcast to peers and wait.
   362  	require.NoError(t, p.BroadcastAttestation(context.Background(), subnet, msg))
   363  	if testutil.WaitTimeout(&wg, 4*time.Second) {
   364  		t.Error("Failed to receive pubsub within 4s")
   365  	}
   366  }