From 0a8a6db36052a167afad2b8988fd4c9cff0a0229 Mon Sep 17 00:00:00 2001 From: cameronvoell Date: Tue, 17 Dec 2024 09:47:31 -0800 Subject: [PATCH 1/2] update libxmtp for fork fixes via intent filter fix --- android/build.gradle | 2 +- example/ios/Podfile.lock | 16 +- example/src/tests/groupTests.ts | 272 ++++++++++++++++++++++++++++++++ example/yarn.lock | 2 +- ios/XMTPReactNative.podspec | 2 +- 5 files changed, 283 insertions(+), 11 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 50f26041..297784f9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -98,7 +98,7 @@ repositories { dependencies { implementation project(':expo-modules-core') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" - implementation "org.xmtp:android:3.0.14" + implementation "org.xmtp:android:3.0.16" implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.facebook.react:react-native:0.71.3' implementation "com.daveanthonythomas.moshipack:moshipack:1.0.1" diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index b7927a00..69a69e06 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -55,7 +55,7 @@ PODS: - hermes-engine/Pre-built (= 0.71.14) - hermes-engine/Pre-built (0.71.14) - libevent (2.1.12) - - LibXMTP (3.0.10) + - LibXMTP (3.0.12) - MessagePacker (0.4.7) - MMKV (2.0.0): - MMKVCore (~> 2.0.0) @@ -448,18 +448,18 @@ PODS: - SQLCipher/standard (4.5.7): - SQLCipher/common - SwiftProtobuf (1.28.2) - - XMTP (3.0.15): + - XMTP (3.0.17): - Connect-Swift (= 1.0.0) - CryptoSwift (= 1.8.3) - CSecp256k1 (~> 0.2) - - LibXMTP (= 3.0.10) + - LibXMTP (= 3.0.12) - SQLCipher (= 4.5.7) - - XMTPReactNative (3.1.1): + - XMTPReactNative (3.1.2): - CSecp256k1 (~> 0.2) - ExpoModulesCore - MessagePacker - SQLCipher (= 4.5.7) - - XMTP (= 3.0.15) + - XMTP (= 3.0.17) - Yoga (1.14.0) DEPENDENCIES: @@ -711,7 +711,7 @@ SPEC CHECKSUMS: glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - LibXMTP: e550ccb4565023eb77f5a2eddd9d5a71cfb9d2b3 + LibXMTP: 7e7607786ccc82c0230960964aa00843a5d47094 MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02 MMKV: f7d1d5945c8765f97f39c3d121f353d46735d801 MMKVCore: c04b296010fcb1d1638f2c69405096aac12f6390 @@ -762,8 +762,8 @@ SPEC CHECKSUMS: RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396 SQLCipher: 5e6bfb47323635c8b657b1b27d25c5f1baf63bf5 SwiftProtobuf: 4dbaffec76a39a8dc5da23b40af1a5dc01a4c02d - XMTP: 8b0c84096edf74642c5780e4fca9ebbc848fdcf2 - XMTPReactNative: fa98630d85a3947eccfde6062916bcf3de9c32e2 + XMTP: 5be6a8212c789e828b6eeef9edae84a227c61f22 + XMTPReactNative: 72e92330c1d9883a93c4a1be62ac4342e95dd80d Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9 PODFILE CHECKSUM: 0e6fe50018f34e575d38dc6a1fdf1f99c9596cdd diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index fa0b2dcb..95585c89 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -16,6 +16,7 @@ import { DecodedMessage, ConsentRecord, } from '../../../src/index' +import { DefaultContentTypes } from 'xmtp-react-native-sdk/lib/types/DefaultContentType' export const groupTests: Test[] = [] let counter = 1 @@ -23,6 +24,277 @@ function test(name: string, perform: () => Promise) { groupTests.push({ name: String(counter++) + '. ' + name, run: perform }) } +test('groups cannot fork', async () => { + const [alix, bo, caro] = await createClients(3) + // Create group with 3 users + const { id: groupId } = await alix.conversations.newGroup([ + bo.address, + caro.address, + ]) + + const getGroupForClient = async (client: Client) => { + // Always sync the client before getting the group + await client.conversations.sync() + const group = await client.conversations.findGroup(groupId) + assert(group !== undefined, `Group not found for ${client.address}`) + return group as Group + } + + const syncClientAndGroup = async (client: Client) => { + const group = await getGroupForClient(client) + await group.sync() + } + + const addMemberToGroup = async (fromClient: Client, addresses: string[]) => { + await syncClientAndGroup(fromClient) + const group = await getGroupForClient(fromClient) + await group.addMembers(addresses) + await delayToPropogate(500) + } + + const removeMemberFromGroup = async ( + fromClient: Client, + addresses: string[] + ) => { + await syncClientAndGroup(fromClient) + const group = await getGroupForClient(fromClient) + await group.removeMembers(addresses) + await delayToPropogate(500) + } + + // Helper to send a message from a bunch of senders and make sure it is received by all receivers + const testMessageSending = async (senderClient: Client, receiver: Client) => { + // for (const senderClient of senders) { + const messageContent = Math.random().toString(36) + await syncClientAndGroup(senderClient) + const senderGroup = await getGroupForClient(senderClient) + await senderGroup.send(messageContent) + + await delayToPropogate(500) + await senderGroup.sync() + + await syncClientAndGroup(receiver) + + const receiverGroupToCheck = await getGroupForClient(receiver) + await receiverGroupToCheck.sync() + + const messages = await receiverGroupToCheck.messages({ + direction: 'DESCENDING', + }) + const lastMessage = messages[0] + // console.log(lastMessage); + console.log( + `${receiverGroupToCheck.client.address} sees ${messages.length} messages in group` + ) + assert( + lastMessage !== undefined && + lastMessage.nativeContent.text === messageContent, + `${receiverGroupToCheck.client.address} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` + ) + // } + } + + console.log('Testing that messages sent by alix are received by bo') + await testMessageSending(alix, bo) + console.log('Alix & Bo are not forked at the beginning') + + // Test adding members one by one + // console.log('Testing adding members one by one...') + const newClients = await createClients(2) + + // Add back several members + console.log('Adding new members to the group...') + for (const client of newClients) { + console.log(`Adding member ${client.address}...`) + await addMemberToGroup(alix, [client.address]) + } + await delayToPropogate() + + await alix.conversations.sync() + await syncClientAndGroup(alix) + + // NB => if we don't use Promise.all but a loop, we don't get a fork + const REMOVE_MEMBERS_IN_PARALLEL = true + if (REMOVE_MEMBERS_IN_PARALLEL) { + console.log('Removing members in parallel') + + await Promise.all( + newClients.map((client) => { + console.log(`Removing member ${client.address}...`) + return removeMemberFromGroup(alix, [client.address]) + }) + ) + } else { + console.log('Removing members one by one') + + for (const client of newClients) { + console.log(`Removing member ${client.address}...`) + await removeMemberFromGroup(alix, [client.address]) + } + } + + await delayToPropogate(1000) + + // When forked, it stays forked even if we try 5 times + // but sometimes it is not forked and works 5/5 times + let forkCount = 0 + const tryCount = 5 + for (let i = 0; i < tryCount; i++) { + console.log(`Checking fork status ${i+1}/${tryCount}`) + try { + await syncClientAndGroup(alix) + await syncClientAndGroup(bo) + await delayToPropogate(500) + await testMessageSending(alix, bo) + console.log('Not forked!') + } catch (e: any) { + console.log('Forked!') + console.log(e) + forkCount++ + } + } + + assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) + + return true +}) + +test('groups cannot fork short version', async () => { + const [alix, bo, new_one, new_two] = await createClients(4) + // Create group with 2 users + const alixGroup = await alix.conversations.newGroup([ + bo.address, + new_one.address, + new_two.address, + ]) + + // sync clients + await alix.conversations.sync() + await bo.conversations.sync() + const boGroup: Group = (await bo.conversations.findGroup(alixGroup.id))! + + // Remove two members in parallel + // NB => if we don't use Promise.all but a loop, we don't get a fork + console.log('*************libxmtp*********************: Removing members in parallel') + await Promise.all([ + alixGroup.removeMembers([new_one.address]), + alixGroup.removeMembers([new_two.address]) + ]) + + // Helper to send a message from a bunch of senders and make sure it is received by all receivers + const testMessageSending = async (senderGroup: Group, receiverGroup: Group) => { + const messageContent = Math.random().toString(36) + await senderGroup.sync() + await alixGroup.send(messageContent) + + await delayToPropogate(500) + await alixGroup.sync() + await receiverGroup.sync() + + const messages = await receiverGroup.messages({ + direction: 'DESCENDING', + }) + const lastMessage = messages[0] + console.log( + `${receiverGroup.client.address} sees ${messages.length} messages in group` + ) + assert( + lastMessage !== undefined && + lastMessage.nativeContent.text === messageContent, + `${receiverGroup.client.address} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` + ) + } + // When forked, it stays forked even if we try 5 times + // but sometimes it is not forked and works 5/5 times + let forkCount = 0 + const tryCount = 5 + for (let i = 0; i < tryCount; i++) { + console.log(`Checking fork status ${i+1}/${tryCount}`) + try { + await alixGroup.sync() + await boGroup.sync() + await delayToPropogate(500) + await testMessageSending(alixGroup, boGroup) + console.log('Not forked!') + } catch (e: any) { + console.log('Forked!') + console.log(e) + forkCount++ + } + } + assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) + + return true +}) + +test('groups cannot fork short version - update metadata', async () => { + const [alix, bo, new_one, new_two, new_three, new_four] = await createClients(6) + // Create group with 2 users + const alixGroup = await alix.conversations.newGroup([ + bo.address, + new_one.address, + new_two.address, + ]) + + // sync clients + await alix.conversations.sync() + await bo.conversations.sync() + const boGroup: Group = (await bo.conversations.findGroup(alixGroup.id))! + + // Remove two members in parallel + // NB => if we don't use Promise.all but a loop, we don't get a fork + console.log('*************libxmtp*********************: Updating metadata in parallel') + await Promise.all([ + alixGroup.updateGroupName('new name'), + alixGroup.updateGroupName('new name 2') + ]) + + // Helper to send a message from a bunch of senders and make sure it is received by all receivers + const testMessageSending = async (senderGroup: Group, receiverGroup: Group) => { + const messageContent = Math.random().toString(36) + await senderGroup.sync() + await alixGroup.send(messageContent) + + await delayToPropogate(500) + await alixGroup.sync() + await receiverGroup.sync() + + const messages = await receiverGroup.messages({ + direction: 'DESCENDING', + }) + const lastMessage = messages[0] + console.log( + `${receiverGroup.client.address} sees ${messages.length} messages in group` + ) + assert( + lastMessage !== undefined && + lastMessage.nativeContent.text === messageContent, + `${receiverGroup.client.address} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` + ) + } + // When forked, it stays forked even if we try 5 times + // but sometimes it is not forked and works 5/5 times + let forkCount = 0 + const tryCount = 5 + for (let i = 0; i < tryCount; i++) { + console.log(`Checking fork status ${i+1}/${tryCount}`) + try { + await alixGroup.sync() + await boGroup.sync() + await delayToPropogate(500) + await testMessageSending(alixGroup, boGroup) + console.log('Not forked!') + } catch (e: any) { + console.log('Forked!') + console.log(e) + forkCount++ + } + } + assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) + + return true +}) + test('can cancel streams', async () => { const [alix, bo] = await createClients(2) let messageCallbacks = 0 diff --git a/example/yarn.lock b/example/yarn.lock index 5bcc21b5..aebad0f2 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -6283,7 +6283,7 @@ resolved "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz" integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== -"@types/node@^20.14.9": +"@types/node@^20.15.0": version "20.17.10" resolved "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz#3f7166190aece19a0d1d364d75c8b0b5778c1e18" integrity sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA== diff --git a/ios/XMTPReactNative.podspec b/ios/XMTPReactNative.podspec index f3e0d9e2..80ef7bee 100644 --- a/ios/XMTPReactNative.podspec +++ b/ios/XMTPReactNative.podspec @@ -26,7 +26,7 @@ Pod::Spec.new do |s| s.source_files = "**/*.{h,m,swift}" s.dependency "MessagePacker" - s.dependency "XMTP", "= 3.0.15" + s.dependency "XMTP", "= 3.0.17" s.dependency 'CSecp256k1', '~> 0.2' s.dependency "SQLCipher", "= 4.5.7" end From 57ce753b6cf7717a580575c52b042556b38a7449 Mon Sep 17 00:00:00 2001 From: Cameron Voell <1103838+cameronvoell@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:51:55 -0800 Subject: [PATCH 2/2] Create fork-fix-filter-adjustment.md --- .changeset/fork-fix-filter-adjustment.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fork-fix-filter-adjustment.md diff --git a/.changeset/fork-fix-filter-adjustment.md b/.changeset/fork-fix-filter-adjustment.md new file mode 100644 index 00000000..3e8027fe --- /dev/null +++ b/.changeset/fork-fix-filter-adjustment.md @@ -0,0 +1,5 @@ +--- +"@xmtp/react-native-sdk": patch +--- + +Fix for forked groups via intent filter adjustment