refactor(monero-rpc-pool): Pass around tuple of (scheme, host, port) as nodes

This commit is contained in:
Binarybaron 2025-06-24 11:36:44 +02:00
parent dc21ab9cee
commit 1c5acdc1c7
25 changed files with 792 additions and 810 deletions

View file

@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "SELECT id FROM monero_nodes WHERE scheme = ? AND host = ? AND port = ?",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer"
}
],
"parameters": {
"Right": 3
},
"nullable": [
true
]
},
"hash": "03e5b2bccf8bffb962a56443448311800bb832efe37fe6c52181bd7bf631740c"
}

View file

@ -0,0 +1,110 @@
{
"db_name": "SQLite",
"query": "\n SELECT \n n.id as \"id!: i64\",\n n.scheme,\n n.host,\n n.port,\n n.network,\n n.first_seen_at,\n CAST(COALESCE(stats.success_count, 0) AS INTEGER) as \"success_count!: i64\",\n CAST(COALESCE(stats.failure_count, 0) AS INTEGER) as \"failure_count!: i64\",\n stats.last_success as \"last_success?: String\",\n stats.last_failure as \"last_failure?: String\",\n stats.last_checked as \"last_checked?: String\",\n CAST(CASE WHEN reliable_nodes.node_id IS NOT NULL THEN 1 ELSE 0 END AS INTEGER) as \"is_reliable!: i64\",\n stats.avg_latency_ms as \"avg_latency_ms?: f64\",\n stats.min_latency_ms as \"min_latency_ms?: f64\",\n stats.max_latency_ms as \"max_latency_ms?: f64\",\n stats.last_latency_ms as \"last_latency_ms?: f64\"\n FROM monero_nodes n\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n MAX(CASE WHEN was_successful THEN timestamp END) as last_success,\n MAX(CASE WHEN NOT was_successful THEN timestamp END) as last_failure,\n MAX(timestamp) as last_checked,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms,\n MIN(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as min_latency_ms,\n MAX(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as max_latency_ms,\n (SELECT latency_ms FROM health_checks hc2 WHERE hc2.node_id = health_checks.node_id ORDER BY timestamp DESC LIMIT 1) as last_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) stats ON n.id = stats.node_id\n LEFT JOIN (\n SELECT DISTINCT node_id FROM (\n SELECT \n n2.id as node_id,\n COALESCE(s2.success_count, 0) as success_count,\n COALESCE(s2.failure_count, 0) as failure_count,\n s2.avg_latency_ms,\n (CAST(COALESCE(s2.success_count, 0) AS REAL) / CAST(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0) AS REAL)) * \n (MIN(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0), 200) / 200.0) * 0.8 +\n CASE \n WHEN s2.avg_latency_ms IS NOT NULL THEN (1.0 - (MIN(s2.avg_latency_ms, 2000) / 2000.0)) * 0.2\n ELSE 0.0 \n END as reliability_score\n FROM monero_nodes n2\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) s2 ON n2.id = s2.node_id\n WHERE n2.network = ? AND (COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0)) > 0\n ORDER BY reliability_score DESC\n LIMIT 4\n )\n ) reliable_nodes ON n.id = reliable_nodes.node_id\n WHERE n.network = ? AND (COALESCE(stats.success_count, 0) + COALESCE(stats.failure_count, 0)) > 0\n ORDER BY \n (CAST(COALESCE(stats.success_count, 0) AS REAL) / CAST(COALESCE(stats.success_count, 0) + COALESCE(stats.failure_count, 0) AS REAL)) DESC,\n stats.avg_latency_ms ASC\n LIMIT ?\n ",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "scheme",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "host",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "port",
"ordinal": 3,
"type_info": "Integer"
},
{
"name": "network",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "first_seen_at",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "success_count!: i64",
"ordinal": 6,
"type_info": "Null"
},
{
"name": "failure_count!: i64",
"ordinal": 7,
"type_info": "Null"
},
{
"name": "last_success?: String",
"ordinal": 8,
"type_info": "Null"
},
{
"name": "last_failure?: String",
"ordinal": 9,
"type_info": "Null"
},
{
"name": "last_checked?: String",
"ordinal": 10,
"type_info": "Null"
},
{
"name": "is_reliable!: i64",
"ordinal": 11,
"type_info": "Null"
},
{
"name": "avg_latency_ms?: f64",
"ordinal": 12,
"type_info": "Null"
},
{
"name": "min_latency_ms?: f64",
"ordinal": 13,
"type_info": "Null"
},
{
"name": "max_latency_ms?: f64",
"ordinal": 14,
"type_info": "Null"
},
{
"name": "last_latency_ms?: f64",
"ordinal": 15,
"type_info": "Float"
}
],
"parameters": {
"Right": 3
},
"nullable": [
true,
false,
false,
false,
false,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true
]
},
"hash": "08d143b977a7fa23b289c22dee3cab4d64debeea9932c58047cc6244d136f80d"
}

View file

@ -0,0 +1,110 @@
{
"db_name": "SQLite",
"query": "\n SELECT \n n.id as \"id!: i64\",\n n.scheme,\n n.host,\n n.port,\n n.network,\n n.first_seen_at,\n CAST(COALESCE(stats.success_count, 0) AS INTEGER) as \"success_count!: i64\",\n CAST(COALESCE(stats.failure_count, 0) AS INTEGER) as \"failure_count!: i64\",\n stats.last_success as \"last_success?: String\",\n stats.last_failure as \"last_failure?: String\",\n stats.last_checked as \"last_checked?: String\",\n CAST(CASE WHEN reliable_nodes.node_id IS NOT NULL THEN 1 ELSE 0 END AS INTEGER) as \"is_reliable!: i64\",\n stats.avg_latency_ms as \"avg_latency_ms?: f64\",\n stats.min_latency_ms as \"min_latency_ms?: f64\",\n stats.max_latency_ms as \"max_latency_ms?: f64\",\n stats.last_latency_ms as \"last_latency_ms?: f64\"\n FROM monero_nodes n\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n MAX(CASE WHEN was_successful THEN timestamp END) as last_success,\n MAX(CASE WHEN NOT was_successful THEN timestamp END) as last_failure,\n MAX(timestamp) as last_checked,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms,\n MIN(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as min_latency_ms,\n MAX(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as max_latency_ms,\n (SELECT latency_ms FROM health_checks hc2 WHERE hc2.node_id = health_checks.node_id ORDER BY timestamp DESC LIMIT 1) as last_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) stats ON n.id = stats.node_id\n LEFT JOIN (\n SELECT DISTINCT node_id FROM (\n SELECT \n n2.id as node_id,\n COALESCE(s2.success_count, 0) as success_count,\n COALESCE(s2.failure_count, 0) as failure_count,\n s2.avg_latency_ms,\n (CAST(COALESCE(s2.success_count, 0) AS REAL) / CAST(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0) AS REAL)) * \n (MIN(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0), 200) / 200.0) * 0.8 +\n CASE \n WHEN s2.avg_latency_ms IS NOT NULL THEN (1.0 - (MIN(s2.avg_latency_ms, 2000) / 2000.0)) * 0.2\n ELSE 0.0 \n END as reliability_score\n FROM monero_nodes n2\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) s2 ON n2.id = s2.node_id\n WHERE n2.network = ? AND (COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0)) > 0\n ORDER BY reliability_score DESC\n LIMIT 4\n )\n ) reliable_nodes ON n.id = reliable_nodes.node_id\n WHERE n.network = ?\n ORDER BY stats.avg_latency_ms ASC, stats.success_count DESC\n ",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "scheme",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "host",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "port",
"ordinal": 3,
"type_info": "Integer"
},
{
"name": "network",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "first_seen_at",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "success_count!: i64",
"ordinal": 6,
"type_info": "Null"
},
{
"name": "failure_count!: i64",
"ordinal": 7,
"type_info": "Null"
},
{
"name": "last_success?: String",
"ordinal": 8,
"type_info": "Null"
},
{
"name": "last_failure?: String",
"ordinal": 9,
"type_info": "Null"
},
{
"name": "last_checked?: String",
"ordinal": 10,
"type_info": "Null"
},
{
"name": "is_reliable!: i64",
"ordinal": 11,
"type_info": "Null"
},
{
"name": "avg_latency_ms?: f64",
"ordinal": 12,
"type_info": "Null"
},
{
"name": "min_latency_ms?: f64",
"ordinal": 13,
"type_info": "Null"
},
{
"name": "max_latency_ms?: f64",
"ordinal": 14,
"type_info": "Null"
},
{
"name": "last_latency_ms?: f64",
"ordinal": 15,
"type_info": "Float"
}
],
"parameters": {
"Right": 2
},
"nullable": [
true,
false,
false,
false,
false,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true
]
},
"hash": "0aa34e769813a40e0518f5311ff95685fd5278103d13f56795ff5a51b0ef8036"
}

