diff --git a/src/routing_layer/topo_node_sqlite.c b/src/routing_layer/topo_node_sqlite.c index 1490f01c..87692864 100644 --- a/src/routing_layer/topo_node_sqlite.c +++ b/src/routing_layer/topo_node_sqlite.c @@ -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) { diff --git a/tests/test_node_snapshot.c b/tests/test_node_snapshot.c index 26a947d4..b6000e39 100644 --- a/tests/test_node_snapshot.c +++ b/tests/test_node_snapshot.c @@ -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);