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

     1  package communities
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"database/sql"
     6  	"math/big"
     7  	"reflect"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/golang/protobuf/proto"
    12  	"github.com/stretchr/testify/suite"
    13  
    14  	"github.com/status-im/status-go/appdatabase"
    15  	"github.com/status-im/status-go/eth-node/crypto"
    16  	"github.com/status-im/status-go/eth-node/types"
    17  	"github.com/status-im/status-go/protocol/common"
    18  	"github.com/status-im/status-go/protocol/common/shard"
    19  	"github.com/status-im/status-go/protocol/communities/token"
    20  	"github.com/status-im/status-go/protocol/encryption"
    21  	"github.com/status-im/status-go/protocol/protobuf"
    22  	"github.com/status-im/status-go/protocol/sqlite"
    23  	"github.com/status-im/status-go/services/wallet/bigint"
    24  	"github.com/status-im/status-go/t/helpers"
    25  )
    26  
    27  func TestPersistenceSuite(t *testing.T) {
    28  	suite.Run(t, new(PersistenceSuite))
    29  }
    30  
    31  type PersistenceSuite struct {
    32  	suite.Suite
    33  
    34  	db       *Persistence
    35  	identity *ecdsa.PrivateKey
    36  }
    37  
    38  func (s *PersistenceSuite) SetupTest() {
    39  	s.db = nil
    40  
    41  	db, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
    42  	s.Require().NoError(err, "creating sqlite db instance")
    43  
    44  	err = sqlite.Migrate(db)
    45  	s.Require().NoError(err, "protocol migrate")
    46  
    47  	s.identity, err = crypto.GenerateKey()
    48  	s.Require().NoError(err)
    49  
    50  	s.db = &Persistence{db: db, recordBundleToCommunity: func(r *CommunityRecordBundle) (*Community, error) {
    51  		return recordBundleToCommunity(r, s.identity, "", nil, &TimeSourceStub{}, &DescriptionEncryptorMock{}, nil, nil)
    52  	}}
    53  }
    54  
    55  func (s *PersistenceSuite) TestSaveCommunity() {
    56  	communities, err := s.db.AllCommunities(&s.identity.PublicKey)
    57  	s.Require().NoError(err)
    58  	s.Require().Len(communities, 0)
    59  
    60  	community := Community{
    61  		config: &Config{
    62  			PrivateKey:           s.identity,
    63  			ControlNode:          &s.identity.PublicKey,
    64  			ControlDevice:        true,
    65  			ID:                   &s.identity.PublicKey,
    66  			Joined:               true,
    67  			Spectated:            true,
    68  			Verified:             true,
    69  			Muted:                true,
    70  			MuteTill:             time.Time{},
    71  			CommunityDescription: &protobuf.CommunityDescription{},
    72  		},
    73  	}
    74  	s.Require().NoError(s.db.SaveCommunity(&community))
    75  
    76  	communities, err = s.db.AllCommunities(&s.identity.PublicKey)
    77  	s.Require().NoError(err)
    78  	s.Require().Len(communities, 1)
    79  	s.Equal(types.HexBytes(crypto.CompressPubkey(&s.identity.PublicKey)), communities[0].ID())
    80  	s.Equal(true, communities[0].Joined())
    81  	s.Equal(true, communities[0].Spectated())
    82  	s.Equal(true, communities[0].Verified())
    83  	s.Equal(true, communities[0].Muted())
    84  	s.Equal(time.Time{}, communities[0].MuteTill())
    85  }
    86  
    87  func (s *PersistenceSuite) TestShouldHandleSyncCommunity() {
    88  	sc := &protobuf.SyncInstallationCommunity{
    89  		Id:          []byte("0x123456"),
    90  		Description: []byte("this is a description"),
    91  		Joined:      true,
    92  		Verified:    true,
    93  		Clock:       uint64(time.Now().Unix()),
    94  	}
    95  
    96  	// check an empty db to see if a community should be synced
    97  	should, err := s.db.ShouldHandleSyncCommunity(sc)
    98  	s.Require().NoError(err, "SaveSyncCommunity")
    99  	s.True(should)
   100  
   101  	// add a new community to the db
   102  	err = s.db.saveRawCommunityRow(fromSyncCommunityProtobuf(sc))
   103  	s.Require().NoError(err, "saveRawCommunityRow")
   104  
   105  	rcrs, err := s.db.getAllCommunitiesRaw()
   106  	s.Require().NoError(err, "should have no error from getAllCommunitiesRaw")
   107  	s.Len(rcrs, 1, "length of all communities raw should be 1")
   108  
   109  	// check again to see is the community should be synced
   110  	sc.Clock--
   111  	should, err = s.db.ShouldHandleSyncCommunity(sc)
   112  	s.Require().NoError(err, "SaveSyncCommunity")
   113  	s.False(should)
   114  
   115  	// check again to see is the community should be synced
   116  	sc.Clock++
   117  	sc.Clock++
   118  	should, err = s.db.ShouldHandleSyncCommunity(sc)
   119  	s.Require().NoError(err, "SaveSyncCommunity")
   120  	s.True(should)
   121  }
   122  
   123  func (s *PersistenceSuite) TestSetSyncClock() {
   124  	sc := &protobuf.SyncInstallationCommunity{
   125  		Id:          []byte("0x123456"),
   126  		Description: []byte("this is a description"),
   127  		Joined:      true,
   128  		Verified:    true,
   129  	}
   130  
   131  	// add a new community to the db
   132  	err := s.db.saveRawCommunityRow(fromSyncCommunityProtobuf(sc))
   133  	s.Require().NoError(err, "saveRawCommunityRow")
   134  
   135  	// retrieve row from db synced_at must be zero
   136  	rcr, err := s.db.getRawCommunityRow(sc.Id)
   137  	s.Require().NoError(err, "getRawCommunityRow")
   138  	s.Require().Zero(rcr.SyncedAt, "synced_at must be zero value")
   139  
   140  	// Set the synced_at value
   141  	clock := uint64(time.Now().Unix())
   142  	err = s.db.SetSyncClock(sc.Id, clock)
   143  	s.Require().NoError(err, "SetSyncClock")
   144  
   145  	// Retrieve row from db and check clock matches synced_at value
   146  	rcr, err = s.db.getRawCommunityRow(sc.Id)
   147  	s.Require().NoError(err, "getRawCommunityRow")
   148  	s.Require().Equal(clock, rcr.SyncedAt, "synced_at must equal the value of the clock")
   149  
   150  	// Set Synced At with an older clock value
   151  	olderClock := clock - uint64(256)
   152  	err = s.db.SetSyncClock(sc.Id, olderClock)
   153  	s.Require().NoError(err, "SetSyncClock")
   154  
   155  	// Retrieve row from db and check olderClock matches synced_at value
   156  	rcr, err = s.db.getRawCommunityRow(sc.Id)
   157  	s.Require().NoError(err, "getRawCommunityRow")
   158  	s.Require().NotEqual(olderClock, rcr.SyncedAt, "synced_at must not equal the value of the olderClock value")
   159  
   160  	// Set Synced At with a newer clock value
   161  	newerClock := clock + uint64(512)
   162  	err = s.db.SetSyncClock(sc.Id, newerClock)
   163  	s.Require().NoError(err, "SetSyncClock")
   164  
   165  	// Retrieve row from db and check olderClock matches synced_at value
   166  	rcr, err = s.db.getRawCommunityRow(sc.Id)
   167  	s.Require().NoError(err, "getRawCommunityRow")
   168  	s.Equal(newerClock, rcr.SyncedAt, "synced_at must equal the value of the newerClock value")
   169  }
   170  
   171  func (s *PersistenceSuite) TestSetPrivateKey() {
   172  	sc := &protobuf.SyncInstallationCommunity{
   173  		Id:          []byte("0x123456"),
   174  		Description: []byte("this is a description"),
   175  		Joined:      true,
   176  		Verified:    true,
   177  	}
   178  
   179  	// add a new community to the db with no private key
   180  	err := s.db.saveRawCommunityRow(fromSyncCommunityProtobuf(sc))
   181  	s.Require().NoError(err, "saveRawCommunityRow")
   182  
   183  	// retrieve row from db, private key must be zero
   184  	rcr, err := s.db.getRawCommunityRow(sc.Id)
   185  	s.Require().NoError(err, "getRawCommunityRow")
   186  	s.Zero(rcr.PrivateKey, "private key must be zero value")
   187  
   188  	// Set private key
   189  	err = s.db.SetPrivateKey(sc.Id, s.identity)
   190  	s.Require().NoError(err, "SetPrivateKey")
   191  
   192  	// retrieve row from db again, private key must match the given key
   193  	rcr, err = s.db.getRawCommunityRow(sc.Id)
   194  	s.Require().NoError(err, "getRawCommunityRow")
   195  	s.Equal(crypto.FromECDSA(s.identity), rcr.PrivateKey, "private key must match given key")
   196  }
   197  
   198  func (s *PersistenceSuite) TestJoinedAndPendingCommunitiesWithRequests() {
   199  	clock := uint64(time.Now().Unix())
   200  
   201  	// Add a new community that we have joined
   202  	com := s.makeNewCommunity(s.identity)
   203  	com.Join()
   204  	sc, err := com.ToSyncInstallationCommunityProtobuf(clock, nil, nil)
   205  	s.Require().NoError(err, "Community.ToSyncInstallationCommunityProtobuf shouldn't give any error")
   206  	err = s.db.saveRawCommunityRow(fromSyncCommunityProtobuf(sc))
   207  	s.Require().NoError(err, "saveRawCommunityRow")
   208  
   209  	// Add a new community that we have requested to join, but not yet joined
   210  	com2 := s.makeNewCommunity(s.identity)
   211  	err = s.db.SaveCommunity(com2)
   212  	s.Require().NoError(err, "SaveCommunity shouldn't give any error")
   213  
   214  	rtj := &RequestToJoin{
   215  		ID:          types.HexBytes{1, 2, 3, 4, 5, 6, 7, 8},
   216  		PublicKey:   common.PubkeyToHex(&s.identity.PublicKey),
   217  		Clock:       clock,
   218  		CommunityID: com2.ID(),
   219  		State:       RequestToJoinStatePending,
   220  	}
   221  	err = s.db.SaveRequestToJoin(rtj)
   222  	s.Require().NoError(err, "SaveRequestToJoin shouldn't give any error")
   223  
   224  	comms, err := s.db.JoinedAndPendingCommunitiesWithRequests(&s.identity.PublicKey)
   225  	s.Require().NoError(err, "JoinedAndPendingCommunitiesWithRequests shouldn't give any error")
   226  	s.Len(comms, 2, "Should have 2 communities")
   227  
   228  	for _, comm := range comms {
   229  		switch comm.IDString() {
   230  		case com.IDString():
   231  			s.Len(comm.RequestsToJoin(), 0, "Should have no RequestsToJoin")
   232  		case com2.IDString():
   233  			rtjs := comm.RequestsToJoin()
   234  			s.Len(rtjs, 1, "Should have one RequestsToJoin")
   235  			s.Equal(rtjs[0], rtj, "RequestToJoin should match the Request stored in the db")
   236  		}
   237  	}
   238  }
   239  
   240  func (s *PersistenceSuite) TestSaveRequestToLeave() {
   241  	rtl := &RequestToLeave{
   242  		ID:          []byte("0x123456"),
   243  		PublicKey:   "0xffffff",
   244  		Clock:       2,
   245  		CommunityID: []byte("0x654321"),
   246  	}
   247  
   248  	err := s.db.SaveRequestToLeave(rtl)
   249  	s.Require().NoError(err)
   250  
   251  	// older clocks should not be saved
   252  	rtl.Clock = 1
   253  	err = s.db.SaveRequestToLeave(rtl)
   254  	s.Error(err)
   255  }
   256  
   257  func (s *PersistenceSuite) makeNewCommunity(identity *ecdsa.PrivateKey) *Community {
   258  	comPrivKey, err := crypto.GenerateKey()
   259  	s.Require().NoError(err, "crypto.GenerateKey shouldn't give any error")
   260  
   261  	com, err := New(Config{
   262  		MemberIdentity: identity,
   263  		PrivateKey:     comPrivKey,
   264  		ControlNode:    &comPrivKey.PublicKey,
   265  		ControlDevice:  true,
   266  		ID:             &comPrivKey.PublicKey,
   267  	}, &TimeSourceStub{}, &DescriptionEncryptorMock{}, nil)
   268  	s.NoError(err, "New shouldn't give any error")
   269  
   270  	md, err := com.MarshaledDescription()
   271  	s.Require().NoError(err, "Community.MarshaledDescription shouldn't give any error")
   272  	com.config.CommunityDescriptionProtocolMessage = md
   273  
   274  	return com
   275  }
   276  
   277  func (s *PersistenceSuite) TestGetSyncedRawCommunity() {
   278  	sc := &protobuf.SyncInstallationCommunity{
   279  		Id:          []byte("0x123456"),
   280  		Description: []byte("this is a description"),
   281  		Joined:      true,
   282  		Verified:    true,
   283  		Spectated:   true,
   284  	}
   285  
   286  	// add a new community to the db
   287  	err := s.db.saveRawCommunityRowWithoutSyncedAt(fromSyncCommunityProtobuf(sc))
   288  	s.Require().NoError(err, "saveRawCommunityRow")
   289  
   290  	// retrieve row from db synced_at must be zero
   291  	rcr, err := s.db.getRawCommunityRow(sc.Id)
   292  	s.Require().NoError(err, "getRawCommunityRow")
   293  	s.Zero(rcr.SyncedAt, "synced_at must be zero value")
   294  
   295  	// retrieve synced row from db, should fail
   296  	src, err := s.db.getSyncedRawCommunity(sc.Id)
   297  	s.EqualError(err, sql.ErrNoRows.Error())
   298  	s.Nil(src)
   299  
   300  	// Set the synced_at value
   301  	clock := uint64(time.Now().Unix())
   302  	err = s.db.SetSyncClock(sc.Id, clock)
   303  	s.Require().NoError(err, "SetSyncClock")
   304  
   305  	// retrieve row from db synced_at must not be zero
   306  	rcr, err = s.db.getRawCommunityRow(sc.Id)
   307  	s.Require().NoError(err, "getRawCommunityRow")
   308  	s.NotZero(rcr.SyncedAt, "synced_at must be zero value")
   309  
   310  	// retrieve synced row from db, should succeed
   311  	src, err = s.db.getSyncedRawCommunity(sc.Id)
   312  	s.Require().NoError(err)
   313  	s.NotNil(src)
   314  	s.Equal(clock, src.SyncedAt)
   315  }
   316  
   317  func (s *PersistenceSuite) TestGetCommunitiesSettings() {
   318  	settings := []CommunitySettings{
   319  		{CommunityID: "0x01", HistoryArchiveSupportEnabled: false},
   320  		{CommunityID: "0x02", HistoryArchiveSupportEnabled: true},
   321  		{CommunityID: "0x03", HistoryArchiveSupportEnabled: false},
   322  	}
   323  
   324  	for i := range settings {
   325  		stg := settings[i]
   326  		err := s.db.SaveCommunitySettings(stg)
   327  		s.Require().NoError(err)
   328  	}
   329  
   330  	rst, err := s.db.GetCommunitiesSettings()
   331  	s.Require().NoError(err)
   332  	s.Equal(settings, rst)
   333  }
   334  
   335  func (s *PersistenceSuite) TestSaveCommunitySettings() {
   336  	settings := CommunitySettings{CommunityID: "0x01", HistoryArchiveSupportEnabled: false}
   337  	err := s.db.SaveCommunitySettings(settings)
   338  	s.Require().NoError(err)
   339  	rst, err := s.db.GetCommunitiesSettings()
   340  	s.Require().NoError(err)
   341  	s.Equal(1, len(rst))
   342  }
   343  
   344  func (s *PersistenceSuite) TestDeleteCommunitySettings() {
   345  	settings := CommunitySettings{CommunityID: "0x01", HistoryArchiveSupportEnabled: false}
   346  
   347  	err := s.db.SaveCommunitySettings(settings)
   348  	s.Require().NoError(err)
   349  
   350  	rst, err := s.db.GetCommunitiesSettings()
   351  	s.Require().NoError(err)
   352  	s.Equal(1, len(rst))
   353  	s.Require().NoError(s.db.DeleteCommunitySettings(types.HexBytes{0x01}))
   354  	rst2, err := s.db.GetCommunitiesSettings()
   355  	s.Require().NoError(err)
   356  	s.Equal(0, len(rst2))
   357  }
   358  
   359  func (s *PersistenceSuite) TestUpdateCommunitySettings() {
   360  	settings := []CommunitySettings{
   361  		{CommunityID: "0x01", HistoryArchiveSupportEnabled: true},
   362  		{CommunityID: "0x02", HistoryArchiveSupportEnabled: false},
   363  	}
   364  
   365  	s.Require().NoError(s.db.SaveCommunitySettings(settings[0]))
   366  	s.Require().NoError(s.db.SaveCommunitySettings(settings[1]))
   367  
   368  	settings[0].HistoryArchiveSupportEnabled = true
   369  	settings[1].HistoryArchiveSupportEnabled = false
   370  
   371  	s.Require().NoError(s.db.UpdateCommunitySettings(settings[0]))
   372  	s.Require().NoError(s.db.UpdateCommunitySettings(settings[1]))
   373  
   374  	rst, err := s.db.GetCommunitiesSettings()
   375  	s.Require().NoError(err)
   376  	s.Equal(settings, rst)
   377  }
   378  
   379  func (s *PersistenceSuite) TestGetCommunityToken() {
   380  	tokens, err := s.db.GetCommunityTokens("123")
   381  	s.Require().NoError(err)
   382  	s.Require().Len(tokens, 0)
   383  
   384  	tokenERC721 := token.CommunityToken{
   385  		CommunityID:        "123",
   386  		TokenType:          protobuf.CommunityTokenType_ERC721,
   387  		Address:            "0x123",
   388  		Name:               "StatusToken",
   389  		Symbol:             "STT",
   390  		Description:        "desc",
   391  		Supply:             &bigint.BigInt{Int: big.NewInt(123)},
   392  		InfiniteSupply:     false,
   393  		Transferable:       true,
   394  		RemoteSelfDestruct: true,
   395  		ChainID:            1,
   396  		DeployState:        token.InProgress,
   397  		Base64Image:        "ABCD",
   398  		TransactionHash:    "0x1234",
   399  		Version:            "1.0.0",
   400  	}
   401  
   402  	err = s.db.AddCommunityToken(&tokenERC721)
   403  	s.Require().NoError(err)
   404  
   405  	token, err := s.db.GetCommunityToken("123", 1, "0x123")
   406  	s.Require().NoError(err)
   407  	s.Require().Equal(&tokenERC721, token)
   408  }
   409  
   410  func (s *PersistenceSuite) TestGetCommunityTokens() {
   411  	tokens, err := s.db.GetCommunityTokens("123")
   412  	s.Require().NoError(err)
   413  	s.Require().Len(tokens, 0)
   414  
   415  	tokenERC721 := token.CommunityToken{
   416  		CommunityID:        "123",
   417  		TokenType:          protobuf.CommunityTokenType_ERC721,
   418  		Address:            "0x123",
   419  		Name:               "StatusToken",
   420  		Symbol:             "STT",
   421  		Description:        "desc",
   422  		Supply:             &bigint.BigInt{Int: big.NewInt(123)},
   423  		InfiniteSupply:     false,
   424  		Transferable:       true,
   425  		RemoteSelfDestruct: true,
   426  		ChainID:            1,
   427  		DeployState:        token.InProgress,
   428  		Base64Image:        "ABCD",
   429  		Deployer:           "0xDep1",
   430  		PrivilegesLevel:    token.OwnerLevel,
   431  		TransactionHash:    "0x1234",
   432  		Version:            "1.0.0",
   433  	}
   434  
   435  	tokenERC20 := token.CommunityToken{
   436  		CommunityID:        "345",
   437  		TokenType:          protobuf.CommunityTokenType_ERC20,
   438  		Address:            "0x345",
   439  		Name:               "StatusToken",
   440  		Symbol:             "STT",
   441  		Description:        "desc",
   442  		Supply:             &bigint.BigInt{Int: big.NewInt(345)},
   443  		InfiniteSupply:     false,
   444  		Transferable:       true,
   445  		RemoteSelfDestruct: true,
   446  		ChainID:            2,
   447  		DeployState:        token.Failed,
   448  		Base64Image:        "QWERTY",
   449  		Decimals:           21,
   450  		Deployer:           "0xDep2",
   451  		PrivilegesLevel:    token.CommunityLevel,
   452  		TransactionHash:    "0x123456",
   453  		Version:            "2.0.0",
   454  	}
   455  
   456  	err = s.db.AddCommunityToken(&tokenERC721)
   457  	s.Require().NoError(err)
   458  	err = s.db.AddCommunityToken(&tokenERC20)
   459  	s.Require().NoError(err)
   460  
   461  	tokens, err = s.db.GetCommunityTokens("123")
   462  	s.Require().NoError(err)
   463  	s.Require().Len(tokens, 1)
   464  	s.Require().Equal(tokenERC721, *tokens[0])
   465  
   466  	err = s.db.UpdateCommunityTokenState(1, "0x123", token.Deployed)
   467  	s.Require().NoError(err)
   468  	tokens, err = s.db.GetCommunityTokens("123")
   469  	s.Require().NoError(err)
   470  	s.Require().Len(tokens, 1)
   471  	s.Require().Equal(token.Deployed, tokens[0].DeployState)
   472  
   473  	tokens, err = s.db.GetCommunityTokens("345")
   474  	s.Require().NoError(err)
   475  	s.Require().Len(tokens, 1)
   476  	s.Require().Equal(tokenERC20, *tokens[0])
   477  
   478  	err = s.db.UpdateCommunityTokenAddress(1, "0x123", "0x123-newAddr")
   479  	s.Require().NoError(err)
   480  	tokens, err = s.db.GetCommunityTokens("123")
   481  	s.Require().NoError(err)
   482  	s.Require().Len(tokens, 1)
   483  	s.Require().Equal("0x123-newAddr", tokens[0].Address)
   484  }
   485  
   486  func (s *PersistenceSuite) TestSaveCheckChannelPermissionResponse() {
   487  
   488  	viewAndPostPermissionResults := make(map[string]*PermissionTokenCriteriaResult)
   489  	viewAndPostPermissionResults["one"] = &PermissionTokenCriteriaResult{
   490  		Criteria: []bool{true, true, true, true},
   491  	}
   492  	viewAndPostPermissionResults["two"] = &PermissionTokenCriteriaResult{
   493  		Criteria: []bool{false},
   494  	}
   495  	chatID := "some-chat-id"
   496  	communityID := "some-community-id"
   497  
   498  	checkChannelPermissionResponse := &CheckChannelPermissionsResponse{
   499  		ViewOnlyPermissions: &CheckChannelViewOnlyPermissionsResult{
   500  			Satisfied:   true,
   501  			Permissions: make(map[string]*PermissionTokenCriteriaResult),
   502  		},
   503  		ViewAndPostPermissions: &CheckChannelViewAndPostPermissionsResult{
   504  			Satisfied:   true,
   505  			Permissions: viewAndPostPermissionResults,
   506  		},
   507  	}
   508  
   509  	err := s.db.SaveCheckChannelPermissionResponse(communityID, chatID, checkChannelPermissionResponse)
   510  	s.Require().NoError(err)
   511  
   512  	responses, err := s.db.GetCheckChannelPermissionResponses(communityID)
   513  	s.Require().NoError(err)
   514  	s.Require().Len(responses, 1)
   515  	s.Require().NotNil(responses[chatID])
   516  	s.Require().True(responses[chatID].ViewOnlyPermissions.Satisfied)
   517  	s.Require().Len(responses[chatID].ViewOnlyPermissions.Permissions, 0)
   518  	s.Require().True(responses[chatID].ViewAndPostPermissions.Satisfied)
   519  	s.Require().Len(responses[chatID].ViewAndPostPermissions.Permissions, 2)
   520  	s.Require().Equal(responses[chatID].ViewAndPostPermissions.Permissions["one"].Criteria, []bool{true, true, true, true})
   521  	s.Require().Equal(responses[chatID].ViewAndPostPermissions.Permissions["two"].Criteria, []bool{false})
   522  }
   523  
   524  func (s *PersistenceSuite) TestGetCommunityRequestsToJoinWithRevealedAddresses() {
   525  	clock := uint64(time.Now().Unix())
   526  	communityID := types.HexBytes{7, 7, 7, 7, 7, 7, 7, 7}
   527  	revealedAddresses := []string{"address1", "address2", "address3"}
   528  	chainIds := []uint64{1, 2}
   529  
   530  	// No data in database
   531  	rtjResult, err := s.db.GetCommunityRequestsToJoinWithRevealedAddresses(communityID)
   532  	s.Require().NoError(err, "GetCommunityRequestsToJoinWithRevealedAddresses shouldn't give any error")
   533  	s.Require().Len(rtjResult, 0)
   534  
   535  	// RTJ with 2 revealed Addresses
   536  	expectedRtj1 := &RequestToJoin{
   537  		ID:          types.HexBytes{1, 2, 3, 4, 5, 6, 7, 8},
   538  		PublicKey:   common.PubkeyToHex(&s.identity.PublicKey),
   539  		Clock:       clock,
   540  		CommunityID: communityID,
   541  		State:       RequestToJoinStateAccepted,
   542  		RevealedAccounts: []*protobuf.RevealedAccount{
   543  			{
   544  				Address: revealedAddresses[0],
   545  			},
   546  			{
   547  				Address: revealedAddresses[1],
   548  			},
   549  		},
   550  	}
   551  	err = s.db.SaveRequestToJoin(expectedRtj1)
   552  	s.Require().NoError(err, "SaveRequestToJoin shouldn't give any error")
   553  
   554  	err = s.db.SaveRequestToJoinRevealedAddresses(expectedRtj1.ID, expectedRtj1.RevealedAccounts)
   555  	s.Require().NoError(err, "SaveRequestToJoinRevealedAddresses shouldn't give any error")
   556  
   557  	rtjResult, err = s.db.GetCommunityRequestsToJoinWithRevealedAddresses(communityID)
   558  	s.Require().NoError(err, "GetCommunityRequestsToJoinWithRevealedAddresses shouldn't give any error")
   559  	s.Require().Len(rtjResult, 1)
   560  	s.Require().Equal(expectedRtj1.ID, rtjResult[0].ID)
   561  	s.Require().Equal(expectedRtj1.PublicKey, rtjResult[0].PublicKey)
   562  	s.Require().Equal(expectedRtj1.Clock, rtjResult[0].Clock)
   563  	s.Require().Equal(expectedRtj1.CommunityID, rtjResult[0].CommunityID)
   564  	s.Require().Len(rtjResult[0].RevealedAccounts, 2)
   565  
   566  	for index, account := range rtjResult[0].RevealedAccounts {
   567  		s.Require().Equal(revealedAddresses[index], account.Address)
   568  	}
   569  
   570  	// RTJ with 1 revealed Address, ChainIds, IsAirdropAddress and Signature
   571  	signature := []byte("test")
   572  	expectedRtj2 := &RequestToJoin{
   573  		ID:          types.HexBytes{8, 7, 6, 5, 4, 3, 2, 1},
   574  		PublicKey:   common.PubkeyToHex(&s.identity.PublicKey),
   575  		Clock:       clock,
   576  		CommunityID: communityID,
   577  		State:       RequestToJoinStateAccepted,
   578  		RevealedAccounts: []*protobuf.RevealedAccount{
   579  			{
   580  				Address:          revealedAddresses[2],
   581  				ChainIds:         chainIds,
   582  				IsAirdropAddress: true,
   583  				Signature:        signature,
   584  			},
   585  		},
   586  	}
   587  	err = s.db.SaveRequestToJoin(expectedRtj2)
   588  	s.Require().NoError(err, "SaveRequestToJoin shouldn't give any error")
   589  
   590  	err = s.db.SaveRequestToJoinRevealedAddresses(expectedRtj2.ID, expectedRtj2.RevealedAccounts)
   591  	s.Require().NoError(err, "SaveRequestToJoinRevealedAddresses shouldn't give any error")
   592  
   593  	rtjResult, err = s.db.GetCommunityRequestsToJoinWithRevealedAddresses(communityID)
   594  	s.Require().NoError(err, "GetCommunityRequestsToJoinWithRevealedAddresses shouldn't give any error")
   595  	s.Require().Len(rtjResult, 2)
   596  
   597  	s.Require().Len(rtjResult[1].RevealedAccounts, 1)
   598  	s.Require().Equal(revealedAddresses[2], rtjResult[1].RevealedAccounts[0].Address)
   599  	s.Require().Equal(chainIds, rtjResult[1].RevealedAccounts[0].ChainIds)
   600  	s.Require().Equal(true, rtjResult[1].RevealedAccounts[0].IsAirdropAddress)
   601  	s.Require().Equal(rtjResult[1].RevealedAccounts[0].Signature, signature)
   602  
   603  	// RTJ without RevealedAccounts
   604  	expectedRtjWithoutRevealedAccounts := &RequestToJoin{
   605  		ID:          types.HexBytes{1, 6, 6, 6, 6, 6, 6, 6},
   606  		PublicKey:   common.PubkeyToHex(&s.identity.PublicKey),
   607  		Clock:       clock,
   608  		CommunityID: communityID,
   609  		State:       RequestToJoinStateAccepted,
   610  	}
   611  	err = s.db.SaveRequestToJoin(expectedRtjWithoutRevealedAccounts)
   612  	s.Require().NoError(err, "SaveRequestToJoin shouldn't give any error")
   613  
   614  	rtjResult, err = s.db.GetCommunityRequestsToJoinWithRevealedAddresses(communityID)
   615  	s.Require().NoError(err, "GetCommunityRequestsToJoinWithRevealedAddresses shouldn't give any error")
   616  	s.Require().Len(rtjResult, 3)
   617  
   618  	s.Require().Len(rtjResult[2].RevealedAccounts, 0)
   619  
   620  	// RTJ with RevealedAccount but with empty Address
   621  	expectedRtjWithEmptyAddress := &RequestToJoin{
   622  		ID:          types.HexBytes{2, 6, 6, 6, 6, 6, 6, 6},
   623  		PublicKey:   common.PubkeyToHex(&s.identity.PublicKey),
   624  		Clock:       clock,
   625  		CommunityID: communityID,
   626  		State:       RequestToJoinStateAccepted,
   627  		RevealedAccounts: []*protobuf.RevealedAccount{
   628  			{
   629  				Address: "",
   630  			},
   631  		},
   632  	}
   633  	err = s.db.SaveRequestToJoin(expectedRtjWithEmptyAddress)
   634  	s.Require().NoError(err, "SaveRequestToJoin shouldn't give any error")
   635  
   636  	rtjResult, err = s.db.GetCommunityRequestsToJoinWithRevealedAddresses(communityID)
   637  	s.Require().NoError(err, "GetCommunityRequestsToJoinWithRevealedAddresses shouldn't give any error")
   638  	s.Require().Len(rtjResult, 4)
   639  	s.Require().Len(rtjResult[3].RevealedAccounts, 0)
   640  }
   641  
   642  func (s *PersistenceSuite) TestCuratedCommunities() {
   643  	communities, err := s.db.GetCuratedCommunities()
   644  	s.Require().NoError(err)
   645  	s.Require().Empty(communities.ContractCommunities)
   646  	s.Require().Empty(communities.ContractFeaturedCommunities)
   647  
   648  	setCommunities := &CuratedCommunities{
   649  		ContractCommunities:         []string{"x", "d"},
   650  		ContractFeaturedCommunities: []string{"x"},
   651  	}
   652  
   653  	err = s.db.SetCuratedCommunities(setCommunities)
   654  	s.Require().NoError(err)
   655  
   656  	communities, err = s.db.GetCuratedCommunities()
   657  	s.Require().NoError(err)
   658  	s.Require().True(reflect.DeepEqual(communities, setCommunities))
   659  
   660  	setCommunities = &CuratedCommunities{
   661  		ContractCommunities:         []string{"p", "a", "t", "r", "y", "k"},
   662  		ContractFeaturedCommunities: []string{"p", "k"},
   663  	}
   664  
   665  	err = s.db.SetCuratedCommunities(setCommunities)
   666  	s.Require().NoError(err)
   667  
   668  	communities, err = s.db.GetCuratedCommunities()
   669  	s.Require().NoError(err)
   670  	s.Require().True(reflect.DeepEqual(communities, setCommunities))
   671  }
   672  
   673  func (s *PersistenceSuite) TestGetCommunityRequestToJoinWithRevealedAddresses() {
   674  	clock := uint64(time.Now().Unix())
   675  	communityID := types.HexBytes{7, 7, 7, 7, 7, 7, 7, 7}
   676  	revealedAddresses := []string{"address1", "address2", "address3"}
   677  	chainIds := []uint64{1, 2}
   678  	publicKey := common.PubkeyToHex(&s.identity.PublicKey)
   679  	signature := []byte("test")
   680  
   681  	// No data in database
   682  	_, err := s.db.GetCommunityRequestToJoinWithRevealedAddresses(publicKey, communityID)
   683  	s.Require().ErrorIs(err, sql.ErrNoRows)
   684  
   685  	// RTJ with 2 withoutRevealed Addresses
   686  	expectedRtj := &RequestToJoin{
   687  		ID:          types.HexBytes{1, 2, 3, 4, 5, 6, 7, 8},
   688  		PublicKey:   publicKey,
   689  		Clock:       clock,
   690  		CommunityID: communityID,
   691  		State:       RequestToJoinStateAccepted,
   692  		RevealedAccounts: []*protobuf.RevealedAccount{
   693  			{
   694  				Address:          revealedAddresses[2],
   695  				ChainIds:         chainIds,
   696  				IsAirdropAddress: true,
   697  				Signature:        signature,
   698  			},
   699  		},
   700  	}
   701  	err = s.db.SaveRequestToJoin(expectedRtj)
   702  	s.Require().NoError(err, "SaveRequestToJoin shouldn't give any error")
   703  
   704  	// check that there will be no error if revealed account is absent
   705  	rtjResult, err := s.db.GetCommunityRequestToJoinWithRevealedAddresses(publicKey, communityID)
   706  	s.Require().NoError(err, "RevealedAccounts empty, shouldn't give any error")
   707  
   708  	s.Require().Len(rtjResult.RevealedAccounts, 0)
   709  
   710  	// save revealed accounts for previous request to join
   711  	err = s.db.SaveRequestToJoinRevealedAddresses(expectedRtj.ID, expectedRtj.RevealedAccounts)
   712  	s.Require().NoError(err)
   713  
   714  	rtjResult, err = s.db.GetCommunityRequestToJoinWithRevealedAddresses(publicKey, communityID)
   715  	s.Require().NoError(err)
   716  	s.Require().Equal(expectedRtj.ID, rtjResult.ID)
   717  	s.Require().Equal(expectedRtj.PublicKey, rtjResult.PublicKey)
   718  	s.Require().Equal(expectedRtj.Clock, rtjResult.Clock)
   719  	s.Require().Equal(expectedRtj.CommunityID, rtjResult.CommunityID)
   720  	s.Require().Len(rtjResult.RevealedAccounts, 1)
   721  }
   722  
   723  func (s *PersistenceSuite) TestAllNonApprovedCommunitiesRequestsToJoin() {
   724  	// check on empty db
   725  	result, err := s.db.AllNonApprovedCommunitiesRequestsToJoin()
   726  	s.Require().NoError(err)
   727  	s.Require().Len(result, 0)
   728  
   729  	identity, err := crypto.GenerateKey()
   730  	s.Require().NoError(err, "crypto.GenerateKey shouldn't give any error")
   731  
   732  	clock := uint64(time.Now().Unix())
   733  
   734  	// add a new community
   735  	community := s.makeNewCommunity(identity)
   736  	s.Require().NoError(err)
   737  
   738  	// add requests to join to the community
   739  	allStates := []RequestToJoinState{
   740  		RequestToJoinStatePending,
   741  		RequestToJoinStateDeclined,
   742  		RequestToJoinStateAccepted,
   743  		RequestToJoinStateCanceled,
   744  		RequestToJoinStateAcceptedPending,
   745  		RequestToJoinStateDeclinedPending,
   746  		RequestToJoinStateAwaitingAddresses,
   747  	}
   748  
   749  	for i := range allStates {
   750  		identity, err := crypto.GenerateKey()
   751  		s.Require().NoError(err)
   752  
   753  		rtj := &RequestToJoin{
   754  			ID:          types.HexBytes{1, 2, 3, 4, 5, 6, 7, byte(i)},
   755  			PublicKey:   common.PubkeyToHex(&identity.PublicKey),
   756  			Clock:       clock,
   757  			CommunityID: community.ID(),
   758  			State:       allStates[i],
   759  		}
   760  		err = s.db.SaveRequestToJoin(rtj)
   761  		s.Require().NoError(err, "SaveRequestToJoin shouldn't give any error")
   762  	}
   763  
   764  	result, err = s.db.AllNonApprovedCommunitiesRequestsToJoin()
   765  	s.Require().NoError(err)
   766  	s.Require().Len(result, 6) // all except RequestToJoinStateAccepted
   767  }
   768  
   769  func (s *PersistenceSuite) TestSaveShardInfo() {
   770  	communityID := types.HexBytes{1, 2, 3, 4, 5, 6, 7, 8}
   771  	clock := uint64(1)
   772  	// get non existing community shard
   773  	resultShard, err := s.db.GetCommunityShard(communityID)
   774  	s.Require().Error(err, sql.ErrNoRows)
   775  	s.Require().Nil(resultShard)
   776  
   777  	// shard info is nil
   778  	err = s.db.SaveCommunityShard(communityID, nil, clock)
   779  	s.Require().NoError(err)
   780  
   781  	// save shard info with the same clock
   782  	err = s.db.SaveCommunityShard(communityID, nil, clock)
   783  	s.Require().Error(err, ErrOldShardInfo)
   784  
   785  	resultShard, err = s.db.GetCommunityShard(communityID)
   786  	s.Require().NoError(err)
   787  	s.Require().Nil(resultShard)
   788  
   789  	// not nil shard
   790  	expectedShard := &shard.Shard{
   791  		Cluster: 1,
   792  		Index:   2,
   793  	}
   794  
   795  	// save shard info with the same clock and check that data was not modified
   796  	err = s.db.SaveCommunityShard(communityID, expectedShard, clock)
   797  	s.Require().Error(err, ErrOldShardInfo)
   798  	resultShard, err = s.db.GetCommunityShard(communityID)
   799  	s.Require().NoError(err)
   800  	s.Require().Nil(resultShard)
   801  
   802  	// update the clock and save the shard info
   803  	clock += clock
   804  	err = s.db.SaveCommunityShard(communityID, expectedShard, clock)
   805  	s.Require().NoError(err)
   806  	resultShard, err = s.db.GetCommunityShard(communityID)
   807  	s.Require().NoError(err)
   808  	s.Require().NotNil(resultShard)
   809  	s.Require().Equal(expectedShard, resultShard)
   810  
   811  	// check shard deleting
   812  	err = s.db.DeleteCommunityShard(communityID)
   813  	s.Require().NoError(err)
   814  	resultShard, err = s.db.GetCommunityShard(communityID)
   815  	s.Require().Error(err, sql.ErrNoRows)
   816  	s.Require().Nil(resultShard)
   817  }
   818  
   819  func (s *PersistenceSuite) TestGetCommunityToValidateByID() {
   820  	communityID := types.HexBytes{1, 2, 3, 4, 5, 6, 7, 8}
   821  
   822  	result, err := s.db.getCommunityToValidateByID(communityID)
   823  	s.Require().NoError(err)
   824  	s.Require().Len(result, 0)
   825  }
   826  
   827  func (s *PersistenceSuite) TestProcessedCommunityEvents() {
   828  	community := types.HexBytes{1}
   829  	events, err := s.db.GetAppliedCommunityEvents(community)
   830  	s.Require().NoError(err)
   831  	s.Require().Empty(events)
   832  
   833  	err = s.db.UpsertAppliedCommunityEvents(community, map[string]uint64{"a": 1, "b": 10})
   834  	s.Require().NoError(err)
   835  
   836  	events, err = s.db.GetAppliedCommunityEvents(community)
   837  	s.Require().NoError(err)
   838  	s.Require().Len(events, 2)
   839  	s.Require().True(reflect.DeepEqual(events, map[string]uint64{"a": 1, "b": 10}))
   840  
   841  	err = s.db.UpsertAppliedCommunityEvents(community, map[string]uint64{"a": 2, "b": 8, "c": 1})
   842  	s.Require().NoError(err)
   843  
   844  	events, err = s.db.GetAppliedCommunityEvents(community)
   845  	s.Require().NoError(err)
   846  	s.Require().Len(events, 3)
   847  	s.Require().True(reflect.DeepEqual(events, map[string]uint64{"a": 2, "b": 10, "c": 1}))
   848  }
   849  
   850  func (s *PersistenceSuite) TestDecryptedCommunityCache() {
   851  	communityDescription := &protobuf.CommunityDescription{
   852  		Clock: 1000,
   853  	}
   854  	keyID1 := []byte("key-id-1")
   855  	keyID2 := []byte("key-id-2")
   856  	missingKeys := []*CommunityPrivateDataFailedToDecrypt{
   857  		{KeyID: keyID1},
   858  		{KeyID: keyID2},
   859  	}
   860  	communityID := []byte("id")
   861  	err := s.db.SaveDecryptedCommunityDescription(communityID, missingKeys, communityDescription)
   862  	s.Require().NoError(err)
   863  
   864  	// Can be retrieved
   865  	retrievedCommunity, err := s.db.GetDecryptedCommunityDescription(communityID, 1000)
   866  	s.Require().NoError(err)
   867  	s.Require().True(proto.Equal(communityDescription, retrievedCommunity))
   868  
   869  	// Retrieving a random one doesn't throw an error
   870  	retrievedCommunity, err = s.db.GetDecryptedCommunityDescription([]byte("non-existent-id"), 1000)
   871  	s.Require().NoError(err)
   872  	s.Require().Nil(retrievedCommunity)
   873  
   874  	// Retrieving a random one doesn't throw an error
   875  	retrievedCommunity, err = s.db.GetDecryptedCommunityDescription(communityID, 999)
   876  	s.Require().NoError(err)
   877  	s.Require().Nil(retrievedCommunity)
   878  
   879  	// invalidating the cache
   880  	err = s.db.InvalidateDecryptedCommunityCacheForKeys([]*encryption.HashRatchetInfo{{KeyID: keyID1}})
   881  	s.Require().NoError(err)
   882  
   883  	// community cannot be retrieved anymore
   884  	retrievedCommunity, err = s.db.GetDecryptedCommunityDescription(communityID, 1000)
   885  	s.Require().NoError(err)
   886  	s.Require().Nil(retrievedCommunity)
   887  
   888  	// make sure everything is cleaned up
   889  
   890  	qr := s.db.db.QueryRow("SELECT COUNT(*) FROM encrypted_community_description_missing_keys")
   891  
   892  	var count int
   893  
   894  	err = qr.Scan(&count)
   895  	s.Require().NoError(err)
   896  	s.Require().Equal(count, 0)
   897  
   898  }
   899  
   900  func (s *PersistenceSuite) TestDecryptedCommunityCacheClock() {
   901  	communityDescription := &protobuf.CommunityDescription{
   902  		Clock: 1000,
   903  	}
   904  	keyID1 := []byte("key-id-1")
   905  	keyID2 := []byte("key-id-2")
   906  	keyID3 := []byte("key-id-3")
   907  
   908  	missingKeys := []*CommunityPrivateDataFailedToDecrypt{
   909  		{KeyID: keyID1},
   910  		{KeyID: keyID2},
   911  	}
   912  	communityID := []byte("id")
   913  	err := s.db.SaveDecryptedCommunityDescription(communityID, missingKeys, communityDescription)
   914  	s.Require().NoError(err)
   915  
   916  	// Can be retrieved
   917  	retrievedCommunity, err := s.db.GetDecryptedCommunityDescription(communityID, 1000)
   918  	s.Require().NoError(err)
   919  	s.Require().True(proto.Equal(communityDescription, retrievedCommunity))
   920  
   921  	// Save an earlier community
   922  	communityDescription.Clock = 999
   923  	err = s.db.SaveDecryptedCommunityDescription(communityID, missingKeys, communityDescription)
   924  	s.Require().NoError(err)
   925  
   926  	// The old one should be retrieved
   927  	retrievedCommunity, err = s.db.GetDecryptedCommunityDescription(communityID, 1000)
   928  	s.Require().NoError(err)
   929  	s.Require().NotNil(retrievedCommunity)
   930  	s.Require().Equal(uint64(1000), retrievedCommunity.Clock)
   931  
   932  	// Save a later community, with a single key
   933  	missingKeys = []*CommunityPrivateDataFailedToDecrypt{
   934  		{KeyID: keyID3},
   935  	}
   936  
   937  	communityDescription.Clock = 1001
   938  	err = s.db.SaveDecryptedCommunityDescription(communityID, missingKeys, communityDescription)
   939  	s.Require().NoError(err)
   940  
   941  	// The new one should be retrieved
   942  	retrievedCommunity, err = s.db.GetDecryptedCommunityDescription(communityID, 1001)
   943  	s.Require().NoError(err)
   944  	s.Require().Equal(uint64(1001), retrievedCommunity.Clock)
   945  
   946  	// Make sure the previous two are cleaned up and there's only one left
   947  	qr := s.db.db.QueryRow("SELECT COUNT(*) FROM encrypted_community_description_missing_keys")
   948  
   949  	var count int
   950  
   951  	err = qr.Scan(&count)
   952  	s.Require().NoError(err)
   953  	s.Require().Equal(count, 1)
   954  }
   955  
   956  func (s *PersistenceSuite) TestGetCommunityRequestsToJoinRevealedAddresses() {
   957  	clock := uint64(time.Now().Unix())
   958  	communityID := types.HexBytes{7, 7, 7, 7, 7, 7, 7, 7}
   959  	revealedAddress := "address1"
   960  	chainIds := []uint64{1, 2}
   961  	publicKey := common.PubkeyToHex(&s.identity.PublicKey)
   962  	signature := []byte("test")
   963  
   964  	// No data in database
   965  	accounts, err := s.db.GetCommunityRequestsToJoinRevealedAddresses(communityID)
   966  	s.Require().NoError(err)
   967  	_, exists := accounts[publicKey]
   968  	s.Require().False(exists)
   969  
   970  	expectedRtj := &RequestToJoin{
   971  		ID:          types.HexBytes{1, 2, 3, 4, 5, 6, 7, 8},
   972  		PublicKey:   publicKey,
   973  		Clock:       clock,
   974  		CommunityID: communityID,
   975  		State:       RequestToJoinStateAccepted,
   976  		RevealedAccounts: []*protobuf.RevealedAccount{
   977  			{
   978  				Address:          revealedAddress,
   979  				ChainIds:         chainIds,
   980  				IsAirdropAddress: true,
   981  				Signature:        signature,
   982  			},
   983  		},
   984  	}
   985  
   986  	// Request to join was stored without revealed account
   987  	err = s.db.SaveRequestToJoin(expectedRtj)
   988  	s.Require().NoError(err, "SaveRequestToJoin shouldn't give any error")
   989  
   990  	// revealed account is absent
   991  	accounts, err = s.db.GetCommunityRequestsToJoinRevealedAddresses(communityID)
   992  	s.Require().NoError(err, "RevealedAccounts empty, shouldn't give any error")
   993  
   994  	_, exists = accounts[publicKey]
   995  	s.Require().False(exists)
   996  
   997  	// save revealed accounts for the previous request to join
   998  	err = s.db.SaveRequestToJoinRevealedAddresses(expectedRtj.ID, expectedRtj.RevealedAccounts)
   999  	s.Require().NoError(err)
  1000  
  1001  	accounts, err = s.db.GetCommunityRequestsToJoinRevealedAddresses(communityID)
  1002  	s.Require().NoError(err)
  1003  	memberAccounts, exists := accounts[publicKey]
  1004  	s.Require().True(exists)
  1005  	s.Require().Len(memberAccounts, 1)
  1006  }