github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/sync/subscriber_test.go (about) 1 package sync 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "sync" 8 "testing" 9 "time" 10 11 lru "github.com/hashicorp/golang-lru" 12 "github.com/libp2p/go-libp2p-core/peer" 13 pubsub "github.com/libp2p/go-libp2p-pubsub" 14 pubsubpb "github.com/libp2p/go-libp2p-pubsub/pb" 15 types "github.com/prysmaticlabs/eth2-types" 16 mockChain "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing" 17 "github.com/prysmaticlabs/prysm/beacon-chain/cache" 18 db "github.com/prysmaticlabs/prysm/beacon-chain/db/testing" 19 "github.com/prysmaticlabs/prysm/beacon-chain/operations/slashings" 20 "github.com/prysmaticlabs/prysm/beacon-chain/p2p" 21 p2ptest "github.com/prysmaticlabs/prysm/beacon-chain/p2p/testing" 22 mockSync "github.com/prysmaticlabs/prysm/beacon-chain/sync/initial-sync/testing" 23 pb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 24 "github.com/prysmaticlabs/prysm/shared/abool" 25 "github.com/prysmaticlabs/prysm/shared/bytesutil" 26 "github.com/prysmaticlabs/prysm/shared/params" 27 "github.com/prysmaticlabs/prysm/shared/testutil" 28 "github.com/prysmaticlabs/prysm/shared/testutil/assert" 29 "github.com/prysmaticlabs/prysm/shared/testutil/require" 30 logTest "github.com/sirupsen/logrus/hooks/test" 31 "google.golang.org/protobuf/proto" 32 ) 33 34 func TestSubscribe_ReceivesValidMessage(t *testing.T) { 35 p2pService := p2ptest.NewTestP2P(t) 36 r := Service{ 37 ctx: context.Background(), 38 cfg: &Config{ 39 P2P: p2pService, 40 InitialSync: &mockSync.Sync{IsSyncing: false}, 41 Chain: &mockChain.ChainService{ 42 ValidatorsRoot: [32]byte{'A'}, 43 Genesis: time.Now(), 44 }, 45 }, 46 chainStarted: abool.New(), 47 } 48 var err error 49 p2pService.Digest, err = r.forkDigest() 50 require.NoError(t, err) 51 topic := "/eth2/%x/voluntary_exit" 52 var wg sync.WaitGroup 53 wg.Add(1) 54 55 r.subscribe(topic, r.noopValidator, func(_ context.Context, msg proto.Message) error { 56 m, ok := msg.(*pb.SignedVoluntaryExit) 57 assert.Equal(t, true, ok, "Object is not of type *pb.SignedVoluntaryExit") 58 if m.Exit == nil || m.Exit.Epoch != 55 { 59 t.Errorf("Unexpected incoming message: %+v", m) 60 } 61 wg.Done() 62 return nil 63 }) 64 r.markForChainStart() 65 66 p2pService.ReceivePubSub(topic, &pb.SignedVoluntaryExit{Exit: &pb.VoluntaryExit{Epoch: 55}, Signature: make([]byte, 96)}) 67 68 if testutil.WaitTimeout(&wg, time.Second) { 69 t.Fatal("Did not receive PubSub in 1 second") 70 } 71 } 72 73 func TestSubscribe_ReceivesAttesterSlashing(t *testing.T) { 74 p2pService := p2ptest.NewTestP2P(t) 75 ctx := context.Background() 76 d := db.SetupDB(t) 77 chainService := &mockChain.ChainService{ 78 Genesis: time.Now(), 79 ValidatorsRoot: [32]byte{'A'}, 80 } 81 r := Service{ 82 ctx: ctx, 83 cfg: &Config{ 84 P2P: p2pService, 85 InitialSync: &mockSync.Sync{IsSyncing: false}, 86 SlashingPool: slashings.NewPool(), 87 Chain: chainService, 88 DB: d, 89 }, 90 seenAttesterSlashingCache: make(map[uint64]bool), 91 chainStarted: abool.New(), 92 } 93 topic := "/eth2/%x/attester_slashing" 94 var wg sync.WaitGroup 95 wg.Add(1) 96 params.SetupTestConfigCleanup(t) 97 params.OverrideBeaconConfig(params.MainnetConfig()) 98 r.subscribe(topic, r.noopValidator, func(ctx context.Context, msg proto.Message) error { 99 require.NoError(t, r.attesterSlashingSubscriber(ctx, msg)) 100 wg.Done() 101 return nil 102 }) 103 beaconState, privKeys := testutil.DeterministicGenesisState(t, 64) 104 chainService.State = beaconState 105 r.markForChainStart() 106 attesterSlashing, err := testutil.GenerateAttesterSlashingForValidator( 107 beaconState, 108 privKeys[1], 109 1, /* validator index */ 110 ) 111 require.NoError(t, err, "Error generating attester slashing") 112 err = r.cfg.DB.SaveState(ctx, beaconState, bytesutil.ToBytes32(attesterSlashing.Attestation_1.Data.BeaconBlockRoot)) 113 require.NoError(t, err) 114 p2pService.Digest, err = r.forkDigest() 115 require.NoError(t, err) 116 p2pService.ReceivePubSub(topic, attesterSlashing) 117 118 if testutil.WaitTimeout(&wg, time.Second) { 119 t.Fatal("Did not receive PubSub in 1 second") 120 } 121 as := r.cfg.SlashingPool.PendingAttesterSlashings(ctx, beaconState, false /*noLimit*/) 122 assert.Equal(t, 1, len(as), "Expected attester slashing") 123 } 124 125 func TestSubscribe_ReceivesProposerSlashing(t *testing.T) { 126 p2pService := p2ptest.NewTestP2P(t) 127 ctx := context.Background() 128 chainService := &mockChain.ChainService{ 129 ValidatorsRoot: [32]byte{'A'}, 130 Genesis: time.Now(), 131 } 132 d := db.SetupDB(t) 133 c, err := lru.New(10) 134 require.NoError(t, err) 135 r := Service{ 136 ctx: ctx, 137 cfg: &Config{ 138 P2P: p2pService, 139 InitialSync: &mockSync.Sync{IsSyncing: false}, 140 SlashingPool: slashings.NewPool(), 141 Chain: chainService, 142 DB: d, 143 }, 144 seenProposerSlashingCache: c, 145 chainStarted: abool.New(), 146 } 147 topic := "/eth2/%x/proposer_slashing" 148 var wg sync.WaitGroup 149 wg.Add(1) 150 params.SetupTestConfigCleanup(t) 151 params.OverrideBeaconConfig(params.MainnetConfig()) 152 r.subscribe(topic, r.noopValidator, func(ctx context.Context, msg proto.Message) error { 153 require.NoError(t, r.proposerSlashingSubscriber(ctx, msg)) 154 wg.Done() 155 return nil 156 }) 157 beaconState, privKeys := testutil.DeterministicGenesisState(t, 64) 158 chainService.State = beaconState 159 r.markForChainStart() 160 proposerSlashing, err := testutil.GenerateProposerSlashingForValidator( 161 beaconState, 162 privKeys[1], 163 1, /* validator index */ 164 ) 165 require.NoError(t, err, "Error generating proposer slashing") 166 p2pService.Digest, err = r.forkDigest() 167 require.NoError(t, err) 168 p2pService.ReceivePubSub(topic, proposerSlashing) 169 170 if testutil.WaitTimeout(&wg, time.Second) { 171 t.Fatal("Did not receive PubSub in 1 second") 172 } 173 ps := r.cfg.SlashingPool.PendingProposerSlashings(ctx, beaconState, false /*noLimit*/) 174 assert.Equal(t, 1, len(ps), "Expected proposer slashing") 175 } 176 177 func TestSubscribe_HandlesPanic(t *testing.T) { 178 p := p2ptest.NewTestP2P(t) 179 r := Service{ 180 ctx: context.Background(), 181 cfg: &Config{ 182 Chain: &mockChain.ChainService{ 183 Genesis: time.Now(), 184 ValidatorsRoot: [32]byte{'A'}, 185 }, 186 P2P: p, 187 }, 188 chainStarted: abool.New(), 189 } 190 var err error 191 p.Digest, err = r.forkDigest() 192 require.NoError(t, err) 193 194 topic := p2p.GossipTypeMapping[reflect.TypeOf(&pb.SignedVoluntaryExit{})] 195 var wg sync.WaitGroup 196 wg.Add(1) 197 198 r.subscribe(topic, r.noopValidator, func(_ context.Context, msg proto.Message) error { 199 defer wg.Done() 200 panic("bad") 201 }) 202 r.markForChainStart() 203 p.ReceivePubSub(topic, &pb.SignedVoluntaryExit{Exit: &pb.VoluntaryExit{Epoch: 55}, Signature: make([]byte, 96)}) 204 205 if testutil.WaitTimeout(&wg, time.Second) { 206 t.Fatal("Did not receive PubSub in 1 second") 207 } 208 } 209 210 func TestRevalidateSubscription_CorrectlyFormatsTopic(t *testing.T) { 211 p := p2ptest.NewTestP2P(t) 212 hook := logTest.NewGlobal() 213 r := Service{ 214 ctx: context.Background(), 215 cfg: &Config{ 216 Chain: &mockChain.ChainService{ 217 Genesis: time.Now(), 218 ValidatorsRoot: [32]byte{'A'}, 219 }, 220 P2P: p, 221 }, 222 chainStarted: abool.New(), 223 } 224 digest, err := r.forkDigest() 225 require.NoError(t, err) 226 subscriptions := make(map[uint64]*pubsub.Subscription, params.BeaconConfig().MaxCommitteesPerSlot) 227 228 defaultTopic := "/eth2/testing/%#x/committee%d" 229 // committee index 1 230 fullTopic := fmt.Sprintf(defaultTopic, digest, 1) + r.cfg.P2P.Encoding().ProtocolSuffix() 231 require.NoError(t, r.cfg.P2P.PubSub().RegisterTopicValidator(fullTopic, r.noopValidator)) 232 subscriptions[1], err = r.cfg.P2P.SubscribeToTopic(fullTopic) 233 require.NoError(t, err) 234 235 // committee index 2 236 fullTopic = fmt.Sprintf(defaultTopic, digest, 2) + r.cfg.P2P.Encoding().ProtocolSuffix() 237 err = r.cfg.P2P.PubSub().RegisterTopicValidator(fullTopic, r.noopValidator) 238 require.NoError(t, err) 239 subscriptions[2], err = r.cfg.P2P.SubscribeToTopic(fullTopic) 240 require.NoError(t, err) 241 242 r.reValidateSubscriptions(subscriptions, []uint64{2}, defaultTopic, digest) 243 require.LogsDoNotContain(t, hook, "Could not unregister topic validator") 244 } 245 246 func TestStaticSubnets(t *testing.T) { 247 p := p2ptest.NewTestP2P(t) 248 ctx, cancel := context.WithCancel(context.Background()) 249 r := Service{ 250 ctx: ctx, 251 cfg: &Config{ 252 Chain: &mockChain.ChainService{ 253 Genesis: time.Now(), 254 ValidatorsRoot: [32]byte{'A'}, 255 }, 256 P2P: p, 257 }, 258 chainStarted: abool.New(), 259 } 260 defaultTopic := "/eth2/%x/beacon_attestation_%d" 261 r.subscribeStaticWithSubnets(defaultTopic, r.noopValidator, func(_ context.Context, msg proto.Message) error { 262 // no-op 263 return nil 264 }) 265 topics := r.cfg.P2P.PubSub().GetTopics() 266 if uint64(len(topics)) != params.BeaconNetworkConfig().AttestationSubnetCount { 267 t.Errorf("Wanted the number of subnet topics registered to be %d but got %d", params.BeaconNetworkConfig().AttestationSubnetCount, len(topics)) 268 } 269 cancel() 270 } 271 272 func Test_wrapAndReportValidation(t *testing.T) { 273 type args struct { 274 topic string 275 v pubsub.ValidatorEx 276 chainstarted bool 277 pid peer.ID 278 msg *pubsub.Message 279 } 280 tests := []struct { 281 name string 282 args args 283 want pubsub.ValidationResult 284 }{ 285 { 286 name: "validator Before chainstart", 287 args: args{ 288 topic: "foo", 289 v: func(ctx context.Context, id peer.ID, message *pubsub.Message) pubsub.ValidationResult { 290 return pubsub.ValidationAccept 291 }, 292 msg: &pubsub.Message{ 293 Message: &pubsubpb.Message{ 294 Topic: func() *string { 295 s := "foo" 296 return &s 297 }(), 298 }, 299 }, 300 chainstarted: false, 301 }, 302 want: pubsub.ValidationIgnore, 303 }, 304 { 305 name: "validator panicked", 306 args: args{ 307 topic: "foo", 308 v: func(ctx context.Context, id peer.ID, message *pubsub.Message) pubsub.ValidationResult { 309 panic("oh no!") 310 }, 311 chainstarted: true, 312 msg: &pubsub.Message{ 313 Message: &pubsubpb.Message{ 314 Topic: func() *string { 315 s := "foo" 316 return &s 317 }(), 318 }, 319 }, 320 }, 321 want: pubsub.ValidationIgnore, 322 }, 323 { 324 name: "validator OK", 325 args: args{ 326 topic: "foo", 327 v: func(ctx context.Context, id peer.ID, message *pubsub.Message) pubsub.ValidationResult { 328 return pubsub.ValidationAccept 329 }, 330 chainstarted: true, 331 msg: &pubsub.Message{ 332 Message: &pubsubpb.Message{ 333 Topic: func() *string { 334 s := "foo" 335 return &s 336 }(), 337 }, 338 }, 339 }, 340 want: pubsub.ValidationAccept, 341 }, 342 { 343 name: "nil topic", 344 args: args{ 345 topic: "foo", 346 v: func(ctx context.Context, id peer.ID, message *pubsub.Message) pubsub.ValidationResult { 347 return pubsub.ValidationAccept 348 }, 349 msg: &pubsub.Message{ 350 Message: &pubsubpb.Message{ 351 Topic: nil, 352 }, 353 }, 354 }, 355 want: pubsub.ValidationReject, 356 }, 357 } 358 for _, tt := range tests { 359 t.Run(tt.name, func(t *testing.T) { 360 chainStarted := abool.New() 361 chainStarted.SetTo(tt.args.chainstarted) 362 s := &Service{ 363 chainStarted: chainStarted, 364 } 365 _, v := s.wrapAndReportValidation(tt.args.topic, tt.args.v) 366 got := v(context.Background(), tt.args.pid, tt.args.msg) 367 if got != tt.want { 368 t.Errorf("wrapAndReportValidation() got = %v, want %v", got, tt.want) 369 } 370 }) 371 } 372 } 373 374 func TestFilterSubnetPeers(t *testing.T) { 375 p := p2ptest.NewTestP2P(t) 376 ctx, cancel := context.WithCancel(context.Background()) 377 currSlot := types.Slot(100) 378 r := Service{ 379 ctx: ctx, 380 cfg: &Config{ 381 Chain: &mockChain.ChainService{ 382 Genesis: time.Now(), 383 ValidatorsRoot: [32]byte{'A'}, 384 Slot: &currSlot, 385 }, 386 P2P: p, 387 }, 388 chainStarted: abool.New(), 389 } 390 // Empty cache at the end of the test. 391 defer cache.SubnetIDs.EmptyAllCaches() 392 393 defaultTopic := "/eth2/%x/beacon_attestation_%d" + r.cfg.P2P.Encoding().ProtocolSuffix() 394 subnet10 := r.addDigestAndIndexToTopic(defaultTopic, 10) 395 cache.SubnetIDs.AddAggregatorSubnetID(currSlot, 10) 396 397 subnet20 := r.addDigestAndIndexToTopic(defaultTopic, 20) 398 cache.SubnetIDs.AddAttesterSubnetID(currSlot, 20) 399 400 p1 := createPeer(t, subnet10) 401 p2 := createPeer(t, subnet10, subnet20) 402 p3 := createPeer(t) 403 404 // Connect to all 405 // peers. 406 p.Connect(p1) 407 p.Connect(p2) 408 p.Connect(p3) 409 410 // Sleep a while to allow peers to connect. 411 time.Sleep(100 * time.Millisecond) 412 413 wantedPeers := []peer.ID{p1.PeerID(), p2.PeerID(), p3.PeerID()} 414 // Expect Peer 3 to be marked as suitable. 415 recPeers := r.filterNeededPeers(wantedPeers) 416 assert.DeepEqual(t, []peer.ID{p3.PeerID()}, recPeers) 417 418 // Try with only peers from subnet 20. 419 wantedPeers = []peer.ID{p2.BHost.ID()} 420 // Connect an excess amount of peers in the particular subnet. 421 for i := uint64(1); i <= params.BeaconNetworkConfig().MinimumPeersInSubnet; i++ { 422 nPeer := createPeer(t, subnet20) 423 p.Connect(nPeer) 424 wantedPeers = append(wantedPeers, nPeer.BHost.ID()) 425 time.Sleep(100 * time.Millisecond) 426 } 427 428 recPeers = r.filterNeededPeers(wantedPeers) 429 assert.DeepEqual(t, 1, len(recPeers), "expected at least 1 suitable peer to prune") 430 431 cancel() 432 } 433 434 // Create peer and register them to provided topics. 435 func createPeer(t *testing.T, topics ...string) *p2ptest.TestP2P { 436 p := p2ptest.NewTestP2P(t) 437 for _, tp := range topics { 438 jTop, err := p.PubSub().Join(tp) 439 if err != nil { 440 t.Fatal(err) 441 } 442 _, err = jTop.Subscribe() 443 if err != nil { 444 t.Fatal(err) 445 } 446 } 447 return p 448 }