Browse Source

chat: гейт авто-синхронизации db_sync до member_sync/pubkeys + member tags (verified/deleted) в chatgui

v2
evgeny 4 weeks ago
parent
commit
4b9b0009d8
  1. 1
      src/chat/chat_channel.c
  2. 13
      src/chat/chat_sync.c
  3. 34
      src/chat/db_sync.c
  4. 4
      src/chat/db_sync.h
  5. 12
      tests/test_db_sync.c
  6. 2
      tools/chatgui/src/accountlist.cpp
  7. 18
      tools/chatgui/src/memberpropsdialog.cpp
  8. 2
      tools/chatgui/src/memberpropsdialog.h

1
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 {

13
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);

34
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;
}

4
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);

12
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);

2
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());
}

18
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*)

2
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;
};

Loading…
Cancel
Save