View file

@ -0,0 +1,110 @@
{
"db_name": "SQLite",
"query": "\n SELECT \n n.id as \"id!: i64\",\n n.scheme,\n n.host,\n n.port,\n n.network,\n n.first_seen_at,\n CAST(COALESCE(stats.success_count, 0) AS INTEGER) as \"success_count!: i64\",\n CAST(COALESCE(stats.failure_count, 0) AS INTEGER) as \"failure_count!: i64\",\n stats.last_success as \"last_success?: String\",\n stats.last_failure as \"last_failure?: String\",\n stats.last_checked as \"last_checked?: String\",\n CAST(CASE WHEN reliable_nodes.node_id IS NOT NULL THEN 1 ELSE 0 END AS INTEGER) as \"is_reliable!: i64\",\n stats.avg_latency_ms as \"avg_latency_ms?: f64\",\n stats.min_latency_ms as \"min_latency_ms?: f64\",\n stats.max_latency_ms as \"max_latency_ms?: f64\",\n stats.last_latency_ms as \"last_latency_ms?: f64\"\n FROM monero_nodes n\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n MAX(CASE WHEN was_successful THEN timestamp END) as last_success,\n MAX(CASE WHEN NOT was_successful THEN timestamp END) as last_failure,\n MAX(timestamp) as last_checked,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms,\n MIN(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as min_latency_ms,\n MAX(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as max_latency_ms,\n (SELECT latency_ms FROM health_checks hc2 WHERE hc2.node_id = health_checks.node_id ORDER BY timestamp DESC LIMIT 1) as last_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) stats ON n.id = stats.node_id\n LEFT JOIN (\n SELECT DISTINCT node_id FROM (\n SELECT \n n2.id as node_id,\n COALESCE(s2.success_count, 0) as success_count,\n COALESCE(s2.failure_count, 0) as failure_count,\n s2.avg_latency_ms,\n (CAST(COALESCE(s2.success_count, 0) AS REAL) / CAST(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0) AS REAL)) * \n (MIN(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0), 200) / 200.0) * 0.8 +\n CASE \n WHEN s2.avg_latency_ms IS NOT NULL THEN (1.0 - (MIN(s2.avg_latency_ms, 2000) / 2000.0)) * 0.2\n ELSE 0.0 \n END as reliability_score\n FROM monero_nodes n2\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) s2 ON n2.id = s2.node_id\n WHERE n2.network = ? AND (COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0)) > 0\n ORDER BY reliability_score DESC\n LIMIT 4\n )\n ) reliable_nodes ON n.id = reliable_nodes.node_id\n WHERE n.network = ?\n ORDER BY RANDOM()\n LIMIT ?\n ",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "scheme",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "host",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "port",
"ordinal": 3,
"type_info": "Integer"
},
{
"name": "network",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "first_seen_at",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "success_count!: i64",
"ordinal": 6,
"type_info": "Null"
},
{
"name": "failure_count!: i64",
"ordinal": 7,
"type_info": "Null"
},
{
"name": "last_success?: String",
"ordinal": 8,
"type_info": "Null"
},
{
"name": "last_failure?: String",
"ordinal": 9,
"type_info": "Null"
},
{
"name": "last_checked?: String",
"ordinal": 10,
"type_info": "Null"
},
{
"name": "is_reliable!: i64",
"ordinal": 11,
"type_info": "Null"
},
{
"name": "avg_latency_ms?: f64",
"ordinal": 12,
"type_info": "Null"
},
{
"name": "min_latency_ms?: f64",
"ordinal": 13,
"type_info": "Null"
},
{
"name": "max_latency_ms?: f64",
"ordinal": 14,
"type_info": "Null"
},
{
"name": "last_latency_ms?: f64",
"ordinal": 15,
"type_info": "Float"
}
],
"parameters": {
"Right": 3
},
"nullable": [
true,
false,
false,
false,
false,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true
]
},
"hash": "2a378cb109fe284ba3a939aed1bcb50dc694c89ef1eb08bf3d62e7d9e0902a4e"
}

View file

@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n SELECT \n id as \"id!: i64\",\n scheme,\n host,\n port,\n full_url,\n network as \"network!: String\",\n first_seen_at\n FROM monero_nodes \n ORDER BY id\n ",
"query": "\n SELECT \n id as \"id!: i64\",\n scheme,\n host,\n port,\n network as \"network!: String\",\n first_seen_at\n FROM monero_nodes \n ORDER BY id\n ",
"describe": {
"columns": [
{
@ -24,25 +24,27 @@
"type_info": "Integer"
},
{
"name": "full_url",
"name": "network!: String",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "network!: String",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "first_seen_at",
"ordinal": 6,
"ordinal": 5,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [false, false, false, false, false, false, false]
"nullable": [
false,
false,
false,
false,
false,
false
]
},
"hash": "3e8f39a6ec4443cec6497672891d12bbf7c1d0aca061827740af88ced863ae23"
"hash": "37157927724c8bc647bf4f76f5698631cbd40637778dfa83e8f644ae6a7cf75b"
}

View file

@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO monero_nodes (scheme, host, port, network, first_seen_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(scheme, host, port) DO UPDATE SET\n network = excluded.network,\n updated_at = excluded.updated_at\n RETURNING id\n ",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer"
}
],
"parameters": {
"Right": 6
},
"nullable": [
false
]
},
"hash": "3870c77c7c5fbb9bdd57c365765178a08de20e442a07f3e734e61c410e4f338e"
}

View file

