diff --git a/src/chat/chat_sync.c b/src/chat/chat_sync.c index bc5e9259..50a57b66 100644 --- a/src/chat/chat_sync.c +++ b/src/chat/chat_sync.c @@ -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 */ diff --git a/src/chat/member_sync.c b/src/chat/member_sync.c index 84b9f718..3e2f7ca7 100644 --- a/src/chat/member_sync.c +++ b/src/chat/member_sync.c @@ -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; } diff --git a/src/chat/member_sync.h b/src/chat/member_sync.h index 57926def..4aa755f3 100644 --- a/src/chat/member_sync.h +++ b/src/chat/member_sync.h @@ -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 }