github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/rpc/eth/v1/node/node_test.go (about)

     1  package node
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"runtime"
     8  	"strconv"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/ethereum/go-ethereum/p2p/enode"
    13  	"github.com/ethereum/go-ethereum/p2p/enr"
    14  	grpcruntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    15  	"github.com/libp2p/go-libp2p-core/network"
    16  	"github.com/libp2p/go-libp2p-core/peer"
    17  	libp2ptest "github.com/libp2p/go-libp2p-peerstore/test"
    18  	ma "github.com/multiformats/go-multiaddr"
    19  	types "github.com/prysmaticlabs/eth2-types"
    20  	"github.com/prysmaticlabs/go-bitfield"
    21  	mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
    22  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p"
    23  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers"
    24  	mockp2p "github.com/prysmaticlabs/prysm/beacon-chain/p2p/testing"
    25  	syncmock "github.com/prysmaticlabs/prysm/beacon-chain/sync/initial-sync/testing"
    26  	pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
    27  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1"
    28  	"github.com/prysmaticlabs/prysm/shared/grpcutils"
    29  	"github.com/prysmaticlabs/prysm/shared/interfaces"
    30  	"github.com/prysmaticlabs/prysm/shared/testutil"
    31  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    32  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    33  	"github.com/prysmaticlabs/prysm/shared/version"
    34  	"google.golang.org/grpc"
    35  	"google.golang.org/protobuf/types/known/emptypb"
    36  )
    37  
    38  type dummyIdentity enode.ID
    39  
    40  func (id dummyIdentity) Verify(_ *enr.Record, _ []byte) error { return nil }
    41  func (id dummyIdentity) NodeAddr(_ *enr.Record) []byte        { return id[:] }
    42  
    43  func TestGetVersion(t *testing.T) {
    44  	semVer := version.SemanticVersion()
    45  	os := runtime.GOOS
    46  	arch := runtime.GOARCH
    47  	res, err := (&Server{}).GetVersion(context.Background(), &emptypb.Empty{})
    48  	require.NoError(t, err)
    49  	v := res.Data.Version
    50  	assert.Equal(t, true, strings.Contains(v, semVer))
    51  	assert.Equal(t, true, strings.Contains(v, os))
    52  	assert.Equal(t, true, strings.Contains(v, arch))
    53  }
    54  
    55  func TestGetHealth(t *testing.T) {
    56  	ctx := grpc.NewContextWithServerTransportStream(context.Background(), &grpcruntime.ServerTransportStream{})
    57  	checker := &syncmock.Sync{}
    58  	s := &Server{
    59  		SyncChecker: checker,
    60  	}
    61  
    62  	_, err := s.GetHealth(ctx, &emptypb.Empty{})
    63  	require.ErrorContains(t, "Node not initialized or having issues", err)
    64  	checker.IsInitialized = true
    65  	_, err = s.GetHealth(ctx, &emptypb.Empty{})
    66  	require.NoError(t, err)
    67  	stream, ok := grpc.ServerTransportStreamFromContext(ctx).(*grpcruntime.ServerTransportStream)
    68  	require.Equal(t, true, ok, "type assertion failed")
    69  	assert.Equal(t, stream.Header()[strings.ToLower(grpcutils.HttpCodeMetadataKey)][0], strconv.Itoa(http.StatusPartialContent))
    70  	checker.IsSynced = true
    71  	_, err = s.GetHealth(ctx, &emptypb.Empty{})
    72  	require.NoError(t, err)
    73  }
    74  
    75  func TestGetIdentity(t *testing.T) {
    76  	ctx := context.Background()
    77  	p2pAddr, err := ma.NewMultiaddr("/ip4/7.7.7.7/udp/30303")
    78  	require.NoError(t, err)
    79  	discAddr1, err := ma.NewMultiaddr("/ip4/7.7.7.7/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N")
    80  	require.NoError(t, err)
    81  	discAddr2, err := ma.NewMultiaddr("/ip6/1:2:3:4:5:6:7:8/udp/20202/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N")
    82  	require.NoError(t, err)
    83  	enrRecord := &enr.Record{}
    84  	err = enrRecord.SetSig(dummyIdentity{1}, []byte{42})
    85  	require.NoError(t, err)
    86  	enrRecord.Set(enr.IPv4{7, 7, 7, 7})
    87  	err = enrRecord.SetSig(dummyIdentity{}, []byte{})
    88  	require.NoError(t, err)
    89  	attnets := bitfield.NewBitvector64()
    90  	attnets.SetBitAt(1, true)
    91  	metadataProvider := &mockp2p.MockMetadataProvider{Data: interfaces.WrappedMetadataV0(&pb.MetaDataV0{SeqNumber: 1, Attnets: attnets})}
    92  
    93  	t.Run("OK", func(t *testing.T) {
    94  		peerManager := &mockp2p.MockPeerManager{
    95  			Enr:           enrRecord,
    96  			PID:           "foo",
    97  			BHost:         &mockp2p.MockHost{Addresses: []ma.Multiaddr{p2pAddr}},
    98  			DiscoveryAddr: []ma.Multiaddr{discAddr1, discAddr2},
    99  		}
   100  		s := &Server{
   101  			PeerManager:      peerManager,
   102  			MetadataProvider: metadataProvider,
   103  		}
   104  
   105  		resp, err := s.GetIdentity(ctx, &emptypb.Empty{})
   106  		require.NoError(t, err)
   107  		expectedID := peer.ID("foo").Pretty()
   108  		assert.Equal(t, expectedID, resp.Data.PeerId)
   109  		expectedEnr, err := p2p.SerializeENR(enrRecord)
   110  		require.NoError(t, err)
   111  		assert.Equal(t, fmt.Sprint("enr:", expectedEnr), resp.Data.Enr)
   112  		require.Equal(t, 1, len(resp.Data.P2PAddresses))
   113  		assert.Equal(t, p2pAddr.String()+"/p2p/"+expectedID, resp.Data.P2PAddresses[0])
   114  		require.Equal(t, 2, len(resp.Data.DiscoveryAddresses))
   115  		ipv4Found, ipv6Found := false, false
   116  		for _, address := range resp.Data.DiscoveryAddresses {
   117  			if address == discAddr1.String() {
   118  				ipv4Found = true
   119  			} else if address == discAddr2.String() {
   120  				ipv6Found = true
   121  			}
   122  		}
   123  		assert.Equal(t, true, ipv4Found, "IPv4 discovery address not found")
   124  		assert.Equal(t, true, ipv6Found, "IPv6 discovery address not found")
   125  		assert.Equal(t, discAddr1.String(), resp.Data.DiscoveryAddresses[0])
   126  		assert.Equal(t, discAddr2.String(), resp.Data.DiscoveryAddresses[1])
   127  	})
   128  
   129  	t.Run("ENR failure", func(t *testing.T) {
   130  		peerManager := &mockp2p.MockPeerManager{
   131  			Enr:           &enr.Record{},
   132  			PID:           "foo",
   133  			BHost:         &mockp2p.MockHost{Addresses: []ma.Multiaddr{p2pAddr}},
   134  			DiscoveryAddr: []ma.Multiaddr{discAddr1, discAddr2},
   135  		}
   136  		s := &Server{
   137  			PeerManager:      peerManager,
   138  			MetadataProvider: metadataProvider,
   139  		}
   140  
   141  		_, err = s.GetIdentity(ctx, &emptypb.Empty{})
   142  		assert.ErrorContains(t, "Could not obtain enr", err)
   143  	})
   144  
   145  	t.Run("Discovery addresses failure", func(t *testing.T) {
   146  		peerManager := &mockp2p.MockPeerManager{
   147  			Enr:               enrRecord,
   148  			PID:               "foo",
   149  			BHost:             &mockp2p.MockHost{Addresses: []ma.Multiaddr{p2pAddr}},
   150  			DiscoveryAddr:     []ma.Multiaddr{discAddr1, discAddr2},
   151  			FailDiscoveryAddr: true,
   152  		}
   153  		s := &Server{
   154  			PeerManager:      peerManager,
   155  			MetadataProvider: metadataProvider,
   156  		}
   157  
   158  		_, err = s.GetIdentity(ctx, &emptypb.Empty{})
   159  		assert.ErrorContains(t, "Could not obtain discovery address", err)
   160  	})
   161  }
   162  
   163  func TestSyncStatus(t *testing.T) {
   164  	currentSlot := new(types.Slot)
   165  	*currentSlot = 110
   166  	state, err := testutil.NewBeaconState()
   167  	require.NoError(t, err)
   168  	err = state.SetSlot(100)
   169  	require.NoError(t, err)
   170  	chainService := &mock.ChainService{Slot: currentSlot, State: state}
   171  	syncChecker := &syncmock.Sync{}
   172  	syncChecker.IsSyncing = true
   173  
   174  	s := &Server{
   175  		HeadFetcher:        chainService,
   176  		GenesisTimeFetcher: chainService,
   177  		SyncChecker:        syncChecker,
   178  	}
   179  	resp, err := s.GetSyncStatus(context.Background(), &emptypb.Empty{})
   180  	require.NoError(t, err)
   181  	assert.Equal(t, types.Slot(100), resp.Data.HeadSlot)
   182  	assert.Equal(t, types.Slot(10), resp.Data.SyncDistance)
   183  	assert.Equal(t, true, resp.Data.IsSyncing)
   184  }
   185  
   186  func TestGetPeer(t *testing.T) {
   187  	const rawId = "16Uiu2HAkvyYtoQXZNTsthjgLHjEnv7kvwzEmjvsJjWXpbhtqpSUN"
   188  	ctx := context.Background()
   189  	decodedId, err := peer.Decode(rawId)
   190  	require.NoError(t, err)
   191  	enrRecord := &enr.Record{}
   192  	err = enrRecord.SetSig(dummyIdentity{1}, []byte{42})
   193  	require.NoError(t, err)
   194  	enrRecord.Set(enr.IPv4{7, 7, 7, 7})
   195  	err = enrRecord.SetSig(dummyIdentity{}, []byte{})
   196  	require.NoError(t, err)
   197  	const p2pAddr = "/ip4/7.7.7.7/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N"
   198  	p2pMultiAddr, err := ma.NewMultiaddr(p2pAddr)
   199  	require.NoError(t, err)
   200  	peerFetcher := &mockp2p.MockPeersProvider{}
   201  	s := Server{PeersFetcher: peerFetcher}
   202  	peerFetcher.Peers().Add(enrRecord, decodedId, p2pMultiAddr, network.DirInbound)
   203  
   204  	t.Run("OK", func(t *testing.T) {
   205  		resp, err := s.GetPeer(ctx, &ethpb.PeerRequest{PeerId: rawId})
   206  		require.NoError(t, err)
   207  		assert.Equal(t, rawId, resp.Data.PeerId)
   208  		assert.Equal(t, p2pAddr, resp.Data.LastSeenP2PAddress)
   209  		assert.Equal(t, "enr:yoABgmlwhAcHBwc=", resp.Data.Enr)
   210  		assert.Equal(t, ethpb.ConnectionState_DISCONNECTED, resp.Data.State)
   211  		assert.Equal(t, ethpb.PeerDirection_INBOUND, resp.Data.Direction)
   212  	})
   213  
   214  	t.Run("Invalid ID", func(t *testing.T) {
   215  		_, err = s.GetPeer(ctx, &ethpb.PeerRequest{PeerId: "foo"})
   216  		assert.ErrorContains(t, "Invalid peer ID", err)
   217  	})
   218  
   219  	t.Run("Peer not found", func(t *testing.T) {
   220  		generatedId := "16Uiu2HAmQqFdEcHbSmQTQuLoAhnMUrgoWoraKK4cUJT6FuuqHqTU"
   221  		_, err = s.GetPeer(ctx, &ethpb.PeerRequest{PeerId: generatedId})
   222  		assert.ErrorContains(t, "Peer not found", err)
   223  	})
   224  }
   225  
   226  func TestListPeers(t *testing.T) {
   227  	ids := libp2ptest.GeneratePeerIDs(9)
   228  	peerFetcher := &mockp2p.MockPeersProvider{}
   229  	peerFetcher.ClearPeers()
   230  	peerStatus := peerFetcher.Peers()
   231  
   232  	for i, id := range ids {
   233  		// Make last peer undiscovered
   234  		if i == len(ids)-1 {
   235  			peerStatus.Add(nil, id, nil, network.DirUnknown)
   236  		} else {
   237  			enrRecord := &enr.Record{}
   238  			err := enrRecord.SetSig(dummyIdentity{1}, []byte{42})
   239  			require.NoError(t, err)
   240  			enrRecord.Set(enr.IPv4{127, 0, 0, byte(i)})
   241  			err = enrRecord.SetSig(dummyIdentity{}, []byte{})
   242  			require.NoError(t, err)
   243  			var p2pAddr = "/ip4/127.0.0." + strconv.Itoa(i) + "/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N"
   244  			p2pMultiAddr, err := ma.NewMultiaddr(p2pAddr)
   245  			require.NoError(t, err)
   246  
   247  			var direction network.Direction
   248  			if i%2 == 0 {
   249  				direction = network.DirInbound
   250  			} else {
   251  				direction = network.DirOutbound
   252  			}
   253  			peerStatus.Add(enrRecord, id, p2pMultiAddr, direction)
   254  
   255  			switch i {
   256  			case 0, 1:
   257  				peerStatus.SetConnectionState(id, peers.PeerConnecting)
   258  			case 2, 3:
   259  				peerStatus.SetConnectionState(id, peers.PeerConnected)
   260  			case 4, 5:
   261  				peerStatus.SetConnectionState(id, peers.PeerDisconnecting)
   262  			case 6, 7:
   263  				peerStatus.SetConnectionState(id, peers.PeerDisconnected)
   264  			default:
   265  				t.Fatalf("Failed to set connection state for peer")
   266  			}
   267  		}
   268  	}
   269  
   270  	s := Server{PeersFetcher: peerFetcher}
   271  
   272  	t.Run("Peer data OK", func(t *testing.T) {
   273  		// We will check the first peer from the list.
   274  		expectedId := ids[0]
   275  
   276  		resp, err := s.ListPeers(context.Background(), &ethpb.PeersRequest{
   277  			State:     []ethpb.ConnectionState{ethpb.ConnectionState_CONNECTING},
   278  			Direction: []ethpb.PeerDirection{ethpb.PeerDirection_INBOUND},
   279  		})
   280  		require.NoError(t, err)
   281  		require.Equal(t, 1, len(resp.Data))
   282  		returnedPeer := resp.Data[0]
   283  		assert.Equal(t, expectedId.Pretty(), returnedPeer.PeerId)
   284  		expectedEnr, err := peerStatus.ENR(expectedId)
   285  		require.NoError(t, err)
   286  		serializedEnr, err := p2p.SerializeENR(expectedEnr)
   287  		require.NoError(t, err)
   288  		assert.Equal(t, "enr:"+serializedEnr, returnedPeer.Enr)
   289  		expectedP2PAddr, err := peerStatus.Address(expectedId)
   290  		require.NoError(t, err)
   291  		assert.Equal(t, expectedP2PAddr.String(), returnedPeer.LastSeenP2PAddress)
   292  		assert.Equal(t, ethpb.ConnectionState_CONNECTING, returnedPeer.State)
   293  		assert.Equal(t, ethpb.PeerDirection_INBOUND, returnedPeer.Direction)
   294  	})
   295  
   296  	filterTests := []struct {
   297  		name       string
   298  		states     []ethpb.ConnectionState
   299  		directions []ethpb.PeerDirection
   300  		wantIds    []peer.ID
   301  	}{
   302  		{
   303  			name:       "No filters - return all peers",
   304  			states:     []ethpb.ConnectionState{},
   305  			directions: []ethpb.PeerDirection{},
   306  			wantIds:    ids[:len(ids)-1], // Excluding last peer as it is not connected.
   307  		},
   308  		{
   309  			name:       "State filter empty - return peers for all states",
   310  			states:     []ethpb.ConnectionState{},
   311  			directions: []ethpb.PeerDirection{ethpb.PeerDirection_INBOUND},
   312  			wantIds:    []peer.ID{ids[0], ids[2], ids[4], ids[6]},
   313  		},
   314  		{
   315  			name:       "Direction filter empty - return peers for all directions",
   316  			states:     []ethpb.ConnectionState{ethpb.ConnectionState_CONNECTED},
   317  			directions: []ethpb.PeerDirection{},
   318  			wantIds:    []peer.ID{ids[2], ids[3]},
   319  		},
   320  		{
   321  			name:       "One state and direction",
   322  			states:     []ethpb.ConnectionState{ethpb.ConnectionState_DISCONNECTED},
   323  			directions: []ethpb.PeerDirection{ethpb.PeerDirection_INBOUND},
   324  			wantIds:    []peer.ID{ids[6]},
   325  		},
   326  		{
   327  			name:       "Multiple states and directions",
   328  			states:     []ethpb.ConnectionState{ethpb.ConnectionState_CONNECTING, ethpb.ConnectionState_DISCONNECTING},
   329  			directions: []ethpb.PeerDirection{ethpb.PeerDirection_INBOUND, ethpb.PeerDirection_OUTBOUND},
   330  			wantIds:    []peer.ID{ids[0], ids[1], ids[4], ids[5]},
   331  		},
   332  		{
   333  			name:       "Unknown filter is ignored",
   334  			states:     []ethpb.ConnectionState{ethpb.ConnectionState_CONNECTED, 99},
   335  			directions: []ethpb.PeerDirection{ethpb.PeerDirection_OUTBOUND, 99},
   336  			wantIds:    []peer.ID{ids[3]},
   337  		},
   338  		{
   339  			name:       "Only unknown filters - return all peers",
   340  			states:     []ethpb.ConnectionState{99},
   341  			directions: []ethpb.PeerDirection{99},
   342  			wantIds:    ids[:len(ids)-1], // Excluding last peer as it is not connected.
   343  		},
   344  	}
   345  	for _, tt := range filterTests {
   346  		t.Run(tt.name, func(t *testing.T) {
   347  			resp, err := s.ListPeers(context.Background(), &ethpb.PeersRequest{
   348  				State:     tt.states,
   349  				Direction: tt.directions,
   350  			})
   351  			require.NoError(t, err)
   352  			assert.Equal(t, len(tt.wantIds), len(resp.Data), "Wrong number of peers returned")
   353  			for _, id := range tt.wantIds {
   354  				expectedId := id.Pretty()
   355  				found := false
   356  				for _, returnedPeer := range resp.Data {
   357  					if returnedPeer.PeerId == expectedId {
   358  						found = true
   359  						break
   360  					}
   361  				}
   362  				if !found {
   363  					t.Errorf("Expected ID '" + expectedId + "' not found")
   364  				}
   365  			}
   366  		})
   367  	}
   368  }
   369  
   370  func TestListPeers_NoPeersReturnsEmptyArray(t *testing.T) {
   371  	peerFetcher := &mockp2p.MockPeersProvider{}
   372  	peerFetcher.ClearPeers()
   373  	s := Server{PeersFetcher: peerFetcher}
   374  
   375  	resp, err := s.ListPeers(context.Background(), &ethpb.PeersRequest{
   376  		State: []ethpb.ConnectionState{ethpb.ConnectionState_CONNECTED},
   377  	})
   378  	require.NoError(t, err)
   379  	require.NotNil(t, resp.Data)
   380  	assert.Equal(t, 0, len(resp.Data))
   381  }
   382  
   383  func TestPeerCount(t *testing.T) {
   384  	ids := libp2ptest.GeneratePeerIDs(10)
   385  	peerFetcher := &mockp2p.MockPeersProvider{}
   386  	peerFetcher.ClearPeers()
   387  	peerStatus := peerFetcher.Peers()
   388  
   389  	for i, id := range ids {
   390  		enrRecord := &enr.Record{}
   391  		err := enrRecord.SetSig(dummyIdentity{1}, []byte{42})
   392  		require.NoError(t, err)
   393  		enrRecord.Set(enr.IPv4{127, 0, 0, byte(i)})
   394  		err = enrRecord.SetSig(dummyIdentity{}, []byte{})
   395  		require.NoError(t, err)
   396  		var p2pAddr = "/ip4/127.0.0." + strconv.Itoa(i) + "/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N"
   397  		p2pMultiAddr, err := ma.NewMultiaddr(p2pAddr)
   398  		require.NoError(t, err)
   399  
   400  		var direction network.Direction
   401  		if i%2 == 0 {
   402  			direction = network.DirInbound
   403  		} else {
   404  			direction = network.DirOutbound
   405  		}
   406  		peerStatus.Add(enrRecord, id, p2pMultiAddr, direction)
   407  
   408  		switch i {
   409  		case 0:
   410  			peerStatus.SetConnectionState(id, peers.PeerConnecting)
   411  		case 1, 2:
   412  			peerStatus.SetConnectionState(id, peers.PeerConnected)
   413  		case 3, 4, 5:
   414  			peerStatus.SetConnectionState(id, peers.PeerDisconnecting)
   415  		case 6, 7, 8, 9:
   416  			peerStatus.SetConnectionState(id, peers.PeerDisconnected)
   417  		default:
   418  			t.Fatalf("Failed to set connection state for peer")
   419  		}
   420  	}
   421  
   422  	s := Server{PeersFetcher: peerFetcher}
   423  	resp, err := s.PeerCount(context.Background(), &emptypb.Empty{})
   424  	require.NoError(t, err)
   425  	assert.Equal(t, uint64(1), resp.Data.Connecting, "Wrong number of connecting peers")
   426  	assert.Equal(t, uint64(2), resp.Data.Connected, "Wrong number of connected peers")
   427  	assert.Equal(t, uint64(3), resp.Data.Disconnecting, "Wrong number of disconnecting peers")
   428  	assert.Equal(t, uint64(4), resp.Data.Disconnected, "Wrong number of disconnected peers")
   429  }
   430  
   431  func BenchmarkListPeers(b *testing.B) {
   432  	// We simulate having a lot of peers.
   433  	ids := libp2ptest.GeneratePeerIDs(2000)
   434  	peerFetcher := &mockp2p.MockPeersProvider{}
   435  
   436  	for _, id := range ids {
   437  		enrRecord := &enr.Record{}
   438  		err := enrRecord.SetSig(dummyIdentity{1}, []byte{42})
   439  		require.NoError(b, err)
   440  		enrRecord.Set(enr.IPv4{7, 7, 7, 7})
   441  		err = enrRecord.SetSig(dummyIdentity{}, []byte{})
   442  		require.NoError(b, err)
   443  		const p2pAddr = "/ip4/7.7.7.7/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N"
   444  		p2pMultiAddr, err := ma.NewMultiaddr(p2pAddr)
   445  		require.NoError(b, err)
   446  		peerFetcher.Peers().Add(enrRecord, id, p2pMultiAddr, network.DirInbound)
   447  	}
   448  
   449  	s := Server{PeersFetcher: peerFetcher}
   450  
   451  	b.ResetTimer()
   452  	for i := 0; i < b.N; i++ {
   453  		_, err := s.ListPeers(context.Background(), &ethpb.PeersRequest{
   454  			State:     []ethpb.ConnectionState{},
   455  			Direction: []ethpb.PeerDirection{},
   456  		})
   457  		require.NoError(b, err)
   458  	}
   459  }