Return peer lists atomically after successful SQL iteration

This commit is contained in:
evgeny committed 2026-10-24 14:09:36 +02:00
1 parent 575a3788e3
commit 81e2a9aa4a
2 files changed
+81 -74

No files matched your search

+66 -74
View File
@@ -1395,98 +1395,90 @@ int topo_node_sqlite_get_connect_peers(sqlite3* db, const char* channel_id,
sqlite3_finalize(stmt); *out = peers; *out_count = count; return 0;
}
int topo_node_sqlite_get_connected_peers(sqlite3* db, const char* channel_id,
uint64_t** out_ids, int* out_count) {
if (!db || !channel_id || !out_ids || !out_count) return -1;
/* Один проход SQL: частичный массив никогда не публикуется при ошибке чтения/памяти. */
static int node_ids_query(sqlite3* db, const char* sql, int exclude_self, uint64_t self,
uint64_t** out_ids, int* out_count) {
*out_ids = NULL; *out_count = 0;
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
char sql[192]; snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\" WHERE connected=1 AND deleted=0", peers_tbl);
sqlite3_stmt* st = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1;
int cnt = 0;
while (sqlite3_step(st) == SQLITE_ROW) cnt++;
if (cnt == 0) { sqlite3_finalize(st); return 0; }
uint64_t* ids = u_malloc((size_t)cnt * sizeof(uint64_t));
if (!ids) { sqlite3_finalize(st); return -1; }
sqlite3_reset(st); int i = 0;
while (sqlite3_step(st) == SQLITE_ROW) ids[i++] = (uint64_t)sqlite3_column_int64(st, 0);
uint64_t* ids = NULL;
size_t count = 0, capacity = 0;
int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL);
if (rc == SQLITE_OK && exclude_self) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)self);
if (rc != SQLITE_OK) goto fail;
while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
if (count == capacity) {
size_t next = capacity ? capacity * 2 : 16;
if (next > UINT32_MAX / sizeof(*ids)) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "peer ID list too large: count=%zu", count); goto fail;
}
uint64_t* resized = u_realloc(ids, next * sizeof(*ids));
if (!resized) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "peer ID list allocation failed: count=%zu", count); goto fail; }
ids = resized; capacity = next;
}
ids[count++] = (uint64_t)sqlite3_column_int64(st, 0);
}
if (rc != SQLITE_DONE) goto fail;
sqlite3_finalize(st);
*out_ids = ids; *out_count = cnt;
*out_ids = ids; *out_count = (int)count;
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "peer ID list read: count=%zu exclude_self=%d", count, exclude_self);
return 0;
fail:
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "peer ID query failed: rc=%d rows=%zu sql=%s: %s", rc, count, sql, sqlite3_errmsg(db));
sqlite3_finalize(st); u_free(ids); return -1;
}
int topo_node_sqlite_get_connected_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count) {
if (out_ids) *out_ids = NULL;
if (out_count) *out_count = 0;
if (!db || !channel_id || !out_ids || !out_count) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "connected peers: invalid arguments"); return -1;
}
char table[80], sql[192]; peers_table_name(channel_id, table, sizeof(table));
snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\" WHERE connected=1 AND deleted=0", table);
return node_ids_query(db, sql, 0, 0, out_ids, out_count);
}
int topo_node_sqlite_get_public_peers(sqlite3* db, const char* channel_id,
uint64_t** out_ids, int* out_count, uint64_t self_node_id) {
if (!db || !channel_id || !out_ids || !out_count) return -1;
*out_ids = NULL; *out_count = 0;
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
char sql[384]; snprintf(sql, sizeof(sql),
uint64_t** out_ids, int* out_count, uint64_t self_node_id) {
if (out_ids) *out_ids = NULL;
if (out_count) *out_count = 0;
if (!db || !channel_id || !out_ids || !out_count) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "public peers: invalid arguments"); return -1;
}
char table[80], sql[512]; peers_table_name(channel_id, table, sizeof(table));
snprintf(sql, sizeof(sql),
"SELECT DISTINCT na.node_id FROM node_addresses na"
" WHERE na.node_id!=?1 AND na.family IN (4,6) AND na.addr_type IN (1,2)"
" AND EXISTS (SELECT 1 FROM nodes n WHERE n.node_id=na.node_id)"
" AND NOT EXISTS (SELECT 1 FROM \"%s\" p WHERE p.node_id=na.node_id AND p.deleted=1)",
peers_tbl);
sqlite3_stmt* st = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1;
sqlite3_bind_int64(st, 1, (sqlite3_int64)self_node_id);
int cnt = 0;
while (sqlite3_step(st) == SQLITE_ROW) cnt++;
if (cnt == 0) { sqlite3_finalize(st); return 0; }
uint64_t* ids = u_malloc((size_t)cnt * sizeof(uint64_t));
if (!ids) { sqlite3_finalize(st); return -1; }
sqlite3_reset(st); int i = 0;
while (sqlite3_step(st) == SQLITE_ROW) ids[i++] = (uint64_t)sqlite3_column_int64(st, 0);
sqlite3_finalize(st);
*out_ids = ids; *out_count = cnt;
return 0;
" AND NOT EXISTS (SELECT 1 FROM \"%s\" p WHERE p.node_id=na.node_id AND p.deleted=1)", table);
return node_ids_query(db, sql, 1, self_node_id, out_ids, out_count);
}
int topo_node_sqlite_get_supernode_peers(sqlite3* db, const char* channel_id,
uint64_t** out_ids, int* out_count) {
if (!db || !channel_id || !out_ids || !out_count) return -1;
*out_ids = NULL; *out_count = 0;
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
char sql[256]; snprintf(sql, sizeof(sql),
"SELECT p.node_id FROM \"%s\" p WHERE p.node_type=4 AND p.deleted=0",
peers_tbl);
sqlite3_stmt* st = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1;
int cnt = 0;
while (sqlite3_step(st) == SQLITE_ROW) cnt++;
if (cnt == 0) { sqlite3_finalize(st); return 0; }
uint64_t* ids = u_malloc((size_t)cnt * sizeof(uint64_t));
if (!ids) { sqlite3_finalize(st); return -1; }
sqlite3_reset(st); int i = 0;
while (sqlite3_step(st) == SQLITE_ROW) ids[i++] = (uint64_t)sqlite3_column_int64(st, 0);
sqlite3_finalize(st);
*out_ids = ids; *out_count = cnt;
return 0;
int topo_node_sqlite_get_supernode_peers(sqlite3* db, const char* channel_id, uint64_t** out_ids, int* out_count) {
if (out_ids) *out_ids = NULL;
if (out_count) *out_count = 0;
if (!db || !channel_id || !out_ids || !out_count) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "supernode peers: invalid arguments"); return -1;
}
char table[80], sql[192]; peers_table_name(channel_id, table, sizeof(table));
snprintf(sql, sizeof(sql), "SELECT node_id FROM \"%s\" WHERE node_type=4 AND deleted=0", table);
return node_ids_query(db, sql, 0, 0, out_ids, out_count);
}
int topo_node_sqlite_get_local_peers(sqlite3* db, const char* channel_id,
uint64_t** out_ids, int* out_count, uint64_t self_node_id) {
if (!db || !channel_id || !out_ids || !out_count) return -1;
*out_ids = NULL; *out_count = 0;
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
char sql[384]; snprintf(sql, sizeof(sql),
uint64_t** out_ids, int* out_count, uint64_t self_node_id) {
if (out_ids) *out_ids = NULL;
if (out_count) *out_count = 0;
if (!db || !channel_id || !out_ids || !out_count) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local peers: invalid arguments"); return -1;
}
char table[80], sql[512]; peers_table_name(channel_id, table, sizeof(table));
snprintf(sql, sizeof(sql),
"SELECT DISTINCT na.node_id FROM node_addresses na"
" WHERE na.node_id!=?1 AND na.family IN (4,6) AND na.addr_type IN (0,3)"
" AND EXISTS (SELECT 1 FROM nodes n WHERE n.node_id=na.node_id)"
" AND NOT EXISTS (SELECT 1 FROM \"%s\" p WHERE p.node_id=na.node_id AND p.deleted=1)",
peers_tbl);
sqlite3_stmt* st = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return -1;
sqlite3_bind_int64(st, 1, (sqlite3_int64)self_node_id);
int cnt = 0;
while (sqlite3_step(st) == SQLITE_ROW) cnt++;
if (cnt == 0) { sqlite3_finalize(st); return 0; }
uint64_t* ids = u_malloc((size_t)cnt * sizeof(uint64_t));
if (!ids) { sqlite3_finalize(st); return -1; }
sqlite3_reset(st); int i = 0;
while (sqlite3_step(st) == SQLITE_ROW) ids[i++] = (uint64_t)sqlite3_column_int64(st, 0);
sqlite3_finalize(st);
*out_ids = ids; *out_count = cnt;
return 0;
" AND NOT EXISTS (SELECT 1 FROM \"%s\" p WHERE p.node_id=na.node_id AND p.deleted=1)", table);
return node_ids_query(db, sql, 1, self_node_id, out_ids, out_count);
}
int topo_node_sqlite_get_node_type(sqlite3* db, const char* channel_id, uint64_t node_id) {
+15
View File
@@ -64,6 +64,8 @@ static void test_zero_port_schema(void) {
}
/* Отказ INSERT/UPDATE не должен выглядеть успешным сохранением уже проверенного узла. */
static int interrupt_sql(void* arg) { (void)arg; return 1; }
static void test_verified_write(void) {
sqlite3* db = NULL; uint8_t key[32] = {1};
assert(sqlite3_open(":memory:", &db) == SQLITE_OK && !topo_node_sqlite_init(db));
@@ -89,6 +91,19 @@ static void test_verified_write(void) {
assert(!topo_node_sqlite_member_placeholder_put(db, "123", 7, key, key));
assert(!topo_node_sqlite_channel_put(db, "124", "test", 7, key, NULL, key, NULL, signature));
assert(!topo_node_sqlite_member_placeholder_put(db, "124", 7, key, key));
assert(!topo_node_sqlite_set_connected(db, "123", 7, 1));
uint64_t* ids = NULL; int count = 0;
assert(!topo_node_sqlite_get_connected_peers(db, "123", &ids, &count) && count == 1 && ids[0] == 7); u_free(ids);
for (int query = 0; query < 4; query++) {
ids = (void*)(uintptr_t)1; count = 99;
sqlite3_progress_handler(db, 1, interrupt_sql, NULL);
int rc = query == 0 ? topo_node_sqlite_get_connected_peers(db, "123", &ids, &count) :
query == 1 ? topo_node_sqlite_get_supernode_peers(db, "123", &ids, &count) :
query == 2 ? topo_node_sqlite_get_public_peers(db, "123", &ids, &count, 0) :
topo_node_sqlite_get_local_peers(db, "123", &ids, &count, 0);
sqlite3_progress_handler(db, 0, NULL, NULL);
assert(rc < 0 && !ids && !count && !sqlite3_next_stmt(db, NULL));
}
assert(sqlite3_exec(db, "UPDATE peers_123 SET node_type=2; UPDATE peers_124 SET node_type=4;"
"CREATE TRIGGER reject_classification BEFORE UPDATE ON peers_124 "
"BEGIN SELECT RAISE(ABORT,'test classification rejection'); END", NULL, NULL, NULL) == SQLITE_OK);