From df809f251c4165b01792f73dbc3343920b08f62e Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 22 Jul 2026 11:39:10 +0300 Subject: [PATCH] ntp: use NTP-corrected time everywhere instead of unixepoch()/gettimeofday - db_sync_next_timestamp(): ntp_time_get_us() via si->db_sync->inst - topo_node_sqlite_node_put: +time_t now_sec param, replace unixepoch() - topo_node_sqlite_node_update_verified: +time_t created_at param - topo_node_sqlite_member_put: joined_at = join_ts (already NTP-corrected) - All INSERTs (nodes, local_identity, accounts, peers): replace DEFAULT(unixepoch()) with explicit ntp_time_get_seconds() - ON CONFLICT DO UPDATE: created_at = COALESCE(nodes.created_at, excluded.created_at) - Updated callers: topo_group.c, chat_sync.c, chat_conn_mgr.c, chat_core.c --- src/db_sync.c | 12 ++++++--- src/routing_layer/topo_group.c | 2 +- src/routing_layer/topo_node_sqlite.c | 35 ++++++++++++++----------- src/routing_layer/topo_node_sqlite.h | 5 ++-- tools/chatgui/transport/chat_conn_mgr.c | 6 +++-- tools/chatgui/transport/chat_core.c | 17 +++++++----- tools/chatgui/transport/chat_sync.c | 26 +++++++++++------- 7 files changed, 64 insertions(+), 39 deletions(-) diff --git a/src/db_sync.c b/src/db_sync.c index 2e96fbe0..b3f524db 100644 --- a/src/db_sync.c +++ b/src/db_sync.c @@ -1587,9 +1587,15 @@ uint64_t db_sync_get_last_timestamp(struct DB_SYNC_INSTANCE* si) uint64_t db_sync_next_timestamp(struct DB_SYNC_INSTANCE* si) { if (!si) return 0; - struct timeval tv; - utun_gettimeofday(&tv, NULL); - uint64_t nu = (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)tv.tv_usec / 1000ULL; + int64_t now_us; + if (si->db_sync && si->db_sync->inst && si->db_sync->inst->ntp.synced) + now_us = ntp_time_get_us(si->db_sync->inst); + else { + struct timeval tv; + utun_gettimeofday(&tv, NULL); + now_us = (int64_t)tv.tv_sec * 1000000LL + tv.tv_usec; + } + uint64_t nu = (uint64_t)(now_us / 1000LL); if (nu <= si->last_timestamp_ms) nu = si->last_timestamp_ms + 1; si->last_timestamp_ms = nu; return nu; diff --git a/src/routing_layer/topo_group.c b/src/routing_layer/topo_group.c index 021c37da..680b8791 100644 --- a/src/routing_layer/topo_group.c +++ b/src/routing_layer/topo_group.c @@ -581,7 +581,7 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from if (node_id != group->instance->node_id) { sqlite3* sdb = group->instance->topo_sqlite_db; if (sdb) { - topo_node_sqlite_node_put(sdb, nodeinfo1); + topo_node_sqlite_node_put(sdb, nodeinfo1, ntp_time_get_seconds(group->instance)); if (group->group_type == TOPO_GROUP_TYPE_CHAT && group->channel_id[0]) { topo_node_sqlite_member_put(sdb, group->channel_id, node_id, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL); DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "SQLite member_put: ch=%s node=%016llx", group->channel_id, (unsigned long long)node_id); diff --git a/src/routing_layer/topo_node_sqlite.c b/src/routing_layer/topo_node_sqlite.c index 30531f8e..0941fdc6 100644 --- a/src/routing_layer/topo_node_sqlite.c +++ b/src/routing_layer/topo_node_sqlite.c @@ -118,7 +118,7 @@ static uint8_t collect_socket_ids(struct TOPO_NODE* ni, uint8_t* sock_ids) { return count; } -int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq) { +int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq, time_t now_sec) { if (!db || !nq || !nq->node) return -1; struct TOPO_NODE* ni = nq->node; @@ -127,15 +127,17 @@ int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq) { sqlite3_stmt* stmt = NULL; if (sqlite3_prepare_v2(db, "INSERT INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, last_seen_at)" - " VALUES(?,?,?,?,unixepoch())" + " VALUES(?,?,?,?,?)" " ON CONFLICT(node_id) DO UPDATE SET" " name=excluded.name, x25519_pubkey=excluded.x25519_pubkey," - " ed25519_pubkey=excluded.ed25519_pubkey, last_seen_at=excluded.last_seen_at", + " ed25519_pubkey=excluded.ed25519_pubkey, last_seen_at=excluded.last_seen_at,\n" + " created_at=COALESCE(nodes.created_at, excluded.created_at)", -1, &stmt, NULL) == SQLITE_OK) { sqlite3_bind_int64(stmt, 1, (sqlite3_int64)ni->node_id); sqlite3_bind_text(stmt, 2, ni->node_name ? ni->node_name : "", -1, SQLITE_STATIC); sqlite3_bind_blob(stmt, 3, ni->public_key, 32, SQLITE_STATIC); sqlite3_bind_blob(stmt, 4, ni->ed25519_public_key, 32, SQLITE_STATIC); + sqlite3_bind_int64(stmt, 5, (sqlite3_int64)now_sec); sqlite3_step(stmt); sqlite3_finalize(stmt); } @@ -317,8 +319,8 @@ int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t no char sql[256]; snprintf(sql, sizeof(sql), "INSERT OR REPLACE INTO \"%s\"(node_id, x25519_pubkey, ed25519_pubkey," - " join_sig, join_ts, update_sig, update_ts, userinfo, creator_sig)" - " VALUES(?,?,?,?,?,?,?,?,?)", peers_tbl); + " join_sig, join_ts, joined_at, update_sig, update_ts, userinfo, creator_sig)" + " VALUES(?,?,?,?,?,?,?,?,?,?)", peers_tbl); sqlite3_stmt* stmt = NULL; if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1; sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id); @@ -327,28 +329,30 @@ int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t no if (join_sig) sqlite3_bind_blob(stmt, 4, join_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); else { static const unsigned char zsig[PEERS_JOIN_SIG_SIZE] = {0}; sqlite3_bind_blob(stmt, 4, zsig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); } sqlite3_bind_int64(stmt, 5, (sqlite3_int64)join_ts); - if (update_sig) sqlite3_bind_blob(stmt, 6, update_sig, PEERS_UPDATE_SIG_SIZE, SQLITE_STATIC); - else sqlite3_bind_null(stmt, 6); - sqlite3_bind_int64(stmt, 7, (sqlite3_int64)update_ts); - sqlite3_bind_text(stmt, 8, userinfo ? userinfo : "", -1, SQLITE_STATIC); - if (creator_sig) sqlite3_bind_blob(stmt, 9, creator_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); - else sqlite3_bind_null(stmt, 9); + sqlite3_bind_int64(stmt, 6, (sqlite3_int64)join_ts); + if (update_sig) sqlite3_bind_blob(stmt, 7, update_sig, PEERS_UPDATE_SIG_SIZE, SQLITE_STATIC); + else sqlite3_bind_null(stmt, 7); + sqlite3_bind_int64(stmt, 8, (sqlite3_int64)update_ts); + sqlite3_bind_text(stmt, 9, userinfo ? userinfo : "", -1, SQLITE_STATIC); + if (creator_sig) sqlite3_bind_blob(stmt, 10, creator_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); + else sqlite3_bind_null(stmt, 10); int rc = sqlite3_step(stmt); sqlite3_finalize(stmt); return rc == SQLITE_DONE ? 0 : -1; } int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id, - const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts) { + const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts, time_t created_at) { if (!db || !x25519 || !ed25519) return -1; sqlite3_stmt* stmt = NULL; if (sqlite3_prepare_v2(db, - "INSERT INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, update_ts)" - " VALUES(?,?,?,?,?)" + "INSERT INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, created_at, update_ts)" + " VALUES(?,?,?,?,?,?)" " ON CONFLICT(node_id) DO UPDATE SET" " name=CASE WHEN excluded.name IS NOT NULL AND excluded.name != ''" " THEN excluded.name ELSE nodes.name END," " x25519_pubkey=excluded.x25519_pubkey," " ed25519_pubkey=excluded.ed25519_pubkey," + " created_at=COALESCE(nodes.created_at, excluded.created_at)," " update_ts=CASE WHEN nodes.update_ts IS NULL OR excluded.update_ts > nodes.update_ts" " THEN excluded.update_ts ELSE nodes.update_ts END", -1, &stmt, NULL) != SQLITE_OK) return -1; @@ -356,7 +360,8 @@ int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id, sqlite3_bind_text(stmt, 2, name && name[0] ? name : "", -1, SQLITE_STATIC); sqlite3_bind_blob(stmt, 3, x25519, 32, SQLITE_STATIC); sqlite3_bind_blob(stmt, 4, ed25519, 32, SQLITE_STATIC); - sqlite3_bind_int64(stmt, 5, (sqlite3_int64)update_ts); + sqlite3_bind_int64(stmt, 5, (sqlite3_int64)created_at); + sqlite3_bind_int64(stmt, 6, (sqlite3_int64)update_ts); sqlite3_step(stmt); sqlite3_finalize(stmt); return 0; diff --git a/src/routing_layer/topo_node_sqlite.h b/src/routing_layer/topo_node_sqlite.h index 30a43dd4..3f95126f 100644 --- a/src/routing_layer/topo_node_sqlite.h +++ b/src/routing_layer/topo_node_sqlite.h @@ -4,6 +4,7 @@ #include #include #include +#include struct TOPO_NODEQ; @@ -16,7 +17,7 @@ struct TOPO_NODEQ; int topo_node_sqlite_init(sqlite3* db); -int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq); +int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_NODEQ* nq, time_t now_sec); int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id, const char* name, uint64_t owner_node_id, @@ -36,7 +37,7 @@ int topo_node_sqlite_member_get_join(sqlite3* db, const char* channel_id, uint64 int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t node_id); int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id, - const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts); + const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts, time_t created_at); int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id, char* name_out, int name_sz, uint64_t* owner_node_id, diff --git a/tools/chatgui/transport/chat_conn_mgr.c b/tools/chatgui/transport/chat_conn_mgr.c index fc27f2a7..0cbc3533 100644 --- a/tools/chatgui/transport/chat_conn_mgr.c +++ b/tools/chatgui/transport/chat_conn_mgr.c @@ -598,11 +598,13 @@ void chat_conn_mgr_connect_from_invite(struct chat_conn_mgr* mgr, struct chat_in /* save pubkey */ sqlite3_stmt* st = NULL; - if (sqlite3_prepare_v2(mgr->db, "INSERT INTO nodes(node_id,x25519_pubkey) VALUES(?,?)" - " ON CONFLICT(node_id) DO UPDATE SET x25519_pubkey=excluded.x25519_pubkey", + if (sqlite3_prepare_v2(mgr->db, "INSERT INTO nodes(node_id,x25519_pubkey,created_at) VALUES(?,?,?)" + " ON CONFLICT(node_id) DO UPDATE SET x25519_pubkey=excluded.x25519_pubkey," + " created_at=COALESCE(nodes.created_at, excluded.created_at)", -1, &st, NULL) == SQLITE_OK) { sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id); sqlite3_bind_blob(st, 2, inv->pubkey, 32, SQLITE_STATIC); + sqlite3_bind_int64(st, 3, (sqlite3_int64)ntp_time_get_seconds(mgr->inst)); sqlite3_step(st); sqlite3_finalize(st); } diff --git a/tools/chatgui/transport/chat_core.c b/tools/chatgui/transport/chat_core.c index c869be2d..9ace5fc5 100644 --- a/tools/chatgui/transport/chat_core.c +++ b/tools/chatgui/transport/chat_core.c @@ -112,6 +112,7 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) { g_cc.inst = inst; g_cc.my_node_id = inst->node_id; + sqlite3_int64 now_sec = (sqlite3_int64)ntp_time_get_seconds(inst); topo_node_sqlite_init(g_cc.db); @@ -120,24 +121,27 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) { const char* my_name = inst->name[0] ? inst->name : "Me"; sqlite3_stmt* st = NULL; sqlite3_prepare_v2(g_cc.db, - "INSERT OR REPLACE INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, online)" - " VALUES(?,?,?,?,1)", -1, &st, NULL); + "INSERT OR REPLACE INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, created_at, online)" + " VALUES(?,?,?,?,?,1)", -1, &st, NULL); if (st) { sqlite3_bind_int64(st, 1, (sqlite3_int64)g_cc.my_node_id); sqlite3_bind_text(st, 2, my_name, -1, SQLITE_STATIC); sqlite3_bind_blob(st, 3, inst->my_keys.public_key, 32, SQLITE_STATIC); sqlite3_bind_blob(st, 4, inst->my_ed25519_pubkey, 32, SQLITE_STATIC); + sqlite3_bind_int64(st, 5, now_sec); sqlite3_step(st); sqlite3_finalize(st); } st = NULL; sqlite3_prepare_v2(g_cc.db, - "INSERT OR REPLACE INTO local_identity(id,node_id,name,x25519_pubkey,ed25519_pubkey)" - " VALUES(1,?,?,?,?)", -1, &st, NULL); + "INSERT OR REPLACE INTO local_identity(id,node_id,name,x25519_pubkey,ed25519_pubkey,created_at,updated_at)" + " VALUES(1,?,?,?,?,?,?)", -1, &st, NULL); if (st) { sqlite3_bind_int64(st, 1, (sqlite3_int64)g_cc.my_node_id); sqlite3_bind_text(st, 2, my_name, -1, SQLITE_STATIC); sqlite3_bind_blob(st, 3, inst->my_keys.public_key, 32, SQLITE_STATIC); sqlite3_bind_blob(st, 4, inst->my_ed25519_pubkey, 32, SQLITE_STATIC); + sqlite3_bind_int64(st, 5, now_sec); + sqlite3_bind_int64(st, 6, now_sec); sqlite3_step(st); sqlite3_finalize(st); } } @@ -189,13 +193,14 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) { for (int i = 0; i < 5; i++) { sqlite3_stmt* ins = NULL; sqlite3_prepare_v2(g_cc.db, - "INSERT OR IGNORE INTO accounts(node_id,display_name,avatar_color,avatar_letter)" - " VALUES(?,?,?,?)", -1, &ins, NULL); + "INSERT OR IGNORE INTO accounts(node_id,display_name,avatar_color,avatar_letter,created_at)" + " VALUES(?,?,?,?,?)", -1, &ins, NULL); if (ins) { sqlite3_bind_int64(ins, 1, accs[i].id); sqlite3_bind_text(ins, 2, accs[i].name, -1, SQLITE_STATIC); sqlite3_bind_text(ins, 3, accs[i].color, -1, SQLITE_STATIC); sqlite3_bind_text(ins, 4, accs[i].letter, -1, SQLITE_STATIC); + sqlite3_bind_int64(ins, 5, now_sec); sqlite3_step(ins); sqlite3_finalize(ins); } } diff --git a/tools/chatgui/transport/chat_sync.c b/tools/chatgui/transport/chat_sync.c index 72df5542..0839529d 100644 --- a/tools/chatgui/transport/chat_sync.c +++ b/tools/chatgui/transport/chat_sync.c @@ -1052,14 +1052,16 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, if (vdb && inv_userinfo[0]) { sqlite3_stmt* ns = NULL; sqlite3_prepare_v2(vdb, - "INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey) VALUES(?,?,?,?)" + "INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey,created_at) VALUES(?,?,?,?,?)" " ON CONFLICT(node_id) DO UPDATE SET" - " name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey", + " name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey," + " created_at=COALESCE(nodes.created_at, excluded.created_at)", -1, &ns, NULL); if (ns) { sqlite3_bind_int64(ns, 1, (sqlite3_int64)peer); sqlite3_bind_text(ns, 2, inv_userinfo, -1, SQLITE_STATIC); sqlite3_bind_blob(ns, 3, inv_x25519, 32, SQLITE_STATIC); sqlite3_bind_blob(ns, 4, inv_ed, 32, SQLITE_STATIC); + sqlite3_bind_int64(ns, 5, (sqlite3_int64)ntp_time_get_seconds(cs->inst)); sqlite3_step(ns); sqlite3_finalize(ns); } } /* save inviter's address from ETCP connection */ @@ -1094,7 +1096,7 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, int rc = topo_node_sqlite_member_put(vdb, ch_id, peer, inviter_join_sig, inviter_join_ts, inviter_update_sig, inviter_update_ts, inv_x25519, inv_ed, inv_userinfo, inviter_join_sig); if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(inviter) FAILED ch=%s peer=%016llx rc=%d", CS_ID, ch_id, (unsigned long long)peer, rc); char inv_name[128]; json_flat_get(inv_userinfo, "name", inv_name, sizeof(inv_name)); - topo_node_sqlite_node_update_verified(vdb, peer, inv_name, inv_x25519, inv_ed, inviter_join_ts); + topo_node_sqlite_node_update_verified(vdb, peer, inv_name, inv_x25519, inv_ed, inviter_join_ts, ntp_time_get_seconds(cs->inst)); } /* generate our own join_sig */ @@ -1206,20 +1208,22 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer, int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, joiner_userinfo, NULL); if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(joiner) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc); { char j_name[128]; json_flat_get(joiner_userinfo, "name", j_name, sizeof(j_name)); - topo_node_sqlite_node_update_verified(db, node_id, j_name, x25519, ed_pub, join_ts); } + topo_node_sqlite_node_update_verified(db, node_id, j_name, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); } chat_conn_mgr_add_node(chat_conn_mgr_get(), node_id); /* save joiner node_info to local DB */ if (db && joiner_userinfo[0]) { sqlite3_stmt* ns = NULL; sqlite3_prepare_v2(db, - "INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey) VALUES(?,?,?,?)" + "INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey,created_at) VALUES(?,?,?,?,?)" " ON CONFLICT(node_id) DO UPDATE SET" - " name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey", + " name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey," + " created_at=COALESCE(nodes.created_at, excluded.created_at)", -1, &ns, NULL); if (ns) { sqlite3_bind_int64(ns, 1, (sqlite3_int64)node_id); sqlite3_bind_text(ns, 2, joiner_userinfo, -1, SQLITE_STATIC); sqlite3_bind_blob(ns, 3, x25519, 32, SQLITE_STATIC); sqlite3_bind_blob(ns, 4, ed_pub, 32, SQLITE_STATIC); + sqlite3_bind_int64(ns, 5, (sqlite3_int64)ntp_time_get_seconds(cs->inst)); sqlite3_step(ns); sqlite3_finalize(ns); } } @@ -1378,7 +1382,7 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer, int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_userinfo, NULL); if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(welcome) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc); { char pn[256]; json_flat_get(peer_userinfo, "name", pn, sizeof(pn)); - topo_node_sqlite_node_update_verified(db, node_id, pn, x25519, ed_pub, join_ts); } + topo_node_sqlite_node_update_verified(db, node_id, pn, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); } chat_conn_mgr_add_node(chat_conn_mgr_get(), node_id); @@ -1482,19 +1486,21 @@ static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer, int rc = topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_userinfo, NULL); if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_put(peer_upsert) FAILED ch=%s node=0x%016llx rc=%d", CS_ID, ch_id, (unsigned long long)node_id, rc); { char pn2[128]; json_flat_get(peer_userinfo, "name", pn2, sizeof(pn2)); - topo_node_sqlite_node_update_verified(db, node_id, pn2, x25519, ed_pub, join_ts); } + topo_node_sqlite_node_update_verified(db, node_id, pn2, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); } chat_conn_mgr_add_node(chat_conn_mgr_get(), node_id); if (db && peer_userinfo[0]) { sqlite3_stmt* ns = NULL; sqlite3_prepare_v2(db, - "INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey) VALUES(?,?,?,?)" + "INSERT INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey,created_at) VALUES(?,?,?,?,?)" " ON CONFLICT(node_id) DO UPDATE SET" - " name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey", + " name=excluded.name, x25519_pubkey=excluded.x25519_pubkey, ed25519_pubkey=excluded.ed25519_pubkey," + " created_at=COALESCE(nodes.created_at, excluded.created_at)", -1, &ns, NULL); if (ns) { sqlite3_bind_int64(ns, 1, (sqlite3_int64)node_id); sqlite3_bind_text(ns, 2, peer_userinfo, -1, SQLITE_STATIC); sqlite3_bind_blob(ns, 3, x25519, 32, SQLITE_STATIC); sqlite3_bind_blob(ns, 4, ed_pub, 32, SQLITE_STATIC); + sqlite3_bind_int64(ns, 5, (sqlite3_int64)ntp_time_get_seconds(cs->inst)); sqlite3_step(ns); sqlite3_finalize(ns); } }