Restrict feedback invalidation to credential, key and admission changes

This commit is contained in:
evgeny committed 2026-10-26 14:44:03 +01:00
1 parent 5cae3d095d
commit 953e094c92
3 files changed
+44 -18

No files matched your search

+1 -1
View File
@@ -1276,7 +1276,7 @@ static void cs_handle_join_info_resp(struct chat_sync* cs, uint64_t peer,
}
if (cs->info_req_timer) { uasync_cancel_timeout(cs->inst->ua, cs->info_req_timer); cs->info_req_timer = NULL; }
if (chat_core_ensure_channel_ready(cs->inst, ch_id) < 0) { cs_join_finish(cs, CHAT_CONNECT_ERROR); return; }
member_sync_record_committed(cs->inst, ch_id, c_node_id);
member_sync_record_committed(cs->inst, ch_id, c_node_id, (unsigned)mrc);
DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "join channel committed ch=%s member=%016llx", ch_id, (unsigned long long)c_node_id);
/* построить собственный join_sig */
+40 -16
View File
@@ -508,7 +508,7 @@ static int member_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
return -1;
}
sqlite3* db = _db(inst); if (!db) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record — db is NULL", MS_ID); return -1; }
int stale = 0, changed = 0;
int stale = 0, changed = 0, node_key_changed = 0;
DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record ch=%s nid=%016llx uts=%llu tags_bytes=%zu",
MS_ID, ch_id, (unsigned long long)m->node_id, (unsigned long long)m->update_ts,
@@ -523,11 +523,12 @@ static int member_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
char local_tags[MS_ADM_TAGS_MAX + 1] = "";
uint64_t local_signed_by = 0;
uint8_t local_signature[64] = {0};
int local_src = 0;
int local_src = 0, local_identity_matches = 0, local_deleted = 0;
{
sqlite3_stmt* st = NULL;
char sql[512]; snprintf(sql, sizeof(sql),
"SELECT join_sig, join_ts, update_ts, adm_tags, signed_by, signature, source FROM \"%s\" WHERE node_id=?", peers_tbl);
"SELECT join_sig,join_ts,update_ts,adm_tags,signed_by,signature,source,x25519_pubkey,ed25519_pubkey,deleted"
" FROM \"%s\" WHERE node_id=?", peers_tbl);
int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL);
if (rc == SQLITE_OK) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)m->node_id);
if (rc == SQLITE_OK) {
@@ -545,6 +546,11 @@ static int member_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
const uint8_t* ls = (const uint8_t*)sqlite3_column_blob(st, 5);
if (ls && sqlite3_column_bytes(st, 5) >= 64) memcpy(local_signature, ls, 64);
local_src = sqlite3_column_int(st, 6);
const void* old_x = sqlite3_column_blob(st, 7); const void* old_ed = sqlite3_column_blob(st, 8);
local_identity_matches = old_x && old_ed && sqlite3_column_bytes(st, 7) == 32 && sqlite3_column_bytes(st, 8) == 32
&& !memcmp(old_x, m->x25519, 32) && !memcmp(old_ed, m->ed25519, 32);
local_deleted = sqlite3_column_int(st, 9);
if (sqlite3_errcode(db) == SQLITE_NOMEM) rc = SQLITE_NOMEM;
}
}
sqlite3_finalize(st);
@@ -699,6 +705,12 @@ static int member_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
/* узел в nodes пишет только merkle-путь (verified identity) */
char node_name[128] = "";
if (m->userinfo) json_flat_get(m->userinfo, "name", node_name, sizeof(node_name));
if (merkle_sync_registered(inst, MT_DATA_FEEDBACK)) {
uint8_t previous_key[32];
int lookup = topo_node_sqlite_get_ed25519_pubkey(db, m->node_id, previous_key);
if (lookup == TOPO_SQLITE_LOOKUP_ERROR) return MT_ERR_IO;
node_key_changed = lookup == TOPO_SQLITE_LOOKUP_MISSING || memcmp(previous_key, m->ed25519, 32);
}
if (topo_node_sqlite_node_update_verified(db, m->node_id, node_name, m->x25519, m->ed25519,
m->update_ts ? m->update_ts : m->join_ts,
ntp_time_get_seconds(inst)) < 0) {
@@ -739,23 +751,32 @@ static int member_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
(unsigned long long)m->node_id); return MT_ERR_IO;
}
char deleted_value[16];
int deleted_changed = block_b_changed && local_deleted != (json_flat_get(m->adm_tags, "deleted",
deleted_value, sizeof(deleted_value)) == 0 && !strcmp(deleted_value, "yes"));
int credentials_changed = block_a_changed && (!has_local || local_src != 0 || !local_identity_matches
|| m->join_ts != local_join_ts || (m->join_sig && memcmp(m->join_sig, local_join_sig, 64))
|| (has_signature && (m->signed_by != local_signed_by || memcmp(m->signature, local_signature, 64))));
int feedback_changed = merkle_sync_registered(inst, MT_DATA_FEEDBACK)
&& (credentials_changed || signature_changed || deleted_changed || node_key_changed);
/* Профиль/stream options не меняют допуск feedback и не требуют новых его раундов. */
if (feedback_changed && chat_feedback_member_update(inst, m->node_id) < 0) return MT_ERR_IO;
/* ── recompute и подтвердить реальное изменение по хешу ── */
if (changed) {
/* Общий ключ может разрешить удостоверения приглашённых в других каналах. */
if (chat_feedback_member_update(inst, m->node_id) < 0) return MT_ERR_IO;
int rc = merkle_sync_recompute_path(inst, MT_DATA_MEMBERS, ch_id, m->node_id);
if (rc < 0) return -1;
if (rc == 0) changed = 0; /* запись была no-op (данные идентичны) */
}
DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record ch=%s nid=0x%016llx → changed=%d stale=%d",
MS_ID, ch_id, (unsigned long long)m->node_id, changed, stale);
return (changed ? MS_APPLY_CHANGED : 0) | (stale ? MS_APPLY_STALE : 0);
DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record ch=%s nid=0x%016llx → changed=%d stale=%d feedback=%d",
MS_ID, ch_id, (unsigned long long)m->node_id, changed, stale, feedback_changed);
return (changed ? MS_APPLY_CHANGED : 0) | (stale ? MS_APPLY_STALE : 0) | (feedback_changed ? MS_APPLY_FEEDBACK : 0);
}
void member_sync_record_committed(struct UTUN_INSTANCE* inst, const char* ns, uint64_t node_id) {
void member_sync_record_committed(struct UTUN_INSTANCE* inst, const char* ns, uint64_t node_id, unsigned changes) {
merkle_sync_changed(inst, MT_DATA_MEMBERS, ns);
chat_feedback_member_committed(inst, node_id);
if (changes & MS_APPLY_FEEDBACK) chat_feedback_member_committed(inst, node_id);
struct TOPO_GROUP* g = inst->topo_groups ? topo_groups_find(inst->topo_groups, strtoull(ns, NULL, 10)) : NULL;
struct ETCP_CONN* conn = instance_find_conn(inst, node_id);
if (g && g->group_type == TOPO_GROUP_TYPE_CHAT) {
@@ -785,7 +806,8 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id,
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: record cleanup: %s", MS_ID, sqlite3_errmsg(db));
return -1;
}
if (own && rc >= 0 && (rc & MS_APPLY_CHANGED)) member_sync_record_committed(inst, ch_id, m->node_id);
if (own && rc >= 0 && (rc & (MS_APPLY_CHANGED | MS_APPLY_FEEDBACK)))
member_sync_record_committed(inst, ch_id, m->node_id, (unsigned)rc);
return rc;
}
@@ -1089,7 +1111,7 @@ static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer, co
w.rec.join_sig = w.local_join;
int r = member_sync_apply_record(inst, ns, from_peer, &w.rec);
if (r < 0) { rc = r; break; }
changed[i] = (r & MS_APPLY_CHANGED) != 0;
changed[i] = r & (MS_APPLY_CHANGED | MS_APPLY_FEEDBACK);
off += used;
}
if (rc < 0 && sqlite3_exec(db, "ROLLBACK TO member_page", NULL, NULL, NULL) != SQLITE_OK)
@@ -1105,10 +1127,12 @@ static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer, co
struct member_wire w; size_t used;
if (member_decode(data + off, len - off, &w, &used) < 0) { rc = MT_ERR_DATA; break; }
if (changed[i]) {
member_sync_record_committed(inst, ns, w.rec.node_id);
if (!inst->topo_groups || !topo_groups_find(inst->topo_groups, strtoull(ns, NULL, 10)))
_fire_props_changed(inst, w.rec.node_id, w.tags[0] ? w.tags : NULL, ns);
_fire_apply_cbk(inst, ns, w.rec.node_id, w.rec.x25519, w.rec.signed_by, w.rec.signature);
member_sync_record_committed(inst, ns, w.rec.node_id, changed[i]);
if (changed[i] & MS_APPLY_CHANGED) {
if (!inst->topo_groups || !topo_groups_find(inst->topo_groups, strtoull(ns, NULL, 10)))
_fire_props_changed(inst, w.rec.node_id, w.tags[0] ? w.tags : NULL, ns);
_fire_apply_cbk(inst, ns, w.rec.node_id, w.rec.x25519, w.rec.signed_by, w.rec.signature);
}
}
off += used;
}
+3 -1
View File
@@ -49,6 +49,7 @@ int member_sync_verified_ed25519(struct UTUN_INSTANCE* inst, const char* channel
/* Результат применения рекорда мембера (compare+update по версиям двух блоков). */
#define MS_APPLY_CHANGED 0x01 /* запись и дерево обновлены */
#define MS_APPLY_STALE 0x02 /* локальная версия новее; обратный проход доставит её пиру */
#define MS_APPLY_FEEDBACK 0x04 /* изменилось удостоверение, доверенный ключ или deleted; индекс feedback обновлён */
#define MS_VERIFY_DEFERRED (-1) /* зависимость подписи ещё не получена */
#define MS_VERIFY_ERROR (-2) /* SQL/crypto/аргументы: проверка не выполнена */
@@ -250,8 +251,9 @@ int member_sync_add_props_cbk(struct UTUN_INSTANCE* inst, node_props_changed_fn
void member_sync_remove_props_cbk(struct UTUN_INSTANCE* inst, node_props_changed_fn fn, void* arg);
/* После внешнего commit уведомить Merkle, свойства и планировщик CHAT-группы.
* changes — полная маска MS_APPLY_* из apply_record/put: FEEDBACK разрешает его уведомления.
* inst/channel обязательны; uasync, БД уже вне транзакции. Не вызывает apply callbacks. */
void member_sync_record_committed(struct UTUN_INSTANCE* inst, const char* channel, uint64_t node_id);
void member_sync_record_committed(struct UTUN_INSTANCE* inst, const char* channel, uint64_t node_id, unsigned changes);
#ifdef __cplusplus
}