Skip to content

Commit

Permalink
chore: test archive import of encrypted channel
Browse files Browse the repository at this point in the history
  • Loading branch information
igor-sirotin committed Apr 19, 2024
1 parent b3ad7fc commit b18626c
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 0 deletions.
4 changes: 4 additions & 0 deletions protocol/communities/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -4474,6 +4474,10 @@ func (m *Manager) DownloadHistoryArchivesByMagnetlink(communityID types.HexBytes
}
}

func (m *Manager) SaveMessageArchiveID(communityID types.HexBytes, hash string) error {
return m.persistence.SaveMessageArchiveID(communityID, hash)
}

func (m *Manager) GetMessageArchiveIDsToImport(communityID types.HexBytes) ([]string, error) {
return m.persistence.GetMessageArchiveIDsToImport(communityID)
}
Expand Down
164 changes: 164 additions & 0 deletions protocol/communities_messenger_token_permissions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,27 @@ import (
"crypto/ecdsa"
"errors"
"math/big"
"os"
"strings"
"sync"
"testing"
"time"

"github.com/stretchr/testify/suite"
"go.uber.org/zap"
"golang.org/x/exp/maps"

gethcommon "github.com/ethereum/go-ethereum/common"
hexutil "github.com/ethereum/go-ethereum/common/hexutil"
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/communities"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/protocol/requests"
"github.com/status-im/status-go/protocol/transport"
"github.com/status-im/status-go/protocol/tt"
)

Expand Down Expand Up @@ -1675,3 +1679,163 @@ func checkRoleBasedOnThePermissionType(permissionType protobuf.CommunityTokenPer
panic("Unknown permission, please, update the test")
}
}

func (s *MessengerCommunitiesTokenPermissionsSuite) TestImportDecryptedArchiveMessages() {
// 1.1. Create community
community, chat := s.createCommunity()

// 1.2. Setup permissions
communityPermission := &requests.CreateCommunityTokenPermission{
CommunityID: community.ID(),
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: []*protobuf.TokenCriteria{
{
Type: protobuf.CommunityTokenType_ERC20,
ContractAddresses: map[uint64]string{testChainID1: "0x124"},
Symbol: "TEST2",
AmountInWei: "100000000000000000000",
Decimals: uint64(18),
},
},
}

channelPermission := &requests.CreateCommunityTokenPermission{
CommunityID: community.ID(),
Type: protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL,
ChatIds: []string{chat.ID},
TokenCriteria: []*protobuf.TokenCriteria{
{
Type: protobuf.CommunityTokenType_ERC20,
ContractAddresses: map[uint64]string{testChainID1: "0x124"},
Symbol: "TEST2",
AmountInWei: "200000000000000000000",
Decimals: uint64(18),
},
},
}

waitOnCommunityPermissionCreated := waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
return len(sub.Community.TokenPermissions()) == 2
})

response, err := s.owner.CreateCommunityTokenPermission(communityPermission)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)

response, err = s.owner.CreateCommunityTokenPermission(channelPermission)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)

community = response.Communities()[0]
s.Require().True(community.HasTokenPermissions())
s.Require().Len(community.TokenPermissions(), 2)

err = <-waitOnCommunityPermissionCreated
s.Require().NoError(err)
s.Require().True(community.Encrypted())

// 2. Owner: Send a message A
messageText1 := RandomLettersString(10)
message1 := s.sendChatMessage(s.owner, chat.ID, messageText1)

// 2.2. Retrieve own message (to make it stored in the archive later)
_, err = s.owner.RetrieveAll()
s.Require().NoError(err)

// 3. Owner: Create community archive
const partition = 2 * time.Minute
messageDate := time.UnixMilli(int64(message1.Timestamp))
startDate := messageDate.Add(-time.Minute)
endDate := messageDate.Add(time.Minute)
topic := types.BytesToTopic(transport.ToTopic(chat.ID))
topics := []types.TopicType{topic}

torrentConfig := params.TorrentConfig{
Enabled: true,
DataDir: os.TempDir() + "/archivedata",
TorrentDir: os.TempDir() + "/torrents",
Port: 0,
}
// Share archive directory between all users
s.owner.communitiesManager.SetTorrentConfig(&torrentConfig)
s.bob.communitiesManager.SetTorrentConfig(&torrentConfig)
s.owner.config.messengerSignalsHandler = &MessengerSignalsHandlerMock{}
s.bob.config.messengerSignalsHandler = &MessengerSignalsHandlerMock{}

archiveIDs, err := s.owner.communitiesManager.CreateHistoryArchiveTorrentFromDB(community.ID(), topics, startDate, endDate, partition, community.Encrypted())
s.Require().NoError(err)
s.Require().Len(archiveIDs, 1)
s.logger.Debug("archive created", zap.Any("archiveIDs", archiveIDs))

// 4. Bob: join community (satisfying membership, but not channel permissions)
s.makeAddressSatisfyTheCriteria(testChainID1, bobAddress, communityPermission.TokenCriteria[0])
s.advertiseCommunityTo(community, s.bob)
s.joinCommunity(community, s.bob, bobPassword, []string{})

