github.com/koko1123/flow-go-1@v0.29.6/network/p2p/test/topic_validator_test.go (about) 1 package p2ptest_test 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/koko1123/flow-go-1/network/p2p" 11 p2ptest "github.com/koko1123/flow-go-1/network/p2p/test" 12 13 "github.com/libp2p/go-libp2p/core/peer" 14 "github.com/stretchr/testify/require" 15 16 "github.com/koko1123/flow-go-1/network/p2p/utils" 17 18 "github.com/koko1123/flow-go-1/network/p2p/translator" 19 20 "github.com/koko1123/flow-go-1/model/flow" 21 "github.com/koko1123/flow-go-1/model/messages" 22 "github.com/koko1123/flow-go-1/module/irrecoverable" 23 "github.com/koko1123/flow-go-1/module/metrics" 24 "github.com/koko1123/flow-go-1/network/channels" 25 "github.com/koko1123/flow-go-1/network/internal/p2pfixtures" 26 "github.com/koko1123/flow-go-1/network/message" 27 "github.com/koko1123/flow-go-1/network/slashing" 28 "github.com/koko1123/flow-go-1/network/validator" 29 flowpubsub "github.com/koko1123/flow-go-1/network/validator/pubsub" 30 "github.com/koko1123/flow-go-1/utils/unittest" 31 ) 32 33 // TestTopicValidator_Unstaked tests that the libP2P node topic validator rejects unauthenticated messages on non-public channels (unstaked) 34 func TestTopicValidator_Unstaked(t *testing.T) { 35 ctx, cancel := context.WithCancel(context.Background()) 36 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 37 38 // create a hooked logger 39 logger, hook := unittest.HookedLogger() 40 41 sporkId := unittest.IdentifierFixture() 42 43 sn1, identity1 := p2ptest.NodeFixture(t, sporkId, t.Name(), p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 44 sn2, identity2 := p2ptest.NodeFixture(t, sporkId, t.Name(), p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 45 46 nodes := []p2p.LibP2PNode{sn1, sn2} 47 p2ptest.StartNodes(t, signalerCtx, nodes, 100*time.Millisecond) 48 defer p2ptest.StopNodes(t, nodes, cancel, 100*time.Millisecond) 49 50 channel := channels.ConsensusCommittee 51 topic := channels.TopicFromChannel(channel, sporkId) 52 53 //NOTE: identity2 is not in the ids list simulating an un-staked node 54 ids := flow.IdentityList{&identity1} 55 translator, err := translator.NewFixedTableIdentityTranslator(ids) 56 require.NoError(t, err) 57 58 // peer filter used by the topic validator to check if node is staked 59 isStaked := func(pid peer.ID) error { 60 fid, err := translator.GetFlowID(pid) 61 if err != nil { 62 return fmt.Errorf("could not translate the peer_id %s to a Flow identifier: %w", pid.String(), err) 63 } 64 65 if _, ok := ids.ByNodeID(fid); !ok { 66 return fmt.Errorf("flow id not found: %x", fid) 67 } 68 69 return nil 70 } 71 72 pInfo2, err := utils.PeerAddressInfo(identity2) 73 require.NoError(t, err) 74 75 // node1 is connected to node2 76 // sn1 <-> sn2 77 require.NoError(t, sn1.AddPeer(ctx, pInfo2)) 78 79 slashingViolationsConsumer := unittest.NetworkSlashingViolationsConsumer(logger, metrics.NewNoopCollector()) 80 81 // sn1 will subscribe with is staked callback that should force the TopicValidator to drop the message received from sn2 82 sub1, err := sn1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, isStaked)) 83 require.NoError(t, err) 84 85 // sn2 will subscribe with an unauthenticated callback to allow it to send the unauthenticated message 86 _, err = sn2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter())) 87 require.NoError(t, err) 88 89 // let nodes form the mesh 90 time.Sleep(time.Second) 91 92 timedCtx, cancel5s := context.WithTimeout(ctx, 5*time.Second) 93 defer cancel5s() 94 // create a dummy block proposal to publish from our SN node 95 data1 := p2pfixtures.MustEncodeEvent(t, unittest.ProposalFixture(), channel) 96 97 err = sn2.Publish(timedCtx, topic, data1) 98 require.NoError(t, err) 99 100 // sn1 should not receive message from sn2 because sn2 is unstaked 101 timedCtx, cancel1s := context.WithTimeout(ctx, time.Second) 102 defer cancel1s() 103 p2pfixtures.SubMustNeverReceiveAnyMessage(t, timedCtx, sub1) 104 105 // ensure the correct error is contained in the logged error 106 require.Contains(t, hook.Logs(), "filtering message from un-allowed peer") 107 } 108 109 // TestTopicValidator_PublicChannel tests that the libP2P node topic validator does not reject unauthenticated messages on public channels 110 func TestTopicValidator_PublicChannel(t *testing.T) { 111 ctx, cancel := context.WithCancel(context.Background()) 112 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 113 114 sporkId := unittest.IdentifierFixture() 115 logger := unittest.Logger() 116 117 sn1, _ := p2ptest.NodeFixture(t, sporkId, t.Name(), p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 118 sn2, identity2 := p2ptest.NodeFixture(t, sporkId, t.Name(), p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 119 120 nodes := []p2p.LibP2PNode{sn1, sn2} 121 p2ptest.StartNodes(t, signalerCtx, nodes, 100*time.Millisecond) 122 defer p2ptest.StopNodes(t, nodes, cancel, 100*time.Millisecond) 123 124 // unauthenticated messages should not be dropped on public channels 125 channel := channels.PublicSyncCommittee 126 topic := channels.TopicFromChannel(channel, sporkId) 127 128 pInfo2, err := utils.PeerAddressInfo(identity2) 129 require.NoError(t, err) 130 131 // node1 is connected to node2 132 // sn1 <-> sn2 133 require.NoError(t, sn1.AddPeer(ctx, pInfo2)) 134 135 slashingViolationsConsumer := unittest.NetworkSlashingViolationsConsumer(logger, metrics.NewNoopCollector()) 136 // sn1 & sn2 will subscribe with unauthenticated callback to allow it to send and receive unauthenticated messages 137 sub1, err := sn1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter())) 138 require.NoError(t, err) 139 sub2, err := sn2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter())) 140 require.NoError(t, err) 141 142 // let nodes form the mesh 143 time.Sleep(time.Second) 144 145 timedCtx, cancel5s := context.WithTimeout(ctx, 5*time.Second) 146 defer cancel5s() 147 // create a dummy sync request to publish from our SN node 148 data1 := p2pfixtures.MustEncodeEvent(t, &messages.SyncRequest{Nonce: 0, Height: 0}, channel) 149 150 err = sn2.Publish(timedCtx, topic, data1) 151 require.NoError(t, err) 152 153 var wg sync.WaitGroup 154 155 // sn1 should receive message from sn2 because the public channel is unauthenticated 156 timedCtx, cancel1s := context.WithTimeout(ctx, time.Second) 157 defer cancel1s() 158 159 // sn1 gets the message 160 p2pfixtures.SubMustReceiveMessage(t, timedCtx, data1, sub1) 161 162 // sn2 also gets the message (as part of the libp2p loopback of published topic messages) 163 p2pfixtures.SubMustReceiveMessage(t, timedCtx, data1, sub2) 164 165 unittest.RequireReturnsBefore(t, wg.Wait, 5*time.Second, "could not receive message on time") 166 } 167 168 // TestTopicValidator_TopicMismatch tests that the libP2P node topic validator rejects messages with mismatched topics 169 func TestTopicValidator_TopicMismatch(t *testing.T) { 170 ctx, cancel := context.WithCancel(context.Background()) 171 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 172 173 // create a hooked logger 174 logger, hook := unittest.HookedLogger() 175 176 sporkId := unittest.IdentifierFixture() 177 178 sn1, _ := p2ptest.NodeFixture(t, sporkId, t.Name(), p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 179 sn2, identity2 := p2ptest.NodeFixture(t, sporkId, t.Name(), p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 180 181 nodes := []p2p.LibP2PNode{sn1, sn2} 182 p2ptest.StartNodes(t, signalerCtx, nodes, 100*time.Millisecond) 183 defer p2ptest.StopNodes(t, nodes, cancel, 100*time.Millisecond) 184 185 channel := channels.ConsensusCommittee 186 topic := channels.TopicFromChannel(channel, sporkId) 187 188 pInfo2, err := utils.PeerAddressInfo(identity2) 189 require.NoError(t, err) 190 191 // node1 is connected to node2 192 // sn1 <-> sn2 193 require.NoError(t, sn1.AddPeer(ctx, pInfo2)) 194 195 slashingViolationsConsumer := unittest.NetworkSlashingViolationsConsumer(logger, metrics.NewNoopCollector()) 196 197 // sn2 will subscribe with an unauthenticated callback to allow processing of message after the authorization check 198 _, err = sn1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter())) 199 require.NoError(t, err) 200 201 // sn2 will subscribe with an unauthenticated callback to allow it to send the unauthenticated message 202 _, err = sn2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter())) 203 require.NoError(t, err) 204 205 // let nodes form the mesh 206 time.Sleep(time.Second) 207 208 timedCtx, cancel5s := context.WithTimeout(ctx, 5*time.Second) 209 defer cancel5s() 210 // create a dummy block proposal to publish from our SN node 211 data1 := p2pfixtures.MustEncodeEvent(t, unittest.ProposalFixture(), channels.Channel("invalid-channel")) 212 213 err = sn2.Publish(timedCtx, topic, data1) 214 215 // publish fails because the channel validation fails 216 require.Error(t, err) 217 218 // ensure the correct error is contained in the logged error 219 require.Contains(t, hook.Logs(), "channel id in message does not match pubsub topic") 220 } 221 222 // TestTopicValidator_InvalidTopic tests that the libP2P node topic validator rejects messages with invalid topics 223 func TestTopicValidator_InvalidTopic(t *testing.T) { 224 ctx, cancel := context.WithCancel(context.Background()) 225 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 226 227 // create a hooked logger 228 logger, hook := unittest.HookedLogger() 229 230 sporkId := unittest.IdentifierFixture() 231 232 sn1, _ := p2ptest.NodeFixture(t, sporkId, t.Name(), p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 233 sn2, identity2 := p2ptest.NodeFixture(t, sporkId, t.Name(), p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 234 235 nodes := []p2p.LibP2PNode{sn1, sn2} 236 p2ptest.StartNodes(t, signalerCtx, nodes, 100*time.Millisecond) 237 defer p2ptest.StopNodes(t, nodes, cancel, 100*time.Millisecond) 238 239 topic := channels.Topic("invalid-topic") 240 241 pInfo2, err := utils.PeerAddressInfo(identity2) 242 require.NoError(t, err) 243 244 // node1 is connected to node2 245 // sn1 <-> sn2 246 require.NoError(t, sn1.AddPeer(ctx, pInfo2)) 247 248 slashingViolationsConsumer := unittest.NetworkSlashingViolationsConsumer(logger, metrics.NewNoopCollector()) 249 250 // sn2 will subscribe with an unauthenticated callback to allow processing of message after the authorization check 251 _, err = sn1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter())) 252 require.NoError(t, err) 253 254 // sn2 will subscribe with an unauthenticated callback to allow it to send the unauthenticated message 255 _, err = sn2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter())) 256 require.NoError(t, err) 257 258 // let nodes form the mesh 259 time.Sleep(time.Second) 260 261 timedCtx, cancel5s := context.WithTimeout(ctx, 5*time.Second) 262 defer cancel5s() 263 // create a dummy block proposal to publish from our SN node 264 data1 := p2pfixtures.MustEncodeEvent(t, unittest.ProposalFixture(), channels.PushBlocks) 265 266 err = sn2.Publish(timedCtx, topic, data1) 267 268 // publish fails because the topic conversion fails 269 require.Error(t, err) 270 271 // ensure the correct error is contained in the logged error 272 require.Contains(t, hook.Logs(), "could not convert topic to channel") 273 } 274 275 // TestAuthorizedSenderValidator_Unauthorized tests that the authorized sender validator rejects messages from nodes that are not authorized to send the message 276 func TestAuthorizedSenderValidator_Unauthorized(t *testing.T) { 277 ctx, cancel := context.WithCancel(context.Background()) 278 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 279 280 // create a hooked logger 281 logger, hook := unittest.HookedLogger() 282 283 sporkId := unittest.IdentifierFixture() 284 285 sn1, identity1 := p2ptest.NodeFixture(t, sporkId, t.Name(), p2ptest.WithRole(flow.RoleConsensus)) 286 sn2, identity2 := p2ptest.NodeFixture(t, sporkId, t.Name(), p2ptest.WithRole(flow.RoleConsensus)) 287 an1, identity3 := p2ptest.NodeFixture(t, sporkId, t.Name(), p2ptest.WithRole(flow.RoleAccess)) 288 289 nodes := []p2p.LibP2PNode{sn1, sn2, an1} 290 p2ptest.StartNodes(t, signalerCtx, nodes, 100*time.Millisecond) 291 defer p2ptest.StopNodes(t, nodes, cancel, 100*time.Millisecond) 292 293 channel := channels.ConsensusCommittee 294 topic := channels.TopicFromChannel(channel, sporkId) 295 296 ids := flow.IdentityList{&identity1, &identity2, &identity3} 297 298 translator, err := translator.NewFixedTableIdentityTranslator(ids) 299 require.NoError(t, err) 300 301 violationsConsumer := slashing.NewSlashingViolationsConsumer(logger, metrics.NewNoopCollector()) 302 getIdentity := func(pid peer.ID) (*flow.Identity, bool) { 303 fid, err := translator.GetFlowID(pid) 304 if err != nil { 305 return &flow.Identity{}, false 306 } 307 308 return ids.ByNodeID(fid) 309 } 310 authorizedSenderValidator := validator.NewAuthorizedSenderValidator(logger, violationsConsumer, getIdentity) 311 pubsubMessageValidator := authorizedSenderValidator.PubSubMessageValidator(channel) 312 313 pInfo1, err := utils.PeerAddressInfo(identity1) 314 require.NoError(t, err) 315 316 pInfo2, err := utils.PeerAddressInfo(identity2) 317 require.NoError(t, err) 318 319 // node1 is connected to node2, and the an1 is connected to node1 320 // an1 <-> sn1 <-> sn2 321 require.NoError(t, sn1.AddPeer(ctx, pInfo2)) 322 require.NoError(t, an1.AddPeer(ctx, pInfo1)) 323 324 slashingViolationsConsumer := unittest.NetworkSlashingViolationsConsumer(logger, metrics.NewNoopCollector()) 325 // sn1 and sn2 subscribe to the topic with the topic validator 326 sub1, err := sn1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter(), pubsubMessageValidator)) 327 require.NoError(t, err) 328 sub2, err := sn2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter(), pubsubMessageValidator)) 329 require.NoError(t, err) 330 sub3, err := an1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter())) 331 require.NoError(t, err) 332 333 // let nodes form the mesh 334 time.Sleep(time.Second) 335 336 timedCtx, cancel5s := context.WithTimeout(ctx, 60*time.Second) 337 defer cancel5s() 338 // create a dummy block proposal to publish from our SN node 339 data1 := p2pfixtures.MustEncodeEvent(t, unittest.ProposalFixture(), channel) 340 341 // sn2 publishes the block proposal, sn1 and an1 should receive the message because 342 // SN nodes are authorized to send block proposals 343 err = sn2.Publish(timedCtx, topic, data1) 344 require.NoError(t, err) 345 346 // sn1 gets the message 347 p2pfixtures.SubMustReceiveMessage(t, timedCtx, data1, sub1) 348 349 // sn2 also gets the message (as part of the libp2p loopback of published topic messages) 350 p2pfixtures.SubMustReceiveMessage(t, timedCtx, data1, sub2) 351 352 // an1 also gets the message 353 p2pfixtures.SubMustReceiveMessage(t, timedCtx, data1, sub3) 354 355 timedCtx, cancel2s := context.WithTimeout(ctx, 2*time.Second) 356 defer cancel2s() 357 data2 := p2pfixtures.MustEncodeEvent(t, unittest.ProposalFixture(), channel) 358 359 // the access node now publishes the block proposal message, AN are not authorized to publish block proposals 360 // the message should be rejected by the topic validator on sn1 361 err = an1.Publish(timedCtx, topic, data2) 362 require.NoError(t, err) 363 364 // an1 receives its own message 365 p2pfixtures.SubMustReceiveMessage(t, timedCtx, data2, sub3) 366 367 var wg sync.WaitGroup 368 369 // sn1 does NOT receive the message due to the topic validator 370 timedCtx, cancel1s := context.WithTimeout(ctx, time.Second) 371 defer cancel1s() 372 p2pfixtures.SubMustNeverReceiveAnyMessage(t, timedCtx, sub1) 373 374 // sn2 also does not receive the message via gossip from the sn1 (event after the 1 second hearbeat) 375 timedCtx, cancel2s = context.WithTimeout(ctx, 2*time.Second) 376 defer cancel2s() 377 p2pfixtures.SubMustNeverReceiveAnyMessage(t, timedCtx, sub2) 378 379 unittest.RequireReturnsBefore(t, wg.Wait, 5*time.Second, "could not receive message on time") 380 381 // ensure the correct error is contained in the logged error 382 require.Contains(t, hook.Logs(), message.ErrUnauthorizedRole.Error()) 383 } 384 385 // TestAuthorizedSenderValidator_Authorized tests that the authorized sender validator rejects messages being sent on the wrong channel 386 func TestAuthorizedSenderValidator_InvalidMsg(t *testing.T) { 387 ctx, cancel := context.WithCancel(context.Background()) 388 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 389 390 // create a hooked logger 391 logger, hook := unittest.HookedLogger() 392 393 sporkId := unittest.IdentifierFixture() 394 395 sn1, identity1 := p2ptest.NodeFixture(t, sporkId, "consensus_1", p2ptest.WithRole(flow.RoleConsensus)) 396 sn2, identity2 := p2ptest.NodeFixture(t, sporkId, "consensus_2", p2ptest.WithRole(flow.RoleConsensus)) 397 398 nodes := []p2p.LibP2PNode{sn1, sn2} 399 p2ptest.StartNodes(t, signalerCtx, nodes, 100*time.Millisecond) 400 defer p2ptest.StopNodes(t, nodes, cancel, 100*time.Millisecond) 401 402 // try to publish BlockProposal on invalid SyncCommittee channel 403 channel := channels.SyncCommittee 404 topic := channels.TopicFromChannel(channel, sporkId) 405 406 ids := flow.IdentityList{&identity1, &identity2} 407 translator, err := translator.NewFixedTableIdentityTranslator(ids) 408 require.NoError(t, err) 409 410 violationsConsumer := slashing.NewSlashingViolationsConsumer(logger, metrics.NewNoopCollector()) 411 getIdentity := func(pid peer.ID) (*flow.Identity, bool) { 412 fid, err := translator.GetFlowID(pid) 413 if err != nil { 414 return &flow.Identity{}, false 415 } 416 417 return ids.ByNodeID(fid) 418 } 419 authorizedSenderValidator := validator.NewAuthorizedSenderValidator(logger, violationsConsumer, getIdentity) 420 pubsubMessageValidator := authorizedSenderValidator.PubSubMessageValidator(channel) 421 422 pInfo2, err := utils.PeerAddressInfo(identity2) 423 require.NoError(t, err) 424 425 // node1 is connected to node2 426 // sn1 <-> sn2 427 require.NoError(t, sn1.AddPeer(ctx, pInfo2)) 428 429 slashingViolationsConsumer := unittest.NetworkSlashingViolationsConsumer(logger, metrics.NewNoopCollector()) 430 // sn1 subscribe to the topic with the topic validator, while sn2 will subscribe without the topic validator to allow sn2 to publish unauthorized messages 431 sub1, err := sn1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter(), pubsubMessageValidator)) 432 require.NoError(t, err) 433 _, err = sn2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter())) 434 require.NoError(t, err) 435 436 // let nodes form the mesh 437 time.Sleep(time.Second) 438 439 timedCtx, cancel5s := context.WithTimeout(ctx, 5*time.Second) 440 defer cancel5s() 441 // create a dummy block proposal to publish from our SN node 442 data1 := p2pfixtures.MustEncodeEvent(t, unittest.ProposalFixture(), channel) 443 444 // sn2 publishes the block proposal on the sync committee channel 445 err = sn2.Publish(timedCtx, topic, data1) 446 require.NoError(t, err) 447 448 // sn1 should not receive message from sn2 449 timedCtx, cancel1s := context.WithTimeout(ctx, time.Second) 450 defer cancel1s() 451 p2pfixtures.SubMustNeverReceiveAnyMessage(t, timedCtx, sub1) 452 453 // ensure the correct error is contained in the logged error 454 require.Contains(t, hook.Logs(), message.ErrUnauthorizedMessageOnChannel.Error()) 455 } 456 457 // TestAuthorizedSenderValidator_Ejected tests that the authorized sender validator rejects messages from nodes that are ejected 458 func TestAuthorizedSenderValidator_Ejected(t *testing.T) { 459 ctx, cancel := context.WithCancel(context.Background()) 460 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 461 462 // create a hooked logger 463 logger, hook := unittest.HookedLogger() 464 465 sporkId := unittest.IdentifierFixture() 466 467 sn1, identity1 := p2ptest.NodeFixture(t, sporkId, "consensus_1", p2ptest.WithRole(flow.RoleConsensus)) 468 sn2, identity2 := p2ptest.NodeFixture(t, sporkId, "consensus_2", p2ptest.WithRole(flow.RoleConsensus)) 469 an1, identity3 := p2ptest.NodeFixture(t, sporkId, "access_1", p2ptest.WithRole(flow.RoleAccess)) 470 471 nodes := []p2p.LibP2PNode{sn1, sn2, an1} 472 p2ptest.StartNodes(t, signalerCtx, nodes, 100*time.Millisecond) 473 defer p2ptest.StopNodes(t, nodes, cancel, 100*time.Millisecond) 474 475 channel := channels.ConsensusCommittee 476 topic := channels.TopicFromChannel(channel, sporkId) 477 478 ids := flow.IdentityList{&identity1, &identity2, &identity3} 479 translator, err := translator.NewFixedTableIdentityTranslator(ids) 480 require.NoError(t, err) 481 482 violationsConsumer := slashing.NewSlashingViolationsConsumer(logger, metrics.NewNoopCollector()) 483 getIdentity := func(pid peer.ID) (*flow.Identity, bool) { 484 fid, err := translator.GetFlowID(pid) 485 if err != nil { 486 return &flow.Identity{}, false 487 } 488 489 return ids.ByNodeID(fid) 490 } 491 authorizedSenderValidator := validator.NewAuthorizedSenderValidator(logger, violationsConsumer, getIdentity) 492 pubsubMessageValidator := authorizedSenderValidator.PubSubMessageValidator(channel) 493 494 pInfo1, err := utils.PeerAddressInfo(identity1) 495 require.NoError(t, err) 496 497 pInfo2, err := utils.PeerAddressInfo(identity2) 498 require.NoError(t, err) 499 500 // node1 is connected to node2, and the an1 is connected to node1 501 // an1 <-> sn1 <-> sn2 502 require.NoError(t, sn1.AddPeer(ctx, pInfo2)) 503 require.NoError(t, an1.AddPeer(ctx, pInfo1)) 504 505 slashingViolationsConsumer := unittest.NetworkSlashingViolationsConsumer(logger, metrics.NewNoopCollector()) 506 // sn1 subscribe to the topic with the topic validator, while sn2 will subscribe without the topic validator to allow sn2 to publish unauthorized messages 507 sub1, err := sn1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter(), pubsubMessageValidator)) 508 require.NoError(t, err) 509 sub2, err := sn2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter())) 510 require.NoError(t, err) 511 sub3, err := an1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter())) 512 require.NoError(t, err) 513 514 // let nodes form the mesh 515 time.Sleep(time.Second) 516 517 timedCtx, cancel5s := context.WithTimeout(ctx, 5*time.Second) 518 defer cancel5s() 519 // create a dummy block proposal to publish from our SN node 520 data1 := p2pfixtures.MustEncodeEvent(t, unittest.ProposalFixture(), channel) 521 522 // sn2 publishes the block proposal, sn1 and an1 should receive the message because 523 // SN nodes are authorized to send block proposals 524 err = sn2.Publish(timedCtx, topic, data1) 525 require.NoError(t, err) 526 527 // sn1 gets the message 528 p2pfixtures.SubMustReceiveMessage(t, timedCtx, data1, sub1) 529 530 // sn2 also gets the message (as part of the libp2p loopback of published topic messages) 531 p2pfixtures.SubMustReceiveMessage(t, timedCtx, data1, sub2) 532 533 // an1 also gets the message 534 p2pfixtures.SubMustReceiveMessage(t, timedCtx, data1, sub3) 535 536 // "eject" sn2 to ensure messages published by ejected nodes get rejected 537 identity2.Ejected = true 538 data3 := p2pfixtures.MustEncodeEvent(t, unittest.ProposalFixture(), channel) 539 timedCtx, cancel2s := context.WithTimeout(ctx, time.Second) 540 defer cancel2s() 541 err = sn2.Publish(timedCtx, topic, data3) 542 require.NoError(t, err) 543 544 // sn1 should not receive rejected message from ejected sn2 545 timedCtx, cancel1s := context.WithTimeout(ctx, time.Second) 546 defer cancel1s() 547 p2pfixtures.SubMustNeverReceiveAnyMessage(t, timedCtx, sub1) 548 549 // ensure the correct error is contained in the logged error 550 require.Contains(t, hook.Logs(), validator.ErrSenderEjected.Error()) 551 } 552 553 // TestAuthorizedSenderValidator_ClusterChannel tests that the authorized sender validator correctly validates messages sent on cluster channels 554 func TestAuthorizedSenderValidator_ClusterChannel(t *testing.T) { 555 ctx, cancel := context.WithCancel(context.Background()) 556 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 557 558 sporkId := unittest.IdentifierFixture() 559 560 ln1, identity1 := p2ptest.NodeFixture(t, sporkId, "collection_1", p2ptest.WithRole(flow.RoleCollection)) 561 ln2, identity2 := p2ptest.NodeFixture(t, sporkId, "collection_2", p2ptest.WithRole(flow.RoleCollection)) 562 ln3, identity3 := p2ptest.NodeFixture(t, sporkId, "collection_3", p2ptest.WithRole(flow.RoleCollection)) 563 564 nodes := []p2p.LibP2PNode{ln1, ln2, ln3} 565 p2ptest.StartNodes(t, signalerCtx, nodes, 100*time.Millisecond) 566 defer p2ptest.StopNodes(t, nodes, cancel, 100*time.Millisecond) 567 568 channel := channels.SyncCluster(flow.Testnet) 569 topic := channels.TopicFromChannel(channel, sporkId) 570 571 ids := flow.IdentityList{&identity1, &identity2, &identity3} 572 translator, err := translator.NewFixedTableIdentityTranslator(ids) 573 require.NoError(t, err) 574 575 logger := unittest.Logger() 576 violationsConsumer := slashing.NewSlashingViolationsConsumer(logger, metrics.NewNoopCollector()) 577 getIdentity := func(pid peer.ID) (*flow.Identity, bool) { 578 fid, err := translator.GetFlowID(pid) 579 if err != nil { 580 return &flow.Identity{}, false 581 } 582 583 return ids.ByNodeID(fid) 584 } 585 authorizedSenderValidator := validator.NewAuthorizedSenderValidator(logger, violationsConsumer, getIdentity) 586 pubsubMessageValidator := authorizedSenderValidator.PubSubMessageValidator(channel) 587 588 pInfo1, err := utils.PeerAddressInfo(identity1) 589 require.NoError(t, err) 590 591 pInfo2, err := utils.PeerAddressInfo(identity2) 592 require.NoError(t, err) 593 594 // ln3 <-> sn1 <-> sn2 595 require.NoError(t, ln1.AddPeer(ctx, pInfo2)) 596 require.NoError(t, ln3.AddPeer(ctx, pInfo1)) 597 598 slashingViolationsConsumer := unittest.NetworkSlashingViolationsConsumer(logger, metrics.NewNoopCollector()) 599 sub1, err := ln1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter(), pubsubMessageValidator)) 600 require.NoError(t, err) 601 sub2, err := ln2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter(), pubsubMessageValidator)) 602 require.NoError(t, err) 603 sub3, err := ln3.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.NetworkCodec(), slashingViolationsConsumer, unittest.AllowAllPeerFilter(), pubsubMessageValidator)) 604 require.NoError(t, err) 605 606 // let nodes form the mesh 607 time.Sleep(time.Second) 608 609 timedCtx, cancel5s := context.WithTimeout(ctx, 5*time.Second) 610 defer cancel5s() 611 // create a dummy sync request to publish from our LN node 612 data := p2pfixtures.MustEncodeEvent(t, &messages.RangeRequest{}, channel) 613 614 // ln2 publishes the sync request on the cluster channel 615 err = ln2.Publish(timedCtx, topic, data) 616 require.NoError(t, err) 617 618 // ln1 gets the message 619 p2pfixtures.SubMustReceiveMessage(t, timedCtx, data, sub1) 620 621 // ln2 also gets the message (as part of the libp2p loopback of published topic messages) 622 p2pfixtures.SubMustReceiveMessage(t, timedCtx, data, sub2) 623 624 // ln3 also gets the message 625 p2pfixtures.SubMustReceiveMessage(t, timedCtx, data, sub3) 626 }