github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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/libp2p/go-libp2p/core/peer" 11 "github.com/stretchr/testify/require" 12 13 "github.com/stretchr/testify/mock" 14 15 "github.com/onflow/flow-go/config" 16 "github.com/onflow/flow-go/model/flow" 17 "github.com/onflow/flow-go/model/messages" 18 "github.com/onflow/flow-go/module/irrecoverable" 19 "github.com/onflow/flow-go/module/metrics" 20 mockmodule "github.com/onflow/flow-go/module/mock" 21 "github.com/onflow/flow-go/network" 22 "github.com/onflow/flow-go/network/alsp" 23 "github.com/onflow/flow-go/network/channels" 24 "github.com/onflow/flow-go/network/internal/p2pfixtures" 25 "github.com/onflow/flow-go/network/message" 26 "github.com/onflow/flow-go/network/mocknetwork" 27 "github.com/onflow/flow-go/network/p2p" 28 p2plogging "github.com/onflow/flow-go/network/p2p/logging" 29 p2ptest "github.com/onflow/flow-go/network/p2p/test" 30 "github.com/onflow/flow-go/network/p2p/translator" 31 "github.com/onflow/flow-go/network/p2p/utils" 32 "github.com/onflow/flow-go/network/slashing" 33 "github.com/onflow/flow-go/network/validator" 34 flowpubsub "github.com/onflow/flow-go/network/validator/pubsub" 35 "github.com/onflow/flow-go/utils/unittest" 36 ) 37 38 // TestTopicValidator_Unstaked tests that the libP2P node topic validator rejects unauthenticated messages on non-public channels (unstaked) 39 func TestTopicValidator_Unstaked(t *testing.T) { 40 ctx, cancel := context.WithCancel(context.Background()) 41 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 42 idProvider := mockmodule.NewIdentityProvider(t) 43 // create a hooked logger 44 logger, hook := unittest.HookedLogger() 45 46 sporkId := unittest.IdentifierFixture() 47 48 sn1, identity1 := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 49 sn2, identity2 := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 50 idProvider.On("ByPeerID", sn1.ID()).Return(&identity1, true).Maybe() 51 idProvider.On("ByPeerID", sn2.ID()).Return(&identity2, true).Maybe() 52 nodes := []p2p.LibP2PNode{sn1, sn2} 53 p2ptest.StartNodes(t, signalerCtx, nodes) 54 defer p2ptest.StopNodes(t, nodes, cancel) 55 56 channel := channels.ConsensusCommittee 57 topic := channels.TopicFromChannel(channel, sporkId) 58 59 // NOTE: identity2 is not in the ids list simulating an un-staked node 60 ids := flow.IdentityList{&identity1} 61 translatorFixture, err := translator.NewFixedTableIdentityTranslator(ids) 62 require.NoError(t, err) 63 64 // peer filter used by the topic validator to check if node is staked 65 isStaked := func(pid peer.ID) error { 66 fid, err := translatorFixture.GetFlowID(pid) 67 if err != nil { 68 return fmt.Errorf("could not translate the peer_id %s to a Flow identifier: %w", p2plogging.PeerId(pid), err) 69 } 70 71 if _, ok := ids.ByNodeID(fid); !ok { 72 return fmt.Errorf("flow id not found: %x", fid) 73 } 74 75 return nil 76 } 77 78 pInfo2, err := utils.PeerAddressInfo(identity2.IdentitySkeleton) 79 require.NoError(t, err) 80 81 // node1 is connected to node2 82 // sn1 <-> sn2 83 require.NoError(t, sn1.ConnectToPeer(ctx, pInfo2)) 84 85 // sn1 will subscribe with is staked callback that should force the TopicValidator to drop the message received from sn2 86 sub1, err := sn1.Subscribe(topic, flowpubsub.TopicValidator(logger, isStaked)) 87 require.NoError(t, err) 88 89 // sn2 will subscribe with an unauthenticated callback to allow it to send the unauthenticated message 90 _, err = sn2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter())) 91 require.NoError(t, err) 92 93 // let nodes form the mesh 94 time.Sleep(time.Second) 95 96 timedCtx, cancel5s := context.WithTimeout(ctx, 5*time.Second) 97 defer cancel5s() 98 99 outgoingMessageScope1, err := message.NewOutgoingScope( 100 flow.IdentifierList{identity1.NodeID, identity2.NodeID}, 101 topic, 102 unittest.ProposalFixture(), 103 unittest.NetworkCodec().Encode, 104 message.ProtocolTypePubSub) 105 require.NoError(t, err) 106 107 err = sn2.Publish(timedCtx, outgoingMessageScope1) 108 require.NoError(t, err) 109 110 // sn1 should not receive message from sn2 because sn2 is unstaked 111 timedCtx, cancel1s := context.WithTimeout(ctx, time.Second) 112 defer cancel1s() 113 p2pfixtures.SubMustNeverReceiveAnyMessage(t, timedCtx, sub1) 114 115 // ensure the correct error is contained in the logged error 116 require.Contains(t, hook.Logs(), "filtering message from un-allowed peer") 117 } 118 119 // TestTopicValidator_PublicChannel tests that the libP2P node topic validator does not reject unauthenticated messages on public channels 120 func TestTopicValidator_PublicChannel(t *testing.T) { 121 ctx, cancel := context.WithCancel(context.Background()) 122 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 123 idProvider := mockmodule.NewIdentityProvider(t) 124 sporkId := unittest.IdentifierFixture() 125 logger := unittest.Logger() 126 127 sn1, identity1 := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 128 sn2, identity2 := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 129 idProvider.On("ByPeerID", sn1.ID()).Return(&identity1, true).Maybe() 130 idProvider.On("ByPeerID", sn2.ID()).Return(&identity2, true).Maybe() 131 nodes := []p2p.LibP2PNode{sn1, sn2} 132 p2ptest.StartNodes(t, signalerCtx, nodes) 133 defer p2ptest.StopNodes(t, nodes, cancel) 134 135 // unauthenticated messages should not be dropped on public channels 136 channel := channels.PublicSyncCommittee 137 topic := channels.TopicFromChannel(channel, sporkId) 138 139 pInfo2, err := utils.PeerAddressInfo(identity2.IdentitySkeleton) 140 require.NoError(t, err) 141 142 // node1 is connected to node2 143 // sn1 <-> sn2 144 require.NoError(t, sn1.ConnectToPeer(ctx, pInfo2)) 145 146 // sn1 & sn2 will subscribe with unauthenticated callback to allow it to send and receive unauthenticated messages 147 sub1, err := sn1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter())) 148 require.NoError(t, err) 149 sub2, err := sn2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter())) 150 require.NoError(t, err) 151 152 // let nodes form the mesh 153 time.Sleep(time.Second) 154 155 timedCtx, cancel5s := context.WithTimeout(ctx, 5*time.Second) 156 defer cancel5s() 157 158 outgoingMessageScope1, err := message.NewOutgoingScope( 159 flow.IdentifierList{identity1.NodeID, identity2.NodeID}, 160 topic, 161 &messages.SyncRequest{Nonce: 0, Height: 0}, 162 unittest.NetworkCodec().Encode, 163 message.ProtocolTypePubSub) 164 require.NoError(t, err) 165 166 err = sn2.Publish(timedCtx, outgoingMessageScope1) 167 require.NoError(t, err) 168 169 var wg sync.WaitGroup 170 171 // sn1 should receive message from sn2 because the public channel is unauthenticated 172 timedCtx, cancel1s := context.WithTimeout(ctx, time.Second) 173 defer cancel1s() 174 175 expectedReceivedData, err := outgoingMessageScope1.Proto().Marshal() 176 require.NoError(t, err) 177 178 // sn1 gets the message 179 p2pfixtures.SubMustReceiveMessage(t, timedCtx, expectedReceivedData, sub1) 180 181 // sn2 also gets the message (as part of the libp2p loopback of published topic messages) 182 p2pfixtures.SubMustReceiveMessage(t, timedCtx, expectedReceivedData, sub2) 183 184 unittest.RequireReturnsBefore(t, wg.Wait, 5*time.Second, "could not receive message on time") 185 } 186 187 // TestTopicValidator_TopicMismatch tests that the libP2P node topic validator rejects messages with mismatched topics 188 func TestTopicValidator_TopicMismatch(t *testing.T) { 189 ctx, cancel := context.WithCancel(context.Background()) 190 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 191 idProvider := mockmodule.NewIdentityProvider(t) 192 // create a hooked logger 193 logger, hook := unittest.HookedLogger() 194 195 sporkId := unittest.IdentifierFixture() 196 197 sn1, identity1 := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 198 sn2, identity2 := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 199 idProvider.On("ByPeerID", sn1.ID()).Return(&identity1, true).Maybe() 200 idProvider.On("ByPeerID", sn2.ID()).Return(&identity2, true).Maybe() 201 nodes := []p2p.LibP2PNode{sn1, sn2} 202 p2ptest.StartNodes(t, signalerCtx, nodes) 203 defer p2ptest.StopNodes(t, nodes, cancel) 204 205 channel := channels.ConsensusCommittee 206 topic := channels.TopicFromChannel(channel, sporkId) 207 208 pInfo2, err := utils.PeerAddressInfo(identity2.IdentitySkeleton) 209 require.NoError(t, err) 210 211 // node1 is connected to node2 212 // sn1 <-> sn2 213 require.NoError(t, sn1.ConnectToPeer(ctx, pInfo2)) 214 215 // sn2 will subscribe with an unauthenticated callback to allow processing of message after the authorization check 216 _, err = sn1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter())) 217 require.NoError(t, err) 218 219 // sn2 will subscribe with an unauthenticated callback to allow it to send the unauthenticated message 220 _, err = sn2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter())) 221 require.NoError(t, err) 222 223 // let nodes form the mesh 224 time.Sleep(time.Second) 225 226 timedCtx, cancel5s := context.WithTimeout(ctx, 5*time.Second) 227 defer cancel5s() 228 229 // create a dummy block proposal to publish from our SN node 230 outgoingMessageScope1, err := message.NewOutgoingScope( 231 flow.IdentifierList{identity1.NodeID, identity2.NodeID}, 232 topic, 233 unittest.ProposalFixture(), 234 unittest.NetworkCodec().Encode, 235 message.ProtocolTypePubSub) 236 require.NoError(t, err) 237 238 // intentionally overriding the channel id to be different from the topic 239 outgoingMessageScope1.Proto().ChannelID = channels.PublicSyncCommittee.String() 240 241 err = sn2.Publish(timedCtx, outgoingMessageScope1) 242 // publish fails because the channel validation fails 243 require.Error(t, err) 244 245 // ensure the correct error is contained in the logged error 246 require.Contains(t, hook.Logs(), "channel id in message does not match pubsub topic") 247 } 248 249 // TestTopicValidator_InvalidTopic tests that the libP2P node topic validator rejects messages with invalid topics 250 func TestTopicValidator_InvalidTopic(t *testing.T) { 251 ctx, cancel := context.WithCancel(context.Background()) 252 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 253 idProvider := mockmodule.NewIdentityProvider(t) 254 // create a hooked logger 255 logger, hook := unittest.HookedLogger() 256 257 sporkId := unittest.IdentifierFixture() 258 259 sn1, identity1 := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 260 sn2, identity2 := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, p2ptest.WithRole(flow.RoleConsensus), p2ptest.WithLogger(logger)) 261 idProvider.On("ByPeerID", sn1.ID()).Return(&identity1, true).Maybe() 262 idProvider.On("ByPeerID", sn2.ID()).Return(&identity2, true).Maybe() 263 nodes := []p2p.LibP2PNode{sn1, sn2} 264 p2ptest.StartNodes(t, signalerCtx, nodes) 265 defer p2ptest.StopNodes(t, nodes, cancel) 266 267 topic := channels.Topic("invalid-topic") 268 269 pInfo2, err := utils.PeerAddressInfo(identity2.IdentitySkeleton) 270 require.NoError(t, err) 271 272 // node1 is connected to node2 273 // sn1 <-> sn2 274 require.NoError(t, sn1.ConnectToPeer(ctx, pInfo2)) 275 276 // sn2 will subscribe with an unauthenticated callback to allow processing of message after the authorization check 277 _, err = sn1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter())) 278 require.NoError(t, err) 279 280 // sn2 will subscribe with an unauthenticated callback to allow it to send the unauthenticated message 281 _, err = sn2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter())) 282 require.NoError(t, err) 283 284 // let nodes form the mesh 285 time.Sleep(time.Second) 286 287 timedCtx, cancel5s := context.WithTimeout(ctx, 5*time.Second) 288 defer cancel5s() 289 290 // invalid topic is malformed, hence it cannot be used to create a message scope, as it faces an error. 291 // Hence, we create a dummy block proposal message scope to publish on a legit topic, and then override 292 // the topic in the next step to a malformed topic. 293 dummyMessageScope, err := message.NewOutgoingScope( 294 flow.IdentifierList{identity1.NodeID, identity2.NodeID}, 295 channels.TopicFromChannel(channels.PushBlocks, sporkId), 296 unittest.ProposalFixture(), 297 unittest.NetworkCodec().Encode, 298 message.ProtocolTypePubSub) 299 require.NoError(t, err) 300 301 // overrides the topic to be an invalid topic 302 corruptOutgoingMessageScope := mocknetwork.NewOutgoingMessageScope(t) 303 corruptOutgoingMessageScope.On("Topic").Return(topic) 304 corruptOutgoingMessageScope.On("Proto").Return(dummyMessageScope.Proto()) 305 corruptOutgoingMessageScope.On("PayloadType").Return(dummyMessageScope.PayloadType()) 306 corruptOutgoingMessageScope.On("Size").Return(dummyMessageScope.Size()) 307 308 // create a dummy block proposal to publish from our SN node 309 err = sn2.Publish(timedCtx, corruptOutgoingMessageScope) 310 311 // publish fails because the topic conversion fails 312 require.Error(t, err) 313 // ensure the correct error is contained in the logged error 314 require.Contains(t, hook.Logs(), "could not convert topic to channel") 315 } 316 317 // TestAuthorizedSenderValidator_Unauthorized tests that the authorized sender validator rejects messages from nodes that are not authorized to send the message 318 func TestAuthorizedSenderValidator_Unauthorized(t *testing.T) { 319 ctx, cancel := context.WithCancel(context.Background()) 320 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 321 idProvider := mockmodule.NewIdentityProvider(t) 322 logger := unittest.Logger() 323 324 sporkId := unittest.IdentifierFixture() 325 326 sn1, identity1 := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, p2ptest.WithRole(flow.RoleConsensus)) 327 sn2, identity2 := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, p2ptest.WithRole(flow.RoleConsensus)) 328 an1, identity3 := p2ptest.NodeFixture(t, sporkId, t.Name(), idProvider, p2ptest.WithRole(flow.RoleAccess)) 329 idProvider.On("ByPeerID", sn1.ID()).Return(&identity1, true).Maybe() 330 idProvider.On("ByPeerID", sn2.ID()).Return(&identity2, true).Maybe() 331 idProvider.On("ByPeerID", an1.ID()).Return(&identity3, true).Maybe() 332 nodes := []p2p.LibP2PNode{sn1, sn2, an1} 333 p2ptest.StartNodes(t, signalerCtx, nodes) 334 defer p2ptest.StopNodes(t, nodes, cancel) 335 336 channel := channels.ConsensusCommittee 337 topic := channels.TopicFromChannel(channel, sporkId) 338 339 ids := flow.IdentityList{&identity1, &identity2, &identity3} 340 341 translatorFixture, err := translator.NewFixedTableIdentityTranslator(ids) 342 require.NoError(t, err) 343 344 violation := &network.Violation{ 345 Identity: &identity3, 346 PeerID: p2plogging.PeerId(an1.ID()), 347 OriginID: identity3.NodeID, 348 MsgType: "*messages.BlockProposal", 349 Channel: channel, 350 Protocol: message.ProtocolTypePubSub, 351 Err: message.ErrUnauthorizedRole, 352 } 353 violationsConsumer := mocknetwork.NewViolationsConsumer(t) 354 violationsConsumer.On("OnUnAuthorizedSenderError", violation).Once().Return(nil) 355 getIdentity := func(pid peer.ID) (*flow.Identity, bool) { 356 fid, err := translatorFixture.GetFlowID(pid) 357 if err != nil { 358 return &flow.Identity{}, false 359 } 360 361 return ids.ByNodeID(fid) 362 } 363 authorizedSenderValidator := validator.NewAuthorizedSenderValidator(logger, violationsConsumer, getIdentity) 364 pubsubMessageValidator := authorizedSenderValidator.PubSubMessageValidator(channel) 365 366 pInfo1, err := utils.PeerAddressInfo(identity1.IdentitySkeleton) 367 require.NoError(t, err) 368 369 pInfo2, err := utils.PeerAddressInfo(identity2.IdentitySkeleton) 370 require.NoError(t, err) 371 372 // node1 is connected to node2, and the an1 is connected to node1 373 // an1 <-> sn1 <-> sn2 374 require.NoError(t, sn1.ConnectToPeer(ctx, pInfo2)) 375 require.NoError(t, an1.ConnectToPeer(ctx, pInfo1)) 376 377 // sn1 and sn2 subscribe to the topic with the topic validator 378 sub1, err := sn1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter(), pubsubMessageValidator)) 379 require.NoError(t, err) 380 sub2, err := sn2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter(), pubsubMessageValidator)) 381 require.NoError(t, err) 382 sub3, err := an1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter())) 383 require.NoError(t, err) 384 385 // let nodes form the mesh 386 time.Sleep(time.Second) 387 388 timedCtx, cancel5s := context.WithTimeout(ctx, 60*time.Second) 389 defer cancel5s() 390 391 // sn2 publishes the block proposal, sn1 and an1 should receive the message because 392 // SN nodes are authorized to send block proposals 393 // create a dummy block proposal to publish from our SN node 394 outgoingMessageScope1, err := message.NewOutgoingScope( 395 flow.IdentifierList{identity1.NodeID, identity2.NodeID}, 396 topic, 397 unittest.ProposalFixture(), 398 unittest.NetworkCodec().Encode, 399 message.ProtocolTypePubSub) 400 require.NoError(t, err) 401 err = sn2.Publish(timedCtx, outgoingMessageScope1) 402 require.NoError(t, err) 403 404 expectedReceivedData1, err := outgoingMessageScope1.Proto().Marshal() 405 require.NoError(t, err) 406 407 // sn1 gets the message 408 p2pfixtures.SubMustReceiveMessage(t, timedCtx, expectedReceivedData1, sub1) 409 410 // sn2 also gets the message (as part of the libp2p loopback of published topic messages) 411 p2pfixtures.SubMustReceiveMessage(t, timedCtx, expectedReceivedData1, sub2) 412 413 // an1 also gets the message 414 p2pfixtures.SubMustReceiveMessage(t, timedCtx, expectedReceivedData1, sub3) 415 416 timedCtx, cancel2s := context.WithTimeout(ctx, 2*time.Second) 417 defer cancel2s() 418 419 // the access node now publishes the block proposal message, AN are not authorized to publish block proposals 420 // the message should be rejected by the topic validator on sn1 421 outgoingMessageScope2, err := message.NewOutgoingScope( 422 flow.IdentifierList{identity1.NodeID, identity2.NodeID}, 423 topic, 424 unittest.ProposalFixture(), 425 unittest.NetworkCodec().Encode, 426 message.ProtocolTypePubSub) 427 require.NoError(t, err) 428 err = an1.Publish(timedCtx, outgoingMessageScope2) 429 require.NoError(t, err) 430 431 expectedReceivedData2, err := outgoingMessageScope2.Proto().Marshal() 432 require.NoError(t, err) 433 434 // an1 receives its own message 435 p2pfixtures.SubMustReceiveMessage(t, timedCtx, expectedReceivedData2, sub3) 436 437 var wg sync.WaitGroup 438 439 // sn1 does NOT receive the message due to the topic validator 440 timedCtx, cancel1s := context.WithTimeout(ctx, time.Second) 441 defer cancel1s() 442 p2pfixtures.SubMustNeverReceiveAnyMessage(t, timedCtx, sub1) 443 444 // sn2 also does not receive the message via gossip from the sn1 (event after the 1 second hearbeat) 445 timedCtx, cancel2s = context.WithTimeout(ctx, 2*time.Second) 446 defer cancel2s() 447 p2pfixtures.SubMustNeverReceiveAnyMessage(t, timedCtx, sub2) 448 449 unittest.RequireReturnsBefore(t, wg.Wait, 5*time.Second, "could not receive message on time") 450 } 451 452 // TestAuthorizedSenderValidator_Authorized tests that the authorized sender validator rejects messages being sent on the wrong channel 453 func TestAuthorizedSenderValidator_InvalidMsg(t *testing.T) { 454 ctx, cancel := context.WithCancel(context.Background()) 455 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 456 idProvider := mockmodule.NewIdentityProvider(t) 457 // create a hooked logger 458 logger, hook := unittest.HookedLogger() 459 460 sporkId := unittest.IdentifierFixture() 461 462 sn1, identity1 := p2ptest.NodeFixture(t, sporkId, "consensus_1", idProvider, p2ptest.WithRole(flow.RoleConsensus)) 463 sn2, identity2 := p2ptest.NodeFixture(t, sporkId, "consensus_2", idProvider, p2ptest.WithRole(flow.RoleConsensus)) 464 idProvider.On("ByPeerID", sn1.ID()).Return(&identity1, true).Maybe() 465 idProvider.On("ByPeerID", sn2.ID()).Return(&identity2, true).Maybe() 466 nodes := []p2p.LibP2PNode{sn1, sn2} 467 p2ptest.StartNodes(t, signalerCtx, nodes) 468 defer p2ptest.StopNodes(t, nodes, cancel) 469 470 // try to publish BlockProposal on invalid SyncCommittee channel 471 channel := channels.SyncCommittee 472 topic := channels.TopicFromChannel(channel, sporkId) 473 474 ids := flow.IdentityList{&identity1, &identity2} 475 translatorFixture, err := translator.NewFixedTableIdentityTranslator(ids) 476 require.NoError(t, err) 477 478 expectedMisbehaviorReport, err := alsp.NewMisbehaviorReport(identity2.NodeID, alsp.UnAuthorizedSender) 479 require.NoError(t, err) 480 misbehaviorReportConsumer := mocknetwork.NewMisbehaviorReportConsumer(t) 481 misbehaviorReportConsumer.On("ReportMisbehaviorOnChannel", channel, expectedMisbehaviorReport).Once() 482 violationsConsumer := slashing.NewSlashingViolationsConsumer(logger, metrics.NewNoopCollector(), misbehaviorReportConsumer) 483 getIdentity := func(pid peer.ID) (*flow.Identity, bool) { 484 fid, err := translatorFixture.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 pInfo2, err := utils.PeerAddressInfo(identity2.IdentitySkeleton) 495 require.NoError(t, err) 496 497 // node1 is connected to node2 498 // sn1 <-> sn2 499 require.NoError(t, sn1.ConnectToPeer(ctx, pInfo2)) 500 501 // sn1 subscribe to the topic with the topic validator, while sn2 will subscribe without the topic validator to allow sn2 to publish unauthorized messages 502 sub1, err := sn1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter(), pubsubMessageValidator)) 503 require.NoError(t, err) 504 _, err = sn2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter())) 505 require.NoError(t, err) 506 507 // let nodes form the mesh 508 time.Sleep(time.Second) 509 510 timedCtx, cancel5s := context.WithTimeout(ctx, 5*time.Second) 511 defer cancel5s() 512 513 // create a dummy block proposal to publish from our SN node 514 // sn2 publishes the block proposal on the sync committee channel 515 outgoingMessageScope1, err := message.NewOutgoingScope( 516 flow.IdentifierList{identity1.NodeID, identity2.NodeID}, 517 topic, 518 unittest.ProposalFixture(), 519 unittest.NetworkCodec().Encode, 520 message.ProtocolTypePubSub) 521 require.NoError(t, err) 522 err = sn2.Publish(timedCtx, outgoingMessageScope1) 523 require.NoError(t, err) 524 525 // sn1 should not receive message from sn2 526 timedCtx, cancel1s := context.WithTimeout(ctx, time.Second) 527 defer cancel1s() 528 p2pfixtures.SubMustNeverReceiveAnyMessage(t, timedCtx, sub1) 529 530 // ensure the correct error is contained in the logged error 531 require.Contains(t, hook.Logs(), message.ErrUnauthorizedMessageOnChannel.Error()) 532 } 533 534 // TestAuthorizedSenderValidator_Ejected tests that the authorized sender validator rejects messages from nodes that are ejected 535 func TestAuthorizedSenderValidator_Ejected(t *testing.T) { 536 ctx, cancel := context.WithCancel(context.Background()) 537 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 538 idProvider := mockmodule.NewIdentityProvider(t) 539 // create a hooked logger 540 logger, hook := unittest.HookedLogger() 541 542 sporkId := unittest.IdentifierFixture() 543 544 cfg, err := config.DefaultConfig() 545 require.NoError(t, err) 546 // turn off unstaked peer rejection so that nodes can connect 547 cfg.NetworkConfig.GossipSub.RpcInspector.Validation.InspectionProcess.Inspect.RejectUnstakedPeers = false 548 sn1, identity1 := p2ptest.NodeFixture(t, sporkId, "consensus_1", idProvider, p2ptest.WithRole(flow.RoleConsensus), p2ptest.OverrideFlowConfig(cfg)) 549 sn2, identity2 := p2ptest.NodeFixture(t, sporkId, "consensus_2", idProvider, p2ptest.WithRole(flow.RoleConsensus), p2ptest.OverrideFlowConfig(cfg)) 550 an1, identity3 := p2ptest.NodeFixture(t, sporkId, "access_1", idProvider, p2ptest.WithRole(flow.RoleAccess), p2ptest.OverrideFlowConfig(cfg)) 551 idProvider.On("ByPeerID", sn1.ID()).Return(&identity1, true).Maybe() 552 idProvider.On("ByPeerID", sn2.ID()).Return(&identity2, true).Maybe() 553 idProvider.On("ByPeerID", an1.ID()).Return(&identity3, true).Maybe() 554 nodes := []p2p.LibP2PNode{sn1, sn2, an1} 555 p2ptest.StartNodes(t, signalerCtx, nodes) 556 defer p2ptest.StopNodes(t, nodes, cancel) 557 558 channel := channels.ConsensusCommittee 559 topic := channels.TopicFromChannel(channel, sporkId) 560 561 ids := flow.IdentityList{&identity1, &identity2, &identity3} 562 translatorFixture, err := translator.NewFixedTableIdentityTranslator(ids) 563 require.NoError(t, err) 564 565 expectedMisbehaviorReport, err := alsp.NewMisbehaviorReport(identity2.NodeID, alsp.SenderEjected) 566 require.NoError(t, err) 567 misbehaviorReportConsumer := mocknetwork.NewMisbehaviorReportConsumer(t) 568 misbehaviorReportConsumer.On("ReportMisbehaviorOnChannel", channel, expectedMisbehaviorReport).Once() 569 violationsConsumer := slashing.NewSlashingViolationsConsumer(logger, metrics.NewNoopCollector(), misbehaviorReportConsumer) 570 getIdentity := func(pid peer.ID) (*flow.Identity, bool) { 571 fid, err := translatorFixture.GetFlowID(pid) 572 if err != nil { 573 return &flow.Identity{}, false 574 } 575 576 return ids.ByNodeID(fid) 577 } 578 authorizedSenderValidator := validator.NewAuthorizedSenderValidator(logger, violationsConsumer, getIdentity) 579 pubsubMessageValidator := authorizedSenderValidator.PubSubMessageValidator(channel) 580 581 pInfo1, err := utils.PeerAddressInfo(identity1.IdentitySkeleton) 582 require.NoError(t, err) 583 584 pInfo2, err := utils.PeerAddressInfo(identity2.IdentitySkeleton) 585 require.NoError(t, err) 586 587 // node1 is connected to node2, and the an1 is connected to node1 588 // an1 <-> sn1 <-> sn2 589 require.NoError(t, sn1.ConnectToPeer(ctx, pInfo2)) 590 require.NoError(t, an1.ConnectToPeer(ctx, pInfo1)) 591 592 // sn1 subscribe to the topic with the topic validator, while sn2 will subscribe without the topic validator to allow sn2 to publish unauthorized messages 593 sub1, err := sn1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter(), pubsubMessageValidator)) 594 require.NoError(t, err) 595 sub2, err := sn2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter())) 596 require.NoError(t, err) 597 sub3, err := an1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter())) 598 require.NoError(t, err) 599 600 // let nodes form the mesh 601 time.Sleep(time.Second) 602 603 timedCtx, cancel5s := context.WithTimeout(ctx, 5*time.Second) 604 defer cancel5s() 605 606 // sn2 publishes the block proposal, sn1 and an1 should receive the message because 607 // SN nodes are authorized to send block proposals 608 // create a dummy block proposal to publish from our SN node 609 outgoingMessageScope1, err := message.NewOutgoingScope( 610 flow.IdentifierList{identity1.NodeID, identity2.NodeID}, 611 topic, 612 unittest.ProposalFixture(), 613 unittest.NetworkCodec().Encode, 614 message.ProtocolTypePubSub) 615 require.NoError(t, err) 616 err = sn2.Publish(timedCtx, outgoingMessageScope1) 617 require.NoError(t, err) 618 619 expectedReceivedData1, err := outgoingMessageScope1.Proto().Marshal() 620 require.NoError(t, err) 621 622 // sn1 gets the message 623 p2pfixtures.SubMustReceiveMessage(t, timedCtx, expectedReceivedData1, sub1) 624 625 // sn2 also gets the message (as part of the libp2p loopback of published topic messages) 626 p2pfixtures.SubMustReceiveMessage(t, timedCtx, expectedReceivedData1, sub2) 627 628 // an1 also gets the message 629 p2pfixtures.SubMustReceiveMessage(t, timedCtx, expectedReceivedData1, sub3) 630 631 // "eject" sn2 to ensure messages published by ejected nodes get rejected 632 identity2.EpochParticipationStatus = flow.EpochParticipationStatusEjected 633 634 outgoingMessageScope3, err := message.NewOutgoingScope( 635 flow.IdentifierList{identity1.NodeID, identity2.NodeID}, 636 topic, 637 unittest.ProposalFixture(), 638 unittest.NetworkCodec().Encode, 639 message.ProtocolTypePubSub) 640 require.NoError(t, err) 641 642 timedCtx, cancel2s := context.WithTimeout(ctx, time.Second) 643 defer cancel2s() 644 err = sn2.Publish(timedCtx, outgoingMessageScope3) 645 require.NoError(t, err) 646 647 // sn1 should not receive rejected message from ejected sn2 648 timedCtx, cancel1s := context.WithTimeout(ctx, time.Second) 649 defer cancel1s() 650 p2pfixtures.SubMustNeverReceiveAnyMessage(t, timedCtx, sub1) 651 652 // ensure the correct error is contained in the logged error 653 require.Contains(t, hook.Logs(), validator.ErrSenderEjected.Error()) 654 } 655 656 // TestAuthorizedSenderValidator_ClusterChannel tests that the authorized sender validator correctly validates messages sent on cluster channels 657 func TestAuthorizedSenderValidator_ClusterChannel(t *testing.T) { 658 ctx, cancel := context.WithCancel(context.Background()) 659 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 660 idProvider := mockmodule.NewIdentityProvider(t) 661 sporkId := unittest.IdentifierFixture() 662 663 ln1, identity1 := p2ptest.NodeFixture(t, sporkId, "collection_1", idProvider, p2ptest.WithRole(flow.RoleCollection)) 664 ln2, identity2 := p2ptest.NodeFixture(t, sporkId, "collection_2", idProvider, p2ptest.WithRole(flow.RoleCollection)) 665 ln3, identity3 := p2ptest.NodeFixture(t, sporkId, "collection_3", idProvider, p2ptest.WithRole(flow.RoleCollection)) 666 idProvider.On("ByPeerID", ln1.ID()).Return(&identity1, true).Maybe() 667 idProvider.On("ByPeerID", ln2.ID()).Return(&identity2, true).Maybe() 668 idProvider.On("ByPeerID", ln3.ID()).Return(&identity3, true).Maybe() 669 nodes := []p2p.LibP2PNode{ln1, ln2, ln3} 670 p2ptest.StartNodes(t, signalerCtx, nodes) 671 defer p2ptest.StopNodes(t, nodes, cancel) 672 673 channel := channels.SyncCluster(flow.Testnet) 674 topic := channels.TopicFromChannel(channel, sporkId) 675 676 ids := flow.IdentityList{&identity1, &identity2, &identity3} 677 translatorFixture, err := translator.NewFixedTableIdentityTranslator(ids) 678 require.NoError(t, err) 679 680 logger := unittest.Logger() 681 misbehaviorReportConsumer := mocknetwork.NewMisbehaviorReportConsumer(t) 682 defer misbehaviorReportConsumer.AssertNotCalled(t, "ReportMisbehaviorOnChannel", mock.AnythingOfType("channels.Channel"), mock.AnythingOfType("*alsp.MisbehaviorReport")) 683 violationsConsumer := slashing.NewSlashingViolationsConsumer(logger, metrics.NewNoopCollector(), misbehaviorReportConsumer) 684 getIdentity := func(pid peer.ID) (*flow.Identity, bool) { 685 fid, err := translatorFixture.GetFlowID(pid) 686 if err != nil { 687 return &flow.Identity{}, false 688 } 689 690 return ids.ByNodeID(fid) 691 } 692 authorizedSenderValidator := validator.NewAuthorizedSenderValidator(logger, violationsConsumer, getIdentity) 693 pubsubMessageValidator := authorizedSenderValidator.PubSubMessageValidator(channel) 694 695 pInfo1, err := utils.PeerAddressInfo(identity1.IdentitySkeleton) 696 require.NoError(t, err) 697 698 pInfo2, err := utils.PeerAddressInfo(identity2.IdentitySkeleton) 699 require.NoError(t, err) 700 701 // ln3 <-> sn1 <-> sn2 702 require.NoError(t, ln1.ConnectToPeer(ctx, pInfo2)) 703 require.NoError(t, ln3.ConnectToPeer(ctx, pInfo1)) 704 705 sub1, err := ln1.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter(), pubsubMessageValidator)) 706 require.NoError(t, err) 707 sub2, err := ln2.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter(), pubsubMessageValidator)) 708 require.NoError(t, err) 709 sub3, err := ln3.Subscribe(topic, flowpubsub.TopicValidator(logger, unittest.AllowAllPeerFilter(), pubsubMessageValidator)) 710 require.NoError(t, err) 711 712 // let nodes form the mesh 713 time.Sleep(time.Second) 714 715 timedCtx, cancel5s := context.WithTimeout(ctx, 5*time.Second) 716 defer cancel5s() 717 718 // create a dummy sync request to publish from our LN node 719 outgoingMessageScope1, err := message.NewOutgoingScope( 720 flow.IdentifierList{identity1.NodeID, identity2.NodeID}, 721 topic, 722 &messages.RangeRequest{}, 723 unittest.NetworkCodec().Encode, 724 message.ProtocolTypePubSub) 725 require.NoError(t, err) 726 727 // ln2 publishes the sync request on the cluster channel 728 err = ln2.Publish(timedCtx, outgoingMessageScope1) 729 require.NoError(t, err) 730 731 expectedReceivedData1, err := outgoingMessageScope1.Proto().Marshal() 732 require.NoError(t, err) 733 734 // ln1 gets the message 735 p2pfixtures.SubMustReceiveMessage(t, timedCtx, expectedReceivedData1, sub1) 736 737 // ln2 also gets the message (as part of the libp2p loopback of published topic messages) 738 p2pfixtures.SubMustReceiveMessage(t, timedCtx, expectedReceivedData1, sub2) 739 740 // ln3 also gets the message 741 p2pfixtures.SubMustReceiveMessage(t, timedCtx, expectedReceivedData1, sub3) 742 }