Add socks5 test to gateway-probe (#6393)
* Socks5 in GW probe Bump NS agent version Fix bugs - force route construction - use same entry = exit Fix NS API version check workflow PR feedback More robust test attempts CLI arg validation Fix clippy PR feedback * Test provided endpoints in config at startup Require one valid endpoint * Bump agent to 1.1.0
This commit is contained in:
@@ -3,7 +3,7 @@ name: ci-check-ns-api-version
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "nym-node-status-api/**"
|
||||
- "nym-node-status-api/nym-node-status-api/**"
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "nym-node-status-api/nym-node-status-api"
|
||||
|
||||
Generated
+2
-1
@@ -6489,6 +6489,7 @@ dependencies = [
|
||||
"nym-validator-client",
|
||||
"pnet_packet",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
@@ -7234,7 +7235,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-agent"
|
||||
version = "1.0.7"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM expiration_date_signatures\n WHERE expiration_date = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "epoch_id: u32",
|
||||
"ordinal": 0,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "serialised_signatures",
|
||||
"ordinal": 1,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "serialization_revision: u8",
|
||||
"ordinal": 2,
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "00d857b624e7edab1198114b17cbad1e16988a3f9989d135840500e1143ce5e5"
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT timestamp, reliability as \"reliability: u8\"\n FROM gateway_status\n JOIN gateway_details\n ON gateway_status.gateway_details_id = gateway_details.id\n WHERE gateway_details.node_id=? AND gateway_status.timestamp > ?;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "timestamp",
|
||||
"ordinal": 0,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "reliability: u8",
|
||||
"ordinal": 1,
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "1d4535b58abdefaaca96bc7312fe14f63ccb56fa62976f7ce3d3b4f6eca8b711"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n DELETE FROM emergency_credential\n WHERE id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2f3c12cb0c48084b569e12ecb0315212a6f526f78258e173c96ec177988696ef"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n DELETE FROM emergency_credential\n WHERE type = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4a4f3b32b313f7fbc6eb579659e7cec1442967e53764b83ba0a66cd9a72494f9"
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT serialised_signatures, serialization_revision as \"serialization_revision: u8\"\n FROM expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "serialised_signatures",
|
||||
"ordinal": 0,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "serialization_revision: u8",
|
||||
"ordinal": 1,
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "5d3b8ad051ab6f46c702308c2fc751a5ca340ac9c6dd86da1a5e9a3e65ea589f"
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n d.node_id as \"node_id: NodeId\",\n CASE WHEN count(*) > 3 THEN AVG(reliability) ELSE 100 END as \"value: f32\"\n FROM\n gateway_details d\n JOIN\n gateway_status s on d.id = s.gateway_details_id\n WHERE\n timestamp >= ? AND\n timestamp <= ?\n GROUP BY 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "node_id: NodeId",
|
||||
"ordinal": 0,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "value: f32",
|
||||
"ordinal": 1,
|
||||
"type_info": "Null"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "676299beb2004ab89f7b38cf21ffb84ab5e7d7435297573523e2532560c2e302"
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM global_expiration_date_signatures\n WHERE expiration_date = ?\n ",
|
||||
"query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM global_expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -15,12 +15,12 @@
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "5eb13bfbee53b50641f69d4d6b62383c7f43864bffe98642bb8d1cf7c259d7be"
|
||||
"hash": "697e6d738aecf115a3608139579b2d1d937dd4cc4778dfb42709adf08c1497f2"
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT timestamp, reliability as \"reliability: u8\"\n FROM mixnode_status\n JOIN mixnode_details\n ON mixnode_status.mixnode_details_id = mixnode_details.id\n WHERE mixnode_details.mix_id=? AND mixnode_status.timestamp > ?;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "timestamp",
|
||||
"ordinal": 0,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "reliability: u8",
|
||||
"ordinal": 1,
|
||||
"type_info": "Integer"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "73ca856950a0157acfd3e2ed07b11aca3d875f67c77e2e7c75653c3f337d594e"
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM partial_expiration_date_signatures\n WHERE expiration_date = ?\n ",
|
||||
"query": "\n SELECT epoch_id as \"epoch_id: u32\", serialised_signatures\n FROM partial_expiration_date_signatures\n WHERE expiration_date = ? AND epoch_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -15,12 +15,12 @@
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "1f72d6f538a3655a031a3a8706794559c4c0df6defdfd179c84d02d3b8a6c055"
|
||||
"hash": "7e1829a17b81d7ee3dd8134b7a2be38ffdcaa672afbae44993365ca6910f68f6"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO emergency_credential\n (type, content, expiration)\n VALUES (?, ?, ?)\n ON CONFLICT(type, content) DO NOTHING;\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "805ad4f26e0234d7f482a263e186156311713d2e9f69d39c868cd16296b56326"
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT identity FROM gateway_details WHERE node_id = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "identity",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "924f8eb10c6cbb7f35da6c1bb77e1025442a594dcb5c6401b3dfac7df9c25073"
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n d.mix_id as \"mix_id: NodeId\",\n AVG(s.reliability) as \"value: f32\"\n FROM\n mixnode_details d\n JOIN\n mixnode_status s on d.id = s.mixnode_details_id\n WHERE\n timestamp >= ? AND\n timestamp <= ?\n GROUP BY 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "mix_id: NodeId",
|
||||
"ordinal": 0,
|
||||
"type_info": "Integer"
|
||||
},
|
||||
{
|
||||
"name": "value: f32",
|
||||
"ordinal": 1,
|
||||
"type_info": "Null"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "c19e1b3768bf2929407599e6e8783ead09f4d7319b7997fa2a9bb628f9404166"
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO transaction\n (hash, height, index, success, messages, memo, signatures, signer_infos, fee, gas_wanted, gas_used, raw_log, logs, events)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)\n ON CONFLICT (hash) DO UPDATE\n SET height = excluded.height,\n index = excluded.index,\n success = excluded.success,\n messages = excluded.messages,\n memo = excluded.memo,\n signatures = excluded.signatures,\n signer_infos = excluded.signer_infos,\n fee = excluded.fee,\n gas_wanted = excluded.gas_wanted,\n gas_used = excluded.gas_used,\n raw_log = excluded.raw_log,\n logs = excluded.logs,\n events = excluded.events\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int8",
|
||||
"Int4",
|
||||
"Bool",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"TextArray",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Text",
|
||||
"Jsonb",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "08f4e54ac24fccd54f4208797b3749e457f8cd4ba3d7d906a7ab3bf5b4e7dc9c"
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO validator (consensus_address, consensus_pubkey)\n VALUES ($1, $2)\n ON CONFLICT DO NOTHING\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0d3709efacf763b06bf14803bb803b5ee5b27879b0026bb0480b3f2722318a75"
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM pre_commit WHERE height < $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1c2fb0e9ffceca21ef8dbea19b116422b1f723d0a316314b50c43c8b29f8891d"
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT height\n FROM block\n ORDER BY height ASC\n LIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "height",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2561fb016951ea4cd29e43fb9a4a93e944b0d44ed1f7c1036f306e34372da11c"
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE metadata SET last_processed_height = GREATEST(last_processed_height, $1)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2679cdf11fa66c7920678cde860c57402119ec7c3aae731b0da831327301466f"
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE pruning SET last_pruned_height = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "36ba5941aca6e7b604a10b8b0aba70635028f392fe794d6131827b083e1755e1"
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT last_pruned_height FROM pruning\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "last_pruned_height",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "3bdf81a9db6075f6f77224c30553f419a849d4ec45af40b052a4cbf09b44f3ec"
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM message WHERE height < $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "52c27143720ddfdfd0f5644b60f5b67fd9281ce1de0653efa53b9d9b93cf335d"
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO pre_commit (validator_address, height, timestamp, voting_power, proposer_priority)\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (validator_address, timestamp) DO NOTHING\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int8",
|
||||
"Timestamp",
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "62e14613f5ffe692346a79086857a22f0444fbc679db1c06b651fb8b5538b278"
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO block (height, hash, num_txs, total_gas, proposer_address, timestamp)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT DO NOTHING\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text",
|
||||
"Int4",
|
||||
"Int8",
|
||||
"Text",
|
||||
"Timestamp"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "64a484fd46d8ec46797f944a4cced56b6e270ce186f0e49528865d1924343b78"
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT height\n FROM block\n WHERE timestamp < $1\n ORDER BY timestamp DESC\n LIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "height",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Timestamp"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "7e82426f5dbcadf1631ba1a806e19cc462d04222fb20ad76de2a40f3f4f8fe15"
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT height\n FROM block\n WHERE timestamp > $1\n ORDER BY timestamp\n LIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "height",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Timestamp"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "9455331f9be5a3be28e2bd399a36b2e2d6a9ad4b225c4c883aafc4e9f0428008"
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT COUNT(*) as count FROM pre_commit\n WHERE\n validator_address = $1\n AND height >= $2\n AND height <= $3\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "bc7795e58ce71893c3f32a19db8e77b7bc0a1af315ffd42c3e68156d6e4ace70"
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT * FROM validator\n WHERE EXISTS (\n SELECT 1 FROM pre_commit\n WHERE height = $1\n AND pre_commit.validator_address = validator.consensus_address\n )\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "consensus_address",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "consensus_pubkey",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "be43d4873911deca784b7be0531ab7bd82ecd68041aa932a56c8ce09623251e4"
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT last_processed_height FROM metadata\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "last_processed_height",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c88d07fecc3f33deaa6e93db1469ce71582635df47f52dcf3fd1df4e7be6b96d"
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO message(transaction_hash, index, type, value, involved_accounts_addresses, height)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (transaction_hash, index) DO UPDATE\n SET height = excluded.height,\n type = excluded.type,\n value = excluded.value,\n involved_accounts_addresses = excluded.involved_accounts_addresses\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int8",
|
||||
"Text",
|
||||
"Jsonb",
|
||||
"TextArray",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cc0ae74082d7d8a89f2d3364676890bbf6150ab394c72783114340d4def5f9ef"
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM block WHERE height < $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cdba9b267f143c8a8c6c3d6ed713cf00236490b86779559d84740ec18bcfa3a9"
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM transaction WHERE height < $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d89558c37c51e8e6b1e6a9d5a2b13d0598fd856aa019a0cbbae12d7cafb4672f"
|
||||
}
|
||||
@@ -24,6 +24,7 @@ hex.workspace = true
|
||||
tracing.workspace = true
|
||||
pnet_packet.workspace = true
|
||||
rand.workspace = true
|
||||
reqwest = { workspace = true, features = ["socks"] }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
thiserror.workspace = true
|
||||
|
||||
@@ -12,5 +12,6 @@ pub(crate) mod icmp;
|
||||
pub(crate) mod netstack;
|
||||
pub(crate) mod nodes;
|
||||
pub(crate) mod probe_tests;
|
||||
pub(crate) mod socks5_test;
|
||||
pub(crate) mod types;
|
||||
pub(crate) mod wireguard;
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
use anyhow::{Context, anyhow, bail};
|
||||
use nym_api_requests::models::{
|
||||
AuthenticatorDetailsV2, DeclaredRolesV2, DescribedNodeTypeV2, HostInformationV2,
|
||||
IpPacketRouterDetailsV2, LewesProtocolDetailsV1, NetworkRequesterDetailsV2, NymNodeDataV2,
|
||||
OffsetDateTimeJsonSchemaWrapper, WebSocketsV2, WireguardDetailsV2,
|
||||
IpPacketRouterDetailsV2, LewesProtocolDetailsV1, NetworkRequesterDetailsV1,
|
||||
NetworkRequesterDetailsV2, NymNodeDataV2, OffsetDateTimeJsonSchemaWrapper, WebSocketsV2,
|
||||
WireguardDetailsV2,
|
||||
};
|
||||
use nym_authenticator_requests::AuthenticatorVersion;
|
||||
use nym_bin_common::build_information::BinaryBuildInformationOwned;
|
||||
@@ -161,10 +162,12 @@ impl DirectoryNode {
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
let network_requester_details = self.described.description.network_requester.clone();
|
||||
|
||||
Ok(TestedNodeDetails {
|
||||
identity: self.identity(),
|
||||
exit_router_address,
|
||||
network_requester_details,
|
||||
authenticator_address,
|
||||
authenticator_version,
|
||||
ip_address: Some(ip_address),
|
||||
@@ -409,7 +412,17 @@ impl NymApiDirectory {
|
||||
.iter()
|
||||
.filter(|(_, n)| n.described.description.ip_packet_router.is_some())
|
||||
.choose(&mut rand::thread_rng())
|
||||
.ok_or(anyhow!("no gateways running IPR available"))
|
||||
.context("no gateways running IPR available")
|
||||
.map(|(id, _)| *id)
|
||||
}
|
||||
|
||||
pub fn random_exit_with_nr(&self) -> anyhow::Result<NodeIdentity> {
|
||||
info!("Selecting random gateway with NR enabled");
|
||||
self.nodes
|
||||
.iter()
|
||||
.filter(|(_, n)| n.described.description.ip_packet_router.is_some())
|
||||
.choose(&mut rand::thread_rng())
|
||||
.context("no gateways running NR available")
|
||||
.map(|(id, _)| *id)
|
||||
}
|
||||
|
||||
@@ -419,7 +432,7 @@ impl NymApiDirectory {
|
||||
.iter()
|
||||
.filter(|(_, n)| n.described.description.declared_role.entry)
|
||||
.choose(&mut rand::thread_rng())
|
||||
.ok_or(anyhow!("no entry gateways available"))
|
||||
.context("no entry gateways available")
|
||||
.map(|(id, _)| *id)
|
||||
}
|
||||
|
||||
@@ -439,6 +452,16 @@ impl NymApiDirectory {
|
||||
};
|
||||
Ok(maybe_entry)
|
||||
}
|
||||
|
||||
pub fn exit_gateway_nr(&self, identity: &NodeIdentity) -> anyhow::Result<DirectoryNode> {
|
||||
let Some(maybe_entry) = self.nodes.get(identity).cloned() else {
|
||||
bail!("{identity} not found in directory")
|
||||
};
|
||||
if !maybe_entry.described.description.declared_role.exit_nr {
|
||||
bail!("{identity} doesn't support exit NR mode")
|
||||
};
|
||||
Ok(maybe_entry)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
@@ -468,6 +491,7 @@ impl TestedNode {
|
||||
pub struct TestedNodeDetails {
|
||||
pub identity: NodeIdentity,
|
||||
pub exit_router_address: Option<Recipient>,
|
||||
pub network_requester_details: Option<NetworkRequesterDetailsV1>,
|
||||
pub authenticator_address: Option<Recipient>,
|
||||
pub authenticator_version: AuthenticatorVersion,
|
||||
pub ip_address: Option<IpAddr>,
|
||||
@@ -489,6 +513,7 @@ impl TestedNodeDetails {
|
||||
identity,
|
||||
ip_address: Some(lp_data.address.ip()),
|
||||
lp_data: Some(lp_data),
|
||||
network_requester_details: None,
|
||||
// These are None in localnet mode - only needed for mixnet/authenticator
|
||||
exit_router_address: None,
|
||||
authenticator_address: None,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::NymApiDirectory;
|
||||
use crate::common::helpers::mixnet_debug_config;
|
||||
use crate::common::nodes::{TestedNodeDetails, TestedNodeLpDetails};
|
||||
use crate::common::socks5_test::HttpsConnectivityTest;
|
||||
use crate::common::types::{
|
||||
Entry, Exit, IpPingReplies, LpProbeResults, ProbeOutcome, WgProbeResults,
|
||||
Entry, Exit, IpPingReplies, LpProbeResults, ProbeOutcome, Socks5ProbeResults, WgProbeResults,
|
||||
};
|
||||
use crate::common::wireguard::{
|
||||
TwoHopWgTunnelConfig, WgTunnelConfig, run_tunnel_tests, run_two_hop_tunnel_tests,
|
||||
@@ -27,7 +30,12 @@ use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_ip_packet_client::IprClientConnect;
|
||||
use nym_ip_packet_requests::{IpPair, codec::MultiIpPacketCodec};
|
||||
use nym_registration_client::{LpRegistrationClient, NestedLpSession};
|
||||
use nym_sdk::mixnet::{MixnetClient, NodeIdentity, Recipient};
|
||||
use nym_sdk::mixnet::{MixnetClient, MixnetClientBuilder, NodeIdentity, Recipient, Socks5};
|
||||
use nym_sdk::{
|
||||
DebugConfig, NymApiTopologyProvider, NymApiTopologyProviderConfig, NymNetworkDetails,
|
||||
TopologyProvider,
|
||||
};
|
||||
use nym_topology::{HardcodedTopologyProvider, NymTopology};
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
sync::Arc,
|
||||
@@ -36,6 +44,7 @@ use std::{
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::{codec::Decoder, sync::CancellationToken};
|
||||
use tracing::*;
|
||||
use url::Url;
|
||||
|
||||
pub async fn wg_probe(
|
||||
mut auth_client: AuthenticatorClient,
|
||||
@@ -446,6 +455,7 @@ pub async fn do_ping(
|
||||
exit_result.map(|exit| ProbeOutcome {
|
||||
as_entry: entry,
|
||||
as_exit: exit,
|
||||
socks5: None,
|
||||
wg: None,
|
||||
lp: None,
|
||||
}),
|
||||
@@ -609,3 +619,178 @@ pub async fn listen_for_icmp_ping_replies(
|
||||
can_route_ip_external_v6: registered_replies.external_ip_v6,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Creates a SOCKS5 proxy connection through the mixnet to the exit GW
|
||||
/// and performs necessary tests.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[instrument(level = "info", name = "socks5_test", skip_all)]
|
||||
pub(crate) async fn do_socks5_connectivity_test(
|
||||
network_requester_address: &str,
|
||||
network_details: NymNetworkDetails,
|
||||
directory: &NymApiDirectory,
|
||||
json_rpc_endpoints: Vec<String>,
|
||||
mixnet_client_timeout: u64,
|
||||
test_run_count: u64,
|
||||
failure_count_cutoff: usize,
|
||||
topology: Option<NymTopology>,
|
||||
) -> anyhow::Result<Socks5ProbeResults> {
|
||||
info!(
|
||||
"Starting SOCKS5 test through Network Requester: {}",
|
||||
network_requester_address
|
||||
);
|
||||
if json_rpc_endpoints.is_empty() {
|
||||
bail!("You need to define JSON RPC URLs in order to test SOCKS5")
|
||||
}
|
||||
|
||||
// parse the network requester address
|
||||
let nr_recipient = match network_requester_address.parse::<Recipient>() {
|
||||
Ok(addr) => addr,
|
||||
Err(e) => {
|
||||
error!("Invalid Network Requester address: {}", e);
|
||||
|
||||
return Ok(Socks5ProbeResults::error_before_connecting(format!(
|
||||
"Invalid NR address: {}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
info!(
|
||||
"Network Requester gateway: {}",
|
||||
nr_recipient.gateway().to_base58_string()
|
||||
);
|
||||
info!(
|
||||
"Network Requester identity: {}",
|
||||
nr_recipient.identity().to_base58_string()
|
||||
);
|
||||
|
||||
// create ephemeral SOCKS5 client
|
||||
let socks5_config = Socks5::new(network_requester_address.to_string());
|
||||
|
||||
// since we define both entry & exit gateways to be the same tested GW,
|
||||
// this shouldn't negatively affect mixnet layers but it will force route
|
||||
// construction in case GW would get filtered out of topology
|
||||
let min_gw_performance = Some(0);
|
||||
|
||||
// debug config similar to main probe
|
||||
let debug_config = mixnet_debug_config(min_gw_performance, true);
|
||||
|
||||
// Verify the NR gateway exists in the directory with exit_nr role
|
||||
let nr_gateway_id = nr_recipient.gateway();
|
||||
if let Err(e) = directory.exit_gateway_nr(&nr_gateway_id) {
|
||||
return Ok(Socks5ProbeResults::error_before_connecting(e.to_string()));
|
||||
} else {
|
||||
info!("✔️ Network Requester gateway found in directory with exit_nr role");
|
||||
}
|
||||
|
||||
// use intended exit as entry as well
|
||||
let entry_gateway = nr_gateway_id;
|
||||
|
||||
// use existing topology if available, otherwise fetch it
|
||||
let topology_provider: Box<HardcodedTopologyProvider> = match topology {
|
||||
Some(t) => {
|
||||
info!("✔️ Reusing topology from main mixnet client");
|
||||
Box::new(HardcodedTopologyProvider::new(t))
|
||||
}
|
||||
None => {
|
||||
info!("Fetching topology for SOCKS5 client...");
|
||||
match hardcoded_topology(&network_details, &debug_config).await {
|
||||
Ok(provider) => provider,
|
||||
Err(e) => return Ok(Socks5ProbeResults::error_before_connecting(e)),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let socks5_client_builder = MixnetClientBuilder::new_ephemeral()
|
||||
// Specify entry gateway explicitly
|
||||
.request_gateway(entry_gateway.to_base58_string())
|
||||
.socks5_config(socks5_config)
|
||||
.network_details(network_details)
|
||||
.debug_config(debug_config)
|
||||
.custom_topology_provider(topology_provider)
|
||||
.build()?;
|
||||
|
||||
// connect to mixnet via SOCKS5
|
||||
let socks5_client = match socks5_client_builder.connect_to_mixnet_via_socks5().await {
|
||||
Ok(client) => {
|
||||
info!("🌐 Successfully connected to mixnet via SOCKS5 proxy");
|
||||
info!(
|
||||
"Connected via entry gateway: {}",
|
||||
client.nym_address().gateway().to_base58_string()
|
||||
);
|
||||
client
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to establish SOCKS5 connection: {}", e);
|
||||
return Ok(Socks5ProbeResults::error_before_connecting(format!(
|
||||
"SOCKS5 connection failed: {}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
let test = match HttpsConnectivityTest::new(
|
||||
test_run_count,
|
||||
mixnet_client_timeout,
|
||||
failure_count_cutoff,
|
||||
json_rpc_endpoints,
|
||||
socks5_client.socks5_url(),
|
||||
) {
|
||||
Ok(test) => test,
|
||||
Err(err) => {
|
||||
socks5_client.disconnect().await;
|
||||
|
||||
error!("{err}");
|
||||
return Ok(Socks5ProbeResults::error_after_connecting(
|
||||
"Failed to create client",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let result = test.run_tests().await;
|
||||
socks5_client.disconnect().await;
|
||||
|
||||
Ok(Socks5ProbeResults::with_http_result(result))
|
||||
}
|
||||
|
||||
async fn hardcoded_topology(
|
||||
network_details: &NymNetworkDetails,
|
||||
debug_config: &DebugConfig,
|
||||
) -> Result<Box<HardcodedTopologyProvider>, String> {
|
||||
// get Nym API URLs from network_details
|
||||
let nym_api_urls: Vec<Url> = network_details
|
||||
.nym_api_urls
|
||||
.as_ref()
|
||||
.map(|urls| urls.iter().filter_map(|u| u.url.parse().ok()).collect())
|
||||
.or_else(|| {
|
||||
network_details
|
||||
.endpoints
|
||||
.first()
|
||||
.and_then(|e| e.api_url())
|
||||
.map(|url| vec![url])
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if nym_api_urls.is_empty() {
|
||||
return Err(String::from("No nym-api URLs available to fetch topology"));
|
||||
}
|
||||
|
||||
let topology_config = NymApiTopologyProviderConfig {
|
||||
min_mixnode_performance: debug_config.topology.minimum_mixnode_performance,
|
||||
min_gateway_performance: debug_config.topology.minimum_gateway_performance,
|
||||
use_extended_topology: debug_config.topology.use_extended_topology,
|
||||
ignore_egress_epoch_role: debug_config.topology.ignore_egress_epoch_role,
|
||||
};
|
||||
|
||||
let api_client = nym_http_api_client::Client::new_url(nym_api_urls[0].clone(), None)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mut provider = NymApiTopologyProvider::new(topology_config, nym_api_urls, api_client);
|
||||
|
||||
match provider.get_new_topology().await {
|
||||
Some(topology) => {
|
||||
info!("Fetched network topology");
|
||||
Ok(Box::new(HardcodedTopologyProvider::new(topology)))
|
||||
}
|
||||
None => Err(String::from("Failed to fetch network topology")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
use anyhow::bail;
|
||||
use rand::Rng;
|
||||
use reqwest::Proxy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use tokio::time::Instant;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::common::socks5_test::SingleHttpsTestResult;
|
||||
|
||||
pub struct JsonRpcClient {
|
||||
client: reqwest::Client,
|
||||
client_timeout: Duration,
|
||||
test_endpoints: Vec<String>,
|
||||
}
|
||||
|
||||
impl JsonRpcClient {
|
||||
pub fn new(
|
||||
client_timeout: u64,
|
||||
proxy: Option<Proxy>,
|
||||
test_endpoints: Vec<String>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut builder = reqwest::Client::builder().timeout(Duration::from_secs(client_timeout));
|
||||
|
||||
if let Some(proxy) = proxy {
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
let client = builder.build()?;
|
||||
|
||||
Ok(Self {
|
||||
client_timeout: Duration::from_secs(client_timeout),
|
||||
test_endpoints,
|
||||
client,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) async fn https_request_with_fallbacks(&self) -> SingleHttpsTestResult {
|
||||
let mut error_msg = String::new();
|
||||
|
||||
// endpoints are used as fallbacks: in case of success, return early
|
||||
for endpoint in self.test_endpoints.iter() {
|
||||
info!(
|
||||
"Testing against {} with timeout {}s",
|
||||
endpoint,
|
||||
self.client_timeout.as_secs()
|
||||
);
|
||||
let start = Instant::now();
|
||||
|
||||
let res = self.eth_chainid(endpoint).await;
|
||||
let elapsed = start.elapsed();
|
||||
match res {
|
||||
Ok((status, JsonRpcResponse::Ok { .. })) => {
|
||||
debug!(
|
||||
"HTTPS test completed: status={}, latency={}ms",
|
||||
status.as_u16(),
|
||||
elapsed.as_millis()
|
||||
);
|
||||
return SingleHttpsTestResult {
|
||||
success: true,
|
||||
status_code: Some(status.as_u16()),
|
||||
latency_ms: Some(elapsed.as_millis() as u64),
|
||||
endpoint_used: Some(endpoint.to_string()),
|
||||
error: None,
|
||||
};
|
||||
}
|
||||
Ok((_, JsonRpcResponse::Err { error, .. })) => {
|
||||
warn!("JSON-RPC error: {} (code: {})", error.message, error.code);
|
||||
error_msg = format!("JSON-RPC error: {}", error.message);
|
||||
}
|
||||
Err(e) => {
|
||||
error_msg = e.to_string();
|
||||
error!("{}", &error_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SingleHttpsTestResult {
|
||||
success: false,
|
||||
status_code: None,
|
||||
latency_ms: None,
|
||||
endpoint_used: self.test_endpoints.last().cloned(),
|
||||
error: Some(error_msg),
|
||||
}
|
||||
}
|
||||
|
||||
async fn eth_chainid(
|
||||
&self,
|
||||
endpoint: &str,
|
||||
) -> anyhow::Result<(reqwest::StatusCode, JsonRpcResponse)> {
|
||||
match self
|
||||
.client
|
||||
.post(endpoint)
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
||||
.json(&JsonRpcRequestBody::eth_chainid())
|
||||
.send()
|
||||
.await
|
||||
.and_then(reqwest::Response::error_for_status)
|
||||
{
|
||||
Ok(response) => {
|
||||
let status = response.status();
|
||||
if status.is_success() {
|
||||
// Deserialize body into JsonRpcResponse
|
||||
response
|
||||
.json::<JsonRpcResponse>()
|
||||
.await
|
||||
.map(|res| (status, res))
|
||||
.map_err(From::from)
|
||||
} else {
|
||||
bail!("HTTP error status: {}", status.as_u16());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("HTTPS request failed: {}", e);
|
||||
bail!("HTTPS request failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ensure_endpoint_works(&self) -> anyhow::Result<()> {
|
||||
let mut any_works = false;
|
||||
for endpoint in self.test_endpoints.iter() {
|
||||
if let Err(err) = self.eth_chainid(endpoint).await {
|
||||
warn!("Endpoint {endpoint} error: {err}");
|
||||
} else {
|
||||
any_works = true;
|
||||
}
|
||||
}
|
||||
|
||||
if any_works {
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("None of the endpoints are valid, see logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// https://www.jsonrpc.org/specification
|
||||
#[derive(Serialize)]
|
||||
struct JsonRpcRequestBody {
|
||||
// A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0".
|
||||
jsonrpc: String,
|
||||
method: String,
|
||||
// A Structured value that holds the parameter values to be used during the invocation of the method. This member MAY be omitted.
|
||||
params: serde_json::Value,
|
||||
// The Server MUST reply with the same value in the Response object if included.
|
||||
// This member is used to correlate the context between the two objects.
|
||||
id: i64,
|
||||
}
|
||||
|
||||
impl JsonRpcRequestBody {
|
||||
/// Very simple endpoint that requires no dynamic input
|
||||
///
|
||||
/// https://ethereum.org/developers/docs/apis/json-rpc/#eth_chainId
|
||||
pub fn eth_chainid() -> Self {
|
||||
Self {
|
||||
jsonrpc: String::from("2.0"),
|
||||
method: String::from("eth_chainId"),
|
||||
params: serde_json::json!([]),
|
||||
id: rand::thread_rng().r#gen(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an eth_getBlockByNumber request with invalid params for testing error responses
|
||||
#[cfg(test)]
|
||||
pub fn eth_get_block_by_number_invalid() -> Self {
|
||||
Self {
|
||||
jsonrpc: String::from("2.0"),
|
||||
method: String::from("eth_getBlockByNumber"),
|
||||
// Invalid params: should be [blockNumber, boolean] but we pass garbage
|
||||
params: serde_json::json!(["invalid_block_number"]),
|
||||
id: rand::thread_rng().r#gen(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dead code: we need these fields for deserialization, even if we don't read them explicitly
|
||||
#[allow(dead_code)]
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum JsonRpcResponse {
|
||||
Ok {
|
||||
jsonrpc: String,
|
||||
// have to use opaque Value because spec say this might be string, number or null (we don't care either way)
|
||||
id: serde_json::Value,
|
||||
// we don't really care for the exact result, just whether the response is OK or error
|
||||
result: serde_json::Value,
|
||||
},
|
||||
Err {
|
||||
jsonrpc: String,
|
||||
// have to use opaque Value because spec say this might be string, number or null (we don't care either way)
|
||||
id: serde_json::Value,
|
||||
error: JsonRpcError,
|
||||
},
|
||||
}
|
||||
|
||||
// dead code: we need these fields for deserialization, even if we don't read them explicitly
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct JsonRpcError {
|
||||
pub code: i64,
|
||||
pub message: String,
|
||||
#[serde(default)]
|
||||
pub data: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
const JSON_RPC_ENDPOINT: &str = "https://cloudflare-eth.com";
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_eth_chainid_returns_ok_response() {
|
||||
let client = reqwest::Client::new();
|
||||
let response = client
|
||||
.post(JSON_RPC_ENDPOINT)
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
||||
.json(&JsonRpcRequestBody::eth_chainid())
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to send request");
|
||||
|
||||
assert!(response.status().is_success());
|
||||
|
||||
let json_response: JsonRpcResponse =
|
||||
response.json().await.expect("Failed to parse response");
|
||||
|
||||
assert!(
|
||||
matches!(json_response, JsonRpcResponse::Ok { .. }),
|
||||
"Expected Ok variant for eth_chainId"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_eth_get_block_by_number_invalid_returns_error_response() {
|
||||
let client = reqwest::Client::new();
|
||||
let response = client
|
||||
.post(JSON_RPC_ENDPOINT)
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
||||
.json(&JsonRpcRequestBody::eth_get_block_by_number_invalid())
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to send request");
|
||||
|
||||
assert!(response.status().is_success()); // HTTP 200 but JSON-RPC error
|
||||
|
||||
let json_response: JsonRpcResponse =
|
||||
response.json().await.expect("Failed to parse response");
|
||||
|
||||
assert!(
|
||||
matches!(json_response, JsonRpcResponse::Err { .. }),
|
||||
"Expected Err variant for invalid params"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{info, warn};
|
||||
|
||||
pub(crate) use json_rpc_client::JsonRpcClient;
|
||||
|
||||
mod json_rpc_client;
|
||||
|
||||
pub struct HttpsConnectivityTest {
|
||||
test_count: u64,
|
||||
failure_count_cutoff: usize,
|
||||
client: JsonRpcClient,
|
||||
}
|
||||
|
||||
impl HttpsConnectivityTest {
|
||||
pub fn new(
|
||||
test_count: u64,
|
||||
mixnet_client_timeout: u64,
|
||||
failure_count_cutoff: usize,
|
||||
json_rpc_test_endpoints: Vec<String>,
|
||||
socks5_proxy_url: String,
|
||||
) -> anyhow::Result<Self> {
|
||||
let proxy = reqwest::Proxy::all(socks5_proxy_url)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create proxy: {}", e))?;
|
||||
let client =
|
||||
JsonRpcClient::new(mixnet_client_timeout, Some(proxy), json_rpc_test_endpoints)?;
|
||||
let res = Self {
|
||||
test_count: std::cmp::max(test_count, 1),
|
||||
failure_count_cutoff,
|
||||
client,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn run_tests(self) -> HttpsConnectivityResult {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for i in 1..=self.test_count {
|
||||
info!("Running test {}/{}", i, self.test_count);
|
||||
let interim_res = self.client.https_request_with_fallbacks().await;
|
||||
|
||||
if interim_res.success {
|
||||
info!(
|
||||
"{}/{} latency: {}ms",
|
||||
i,
|
||||
self.test_count,
|
||||
interim_res.latency_ms.unwrap_or(0)
|
||||
);
|
||||
}
|
||||
|
||||
results.push(interim_res);
|
||||
|
||||
// early exit
|
||||
let unsuccessful = results.iter().filter(|r| !r.success).count();
|
||||
if unsuccessful > self.failure_count_cutoff {
|
||||
warn!("Too many failed runs: returning early...");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let final_result = HttpsConnectivityResult::from_results(results);
|
||||
info!("AVG latency (in ms): {:?}", final_result.https_latency_ms);
|
||||
final_result
|
||||
}
|
||||
}
|
||||
|
||||
/// single HTTPS test attempt
|
||||
struct SingleHttpsTestResult {
|
||||
success: bool,
|
||||
status_code: Option<u16>,
|
||||
latency_ms: Option<u64>,
|
||||
endpoint_used: Option<String>,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct HttpsConnectivityResult {
|
||||
/// successfully completed HTTPS request
|
||||
https_success: bool,
|
||||
|
||||
/// HTTPS status code received
|
||||
https_status_code: Option<u16>,
|
||||
|
||||
/// average HTTPS request latency in milliseconds
|
||||
https_latency_ms: Option<u64>,
|
||||
|
||||
/// among multiple endpoints available, list the one actually used
|
||||
endpoint_used: Option<String>,
|
||||
|
||||
/// error message(s) (if any)
|
||||
error: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl HttpsConnectivityResult {
|
||||
pub fn with_errors(error: Vec<String>) -> Self {
|
||||
Self {
|
||||
https_success: false,
|
||||
https_status_code: None,
|
||||
https_latency_ms: None,
|
||||
endpoint_used: None,
|
||||
error: Some(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_results(results: Vec<SingleHttpsTestResult>) -> Self {
|
||||
let (successes, errors): (Vec<SingleHttpsTestResult>, Vec<SingleHttpsTestResult>) =
|
||||
results.into_iter().partition(|r| r.success);
|
||||
let errors = errors
|
||||
.into_iter()
|
||||
.map(|r| r.error)
|
||||
.collect::<Option<Vec<_>>>()
|
||||
// partition above guarantees this vec is non-empty
|
||||
.unwrap_or_default();
|
||||
|
||||
// use the last successful result for status_code and endpoint
|
||||
// this works as an empty check as well: if there is no last success, array must be empty hence only errors are present
|
||||
let Some(last_success) = successes.last() else {
|
||||
return Self::with_errors(errors);
|
||||
};
|
||||
|
||||
// average latency from successful runs
|
||||
let mut successes_count = 0;
|
||||
let total_latency: u64 = successes
|
||||
.iter()
|
||||
.filter_map(|r| {
|
||||
successes_count += 1;
|
||||
r.latency_ms
|
||||
})
|
||||
.sum();
|
||||
let avg_latency = total_latency / successes_count as u64;
|
||||
|
||||
Self {
|
||||
https_success: true,
|
||||
https_status_code: last_success.status_code,
|
||||
https_latency_ms: Some(avg_latency),
|
||||
endpoint_used: last_success.endpoint_used.clone(),
|
||||
// even in case of success, some errors were possible
|
||||
error: if errors.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(errors)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn https_success(&self) -> bool {
|
||||
self.https_success
|
||||
}
|
||||
|
||||
pub fn https_status_code(&self) -> Option<&u16> {
|
||||
self.https_status_code.as_ref()
|
||||
}
|
||||
|
||||
pub fn https_latency_ms(&self) -> Option<&u64> {
|
||||
self.https_latency_ms.as_ref()
|
||||
}
|
||||
|
||||
pub fn endpoint_used(&self) -> Option<&String> {
|
||||
self.endpoint_used.as_ref()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
use nym_connection_monitor::ConnectionStatusEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use super::socks5_test::HttpsConnectivityResult;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProbeResult {
|
||||
pub node: String,
|
||||
@@ -12,6 +14,7 @@ pub struct ProbeResult {
|
||||
pub struct ProbeOutcome {
|
||||
pub as_entry: Entry,
|
||||
pub as_exit: Option<Exit>,
|
||||
pub socks5: Option<Socks5ProbeResults>,
|
||||
pub wg: Option<WgProbeResults>,
|
||||
pub lp: Option<LpProbeResults>,
|
||||
}
|
||||
@@ -132,6 +135,38 @@ impl Exit {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct Socks5ProbeResults {
|
||||
/// whether we could establish a SOCKS5 proxy connection
|
||||
can_connect_socks5: bool,
|
||||
|
||||
/// HTTPS connectivity test
|
||||
https_connectivity: HttpsConnectivityResult,
|
||||
}
|
||||
|
||||
impl Socks5ProbeResults {
|
||||
pub fn with_http_result(https_connectivity: HttpsConnectivityResult) -> Self {
|
||||
Self {
|
||||
can_connect_socks5: true,
|
||||
https_connectivity,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error_before_connecting(error: impl Into<String>) -> Self {
|
||||
Self {
|
||||
can_connect_socks5: false,
|
||||
https_connectivity: HttpsConnectivityResult::with_errors(vec![error.into()]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error_after_connecting(error: impl Into<String>) -> Self {
|
||||
Self {
|
||||
can_connect_socks5: true,
|
||||
https_connectivity: HttpsConnectivityResult::with_errors(vec![error.into()]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct IpPingReplies {
|
||||
pub ipr_tun_ip_v4: bool,
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
mod credentials;
|
||||
mod netstack;
|
||||
mod socks5;
|
||||
mod test_mode;
|
||||
|
||||
pub use credentials::CredentialArgs;
|
||||
pub use netstack::NetstackArgs;
|
||||
pub use socks5::Socks5Args;
|
||||
pub use test_mode::TestMode;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
use clap::Args;
|
||||
|
||||
use crate::common::socks5_test::JsonRpcClient;
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Socks5Args {
|
||||
#[arg(long, value_delimiter = ';')]
|
||||
pub socks5_json_rpc_url_list: Vec<String>,
|
||||
|
||||
#[arg(long, default_value_t = 30)]
|
||||
pub mixnet_client_timeout_sec: u64,
|
||||
|
||||
#[arg(long, default_value_t = 10)]
|
||||
pub test_count: u64,
|
||||
|
||||
/// stops socks5 test early after this many failed attempts
|
||||
#[arg(long, default_value_t = 3)]
|
||||
pub failure_count_cutoff: usize,
|
||||
}
|
||||
|
||||
impl Socks5Args {
|
||||
pub async fn validate_socks5_endpoints(&self) -> anyhow::Result<()> {
|
||||
let client = JsonRpcClient::new(
|
||||
self.mixnet_client_timeout_sec,
|
||||
None,
|
||||
self.socks5_json_rpc_url_list.clone(),
|
||||
)?;
|
||||
client.ensure_endpoint_works().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,13 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::common::helpers;
|
||||
use crate::common::probe_tests::{do_ping, lp_registration_probe, wg_probe, wg_probe_lp};
|
||||
use crate::common::types::{Entry, Exit, WgProbeResults};
|
||||
use crate::common::probe_tests::{
|
||||
do_ping, do_socks5_connectivity_test, lp_registration_probe, wg_probe, wg_probe_lp,
|
||||
};
|
||||
use crate::common::types::{Entry, Exit, Socks5ProbeResults, WgProbeResults};
|
||||
use crate::config::Socks5Args;
|
||||
use anyhow::bail;
|
||||
use nym_api_requests::models::NetworkRequesterDetailsV1;
|
||||
use nym_authenticator_client::{AuthClientMixnetListener, AuthenticatorClient};
|
||||
use nym_client_core::config::ForgetMe;
|
||||
use nym_config::defaults::NymNetworkDetails;
|
||||
@@ -14,6 +18,7 @@ use nym_sdk::mixnet::{
|
||||
CredentialStorage, Ephemeral, KeyStore, MixnetClient, MixnetClientBuilder, MixnetClientStorage,
|
||||
NodeIdentity, StoragePaths,
|
||||
};
|
||||
use nym_topology::NymTopology;
|
||||
use rand::rngs::OsRng;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
@@ -48,6 +53,7 @@ pub struct Probe {
|
||||
localnet_entry: Option<TestedNodeDetails>,
|
||||
/// Localnet exit gateway info (used when --exit-gateway-identity is specified)
|
||||
localnet_exit: Option<TestedNodeDetails>,
|
||||
socks5_args: Socks5Args,
|
||||
}
|
||||
|
||||
impl Probe {
|
||||
@@ -56,6 +62,7 @@ impl Probe {
|
||||
tested_node: TestedNode,
|
||||
netstack_args: NetstackArgs,
|
||||
credentials_args: CredentialArgs,
|
||||
socks5_args: Socks5Args,
|
||||
) -> Self {
|
||||
Self {
|
||||
entrypoint,
|
||||
@@ -67,6 +74,7 @@ impl Probe {
|
||||
exit_gateway_node: None,
|
||||
localnet_entry: None,
|
||||
localnet_exit: None,
|
||||
socks5_args,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +85,7 @@ impl Probe {
|
||||
netstack_args: NetstackArgs,
|
||||
credentials_args: CredentialArgs,
|
||||
gateway_node: DirectoryNode,
|
||||
socks5_args: Socks5Args,
|
||||
) -> Self {
|
||||
Self {
|
||||
entrypoint,
|
||||
@@ -88,6 +97,7 @@ impl Probe {
|
||||
exit_gateway_node: None,
|
||||
localnet_entry: None,
|
||||
localnet_exit: None,
|
||||
socks5_args,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +109,7 @@ impl Probe {
|
||||
credentials_args: CredentialArgs,
|
||||
entry_gateway_node: DirectoryNode,
|
||||
exit_gateway_node: DirectoryNode,
|
||||
socks5_args: Socks5Args,
|
||||
) -> Self {
|
||||
Self {
|
||||
entrypoint,
|
||||
@@ -110,6 +121,7 @@ impl Probe {
|
||||
exit_gateway_node: Some(exit_gateway_node),
|
||||
localnet_entry: None,
|
||||
localnet_exit: None,
|
||||
socks5_args,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +132,7 @@ impl Probe {
|
||||
exit: Option<TestedNodeDetails>,
|
||||
netstack_args: NetstackArgs,
|
||||
credentials_args: CredentialArgs,
|
||||
socks5_args: Socks5Args,
|
||||
) -> Self {
|
||||
let entrypoint = entry.identity;
|
||||
Self {
|
||||
@@ -132,6 +145,7 @@ impl Probe {
|
||||
exit_gateway_node: None,
|
||||
localnet_entry: Some(entry),
|
||||
localnet_exit: exit,
|
||||
socks5_args,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +164,7 @@ impl Probe {
|
||||
only_lp_registration: bool,
|
||||
test_lp_wg: bool,
|
||||
min_mixnet_performance: Option<u8>,
|
||||
network_details: NymNetworkDetails,
|
||||
) -> anyhow::Result<ProbeResult> {
|
||||
let tickets_materials = self.credentials_args.decode_attached_ticket_materials()?;
|
||||
|
||||
@@ -161,7 +176,7 @@ impl Probe {
|
||||
// Connect to the mixnet via the entry gateway
|
||||
let disconnected_mixnet_client = MixnetClientBuilder::new_with_storage(storage.clone())
|
||||
.request_gateway(mixnet_entry_gateway_id.to_string())
|
||||
.network_details(NymNetworkDetails::new_from_env())
|
||||
.network_details(network_details.clone())
|
||||
.debug_config(helpers::mixnet_debug_config(
|
||||
min_mixnet_performance,
|
||||
ignore_egress_epoch_role,
|
||||
@@ -176,6 +191,15 @@ impl Probe {
|
||||
|
||||
let mixnet_client = Box::pin(disconnected_mixnet_client.connect_to_mixnet()).await;
|
||||
|
||||
// Extract topology from the connected client (if successful) to reuse for SOCKS5 test
|
||||
let topology = match &mixnet_client {
|
||||
Ok(client) => client
|
||||
.read_current_route_provider()
|
||||
.await
|
||||
.map(|rp| rp.topology.clone()),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
// Convert legacy flags to TestMode
|
||||
let has_exit = self.exit_gateway_node.is_some() || self.localnet_exit.is_some();
|
||||
let test_mode =
|
||||
@@ -192,6 +216,8 @@ impl Probe {
|
||||
test_mode,
|
||||
only_wireguard,
|
||||
false, // Not using mock ecash in regular probe mode
|
||||
network_details,
|
||||
topology,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -209,6 +235,7 @@ impl Probe {
|
||||
test_lp_wg: bool,
|
||||
min_mixnet_performance: Option<u8>,
|
||||
use_mock_ecash: bool,
|
||||
network_details: NymNetworkDetails,
|
||||
) -> anyhow::Result<ProbeResult> {
|
||||
// Localnet mode - identity + LP address from CLI, no HTTP query
|
||||
// This path is used when --entry-gateway-identity is specified
|
||||
@@ -249,6 +276,8 @@ impl Probe {
|
||||
test_mode,
|
||||
only_wireguard,
|
||||
use_mock_ecash,
|
||||
network_details,
|
||||
None, // No topology (no mixnet client in localnet mode)
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -296,6 +325,8 @@ impl Probe {
|
||||
test_mode,
|
||||
only_wireguard,
|
||||
use_mock_ecash,
|
||||
network_details,
|
||||
None, // No topology (no mixnet client in direct gateway mode)
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -328,7 +359,7 @@ impl Probe {
|
||||
// and keeps its bandwidth between probe runs
|
||||
let disconnected_mixnet_client = MixnetClientBuilder::new_with_storage(storage.clone())
|
||||
.request_gateway(mixnet_entry_gateway_id.to_string())
|
||||
.network_details(NymNetworkDetails::new_from_env())
|
||||
.network_details(network_details.clone())
|
||||
.debug_config(helpers::mixnet_debug_config(
|
||||
min_mixnet_performance,
|
||||
ignore_egress_epoch_role,
|
||||
@@ -371,6 +402,15 @@ impl Probe {
|
||||
|
||||
let mixnet_client = Box::pin(disconnected_mixnet_client.connect_to_mixnet()).await;
|
||||
|
||||
// extract topology from the connected client (if any) to reuse for SOCKS5 test
|
||||
let topology = match &mixnet_client {
|
||||
Ok(client) => client
|
||||
.read_current_route_provider()
|
||||
.await
|
||||
.map(|rp| rp.topology.clone()),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
// Convert legacy flags to TestMode
|
||||
let has_exit = self.exit_gateway_node.is_some() || self.localnet_exit.is_some();
|
||||
let test_mode =
|
||||
@@ -387,6 +427,8 @@ impl Probe {
|
||||
test_mode,
|
||||
only_wireguard,
|
||||
use_mock_ecash,
|
||||
network_details,
|
||||
topology,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -458,11 +500,44 @@ impl Probe {
|
||||
Some(Exit::fail_to_connect())
|
||||
},
|
||||
wg: None,
|
||||
socks5: None,
|
||||
lp: Some(lp_outcome),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async fn test_socks5_if_possible(
|
||||
&self,
|
||||
network_details: NymNetworkDetails,
|
||||
network_requester_details: &Option<NetworkRequesterDetailsV1>,
|
||||
directory: &NymApiDirectory,
|
||||
topology: Option<NymTopology>,
|
||||
) -> Option<Socks5ProbeResults> {
|
||||
if let Some(nr_details) = network_requester_details {
|
||||
match do_socks5_connectivity_test(
|
||||
&nr_details.address,
|
||||
network_details,
|
||||
directory,
|
||||
self.socks5_args.socks5_json_rpc_url_list.clone(),
|
||||
self.socks5_args.mixnet_client_timeout_sec,
|
||||
self.socks5_args.test_count,
|
||||
self.socks5_args.failure_count_cutoff,
|
||||
topology,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(results) => Some(results),
|
||||
Err(e) => {
|
||||
error!("SOCKS5 test failed: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("No NR available, skipping SOCKS5 tests");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn lookup_gateway(
|
||||
&self,
|
||||
directory: &Option<NymApiDirectory>,
|
||||
@@ -535,11 +610,16 @@ impl Probe {
|
||||
test_mode: TestMode,
|
||||
only_wireguard: bool,
|
||||
use_mock_ecash: bool,
|
||||
network_details: NymNetworkDetails,
|
||||
topology: Option<NymTopology>,
|
||||
) -> anyhow::Result<ProbeResult>
|
||||
where
|
||||
T: MixnetClientStorage + Clone + 'static,
|
||||
<T::CredentialStore as CredentialStorage>::StorageError: Send + Sync,
|
||||
{
|
||||
let Some(directory) = directory else {
|
||||
bail!("You need to provide NYM API through environment")
|
||||
};
|
||||
// test_mode replaces the old only_lp_registration and test_lp_wg flags.
|
||||
// only_wireguard is kept separate as it controls ping behavior within Mixnet mode.
|
||||
let mut rng = rand::thread_rng();
|
||||
@@ -557,6 +637,7 @@ impl Probe {
|
||||
Entry::EntryFailure
|
||||
},
|
||||
as_exit: None,
|
||||
socks5: None,
|
||||
wg: None,
|
||||
lp: None,
|
||||
},
|
||||
@@ -595,6 +676,7 @@ impl Probe {
|
||||
Entry::NotTested
|
||||
},
|
||||
as_exit: None,
|
||||
socks5: None,
|
||||
wg: None,
|
||||
lp: None,
|
||||
}),
|
||||
@@ -609,6 +691,7 @@ impl Probe {
|
||||
Ok(ProbeOutcome {
|
||||
as_entry: Entry::NotTested,
|
||||
as_exit: None,
|
||||
socks5: None,
|
||||
wg: None,
|
||||
lp: None,
|
||||
}),
|
||||
@@ -624,6 +707,7 @@ impl Probe {
|
||||
Entry::EntryFailure
|
||||
},
|
||||
as_exit: None,
|
||||
socks5: None,
|
||||
wg: None,
|
||||
lp: None,
|
||||
}),
|
||||
@@ -681,8 +765,6 @@ impl Probe {
|
||||
// The tested node is the exit
|
||||
let exit_gateway = node_info.clone();
|
||||
|
||||
let directory = directory
|
||||
.ok_or_else(|| anyhow::anyhow!("Directory is required for LP-WG test mode"))?;
|
||||
let entry_gateway_node = directory.entry_gateway(&mixnet_entry_gateway_id)?;
|
||||
let entry_gateway = entry_gateway_node.to_testable_node()?;
|
||||
|
||||
@@ -723,9 +805,8 @@ impl Probe {
|
||||
Arc::new(KeyPair::new(&mut rng)),
|
||||
ip_address,
|
||||
);
|
||||
let config = nym_validator_client::nyxd::Config::try_from_nym_network_details(
|
||||
&NymNetworkDetails::new_from_env(),
|
||||
)?;
|
||||
let config =
|
||||
nym_validator_client::nyxd::Config::try_from_nym_network_details(&network_details)?;
|
||||
let client =
|
||||
nym_validator_client::nyxd::NyxdClient::connect(config, nyxd_url.as_str())?;
|
||||
let bw_controller = nym_bandwidth_controller::BandwidthController::new(
|
||||
@@ -790,10 +871,21 @@ impl Probe {
|
||||
None
|
||||
};
|
||||
|
||||
// test failure doesn't stop further tests
|
||||
let socks5_outcome = self
|
||||
.test_socks5_if_possible(
|
||||
network_details,
|
||||
&node_info.network_requester_details,
|
||||
directory,
|
||||
topology,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Disconnect the mixnet client gracefully
|
||||
outcome.map(|mut outcome| {
|
||||
outcome.wg = Some(wg_outcome);
|
||||
outcome.lp = lp_outcome;
|
||||
outcome.socks5 = socks5_outcome;
|
||||
ProbeResult {
|
||||
node: node_info.identity.to_string(),
|
||||
used_entry: mixnet_entry_gateway_id.to_string(),
|
||||
|
||||
@@ -6,6 +6,7 @@ use clap::{Parser, Subcommand};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_config::defaults::setup_env;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_gateway_probe::config::Socks5Args;
|
||||
use nym_gateway_probe::{
|
||||
CredentialArgs, NetstackArgs, NymApiDirectory, ProbeResult, TestMode, TestedNode,
|
||||
TestedNodeDetails, TestedNodeLpDetails, query_gateway_by_ip,
|
||||
@@ -172,6 +173,10 @@ struct CliArgs {
|
||||
/// Arguments to manage credentials
|
||||
#[command(flatten)]
|
||||
credential_args: CredentialArgs,
|
||||
|
||||
/// Arguments to configure socks5 probe
|
||||
#[command(flatten)]
|
||||
socks5_args: Socks5Args,
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG_DIR: &str = "/tmp/nym-gateway-probe/config/";
|
||||
@@ -263,6 +268,8 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
.map(|ep| ep.nyxd_url())
|
||||
.ok_or(anyhow::anyhow!("missing nyxd url"))?;
|
||||
|
||||
args.socks5_args.validate_socks5_endpoints().await?;
|
||||
|
||||
// Three resolution modes in priority order:
|
||||
// 1. Localnet mode: --entry-gateway-identity provided (no HTTP query)
|
||||
// 2. Direct IP mode: --gateway-ip provided (queries HTTP API)
|
||||
@@ -381,6 +388,7 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
exit_details,
|
||||
args.netstack_args,
|
||||
args.credential_args,
|
||||
args.socks5_args,
|
||||
);
|
||||
|
||||
if let Some(awg_args) = args.amnezia_args {
|
||||
@@ -414,6 +422,7 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
test_lp_wg,
|
||||
args.min_gateway_mixnet_performance,
|
||||
*use_mock_ecash,
|
||||
network,
|
||||
))
|
||||
.await
|
||||
}
|
||||
@@ -426,6 +435,7 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
only_lp_registration,
|
||||
test_lp_wg,
|
||||
args.min_gateway_mixnet_performance,
|
||||
network,
|
||||
))
|
||||
.await
|
||||
}
|
||||
@@ -515,6 +525,7 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
args.credential_args,
|
||||
entry_node.clone(),
|
||||
exit_node.clone(),
|
||||
args.socks5_args,
|
||||
)
|
||||
} else if let Some(gw_node) = gateway_node {
|
||||
// Only entry gateway provided
|
||||
@@ -524,17 +535,24 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
args.netstack_args,
|
||||
args.credential_args,
|
||||
gw_node,
|
||||
args.socks5_args,
|
||||
)
|
||||
} else {
|
||||
// No direct gateways, use directory lookup
|
||||
nym_gateway_probe::Probe::new(entry, test_point, args.netstack_args, args.credential_args)
|
||||
nym_gateway_probe::Probe::new(
|
||||
entry,
|
||||
test_point,
|
||||
args.netstack_args,
|
||||
args.credential_args,
|
||||
args.socks5_args,
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(awg_args) = args.amnezia_args {
|
||||
trial.with_amnezia(&awg_args);
|
||||
}
|
||||
|
||||
match &args.command {
|
||||
match args.command {
|
||||
Some(Commands::RunLocal {
|
||||
mnemonic,
|
||||
config_dir,
|
||||
@@ -559,7 +577,8 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
only_lp_registration,
|
||||
test_lp_wg,
|
||||
args.min_gateway_mixnet_performance,
|
||||
*use_mock_ecash,
|
||||
use_mock_ecash,
|
||||
network,
|
||||
))
|
||||
.await
|
||||
}
|
||||
@@ -572,6 +591,7 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
only_lp_registration,
|
||||
test_lp_wg,
|
||||
args.min_gateway_mixnet_performance,
|
||||
network,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node-status-agent"
|
||||
version = "1.0.7"
|
||||
version = "1.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
# used primarily for local testing
|
||||
|
||||
set -eu
|
||||
export ENVIRONMENT=${ENVIRONMENT:-"mainnet"}
|
||||
|
||||
probe_git_ref="nym-vpn-core-v1.4.0"
|
||||
|
||||
crate_root=$(dirname $(realpath "$0"))
|
||||
echo crate_root=${crate_root}
|
||||
monorepo_root=$(realpath "${crate_root}/../..")
|
||||
echo monorepo_root=${monorepo_root}
|
||||
|
||||
echo "Expecting nym-vpn-client repo at a sibling level of nym monorepo dir"
|
||||
gateway_probe_src=$(dirname "${monorepo_root}")/nym-vpn-client/nym-vpn-core
|
||||
gateway_probe_src="${monorepo_root}/nym-gateway-probe"
|
||||
echo "gateway_probe_src=$gateway_probe_src"
|
||||
|
||||
set -a
|
||||
@@ -25,7 +26,8 @@ export RUST_LOG="info"
|
||||
NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1"
|
||||
NODE_STATUS_AGENT_SERVER_PORT="8000"
|
||||
SERVER="${NODE_STATUS_AGENT_SERVER_ADDRESS}|${NODE_STATUS_AGENT_SERVER_PORT}"
|
||||
export NODE_STATUS_AGENT_AUTH_KEY="BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT"
|
||||
# hardcoded key used only for LOCAL TESTING
|
||||
export NODE_STATUS_AGENT_AUTH_KEY=${NODE_STATUS_AGENT_AUTH_KEY_STAGING:-"BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT"}
|
||||
export NODE_STATUS_AGENT_PROBE_PATH="$crate_root/nym-gateway-probe"
|
||||
export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1"
|
||||
|
||||
@@ -35,11 +37,9 @@ echo "Running $workers workers in parallel"
|
||||
# build & copy over GW probe
|
||||
function copy_gw_probe() {
|
||||
pushd $gateway_probe_src
|
||||
git fetch -a
|
||||
git checkout $probe_git_ref
|
||||
|
||||
cargo build --release --package nym-gateway-probe
|
||||
cp target/release/nym-gateway-probe "$crate_root"
|
||||
cp "${monorepo_root}/target/release/nym-gateway-probe" "$crate_root"
|
||||
$crate_root/nym-gateway-probe --version
|
||||
|
||||
popd
|
||||
|
||||
@@ -601,6 +601,13 @@ where
|
||||
);
|
||||
|
||||
let available_gateways = self.available_gateways().await?;
|
||||
for node in available_gateways.iter() {
|
||||
debug!(
|
||||
"node_id={}, identity_key={}",
|
||||
node.node_id,
|
||||
node.identity_key.to_base58_string()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
|
||||
Reference in New Issue
Block a user