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 }