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

     1  package p2p
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/libp2p/go-libp2p-core/peer"
    11  	pubsubpb "github.com/libp2p/go-libp2p-pubsub/pb"
    12  	mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
    13  	"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
    14  	statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
    15  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/encoder"
    16  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    17  	"github.com/prysmaticlabs/prysm/shared/params"
    18  	"github.com/prysmaticlabs/prysm/shared/timeutils"
    19  	"github.com/stretchr/testify/require"
    20  )
    21  
    22  func TestService_CanSubscribe(t *testing.T) {
    23  	currentFork := [4]byte{0x01, 0x02, 0x03, 0x04}
    24  	validProtocolSuffix := "/" + encoder.ProtocolSuffixSSZSnappy
    25  	type test struct {
    26  		name  string
    27  		topic string
    28  		want  bool
    29  	}
    30  	tests := []test{
    31  		{
    32  			name:  "block topic on current fork",
    33  			topic: fmt.Sprintf(BlockSubnetTopicFormat, currentFork) + validProtocolSuffix,
    34  			want:  true,
    35  		},
    36  		{
    37  			name:  "block topic on unknown fork",
    38  			topic: fmt.Sprintf(BlockSubnetTopicFormat, [4]byte{0xFF, 0xEE, 0x56, 0x21}) + validProtocolSuffix,
    39  			want:  false,
    40  		},
    41  		{
    42  			name:  "block topic missing protocol suffix",
    43  			topic: fmt.Sprintf(BlockSubnetTopicFormat, currentFork),
    44  			want:  false,
    45  		},
    46  		{
    47  			name:  "block topic wrong protocol suffix",
    48  			topic: fmt.Sprintf(BlockSubnetTopicFormat, currentFork) + "/foobar",
    49  			want:  false,
    50  		},
    51  		{
    52  			name:  "erroneous topic",
    53  			topic: "hey, want to foobar?",
    54  			want:  false,
    55  		},
    56  		{
    57  			name:  "erroneous topic that has the correct amount of slashes",
    58  			topic: "hey, want to foobar?////",
    59  			want:  false,
    60  		},
    61  		{
    62  			name:  "bad prefix",
    63  			topic: fmt.Sprintf("/eth3/%x/foobar", currentFork) + validProtocolSuffix,
    64  			want:  false,
    65  		},
    66  		{
    67  			name:  "topic not in gossip mapping",
    68  			topic: fmt.Sprintf("/eth2/%x/foobar", currentFork) + validProtocolSuffix,
    69  			want:  false,
    70  		},
    71  		{
    72  			name:  "att subnet topic on current fork",
    73  			topic: fmt.Sprintf(AttestationSubnetTopicFormat, currentFork, 55 /*subnet*/) + validProtocolSuffix,
    74  			want:  true,
    75  		},
    76  		{
    77  			name:  "att subnet topic on unknown fork",
    78  			topic: fmt.Sprintf(AttestationSubnetTopicFormat, [4]byte{0xCC, 0xBB, 0xAA, 0xA1} /*fork digest*/, 54 /*subnet*/) + validProtocolSuffix,
    79  			want:  false,
    80  		},
    81  	}
    82  
    83  	// Ensure all gossip topic mappings pass validation.
    84  	for topic := range GossipTopicMappings {
    85  		formatting := []interface{}{currentFork}
    86  
    87  		// Special case for attestation subnets which have a second formatting placeholder.
    88  		if topic == AttestationSubnetTopicFormat {
    89  			formatting = append(formatting, 0 /* some subnet ID */)
    90  		}
    91  
    92  		tt := test{
    93  			name:  topic,
    94  			topic: fmt.Sprintf(topic, formatting...) + validProtocolSuffix,
    95  			want:  true,
    96  		}
    97  		tests = append(tests, tt)
    98  	}
    99  	for _, tt := range tests {
   100  		t.Run(tt.name, func(t *testing.T) {
   101  			s := &Service{
   102  				currentForkDigest:     currentFork,
   103  				genesisValidatorsRoot: make([]byte, 32),
   104  				genesisTime:           time.Now(),
   105  			}
   106  			if got := s.CanSubscribe(tt.topic); got != tt.want {
   107  				t.Errorf("CanSubscribe(%s) = %v, want %v", tt.topic, got, tt.want)
   108  			}
   109  		})
   110  	}
   111  }
   112  
   113  func TestService_CanSubscribe_uninitialized(t *testing.T) {
   114  	s := &Service{}
   115  	require.False(t, s.CanSubscribe("foo"))
   116  }
   117  
   118  func Test_scanfcheck(t *testing.T) {
   119  	type args struct {
   120  		input  string
   121  		format string
   122  	}
   123  	tests := []struct {
   124  		name    string
   125  		args    args
   126  		want    int
   127  		wantErr bool
   128  	}{
   129  		{
   130  			name: "no formatting, exact match",
   131  			args: args{
   132  				input:  "/foo/bar/zzzzzzzzzzzz/1234567",
   133  				format: "/foo/bar/zzzzzzzzzzzz/1234567",
   134  			},
   135  			want:    0,
   136  			wantErr: false,
   137  		},
   138  		{
   139  			name: "no formatting, mismatch",
   140  			args: args{
   141  				input:  "/foo/bar/zzzzzzzzzzzz/1234567",
   142  				format: "/bar/foo/yyyyyy/7654321",
   143  			},
   144  			want:    0,
   145  			wantErr: true,
   146  		},
   147  		{
   148  			name: "formatting, match",
   149  			args: args{
   150  				input:  "/foo/bar/abcdef/topic_11",
   151  				format: "/foo/bar/%x/topic_%d",
   152  			},
   153  			want:    2,
   154  			wantErr: false,
   155  		},
   156  		{
   157  			name: "formatting, incompatible bytes",
   158  			args: args{
   159  				input:  "/foo/bar/zzzzzz/topic_11",
   160  				format: "/foo/bar/%x/topic_%d",
   161  			},
   162  			want:    0,
   163  			wantErr: true,
   164  		},
   165  		{ // Note: This method only supports integer compatible formatting values.
   166  			name: "formatting, string match",
   167  			args: args{
   168  				input:  "/foo/bar/zzzzzz/topic_11",
   169  				format: "/foo/bar/%s/topic_%d",
   170  			},
   171  			want:    0,
   172  			wantErr: true,
   173  		},
   174  	}
   175  	for _, tt := range tests {
   176  		t.Run(tt.name, func(t *testing.T) {
   177  			got, err := scanfcheck(tt.args.input, tt.args.format)
   178  			if (err != nil) != tt.wantErr {
   179  				t.Errorf("scanfcheck() error = %v, wantErr %v", err, tt.wantErr)
   180  				return
   181  			}
   182  			if got != tt.want {
   183  				t.Errorf("scanfcheck() got = %v, want %v", got, tt.want)
   184  			}
   185  		})
   186  	}
   187  }
   188  
   189  func TestGossipTopicMapping_scanfcheck_GossipTopicFormattingSanityCheck(t *testing.T) {
   190  	// scanfcheck only supports integer based substitutions at the moment. Any others will
   191  	// inaccurately fail validation.
   192  	for topic := range GossipTopicMappings {
   193  		t.Run(topic, func(t *testing.T) {
   194  			for i, c := range topic {
   195  				if string(c) == "%" {
   196  					next := string(topic[i+1])
   197  					if next != "d" && next != "x" {
   198  						t.Errorf("Topic %s has formatting incompatiable with scanfcheck. Only %%d and %%x are supported", topic)
   199  					}
   200  				}
   201  			}
   202  		})
   203  	}
   204  }
   205  
   206  func TestService_FilterIncomingSubscriptions(t *testing.T) {
   207  	currentFork := [4]byte{0x01, 0x02, 0x03, 0x04}
   208  	validProtocolSuffix := "/" + encoder.ProtocolSuffixSSZSnappy
   209  	type args struct {
   210  		id   peer.ID
   211  		subs []*pubsubpb.RPC_SubOpts
   212  	}
   213  	tests := []struct {
   214  		name    string
   215  		args    args
   216  		want    []*pubsubpb.RPC_SubOpts
   217  		wantErr bool
   218  	}{
   219  		{
   220  			name: "too many topics",
   221  			args: args{
   222  				subs: make([]*pubsubpb.RPC_SubOpts, pubsubSubscriptionRequestLimit+1),
   223  			},
   224  			wantErr: true,
   225  		},
   226  		{
   227  			name: "exactly topic limit",
   228  			args: args{
   229  				subs: make([]*pubsubpb.RPC_SubOpts, pubsubSubscriptionRequestLimit),
   230  			},
   231  			wantErr: false,
   232  			want:    nil, // No topics matched filters.
   233  		},
   234  		{
   235  			name: "blocks topic",
   236  			args: args{
   237  				subs: []*pubsubpb.RPC_SubOpts{
   238  					{
   239  						Subscribe: func() *bool {
   240  							b := true
   241  							return &b
   242  						}(),
   243  						Topicid: func() *string {
   244  							s := fmt.Sprintf(BlockSubnetTopicFormat, currentFork) + validProtocolSuffix
   245  							return &s
   246  						}(),
   247  					},
   248  				},
   249  			},
   250  			wantErr: false,
   251  			want: []*pubsubpb.RPC_SubOpts{
   252  				{
   253  					Subscribe: func() *bool {
   254  						b := true
   255  						return &b
   256  					}(),
   257  					Topicid: func() *string {
   258  						s := fmt.Sprintf(BlockSubnetTopicFormat, currentFork) + validProtocolSuffix
   259  						return &s
   260  					}(),
   261  				},
   262  			},
   263  		},
   264  		{
   265  			name: "blocks topic duplicated",
   266  			args: args{
   267  				subs: []*pubsubpb.RPC_SubOpts{
   268  					{
   269  						Subscribe: func() *bool {
   270  							b := true
   271  							return &b
   272  						}(),
   273  						Topicid: func() *string {
   274  							s := fmt.Sprintf(BlockSubnetTopicFormat, currentFork) + validProtocolSuffix
   275  							return &s
   276  						}(),
   277  					},
   278  					{
   279  						Subscribe: func() *bool {
   280  							b := true
   281  							return &b
   282  						}(),
   283  						Topicid: func() *string {
   284  							s := fmt.Sprintf(BlockSubnetTopicFormat, currentFork) + validProtocolSuffix
   285  							return &s
   286  						}(),
   287  					},
   288  				},
   289  			},
   290  			wantErr: false,
   291  			want: []*pubsubpb.RPC_SubOpts{ // Duplicated topics are only present once after filtering.
   292  				{
   293  					Subscribe: func() *bool {
   294  						b := true
   295  						return &b
   296  					}(),
   297  					Topicid: func() *string {
   298  						s := fmt.Sprintf(BlockSubnetTopicFormat, currentFork) + validProtocolSuffix
   299  						return &s
   300  					}(),
   301  				},
   302  			},
   303  		},
   304  	}
   305  	for _, tt := range tests {
   306  		t.Run(tt.name, func(t *testing.T) {
   307  			s := &Service{
   308  				currentForkDigest:     currentFork,
   309  				genesisValidatorsRoot: make([]byte, 32),
   310  				genesisTime:           time.Now(),
   311  			}
   312  			got, err := s.FilterIncomingSubscriptions(tt.args.id, tt.args.subs)
   313  			if (err != nil) != tt.wantErr {
   314  				t.Errorf("FilterIncomingSubscriptions() error = %v, wantErr %v", err, tt.wantErr)
   315  				return
   316  			}
   317  			if !reflect.DeepEqual(got, tt.want) {
   318  				t.Errorf("FilterIncomingSubscriptions() got = %v, want %v", got, tt.want)
   319  			}
   320  		})
   321  	}
   322  }
   323  
   324  func TestService_MonitorsStateForkUpdates(t *testing.T) {
   325  	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
   326  	defer cancel()
   327  	notifier := &mock.MockStateNotifier{}
   328  	s, err := NewService(ctx, &Config{
   329  		StateNotifier: notifier,
   330  	})
   331  	require.NoError(t, err)
   332  
   333  	require.False(t, s.isInitialized())
   334  
   335  	go s.awaitStateInitialized()
   336  
   337  	for n := 0; n == 0; {
   338  		if ctx.Err() != nil {
   339  			t.Fatal(ctx.Err())
   340  		}
   341  		n = notifier.StateFeed().Send(&feed.Event{
   342  			Type: statefeed.Initialized,
   343  			Data: &statefeed.InitializedData{
   344  				StartTime:             timeutils.Now(),
   345  				GenesisValidatorsRoot: bytesutil.PadTo([]byte("genesis"), 32),
   346  			},
   347  		})
   348  	}
   349  
   350  	time.Sleep(50 * time.Millisecond)
   351  
   352  	require.True(t, s.isInitialized())
   353  	require.NotEmpty(t, s.currentForkDigest)
   354  }
   355  
   356  func TestService_doesntSupportForksYet(t *testing.T) {
   357  	// Part of phase 1 will include a state transition which updates the state's fork. In phase 0,
   358  	// there are no forks or fork schedule planned. As such, we'll work on supporting fork upgrades
   359  	// in phase 1 changes.
   360  	if len(params.BeaconConfig().ForkVersionSchedule) > 0 {
   361  		t.Fatal("pubsub subscription filters do not support fork schedule (yet)")
   362  	}
   363  }