github.com/status-im/status-go@v1.1.0/protocol/messenger_communities_sharding_test.go (about)

     1  package protocol
     2  
     3  import (
     4  	"context"
     5  	"crypto/ecdsa"
     6  	"testing"
     7  
     8  	"github.com/golang/protobuf/proto"
     9  	"github.com/stretchr/testify/suite"
    10  	"go.uber.org/zap"
    11  
    12  	gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
    13  	"github.com/status-im/status-go/eth-node/types"
    14  	"github.com/status-im/status-go/protocol/common"
    15  	"github.com/status-im/status-go/protocol/common/shard"
    16  	"github.com/status-im/status-go/protocol/communities"
    17  	"github.com/status-im/status-go/protocol/protobuf"
    18  	"github.com/status-im/status-go/protocol/requests"
    19  	"github.com/status-im/status-go/protocol/tt"
    20  )
    21  
    22  func TestMessengerCommunitiesShardingSuite(t *testing.T) {
    23  	suite.Run(t, new(MessengerCommunitiesShardingSuite))
    24  }
    25  
    26  type MessengerCommunitiesShardingSuite struct {
    27  	suite.Suite
    28  
    29  	owner     *Messenger
    30  	ownerWaku types.Waku
    31  
    32  	alice                         *Messenger
    33  	aliceWaku                     types.Waku
    34  	aliceUnhandledMessagesTracker *unhandledMessagesTracker
    35  
    36  	logger *zap.Logger
    37  
    38  	mockedBalances          communities.BalancesByChain
    39  	mockedCollectibles      communities.CollectiblesByChain
    40  	collectiblesServiceMock *CollectiblesServiceMock
    41  	collectiblesManagerMock *CollectiblesManagerMock
    42  	accountsTestData        map[string][]string
    43  	accountsPasswords       map[string]string
    44  }
    45  
    46  func (s *MessengerCommunitiesShardingSuite) SetupTest() {
    47  	s.logger = tt.MustCreateTestLogger()
    48  	s.collectiblesServiceMock = &CollectiblesServiceMock{}
    49  	s.mockedCollectibles = make(communities.CollectiblesByChain)
    50  	s.collectiblesManagerMock = &CollectiblesManagerMock{
    51  		Collectibles: &s.mockedCollectibles,
    52  	}
    53  	s.accountsTestData = make(map[string][]string)
    54  	s.accountsPasswords = make(map[string]string)
    55  
    56  	wakuNodes := CreateWakuV2Network(&s.Suite, s.logger, []string{"owner", "alice"})
    57  
    58  	nodeConfig := defaultTestCommunitiesMessengerNodeConfig()
    59  
    60  	s.ownerWaku = wakuNodes[0]
    61  	s.owner = newTestCommunitiesMessenger(&s.Suite, s.ownerWaku, testCommunitiesMessengerConfig{
    62  		testMessengerConfig: testMessengerConfig{
    63  			name:   "owner",
    64  			logger: s.logger,
    65  		},
    66  		walletAddresses:     []string{},
    67  		password:            "",
    68  		nodeConfig:          nodeConfig,
    69  		mockedBalances:      &s.mockedBalances,
    70  		collectiblesManager: s.collectiblesManagerMock,
    71  	})
    72  
    73  	s.aliceUnhandledMessagesTracker = &unhandledMessagesTracker{
    74  		messages: map[protobuf.ApplicationMetadataMessage_Type][]*unhandedMessage{},
    75  	}
    76  	s.aliceWaku = wakuNodes[1]
    77  	s.alice = newTestCommunitiesMessenger(&s.Suite, s.aliceWaku, testCommunitiesMessengerConfig{
    78  		testMessengerConfig: testMessengerConfig{
    79  			name:                     "alice",
    80  			logger:                   s.logger,
    81  			unhandledMessagesTracker: s.aliceUnhandledMessagesTracker,
    82  		},
    83  		walletAddresses: []string{aliceAddress1},
    84  		password:        alicePassword,
    85  		nodeConfig:      nodeConfig,
    86  		mockedBalances:  &s.mockedBalances,
    87  	})
    88  
    89  	_, err := s.owner.Start()
    90  	s.Require().NoError(err)
    91  	_, err = s.alice.Start()
    92  	s.Require().NoError(err)
    93  }
    94  
    95  func (s *MessengerCommunitiesShardingSuite) TearDownTest() {
    96  	if s.owner != nil {
    97  		TearDownMessenger(&s.Suite, s.owner)
    98  	}
    99  	if s.ownerWaku != nil {
   100  		s.Require().NoError(gethbridge.GetGethWakuV2From(s.ownerWaku).Stop())
   101  	}
   102  	if s.alice != nil {
   103  		TearDownMessenger(&s.Suite, s.alice)
   104  	}
   105  	if s.aliceWaku != nil {
   106  		s.Require().NoError(gethbridge.GetGethWakuV2From(s.aliceWaku).Stop())
   107  	}
   108  	_ = s.logger.Sync()
   109  }
   110  
   111  func (s *MessengerCommunitiesShardingSuite) testPostToCommunityChat(shard *shard.Shard, community *communities.Community, chat *Chat) {
   112  	_, err := s.owner.SetCommunityShard(&requests.SetCommunityShard{
   113  		CommunityID: community.ID(),
   114  		Shard:       shard,
   115  	})
   116  	s.Require().NoError(err)
   117  
   118  	_, err = WaitOnMessengerResponse(s.alice, func(mr *MessengerResponse) bool {
   119  		if len(mr.communities) == 0 {
   120  			return false
   121  		}
   122  		if shard == nil {
   123  			return mr.Communities()[0].Shard() == nil
   124  		}
   125  		return mr.Communities()[0].Shard() != nil && mr.Communities()[0].Shard().Index == shard.Index
   126  	}, "shard info not updated")
   127  	s.Require().NoError(err)
   128  
   129  	message := buildTestMessage(*chat)
   130  	_, err = s.owner.SendChatMessage(context.Background(), message)
   131  	s.Require().NoError(err)
   132  
   133  	_, err = WaitOnMessengerResponse(s.alice, func(mr *MessengerResponse) bool {
   134  		return len(mr.messages) > 0 && mr.Messages()[0].ID == message.ID
   135  	}, "message not received")
   136  	s.Require().NoError(err)
   137  }
   138  
   139  func (s *MessengerCommunitiesShardingSuite) TestPostToCommunityChat() {
   140  	community, chat := createCommunity(&s.Suite, s.owner)
   141  
   142  	advertiseCommunityToUserOldWay(&s.Suite, community, s.owner, s.alice)
   143  	joinCommunity(&s.Suite, community.ID(), s.owner, s.alice, alicePassword, []string{aliceAddress1})
   144  
   145  	// Members should be able to receive messages in a community with sharding enabled.
   146  	{
   147  		shard := &shard.Shard{
   148  			Cluster: shard.MainStatusShardCluster,
   149  			Index:   128,
   150  		}
   151  		s.testPostToCommunityChat(shard, community, chat)
   152  	}
   153  
   154  	// Members should be able to receive messages in a community where the sharding configuration has been edited.
   155  	{
   156  		shard := &shard.Shard{
   157  			Cluster: shard.MainStatusShardCluster,
   158  			Index:   256,
   159  		}
   160  		s.testPostToCommunityChat(shard, community, chat)
   161  	}
   162  
   163  	// Members should continue to receive messages in a community if it is moved back to default shard.
   164  	{
   165  		shard := &shard.Shard{
   166  			Cluster: shard.MainStatusShardCluster,
   167  			Index:   32,
   168  		}
   169  		s.testPostToCommunityChat(shard, community, chat)
   170  	}
   171  }
   172  
   173  func (s *MessengerCommunitiesShardingSuite) TestIgnoreOutdatedShardKey() {
   174  	community, _ := createCommunity(&s.Suite, s.owner)
   175  
   176  	advertiseCommunityToUserOldWay(&s.Suite, community, s.owner, s.alice)
   177  	joinCommunity(&s.Suite, community.ID(), s.owner, s.alice, alicePassword, []string{aliceAddress1})
   178  
   179  	shard := &shard.Shard{
   180  		Cluster: shard.MainStatusShardCluster,
   181  		Index:   128,
   182  	}
   183  
   184  	// Members should receive shard update.
   185  	{
   186  		response, err := s.owner.SetCommunityShard(&requests.SetCommunityShard{
   187  			CommunityID: community.ID(),
   188  			Shard:       shard,
   189  		})
   190  		s.Require().NoError(err)
   191  		s.Require().Len(response.Communities(), 1)
   192  		community = response.Communities()[0]
   193  
   194  		_, err = WaitOnMessengerResponse(s.alice, func(mr *MessengerResponse) bool {
   195  			return len(mr.communities) > 0 && mr.Communities()[0].Shard() != nil && mr.Communities()[0].Shard().Index == shard.Index
   196  		}, "shard info not updated")
   197  		s.Require().NoError(err)
   198  	}
   199  
   200  	// Members should ignore outdated shard update.
   201  	{
   202  		// Simulate outdated CommunityShardKey message.
   203  		shard.Index = 256
   204  		communityShardKey := &protobuf.CommunityShardKey{
   205  			Clock:       community.Clock() - 1, // simulate outdated clock
   206  			CommunityId: community.ID(),
   207  			Shard:       shard.Protobuffer(),
   208  		}
   209  
   210  		encodedMessage, err := proto.Marshal(communityShardKey)
   211  		s.Require().NoError(err)
   212  
   213  		rawMessage := common.RawMessage{
   214  			Recipients:  []*ecdsa.PublicKey{&s.alice.identity.PublicKey},
   215  			ResendType:  common.ResendTypeDataSync,
   216  			MessageType: protobuf.ApplicationMetadataMessage_COMMUNITY_SHARD_KEY,
   217  			Payload:     encodedMessage,
   218  		}
   219  
   220  		_, err = s.owner.sender.SendPubsubTopicKey(context.Background(), &rawMessage)
   221  		s.Require().NoError(err)
   222  
   223  		_, err = WaitOnMessengerResponse(s.alice, func(mr *MessengerResponse) bool {
   224  			msgType := protobuf.ApplicationMetadataMessage_COMMUNITY_SHARD_KEY
   225  			msgs, exists := s.aliceUnhandledMessagesTracker.messages[msgType]
   226  			if !exists {
   227  				return false
   228  			}
   229  
   230  			for _, msg := range msgs {
   231  				p := &protobuf.CommunityShardKey{}
   232  				err := proto.Unmarshal(msg.ApplicationLayer.Payload, p)
   233  				if err != nil {
   234  					panic(err)
   235  				}
   236  
   237  				if msg.err == communities.ErrOldShardInfo && p.Shard != nil && p.Shard.Index == int32(shard.Index) {
   238  					return true
   239  				}
   240  			}
   241  
   242  			return false
   243  		}, "shard info with outdated clock either not received or not ignored")
   244  		s.Require().NoError(err)
   245  	}
   246  }