@ -1,116 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT \n n.id as \"id!: i64\",\n n.scheme,\n n.host,\n n.port,\n n.full_url,\n n.network,\n n.first_seen_at,\n CAST(COALESCE(stats.success_count, 0) AS INTEGER) as \"success_count!: i64\",\n CAST(COALESCE(stats.failure_count, 0) AS INTEGER) as \"failure_count!: i64\",\n stats.last_success as \"last_success?: String\",\n stats.last_failure as \"last_failure?: String\",\n stats.last_checked as \"last_checked?: String\",\n CAST(CASE WHEN reliable_nodes.node_id IS NOT NULL THEN 1 ELSE 0 END AS INTEGER) as \"is_reliable!: i64\",\n stats.avg_latency_ms as \"avg_latency_ms?: f64\",\n stats.min_latency_ms as \"min_latency_ms?: f64\",\n stats.max_latency_ms as \"max_latency_ms?: f64\",\n stats.last_latency_ms as \"last_latency_ms?: f64\"\n FROM monero_nodes n\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n MAX(CASE WHEN was_successful THEN timestamp END) as last_success,\n MAX(CASE WHEN NOT was_successful THEN timestamp END) as last_failure,\n MAX(timestamp) as last_checked,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms,\n MIN(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as min_latency_ms,\n MAX(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as max_latency_ms,\n (SELECT latency_ms FROM health_checks hc2 WHERE hc2.node_id = health_checks.node_id ORDER BY timestamp DESC LIMIT 1) as last_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) stats ON n.id = stats.node_id\n LEFT JOIN (\n SELECT DISTINCT node_id FROM (\n SELECT \n n2.id as node_id,\n COALESCE(s2.success_count, 0) as success_count,\n COALESCE(s2.failure_count, 0) as failure_count,\n s2.avg_latency_ms,\n (CAST(COALESCE(s2.success_count, 0) AS REAL) / CAST(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0) AS REAL)) * \n (MIN(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0), 200) / 200.0) * 0.8 +\n CASE \n WHEN s2.avg_latency_ms IS NOT NULL THEN (1.0 - (MIN(s2.avg_latency_ms, 2000) / 2000.0)) * 0.2\n ELSE 0.0 \n END as reliability_score\n FROM monero_nodes n2\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) s2 ON n2.id = s2.node_id\n WHERE n2.network = ? AND (COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0)) > 0\n ORDER BY reliability_score DESC\n LIMIT 4\n )\n ) reliable_nodes ON n.id = reliable_nodes.node_id\n WHERE n.network = ? AND (COALESCE(stats.success_count, 0) + COALESCE(stats.failure_count, 0)) > 0\n ORDER BY \n (CAST(COALESCE(stats.success_count, 0) AS REAL) / CAST(COALESCE(stats.success_count, 0) + COALESCE(stats.failure_count, 0) AS REAL)) DESC,\n stats.avg_latency_ms ASC\n LIMIT ?\n ",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "scheme",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "host",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "port",
"ordinal": 3,
"type_info": "Integer"
},
{
"name": "full_url",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "network",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "first_seen_at",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "success_count!: i64",
"ordinal": 7,
"type_info": "Null"
},
{
"name": "failure_count!: i64",
"ordinal": 8,
"type_info": "Null"
},
{
"name": "last_success?: String",
"ordinal": 9,
"type_info": "Null"
},
{
"name": "last_failure?: String",
"ordinal": 10,
"type_info": "Null"
},
{
"name": "last_checked?: String",
"ordinal": 11,
"type_info": "Null"
},
{
"name": "is_reliable!: i64",
"ordinal": 12,
"type_info": "Null"
},
{
"name": "avg_latency_ms?: f64",
"ordinal": 13,
"type_info": "Null"
},
{
"name": "min_latency_ms?: f64",
"ordinal": 14,
"type_info": "Null"
},
{
"name": "max_latency_ms?: f64",
"ordinal": 15,
"type_info": "Null"
},
{
"name": "last_latency_ms?: f64",
"ordinal": 16,
"type_info": "Float"
}
],
"parameters": {
"Right": 3
},
"nullable": [
true,
false,
false,
false,
false,
false,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true
]
},
"hash": "549f5ef13ec7bf5d987dcb893753a9c903edcafa3a66bd82965b40a9e7f238b6"
}

View file

@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n UPDATE monero_nodes \n SET network = ?, updated_at = ?\n WHERE full_url = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "5736de2aac47eb69d7f6835d266aa28732b02a5e8e055ffaebcb452ed1b5044c"
}

View file

@ -1,18 +0,0 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO monero_nodes (scheme, host, port, full_url, network, first_seen_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(full_url) DO UPDATE SET\n network = excluded.network,\n updated_at = excluded.updated_at\n RETURNING id\n ",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer"
}
],
"parameters": {
"Right": 7
},
"nullable": [false]
},
"hash": "5798d9589772742f074e0ecc2551a40d943bfb7ed2e295f09f12d77cb65ce821"
}

View file

@ -1,116 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT \n n.id as \"id!: i64\",\n n.scheme,\n n.host,\n n.port,\n n.full_url,\n n.network,\n n.first_seen_at,\n CAST(COALESCE(stats.success_count, 0) AS INTEGER) as \"success_count!: i64\",\n CAST(COALESCE(stats.failure_count, 0) AS INTEGER) as \"failure_count!: i64\",\n stats.last_success as \"last_success?: String\",\n stats.last_failure as \"last_failure?: String\",\n stats.last_checked as \"last_checked?: String\",\n CAST(CASE WHEN reliable_nodes.node_id IS NOT NULL THEN 1 ELSE 0 END AS INTEGER) as \"is_reliable!: i64\",\n stats.avg_latency_ms as \"avg_latency_ms?: f64\",\n stats.min_latency_ms as \"min_latency_ms?: f64\",\n stats.max_latency_ms as \"max_latency_ms?: f64\",\n stats.last_latency_ms as \"last_latency_ms?: f64\"\n FROM monero_nodes n\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n MAX(CASE WHEN was_successful THEN timestamp END) as last_success,\n MAX(CASE WHEN NOT was_successful THEN timestamp END) as last_failure,\n MAX(timestamp) as last_checked,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms,\n MIN(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as min_latency_ms,\n MAX(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as max_latency_ms,\n (SELECT latency_ms FROM health_checks hc2 WHERE hc2.node_id = health_checks.node_id ORDER BY timestamp DESC LIMIT 1) as last_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) stats ON n.id = stats.node_id\n LEFT JOIN (\n SELECT DISTINCT node_id FROM (\n SELECT \n n2.id as node_id,\n COALESCE(s2.success_count, 0) as success_count,\n COALESCE(s2.failure_count, 0) as failure_count,\n s2.avg_latency_ms,\n (CAST(COALESCE(s2.success_count, 0) AS REAL) / CAST(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0) AS REAL)) * \n (MIN(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0), 200) / 200.0) * 0.8 +\n CASE \n WHEN s2.avg_latency_ms IS NOT NULL THEN (1.0 - (MIN(s2.avg_latency_ms, 2000) / 2000.0)) * 0.2\n ELSE 0.0 \n END as reliability_score\n FROM monero_nodes n2\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) s2 ON n2.id = s2.node_id\n WHERE n2.network = ? AND (COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0)) > 0\n ORDER BY reliability_score DESC\n LIMIT 4\n )\n ) reliable_nodes ON n.id = reliable_nodes.node_id\n WHERE n.network = ?\n ORDER BY RANDOM()\n LIMIT ?\n ",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "scheme",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "host",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "port",
"ordinal": 3,
"type_info": "Integer"
},
{
"name": "full_url",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "network",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "first_seen_at",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "success_count!: i64",
"ordinal": 7,
"type_info": "Null"
},
{
"name": "failure_count!: i64",
"ordinal": 8,
"type_info": "Null"
},
{
"name": "last_success?: String",
"ordinal": 9,
"type_info": "Null"
},
{
"name": "last_failure?: String",
"ordinal": 10,
"type_info": "Null"
},
{
"name": "last_checked?: String",
"ordinal": 11,
"type_info": "Null"
},
{
"name": "is_reliable!: i64",
"ordinal": 12,
"type_info": "Null"
},
{
"name": "avg_latency_ms?: f64",
"ordinal": 13,
"type_info": "Null"
},
{
"name": "min_latency_ms?: f64",
"ordinal": 14,
"type_info": "Null"
},
{
"name": "max_latency_ms?: f64",
"ordinal": 15,
"type_info": "Null"
},
{
"name": "last_latency_ms?: f64",
"ordinal": 16,
"type_info": "Float"
}
],
"parameters": {
"Right": 3
},
"nullable": [
true,
false,
false,
false,
false,
false,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true
]
},
"hash": "5a25c95c04b11a60a04ad97b5fb684e9a0cc2eb5daf64f33e924f0c38a2edfec"
}

View file

