fix(concord): only create a community once a relay holds its genesis

The genesis (metadata + #general) was published fire-and-forget and the
community saved to the List regardless. If no relay stored it, because the relay
refused it or the app was killed mid-send (routine on low-memory tablets), the
community sat in the List forever with nothing anyone could fold, including
us after a restart. Reproduced on the SM-T220: the genesis of a community
force-stopped right after creation never reached relay.us.whitenoise.chat.

Creation now confirms every genesis wrap with at least one relay before saving,
and the create screen says so when none accepts it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vitor Pamplona
2026-09-29 14:59:05 -04:00
co-authored by Claude Opus 5.5
parent 184758015b
commit 07c1a7489e
3 changed files with 20 additions and 3 deletions
@@ -49,6 +49,7 @@ import com.vitorpamplona.amethyst.commons.model.navigation.Route
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.back
import com.vitorpamplona.amethyst.commons.resources.concord_create_action
import com.vitorpamplona.amethyst.commons.resources.concord_create_failed
import com.vitorpamplona.amethyst.commons.resources.concord_create_relays
import com.vitorpamplona.amethyst.commons.resources.concord_create_relays_desc
import com.vitorpamplona.amethyst.commons.resources.concord_create_title
@@ -139,7 +140,11 @@ fun ConcordCreateScreen(
// Replace this form with the new community, as the Marmot and relay-group creators
// do. newStack only popped up to the community route itself, which was not on the
// stack, so Back from the new community reopened a filled-in create form.
if (communityId != null) nav.popUpTo(Route.ConcordServer(communityId), Route.ConcordCreate::class)
if (communityId != null) {
nav.popUpTo(Route.ConcordServer(communityId), Route.ConcordCreate::class)
} else {
accountViewModel.toastManager.toast(Res.string.concord_create_title, Res.string.concord_create_failed)
}
}
},
enabled = name.value.isNotBlank() && !working,
@@ -232,7 +232,7 @@ class AccountConcordActions(
* Create a new Concord community: mint its genesis (metadata + #general),
* publish the owner-signed genesis wraps to [relays] (or our outbox), and add
* the secret-bearing entry to the Community List (kind 33302). Returns the new
* community id, or null if not writeable.
* community id, or null if not writeable or no relay accepted the genesis.
*/
suspend fun createConcordCommunity(
name: String,
@@ -251,7 +251,18 @@ class AccountConcordActions(
val community = ConcordActions.createCommunity(account.signer, name, TimeUtils.now(), description, relayUrls, icon)
val publishTo = relayUrls.mapNotNullTo(mutableSetOf()) { RelayUrlNormalizer.normalizeOrNull(it) }.ifEmpty { account.outboxRelays.flow.value }
community.genesisWraps.forEach { account.client.publish(it, publishTo) }
// The genesis is the community: with no relay holding it, nobody (us included, after a
// restart) can ever fold it. It used to be fired and forgotten, then the community saved
// anyway, so a genesis a relay refused or an app killed mid-send left a community in the
// List that could never load again. Confirm every genesis wrap before saving it.
val landed =
coroutineScope {
community.genesisWraps.map { async { account.client.publishAndConfirm(it, publishTo) } }.awaitAll()
}
if (!landed.all { it }) {
Log.w("Concord") { "createConcordCommunity: no relay in $publishTo accepted the genesis; not creating ${community.communityIdHex}" }
return null
}
joinConcordCommunity(
ConcordCommunityListEntry(
@@ -3614,6 +3614,7 @@
<string name="concord_channel_rename_save">Rename</string>
<string name="concord_channels_empty">No channels yet.</string>
<string name="concord_create_action">Create</string>
<string name="concord_create_failed">No relay accepted the new community, so it was not created. Check the relays and try again.</string>
<string name="concord_create_relays">Relays</string>
<string name="concord_create_relays_desc">Relays that store this community's encrypted messages. Leave empty to use your own.</string>
<string name="concord_create_title">New Concord Channel</string>