Store channel metadata and peer schema atomically with checked SQL operations
This commit is contained in:
1 parent
999fe909e4
commit
6a6ad5c612
2 files changed
+74
-41
No files matched your search
@@ -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 сохраняет внешнюю транзакцию. */
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user