@ -1,116 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT \n n.id as \"id!: i64\",\n n.scheme,\n n.host,\n n.port,\n n.full_url,\n n.network,\n n.first_seen_at,\n CAST(COALESCE(stats.success_count, 0) AS INTEGER) as \"success_count!: i64\",\n CAST(COALESCE(stats.failure_count, 0) AS INTEGER) as \"failure_count!: i64\",\n stats.last_success as \"last_success?: String\",\n stats.last_failure as \"last_failure?: String\",\n stats.last_checked as \"last_checked?: String\",\n CAST(CASE WHEN reliable_nodes.node_id IS NOT NULL THEN 1 ELSE 0 END AS INTEGER) as \"is_reliable!: i64\",\n stats.avg_latency_ms as \"avg_latency_ms?: f64\",\n stats.min_latency_ms as \"min_latency_ms?: f64\",\n stats.max_latency_ms as \"max_latency_ms?: f64\",\n stats.last_latency_ms as \"last_latency_ms?: f64\"\n FROM monero_nodes n\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n MAX(CASE WHEN was_successful THEN timestamp END) as last_success,\n MAX(CASE WHEN NOT was_successful THEN timestamp END) as last_failure,\n MAX(timestamp) as last_checked,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms,\n MIN(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as min_latency_ms,\n MAX(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as max_latency_ms,\n (SELECT latency_ms FROM health_checks hc2 WHERE hc2.node_id = health_checks.node_id ORDER BY timestamp DESC LIMIT 1) as last_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) stats ON n.id = stats.node_id\n LEFT JOIN (\n SELECT DISTINCT node_id FROM (\n SELECT \n n2.id as node_id,\n COALESCE(s2.success_count, 0) as success_count,\n COALESCE(s2.failure_count, 0) as failure_count,\n s2.avg_latency_ms,\n (CAST(COALESCE(s2.success_count, 0) AS REAL) / CAST(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0) AS REAL)) * \n (MIN(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0), 200) / 200.0) * 0.8 +\n CASE \n WHEN s2.avg_latency_ms IS NOT NULL THEN (1.0 - (MIN(s2.avg_latency_ms, 2000) / 2000.0)) * 0.2\n ELSE 0.0 \n END as reliability_score\n FROM monero_nodes n2\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) s2 ON n2.id = s2.node_id\n WHERE n2.network = ? AND (COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0)) > 0\n ORDER BY reliability_score DESC\n LIMIT 4\n )\n ) reliable_nodes ON n.id = reliable_nodes.node_id\n WHERE n.network = ?\n ORDER BY stats.avg_latency_ms ASC, stats.success_count DESC\n ",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "scheme",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "host",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "port",
"ordinal": 3,
"type_info": "Integer"
},
{
"name": "full_url",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "network",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "first_seen_at",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "success_count!: i64",
"ordinal": 7,
"type_info": "Null"
},
{
"name": "failure_count!: i64",
"ordinal": 8,
"type_info": "Null"
},
{
"name": "last_success?: String",
"ordinal": 9,
"type_info": "Null"
},
{
"name": "last_failure?: String",
"ordinal": 10,
"type_info": "Null"
},
{
"name": "last_checked?: String",
"ordinal": 11,
"type_info": "Null"
},
{
"name": "is_reliable!: i64",
"ordinal": 12,
"type_info": "Null"
},
{
"name": "avg_latency_ms?: f64",
"ordinal": 13,
"type_info": "Null"
},
{
"name": "min_latency_ms?: f64",
"ordinal": 14,
"type_info": "Null"
},
{
"name": "max_latency_ms?: f64",
"ordinal": 15,
"type_info": "Null"
},
{
"name": "last_latency_ms?: f64",
"ordinal": 16,
"type_info": "Float"
}
],
"parameters": {
"Right": 2
},
"nullable": [
true,
false,
false,
false,
false,
false,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true
]
},
"hash": "5ff27bdd9b6e7aadc8dd4936e0ee7e6a611aaef28697a0e9535dfb30d1c4861d"
}

View file

@ -0,0 +1,110 @@
{
"db_name": "SQLite",
"query": "\n SELECT \n n.id as \"id!: i64\",\n n.scheme,\n n.host,\n n.port,\n n.network,\n n.first_seen_at,\n CAST(COALESCE(stats.success_count, 0) AS INTEGER) as \"success_count!: i64\",\n CAST(COALESCE(stats.failure_count, 0) AS INTEGER) as \"failure_count!: i64\",\n stats.last_success as \"last_success?: String\",\n stats.last_failure as \"last_failure?: String\",\n stats.last_checked as \"last_checked?: String\",\n CAST(CASE WHEN reliable_nodes.node_id IS NOT NULL THEN 1 ELSE 0 END AS INTEGER) as \"is_reliable!: i64\",\n stats.avg_latency_ms as \"avg_latency_ms?: f64\",\n stats.min_latency_ms as \"min_latency_ms?: f64\",\n stats.max_latency_ms as \"max_latency_ms?: f64\",\n stats.last_latency_ms as \"last_latency_ms?: f64\"\n FROM monero_nodes n\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n MAX(CASE WHEN was_successful THEN timestamp END) as last_success,\n MAX(CASE WHEN NOT was_successful THEN timestamp END) as last_failure,\n MAX(timestamp) as last_checked,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms,\n MIN(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as min_latency_ms,\n MAX(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as max_latency_ms,\n (SELECT latency_ms FROM health_checks hc2 WHERE hc2.node_id = health_checks.node_id ORDER BY timestamp DESC LIMIT 1) as last_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) stats ON n.id = stats.node_id\n LEFT JOIN (\n SELECT DISTINCT node_id FROM (\n SELECT \n n2.id as node_id,\n COALESCE(s2.success_count, 0) as success_count,\n COALESCE(s2.failure_count, 0) as failure_count,\n s2.avg_latency_ms,\n (CAST(COALESCE(s2.success_count, 0) AS REAL) / CAST(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0) AS REAL)) * \n (MIN(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0), 200) / 200.0) * 0.8 +\n CASE \n WHEN s2.avg_latency_ms IS NOT NULL THEN (1.0 - (MIN(s2.avg_latency_ms, 2000) / 2000.0)) * 0.2\n ELSE 0.0 \n END as reliability_score\n FROM monero_nodes n2\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) s2 ON n2.id = s2.node_id\n WHERE n2.network = ? AND (COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0)) > 0\n ORDER BY reliability_score DESC\n LIMIT 4\n )\n ) reliable_nodes ON n.id = reliable_nodes.node_id\n WHERE n.network = ? AND stats.success_count > 0\n ORDER BY stats.avg_latency_ms ASC, stats.success_count DESC\n ",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "scheme",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "host",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "port",
"ordinal": 3,
"type_info": "Integer"
},
{
"name": "network",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "first_seen_at",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "success_count!: i64",
"ordinal": 6,
"type_info": "Null"
},
{
"name": "failure_count!: i64",
"ordinal": 7,
"type_info": "Null"
},
{
"name": "last_success?: String",
"ordinal": 8,
"type_info": "Null"
},
{
"name": "last_failure?: String",
"ordinal": 9,
"type_info": "Null"
},
{
"name": "last_checked?: String",
"ordinal": 10,
"type_info": "Null"
},
{
"name": "is_reliable!: i64",
"ordinal": 11,
"type_info": "Null"
},
{
"name": "avg_latency_ms?: f64",
"ordinal": 12,
"type_info": "Null"
},
{
"name": "min_latency_ms?: f64",
"ordinal": 13,
"type_info": "Null"
},
{
"name": "max_latency_ms?: f64",
"ordinal": 14,
"type_info": "Null"
},
{
"name": "last_latency_ms?: f64",
"ordinal": 15,
"type_info": "Float"
}
],
"parameters": {
"Right": 2
},
"nullable": [
true,
false,
false,
false,
false,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true
]
},
"hash": "75ad770e6f70443871f919c26c189aaefc306e2a72b456fc2d03d4aa870e150b"
}

View file