// 5. Bob: Import community archive
// The archive is successfully decrypted, but the message inside is not.
// https://github.com/status-im/status-desktop/issues/13105 can be reproduced at this stage
// by forcing `encryption.ErrHashRatchetGroupIDNotFound` in `ExtractMessagesFromHistoryArchive` after decryption here:
// https://github.com/status-im/status-go/blob/6c82a6c2be7ebed93bcae3b9cf5053da3820de50/protocol/communities/manager.go#L4403

// Ensure owner has archive
archiveIndex, err := s.owner.communitiesManager.LoadHistoryArchiveIndexFromFile(s.owner.identity, community.ID())
s.Require().NoError(err)
s.Require().Len(archiveIndex.Archives, 1)

// Ensure bob has archive (because they share same local directory)
archiveIndex, err = s.bob.communitiesManager.LoadHistoryArchiveIndexFromFile(s.bob.identity, community.ID())
s.Require().NoError(err)
s.Require().Len(archiveIndex.Archives, 1)

archiveHash := maps.Keys(archiveIndex.Archives)[0]

// Save message archive ID as in
// https://github.com/status-im/status-go/blob/6c82a6c2be7ebed93bcae3b9cf5053da3820de50/protocol/communities/manager.go#L4325-L4336
err = s.bob.communitiesManager.SaveMessageArchiveID(community.ID(), archiveHash)
s.Require().NoError(err)

s.bob.importDelayer.once.Do(func() {
close(s.bob.importDelayer.wait)
})
cancel := make(chan struct{})
err = s.bob.importHistoryArchives(community.ID(), cancel)
s.Require().NoError(err)

// Ensure message1 wasn't imported, as it's encrypted, and we don't have access to the channel
receivedMessage1, err := s.bob.MessageByID(message1.ID)
s.Require().Nil(receivedMessage1)
s.Require().Error(err)

// Make bob satisfy channel criteria
s.makeAddressSatisfyTheCriteria(testChainID1, bobAddress, channelPermission.TokenCriteria[0])

waitOnChannelKeyToBeDistributedToBob := s.waitOnKeyDistribution(func(sub *CommunityAndKeyActions) bool {
action, ok := sub.keyActions.ChannelKeysActions[chat.CommunityChatID()]
if !ok || action.ActionType != communities.EncryptionKeySendToMembers {
return false
}
_, ok = action.Members[common.PubkeyToHex(&s.bob.identity.PublicKey)]
return ok
})

// force owner to reevaluate channel members
// in production it will happen automatically, by periodic check
community, err = s.owner.communitiesManager.GetByID(community.ID())
s.Require().NoError(err)
_, err = s.owner.communitiesManager.ReevaluateMembers(community)
s.Require().NoError(err)

err = <-waitOnChannelKeyToBeDistributedToBob
s.Require().NoError(err)

// Finally ensure that the message from archive was retrieved and decrypted
response, err = s.bob.RetrieveAll()
s.Require().NoError(err)
s.Require().Len(response.Messages(), 1)
receivedMessage1, ok := response.messages[message1.ID]
s.Require().True(ok)
s.Require().Equal(messageText1, receivedMessage1.Text)
}
13 changes: 13 additions & 0 deletions protocol/messenger_testing_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ func (m *MessengerSignalsHandlerMock) SendWakuBackedUpKeypair(*wakusync.WakuBack
func (m *MessengerSignalsHandlerMock) SendWakuBackedUpWatchOnlyAccount(*wakusync.WakuBackedUpDataResponse) {
}

func (m *MessengerSignalsHandlerMock) BackupPerformed(uint64) {}
func (m *MessengerSignalsHandlerMock) HistoryArchivesProtocolEnabled() {}
func (m *MessengerSignalsHandlerMock) HistoryArchivesProtocolDisabled() {}
func (m *MessengerSignalsHandlerMock) CreatingHistoryArchives(string) {}
func (m *MessengerSignalsHandlerMock) NoHistoryArchivesCreated(string, int, int) {}
func (m *MessengerSignalsHandlerMock) HistoryArchivesCreated(string, int, int) {}
func (m *MessengerSignalsHandlerMock) HistoryArchivesSeeding(string) {}
func (m *MessengerSignalsHandlerMock) HistoryArchivesUnseeded(string) {}
func (m *MessengerSignalsHandlerMock) HistoryArchiveDownloaded(string, int, int) {}
func (m *MessengerSignalsHandlerMock) DownloadingHistoryArchivesStarted(string) {}
func (m *MessengerSignalsHandlerMock) DownloadingHistoryArchivesFinished(string) {}
func (m *MessengerSignalsHandlerMock) ImportingHistoryArchiveMessages(string) {}

func (m *MessengerSignalsHandlerMock) MessengerResponse(response *MessengerResponse) {
// Non-blocking send
select {
Expand Down

0 comments on commit b18626c

Please sign in to comment.