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  }