@ -0,0 +1,110 @@
{
"db_name": "SQLite",
"query": "\n SELECT \n n.id as \"id!: i64\",\n n.scheme,\n n.host,\n n.port,\n n.network,\n n.first_seen_at,\n CAST(COALESCE(stats.success_count, 0) AS INTEGER) as \"success_count!: i64\",\n CAST(COALESCE(stats.failure_count, 0) AS INTEGER) as \"failure_count!: i64\",\n stats.last_success as \"last_success?: String\",\n stats.last_failure as \"last_failure?: String\",\n stats.last_checked as \"last_checked?: String\",\n CAST(CASE WHEN reliable_nodes.node_id IS NOT NULL THEN 1 ELSE 0 END AS INTEGER) as \"is_reliable!: i64\",\n stats.avg_latency_ms as \"avg_latency_ms?: f64\",\n stats.min_latency_ms as \"min_latency_ms?: f64\",\n stats.max_latency_ms as \"max_latency_ms?: f64\",\n stats.last_latency_ms as \"last_latency_ms?: f64\"\n FROM monero_nodes n\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n MAX(CASE WHEN was_successful THEN timestamp END) as last_success,\n MAX(CASE WHEN NOT was_successful THEN timestamp END) as last_failure,\n MAX(timestamp) as last_checked,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms,\n MIN(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as min_latency_ms,\n MAX(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as max_latency_ms,\n (SELECT latency_ms FROM health_checks hc2 WHERE hc2.node_id = health_checks.node_id ORDER BY timestamp DESC LIMIT 1) as last_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) stats ON n.id = stats.node_id\n LEFT JOIN (\n SELECT DISTINCT node_id FROM (\n SELECT \n n2.id as node_id,\n COALESCE(s2.success_count, 0) as success_count,\n COALESCE(s2.failure_count, 0) as failure_count,\n s2.avg_latency_ms,\n (CAST(COALESCE(s2.success_count, 0) AS REAL) / CAST(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0) AS REAL)) * \n (MIN(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0), 200) / 200.0) * 0.8 +\n CASE \n WHEN s2.avg_latency_ms IS NOT NULL THEN (1.0 - (MIN(s2.avg_latency_ms, 2000) / 2000.0)) * 0.2\n ELSE 0.0 \n END as reliability_score\n FROM monero_nodes n2\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) s2 ON n2.id = s2.node_id\n WHERE n2.network = ? AND (COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0)) > 0\n ORDER BY reliability_score DESC\n LIMIT 4\n )\n ) reliable_nodes ON n.id = reliable_nodes.node_id\n WHERE n.network = ?\n ORDER BY RANDOM()\n LIMIT ?\n ",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "scheme",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "host",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "port",
"ordinal": 3,
"type_info": "Integer"
},
{
"name": "network",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "first_seen_at",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "success_count!: i64",
"ordinal": 6,
"type_info": "Null"
},
{
"name": "failure_count!: i64",
"ordinal": 7,
"type_info": "Null"
},
{
"name": "last_success?: String",
"ordinal": 8,
"type_info": "Null"
},
{
"name": "last_failure?: String",
"ordinal": 9,
"type_info": "Null"
},
{
"name": "last_checked?: String",
"ordinal": 10,
"type_info": "Null"
},
{
"name": "is_reliable!: i64",
"ordinal": 11,
"type_info": "Null"
},
{
"name": "avg_latency_ms?: f64",
"ordinal": 12,
"type_info": "Null"
},
{
"name": "min_latency_ms?: f64",
"ordinal": 13,
"type_info": "Null"
},
{
"name": "max_latency_ms?: f64",
"ordinal": 14,
"type_info": "Null"
},
{
"name": "last_latency_ms?: f64",
"ordinal": 15,
"type_info": "Float"
}
],
"parameters": {
"Right": 3
},
"nullable": [
true,
false,
false,
false,
false,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true
]
},
"hash": "9f6d042ab61e1d3d652d85c7d77d86a847c4a25d4ee0eab57380d10b94d2686d"
}

View file

@ -1,116 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT \n n.id as \"id!: i64\",\n n.scheme,\n n.host,\n n.port,\n n.full_url,\n n.network,\n n.first_seen_at,\n CAST(COALESCE(stats.success_count, 0) AS INTEGER) as \"success_count!: i64\",\n CAST(COALESCE(stats.failure_count, 0) AS INTEGER) as \"failure_count!: i64\",\n stats.last_success as \"last_success?: String\",\n stats.last_failure as \"last_failure?: String\",\n stats.last_checked as \"last_checked?: String\",\n CAST(1 AS INTEGER) as \"is_reliable!: i64\",\n stats.avg_latency_ms as \"avg_latency_ms?: f64\",\n stats.min_latency_ms as \"min_latency_ms?: f64\",\n stats.max_latency_ms as \"max_latency_ms?: f64\",\n stats.last_latency_ms as \"last_latency_ms?: f64\"\n FROM monero_nodes n\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n MAX(CASE WHEN was_successful THEN timestamp END) as last_success,\n MAX(CASE WHEN NOT was_successful THEN timestamp END) as last_failure,\n MAX(timestamp) as last_checked,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms,\n MIN(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as min_latency_ms,\n MAX(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as max_latency_ms,\n (SELECT latency_ms FROM health_checks hc2 WHERE hc2.node_id = health_checks.node_id ORDER BY timestamp DESC LIMIT 1) as last_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) stats ON n.id = stats.node_id\n WHERE n.network = ? AND (COALESCE(stats.success_count, 0) + COALESCE(stats.failure_count, 0)) > 0\n ORDER BY \n (CAST(COALESCE(stats.success_count, 0) AS REAL) / CAST(COALESCE(stats.success_count, 0) + COALESCE(stats.failure_count, 0) AS REAL)) * \n (MIN(COALESCE(stats.success_count, 0) + COALESCE(stats.failure_count, 0), 200) / 200.0) * 0.8 +\n CASE \n WHEN stats.avg_latency_ms IS NOT NULL THEN (1.0 - (MIN(stats.avg_latency_ms, 2000) / 2000.0)) * 0.2\n ELSE 0.0 \n END DESC\n LIMIT 4\n ",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "scheme",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "host",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "port",
"ordinal": 3,
"type_info": "Integer"
},
{
"name": "full_url",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "network",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "first_seen_at",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "success_count!: i64",
"ordinal": 7,
"type_info": "Null"
},
{
"name": "failure_count!: i64",
"ordinal": 8,
"type_info": "Null"
},
{
"name": "last_success?: String",
"ordinal": 9,
"type_info": "Null"
},
{
"name": "last_failure?: String",
"ordinal": 10,
"type_info": "Null"
},
{
"name": "last_checked?: String",
"ordinal": 11,
"type_info": "Null"
},
{
"name": "is_reliable!: i64",
"ordinal": 12,
"type_info": "Null"
},
{
"name": "avg_latency_ms?: f64",
"ordinal": 13,
"type_info": "Null"
},
{
"name": "min_latency_ms?: f64",
"ordinal": 14,
"type_info": "Null"
},
{
"name": "max_latency_ms?: f64",
"ordinal": 15,
"type_info": "Null"
},
{
"name": "last_latency_ms?: f64",
"ordinal": 16,
"type_info": "Float"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
false,
false,
false,
false,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true
]
},
"hash": "a032eb9773d4553aeaff4fb15ed99dbaef7d16d48750ee7bd4ab83233a9a732b"
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n UPDATE monero_nodes \n SET network = ?, updated_at = ?\n WHERE scheme = ? AND host = ? AND port = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 5
},
"nullable": []
},
"hash": "b6d85d42bf72888afa22e27710e8cfe3885ed226ae6ae02d6585c1f2f4140d68"
}

View file

