Skip to content

Commit

Permalink
Merge branch 'main' into bwcDvorak-patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
bwcDvorak authored Oct 30, 2023
2 parents a06ecc1 + d42462b commit f2e273c
Show file tree
Hide file tree
Showing 17 changed files with 555 additions and 65 deletions.
20 changes: 4 additions & 16 deletions dev/local/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: "3.8"
services:
wakunode:
waku-node:
image: xmtp/node-go:latest
platform: linux/amd64
environment:
- GOWAKU-NODEKEY=8a30dcb604b0b53627a5adc054dbf434b446628d4bd1eccc681d223f0550ce67
command:
Expand All @@ -12,22 +12,10 @@ services:
- --api.authn.enable
ports:
- 9001:9001
- 5555:5555 # http message API
- 5556:5556 # grpc message API
- 5555:5555
depends_on:
- db
healthcheck:
test: [ "CMD", "lsof", "-i", ":5556" ]
interval: 3s
timeout: 10s
retries: 5
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: xmtp
js:
restart: always
depends_on:
wakunode:
condition: service_healthy
build: ./test
POSTGRES_PASSWORD: xmtp
8 changes: 7 additions & 1 deletion library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ dokkaGfmPartial {
outputDirectory.set(file("build/docs/partial"))
}

ktlint {
filter {
exclude { it.file.path.contains("xmtp_dh") }
}
}

