mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
perf(quartz): cap NIP-17 wrap building at 4 concurrent bunker RPCs
NIP17Factory.createWraps launches all per-recipient seal builds via mapNotNullAsync, which today runs them fully parallel. Each seal needs nip44_encrypt + sign — for a NIP-46 (bunker) signer that means two round-trips per recipient. A 5-recipient group send launches 10 concurrent in-flight requests against the bunker socket, and nsec.app / Amber / Keychat typically serialize past ~10 in-flight, so some requests queue past the 65s timeout and silently fail. Cap at 4 concurrent when signer is NostrSignerRemote. Local signers (NostrSignerInternal, NostrSignerSync) bypass the semaphore and stay fully parallel — no overhead, no behaviour change for nsec users. The real fix is the batched nip44_get_conversation_keys NIP-46 RPC (separate spec PR + plan) which collapses N×2 round-trips into ~2. This commit is the interim throttle until that lands.
This commit is contained in:
@@ -34,9 +34,12 @@ import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag
|
||||
import com.vitorpamplona.quartz.nip40Expiration.expiration
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.signer.NostrSignerRemote
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.utils.mapNotNullAsync
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
|
||||
class NIP17Factory {
|
||||
data class Result(
|
||||
@@ -56,6 +59,16 @@ class NIP17Factory {
|
||||
* DM inbox relay as a hint. Pass [recipientRelayHints] to surface those;
|
||||
* the default `{ null }` lambda preserves the historical 2-element tag
|
||||
* shape for every recipient.
|
||||
*
|
||||
* When [signer] is a [NostrSignerRemote] (NIP-46 bunker), seal building
|
||||
* is rate-limited to [BUNKER_PARALLELISM] concurrent operations. Each
|
||||
* seal needs `nip44_encrypt` + `sign` round-trips against the bunker; a
|
||||
* 5-recipient group otherwise launches 10 concurrent in-flight RPCs and
|
||||
* saturates the bunker socket. Local signers (NostrSignerInternal,
|
||||
* NostrSignerSync) run fully parallel — no semaphore overhead.
|
||||
*
|
||||
* The proper fix is the batched `nip44_get_conversation_keys` NIP-46
|
||||
* RPC (separate plan); this is the interim throttle until that lands.
|
||||
*/
|
||||
private suspend fun createWraps(
|
||||
event: Event,
|
||||
@@ -72,24 +85,40 @@ class NIP17Factory {
|
||||
}
|
||||
}
|
||||
|
||||
val bunkerLimiter = if (signer is NostrSignerRemote) Semaphore(BUNKER_PARALLELISM) else null
|
||||
|
||||
return mapNotNullAsync(
|
||||
to.toList(),
|
||||
) { next ->
|
||||
GiftWrapEvent.create(
|
||||
event =
|
||||
SealedRumorEvent.create(
|
||||
event = event,
|
||||
encryptTo = next,
|
||||
expirationDelta = innerExpDelta,
|
||||
signer = signer,
|
||||
),
|
||||
recipientPubKey = next,
|
||||
expirationDelta = innerExpDelta,
|
||||
recipientRelayHint = recipientRelayHints(next),
|
||||
)
|
||||
val build: suspend () -> GiftWrapEvent = {
|
||||
GiftWrapEvent.create(
|
||||
event =
|
||||
SealedRumorEvent.create(
|
||||
event = event,
|
||||
encryptTo = next,
|
||||
expirationDelta = innerExpDelta,
|
||||
signer = signer,
|
||||
),
|
||||
recipientPubKey = next,
|
||||
expirationDelta = innerExpDelta,
|
||||
recipientRelayHint = recipientRelayHints(next),
|
||||
)
|
||||
}
|
||||
bunkerLimiter?.withPermit { build() } ?: build()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Max concurrent in-flight NIP-46 RPCs when building wraps via a
|
||||
* remote signer. Empirically a sweet spot — covers parallelism
|
||||
* speedup for 2–4 recipient sends without saturating typical
|
||||
* bunker apps (nsec.app, Amber, Keychat) that serialize requests
|
||||
* internally past ~10 in-flight.
|
||||
*/
|
||||
const val BUNKER_PARALLELISM = 4
|
||||
}
|
||||
|
||||
suspend fun createMessageNIP17(
|
||||
template: EventTemplate<ChatMessageEvent>,
|
||||
signer: NostrSigner,
|
||||
|
||||
Reference in New Issue
Block a user