@ -0,0 +1,110 @@
{
"db_name": "SQLite",
"query": "\n SELECT \n n.id as \"id!: i64\",\n n.scheme,\n n.host,\n n.port,\n n.network,\n n.first_seen_at,\n CAST(COALESCE(stats.success_count, 0) AS INTEGER) as \"success_count!: i64\",\n CAST(COALESCE(stats.failure_count, 0) AS INTEGER) as \"failure_count!: i64\",\n stats.last_success as \"last_success?: String\",\n stats.last_failure as \"last_failure?: String\",\n stats.last_checked as \"last_checked?: String\",\n CAST(1 AS INTEGER) as \"is_reliable!: i64\",\n stats.avg_latency_ms as \"avg_latency_ms?: f64\",\n stats.min_latency_ms as \"min_latency_ms?: f64\",\n stats.max_latency_ms as \"max_latency_ms?: f64\",\n stats.last_latency_ms as \"last_latency_ms?: f64\"\n FROM monero_nodes n\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n MAX(CASE WHEN was_successful THEN timestamp END) as last_success,\n MAX(CASE WHEN NOT was_successful THEN timestamp END) as last_failure,\n MAX(timestamp) as last_checked,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms,\n MIN(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as min_latency_ms,\n MAX(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as max_latency_ms,\n (SELECT latency_ms FROM health_checks hc2 WHERE hc2.node_id = health_checks.node_id ORDER BY timestamp DESC LIMIT 1) as last_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) stats ON n.id = stats.node_id\n WHERE n.network = ? AND (COALESCE(stats.success_count, 0) + COALESCE(stats.failure_count, 0)) > 0\n ORDER BY \n (CAST(COALESCE(stats.success_count, 0) AS REAL) / CAST(COALESCE(stats.success_count, 0) + COALESCE(stats.failure_count, 0) AS REAL)) * \n (MIN(COALESCE(stats.success_count, 0) + COALESCE(stats.failure_count, 0), 200) / 200.0) * 0.8 +\n CASE \n WHEN stats.avg_latency_ms IS NOT NULL THEN (1.0 - (MIN(stats.avg_latency_ms, 2000) / 2000.0)) * 0.2\n ELSE 0.0 \n END DESC\n LIMIT 4\n ",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "scheme",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "host",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "port",
"ordinal": 3,
"type_info": "Integer"
},
{
"name": "network",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "first_seen_at",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "success_count!: i64",
"ordinal": 6,
"type_info": "Null"
},
{
"name": "failure_count!: i64",
"ordinal": 7,
"type_info": "Null"
},
{
"name": "last_success?: String",
"ordinal": 8,
"type_info": "Null"
},
{
"name": "last_failure?: String",
"ordinal": 9,
"type_info": "Null"
},
{
"name": "last_checked?: String",
"ordinal": 10,
"type_info": "Null"
},
{
"name": "is_reliable!: i64",
"ordinal": 11,
"type_info": "Null"
},
{
"name": "avg_latency_ms?: f64",
"ordinal": 12,
"type_info": "Null"
},
{
"name": "min_latency_ms?: f64",
"ordinal": 13,
"type_info": "Null"
},
{
"name": "max_latency_ms?: f64",
"ordinal": 14,
"type_info": "Null"
},
{
"name": "last_latency_ms?: f64",
"ordinal": 15,
"type_info": "Float"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
false,
false,
false,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true
]
},
"hash": "b96b866cfc801e275e39e788993d26f6dba1a17a0dee265333c4ae5817ff1e55"
}

View file

@ -1,116 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT \n n.id as \"id!: i64\",\n n.scheme,\n n.host,\n n.port,\n n.full_url,\n n.network,\n n.first_seen_at,\n CAST(COALESCE(stats.success_count, 0) AS INTEGER) as \"success_count!: i64\",\n CAST(COALESCE(stats.failure_count, 0) AS INTEGER) as \"failure_count!: i64\",\n stats.last_success as \"last_success?: String\",\n stats.last_failure as \"last_failure?: String\",\n stats.last_checked as \"last_checked?: String\",\n CAST(CASE WHEN reliable_nodes.node_id IS NOT NULL THEN 1 ELSE 0 END AS INTEGER) as \"is_reliable!: i64\",\n stats.avg_latency_ms as \"avg_latency_ms?: f64\",\n stats.min_latency_ms as \"min_latency_ms?: f64\",\n stats.max_latency_ms as \"max_latency_ms?: f64\",\n stats.last_latency_ms as \"last_latency_ms?: f64\"\n FROM monero_nodes n\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n MAX(CASE WHEN was_successful THEN timestamp END) as last_success,\n MAX(CASE WHEN NOT was_successful THEN timestamp END) as last_failure,\n MAX(timestamp) as last_checked,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms,\n MIN(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as min_latency_ms,\n MAX(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as max_latency_ms,\n (SELECT latency_ms FROM health_checks hc2 WHERE hc2.node_id = health_checks.node_id ORDER BY timestamp DESC LIMIT 1) as last_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) stats ON n.id = stats.node_id\n LEFT JOIN (\n SELECT DISTINCT node_id FROM (\n SELECT \n n2.id as node_id,\n COALESCE(s2.success_count, 0) as success_count,\n COALESCE(s2.failure_count, 0) as failure_count,\n s2.avg_latency_ms,\n (CAST(COALESCE(s2.success_count, 0) AS REAL) / CAST(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0) AS REAL)) * \n (MIN(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0), 200) / 200.0) * 0.8 +\n CASE \n WHEN s2.avg_latency_ms IS NOT NULL THEN (1.0 - (MIN(s2.avg_latency_ms, 2000) / 2000.0)) * 0.2\n ELSE 0.0 \n END as reliability_score\n FROM monero_nodes n2\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) s2 ON n2.id = s2.node_id\n WHERE n2.network = ? AND (COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0)) > 0\n ORDER BY reliability_score DESC\n LIMIT 4\n )\n ) reliable_nodes ON n.id = reliable_nodes.node_id\n WHERE n.network = ?\n ORDER BY RANDOM()\n LIMIT ?\n ",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "scheme",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "host",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "port",
"ordinal": 3,
"type_info": "Integer"
},
{
"name": "full_url",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "network",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "first_seen_at",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "success_count!: i64",
"ordinal": 7,
"type_info": "Null"
},
{
"name": "failure_count!: i64",
"ordinal": 8,
"type_info": "Null"
},
{
"name": "last_success?: String",
"ordinal": 9,
"type_info": "Null"
},
{
"name": "last_failure?: String",
"ordinal": 10,
"type_info": "Null"
},
{
"name": "last_checked?: String",
"ordinal": 11,
"type_info": "Null"
},
{
"name": "is_reliable!: i64",
"ordinal": 12,
"type_info": "Null"
},
{
"name": "avg_latency_ms?: f64",
"ordinal": 13,
"type_info": "Null"
},
{
"name": "min_latency_ms?: f64",
"ordinal": 14,
"type_info": "Null"
},
{
"name": "max_latency_ms?: f64",
"ordinal": 15,
"type_info": "Null"
},
{
"name": "last_latency_ms?: f64",
"ordinal": 16,
"type_info": "Float"
}
],
"parameters": {
"Right": 3
},
"nullable": [
true,
false,
false,
false,
false,
false,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true
]
},
"hash": "ba231efaf208a42fa857f716ef296b428c937f2eb7c8ce9c631f7f721e914c14"
}

View file

@ -17,7 +17,10 @@
"parameters": {
"Right": 1
},
"nullable": [true, true]
"nullable": [
true,
true
]
},
"hash": "d32d91ca2debc4212841282533482b2ff081234c7f9f848a7223ae04234995d9"
}

View file

@ -1,18 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT id FROM monero_nodes WHERE full_url = ?",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer"
}
],
"parameters": {
"Right": 1
},
"nullable": [true]
},
"hash": "e0865335c2dcb040a34e3f1305fe1a823d6fcde4a061def602cba30971817781"
}

View file

