diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt index 2a70f2084f..34efbe282e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt @@ -100,6 +100,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayLogger import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayOfflineTracker import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.stats.RelayReqStats import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats +import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.CachingEventDecoder import com.vitorpamplona.quartz.nip03Timestamp.VerificationStateCache import com.vitorpamplona.quartz.nip03Timestamp.okhttp.OkHttpBitcoinExplorer import com.vitorpamplona.quartz.nip03Timestamp.ots.OtsBlockHeightCache @@ -499,8 +500,10 @@ class AppModules( OkHttpLnurlEndpointResolver(roleBasedHttpClientBuilder::okHttpClientForMoney) } - // Provides a relay pool - val client: INostrClient = NostrClient(websocketBuilder, applicationIOScope) + // Provides a relay pool. The caching decoder skips re-parsing EVENT frames + // that arrive again via another subscription or relay (14-57% of frames in + // production measurements). + val client: INostrClient = NostrClient(websocketBuilder, applicationIOScope, CachingEventDecoder()) // Self-heals the "Tor Active but every circuit dead" state the lifecycle watchdogs can't // see (they only arm while Connecting). Watches Tor-routed relay outcomes and, when enough diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 59f1f722fa..d2b563a1bb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -112,6 +112,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayOfflineTracker import com.vitorpamplona.quartz.nip01Core.relay.client.auth.EmptyIAuthStatus import com.vitorpamplona.quartz.nip01Core.relay.client.auth.RelayAuthenticator +import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.CachingEventDecoder import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions @@ -310,8 +311,9 @@ class AccountViewModel( // but uses a SupervisorJob so child failures are independent. val customScope = CoroutineScope(viewModelScope.coroutineContext + SupervisorJob()) - // Provides a relay pool - val newClient = NostrClient(Amethyst.instance.websocketBuilder, customScope) + // Provides a relay pool. Crawls hit many relays with overlapping + // filters, so the duplicate-frame decoder pays off most here. + val newClient = NostrClient(Amethyst.instance.websocketBuilder, customScope, CachingEventDecoder()) // Authenticates with relays (registers itself with the client). RelayAuthenticator( diff --git a/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/Context.kt b/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/Context.kt index 36cc92a817..a96fc68f69 100644 --- a/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/Context.kt +++ b/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/Context.kt @@ -45,6 +45,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.publishAndConfirmDetailed import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId +import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.CachingEventDecoder import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer @@ -115,6 +116,9 @@ class Context( val client: NostrClient = NostrClient( websocketBuilder = BasicOkHttpWebSocket.Builder { okhttp }, + // Skips re-parsing EVENT frames that arrive again via another + // subscription or relay. + decoder = CachingEventDecoder(), ) /** diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/network/RelayConnectionManager.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/network/RelayConnectionManager.kt index 68a0f2997f..9e07c9eb21 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/network/RelayConnectionManager.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/network/RelayConnectionManager.kt @@ -25,6 +25,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayConnectionListener import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient +import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.CachingEventDecoder import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command @@ -56,7 +57,9 @@ data class RelayMetrics( open class RelayConnectionManager( websocketBuilder: WebsocketBuilder, ) : RelayConnectionListener { - private val _client = NostrClient(websocketBuilder) + // The caching decoder skips re-parsing EVENT frames that arrive again via + // another subscription or relay. + private val _client = NostrClient(websocketBuilder, decoder = CachingEventDecoder()) /** Exposes the underlying INostrClient for subscription coordinators */ val client: com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient get() = _client