diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 19f5ecca5..4d5e08d34 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -162,7 +162,8 @@ import com.vitorpamplona.quartz.nip01Core.tags.geohash.getGeoHash import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent import com.vitorpamplona.quartz.nip10Notes.BaseTextNoteEvent import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent -import com.vitorpamplona.quartz.nip13Pow.getPoWRank +import com.vitorpamplona.quartz.nip13Pow.pow +import com.vitorpamplona.quartz.nip13Pow.strongPoWOrNull import com.vitorpamplona.quartz.nip17Dm.ChatMessageEncryptedFileHeaderEvent import com.vitorpamplona.quartz.nip17Dm.ChatMessageEvent import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent @@ -980,8 +981,8 @@ fun SecondUserInfoRow( DisplayReward(baseReward, note, accountViewModel, nav) } - val pow = remember(noteEvent) { noteEvent.getPoWRank() } - if (pow > 20) { + val pow = remember(noteEvent) { noteEvent.strongPoWOrNull() } + if (pow != null) { Spacer(StdHorzSpacer) DisplayPoW(pow) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayPoW.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayPoW.kt index 3f627ef88..6cd258d28 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayPoW.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayPoW.kt @@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.note.elements import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import com.vitorpamplona.amethyst.ui.theme.Font14SP @@ -40,10 +39,8 @@ fun DisplayPoWPreview() { @Composable fun DisplayPoW(pow: Int) { - val powStr = remember(pow) { "PoW-$pow" } - Text( - powStr, + "PoW-$pow", color = MaterialTheme.colorScheme.lessImportantLink, fontSize = Font14SP, fontWeight = FontWeight.Bold, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt index f55c6f699..d9cd9d07d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt @@ -175,7 +175,8 @@ import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.tags.addressables.isTaggedAddressableKind import com.vitorpamplona.quartz.nip01Core.tags.geohash.getGeoHash import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent -import com.vitorpamplona.quartz.nip13Pow.getPoWRank +import com.vitorpamplona.quartz.nip13Pow.pow +import com.vitorpamplona.quartz.nip13Pow.strongPoWOrNull import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent @@ -478,8 +479,8 @@ private fun FullBleedNoteCompose( DisplayReward(baseReward, baseNote, accountViewModel, nav) } - val pow = remember { noteEvent.getPoWRank() } - if (pow > 20) { + val pow = remember(noteEvent) { noteEvent.strongPoWOrNull() } + if (pow != null) { DisplayPoW(pow) } diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip13Pow/PoWRankTest.kt b/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip13Pow/PoWRankProcessorTest.kt similarity index 74% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip13Pow/PoWRankTest.kt rename to quartz/src/androidTest/java/com/vitorpamplona/quartz/nip13Pow/PoWRankProcessorTest.kt index 8ca049981..863a2c147 100644 --- a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip13Pow/PoWRankTest.kt +++ b/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip13Pow/PoWRankProcessorTest.kt @@ -26,24 +26,24 @@ import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class PoWRankTest { +class PoWRankProcessorTest { @Test fun setPoW() { - assertEquals(26, PoWRank.get("00000026c91e9fc75fdb95b367776e2594b931cebda6d5ca3622501006669c9e")) + assertEquals(26, PoWRankProcessor.calculatePowRankOf("00000026c91e9fc75fdb95b367776e2594b931cebda6d5ca3622501006669c9e")) } @Test fun setPoWIfCommited25() { - assertEquals(25, PoWRank.getCommited("00000026c91e9fc75fdb95b367776e2594b931cebda6d5ca3622501006669c9e", 25)) + assertEquals(25, PoWRankProcessor.compute("00000026c91e9fc75fdb95b367776e2594b931cebda6d5ca3622501006669c9e", 25)) } @Test fun setPoWIfCommited26() { - assertEquals(26, PoWRank.getCommited("00000026c91e9fc75fdb95b367776e2594b931cebda6d5ca3622501006669c9e", 26)) + assertEquals(26, PoWRankProcessor.compute("00000026c91e9fc75fdb95b367776e2594b931cebda6d5ca3622501006669c9e", 26)) } @Test fun setPoWIfCommited27() { - assertEquals(26, PoWRank.getCommited("00000026c91e9fc75fdb95b367776e2594b931cebda6d5ca3622501006669c9e", 27)) + assertEquals(26, PoWRankProcessor.compute("00000026c91e9fc75fdb95b367776e2594b931cebda6d5ca3622501006669c9e", 27)) } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/limits/LimitProcessor.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/limits/LimitProcessor.kt index e73f1d98e..efe076e09 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/limits/LimitProcessor.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/limits/LimitProcessor.kt @@ -22,7 +22,7 @@ package com.vitorpamplona.quartz.experimental.limits import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.relays.filters.Filter -import com.vitorpamplona.quartz.nip13Pow.getPoWRank +import com.vitorpamplona.quartz.nip13Pow.pow import com.vitorpamplona.quartz.utils.TimeUtils class LimitProcessor { @@ -77,7 +77,7 @@ class LimitProcessor { if (!limits.acceptedEventKinds.isNullOrEmpty() && ev.kind !in limits.acceptedEventKinds) return false if (!limits.blockedEventKinds.isNullOrEmpty() && ev.kind in limits.blockedEventKinds) return false - if (limits.minPoW != null && ev.getPoWRank() < limits.minPoW) return false + if (limits.minPoW != null && ev.pow() < limits.minPoW) return false if (limits.maxEventTags != null && ev.tags.size > limits.maxEventTags) return false if (limits.maxContentLength != null && ev.content.length > limits.maxContentLength) return false diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/EventExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/EventExt.kt index 562e2a328..a38053a73 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/EventExt.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/EventExt.kt @@ -22,7 +22,22 @@ package com.vitorpamplona.quartz.nip13Pow import com.vitorpamplona.quartz.nip01Core.core.Event -fun Event.getPoWRank(): Int { - val commitedPoW = tags.firstOrNull { it.size > 2 && it[0] == "nonce" }?.get(2)?.toIntOrNull() - return PoWRank.getCommited(id, commitedPoW) +fun Event.pow() = PoWRankProcessor.compute(id, tags.commitedPoW()) + +fun Event.hasPoWTag() = tags.hasPoW() + +/** + * Returns the Proof Or Work rank when commited if it is equal or stronger than the minimum. + * + * Performance-conscious method + */ +fun Event.strongPoWOrNull(min: Int = 20): Int? { + val commitment = tags.commitedPoW() + if (commitment != null) { + val pow = PoWRankProcessor.compute(id, commitment) + if (pow >= min) { + return pow + } + } + return null } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/PoWRank.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/PoWRankProcessor.kt similarity index 90% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/PoWRank.kt rename to quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/PoWRankProcessor.kt index 1eac8885b..638798647 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/PoWRank.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/PoWRankProcessor.kt @@ -20,13 +20,15 @@ */ package com.vitorpamplona.quartz.nip13Pow -class PoWRank { +import com.vitorpamplona.quartz.nip01Core.HexKey + +class PoWRankProcessor { companion object { - fun getCommited( - id: String, + fun compute( + id: HexKey, commitedPoW: Int?, ): Int { - val actualRank = get(id) + val actualRank = calculatePowRankOf(id) return if (commitedPoW == null) { actualRank @@ -39,7 +41,7 @@ class PoWRank { } } - fun get(id: String): Int { + fun calculatePowRankOf(id: HexKey): Int { var rank = 0 for (i in 0..id.length) { if (id[i] == '0') { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/PoWTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/PoWTag.kt new file mode 100644 index 000000000..0188991cd --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/PoWTag.kt @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip13Pow + +import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.bytesUsedInMemory +import com.vitorpamplona.quartz.utils.pointerSizeInBytes + +class PoWTag( + val nonce: String, + val commitment: String?, +) { + fun countMemory(): Long = 2 * pointerSizeInBytes + nonce.bytesUsedInMemory() + (commitment?.bytesUsedInMemory() ?: 0) + + fun toTagArray() = assemble(nonce, commitment) + + companion object { + val TAG_NAME = "nonce" + + @JvmStatic + fun parse(tags: Array): PoWTag { + require(tags[0] == TAG_NAME) + return PoWTag(tags[1], tags.getOrNull(2)) + } + + @JvmStatic + fun assemble( + nonce: String, + commitment: String?, + ) = arrayOfNotNull(TAG_NAME, nonce, commitment) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/TagArrayBuilderExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/TagArrayBuilderExt.kt new file mode 100644 index 000000000..7213dd466 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/TagArrayBuilderExt.kt @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip13Pow + +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder + +fun TagArrayBuilder.pow( + nonce: String, + commitment: Int, +) = add(PoWTag.assemble(nonce, commitment.toString())) + +fun TagArrayBuilder.pow( + nonce: String, + commitment: String, +) = add(PoWTag.assemble(nonce, commitment)) + +fun TagArrayBuilder.pow(tag: PoWTag) = add(tag.toTagArray()) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/TagArrayExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/TagArrayExt.kt new file mode 100644 index 000000000..a89aa2998 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/TagArrayExt.kt @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip13Pow + +import com.vitorpamplona.quartz.nip01Core.core.TagArray +import com.vitorpamplona.quartz.nip01Core.core.hasTagWithContent +import com.vitorpamplona.quartz.nip01Core.core.mapTagged + +fun TagArray.commitedPoW() = this.firstOrNull { it.size > 2 && it[0] == "nonce" }?.get(2)?.toIntOrNull() + +fun TagArray.hasPoW() = this.hasTagWithContent(PoWTag.TAG_NAME) + +fun TagArray.powTags() = this.mapTagged(PoWTag.TAG_NAME) { PoWTag.parse(it) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/ArrayUtils.kt b/quartz/src/main/java/com/vitorpamplona/quartz/utils/ArrayUtils.kt index bba5bbfb4..f8c891f87 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/utils/ArrayUtils.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/utils/ArrayUtils.kt @@ -20,6 +20,8 @@ */ package com.vitorpamplona.quartz.utils +public fun arrayOfNotNull(vararg elements: String?) = removeTrailingNullsAndEmptyOthers(*elements) + public fun removeTrailingNullsAndEmptyOthers(vararg elements: String?): Array { val lastNonNullIndex = elements.indexOfLast { it != null }