feat: introduce keyupdate messages informing contacts about relay changes

When the published relay list changes, key-contacts are informed with an
unsigned message carrying the re-signed key, encrypted to a chunk of contacts
at a time. It is shaped like a receipt notification naming no message, so that
cores which know nothing about keyupdates trash it as well.

See the src/keyupdate.rs module docs for the design.
This commit is contained in:
holger krekel
2026-08-29 20:36:13 +02:00
parent 370cc5c1fd
commit f162749dfe
17 changed files with 830 additions and 26 deletions

View File

@@ -2610,6 +2610,19 @@ UPDATE msgs SET state=24 WHERE state=18; -- Change OutPreparing to OutFailed.
.await?;
}
inc_and_check(&mut migration_version, 164)?;
if dbversion < migration_version {
// Seed the keyupdate baseline so that upgrading alone sends nothing,
// see `keyupdate.rs`.
sql.execute_migration(
"INSERT OR REPLACE INTO config (keyname, value)
SELECT 'keyupdate_baseline', IFNULL(group_concat(addr, ' ' ORDER BY addr), '')
FROM transports WHERE is_published=1",
migration_version,
)
.await?;
}
let new_version = sql
.get_raw_config_int(VERSION_CFG)
.await?

View File

@@ -8,6 +8,7 @@ use crate::contact::ContactId;
use crate::contact::Origin;
use crate::test_utils::TestContext;
use crate::tools;
use crate::transport::add_pseudo_transport;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_clear_config_cache() -> anyhow::Result<()> {
@@ -29,6 +30,29 @@ async fn test_clear_config_cache() -> anyhow::Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_keyupdate_baseline_migration() -> Result<()> {
let configured = STOP_MIGRATIONS_AT
.scope(163, async move { TestContext::new_alice().await })
.await;
// An address sorting before the primary pins the seed's ORDER BY,
// an unpublished transport pins its filter.
add_pseudo_transport(&configured, "aa@example.org").await?;
add_pseudo_transport(&configured, "unpublished@example.org").await?;
configured
.sql
.execute(
"UPDATE transports SET is_published=0 WHERE addr='unpublished@example.org'",
(),
)
.await?;
configured.sql.run_migrations(&configured).await?;
let relays = configured.get_config(Config::KeyupdateBaseline).await?;
assert_eq!(relays.as_deref(), Some("aa@example.org alice@example.org"));
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_key_contacts_migration_autocrypt() -> Result<()> {
let t = STOP_MIGRATIONS_AT