From 4b9b0009d8f30a3d9dafe965f59dc4f4851ab263 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 5 Sep 2026 20:38:22 +0300 Subject: [PATCH] =?UTF-8?q?chat:=20=D0=B3=D0=B5=D0=B9=D1=82=20=D0=B0=D0=B2?= =?UTF-8?q?=D1=82=D0=BE-=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20db=5Fsync=20=D0=B4=D0=BE=20mem?= =?UTF-8?q?ber=5Fsync/pubkeys=20+=20member=20tags=20(verified/deleted)=20?= =?UTF-8?q?=D0=B2=20chatgui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/chat_channel.c | 1 + src/chat/chat_sync.c | 13 +++++++++- src/chat/db_sync.c | 34 ++++++++++++++++++++++++- src/chat/db_sync.h | 4 +++ tests/test_db_sync.c | 12 +++++++++ tools/chatgui/src/accountlist.cpp | 2 +- tools/chatgui/src/memberpropsdialog.cpp | 18 +++++++++++++ tools/chatgui/src/memberpropsdialog.h | 2 ++ 8 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/chat/chat_channel.c b/src/chat/chat_channel.c index ec6a2cb7..f911e08c 100644 --- a/src/chat/chat_channel.c +++ b/src/chat/chat_channel.c @@ -68,6 +68,7 @@ void chat_core_ensure_channel_ready(const char* ch_id) { if (si) { si_register(si, ch_id); db_sync_set_insert_cb(si, on_msg_inserted, u_strdup(ch_id)); + db_sync_instance_set_gated(si, 1); DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: channel ready ch=%s tbl=%s group=0x%016llx", CC_ID, ch_id, tbl_msg, (unsigned long long)gid); } else { diff --git a/src/chat/chat_sync.c b/src/chat/chat_sync.c index 9e756516..03cb87b3 100644 --- a/src/chat/chat_sync.c +++ b/src/chat/chat_sync.c @@ -337,6 +337,8 @@ static void cs_post_channel_online(struct chat_sync* cs, const char* ch_id) { /* ── Throttled sync (не чаще CS_SYNC_INTERVAL_MS) ── */ +static void _on_member_sync_done(uint64_t peer, const char* ns, int result, void* arg); + static void cs_flush_sync(struct chat_sync* cs) { uint64_t myid = cs->inst->node_id; for (int i = 0; i < cs->channel_count; i++) { @@ -346,7 +348,7 @@ static void cs_flush_sync(struct chat_sync* cs) { if (pid == myid || !cs_is_peer_online(cs->inst, pid)) continue; DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: flush_sync start for peer=%016llx ch=%s", CS_ID, (unsigned long long)pid, ch->channel_id); - member_sync_start(cs->inst, pid, ch->channel_id, NULL, NULL); + member_sync_start(cs->inst, pid, ch->channel_id, _on_member_sync_done, ch); } } } @@ -429,9 +431,16 @@ static void cs_on_peer_status_changed(uint64_t peer, int online) { cs_schedule_sync(g_cs); } +static void cs_resume_db_sync(const char* ch_id) { + if (!g_cs || !ch_id || !ch_id[0]) return; + struct DB_SYNC_INSTANCE* si = si_find(ch_id); + if (si) db_sync_instance_set_gated(si, 0); +} + static void _on_member_sync_done(uint64_t peer, const char* ns, int result, void* arg) { struct channel_cache* ch = (struct channel_cache*)arg; if (result == MT_OK && ch) ch->synced = CS_SYNC_DONE; + if (result == MT_OK) cs_resume_db_sync(ns); DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: member_sync %s ns=%s peer=%016llx", CS_ID, result == MT_OK ? "OK" : "FAIL", ns, (unsigned long long)peer); } @@ -452,6 +461,7 @@ static void _on_invite_sync_done(uint64_t peer, const char* ns, int result, void CS_ID, (void*)sa, (unsigned long long)sa->node_id, sa->ch_id); } else { cs_post_channel_online(g_cs, sa->ch_id); + cs_resume_db_sync(sa->ch_id); if (member_sync_set_online(g_cs->inst, sa->node_id, 1)) { uint8_t one = 1; merkle_sync_push_update(g_cs->inst, sa->ch_id, sa->node_id, 0x01, &one, 1); @@ -1341,6 +1351,7 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer, DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME calling cs_post_channel_online ch=%s", CS_ID, ch_id); cs_post_channel_online(cs, ch_id); + cs_resume_db_sync(ch_id); DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME processed ch=%s peers=%d — message sync triggered via db_sync (active conn or peer_check timer every 5s)", CS_ID, ch_id, pc); diff --git a/src/chat/db_sync.c b/src/chat/db_sync.c index 3ae8ff89..95e794ba 100644 --- a/src/chat/db_sync.c +++ b/src/chat/db_sync.c @@ -68,6 +68,7 @@ struct DB_SYNC_INSTANCE { int peer_count, peer_capacity; db_sync_insert_cb on_insert; void* on_insert_arg; + uint8_t sync_gated; /* 1 = не запускать авто-синхронизацию (ждём member_sync/pubkeys) */ }; struct db_sync_done_cbk_entry { @@ -1297,6 +1298,7 @@ static void db_sync_on_conn_up(struct ETCP_CONN* conn, void* arg) struct SI_PEER* p = si_peer_add(si, pid); if (!p) continue; if (p->sync_state != 0) continue; + if (si->sync_gated) continue; if (!conn->initialized || !conn->links_up) continue; { uint8_t ek[32]; if (db_get_ed25519_pubkey(db, pid, ek) != 0) continue; } p->sync_state = 1; @@ -1447,6 +1449,35 @@ void db_sync_peer_set_state(struct DB_SYNC_INSTANCE* si, uint64_t node_id, uint8 if (p) p->sync_state = state; } +// Гейт авто-синхронизации: пока gated — инстанс не запускает sync автоматически +// (ждёт member_sync/pubkeys). При снятии гейта запускает sync к пирам в sync_state==0. +void db_sync_instance_set_gated(struct DB_SYNC_INSTANCE* si, int gated) +{ + if (!si || !si->enabled) return; + gated = gated ? 1 : 0; + if (si->sync_gated == (uint8_t)gated) return; + si->sync_gated = (uint8_t)gated; + if (!gated) { + struct DB_SYNC* db = si->db_sync; + int launched = 0; + for (int i = 0; i < si->peer_count; i++) { + struct SI_PEER* p = &si->peers[i]; + if (p->sync_state != 0) continue; + uint8_t ek[32]; + if (db_get_ed25519_pubkey(db, p->node_id, ek) != 0) continue; + p->sync_state = 1; p->sync_start_tb = get_time_tb(); + db_sync_initiate_sync(si, p->node_id); + launched++; + } + if (launched == 0) db_sync_resume_peer_check(db); + DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "sync [%s] → un-gated: launched sync for %d peers (mc=%u)", + SI_SHRT(si), launched, db_count(si)); + } else { + DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "sync [%s] → gated: auto-sync suspended (mc=%u)", + SI_SHRT(si), db_count(si)); + } +} + // Принудительно перезапускает синхронизацию с указанным пиром (для тестов) void db_sync_reinitiate(struct DB_SYNC_INSTANCE* si, uint64_t node_id) { @@ -1517,6 +1548,7 @@ static void db_sync_peer_check_cb(void* arg) for (int i = 0; i < db->instance_count; i++) { struct DB_SYNC_INSTANCE* si = &db->instances[i]; if (!si->enabled) continue; + if (si->sync_gated) continue; int peers_found = 0; struct ll_entry* e = g->senders_list->head; @@ -1776,7 +1808,7 @@ struct DB_SYNC_INSTANCE* db_sync_instance_add(struct UTUN_INSTANCE* inst, const peers_found++; struct SI_PEER* p = si_peer_add(si, pid); if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "si_peer_add failed for %N in %s", (unsigned long long)pid, SI_TBL(si)); continue; } - if (p->sync_state == 0 && auto_sync) { p->sync_state = 1; db_sync_initiate_sync(si, pid); peers_synced++; } + if (p->sync_state == 0 && auto_sync && !si->sync_gated) { p->sync_state = 1; db_sync_initiate_sync(si, pid); peers_synced++; } } entry = entry->next; } diff --git a/src/chat/db_sync.h b/src/chat/db_sync.h index ca16654b..d7b71df4 100644 --- a/src/chat/db_sync.h +++ b/src/chat/db_sync.h @@ -133,6 +133,10 @@ int db_sync_chain_verify(struct DB_SYNC_INSTANCE* si); // Peer state control (for testing — disable PUSH to isolate sync protocol) void db_sync_peer_set_state(struct DB_SYNC_INSTANCE* si, uint64_t node_id, uint8_t state); +// Gate auto-sync: while gated the instance does not auto-initiate sync (waits for +// member_sync/pubkeys). Un-gating launches sync to peers in sync_state==0. +void db_sync_instance_set_gated(struct DB_SYNC_INSTANCE* si, int gated); + // Force re-initiate sync to a specific peer (for testing) void db_sync_reinitiate(struct DB_SYNC_INSTANCE* si, uint64_t node_id); diff --git a/tests/test_db_sync.c b/tests/test_db_sync.c index 26ce910d..187ccf47 100644 --- a/tests/test_db_sync.c +++ b/tests/test_db_sync.c @@ -20,6 +20,7 @@ #include "../src/config_updater.h" #include "../src/utun_instance.h" #include "routing.h" +#include "topo_node.h" #include "../src/tun_if.h" #include "secure_channel.h" #include "secure_channel.h" @@ -154,6 +155,16 @@ static int cond_links_init(void) { e = e->next; } return links >= (inst_c ? 2 : 1); } +static int pubkey_known(struct UTUN_INSTANCE* inst, uint64_t node_id) { + if (!inst || !inst->topo_groups || !node_id) return 0; + struct TOPO_NODE* ni = topo_node_registry_find(inst->topo_groups, node_id); + if (!ni) return 0; + uint64_t chk; memcpy(&chk, ni->ed25519_public_key, 8); + return chk != 0; +} +static int cond_pubkeys_ready(void) { + return inst_b && inst_c && pubkey_known(inst_b, inst_c->node_id) && pubkey_known(inst_c, inst_b->node_id); +} static int _cond_ca(void) { return g_done_a && si_a && db_sync_count(si_a) == ca_target; } static int _cond_cb(void) { return g_done_b && si_b && db_sync_count(si_b) == cb_target; } static int _cond_cc(void) { return g_done_c && si_c && db_sync_count(si_c) == cc_target; } @@ -306,6 +317,7 @@ int main(void) { if (!inst_c || utun_instance_init(inst_c) != 0) { fprintf(stderr, "inst_c fail\n"); test_phase = 2; goto done; } db_sync_add_done_cbk(inst_c, test_done_cb, (void*)2); if (!wait_for("C links up", cond_links_init, PHASE_TIMEOUT_TB)) { test_phase = 2; goto done; } + if (!wait_for("pubkeys", cond_pubkeys_ready, PHASE_TIMEOUT_TB)) { test_phase = 2; goto done; } si_a = db_sync_instance_add(inst_a, "triple", 70, 0); si_b = db_sync_instance_add(inst_b, "triple", 70, 0); diff --git a/tools/chatgui/src/accountlist.cpp b/tools/chatgui/src/accountlist.cpp index eb7669a2..a3d0b831 100644 --- a/tools/chatgui/src/accountlist.cpp +++ b/tools/chatgui/src/accountlist.cpp @@ -163,7 +163,7 @@ void AccountList::setChannel(const QString& channelId) { if (ch.channelId == channelId) { isOwner = (ch.ed25519_privkey.size() == 32); break; } } } - m_header->setText(isOwner ? QStringLiteral("Members \xF0\x9F\x94\x91") : QStringLiteral("Members")); + m_header->setText(isOwner ? QString::fromUtf8("Members \xF0\x9F\x94\x91") : QStringLiteral("Members")); m_header->setToolTip(isOwner ? QStringLiteral("Channel owner (has private key)") : QString()); } diff --git a/tools/chatgui/src/memberpropsdialog.cpp b/tools/chatgui/src/memberpropsdialog.cpp index 6e7442af..dd02bc73 100644 --- a/tools/chatgui/src/memberpropsdialog.cpp +++ b/tools/chatgui/src/memberpropsdialog.cpp @@ -43,6 +43,8 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId, /* load current nick and version from DB (local_nick is local-only, ver is in adm_tags) */ QString curNick; + bool curVerified = false; + bool curDeleted = false; m_currentVer = 0; if (m_db->m_db) { QString tbl = m_db->peersTableName(channelId); @@ -57,6 +59,10 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId, if (tags) { const char* vp = strstr(tags, "\"ver\":\""); if (vp) { vp += 7; m_currentVer = atoi(vp); } + const char* vr = strstr(tags, "\"verified\":\""); + if (vr) { vr += 12; curVerified = (strncmp(vr, "yes", 3) == 0); } + const char* dl = strstr(tags, "\"deleted\":\""); + if (dl) { dl += 11; curDeleted = (strncmp(dl, "yes", 3) == 0); } } curNick = DbManager::colText(st, 1); } @@ -95,6 +101,16 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId, m_storageCb->setEnabled(haveKey); layout->addWidget(m_storageCb); + m_verifiedCb = new QCheckBox("Verified", this); + m_verifiedCb->setChecked(curVerified); + m_verifiedCb->setEnabled(haveKey); + layout->addWidget(m_verifiedCb); + + m_deletedCb = new QCheckBox("Deleted", this); + m_deletedCb->setChecked(curDeleted); + m_deletedCb->setEnabled(haveKey); + layout->addWidget(m_deletedCb); + if (!haveKey) { auto* hint = new QLabel("(Admin/Super require channel private key)", this); hint->setStyleSheet("color: gray; font-size: 11px;"); @@ -157,6 +173,8 @@ void MemberPropsDialog::onSave() { json += QString(",\"admin\":\"%1\"").arg(m_adminCb->isChecked() ? "yes" : "no"); json += QString(",\"supernode\":\"%1\"").arg(m_superCb->isChecked() ? "yes" : "no"); json += QString(",\"storage\":\"%1\"").arg(m_storageCb->isChecked() ? "yes" : "no"); + json += QString(",\"verified\":\"%1\"").arg(m_verifiedCb->isChecked() ? "yes" : "no"); + json += QString(",\"deleted\":\"%1\"").arg(m_deletedCb->isChecked() ? "yes" : "no"); json += "}"; struct chat_member_tags_req* req = (struct chat_member_tags_req*) diff --git a/tools/chatgui/src/memberpropsdialog.h b/tools/chatgui/src/memberpropsdialog.h index 0efd79ad..f612e17d 100644 --- a/tools/chatgui/src/memberpropsdialog.h +++ b/tools/chatgui/src/memberpropsdialog.h @@ -30,4 +30,6 @@ private: QCheckBox* m_adminCb; QCheckBox* m_superCb; QCheckBox* m_storageCb; + QCheckBox* m_verifiedCb; + QCheckBox* m_deletedCb; };