android {
namespace 'org.xmtp.android.library'
compileSdk 33
Expand Down Expand Up @@ -80,7 +86,7 @@ dependencies {
implementation 'org.web3j:crypto:5.0.0'
implementation "net.java.dev.jna:jna:5.13.0@aar"
api 'com.google.protobuf:protobuf-kotlin-lite:3.22.3'
api 'org.xmtp:proto-kotlin:3.24.1'
api 'org.xmtp:proto-kotlin:3.31.0'

testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'app.cash.turbine:turbine:0.12.1'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package org.xmtp.android.library

import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.xmtp.android.library.messages.PrivateKeyBuilder
import org.xmtp.android.library.messages.PrivateKeyBundleV1Builder
import org.xmtp.android.library.messages.generate
import org.xmtp.proto.message.contents.PrivateKeyOuterClass

@RunWith(AndroidJUnit4::class)
class ClientTest {
@Test
fun testTakesAWallet() {
Expand All @@ -20,6 +26,20 @@ class ClientTest {
assert(preKey?.publicKey?.hasSignature() ?: false)
}

@Test
fun testSerialization() {
val wallet = PrivateKeyBuilder()
val v1 =
PrivateKeyOuterClass.PrivateKeyBundleV1.newBuilder().build().generate(wallet = wallet)
val encodedData = PrivateKeyBundleV1Builder.encodeData(v1)
val v1Copy = PrivateKeyBundleV1Builder.fromEncodedData(encodedData)
val client = Client().buildFrom(v1Copy)
assertEquals(
wallet.address,
client.address
)
}

@Test
fun testCanBeCreatedWithBundle() {
val fakeWallet = PrivateKeyBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package org.xmtp.android.library

import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.xmtp.android.library.messages.walletAddress

@RunWith(AndroidJUnit4::class)
class ContactsTest {

@Test
Expand Down Expand Up @@ -35,4 +37,34 @@ class ContactsTest {
}
assert(fixtures.aliceClient.contacts.has(fixtures.bob.walletAddress))
}

@Test
fun testAllowAddress() {
val fixtures = fixtures()

val contacts = fixtures.bobClient.contacts
var result = contacts.isAllowed(fixtures.alice.walletAddress)

assert(!result)

contacts.allow(listOf(fixtures.alice.walletAddress))

result = contacts.isAllowed(fixtures.alice.walletAddress)
assert(result)
}

@Test
fun testBlockAddress() {
val fixtures = fixtures()

val contacts = fixtures.bobClient.contacts
var result = contacts.isAllowed(fixtures.alice.walletAddress)

assert(!result)

contacts.block(listOf(fixtures.alice.walletAddress))

result = contacts.isBlocked(fixtures.alice.walletAddress)
assert(result)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -713,4 +713,41 @@ class ConversationTest {
assertEquals(1, messages.size)
assertEquals("hi", messages[0].content())
}

@Test
fun testCanHaveConsentState() {
val bobConversation = bobClient.conversations.newConversation(alice.walletAddress, null)
val isAllowed = bobConversation.consentState() == ConsentState.ALLOWED

// Conversations you start should start as allowed
assertTrue(isAllowed)
assertTrue(bobClient.contacts.isAllowed(alice.walletAddress))

bobClient.contacts.block(listOf(alice.walletAddress))
bobClient.contacts.refreshConsentList()

val isBlocked = bobConversation.consentState() == ConsentState.BLOCKED
assertTrue(isBlocked)

val aliceConversation = aliceClient.conversations.list()[0]
val isUnknown = aliceConversation.consentState() == ConsentState.UNKNOWN

// Conversations started with you should start as unknown
assertTrue(isUnknown)

aliceClient.contacts.allow(listOf(bob.walletAddress))

val isBobAllowed = aliceConversation.consentState() == ConsentState.ALLOWED
assertTrue(isBobAllowed)

val aliceClient2 = Client().create(aliceWallet, fakeApiClient)
val aliceConversation2 = aliceClient2.conversations.list()[0]

aliceClient2.contacts.refreshConsentList()

// Allow state should sync across clients
val isBobAllowed2 = aliceConversation2.consentState() == ConsentState.ALLOWED

assertTrue(isBobAllowed2)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.xmtp.android.library.codecs.TextCodec
Expand Down Expand Up @@ -78,6 +79,7 @@ class ConversationsTest {
}

@Test
@Ignore("Flaky Test")
fun testStreamAllMessages() = runBlocking {
val bo = PrivateKeyBuilder()
val alix = PrivateKeyBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.xmtp.proto.message.contents.PrivateKeyOuterClass.PrivateKeyBundle
import java.util.Date

@RunWith(AndroidJUnit4::class)
@Ignore("All Flaky")
class LocalInstrumentedTest {
@Test
fun testPublishingAndFetchingContactBundlesWithWhileGeneratingKeys() {
Expand Down Expand Up @@ -277,7 +278,7 @@ class LocalInstrumentedTest {

@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun testStreamAllMessagesWorksWithIntros() = runBlocking {
fun testStreamAllMessagesWorksWithIntros() {
val bob = PrivateKeyBuilder()
val alice = PrivateKeyBuilder()
val clientOptions =
Expand Down
154 changes: 153 additions & 1 deletion library/src/main/java/org/xmtp/android/library/Contacts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,167 @@ package org.xmtp.android.library
import kotlinx.coroutines.runBlocking
import org.xmtp.android.library.messages.ContactBundle
import org.xmtp.android.library.messages.ContactBundleBuilder
import org.xmtp.android.library.messages.EnvelopeBuilder
import org.xmtp.android.library.messages.Topic
import org.xmtp.android.library.messages.walletAddress
import org.xmtp.proto.message.contents.PrivatePreferences.PrivatePreferencesAction
import java.util.Date

enum class ConsentState {
ALLOWED,
BLOCKED,
UNKNOWN
}

data class ConsentListEntry(
val value: String,
val entryType: EntryType,
val consentType: ConsentState,
) {
enum class EntryType {
ADDRESS
}

companion object {
fun address(
address: String,
type: ConsentState = ConsentState.UNKNOWN,
): ConsentListEntry {
return ConsentListEntry(address, EntryType.ADDRESS, type)
}
}

val key: String
get() = "${entryType.name}-$value"
}

class ConsentList(val client: Client) {
private val entries: MutableMap<String, ConsentState> = mutableMapOf()
private val publicKey =
client.privateKeyBundleV1.identityKey.publicKey.secp256K1Uncompressed.bytes
private val privateKey = client.privateKeyBundleV1.identityKey.secp256K1.bytes

@OptIn(ExperimentalUnsignedTypes::class)
private val identifier: String = uniffi.xmtp_dh.generatePrivatePreferencesTopicIdentifier(
privateKey.toByteArray().toUByteArray().toList()
)

@OptIn(ExperimentalUnsignedTypes::class)
suspend fun load(): ConsentList {
val envelopes = client.query(Topic.preferenceList(identifier))
val consentList = ConsentList(client)
val preferences: MutableList<PrivatePreferencesAction> = mutableListOf()

for (envelope in envelopes.envelopesList) {
val payload = uniffi.xmtp_dh.eciesDecryptK256Sha3256(
publicKey.toByteArray().toUByteArray().toList(),
privateKey.toByteArray().toUByteArray().toList(),
envelope.message.toByteArray().toUByteArray().toList()
)

preferences.add(
PrivatePreferencesAction.parseFrom(
payload.toUByteArray().toByteArray()
)
)
}

preferences.reversed().iterator().forEach { preference ->
preference.allow?.walletAddressesList?.forEach { address ->
consentList.allow(address)
}
preference.block?.walletAddressesList?.forEach { address ->
consentList.block(address)
}
}

return consentList
}

@OptIn(ExperimentalUnsignedTypes::class)
fun publish(entry: ConsentListEntry) {
val payload = PrivatePreferencesAction.newBuilder().also {
when (entry.consentType) {
ConsentState.ALLOWED -> it.setAllow(
PrivatePreferencesAction.Allow.newBuilder().addWalletAddresses(entry.value)
)

ConsentState.BLOCKED -> it.setBlock(
PrivatePreferencesAction.Block.newBuilder().addWalletAddresses(entry.value)
)

ConsentState.UNKNOWN -> it.clearMessageType()
}
}.build()

val message = uniffi.xmtp_dh.eciesEncryptK256Sha3256(
publicKey.toByteArray().toUByteArray().toList(),
privateKey.toByteArray().toUByteArray().toList(),
payload.toByteArray().toUByteArray().toList()
)

val envelope = EnvelopeBuilder.buildFromTopic(
Topic.preferenceList(identifier),
Date(),
ByteArray(message.size) { message[it].toByte() }
)

client.publish(listOf(envelope))
}

fun allow(address: String): ConsentListEntry {
entries[ConsentListEntry.address(address).key] = ConsentState.ALLOWED

return ConsentListEntry.address(address, ConsentState.ALLOWED)
}

fun block(address: String): ConsentListEntry {
entries[ConsentListEntry.address(address).key] = ConsentState.BLOCKED

return ConsentListEntry.address(address, ConsentState.BLOCKED)
}

fun state(address: String): ConsentState {
val state = entries[ConsentListEntry.address(address).key]

return state ?: ConsentState.UNKNOWN
}
}

data class Contacts(
var client: Client,
val knownBundles: MutableMap<String, ContactBundle> = mutableMapOf(),
val hasIntroduced: MutableMap<String, Boolean> = mutableMapOf()
val hasIntroduced: MutableMap<String, Boolean> = mutableMapOf(),
) {

var consentList: ConsentList = ConsentList(client)

fun refreshConsentList() {
runBlocking {
consentList = ConsentList(client).load()
}
}

fun isAllowed(address: String): Boolean {
return consentList.state(address) == ConsentState.ALLOWED
}

fun isBlocked(address: String): Boolean {
return consentList.state(address) == ConsentState.BLOCKED
}

fun allow(addresses: List<String>) {
for (address in addresses) {
ConsentList(client).publish(consentList.allow(address))
}
}

fun block(addresses: List<String>) {
for (address in addresses) {
ConsentList(client).publish(consentList.block(address))
}
}

fun has(peerAddress: String): Boolean =
knownBundles[peerAddress] != null

Expand Down
Loading

0 comments on commit f2e273c

Please sign in to comment.