@ -1,116 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT \n n.id as \"id!: i64\",\n n.scheme,\n n.host,\n n.port,\n n.full_url,\n n.network,\n n.first_seen_at,\n CAST(COALESCE(stats.success_count, 0) AS INTEGER) as \"success_count!: i64\",\n CAST(COALESCE(stats.failure_count, 0) AS INTEGER) as \"failure_count!: i64\",\n stats.last_success as \"last_success?: String\",\n stats.last_failure as \"last_failure?: String\",\n stats.last_checked as \"last_checked?: String\",\n CAST(CASE WHEN reliable_nodes.node_id IS NOT NULL THEN 1 ELSE 0 END AS INTEGER) as \"is_reliable!: i64\",\n stats.avg_latency_ms as \"avg_latency_ms?: f64\",\n stats.min_latency_ms as \"min_latency_ms?: f64\",\n stats.max_latency_ms as \"max_latency_ms?: f64\",\n stats.last_latency_ms as \"last_latency_ms?: f64\"\n FROM monero_nodes n\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n MAX(CASE WHEN was_successful THEN timestamp END) as last_success,\n MAX(CASE WHEN NOT was_successful THEN timestamp END) as last_failure,\n MAX(timestamp) as last_checked,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms,\n MIN(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as min_latency_ms,\n MAX(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as max_latency_ms,\n (SELECT latency_ms FROM health_checks hc2 WHERE hc2.node_id = health_checks.node_id ORDER BY timestamp DESC LIMIT 1) as last_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) stats ON n.id = stats.node_id\n LEFT JOIN (\n SELECT DISTINCT node_id FROM (\n SELECT \n n2.id as node_id,\n COALESCE(s2.success_count, 0) as success_count,\n COALESCE(s2.failure_count, 0) as failure_count,\n s2.avg_latency_ms,\n (CAST(COALESCE(s2.success_count, 0) AS REAL) / CAST(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0) AS REAL)) * \n (MIN(COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0), 200) / 200.0) * 0.8 +\n CASE \n WHEN s2.avg_latency_ms IS NOT NULL THEN (1.0 - (MIN(s2.avg_latency_ms, 2000) / 2000.0)) * 0.2\n ELSE 0.0 \n END as reliability_score\n FROM monero_nodes n2\n LEFT JOIN (\n SELECT \n node_id,\n SUM(CASE WHEN was_successful THEN 1 ELSE 0 END) as success_count,\n SUM(CASE WHEN NOT was_successful THEN 1 ELSE 0 END) as failure_count,\n AVG(CASE WHEN was_successful AND latency_ms IS NOT NULL THEN latency_ms END) as avg_latency_ms\n FROM health_checks \n GROUP BY node_id\n ) s2 ON n2.id = s2.node_id\n WHERE n2.network = ? AND (COALESCE(s2.success_count, 0) + COALESCE(s2.failure_count, 0)) > 0\n ORDER BY reliability_score DESC\n LIMIT 4\n )\n ) reliable_nodes ON n.id = reliable_nodes.node_id\n WHERE n.network = ? AND stats.success_count > 0\n ORDER BY stats.avg_latency_ms ASC, stats.success_count DESC\n ",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "scheme",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "host",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "port",
"ordinal": 3,
"type_info": "Integer"
},
{
"name": "full_url",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "network",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "first_seen_at",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "success_count!: i64",
"ordinal": 7,
"type_info": "Null"
},
{
"name": "failure_count!: i64",
"ordinal": 8,
"type_info": "Null"
},
{
"name": "last_success?: String",
"ordinal": 9,
"type_info": "Null"
},
{
"name": "last_failure?: String",
"ordinal": 10,
"type_info": "Null"
},
{
"name": "last_checked?: String",
"ordinal": 11,
"type_info": "Null"
},
{
"name": "is_reliable!: i64",
"ordinal": 12,
"type_info": "Null"
},
{
"name": "avg_latency_ms?: f64",
"ordinal": 13,
"type_info": "Null"
},
{
"name": "min_latency_ms?: f64",
"ordinal": 14,
"type_info": "Null"
},
{
"name": "max_latency_ms?: f64",
"ordinal": 15,
"type_info": "Null"
},
{
"name": "last_latency_ms?: f64",
"ordinal": 16,
"type_info": "Float"
}
],
"parameters": {
"Right": 2
},
"nullable": [
true,
false,
false,
false,
false,
false,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true
]
},
"hash": "fac12e3ca6ac1db1a4812a5390a333ec95a2e5e2cd554c169ceecc61b7ff2864"
}

View file

@ -22,7 +22,11 @@
"parameters": {
"Right": 2
},
"nullable": [false, true, false]
"nullable": [
false,
true,
false
]
},
"hash": "ffa1b76d20c86d6bea02bd03e5e7de159adbb7c7c0ef585ce4df9ec648bea7f8"
}

View file

