Store channel metadata and peer schema atomically with checked SQL operations

This commit is contained in:
evgeny committed 2026-10-27 04:19:14 +01:00
1 parent 999fe909e4
commit 6a6ad5c612
2 files changed
+74 -41

No files matched your search

+71 -41
View File
@@ -491,9 +491,10 @@ int topo_node_sqlite_member_local_init(sqlite3* db, const char* channel_id) {
const char* column = (const char*)sqlite3_column_text(st, 1);
if (column && !strcmp(column, "radio_muted")) has_radio_muted = 1;
}
sqlite3_finalize(st);
if (rc != SQLITE_DONE) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: read member schema ch=%s: %s", channel_id, sqlite3_errmsg(db)); return -1;
int final = sqlite3_finalize(st);
if (rc != SQLITE_DONE || final != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: read member schema ch=%s step=%d finalize=%d: %s",
channel_id, rc, final, sqlite3_errmsg(db)); return -1;
}
if (!has_radio_muted) {
snprintf(sql, sizeof(sql), "ALTER TABLE \"%s\" ADD COLUMN radio_muted INTEGER NOT NULL DEFAULT 0", peers_tbl);
@@ -510,7 +511,15 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
const uint8_t* x25519_pub, const uint8_t* x25519_priv,
const uint8_t* ed25519_pub, const uint8_t* ed25519_priv,
const uint8_t* signature) {
if (!db || !channel_id || !name || !x25519_pub || !ed25519_pub || !signature) return -1;
if (!db || !channel_id || !name || !x25519_pub || !ed25519_pub || !signature) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "channel put: invalid database/description arguments"); return -1;
}
int rc = sqlite3_exec(db, "SAVEPOINT channel_put", NULL, NULL, NULL);
if (rc != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "channel put: begin channel=%s rc=%d: %s", channel_id, rc, sqlite3_errmsg(db)); return -1;
}
sqlite3_stmt* stmt = NULL;
const char* stage = "allocate peers schema";
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
@@ -548,38 +557,36 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
int ddl_sz = snprintf(NULL, 0, ddl_tmpl, peers_tbl);
char* ddl = u_malloc(ddl_sz + 1);
if (ddl) {
snprintf(ddl, ddl_sz + 1, ddl_tmpl, peers_tbl);
char* err = NULL;
sqlite3_exec(db, ddl, NULL, NULL, &err);
if (err) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "topo_node_sqlite_channel_put peers: %s", err); sqlite3_free(err); }
u_free(ddl);
}
if (!ddl) { rc = SQLITE_NOMEM; goto error; }
snprintf(ddl, ddl_sz + 1, ddl_tmpl, peers_tbl); stage = "create peers schema";
rc = sqlite3_exec(db, ddl, NULL, NULL, NULL); u_free(ddl);
if (rc != SQLITE_OK) goto error;
if (topo_node_sqlite_member_local_init(db, channel_id) < 0) return -1;
stage = "local member schema";
if (topo_node_sqlite_member_local_init(db, channel_id) < 0) { rc = SQLITE_ERROR; goto error; }
int idx_sz = snprintf(NULL, 0,
"CREATE INDEX IF NOT EXISTS \"idx_%s_type_rtt\" ON \"%s\" (node_type, node_RTT)", peers_tbl, peers_tbl);
char* idx_sql = u_malloc(idx_sz + 1);
if (idx_sql) {
snprintf(idx_sql, idx_sz + 1,
"CREATE INDEX IF NOT EXISTS \"idx_%s_type_rtt\" ON \"%s\" (node_type, node_RTT)", peers_tbl, peers_tbl);
sqlite3_exec(db, idx_sql, NULL, NULL, NULL);
u_free(idx_sql);
}
stage = "allocate type index";
if (!idx_sql) { rc = SQLITE_NOMEM; goto error; }
snprintf(idx_sql, idx_sz + 1,
"CREATE INDEX IF NOT EXISTS \"idx_%s_type_rtt\" ON \"%s\" (node_type, node_RTT)", peers_tbl, peers_tbl);
stage = "create type index"; rc = sqlite3_exec(db, idx_sql, NULL, NULL, NULL); u_free(idx_sql);
if (rc != SQLITE_OK) goto error;
int sidx_sz = snprintf(NULL, 0,
"CREATE INDEX IF NOT EXISTS \"idx_%s_storage\" ON \"%s\" (storage)", peers_tbl, peers_tbl);
char* sidx_sql = u_malloc(sidx_sz + 1);
if (sidx_sql) {
snprintf(sidx_sql, sidx_sz + 1,
"CREATE INDEX IF NOT EXISTS \"idx_%s_storage\" ON \"%s\" (storage)", peers_tbl, peers_tbl);
sqlite3_exec(db, sidx_sql, NULL, NULL, NULL);
u_free(sidx_sql);
}
stage = "allocate storage index";
if (!sidx_sql) { rc = SQLITE_NOMEM; goto error; }
snprintf(sidx_sql, sidx_sz + 1,
"CREATE INDEX IF NOT EXISTS \"idx_%s_storage\" ON \"%s\" (storage)", peers_tbl, peers_tbl);
stage = "create storage index"; rc = sqlite3_exec(db, sidx_sql, NULL, NULL, NULL); u_free(sidx_sql);
if (rc != SQLITE_OK) goto error;
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db,
stage = "prepare channel";
rc = sqlite3_prepare_v2(db,
"INSERT INTO channels(channel_id, name, owner_node_id,"
" x25519_pubkey, x25519_privkey, ed25519_pubkey, ed25519_privkey, signature)"
" VALUES(?,?,?,?,?,?,?,?) ON CONFLICT(channel_id) DO UPDATE SET"
@@ -587,22 +594,45 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
" x25519_privkey=COALESCE(excluded.x25519_privkey,channels.x25519_privkey),"
" ed25519_privkey=COALESCE(excluded.ed25519_privkey,channels.ed25519_privkey)"
" WHERE channels.x25519_pubkey=excluded.x25519_pubkey AND channels.ed25519_pubkey=excluded.ed25519_pubkey",
-1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, name, -1, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 3, (sqlite3_int64)owner_node_id);
sqlite3_bind_blob(stmt, 4, x25519_pub, 32, SQLITE_STATIC);
if (x25519_priv) sqlite3_bind_blob(stmt, 5, x25519_priv, 32, SQLITE_STATIC);
else sqlite3_bind_null(stmt, 5);
sqlite3_bind_blob(stmt, 6, ed25519_pub, 32, SQLITE_STATIC);
if (ed25519_priv) sqlite3_bind_blob(stmt, 7, ed25519_priv, 32, SQLITE_STATIC);
else sqlite3_bind_null(stmt, 7);
sqlite3_bind_blob(stmt, 8, signature, 64, SQLITE_STATIC);
int rc = sqlite3_step(stmt); sqlite3_finalize(stmt);
if (rc == SQLITE_DONE && !sqlite3_changes(db)) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "channel: rejected identity change channel=%s", channel_id); return -1;
-1, &stmt, NULL);
if (rc == SQLITE_OK) { stage = "bind channel"; rc = sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC); }
if (rc == SQLITE_OK) { stage = "bind name"; rc = sqlite3_bind_text(stmt, 2, name, -1, SQLITE_STATIC); }
if (rc == SQLITE_OK) { stage = "bind owner"; rc = sqlite3_bind_int64(stmt, 3, (sqlite3_int64)owner_node_id); }
if (rc == SQLITE_OK) { stage = "bind X25519 public"; rc = sqlite3_bind_blob(stmt, 4, x25519_pub, 32, SQLITE_STATIC); }
if (rc == SQLITE_OK) {
stage = "bind X25519 private";
rc = x25519_priv ? sqlite3_bind_blob(stmt, 5, x25519_priv, 32, SQLITE_STATIC) : sqlite3_bind_null(stmt, 5);
}
return rc == SQLITE_DONE ? 0 : -1;
if (rc == SQLITE_OK) { stage = "bind Ed25519 public"; rc = sqlite3_bind_blob(stmt, 6, ed25519_pub, 32, SQLITE_STATIC); }
if (rc == SQLITE_OK) {
stage = "bind Ed25519 private";
rc = ed25519_priv ? sqlite3_bind_blob(stmt, 7, ed25519_priv, 32, SQLITE_STATIC) : sqlite3_bind_null(stmt, 7);
}
if (rc == SQLITE_OK) { stage = "bind signature"; rc = sqlite3_bind_blob(stmt, 8, signature, 64, SQLITE_STATIC); }
if (rc == SQLITE_OK) { stage = "write channel"; rc = sqlite3_step(stmt); }
if (rc != SQLITE_DONE) goto error;
stage = "channel row count";
if (sqlite3_changes(db) != 1) { rc = SQLITE_CONSTRAINT; goto error; }
stage = "finalize channel"; rc = sqlite3_finalize(stmt); stmt = NULL;
if (rc != SQLITE_OK) goto error;
stage = "release channel"; rc = sqlite3_exec(db, "RELEASE channel_put", NULL, NULL, NULL);
if (rc == SQLITE_OK) return 0;
error:
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "channel put: channel=%s stage=%s rc=%d (%s) db=%s",
channel_id, stage, rc, sqlite3_errstr(rc), sqlite3_errmsg(db));
{
int final = sqlite3_finalize(stmt);
if (final != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "channel put: cleanup finalize channel=%s rc=%d", channel_id, final);
}
if (sqlite3_get_autocommit(db)) return -1;
rc = sqlite3_exec(db, "ROLLBACK TO channel_put", NULL, NULL, NULL);
if (rc != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "channel put: rollback channel=%s rc=%d; transaction must be rolled back or closed", channel_id, rc);
return -1;
}
rc = sqlite3_exec(db, "RELEASE channel_put", NULL, NULL, NULL);
if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "channel put: rollback release channel=%s rc=%d", channel_id, rc);
return -1;
}
/* Удаляет канал и производные таблицы целиком; savepoint сохраняет внешнюю транзакцию. */
+3
View File
@@ -49,6 +49,9 @@ int topo_node_sqlite_get_member_channels(sqlite3* db, uint64_t node_id,
/* Создать peers_<id> и сохранить канал; 0/-1. Ключи — по 32, signature — 64 байта, privkey необязательны.
* При обновлении публичные ключи обязаны совпасть; имя/владелец/подпись заменяются, NULL privkey сохраняет прежний.
* Свой savepoint включает peers/индексы/описание; проверяет DDL,все bind/step/changes1/finalize/release.
* Ошибка сохраняет прежние данные/схему. Внешняя транзакция допустима и не завершается.
* Отказ rollback не выполняет release:caller должен откатить/закрыть оставшуюся транзакцию.
* Подпись/пары ключей не проверяет. Табличный суффикс: первые 63 символа, вне [A-Za-z0-9_] заменены на '_'. */
int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
const char* name, uint64_t owner_node_id,