@ -149,14 +149,13 @@ impl Database {
port: i64,
network: &str,
) -> Result<i64> {
let full_url = format!("{}://{}:{}", scheme, host, port);
let now = chrono::Utc::now().to_rfc3339();
let result = sqlx::query!(
r#"
INSERT INTO monero_nodes (scheme, host, port, full_url, network, first_seen_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(full_url) DO UPDATE SET
INSERT INTO monero_nodes (scheme, host, port, network, first_seen_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT(scheme, host, port) DO UPDATE SET
network = excluded.network,
updated_at = excluded.updated_at
RETURNING id
@ -164,7 +163,6 @@ impl Database {
scheme,
host,
port,
full_url,
network,
now,
now
@ -176,26 +174,28 @@ impl Database {
}
/// Update a node's network after it has been identified
pub async fn update_node_network(&self, url: &str, network: &str) -> Result<()> {
pub async fn update_node_network(&self, scheme: &str, host: &str, port: i64, network: &str) -> Result<()> {
let now = chrono::Utc::now().to_rfc3339();
let result = sqlx::query!(
r#"
UPDATE monero_nodes
SET network = ?, updated_at = ?
WHERE full_url = ?
WHERE scheme = ? AND host = ? AND port = ?
"#,
network,
now,
url
scheme,
host,
port
)
.execute(&self.pool)
.await?;
if result.rows_affected() > 0 {
debug!("Updated network for node {} to {}", url, network);
debug!("Updated network for node {}://{}:{} to {}", scheme, host, port, network);
} else {
warn!("Failed to update network for node {}: not found", url);
warn!("Failed to update network for node {}://{}:{}: not found", scheme, host, port);
}
Ok(())
@ -204,21 +204,23 @@ impl Database {
/// Record a health check event
pub async fn record_health_check(
&self,
url: &str,
scheme: &str,
host: &str,
port: i64,
was_successful: bool,
latency_ms: Option<f64>,
) -> Result<()> {
let now = chrono::Utc::now().to_rfc3339();
// First get the node_id
let node_row = sqlx::query!("SELECT id FROM monero_nodes WHERE full_url = ?", url)
let node_row = sqlx::query!("SELECT id FROM monero_nodes WHERE scheme = ? AND host = ? AND port = ?", scheme, host, port)
.fetch_optional(&self.pool)
.await?;
let node_id = match node_row {
Some(row) => row.id,
None => {
warn!("Cannot record health check for unknown node: {}", url);
warn!("Cannot record health check for unknown node: {}://{}:{}", scheme, host, port);
return Ok(());
}
};
@ -248,7 +250,6 @@ impl Database {
n.scheme,
n.host,
n.port,
n.full_url,
n.network,
n.first_seen_at,
CAST(COALESCE(stats.success_count, 0) AS INTEGER) as "success_count!: i64",
@ -353,7 +354,6 @@ impl Database {
n.scheme,
n.host,
n.port,
n.full_url,
n.network,
n.first_seen_at,
CAST(COALESCE(stats.success_count, 0) AS INTEGER) as "success_count!: i64",
@ -516,7 +516,6 @@ impl Database {
n.scheme,
n.host,
n.port,
n.full_url,
n.network,
n.first_seen_at,
CAST(COALESCE(stats.success_count, 0) AS INTEGER) as "success_count!: i64",
@ -623,7 +622,6 @@ impl Database {
n.scheme,
n.host,
n.port,
n.full_url,
n.network,
n.first_seen_at,
CAST(COALESCE(stats.success_count, 0) AS INTEGER) as "success_count!: i64",
@ -734,7 +732,6 @@ impl Database {
n.scheme,
n.host,
n.port,
n.full_url,
n.network,
n.first_seen_at,
CAST(COALESCE(stats.success_count, 0) AS INTEGER) as "success_count!: i64",
@ -835,7 +832,6 @@ impl Database {
n.scheme,
n.host,
n.port,
n.full_url,
n.network,
n.first_seen_at,
CAST(COALESCE(stats.success_count, 0) AS INTEGER) as "success_count!: i64",

View file

@ -116,7 +116,7 @@ impl NodeDiscovery {
}
/// Enhanced health check that detects network and validates node identity
pub async fn check_node_health(&self, url: &str) -> Result<HealthCheckOutcome> {
pub async fn check_node_health(&self, scheme: &str, host: &str, port: i64) -> Result<HealthCheckOutcome> {
let start_time = Instant::now();
let rpc_request = serde_json::json!({
@ -125,8 +125,8 @@ impl NodeDiscovery {
"method": "get_info"
});
let full_url = format!("{}/json_rpc", url);
let response = self.client.post(&full_url).json(&rpc_request).send().await;
let node_url = format!("{}://{}:{}/json_rpc", scheme, host, port);
let response = self.client.post(&node_url).json(&rpc_request).send().await;
let latency = start_time.elapsed();
@ -214,7 +214,6 @@ impl NodeDiscovery {
scheme,
host,
port,
full_url,
network as "network!: String",
first_seen_at
FROM monero_nodes
@ -229,12 +228,14 @@ impl NodeDiscovery {
let mut corrected_count = 0;
for node in all_nodes {
match self.check_node_health(&node.full_url).await {
match self.check_node_health(&node.scheme, &node.host, node.port).await {
Ok(outcome) => {
// Always record the health check
self.db
.record_health_check(
&node.full_url,
&node.scheme,
&node.host,
node.port,
outcome.was_successful,
if outcome.was_successful {
Some(outcome.latency.as_millis() as f64)
@ -251,10 +252,11 @@ impl NodeDiscovery {
if let Some(discovered_network) = outcome.discovered_network {
let discovered_network_str = network_to_string(&discovered_network);
if node.network != discovered_network_str {
let node_url = format!("{}://{}:{}", node.scheme, node.host, node.port);
warn!("Network mismatch detected for node {}: stored={}, discovered={}. Correcting...",
node.full_url, node.network, discovered_network_str);
node_url, node.network, discovered_network_str);
self.db
.update_node_network(&node.full_url, &discovered_network_str)
.update_node_network(&node.scheme, &node.host, node.port, &discovered_network_str)
.await?;
corrected_count += 1;
}
@ -264,7 +266,7 @@ impl NodeDiscovery {
}
Err(_e) => {
self.db
.record_health_check(&node.full_url, false, None)
.record_health_check(&node.scheme, &node.host, node.port, false, None)
.await?;
}
}

View file

@ -100,15 +100,15 @@ impl NodePool {
score
}
pub async fn record_success(&self, url: &str, latency_ms: f64) -> Result<()> {
pub async fn record_success(&self, scheme: &str, host: &str, port: i64, latency_ms: f64) -> Result<()> {
self.db
.record_health_check(url, true, Some(latency_ms))
.record_health_check(scheme, host, port, true, Some(latency_ms))
.await?;
Ok(())
}
pub async fn record_failure(&self, url: &str) -> Result<()> {
self.db.record_health_check(url, false, None).await?;
pub async fn record_failure(&self, scheme: &str, host: &str, port: i64) -> Result<()> {
self.db.record_health_check(scheme, host, port, false, None).await?;
Ok(())
}

View file

@ -60,7 +60,7 @@ fn extract_jsonrpc_method(body: &[u8]) -> Option<String> {
}
async fn raw_http_request(
node_url: &str,
node_url: (String, String, i64),
path: &str,
method: &str,
headers: &HeaderMap,
@ -71,7 +71,8 @@ async fn raw_http_request(
.build()
.map_err(|e| HandlerError::RequestError(format!("{:#?}", e)))?;
let url = format!("{}{}", node_url, path);
let (scheme, host, port) = &node_url;
let url = format!("{}://{}:{}{}", scheme, host, port, path);
// Use generic request method to support any HTTP verb
let http_method = method
@ -148,31 +149,33 @@ async fn raw_http_request(
Ok(axum_response)
}
async fn record_success(state: &AppState, node_url: &str, latency_ms: f64) {
async fn record_success(state: &AppState, scheme: &str, host: &str, port: i64, latency_ms: f64) {
let node_pool_guard = state.node_pool.read().await;
if let Err(e) = node_pool_guard.record_success(node_url, latency_ms).await {
error!("Failed to record success for {}: {}", node_url, e);
if let Err(e) = node_pool_guard.record_success(scheme, host, port, latency_ms).await {
error!("Failed to record success for {}://{}:{}: {}", scheme, host, port, e);
}
}
async fn record_failure(state: &AppState, node_url: &str) {
async fn record_failure(state: &AppState, scheme: &str, host: &str, port: i64) {
let node_pool_guard = state.node_pool.read().await;
if let Err(e) = node_pool_guard.record_failure(node_url).await {
error!("Failed to record failure for {}: {}", node_url, e);
if let Err(e) = node_pool_guard.record_failure(scheme, host, port).await {
error!("Failed to record failure for {}://{}:{}: {}", scheme, host, port, e);
}
}
async fn single_raw_request(
state: &AppState,
node_url: String,
node_url: (String, String, i64),
path: &str,
method: &str,
headers: &HeaderMap,
body: Option<&[u8]>,
) -> Result<(Response, String, f64), HandlerError> {
) -> Result<(Response, (String, String, i64), f64), HandlerError> {
let (scheme, host, port) = &node_url;
let start_time = Instant::now();
match raw_http_request(&node_url, path, method, headers, body).await {
match raw_http_request(node_url.clone(), path, method, headers, body).await {
Ok(response) => {
let elapsed = start_time.elapsed();
let latency_ms = elapsed.as_millis() as f64;
@ -187,22 +190,22 @@ async fn single_raw_request(
.map_err(|e| HandlerError::RequestError(format!("{:#?}", e)))?;
if is_jsonrpc_error(&body_bytes) {
record_failure(state, &node_url).await;
record_failure(state, scheme, host, *port).await;
return Err(HandlerError::RequestError("JSON-RPC error".to_string()));
}
// Reconstruct response with the body we consumed
let response = Response::from_parts(parts, Body::from(body_bytes));
record_success(state, &node_url, latency_ms).await;
record_success(state, scheme, host, *port, latency_ms).await;
Ok((response, node_url, latency_ms))
} else {
// For non-JSON-RPC endpoints, HTTP success is enough
record_success(state, &node_url, latency_ms).await;
record_success(state, scheme, host, *port, latency_ms).await;
Ok((response, node_url, latency_ms))
}
} else {
// Non-200 status codes are failures
record_failure(state, &node_url).await;
record_failure(state, scheme, host, *port).await;
Err(HandlerError::RequestError(format!(
"HTTP {}",
response.status()
@ -210,7 +213,7 @@ async fn single_raw_request(
}
}
Err(e) => {
record_failure(state, &node_url).await;
record_failure(state, scheme, host, *port).await;
Err(e)
}
}
@ -246,9 +249,9 @@ async fn race_requests(
.await
.map_err(|e| HandlerError::PoolError(e.to_string()))?;
let pool: Vec<String> = reliable_nodes
let pool: Vec<(String, String, i64)> = reliable_nodes
.into_iter()
.map(|node| node.full_url())
.map(|node| (node.scheme, node.host, node.port))
.collect();
pool
@ -286,7 +289,7 @@ async fn race_requests(
}
// Store node URLs for error tracking before consuming them
let current_nodes: Vec<String> = [&node1_option, &node2_option]
let current_nodes: Vec<(String, String, i64)> = [&node1_option, &node2_option]
.iter()
.filter_map(|opt| opt.as_ref())
.cloned()
@ -362,6 +365,9 @@ async fn race_requests(
match result {
Ok((response, winning_node, latency_ms)) => {
let (scheme, host, port) = &winning_node;
let winning_node = format!("{}://{}:{}", scheme, host, port);
match &jsonrpc_method {
Some(rpc_method) => {
debug!(
@ -377,14 +383,15 @@ async fn race_requests(
tried_nodes.len()
),
}
record_success(state, &winning_node, latency_ms).await;
record_success(state, scheme, host, *port, latency_ms).await;
return Ok(response);
}
Err(e) => {
// Since we don't know which specific node failed in the race,
// record the error for all nodes in this batch
for node_url in &current_nodes {
collected_errors.push((node_url.clone(), e.to_string()));
for (scheme, host, port) in &current_nodes {
let node_display = format!("{}://{}:{}", scheme, host, port);
collected_errors.push((node_display, e.to_string()));
}
debug!(
"Request failed: {} - retrying with different nodes from pool...",