From af9f6e5ca0761a057f49f8afed4a0d9311233616 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Tue, 22 Jul 2025 15:25:43 +0200 Subject: [PATCH] Allow PG database backend (#5880) * feat(db): add SQL query wrapper for PostgreSQL placeholder conversion - Created query_wrapper module with functions to automatically convert SQLite ? placeholders to PostgreSQL $1, $2, ... format - Updated build.rs to handle mutually exclusive feature flags - Modified one query in mixnodes.rs as proof of concept - Added type conversions for PostgreSQL compatibility (u32->i64, u16->i32) This is a checkpoint commit before converting all queries to use the wrapper. * feat(nym-node-status-api): add PostgreSQL database support via feature flags Implement dual database support for SQLite and PostgreSQL through Cargo feature flags. The implementation uses a query wrapper that automatically converts SQLite-style ? placeholders to PostgreSQL-style $1, $2, ... placeholders at runtime. Key changes: - Add query wrapper functions that handle placeholder conversion - Convert all sqlx::query\! macros to use wrapper functions - Handle type conversions between databases (i64 vs i32) - Add feature-gated implementations for database-specific SQL syntax - Update Makefile with clippy targets for both database features - Document database support in README * feat(nym-node-status-agent): add multi-API support with random selection Agents can now connect to multiple APIs and randomly select one for each testrun: - Accept multiple --server arguments in format "address:port:auth_key" - Randomly shuffle server list before attempting connections - Try each server until a testrun is obtained - Submit results back only to the API that provided the testrun - Continue to next server if one is down or has no testruns available * feat(nym-node-status): implement primary/secondary server architecture - Agent now requests testruns only from primary server (first in list) - Results are submitted to all configured servers in parallel - Secondary servers accept external testruns via new v2 endpoint - Added auto-creation of gateway and testrun records on secondary servers - New database queries: get_or_create_gateway, insert_external_testrun - Client library enhanced with submit_results_with_context method * Bump Node status API version * Fix build workdir * Bump to 3.1.4 * Fix types and queries * 3.1.6 * Fix gateway perf, bump 3.1.7 * NodeId -> i32, 3.1.8 * Bump agent version * i64 -> i32 * Use image yq * Migration and more types * Update remaining JSONB columns * Simplify server config * Update build path * Change delimiter * bump agent * Split up pg and sqlite builds * More typing fixes, build-and-push script * Fix Dockerfile-pg * Bump node-status-api * TYping * Agent build script * More logging around testruns * Fail loudly on read errors * Cleanup * Debug get gateways query * Fix get_gateways query * Use pg cert, 3.1.16 * Submit regular results to primary server * Bump freshenss cutoff * Update Cargo.lock * fix: resolve rebase conflicts and compilation errors After rebasing onto develop, fixed several issues: - Fixed borrowed data escapes error by using sqlx::query directly in transaction functions - Removed unused imports and cleaned up code - Maintained database-specific implementations for transaction functions * fmt * Make PG default to make lives easier * Performance improvements for Explorer v2 * Fix sqlite build * Fix PG migration * Tests round 1 * DB tests * More tests * And some more tests * And some more, more tests * cargo fmt * Fix some failing lints * Fix lioness version problems * Clippy in tests --------- Co-authored-by: dynco-nym <173912580+dynco-nym@users.noreply.github.com> --- .github/workflows/push-node-status-agent.yaml | 5 +- .github/workflows/push-node-status-api.yaml | 5 +- CLAUDE.md | 686 ++++++ Cargo.lock | 1954 +++++++++-------- nym-node-status-api/README.md | 58 + .../nym-node-status-agent/Cargo.toml | 22 +- .../nym-node-status-agent/Dockerfile | 2 +- .../build-push-node-status-agent.sh | 71 + .../nym-node-status-agent/src/cli/mod.rs | 78 +- .../src/cli/run_probe.rs | 151 +- .../nym-node-status-api/.env.example | 32 + ...d614a89d88adf02358800433e06100c13c548.json | 12 - ...906b42a7e4b280c8efb32db15d7c6a51d7a27.json | 32 - ...1af626ae15728d615f539470cd7c189312385.json | 12 - ...55c69cd5c1a7e7d87073c94600c783a0a3984.json | 20 - ...85dab12730e538be194438f19ed7f198bd50e.json | 12 - ...c46bdbd162c7c689f74fe9ddfdfd48f5ddc07.json | 34 +- ...7dc549cf503409fd6dfedcdc02dbcd61d5454.json | 32 - ...81df3e591008a1e3fd664ed83ca86ac51bd8c.json | 12 - ...5ceb89b9925cba46efcf4ed79ef0759a01129.json | 26 - ...5d1bcd636315ab23537cb5f6d2f82a2bcb7bf.json | 26 - ...2f49ba5db84d63e89440bb494e8327fe73686.json | 20 - ...c7ad6e68c81932d295ff18ed198c54706a57c.json | 86 - ...0505d0dbcc62703f40e090e80ff48c77723c4.json | 12 - ...811e842090e98982e1032670df77961870b32.json | 12 - ...7708c9f01047b0689eb0d4f7a973b328c609d.json | 38 - ...4b9a979f4bdb11ea81b2d0f022555bab51ecb.json | 92 - ...1cc0c100a1d7a7c347393adde2410fa6f4dfe.json | 20 - ...203c8569f03fdd39ce09d7b74177896e52a8c.json | 12 - ...570ee787fc7daf00fcb916f18d1cb7d6c8f08.json | 12 - ...dcdebd4969e1f240def9fb1bf946f4a1f342f.json | 12 - ...af481a837eda4f3e1bee1d430a4eb102a5b3d.json | 22 + ...25b31715c2098310fe7a9db688bc2fd36aad4.json | 12 - ...989e199aac4d4a71913c7074359b4bd676b02.json | 26 - ...6b326b31c93298f20019772cce2e277a194f8.json | 12 - ...a667e8cacdf677ccb906415b3fe92be0c436b.json | 10 +- ...56679d5e28f95143f4e2a2b409009dc0f55ba.json | 12 - ...3ac5ebe44a398b199ab0120207a5569f54d0b.json | 26 + ...f3de5949a5ff2083453cb879af8a1689efe2f.json | 12 - ...36a2b964b981c8cbcc282117fe9bc38338dd.json} | 60 +- ...03a2752e28df775d1af8fd943a5fc8d7dc00d.json | 38 - ...1c74c8cf1c08e5e2896a1d6d5b85c91991753.json | 32 - ...6c5e41f3c0bb56a27648b5e25466b8634a578.json | 12 - ...86b86d1b9be62336c9eac0eef44987a5451b5.json | 10 +- ...ffc8ed1011f56714fde6007e50951e569854b.json | 32 - ...90f6c66c14d4267a2cc2ca73354becc2c8bb8.json | 12 +- ...222615bb282e5334665700709ae475a5daea2.json | 86 - ...c39da94025494395ec7b093aefef696f2d0c5.json | 12 - ...9295c01a78861a2a0597ad28b1579a14bf008.json | 56 - ...ccd585be0641e5878acb6283b879f22ed2b4c.json | 20 - ...16610e15be2d7ee3f6c4d3debf23f95fb9c2e.json | 44 - ...69682c4ae96f774261f5c298264d3c12e5b67.json | 12 - ...c586c4c29c03efb4cf0c40b73a5c76159cf5c.json | 12 - ...64741aefc9f7a7d1630f6b863ebd8174b6684.json | 12 - ...68bd5438d6c8496ca9cbdcfb70bb5375b345e.json | 26 - ...bde9ec6159c9fa03b13652e1f620dcd92125e.json | 20 - ...3b216b93440d0fddc0a37dd1b6c1813741f6a.json | 12 - ...432c39d761f2a0ac1d6515cf73416f2eb6c61.json | 20 - ...74a5bf4912ee73468e62e7d0d96b1d9074cbe.json | 12 - ...c288364855003cf0e4d63e95047e7b502c650.json | 12 - ...e6720c9a9fc929144188460849be85d915004.json | 56 - ...29f8758ec922dd2e7ea064a1e537e580c9ee5.json | 12 - ...a811588c2bbc50d4975487a0464321a1b18c9.json | 12 - .../nym-node-status-api/Cargo.toml | 20 +- .../{Dockerfile => Dockerfile-pg} | 4 +- .../nym-node-status-api/Dockerfile-sqlite | 37 + .../nym-node-status-api/Makefile | 117 + .../nym-node-status-api/README_PG.md | 104 + .../build-push-node-status-api.sh | 81 + .../nym-node-status-api/build.rs | 26 +- .../nym-node-status-api/docker-compose.yml | 21 + .../migrations/008_performance_indexes.sql | 16 + .../migrations_pg/20240101000000_init.sql | 113 + .../20240101000001_last_assigned_utc.sql | 5 + .../20240101000002_session_stats.sql | 16 + .../20240101000003_scraper_tables.sql | 11 + .../20240101000004_obsolete_fields.sql | 54 + .../20240101000005_node_self_described.sql | 23 + ...0240101000006_remove_unique_constraint.sql | 9 + .../migrations_pg/20240101000007_date_fix.sql | 112 + .../20240101000008_jsonb_columns.sql | 3 + .../20240101000009_more_jsonb_columns.sql | 7 + ...101000010_performance_indexes_postgres.sql | 17 + .../nym-node-status-api/src/db/mod.rs | 65 +- .../nym-node-status-api/src/db/models.rs | 47 +- .../src/db/queries/gateways.rs | 124 +- .../src/db/queries/gateways_stats.rs | 36 +- .../src/db/queries/misc.rs | 24 +- .../src/db/queries/mixnodes.rs | 64 +- .../nym-node-status-api/src/db/queries/mod.rs | 3 +- .../src/db/queries/nym_nodes.rs | 133 +- .../src/db/queries/packet_stats.rs | 267 ++- .../src/db/queries/scraper.rs | 25 +- .../src/db/queries/summary.rs | 22 +- .../src/db/queries/testruns.rs | 259 ++- .../src/db/query_wrapper.rs | 251 +++ .../nym-node-status-api/src/db/tests.rs | 461 ++++ .../src/http/api/dvpn/mod.rs | 33 + .../src/http/api/gateways.rs | 139 +- .../src/http/api/metrics/mod.rs | 12 + .../src/http/api/mixnodes.rs | 164 +- .../nym-node-status-api/src/http/api/mod.rs | 35 + .../src/http/api/nym_nodes.rs | 30 + .../src/http/api/services/json_path.rs | 84 + .../src/http/api/services/mod.rs | 332 ++- .../src/http/api/status.rs | 12 + .../src/http/api/summary.rs | 12 + .../src/http/api/testruns.rs | 180 +- .../nym-node-status-api/src/http/error.rs | 101 + .../nym-node-status-api/src/http/mod.rs | 173 ++ .../nym-node-status-api/src/http/models.rs | 236 ++ .../nym-node-status-api/src/http/state.rs | 113 +- .../nym-node-status-api/src/main.rs | 6 + .../src/metrics_scraper/error.rs | 121 + .../nym-node-status-api/src/monitor/mod.rs | 43 +- .../src/node_scraper/description.rs | 12 +- .../src/node_scraper/helpers.rs | 150 +- .../src/node_scraper/packet_stats.rs | 11 +- .../src/test_helpers/mod.rs | 176 ++ .../src/testruns/models.rs | 159 +- .../nym-node-status-api/src/testruns/queue.rs | 68 +- .../nym-node-status-api/src/utils.rs | 51 +- .../nym-node-status-client/Cargo.toml | 9 +- .../nym-node-status-client/src/api.rs | 7 + .../nym-node-status-client/src/lib.rs | 33 +- .../nym-node-status-client/src/models.rs | 37 +- nym-node/Cargo.toml | 18 +- nym-node/src/cli/commands/test_throughput.rs | 4 - nym-node/src/throughput_tester/mod.rs | 13 - 129 files changed, 6752 insertions(+), 2761 deletions(-) create mode 100644 CLAUDE.md create mode 100755 nym-node-status-api/nym-node-status-agent/build-push-node-status-agent.sh create mode 100644 nym-node-status-api/nym-node-status-api/.env.example delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-01ee4a30bc3104712e5bc371a45d614a89d88adf02358800433e06100c13c548.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-021c6c65d1ed806d8430bef7883906b42a7e4b280c8efb32db15d7c6a51d7a27.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-06065394c157927e4002ddd5c7c1af626ae15728d615f539470cd7c189312385.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-0cf0e4d4f30e90caecffd6255ef85dab12730e538be194438f19ed7f198bd50e.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-21e44766729777756f6eb04bf3b81df3e591008a1e3fd664ed83ca86ac51bd8c.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-227539374e7473f6f9642289c5b5d1bcd636315ab23537cb5f6d2f82a2bcb7bf.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-25300e435780101fa207c8e26ef2f49ba5db84d63e89440bb494e8327fe73686.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-283f49a65c7d70bf271702ff6a5c7ad6e68c81932d295ff18ed198c54706a57c.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-3243cf5646255a9430d1e6710970505d0dbcc62703f40e090e80ff48c77723c4.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-3cd5cb4bfca4243925da4ddbccd811e842090e98982e1032670df77961870b32.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-3e7e987780937873cdb393b157d7708c9f01047b0689eb0d4f7a973b328c609d.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-3eb1d8491bda3c1d6e071b6eb364b9a979f4bdb11ea81b2d0f022555bab51ecb.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-3fc2baabf194b147b20be2a49401cc0c100a1d7a7c347393adde2410fa6f4dfe.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-4d865e873c9cb133883da94db72dcdebd4969e1f240def9fb1bf946f4a1f342f.json create mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-4fca38abbb416d9457c34a8ba4faf481a837eda4f3e1bee1d430a4eb102a5b3d.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-5912ea335a957d217f5e2b3a63a25b31715c2098310fe7a9db688bc2fd36aad4.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-5e9cbb39f5fb53774803270f422989e199aac4d4a71913c7074359b4bd676b02.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-664e059ac2c58e1115fe214376a6b326b31c93298f20019772cce2e277a194f8.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-6a9780aad1f2f0c8ef780e51fe856679d5e28f95143f4e2a2b409009dc0f55ba.json create mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-6de15de62c0caa545910a17877a3ac5ebe44a398b199ab0120207a5569f54d0b.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f.json rename nym-node-status-api/nym-node-status-api/.sqlx/{query-f75af377da33db1455c6e0f612e0fa9583888f343b8b59faf37fc6799b244379.json => query-74b76cd0d94c1afc51c21c14c12236a2b964b981c8cbcc282117fe9bc38338dd.json} (61%) delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-786b6a5d954e38b204cebf322711c74c8cf1c08e5e2896a1d6d5b85c91991753.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-8bdf85a61e443fa5f4835bffd0bffc8ed1011f56714fde6007e50951e569854b.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-9334f0c91252fcd7ec72558a271222615bb282e5334665700709ae475a5daea2.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-9796d354ae075eab4cbd3438839c39da94025494395ec7b093aefef696f2d0c5.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-a1d9eb816acd1a91ed0975c801c9295c01a78861a2a0597ad28b1579a14bf008.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-a79a61b87325f3f1d9c5a4fb386ccd585be0641e5878acb6283b879f22ed2b4c.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-aa602604c0e7b6eef41ea3cd83c16610e15be2d7ee3f6c4d3debf23f95fb9c2e.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-b68796d1d8d2384b30f1aace06269682c4ae96f774261f5c298264d3c12e5b67.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-c42917c9542c1d720d92035863064741aefc9f7a7d1630f6b863ebd8174b6684.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-c7656b2b1b4328415772ce69d0568bd5438d6c8496ca9cbdcfb70bb5375b345e.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-c910788edefe64bbb34379702bcbde9ec6159c9fa03b13652e1f620dcd92125e.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-c9e61180ec35dfab8623333fafa3b216b93440d0fddc0a37dd1b6c1813741f6a.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-d2e07d44594ca5b44a6100482ff432c39d761f2a0ac1d6515cf73416f2eb6c61.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-e9790b63ebe4bff5172bb8cb7bfc288364855003cf0e4d63e95047e7b502c650.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-f0c64794cbaed87a1d3166251d8e6720c9a9fc929144188460849be85d915004.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-f7e3fa31d68c028bf39cc95389f29f8758ec922dd2e7ea064a1e537e580c9ee5.json delete mode 100644 nym-node-status-api/nym-node-status-api/.sqlx/query-fcb1698d9e0e3a14524c92e7c99a811588c2bbc50d4975487a0464321a1b18c9.json rename nym-node-status-api/nym-node-status-api/{Dockerfile => Dockerfile-pg} (90%) create mode 100644 nym-node-status-api/nym-node-status-api/Dockerfile-sqlite create mode 100644 nym-node-status-api/nym-node-status-api/Makefile create mode 100644 nym-node-status-api/nym-node-status-api/README_PG.md create mode 100755 nym-node-status-api/nym-node-status-api/build-push-node-status-api.sh create mode 100644 nym-node-status-api/nym-node-status-api/docker-compose.yml create mode 100644 nym-node-status-api/nym-node-status-api/migrations/008_performance_indexes.sql create mode 100644 nym-node-status-api/nym-node-status-api/migrations_pg/20240101000000_init.sql create mode 100644 nym-node-status-api/nym-node-status-api/migrations_pg/20240101000001_last_assigned_utc.sql create mode 100644 nym-node-status-api/nym-node-status-api/migrations_pg/20240101000002_session_stats.sql create mode 100644 nym-node-status-api/nym-node-status-api/migrations_pg/20240101000003_scraper_tables.sql create mode 100644 nym-node-status-api/nym-node-status-api/migrations_pg/20240101000004_obsolete_fields.sql create mode 100644 nym-node-status-api/nym-node-status-api/migrations_pg/20240101000005_node_self_described.sql create mode 100644 nym-node-status-api/nym-node-status-api/migrations_pg/20240101000006_remove_unique_constraint.sql create mode 100644 nym-node-status-api/nym-node-status-api/migrations_pg/20240101000007_date_fix.sql create mode 100644 nym-node-status-api/nym-node-status-api/migrations_pg/20240101000008_jsonb_columns.sql create mode 100644 nym-node-status-api/nym-node-status-api/migrations_pg/20240101000009_more_jsonb_columns.sql create mode 100644 nym-node-status-api/nym-node-status-api/migrations_pg/20240101000010_performance_indexes_postgres.sql create mode 100644 nym-node-status-api/nym-node-status-api/src/db/query_wrapper.rs create mode 100644 nym-node-status-api/nym-node-status-api/src/db/tests.rs create mode 100644 nym-node-status-api/nym-node-status-api/src/test_helpers/mod.rs diff --git a/.github/workflows/push-node-status-agent.yaml b/.github/workflows/push-node-status-agent.yaml index ccc7a60e1a..b2c156b1d9 100644 --- a/.github/workflows/push-node-status-agent.yaml +++ b/.github/workflows/push-node-status-agent.yaml @@ -38,10 +38,9 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.45.4 id: get_version - with: - cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml + run: | + yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml - name: cleanup-gateway-probe-ref id: cleanup_gateway_probe_ref diff --git a/.github/workflows/push-node-status-api.yaml b/.github/workflows/push-node-status-api.yaml index 8548841435..9087fda7aa 100644 --- a/.github/workflows/push-node-status-api.yaml +++ b/.github/workflows/push-node-status-api.yaml @@ -32,10 +32,9 @@ jobs: git config --global user.name "Lawrence Stalder" - name: Get version from cargo.toml - uses: mikefarah/yq@v4.45.4 id: get_version - with: - cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml + run: | + yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml - name: Set GIT_TAG variable run: echo "GIT_TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}" >> $GITHUB_ENV diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..573e3538ca --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,686 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Nym is a privacy platform that uses mixnet technology to protect against metadata surveillance. The platform consists of several key components: +- Mixnet nodes (mixnodes) for packet mixing +- Gateways (entry/exit points for the network) +- Clients for interacting with the network +- Network monitoring tools +- Validators for network consensus +- Various service providers and integrations + +## Build Commands + +### Rust Components + +```bash +# Default build (debug) +cargo build + +# Release build +cargo build --release + +# Build a specific package +cargo build -p + +# Build main components +make build + +# Build release versions of main binaries and contracts +make build-release + +# Build specific binaries +make build-nym-cli +cargo build -p nym-node --release +cargo build -p nym-api --release +``` + +### Testing + +```bash +# Run clippy, unit tests, and formatting +make test + +# Run all tests including slow tests +make test-all + +# Run clippy on all workspaces +make clippy + +# Run unit tests for a specific package +cargo test -p + +# Run only expensive/ignored tests +cargo test --workspace -- --ignored + +# Run API tests +dotenv -f envs/sandbox.env -- cargo test --test public-api-tests + +# Run tests with specific log level +RUST_LOG=debug cargo test -p + +# Run specific test scripts +./nym-node/tests/test_apis.sh +./scripts/wireguard-exit-policy/exit-policy-tests.sh +``` + +### Linting and Formatting + +```bash +# Run rustfmt on all code +make fmt + +# Check formatting without modifying +cargo fmt --all -- --check + +# Run clippy with all targets +cargo clippy --workspace --all-targets -- -D warnings + +# TypeScript linting +yarn lint +yarn lint:fix +yarn types:lint:fix + +# Check dependencies for security/licensing issues +cargo deny check +``` + +### WASM Components + +```bash +# Build all WASM components +make sdk-wasm-build + +# Build TypeScript SDK +yarn build:sdk +npx lerna run --scope @nymproject/sdk build --stream + +# Build and test WASM components +make sdk-wasm + +# Build specific WASM packages +cd wasm/client && make +cd wasm/mix-fetch && make +cd wasm/node-tester && make +``` + +### Contract Development + +```bash +# Build all contracts +make contracts + +# Build contracts in release mode +make build-release-contracts + +# Generate contract schemas +make contract-schema + +# Run wasm-opt on contracts +make wasm-opt-contracts + +# Check contracts with cosmwasm-check +make cosmwasm-check-contracts +``` + +### Running Components + +```bash +# Run nym-node as a mixnode +cargo run -p nym-node -- run --mode mixnode + +# Run nym-node as a gateway +cargo run -p nym-node -- run --mode gateway + +# Run the network monitor +cargo run -p nym-network-monitor + +# Run the API server +cargo run -p nym-api + +# Run with specific environment +dotenv -f envs/sandbox.env -- cargo run -p nym-api + +# Start a local network +./scripts/localnet_start.sh +``` + +## Architecture + +The Nym platform consists of various components organized as a monorepo: + +1. **Core Mixnet Infrastructure**: + - `nym-node`: Core binary supporting mixnode and gateway modes + - `common/nymsphinx`: Implementation of the Sphinx packet format + - `common/topology`: Network topology management + - `common/types`: Shared data types across components + +2. **Network Monitoring**: + - `nym-network-monitor`: Monitors the network's reliability and performance + - `nym-api`: API server for network stats and monitoring data + - Metrics tracking for nodes, routes, and overall network health + +3. **Client Implementations**: + - `clients/native`: Native Rust client implementation + - `clients/socks5`: SOCKS5 proxy client for standard applications + - `wasm`: WebAssembly client implementations (for browsers) + - `nym-connect`: Desktop and mobile clients + +4. **Blockchain & Smart Contracts**: + - `common/cosmwasm-smart-contracts`: Smart contract implementations + - `contracts`: CosmWasm contracts for the Nym network + - `common/ledger`: Blockchain integration + +5. **Utilities & Tools**: + - `tools`: Various CLI tools and utilities + - `sdk`: SDKs for different languages and platforms + - `documentation`: Documentation generation and management + +## Packet System + +Nym uses a modified Sphinx packet format for its mixnet: + +1. **Message Chunking**: + - Messages are divided into "sets" and "fragments" + - Each fragment fits in a single Sphinx packet + - The `common/nymsphinx/chunking` module handles message fragmentation + +2. **Routing**: + - Packets traverse through 3 layers of mixnodes + - Routing information is encrypted in layers (onion routing) + - The final gateway receives and processes the messages + +3. **Monitoring**: + - Monitoring system tracks packet delivery through the network + - Routes are analyzed for reliability statistics + - Node performance metrics are collected + +## Network Protocol + +Nym implements the Loopix mixnet design with several key privacy features: + +1. **Continuous-time Mixing**: + - Each mixnode delays messages independently with an exponential distribution + - This creates random reordering of packets, destroying timing correlations + - Offers better anonymity properties than batch mixing approaches + +2. **Cover Traffic**: + - Clients and nodes generate dummy "loop" packets that circulate through the network + - These packets are indistinguishable from real traffic + - Creates a baseline level of traffic that hides actual communication patterns + - Provides unobservability (hiding when and how much real traffic is being sent) + +3. **Stratified Network Architecture**: + - Traffic flows through Entry Gateway → 3 Mixnode Layers → Exit Gateway + - Path selection is independent per-message (unlike Tor) + - Each node connects only to adjacent layers + +4. **Anonymous Replies**: + - Single-Use Reply Blocks (SURBs) allow receiving messages without revealing identity + - Enables bidirectional communication while maintaining privacy + +## Network Monitoring Architecture + +The network monitoring system is a core component that measures mixnet reliability: + +1. The `nym-network-monitor` sends test packets through the network +2. These packets follow predefined routes through multiple mixnodes +3. Metrics are collected about: + - Successful and failed packet deliveries + - Node reliability (percentage of successful packet handling) + - Route reliability (which specific route combinations work best) +4. Results are stored in the database and used by `nym-api` to: + - Present node performance statistics + - Determine network rewards + - Provide route selection guidance to clients + +In the current branch, metrics collection is being enhanced with a fanout approach to submit to multiple API endpoints. + +## Development Environment + +### Required Dependencies + +- Rust toolchain (stable, 1.80+) +- Node.js (v20+) and yarn for TypeScript components +- SQLite for local database development +- PostgreSQL for API database (optional, for full API functionality) +- CosmWasm tools for contract development +- For building contracts: `wasm-opt` tool from `binaryen` +- Python 3.8+ for some scripts +- Docker (optional, for containerized development) +- protoc (Protocol Buffers compiler) for some components + +### Environment Configurations + +The `envs/` directory contains pre-configured environments: + +#### Available Environments + +- **`local.env`**: Local development environment + - Points to local services (localhost) + - Uses test mnemonics and keys + - Ideal for testing without external dependencies + +- **`sandbox.env`**: Sandbox test network + - Public test network with real nodes + - Test tokens available from faucet + - Contract addresses for sandbox deployment + - API: https://sandbox-nym-api1.nymtech.net + +- **`mainnet.env`**: Production mainnet + - Real network with real tokens + - Production contract addresses + - API: https://validator.nymtech.net + - Use with caution! + +- **`canary.env`**: Canary deployment + - Pre-release testing environment + - Tests new features before mainnet + +- **`mainnet-local-api.env`**: Hybrid environment + - Uses mainnet contracts but local API + - Useful for API development against mainnet data + +#### Key Environment Variables + +```bash +# Network configuration +NETWORK_NAME=sandbox # Network identifier +BECH32_PREFIX=n # Address prefix (n for sandbox, n for mainnet) +NYM_API=https://sandbox-nym-api1.nymtech.net/api +NYXD=https://rpc.sandbox.nymtech.net +NYM_API_NETWORK=sandbox + +# Contract addresses (network-specific) +MIXNET_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav +VESTING_CONTRACT_ADDRESS=n1unyuj8qnmygvzuex3dwmg9yzt9alhvyeat0uu0jedg2wj33efl5qackslz +# ... other contract addresses + +# Mnemonic for testing (NEVER use in production) +MNEMONIC="clutch captain shoe salt awake harvest setup primary inmate ugly among become" + +# API Keys and tokens +IPINFO_API_TOKEN=your_token_here +AUTHENTICATOR_PASSWORD=password_here + +# Logging +RUST_LOG=info # Options: error, warn, info, debug, trace +RUST_BACKTRACE=1 # Enable backtraces + +# Database +DATABASE_URL=postgresql://user:pass@localhost/nym_api +``` + +#### Using Environment Files + +```bash +# Load environment and run command +dotenv -f envs/sandbox.env -- cargo run -p nym-api + +# Export to shell +source envs/sandbox.env + +# Use with make targets +dotenv -f envs/sandbox.env -- make run-api-tests +``` + +## Initial Setup + +### First Time Setup + +1. **Install Prerequisites** + ```bash + # Install Rust + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + + # Install Node.js and yarn + # Via nvm (recommended): + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash + nvm install 20 + npm install -g yarn + + # Install build tools + # Ubuntu/Debian: + sudo apt-get install build-essential pkg-config libssl-dev protobuf-compiler libpq-dev + + # macOS: + brew install protobuf postgresql + + # Install wasm-opt for contract builds + npm install -g wasm-opt + + # Add wasm target for Rust + rustup target add wasm32-unknown-unknown + ``` + +2. **Clone and Setup Repository** + ```bash + git clone https://github.com/nymtech/nym.git + cd nym/nym + + # Install JavaScript dependencies + yarn install + + # Build the project + make build + ``` + +3. **Database Setup (Optional, for API development)** + ```bash + # Install PostgreSQL + # Create database + createdb nym_api + + # Run migrations (from nym-api directory) + cd nym-api + sqlx migrate run + ``` + +### Quick Start + +```bash +# Run a mixnode locally +dotenv -f envs/sandbox.env -- cargo run -p nym-node -- run --mode mixnode --id my-mixnode + +# Run a gateway locally +dotenv -f envs/sandbox.env -- cargo run -p nym-node -- run --mode gateway --id my-gateway + +# Run the API server +dotenv -f envs/sandbox.env -- cargo run -p nym-api + +# Run a client +cargo run -p nym-client -- init --id my-client +cargo run -p nym-client -- run --id my-client +``` + +## CI/CD Pipeline + +The project uses GitHub Actions for CI/CD with several key workflows: + +1. **Build and Test**: + - `ci-build.yml`: Main build workflow for Rust components + - Tests are run on multiple platforms (Linux, Windows, macOS) + - Includes formatting check (rustfmt) and linting (clippy) + +2. **Release Process**: + - Binary artifacts are published on release tags + - Multiple platform builds are created + +3. **Documentation**: + - Documentation is automatically built and deployed + +## Database Structure + +The system uses SQLite databases with tables like: +- `mixnode_status`: Status information about mixnodes +- `gateway_status`: Status information about gateways +- `routes`: Route performance information (success/failure of specific paths) +- `monitor_run`: Information about monitoring test runs + +## Development Workflows + +### Running a Node + +To run the mixnode or gateway: + +```bash +# Run nym-node as a mixnode with specified identity +cargo run -p nym-node -- run --mode mixnode --id my-mixnode + +# Run nym-node as a gateway +cargo run -p nym-node -- run --mode gateway --id my-gateway +``` + +### Configuration + +Nodes can be configured with files in various locations: +- Command-line arguments +- Environment variables +- `.env` files specified with `--config-env-file` + +### Monitoring + +To monitor the health of your node: +- View logs for real-time information +- Use the node's HTTP API for status information +- Check the explorer for public node statistics + +## Common Libraries + +- `common/types`: Shared data types across all components +- `common/crypto`: Cryptographic primitives and wrappers +- `common/client-core`: Core client functionality +- `common/gateway-client`: Client-gateway communication +- `common/task`: Task management and concurrency utilities +- `common/nymsphinx`: Sphinx packet implementation for mixnet +- `common/topology`: Network topology management +- `common/credentials`: Credential system for privacy-preserving authentication +- `common/bandwidth-controller`: Bandwidth management and accounting + +## Code Conventions + +- Error handling: Use anyhow/thiserror for structured error handling +- Logging: Use the tracing framework for logging and diagnostics +- State management: Generally use Tokio/futures for async code +- Configuration: Use the config crate and env vars with defaults +- Database: Use sqlx for type-safe database queries +- Follow clippy recommendations and rustfmt formatting +- Use semantic commit messages: feat, fix, docs, refactor, test, chore + +## When Making Changes + +- Run `make test` before submitting PRs +- Follow Rust naming conventions +- Use `clippy` to check for common issues +- Update SQLx query caches when modifying DB queries: `cargo sqlx prepare` +- Consider backward compatibility for protocol changes +- Use lefthook pre-commit hooks for TypeScript formatting +- Run `cargo deny check` to verify dependency compliance +- Test against both sandbox and local environments when possible +- Update relevant documentation and CHANGELOG.md + +## Development Tools + +### Useful Cargo Commands + +```bash +# Check for outdated dependencies +cargo outdated + +# Analyze binary size +cargo bloat --release -p nym-node + +# Generate dependency graph +cargo tree -p nym-api + +# Run with instrumentation +cargo run --features profiling -p nym-node + +# Check for security advisories +cargo audit +``` + +### Database Tools + +```bash +# SQLx CLI for migrations +cargo install sqlx-cli + +# Create new migration +cd nym-api && sqlx migrate add + +# Prepare query metadata for offline compilation +cargo sqlx prepare --workspace + +# View database schema +./nym-api/enter_db.sh +``` + +### Development Scripts + +- `scripts/build_topology.py`: Generate network topology files +- `scripts/node_api_check.py`: Verify node API endpoints +- `scripts/network_tunnel_manager.sh`: Manage network tunnels +- `scripts/localnet_start.sh`: Start a local test network +- Various deployment scripts in `deployment/` for different environments + +## Debugging + +- Enable more verbose logging with the RUST_LOG environment variable: + ``` + RUST_LOG=debug,nym_node=trace cargo run -p nym-node -- run --mode mixnode + ``` +- Use the HTTP API endpoints for status information +- Check monitoring data in the database for network performance metrics +- For complex issues, use tracing tools to follow packet flow +- Enable backtraces: `RUST_BACKTRACE=full` +- For WASM debugging: Use browser developer tools with source maps + +## Deployment and Advanced Configurations + +### Deployment Structure + +The `deployment/` directory contains Ansible playbooks and configurations for various deployment scenarios: + +- **`aws/`**: AWS-specific deployment configurations +- **`mixnode/`**: Mixnode deployment playbooks +- **`gateway/`**: Gateway deployment playbooks +- **`validator/`**: Validator node deployment +- **`sandbox-v2/`**: Complete sandbox environment setup +- **`big-dipper-2/`**: Block explorer deployment + +### Sandbox V2 Deployment + +The sandbox-v2 deployment (`deployment/sandbox-v2/`) provides a complete test environment: + +```bash +# Key playbooks: +- deploy.yaml # Main deployment orchestrator +- deploy-mixnodes.yaml # Deploy mixnodes +- deploy-gateways.yaml # Deploy gateways +- deploy-validators.yaml # Deploy validator nodes +- deploy-nym-api.yaml # Deploy API services +``` + +### Custom Environment Setup + +To create a custom environment: + +1. Copy an existing env file: `cp envs/sandbox.env envs/custom.env` +2. Modify the network endpoints and contract addresses +3. Update the `NETWORK_NAME` to your identifier +4. Set appropriate mnemonics and keys (use fresh ones for production!) + +### Contract Addresses + +Contract addresses are network-specific and defined in environment files: +- Mixnet contract: Manages mixnode/gateway registry +- Vesting contract: Handles token vesting schedules +- Coconut contracts: Privacy-preserving credentials +- Name service: Human-readable address mapping +- Ecash contract: Electronic cash functionality + +### Local Network Setup + +For a completely local network: +```bash +# Start local chain +./scripts/localnet_start.sh + +# Deploy contracts +cd contracts +make deploy-local + +# Start nodes with local config +dotenv -f envs/local.env -- cargo run -p nym-node -- run --mode mixnode +``` + +## Common Issues and Troubleshooting + +### Database Issues + +- When modifying database queries, you must update SQLx query caches: + ```bash + cargo sqlx prepare + ``` +- If you see SQLx errors about missing query files, this is likely the cause +- For "database is locked" errors with SQLite, ensure only one process accesses the DB +- For PostgreSQL connection issues, verify DATABASE_URL and that the server is running + +### API Connection Issues + +- Check the environment variables pointing to the APIs (NYM_API, NYXD) +- Verify network connectivity and API health endpoints +- For authentication issues, check node keys and credentials +- Common endpoints to verify: + - API health: `$NYM_API/health` + - Chain status: `$NYXD/status` + - Contract info: `$NYXD/cosmwasm/wasm/v1/contract/$CONTRACT_ADDRESS` + +### Build Problems + +- Clean dependencies with `cargo clean` for a fresh build +- Check for compatible Rust version (1.80+ recommended) +- For smart contract builds, ensure wasm-opt is installed: `npm install -g wasm-opt` +- For cross-compilation issues, check target-specific dependencies +- WASM build issues: Ensure wasm32-unknown-unknown target is installed: + ```bash + rustup target add wasm32-unknown-unknown + ``` +- For "cannot find -lpq" errors, install PostgreSQL development files: + ```bash + # Ubuntu/Debian + sudo apt-get install libpq-dev + # macOS + brew install postgresql + ``` + +### Environment Issues + +- Contract address mismatches: Ensure you're using the correct environment file +- "Account sequence mismatch": The account nonce is out of sync, wait and retry +- Token decimal issues: Sandbox uses different decimal places than mainnet +- API version mismatches: Ensure your local API version matches the network +- "Insufficient funds": Get test tokens from faucet (sandbox) or check balance +- Gateway/mixnode bonding issues: Verify minimum stake requirements + +## Working with Routes and Monitoring + +1. Route monitoring metrics are stored in a `routes` table with: + - Layer node IDs (layer1, layer2, layer3, gw) + - Success flag (boolean) + - Timestamp + +2. To analyze routes: + - Check `NetworkAccount` and `AccountingRoute` in `nym-network-monitor/src/accounting.rs` + - View monitoring logic in `common/nymsphinx/chunking/monitoring.rs` + - Observe how routes are submitted to the database in the `submit_accounting_routes_to_db` function + +## Performance Optimization + +### Profiling and Benchmarking + +```bash +# Run benchmarks +cargo bench -p nym-node + +# Profile with perf (Linux) +cargo build --release --features profiling +perf record --call-graph=dwarf ./target/release/nym-node run --mode mixnode +perf report + +# Generate flamegraph +cargo install flamegraph +cargo flamegraph --bin nym-node -- run --mode mixnode +``` + +### Common Performance Considerations + +- Use bounded channels for backpressure +- Batch database operations where possible +- Monitor memory usage with `RUST_LOG=nym_node::metrics=debug` +- Use connection pooling for database connections +- Consider using `jemalloc` for better memory allocation performance \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index ba482cdb2e..29286bbf5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,14 +4,14 @@ version = 3 [[package]] name = "accessory" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb3791c4beae5b827e93558ac83a88e63a841aad61759a05d9b577ef16030470" +checksum = "28e416a3ab45838bac2ab2d81b1088d738d7b2d2c5272a54d39366565a29bd80" dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -35,9 +35,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" @@ -91,14 +91,14 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -133,9 +133,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "ammonia" -version = "4.1.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ada2ee439075a3e70b6992fce18ac4e407cd05aea9ca3f75d2c0b0c20bbb364" +checksum = "d6b346764dd0814805de8abf899fe03065bcee69bb1a4771c785817e39f3978f" dependencies = [ "cssparser", "html5ever", @@ -167,9 +167,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -182,36 +182,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] @@ -408,7 +408,7 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -446,9 +446,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.18" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" +checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" dependencies = [ "brotli", "flate2", @@ -490,7 +490,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -501,7 +501,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -544,9 +544,9 @@ checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "autodoc" @@ -571,7 +571,7 @@ dependencies = [ "http-body 0.4.6", "hyper 0.14.32", "itoa", - "matchit", + "matchit 0.7.3", "memchr", "mime", "percent-encoding", @@ -601,7 +601,41 @@ dependencies = [ "hyper 1.6.0", "hyper-util", "itoa", - "matchit", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +dependencies = [ + "axum-core 0.5.2", + "bytes", + "form_urlencoded", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "itoa", + "matchit 0.8.4", "memchr", "mime", "percent-encoding", @@ -668,6 +702,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.2", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "axum-extra" version = "0.9.6" @@ -700,7 +754,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -714,7 +768,7 @@ dependencies = [ "auto-future", "axum 0.7.9", "bytes", - "bytesize", + "bytesize 1.3.3", "cookie", "http 1.3.1", "http-body-util", @@ -723,7 +777,37 @@ dependencies = [ "mime", "pretty_assertions", "reserve-port", - "rust-multipart-rfc7578_2", + "rust-multipart-rfc7578_2 0.6.1", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "tokio", + "tower 0.5.2", + "url", +] + +[[package]] +name = "axum-test" +version = "17.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb1dfb84bd48bad8e4aa1acb82ed24c2bb5e855b659959b4e03b4dca118fcac" +dependencies = [ + "anyhow", + "assert-json-diff", + "auto-future", + "axum 0.8.4", + "bytes", + "bytesize 2.0.1", + "cookie", + "http 1.3.1", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "mime", + "pretty_assertions", + "reserve-port", + "rust-multipart-rfc7578_2 0.8.0", "serde", "serde_json", "serde_urlencoded", @@ -735,9 +819,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -774,9 +858,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "base85rs" @@ -786,9 +870,9 @@ checksum = "87678d33a2af71f019ed11f52db246ca6c5557edee2cccbe689676d1ad9c6b5a" [[package]] name = "basic-toml" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" dependencies = [ "serde", ] @@ -827,9 +911,9 @@ dependencies = [ [[package]] name = "bip39" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" +checksum = "43d193de1f7487df1914d3a568b772458861d33f9c54249612cc2893d6915054" dependencies = [ "bitcoin_hashes", "rand 0.8.5", @@ -863,9 +947,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -905,9 +989,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.7.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17679a8d69b6d7fd9cd9801a536cec9fa5e5970b69f9d4747f70b39b031f5e7" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" dependencies = [ "arrayref", "arrayvec", @@ -950,7 +1034,7 @@ version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f6d7f06817e48ea4e17532fa61bc4e8b9a101437f0623f69d2ea54284f3a817" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "siphasher 1.0.1", ] @@ -978,9 +1062,9 @@ checksum = "3e31ea183f6ee62ac8b8a8cf7feddd766317adfb13ff469de57ce033efd6a790" [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -989,9 +1073,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1009,9 +1093,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byte-tools" @@ -1046,15 +1130,21 @@ dependencies = [ [[package]] name = "bytesize" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2c12f985c78475a6b8d629afd0c360260ef34cfef52efccdcfd31972f81c2e" +checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" + +[[package]] +name = "bytesize" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c8f83209414aacf0eeae3cf730b18d6981697fba62f200fcfb92b9f082acba" [[package]] name = "camino" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" dependencies = [ "serde", ] @@ -1110,9 +1200,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.2.14" +version = "1.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" dependencies = [ "jobserver", "libc", @@ -1131,9 +1221,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -1230,9 +1320,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.38" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" dependencies = [ "clap_builder", "clap_derive", @@ -1240,9 +1330,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.38" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", @@ -1252,9 +1342,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.50" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91d3baa3bcd889d60e6ef28874126a0b384fd225ab83aa6d8a801c519194ce1" +checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a" dependencies = [ "clap", ] @@ -1271,27 +1361,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" @@ -1311,7 +1401,7 @@ checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" dependencies = [ "crossterm 0.28.1", "unicode-segmentation", - "unicode-width 0.2.0", + "unicode-width 0.2.1", ] [[package]] @@ -1332,10 +1422,23 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.0", + "unicode-width 0.2.1", "windows-sys 0.59.0", ] +[[package]] +name = "console" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.1", + "windows-sys 0.60.2", +] + [[package]] name = "console-api" version = "0.5.0" @@ -1515,7 +1618,7 @@ checksum = "a782b93fae93e57ca8ad3e9e994e784583f5933aeaaa5c80a545c4b437be2047" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -1525,7 +1628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6984ab21b47a096e17ae4c73cea2123a704d4b6686c39421247ad67020d76f95" dependencies = [ "cosmwasm-schema-derive", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "thiserror 1.0.69", @@ -1539,7 +1642,7 @@ checksum = "e01c9214319017f6ebd8e299036e1f717fa9bb6724e758f7d6fb2477599d1a29" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -1558,7 +1661,7 @@ dependencies = [ "hex", "rand_core 0.6.4", "rmp-serde", - "schemars", + "schemars 0.8.22", "serde", "serde-json-wasm", "sha2 0.10.9", @@ -1577,9 +1680,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] @@ -1592,9 +1695,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -1708,7 +1811,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "crossterm_winapi", "parking_lot", "rustix 0.38.44", @@ -1726,9 +1829,9 @@ dependencies = [ [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" @@ -1783,7 +1886,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -1828,9 +1931,9 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.47" +version = "0.4.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fb4d13a1be2b58f14d60adba57c9834b78c62fd86c3e76a148f732686e9265" +checksum = "9e2d5c8f48d9c0c23250e52b55e82a6ab4fdba6650c931f5a0a57a43abda812b" dependencies = [ "curl-sys", "libc", @@ -1838,14 +1941,14 @@ dependencies = [ "openssl-sys", "schannel", "socket2", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "curl-sys" -version = "0.4.80+curl-8.12.1" +version = "0.4.82+curl-8.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55f7df2eac63200c3ab25bde3b2268ef2ee56af3d238e76d61f01c3c49bff734" +checksum = "c4d63638b5ec65f1a4ae945287b3fd035be4554bbaf211901159c9a2a74fb5be" dependencies = [ "cc", "libc", @@ -1853,7 +1956,7 @@ dependencies = [ "openssl-sys", "pkg-config", "vcpkg", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1881,7 +1984,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -1907,7 +2010,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus", "cw-utils", - "schemars", + "schemars 0.8.22", "serde", "thiserror 1.0.69", ] @@ -1926,7 +2029,7 @@ dependencies = [ "cw-utils", "itertools 0.14.0", "prost 0.13.5", - "schemars", + "schemars 0.8.22", "serde", "sha2 0.10.9", "thiserror 2.0.12", @@ -1939,7 +2042,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f13360e9007f51998d42b1bc6b7fa0141f74feae61ed5fd1e5b0a89eec7b5de1" dependencies = [ "cosmwasm-std", - "schemars", + "schemars 0.8.22", "serde", ] @@ -1951,7 +2054,7 @@ checksum = "07dfee7f12f802431a856984a32bce1cb7da1e6c006b5409e3981035ce562dec" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "schemars", + "schemars 0.8.22", "serde", "thiserror 1.0.69", ] @@ -1965,7 +2068,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", - "schemars", + "schemars 0.8.22", "semver 1.0.26", "serde", "thiserror 1.0.69", @@ -1980,7 +2083,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-utils", - "schemars", + "schemars 0.8.22", "serde", ] @@ -1994,7 +2097,7 @@ dependencies = [ "cosmwasm-std", "cw-utils", "cw20", - "schemars", + "schemars 0.8.22", "serde", "thiserror 1.0.69", ] @@ -2008,15 +2111,15 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", - "schemars", + "schemars 0.8.22", "serde", ] [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -2024,27 +2127,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -2063,9 +2166,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "defguard_wireguard_rs" @@ -2097,14 +2200,14 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", @@ -2140,7 +2243,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -2169,7 +2272,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "unicode-xid", ] @@ -2181,7 +2284,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "unicode-xid", ] @@ -2250,7 +2353,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -2299,9 +2402,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" [[package]] name = "easy-addr" @@ -2378,9 +2481,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", "ed25519", @@ -2408,9 +2511,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ "serde", ] @@ -2459,7 +2562,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -2502,12 +2605,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2549,9 +2652,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener 5.4.0", "pin-project-lite", @@ -2591,14 +2694,14 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fancy_constructor" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac0fd7f4636276b4bd7b3148d0ba2c1c3fbede2b5214e47e7fedb70b02cde44" +checksum = "28a27643a5d05f3a22f5afd6e0d0e6e354f92d37907006f97b84b9cb79082198" dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -2652,9 +2755,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -2828,7 +2931,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -2869,15 +2972,16 @@ checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" [[package]] name = "generator" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" dependencies = [ + "cc", "cfg-if", "libc", "log", "rustversion", - "windows 0.58.0", + "windows 0.61.3", ] [[package]] @@ -2903,41 +3007,41 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.13.3+wasi-0.2.2", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", "wasm-bindgen", - "windows-targets 0.52.6", ] [[package]] name = "getset" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3586f256131df87204eb733da72e3d3eb4f343c639f4b7be279ac7c48baeafe" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -3045,9 +3149,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" dependencies = [ "bytes", "fnv", @@ -3055,7 +3159,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.7.1", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -3074,7 +3178,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap 2.7.1", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -3083,9 +3187,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", @@ -3100,7 +3204,7 @@ dependencies = [ "log", "pest", "pest_derive", - "quick-error 2.0.1", + "quick-error", "serde", "serde_json", ] @@ -3132,9 +3236,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -3147,7 +3251,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.4", ] [[package]] @@ -3165,11 +3269,11 @@ dependencies = [ [[package]] name = "headers" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", "headers-core", "http 1.3.1", @@ -3210,15 +3314,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -3257,16 +3355,16 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.9.0", + "rand 0.9.2", "ring", - "rustls 0.23.25", + "rustls 0.23.29", "thiserror 2.0.12", "tinyvec", "tokio", "tokio-rustls 0.26.2", "tracing", "url", - "webpki-roots 0.26.8", + "webpki-roots 0.26.11", ] [[package]] @@ -3282,15 +3380,15 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand 0.9.0", + "rand 0.9.2", "resolv-conf", - "rustls 0.23.25", + "rustls 0.23.29", "smallvec", "thiserror 2.0.12", "tokio", "tokio-rustls 0.26.2", "tracing", - "webpki-roots 0.26.8", + "webpki-roots 0.26.11", ] [[package]] @@ -3332,25 +3430,13 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - [[package]] name = "html5ever" -version = "0.31.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953cbbe631aae7fc0a112702ad5d3aaf09da38beaf45ea84610d6e1c358f569c" +checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" dependencies = [ "log", - "mac", "markup5ever", "match_token", ] @@ -3419,9 +3505,9 @@ checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpcodec" @@ -3471,7 +3557,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.3.26", + "h2 0.3.27", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -3521,20 +3607,19 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "futures-util", "http 1.3.1", "hyper 1.6.0", "hyper-util", - "rustls 0.23.25", + "rustls 0.23.29", "rustls-pki-types", "tokio", "tokio-rustls 0.26.2", "tower-service", - "webpki-roots 0.26.8", + "webpki-roots 1.0.2", ] [[package]] @@ -3551,17 +3636,21 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http 1.3.1", "http-body 1.0.1", "hyper 1.6.0", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -3571,16 +3660,17 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.61.2", ] [[package]] @@ -3594,21 +3684,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -3617,31 +3708,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -3649,67 +3720,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -3729,9 +3787,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -3745,7 +3803,7 @@ checksum = "0ab604ee7085efba6efc65e4ebca0e9533e3aff6cb501d7d77b211e3a781c6d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -3814,7 +3872,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -3830,12 +3888,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "serde", ] @@ -3845,10 +3903,23 @@ version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" dependencies = [ - "console", + "console 0.15.11", "number_prefix", "portable-atomic", - "unicode-width 0.2.0", + "unicode-width 0.2.1", + "web-time", +] + +[[package]] +name = "indicatif" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" +dependencies = [ + "console 0.16.0", + "portable-atomic", + "unicode-width 0.2.1", + "unit-prefix", "vt100", "web-time", ] @@ -3875,9 +3946,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "block-padding", "generic-array 0.14.7", @@ -3916,13 +3987,24 @@ checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" [[package]] name = "inventory" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b12ebb6799019b044deaf431eadfe23245b259bba5a2c0796acec3943a3cdb" +checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" dependencies = [ "rustversion", ] +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + [[package]] name = "ip_network" version = "0.4.1" @@ -3957,12 +4039,22 @@ dependencies = [ ] [[package]] -name = "is-terminal" -version = "0.4.15" +name = "iri-string" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" dependencies = [ - "hermit-abi 0.4.0", + "memchr", + "serde", +] + +[[package]] +name = "is-terminal" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +dependencies = [ + "hermit-abi", "libc", "windows-sys 0.59.0", ] @@ -4018,15 +4110,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.4" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "log", @@ -4037,21 +4129,22 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.4" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.3", "libc", ] @@ -4087,9 +4180,9 @@ checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" [[package]] name = "kqueue" -version = "1.0.8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ "kqueue-sys", "libc", @@ -4153,23 +4246,23 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.171" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "libc", "redox_syscall", ] @@ -4187,9 +4280,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.21" +version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" +checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" dependencies = [ "cc", "libc", @@ -4205,9 +4298,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "lioness" @@ -4223,26 +4316,20 @@ dependencies = [ [[package]] name = "litemap" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", ] -[[package]] -name = "lockfree-object-pool" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" - [[package]] name = "log" version = "0.4.27" @@ -4262,6 +4349,12 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "mac" version = "0.1.1" @@ -4288,7 +4381,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -4300,7 +4393,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -4313,7 +4406,7 @@ dependencies = [ "macroific_core", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -4324,30 +4417,24 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "markup5ever" -version = "0.16.1" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a8096766c229e8c88a3900c9b44b7e06aa7f7343cc229158c3e58ef8f9973a" +checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" dependencies = [ "log", "tendril", "web_atoms", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "match_token" -version = "0.1.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -4365,6 +4452,12 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "md-5" version = "0.10.6" @@ -4377,9 +4470,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memoffset" @@ -4424,9 +4517,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -4439,19 +4532,19 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.48.0", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -4569,7 +4662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55e5bda7ca0f9ac5e75b5debac3b75e29a8ac8e2171106a2c3bb466389a8dd83" dependencies = [ "anyhow", - "bitflags 2.8.0", + "bitflags 2.9.1", "byteorder", "libc", "log", @@ -4635,7 +4728,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "cfg-if", "libc", ] @@ -4646,7 +4739,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", @@ -4777,11 +4870,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] @@ -4808,7 +4901,7 @@ dependencies = [ "async-trait", "axum 0.7.9", "axum-extra", - "axum-test", + "axum-test 16.4.1", "bincode", "bip39", "bs58", @@ -4864,8 +4957,8 @@ dependencies = [ "pin-project", "rand 0.8.5", "rand_chacha 0.3.1", - "reqwest 0.12.15", - "schemars", + "reqwest 0.12.22", + "schemars 0.8.22", "semver 1.0.26", "serde", "serde_json", @@ -4878,7 +4971,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", - "tower-http", + "tower-http 0.5.2", "tracing", "ts-rs", "url", @@ -4912,7 +5005,7 @@ dependencies = [ "nym-serde-helpers 0.1.0", "nym-ticketbooks-merkle 0.1.0", "rand_chacha 0.3.1", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "sha2 0.10.9", @@ -4928,7 +5021,7 @@ dependencies = [ [[package]] name = "nym-api-requests" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bs58", "cosmrs", @@ -4948,7 +5041,7 @@ dependencies = [ "nym-node-requests 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-serde-helpers 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-ticketbooks-merkle 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "sha2 0.10.9", @@ -5065,7 +5158,7 @@ dependencies = [ "log", "opentelemetry", "opentelemetry-jaeger", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "tracing", @@ -5079,11 +5172,11 @@ dependencies = [ [[package]] name = "nym-bin-common" version = "0.6.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "const-str", "log", - "schemars", + "schemars 0.8.22", "serde", "utoipa", "vergen", @@ -5166,7 +5259,7 @@ dependencies = [ "thiserror 2.0.12", "time", "tokio", - "toml 0.8.22", + "toml 0.8.23", "url", "zeroize", ] @@ -5364,7 +5457,7 @@ dependencies = [ [[package]] name = "nym-coconut-dkg-common" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -5402,7 +5495,7 @@ dependencies = [ [[package]] name = "nym-compact-ecash" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bincode", "bls12_381", @@ -5432,14 +5525,14 @@ dependencies = [ "nym-network-defaults 0.1.0", "serde", "thiserror 2.0.12", - "toml 0.8.22", + "toml 0.8.23", "url", ] [[package]] name = "nym-config" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "dirs", "handlebars", @@ -5447,7 +5540,7 @@ dependencies = [ "nym-network-defaults 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "serde", "thiserror 2.0.12", - "toml 0.8.22", + "toml 0.8.23", "url", ] @@ -5460,7 +5553,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "thiserror 2.0.12", @@ -5471,13 +5564,13 @@ dependencies = [ [[package]] name = "nym-contracts-common" version = "0.5.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bs58", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", - "schemars", + "schemars 0.8.22", "serde", "thiserror 2.0.12", "vergen", @@ -5537,7 +5630,7 @@ dependencies = [ "nym-network-defaults 0.1.0", "nym-validator-client 0.1.0", "rand 0.8.5", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "sqlx", @@ -5549,7 +5642,7 @@ dependencies = [ "tokio", "tokio-util", "tower 0.5.2", - "tower-http", + "tower-http 0.5.2", "tracing", "url", "utoipa", @@ -5568,8 +5661,8 @@ dependencies = [ "nym-http-api-client 0.1.0", "nym-http-api-common 0.1.0", "nym-serde-helpers 0.1.0", - "reqwest 0.12.15", - "schemars", + "reqwest 0.12.22", + "schemars 0.8.22", "serde", "serde_json", "time", @@ -5682,7 +5775,7 @@ dependencies = [ [[package]] name = "nym-credentials-interface" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bls12_381", "nym-compact-ecash 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -5728,7 +5821,7 @@ dependencies = [ [[package]] name = "nym-crypto" version = "0.4.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "aead", "aes", @@ -5794,7 +5887,7 @@ dependencies = [ [[package]] name = "nym-ecash-contract-common" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bs58", "cosmwasm-schema", @@ -5816,7 +5909,7 @@ dependencies = [ [[package]] name = "nym-ecash-time" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "time", ] @@ -5833,7 +5926,7 @@ dependencies = [ name = "nym-exit-policy" version = "0.1.0" dependencies = [ - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "thiserror 2.0.12", @@ -5844,7 +5937,7 @@ dependencies = [ [[package]] name = "nym-exit-policy" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "serde", "serde_json", @@ -5924,7 +6017,7 @@ name = "nym-gateway-client" version = "0.1.0" dependencies = [ "futures", - "getrandom 0.2.15", + "getrandom 0.2.16", "gloo-utils 0.2.0", "nym-bandwidth-controller", "nym-credential-storage", @@ -6042,19 +6135,19 @@ dependencies = [ "cosmwasm-schema", "cw-controllers", "cw4", - "schemars", + "schemars 0.8.22", "serde", ] [[package]] name = "nym-group-contract-common" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "cosmwasm-schema", "cw-controllers", "cw4", - "schemars", + "schemars 0.8.22", "serde", ] @@ -6073,7 +6166,7 @@ dependencies = [ "nym-bin-common 0.6.0", "nym-http-api-common 0.1.0", "once_cell", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "thiserror 2.0.12", @@ -6086,7 +6179,7 @@ dependencies = [ [[package]] name = "nym-http-api-client" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "async-trait", "bincode", @@ -6098,7 +6191,7 @@ dependencies = [ "nym-bin-common 0.6.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-http-api-common 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "once_cell", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "thiserror 2.0.12", @@ -6132,7 +6225,7 @@ dependencies = [ [[package]] name = "nym-http-api-common" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "axum 0.7.9", "axum-client-ip", @@ -6231,7 +6324,7 @@ dependencies = [ "nym-wireguard", "nym-wireguard-types 0.1.0", "rand 0.8.5", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "thiserror 2.0.12", @@ -6266,7 +6359,7 @@ dependencies = [ [[package]] name = "nym-metrics" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "dashmap", "lazy_static", @@ -6304,7 +6397,7 @@ dependencies = [ "humantime-serde", "nym-contracts-common 0.5.0", "rand_chacha 0.3.1", - "schemars", + "schemars 0.8.22", "semver 1.0.26", "serde", "serde_repr", @@ -6317,7 +6410,7 @@ dependencies = [ [[package]] name = "nym-mixnet-contract-common" version = "0.6.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bs58", "cosmwasm-schema", @@ -6326,7 +6419,7 @@ dependencies = [ "cw-storage-plus", "humantime-serde", "nym-contracts-common 0.5.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "schemars", + "schemars 0.8.22", "semver 1.0.26", "serde", "serde-json-wasm", @@ -6373,7 +6466,7 @@ dependencies = [ "cw-utils", "cw3", "cw4", - "schemars", + "schemars 0.8.22", "serde", "thiserror 2.0.12", ] @@ -6381,7 +6474,7 @@ dependencies = [ [[package]] name = "nym-multisig-contract-common" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -6389,7 +6482,7 @@ dependencies = [ "cw-utils", "cw3", "cw4", - "schemars", + "schemars 0.8.22", "serde", "thiserror 2.0.12", ] @@ -6402,7 +6495,7 @@ dependencies = [ "dotenvy", "log", "regex", - "schemars", + "schemars 0.8.22", "serde", "url", "utoipa", @@ -6411,13 +6504,13 @@ dependencies = [ [[package]] name = "nym-network-defaults" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "cargo_metadata 0.18.1", "dotenvy", "log", "regex", - "schemars", + "schemars 0.8.22", "serde", "url", "utoipa", @@ -6446,7 +6539,7 @@ dependencies = [ "petgraph", "rand 0.8.5", "rand_chacha 0.3.1", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "tokio", @@ -6492,7 +6585,7 @@ dependencies = [ "publicsuffix", "rand 0.8.5", "regex", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "sqlx", @@ -6531,7 +6624,7 @@ dependencies = [ "hkdf", "human-repr", "humantime-serde", - "indicatif", + "indicatif 0.17.11", "ipnetwork", "lioness", "nym-authenticator", @@ -6578,8 +6671,8 @@ dependencies = [ "time", "tokio", "tokio-util", - "toml 0.8.22", - "tower-http", + "toml 0.8.23", + "tower-http 0.5.2", "tracing", "tracing-indicatif", "tracing-subscriber", @@ -6618,7 +6711,7 @@ dependencies = [ "nym-noise-keys", "nym-wireguard-types 0.1.0", "rand_chacha 0.3.1", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "strum 0.26.3", @@ -6631,7 +6724,7 @@ dependencies = [ [[package]] name = "nym-node-requests" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "async-trait", "celes", @@ -6642,7 +6735,7 @@ dependencies = [ "nym-exit-policy 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-http-api-client 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-wireguard-types 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "strum 0.26.3", @@ -6653,13 +6746,14 @@ dependencies = [ [[package]] name = "nym-node-status-agent" -version = "1.0.0" +version = "1.0.4" dependencies = [ "anyhow", "clap", - "nym-bin-common 0.6.0", - "nym-crypto 0.4.0", - "nym-node-status-client 0.1.1", + "futures", + "nym-bin-common 0.6.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-node-status-client", "rand 0.8.5", "tempfile", "tokio", @@ -6669,11 +6763,12 @@ dependencies = [ [[package]] name = "nym-node-status-api" -version = "3.1.2" +version = "3.2.2" dependencies = [ "ammonia", "anyhow", "axum 0.7.9", + "axum-test 17.3.0", "bip39", "celes", "clap", @@ -6691,7 +6786,7 @@ dependencies = [ "nym-network-defaults 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-node-metrics", "nym-node-requests 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-node-status-client 0.1.0", + "nym-node-status-client", "nym-serde-helpers 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-statistics-common 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-task 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -6699,7 +6794,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "regex", - "reqwest 0.12.15", + "reqwest 0.12.22", "semver 1.0.26", "serde", "serde_json", @@ -6711,7 +6806,7 @@ dependencies = [ "time", "tokio", "tokio-util", - "tower-http", + "tower-http 0.5.2", "tracing", "tracing-log 0.2.0", "tracing-subscriber", @@ -6722,29 +6817,13 @@ dependencies = [ [[package]] name = "nym-node-status-client" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +version = "0.1.2" dependencies = [ "anyhow", "bincode", - "chrono", "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-http-api-client 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "reqwest 0.12.15", - "serde", - "serde_json", - "tracing", -] - -[[package]] -name = "nym-node-status-client" -version = "0.1.1" -dependencies = [ - "anyhow", - "bincode", - "nym-crypto 0.4.0", - "nym-http-api-client 0.1.0", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "time", @@ -6817,7 +6896,7 @@ name = "nym-noise-keys" version = "0.1.0" dependencies = [ "nym-crypto 0.4.0", - "schemars", + "schemars 0.8.22", "serde", "utoipa", ] @@ -6865,7 +6944,7 @@ dependencies = [ "chacha20poly1305", "criterion", "fastrand 2.3.0", - "getrandom 0.2.15", + "getrandom 0.2.16", "log", "rand 0.8.5", "rayon", @@ -6878,12 +6957,12 @@ dependencies = [ [[package]] name = "nym-outfox" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "blake3", "chacha20", "chacha20poly1305", - "getrandom 0.2.15", + "getrandom 0.2.16", "log", "rand 0.8.5", "rayon", @@ -6905,7 +6984,7 @@ dependencies = [ [[package]] name = "nym-pemstore" version = "0.3.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "pem", "tracing", @@ -6919,7 +6998,7 @@ dependencies = [ "cosmwasm-std", "cw-controllers", "nym-contracts-common 0.5.0", - "schemars", + "schemars 0.8.22", "serde", "thiserror 2.0.12", ] @@ -6931,7 +7010,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-controllers", - "schemars", + "schemars 0.8.22", "serde", "thiserror 2.0.12", "time", @@ -6978,7 +7057,7 @@ dependencies = [ "nym-validator-client 0.1.0", "parking_lot", "rand 0.8.5", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "tap", "tempfile", @@ -6987,7 +7066,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", - "toml 0.8.22", + "toml 0.8.23", "tracing", "tracing-subscriber", "url", @@ -7009,7 +7088,7 @@ dependencies = [ [[package]] name = "nym-serde-helpers" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "base64 0.22.1", "bs58", @@ -7100,8 +7179,8 @@ dependencies = [ "nym-validator-client 0.1.0", "pin-project", "rand 0.8.5", - "reqwest 0.12.15", - "schemars", + "reqwest 0.12.22", + "schemars 0.8.22", "serde", "tap", "thiserror 2.0.12", @@ -7168,7 +7247,7 @@ dependencies = [ [[package]] name = "nym-sphinx" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-metrics 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7212,7 +7291,7 @@ dependencies = [ [[package]] name = "nym-sphinx-acknowledgements" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-pemstore 0.3.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7240,7 +7319,7 @@ dependencies = [ [[package]] name = "nym-sphinx-addressing" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7269,7 +7348,7 @@ dependencies = [ [[package]] name = "nym-sphinx-anonymous-replies" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bs58", "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7305,7 +7384,7 @@ dependencies = [ [[package]] name = "nym-sphinx-chunking" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "dashmap", "log", @@ -7340,7 +7419,7 @@ dependencies = [ [[package]] name = "nym-sphinx-cover" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-sphinx-acknowledgements 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7369,7 +7448,7 @@ dependencies = [ [[package]] name = "nym-sphinx-forwarding" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-outfox 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-sphinx-addressing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7397,7 +7476,7 @@ dependencies = [ [[package]] name = "nym-sphinx-framing" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bytes", "nym-sphinx-acknowledgements 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7423,7 +7502,7 @@ dependencies = [ [[package]] name = "nym-sphinx-params" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7443,7 +7522,7 @@ dependencies = [ [[package]] name = "nym-sphinx-routing" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-sphinx-addressing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7462,7 +7541,7 @@ dependencies = [ [[package]] name = "nym-sphinx-types" version = "0.2.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-outfox 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "sphinx-packet", @@ -7491,7 +7570,7 @@ dependencies = [ "time", "tokio", "tokio-util", - "tower-http", + "tower-http 0.5.2", "tracing", "tracing-subscriber", "url", @@ -7527,7 +7606,7 @@ dependencies = [ [[package]] name = "nym-statistics-common" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "futures", "log", @@ -7555,7 +7634,7 @@ dependencies = [ "aes-gcm", "argon2", "generic-array 0.14.7", - "getrandom 0.2.15", + "getrandom 0.2.16", "rand 0.8.5", "serde", "serde_json", @@ -7582,7 +7661,7 @@ dependencies = [ [[package]] name = "nym-task" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "cfg-if", "futures", @@ -7605,7 +7684,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rs_merkle", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "sha2 0.10.9", @@ -7616,12 +7695,12 @@ dependencies = [ [[package]] name = "nym-ticketbooks-merkle" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-credentials-interface 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-serde-helpers 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "rs_merkle", - "schemars", + "schemars 0.8.22", "serde", "sha2 0.10.9", "time", @@ -7639,7 +7718,7 @@ dependencies = [ "nym-sphinx-addressing 0.1.0", "nym-sphinx-types 0.2.0", "rand 0.8.5", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "thiserror 2.0.12", @@ -7653,7 +7732,7 @@ dependencies = [ [[package]] name = "nym-topology" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "async-trait", "nym-api-requests 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7664,7 +7743,7 @@ dependencies = [ "nym-sphinx-routing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "rand 0.8.5", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "thiserror 2.0.12", "tracing", @@ -7698,8 +7777,8 @@ dependencies = [ "nym-mixnet-contract-common 0.6.0", "nym-validator-client 0.1.0", "nym-vesting-contract-common 0.7.0", - "reqwest 0.12.15", - "schemars", + "reqwest 0.12.22", + "schemars 0.8.22", "serde", "serde_json", "sha2 0.10.9", @@ -7747,7 +7826,7 @@ dependencies = [ "nym-serde-helpers 0.1.0", "nym-vesting-contract-common 0.7.0", "prost 0.13.5", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "sha2 0.10.9", @@ -7765,7 +7844,7 @@ dependencies = [ [[package]] name = "nym-validator-client" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "async-trait", "base64 0.22.1", @@ -7797,7 +7876,7 @@ dependencies = [ "nym-serde-helpers 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-vesting-contract-common 0.7.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "prost 0.13.5", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "sha2 0.10.9", @@ -7889,7 +7968,7 @@ dependencies = [ [[package]] name = "nym-vesting-contract-common" version = "0.7.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -7904,7 +7983,7 @@ name = "nym-vpn-api-lib-wasm" version = "0.1.0" dependencies = [ "bs58", - "getrandom 0.2.15", + "getrandom 0.2.16", "js-sys", "nym-bin-common 0.6.0", "nym-compact-ecash 0.1.0", @@ -7989,7 +8068,7 @@ dependencies = [ [[package]] name = "nym-wireguard-types" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "base64 0.22.1", "log", @@ -8018,7 +8097,7 @@ dependencies = [ "nym-bin-common 0.6.0", "nym-config 0.1.0", "nym-task 0.1.0", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "sha2 0.10.9", @@ -8045,15 +8124,15 @@ dependencies = [ "nym-task 0.1.0", "nym-validator-client 0.1.0", "nyxd-scraper", - "reqwest 0.12.15", - "schemars", + "reqwest 0.12.22", + "schemars 0.8.22", "serde", "sqlx", "thiserror 2.0.12", "time", "tokio", "tokio-util", - "tower-http", + "tower-http 0.5.2", "tracing", "tracing-subscriber", "utoipa", @@ -8105,10 +8184,16 @@ dependencies = [ ] [[package]] -name = "oorandom" -version = "11.1.4" +name = "once_cell_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "opaque-debug" @@ -8130,9 +8215,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.106" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -8280,9 +8365,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -8290,9 +8375,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -8320,9 +8405,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "peg" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295283b02df346d1ef66052a757869b2876ac29a6bb0ac3f5f7cd44aebe40e8f" +checksum = "9928cfca101b36ec5163e70049ee5368a8a1c3c6efc9ca9c5f9cc2f816152477" dependencies = [ "peg-macros", "peg-runtime", @@ -8330,9 +8415,9 @@ dependencies = [ [[package]] name = "peg-macros" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdad6a1d9cf116a059582ce415d5f5566aabcd4008646779dab7fdc2a9a9d426" +checksum = "6298ab04c202fa5b5d52ba03269fb7b74550b150323038878fe6c372d8280f71" dependencies = [ "peg-runtime", "proc-macro2", @@ -8341,9 +8426,9 @@ dependencies = [ [[package]] name = "peg-runtime" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a" +checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" [[package]] name = "pem" @@ -8373,9 +8458,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", "thiserror 2.0.12", @@ -8384,9 +8469,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -8394,24 +8479,23 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "pest_meta" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ - "once_cell", "pest", "sha2 0.10.9", ] @@ -8423,7 +8507,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.7.1", + "indexmap 2.10.0", ] [[package]] @@ -8466,7 +8550,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -8495,7 +8579,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -8533,9 +8617,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plain" @@ -8612,9 +8696,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.10.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -8638,7 +8722,7 @@ dependencies = [ "hmac", "md-5", "memchr", - "rand 0.9.0", + "rand 0.9.2", "sha2 0.10.9", "stringprep", ] @@ -8654,6 +8738,15 @@ dependencies = [ "postgres-protocol", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -8662,11 +8755,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -8713,23 +8806,23 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "proc_pidinfo" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af53dad2390f8df98dda1e4188322bdf2f91c86cf6001f51d10d64451edf463a" +checksum = "29492a7b48a00ab80202528e235d2f80a04ccff3747540b4ec6881f2f2bc42d1" dependencies = [ "libc", ] @@ -8792,7 +8885,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -8826,9 +8919,9 @@ dependencies = [ [[package]] name = "psl" -version = "2.1.86" +version = "2.1.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e02ed846877ce4044391085ca68b470b0d379cd18a9be0666161764d35448" +checksum = "45f621acfbd2ca5670eee9a95270747dfa9a29e63e1937d7e6a1ac6994331966" dependencies = [ "psl-types", ] @@ -8849,12 +8942,6 @@ dependencies = [ "psl-types", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quick-error" version = "2.0.1" @@ -8863,9 +8950,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quinn" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", "cfg_aliases", @@ -8873,7 +8960,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.25", + "rustls 0.23.29", "socket2", "thiserror 2.0.12", "tokio", @@ -8883,16 +8970,17 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.10" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.3.1", - "rand 0.9.0", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.25", + "rustls 0.23.29", "rustls-pki-types", "slab", "thiserror 2.0.12", @@ -8903,9 +8991,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ "cfg_aliases", "libc", @@ -8924,6 +9012,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "radium" version = "0.7.0" @@ -8943,13 +9037,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.1", - "zerocopy 0.8.20", + "rand_core 0.9.3", ] [[package]] @@ -8969,7 +9062,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.1", + "rand_core 0.9.3", ] [[package]] @@ -8978,17 +9071,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] name = "rand_core" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", - "zerocopy 0.8.20", + "getrandom 0.3.3", ] [[package]] @@ -9023,11 +9115,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", ] [[package]] @@ -9036,11 +9128,31 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 1.0.69", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "regex" version = "1.11.1" @@ -9096,7 +9208,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.3.26", + "h2 0.3.27", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.32", @@ -9128,9 +9240,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "async-compression", "base64 0.22.1", @@ -9141,18 +9253,14 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "hyper 1.6.0", - "hyper-rustls 0.27.5", + "hyper-rustls 0.27.7", "hyper-util", - "ipnet", "js-sys", "log", - "mime", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.25", - "rustls-pemfile 2.2.0", + "rustls 0.23.29", "rustls-pki-types", "serde", "serde_json", @@ -9160,38 +9268,32 @@ dependencies = [ "sync_wrapper 1.0.2", "tokio", "tokio-rustls 0.26.2", - "tokio-socks", "tokio-util", "tower 0.5.2", + "tower-http 0.6.6", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.26.8", - "windows-registry", + "webpki-roots 1.0.2", ] [[package]] name = "reserve-port" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "359fc315ed556eb0e42ce74e76f4b1cd807b50fa6307f3de4e51f92dbe86e2d5" +checksum = "21918d6644020c6f6ef1993242989bf6d4952d2e025617744f184c02df51c356" dependencies = [ - "lazy_static", "thiserror 2.0.12", ] [[package]] name = "resolv-conf" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error 1.2.3", -] +checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" [[package]] name = "rfc6979" @@ -9205,13 +9307,13 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -9259,9 +9361,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest 0.10.7", @@ -9279,9 +9381,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.5.0" +version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" +checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -9290,22 +9392,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.5.0" +version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" +checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.98", + "syn 2.0.104", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.5.0" +version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" +checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" dependencies = [ "sha2 0.10.9", "walkdir", @@ -9328,10 +9430,25 @@ dependencies = [ ] [[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "rust-multipart-rfc7578_2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "c839d037155ebc06a571e305af66ff9fd9063a6e662447051737e1ac75beea41" +dependencies = [ + "bytes", + "futures-core", + "futures-util", + "http 1.3.1", + "mime", + "rand 0.9.2", + "thiserror 2.0.12", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -9363,7 +9480,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -9372,15 +9489,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.1" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys 0.9.2", - "windows-sys 0.59.0", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", ] [[package]] @@ -9411,15 +9528,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.25" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.1", + "rustls-webpki 0.103.4", "subtle 2.6.1", "zeroize", ] @@ -9469,11 +9586,12 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", + "zeroize", ] [[package]] @@ -9499,9 +9617,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -9510,15 +9628,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -9552,6 +9670,30 @@ dependencies = [ "uuid", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars_derive" version = "0.8.22" @@ -9561,7 +9703,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.29.1", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -9587,13 +9729,13 @@ dependencies = [ [[package]] name = "scroll_derive" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -9614,7 +9756,7 @@ checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -9643,9 +9785,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +checksum = "4473013577ec77b4ee3668179ef1186df3146e2cf2d927bd200974c6fe60fd99" dependencies = [ "cc", ] @@ -9656,7 +9798,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "core-foundation", "core-foundation-sys", "libc", @@ -9754,7 +9896,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -9765,7 +9907,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -9776,14 +9918,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", "memchr", @@ -9838,14 +9980,14 @@ checksum = "aafbefbe175fa9bf03ca83ef89beecff7d2a95aaacd5732325b90ac8c3bd7b90" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "serde_path_to_error" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" dependencies = [ "itoa", "serde", @@ -9859,14 +10001,14 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -9885,15 +10027,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.7.1", + "indexmap 2.10.0", + "schemars 0.9.0", + "schemars 1.0.4", "serde", "serde_derive", "serde_json", @@ -9903,14 +10047,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -9919,7 +10063,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.10.0", "itoa", "ryu", "serde", @@ -10004,9 +10148,9 @@ checksum = "b72e7cd0744e007e382ba320435f1ed1ecd709409b4ebd5cfbc843d77b25a8aa" [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", @@ -10025,9 +10169,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -10062,12 +10206,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "sluice" @@ -10082,9 +10223,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] @@ -10135,9 +10276,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -10218,14 +10359,14 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "hashlink", - "indexmap 2.7.1", + "indexmap 2.10.0", "log", "memchr", "once_cell", "percent-encoding", - "rustls 0.23.25", + "rustls 0.23.29", "serde", "serde_json", "sha2 0.10.9", @@ -10236,7 +10377,7 @@ dependencies = [ "tokio-stream", "tracing", "url", - "webpki-roots 0.26.8", + "webpki-roots 0.26.11", ] [[package]] @@ -10249,7 +10390,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -10272,7 +10413,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.98", + "syn 2.0.104", "tokio", "url", ] @@ -10285,7 +10426,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.8.0", + "bitflags 2.9.1", "byteorder", "bytes", "chrono", @@ -10331,7 +10472,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "windows 0.61.1", + "windows 0.61.3", ] [[package]] @@ -10342,7 +10483,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.8.0", + "bitflags 2.9.1", "byteorder", "chrono", "crc", @@ -10423,9 +10564,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "string_cache" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", @@ -10504,7 +10645,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -10547,9 +10688,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.98" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -10573,13 +10714,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -10647,9 +10788,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand 2.3.0", - "getrandom 0.3.1", + "getrandom 0.3.3", "once_cell", - "rustix 1.0.1", + "rustix 1.0.8", "windows-sys 0.59.0", ] @@ -10693,7 +10834,7 @@ dependencies = [ "serde", "serde_json", "tendermint", - "toml 0.8.22", + "toml 0.8.23", "url", ] @@ -10723,7 +10864,7 @@ dependencies = [ "bytes", "flex-error", "futures", - "getrandom 0.2.15", + "getrandom 0.2.16", "peg", "pin-project", "rand 0.8.5", @@ -10774,11 +10915,11 @@ dependencies = [ "bip39", "bs58", "clap", - "console", + "console 0.15.11", "cw-utils", "dkg-bypass-contract", "humantime", - "indicatif", + "indicatif 0.17.11", "nym-bin-common 0.6.0", "nym-coconut-dkg-common 0.1.0", "nym-compact-ecash 0.1.0", @@ -10801,7 +10942,7 @@ dependencies = [ "thiserror 2.0.12", "time", "tokio", - "toml 0.8.22", + "toml 0.8.23", "tracing", "url", "zeroize", @@ -10809,9 +10950,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ "smawk", ] @@ -10842,7 +10983,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -10853,17 +10994,16 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -10924,9 +11064,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -10944,9 +11084,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -10959,17 +11099,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", - "mio 1.0.3", + "mio 1.0.4", "parking_lot", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", "tracing", @@ -10978,9 +11120,9 @@ dependencies = [ [[package]] name = "tokio-io-timeout" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +checksum = "0bd86198d9ee903fedd2f9a2e72014287c0d9167e4ae43b5853007205dda1b76" dependencies = [ "pin-project-lite", "tokio", @@ -10994,7 +11136,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -11016,7 +11158,7 @@ dependencies = [ "pin-project-lite", "postgres-protocol", "postgres-types", - "rand 0.9.0", + "rand 0.9.2", "socket2", "tokio", "tokio-util", @@ -11050,19 +11192,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.25", - "tokio", -] - -[[package]] -name = "tokio-socks" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" -dependencies = [ - "either", - "futures-util", - "thiserror 1.0.69", + "rustls 0.23.29", "tokio", ] @@ -11128,7 +11258,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "pin-project-lite", "slab", "tokio", @@ -11145,9 +11275,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -11157,20 +11287,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.10.0", "serde", "serde_spanned", "toml_datetime", @@ -11180,9 +11310,9 @@ dependencies = [ [[package]] name = "toml_write" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tonic" @@ -11196,7 +11326,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "h2 0.3.26", + "h2 0.3.27", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.32", @@ -11255,7 +11385,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "async-compression", - "bitflags 2.8.0", + "bitflags 2.9.1", "bytes", "futures-core", "futures-util", @@ -11275,6 +11405,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -11301,20 +11449,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -11332,11 +11480,11 @@ dependencies = [ [[package]] name = "tracing-indicatif" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8201ca430e0cd893ef978226fd3516c06d9c494181c8bf4e5b32e30ed4b40aa1" +checksum = "8c714cc8fc46db04fcfddbd274c6ef59bebb1b435155984e7c6e89c3ce66f200" dependencies = [ - "indicatif", + "indicatif 0.18.0", "tracing", "tracing-core", "tracing-subscriber", @@ -11477,7 +11625,7 @@ checksum = "0e9d8656589772eeec2cf7a8264d9cda40fb28b9bc53118ceb9e8c07f8f38730" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "termcolor", ] @@ -11504,7 +11652,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.28.0", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -11575,15 +11723,15 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -11608,9 +11756,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unicode-xid" @@ -11620,9 +11768,9 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "uniffi" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd1d240101ba3b9d7532ae86d9cb64d9a7ff63e13a2b7b9e94a32a601d8233" +checksum = "b334fd69b3cf198b63616c096aabf9820ab21ed9b2aa1367ddd4b411068bf520" dependencies = [ "anyhow", "camino", @@ -11637,9 +11785,9 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0525f06d749ea80d8049dc0bb038bb87941e3d909eefa76b6f0a5589b59ac5" +checksum = "2ff0132b533483cf19abb30bba5c72c24d9f3e4d9a2ff71cb3e22e73899fd46e" dependencies = [ "anyhow", "askama", @@ -11649,7 +11797,7 @@ dependencies = [ "glob", "goblin", "heck 0.5.0", - "indexmap 2.7.1", + "indexmap 2.10.0", "once_cell", "serde", "tempfile", @@ -11663,9 +11811,9 @@ dependencies = [ [[package]] name = "uniffi_build" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aed2f0204e942bb9c11c9f11a323b4abf70cf11b2e5957d60b3f2728430f6c6f" +checksum = "0d84d607076008df3c32dd2100ee4e727269f11d3faa35691af70d144598f666" dependencies = [ "anyhow", "camino", @@ -11674,9 +11822,9 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fa8eb4d825b4ed095cb13483cba6927c3002b9eb603cef9b7688758cc3772e" +checksum = "53e3b997192dc15ef1778c842001811ec7f241a093a693ac864e1fc938e64fa9" dependencies = [ "anyhow", "bytes", @@ -11686,22 +11834,22 @@ dependencies = [ [[package]] name = "uniffi_internal_macros" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b547d69d699e52f2129fde4b57ae0d00b5216e59ed5b56097c95c86ba06095" +checksum = "f64bec2f3a33f2f08df8150e67fa45ba59a2ca740bf20c1beb010d4d791f9a1b" dependencies = [ "anyhow", - "indexmap 2.7.1", + "indexmap 2.10.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "uniffi_macros" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f1de72edc8cb9201c7d650e3678840d143e4499004571aac49e6cb1b17da43" +checksum = "5d8708716d2582e4f3d7e9f320290b5966eb951ca421d7630571183615453efc" dependencies = [ "camino", "fs-err", @@ -11709,16 +11857,16 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.98", + "syn 2.0.104", "toml 0.5.11", "uniffi_meta", ] [[package]] name = "uniffi_meta" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acc9204632f6a555b2cba7c8852c5523bc1aa5f3eff605c64af5054ea28b72e" +checksum = "3d226fc167754ce548c5ece9828c8a06f03bf1eea525d2659ba6bd648bd8e2f3" dependencies = [ "anyhow", "siphasher 0.3.11", @@ -11728,22 +11876,22 @@ dependencies = [ [[package]] name = "uniffi_pipeline" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b5336a9a925b358183837d31541d12590b7fcec373256d3770de02dff24c69" +checksum = "b925b6421df15cf4bedee27714022cd9626fb4d7eee0923522a608b274ba4371" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.7.1", + "indexmap 2.10.0", "tempfile", "uniffi_internal_macros", ] [[package]] name = "uniffi_udl" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f95e73373d85f04736bc51997d3e6855721144ec4384cae9ca8513c80615e129" +checksum = "9c42649b721df759d9d4692a376b82b62ce3028ec9fc466f4780fb8cdf728996" dependencies = [ "anyhow", "textwrap", @@ -11751,6 +11899,12 @@ dependencies = [ "weedle2", ] +[[package]] +name = "unit-prefix" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" + [[package]] name = "universal-hash" version = "0.5.1" @@ -11797,12 +11951,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -11817,11 +11965,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utoipa" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0" +checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.10.0", "serde", "serde_json", "utoipa-gen", @@ -11829,14 +11977,14 @@ dependencies = [ [[package]] name = "utoipa-gen" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77d306bc75294fd52f3e99b13ece67c02c1a2789190a6f31d32f736624326f7" +checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.98", + "syn 2.0.104", "uuid", ] @@ -11875,7 +12023,7 @@ checksum = "268d76aaebb80eba79240b805972e52d7d410d4bcc52321b951318b0f440cd60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -11886,18 +12034,20 @@ checksum = "382673bda1d05c85b4550d32fd4192ccd4cffe9a908543a0795d1e7682b36246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "utoipauto-core", ] [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.3", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] @@ -12011,15 +12161,15 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -12052,7 +12202,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -12087,7 +12237,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -12122,7 +12272,7 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -12163,7 +12313,7 @@ name = "wasm-storage" version = "0.1.0" dependencies = [ "async-trait", - "getrandom 0.2.15", + "getrandom 0.2.16", "indexed_db_futures", "js-sys", "nym-store-cipher", @@ -12193,7 +12343,7 @@ version = "0.1.0" dependencies = [ "console_error_panic_hook", "futures", - "getrandom 0.2.15", + "getrandom 0.2.16", "gloo-net", "gloo-utils 0.2.0", "js-sys", @@ -12205,9 +12355,9 @@ dependencies = [ [[package]] name = "wasmtimer" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" +checksum = "d8d49b5d6c64e8558d9b1b065014426f35c18de636895d24893dbbd329743446" dependencies = [ "futures", "js-sys", @@ -12239,9 +12389,9 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9c5f0bc545ea3b20b423e33b9b457764de0b3730cd957f6c6aa6c301785f6e" +checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" dependencies = [ "phf", "phf_codegen", @@ -12266,9 +12416,18 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.8" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.2", +] + +[[package]] +name = "webpki-roots" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] @@ -12284,9 +12443,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ "redox_syscall", "wasite", @@ -12295,9 +12454,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" [[package]] name = "winapi" @@ -12342,19 +12501,9 @@ dependencies = [ [[package]] name = "windows" -version = "0.58.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows" -version = "0.61.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", "windows-core 0.61.2", @@ -12372,15 +12521,6 @@ dependencies = [ "windows-core 0.61.2", ] -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.57.0" @@ -12393,19 +12533,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.61.2" @@ -12416,7 +12543,7 @@ dependencies = [ "windows-interface 0.59.1", "windows-link", "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-strings", ] [[package]] @@ -12438,18 +12565,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", -] - -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -12460,7 +12576,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -12471,18 +12587,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", -] - -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -12493,14 +12598,14 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-numerics" @@ -12512,17 +12617,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-registry" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" -dependencies = [ - "windows-result 0.3.4", - "windows-strings 0.3.1", - "windows-targets 0.53.0", -] - [[package]] name = "windows-result" version = "0.1.2" @@ -12532,15 +12626,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-result" version = "0.3.4" @@ -12550,25 +12635,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-strings" version = "0.4.2" @@ -12614,6 +12680,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -12662,9 +12737,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", @@ -12867,9 +12942,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -12886,24 +12961,18 @@ dependencies = [ [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wyz" @@ -12928,13 +12997,12 @@ dependencies = [ [[package]] name = "xattr" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" +checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" dependencies = [ "libc", - "linux-raw-sys 0.4.15", - "rustix 0.38.44", + "rustix 1.0.8", ] [[package]] @@ -12945,9 +13013,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -12957,75 +13025,54 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "synstructure", ] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" -dependencies = [ - "zerocopy-derive 0.8.20", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "synstructure", ] @@ -13046,14 +13093,25 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", ] [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -13062,27 +13120,27 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "zip" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938cc23ac49778ac8340e366ddc422b2227ea176edb447e23fc0627608dddadd" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" dependencies = [ "arbitrary", "crc32fast", "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.7.1", + "indexmap 2.10.0", "memchr", "thiserror 2.0.12", "zopfli", @@ -13095,7 +13153,7 @@ dependencies = [ "anyhow", "async-trait", "bs58", - "getrandom 0.2.15", + "getrandom 0.2.16", "js-sys", "nym-bin-common 0.6.0", "nym-compact-ecash 0.1.0", @@ -13103,7 +13161,7 @@ dependencies = [ "nym-crypto 0.4.0", "nym-http-api-client 0.1.0", "rand 0.8.5", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "thiserror 2.0.12", "tokio", @@ -13117,41 +13175,39 @@ dependencies = [ [[package]] name = "zopfli" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" dependencies = [ "bumpalo", "crc32fast", - "lockfree-object-pool", "log", - "once_cell", "simd-adler32", ] [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", diff --git a/nym-node-status-api/README.md b/nym-node-status-api/README.md index 03da6ca3cb..1ef6706c1d 100644 --- a/nym-node-status-api/README.md +++ b/nym-node-status-api/README.md @@ -4,3 +4,61 @@ The Node Status API serves information about individual `nym-nodes` in the Mixne We recommend that developers building applications such as explorers or analytics interfaces about the Mixnet run their own instance of the API, in order to promote a robust network of downstream services, and spread the load of API calls amongst as many endpoints as possible. You can find build and operation instructions in the [docs](https://nym.com/docs/apis/ns-api). + +## Database Support + +The Node Status API supports both SQLite and PostgreSQL databases through Cargo feature flags: + +- **SQLite** (default): Lightweight, file-based database suitable for development and small deployments +- **PostgreSQL**: Full-featured database recommended for production deployments + +### Building with Different Database Backends + +```bash +# Build with SQLite (default) +cargo build --features sqlite --no-default-features + +# Build with PostgreSQL +cargo build --features pg --no-default-features +``` + +### Running Tests + +```bash +# Test with SQLite +cargo test --features sqlite --no-default-features + +# Test with PostgreSQL +make test-db # This sets up a test PostgreSQL instance +``` + +### Development Commands + +The project includes a Makefile with helpful commands for both database backends: + +```bash +# Check code compilation +make check-sqlite # Check with SQLite +make check-pg # Check with PostgreSQL + +# Run clippy linter +make clippy-sqlite # Lint with SQLite +make clippy-pg # Lint with PostgreSQL +make clippy # Run both + +# PostgreSQL development +make dev-db # Start a PostgreSQL instance for development +make prepare-pg # Prepare SQLx offline cache for PostgreSQL +``` + +### Implementation Details + +The database abstraction is implemented using a query wrapper that automatically converts SQLite-style `?` placeholders to PostgreSQL-style `$1, $2, ...` placeholders at runtime. This allows writing queries once using SQLite syntax while maintaining compatibility with both databases. + +Key differences handled: +- Placeholder syntax (`?` vs `$1, $2, ...`) +- Type conversions (SQLite uses i64, PostgreSQL uses i32 for many fields) +- SQL dialect differences (e.g., `INSERT OR IGNORE` vs `ON CONFLICT DO NOTHING`) +- RETURNING clause behavior + +For more details on PostgreSQL setup, see [README_PG.md](nym-node-status-api/README_PG.md). diff --git a/nym-node-status-api/nym-node-status-agent/Cargo.toml b/nym-node-status-api/nym-node-status-agent/Cargo.toml index 615b2c484f..f6671e611b 100644 --- a/nym-node-status-api/nym-node-status-agent/Cargo.toml +++ b/nym-node-status-api/nym-node-status-agent/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-agent" -version = "1.0.0" +version = "1.0.4" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -14,13 +14,25 @@ rust-version.workspace = true readme.workspace = true [dependencies] -anyhow = { workspace = true} +anyhow = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } -nym-bin-common = { path = "../../common/bin-common", features = ["models"]} +futures = { workspace = true } +# nym-bin-common = { path = "../../common/bin-common", features = ["models"] } +nym-bin-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ + "models", +] } nym-node-status-client = { path = "../nym-node-status-client" } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } +nym-crypto = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ + "asymmetric", + "rand", +] } rand = { workspace = true } -tokio = { workspace = true, features = ["macros", "rt-multi-thread", "process", "fs"] } +tokio = { workspace = true, features = [ + "macros", + "rt-multi-thread", + "process", + "fs", +] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/nym-node-status-api/nym-node-status-agent/Dockerfile b/nym-node-status-api/nym-node-status-agent/Dockerfile index fcab377e26..192af6678d 100644 --- a/nym-node-status-api/nym-node-status-agent/Dockerfile +++ b/nym-node-status-api/nym-node-status-agent/Dockerfile @@ -16,7 +16,7 @@ WORKDIR /usr/src/nym-vpn-client/nym-vpn-core RUN cargo build --release --package nym-gateway-probe COPY ./ /usr/src/nym -WORKDIR /usr/src/nym/nym-node-status-agent +WORKDIR /usr/src/nym/nym-node-status-api/nym-node-status-agent RUN cargo build --release #------------------------------------------------------------------- diff --git a/nym-node-status-api/nym-node-status-agent/build-push-node-status-agent.sh b/nym-node-status-api/nym-node-status-agent/build-push-node-status-agent.sh new file mode 100755 index 0000000000..2cf972a8f3 --- /dev/null +++ b/nym-node-status-api/nym-node-status-agent/build-push-node-status-agent.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# Build and push Node Status Agent container to harbor.nymte.ch + +set -e + +# Configuration +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +WORKING_DIRECTORY="${SCRIPT_DIR}" +CONTAINER_NAME="node-status-agent" +REGISTRY="harbor.nymte.ch" +NAMESPACE="nym" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to display usage +usage() { + echo "Usage: $0 " + echo " gateway-probe-git-ref - Git reference (branch/tag/commit) for gateway probe" + echo "" + echo "Example: $0 main" + echo "Example: $0 release/2025.11-cheddar" + echo "Example: $0 v1.2.3" + exit 1 +} + +# Parse arguments +if [ $# -ne 1 ]; then + usage +fi + +GATEWAY_PROBE_GIT_REF="$1" + +# Get version from Cargo.toml +VERSION=$(grep "^version = " "${WORKING_DIRECTORY}/Cargo.toml" | sed -E 's/version = "(.*)"/\1/') +if [ -z "$VERSION" ]; then + echo -e "${RED}Error: Could not extract version from Cargo.toml${NC}" + exit 1 +fi + +# Clean up git ref for use in tag (replace / with -) +GIT_REF_SLUG="${GATEWAY_PROBE_GIT_REF//\//-}" + +echo -e "${YELLOW}Building Node Status Agent${NC}" +echo -e "${YELLOW}Version: ${VERSION}${NC}" +echo -e "${YELLOW}Gateway Probe Git Ref: ${GATEWAY_PROBE_GIT_REF} (slug: ${GIT_REF_SLUG})${NC}" + +# Login to Harbor +echo -e "${GREEN}Logging into Harbor...${NC}" +docker login "${REGISTRY}" + +# Build the container +echo -e "${GREEN}Building container with gateway probe from ${GATEWAY_PROBE_GIT_REF}...${NC}" +# Build from repository root (two levels up from script location) +docker build \ + --build-arg GIT_REF="${GATEWAY_PROBE_GIT_REF}" \ + -f "${WORKING_DIRECTORY}/Dockerfile" \ + "${SCRIPT_DIR}/../.." \ + -t "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:${VERSION}-${GIT_REF_SLUG}" \ + -t "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:latest-${GIT_REF_SLUG}" + +# Push to Harbor +echo -e "${GREEN}Pushing container to Harbor...${NC}" +docker push "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:${VERSION}-${GIT_REF_SLUG}" +docker push "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:latest-${GIT_REF_SLUG}" + +echo -e "${GREEN}Successfully built and pushed ${CONTAINER_NAME}:${VERSION}-${GIT_REF_SLUG}${NC}" \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-agent/src/cli/mod.rs b/nym-node-status-api/nym-node-status-agent/src/cli/mod.rs index 09aa7b7dfd..24283b081d 100644 --- a/nym-node-status-api/nym-node-status-agent/src/cli/mod.rs +++ b/nym-node-status-api/nym-node-status-agent/src/cli/mod.rs @@ -1,17 +1,45 @@ use crate::probe::GwProbe; use clap::{Parser, Subcommand}; use nym_bin_common::bin_info; -use std::sync::OnceLock; +use nym_crypto::asymmetric::ed25519::PrivateKey; +use std::{env, sync::OnceLock}; pub(crate) mod generate_keypair; pub(crate) mod run_probe; +#[derive(Debug)] +pub(crate) struct ServerConfig { + pub(crate) address: String, + pub(crate) port: u16, + pub(crate) auth_key: PrivateKey, +} + // Helper for passing LONG_VERSION to clap fn pretty_build_info_static() -> &'static str { static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) } +fn parse_server_config(s: &str) -> Result { + let parts: Vec<&str> = s.split('|').collect(); + if parts.len() != 2 { + return Err("Server config must be in format 'address|port'".to_string()); + } + + let address = parts[0].to_string(); + let port = parts[1] + .parse::() + .map_err(|_| "Invalid port number".to_string())?; + let auth_key = + PrivateKey::from_base58_string(env::var("NODE_STATUS_AGENT_AUTH_KEY").unwrap()).unwrap(); + + Ok(ServerConfig { + address, + port, + auth_key, + }) +} + #[derive(Parser, Debug)] #[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)] pub(crate) struct Args { @@ -22,15 +50,10 @@ pub(crate) struct Args { #[derive(Subcommand, Debug)] pub(crate) enum Command { RunProbe { - #[arg(short, long, env = "NODE_STATUS_AGENT_SERVER_ADDRESS")] - server_address: String, - - #[arg(short = 'p', long, env = "NODE_STATUS_AGENT_SERVER_PORT")] - server_port: u16, - - /// base58-encoded private key - #[arg(long, env = "NODE_STATUS_AGENT_AUTH_KEY")] - ns_api_auth_key: String, + /// Server configurations in format "address:port:auth_key" + /// Can be specified multiple times for multiple servers + #[arg(short, long, required = true)] + server: Vec, /// path of binary to run #[arg(long, env = "NODE_STATUS_AGENT_PROBE_PATH")] @@ -58,24 +81,29 @@ impl Args { pub(crate) async fn execute(&self) -> anyhow::Result<()> { match &self.command { Command::RunProbe { - server_address, - server_port, - ns_api_auth_key, + server, probe_path, mnemonic, probe_extra_args, - } => run_probe::run_probe( - server_address, - server_port.to_owned(), - ns_api_auth_key, - probe_path, - mnemonic, - probe_extra_args, - ) - .await - .inspect_err(|err| { - tracing::error!("{err}"); - })?, + } => { + // Parse server configs + let mut servers = Vec::new(); + for s in server { + match parse_server_config(s) { + Ok(config) => servers.push(config), + Err(e) => { + tracing::error!("Invalid server config '{}': {}", s, e); + anyhow::bail!("Invalid server config '{}': {}", s, e); + } + } + } + + run_probe::run_probe(&servers, probe_path, mnemonic, probe_extra_args) + .await + .inspect_err(|err| { + tracing::error!("{err}"); + })? + } Command::GenerateKeypair { path } => { let path = path .to_owned() diff --git a/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs b/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs index e2b9986785..07fa3b514f 100644 --- a/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs +++ b/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs @@ -1,38 +1,143 @@ -use crate::cli::GwProbe; -use anyhow::Context; -use nym_crypto::asymmetric::ed25519::PrivateKey; +use crate::cli::{GwProbe, ServerConfig}; pub(crate) async fn run_probe( - server_ip: &str, - server_port: u16, - ns_api_auth_key: &str, + servers: &[ServerConfig], probe_path: &str, mnemonic: &str, probe_extra_args: &Vec, ) -> anyhow::Result<()> { - let auth_key = PrivateKey::from_base58_string(ns_api_auth_key) - .context("Couldn't parse auth key, exiting")?; - - let ns_api_client = nym_node_status_client::NsApiClient::new(server_ip, server_port, auth_key); + if servers.is_empty() { + anyhow::bail!("No servers configured"); + } let probe = GwProbe::new(probe_path.to_string()); let version = probe.version().await; tracing::info!("Probe version:\n{}", version); - if let Some(testrun) = ns_api_client.request_testrun().await? { - let log = probe.run_and_get_log( - &Some(testrun.gateway_identity_key), - mnemonic, - probe_extra_args, - ); + // Always use first server as primary + let primary_server = &servers[0]; + tracing::info!( + "Requesting testrun from primary server: {}:{}", + primary_server.address, + primary_server.port + ); - ns_api_client - .submit_results(testrun.testrun_id, log, testrun.assigned_at_utc) - .await?; - } else { - tracing::info!("No testruns available, exiting") + let auth_key = nym_crypto::asymmetric::ed25519::PrivateKey::from_bytes( + &primary_server.auth_key.to_bytes(), + ) + .expect("Failed to clone auth key"); + let ns_api_client = nym_node_status_client::NsApiClient::new( + &primary_server.address, + primary_server.port, + auth_key, + ); + + match ns_api_client.request_testrun().await { + Ok(Some(testrun)) => { + tracing::info!( + "Received testrun {} for gateway {} from primary", + testrun.testrun_id, + testrun.gateway_identity_key + ); + + // Run the probe + let log = probe.run_and_get_log( + &Some(testrun.gateway_identity_key.clone()), + mnemonic, + probe_extra_args, + ); + + // Submit to ALL servers in parallel + let handles = servers + .iter() + .enumerate() + .map(|(idx, server)| { + let testrun = testrun.clone(); + let log = log.clone(); + + async move { + let auth_key = nym_crypto::asymmetric::ed25519::PrivateKey::from_bytes( + &server.auth_key.to_bytes(), + ) + .expect("Failed to clone auth key"); + let client = nym_node_status_client::NsApiClient::new( + &server.address, + server.port, + auth_key, + ); + + let result = if idx == 0 { + // Primary server: submit regular results without context + client + .submit_results( + testrun.testrun_id as i64, + log, + testrun.assigned_at_utc, + ) + .await + } else { + // Other servers: submit results with context + client + .submit_results_with_context( + testrun.testrun_id, + log, + testrun.assigned_at_utc, + testrun.gateway_identity_key, + ) + .await + }; + + (idx, server.address.clone(), server.port, result) + } + }) + .collect::>(); + + let results = futures::future::join_all(handles).await; + + for result in results { + match result.3 { + Ok(()) => { + let method = if result.0 == 0 { + "regular" + } else { + "with context" + }; + tracing::info!( + "✅ Successfully submitted {} to server[{}] {}:{}", + method, + result.0, + result.1, + result.2 + ); + } + Err(e) => { + let method = if result.0 == 0 { + "regular" + } else { + "with context" + }; + tracing::warn!( + "❌ Failed to submit {} to server[{}] {}:{} - {}", + method, + result.0, + result.1, + result.2, + e + ); + } + } + } + + Ok(()) + } + Ok(None) => { + tracing::info!("No testruns available from primary server"); + Ok(()) + } + Err(e) => { + tracing::error!("Failed to contact primary server: {}", e); + Err(e) + } } - - Ok(()) } diff --git a/nym-node-status-api/nym-node-status-api/.env.example b/nym-node-status-api/nym-node-status-api/.env.example new file mode 100644 index 0000000000..7625347a7c --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/.env.example @@ -0,0 +1,32 @@ +# Example environment variables for nym-node-status-api + +# Database configuration +# For SQLite: +# DATABASE_URL=sqlite://nym-node-status-api.sqlite + +# For PostgreSQL: +# DATABASE_URL=postgres://testuser:testpass@localhost:5433/nym_node_status_api_test + +# Network configuration +NETWORK_NAME=sandbox +NYM_API=https://sandbox-nym-api1.nymtech.net/api +NYXD=https://rpc.sandbox.nymtech.net + +# API configuration +NYM_NODE_STATUS_API_HTTP_PORT=8000 +NYM_API_CLIENT_TIMEOUT=15 +SQLX_BUSY_TIMEOUT_S=5 + +# Monitoring intervals +NODE_STATUS_API_MONITOR_REFRESH_INTERVAL=300 +NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL=300 +NODE_STATUS_API_GEODATA_TTL=86400 + +# Agent keys (comma-separated list) +NODE_STATUS_API_AGENT_KEY_LIST= + +# External service tokens +IPINFO_API_TOKEN=your_token_here + +# MixNodes (Optional) +DATA_PROVIDER_DELEGATION_LIST= \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-01ee4a30bc3104712e5bc371a45d614a89d88adf02358800433e06100c13c548.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-01ee4a30bc3104712e5bc371a45d614a89d88adf02358800433e06100c13c548.json deleted file mode 100644 index d59ea9da1a..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-01ee4a30bc3104712e5bc371a45d614a89d88adf02358800433e06100c13c548.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO mixnode_daily_stats (\n mix_id, date_utc,\n total_stake, packets_received,\n packets_sent, packets_dropped\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(mix_id, date_utc) DO UPDATE SET\n total_stake = excluded.total_stake,\n packets_received = mixnode_daily_stats.packets_received + excluded.packets_received,\n packets_sent = mixnode_daily_stats.packets_sent + excluded.packets_sent,\n packets_dropped = mixnode_daily_stats.packets_dropped + excluded.packets_dropped\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "01ee4a30bc3104712e5bc371a45d614a89d88adf02358800433e06100c13c548" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-021c6c65d1ed806d8430bef7883906b42a7e4b280c8efb32db15d7c6a51d7a27.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-021c6c65d1ed806d8430bef7883906b42a7e4b280c8efb32db15d7c6a51d7a27.json deleted file mode 100644 index eb5cdd6aaa..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-021c6c65d1ed806d8430bef7883906b42a7e4b280c8efb32db15d7c6a51d7a27.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT mix_id as node_id, host, http_api_port\n FROM mixnodes\n WHERE bonded = true\n ", - "describe": { - "columns": [ - { - "name": "node_id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "host", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "http_api_port", - "ordinal": 2, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "021c6c65d1ed806d8430bef7883906b42a7e4b280c8efb32db15d7c6a51d7a27" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-06065394c157927e4002ddd5c7c1af626ae15728d615f539470cd7c189312385.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-06065394c157927e4002ddd5c7c1af626ae15728d615f539470cd7c189312385.json deleted file mode 100644 index 205bfdc79b..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-06065394c157927e4002ddd5c7c1af626ae15728d615f539470cd7c189312385.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO mixnode_description (\n mix_id, moniker, website, security_contact, details, last_updated_utc\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (mix_id) DO UPDATE SET\n moniker = excluded.moniker,\n website = excluded.website,\n security_contact = excluded.security_contact,\n details = excluded.details,\n last_updated_utc = excluded.last_updated_utc\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "06065394c157927e4002ddd5c7c1af626ae15728d615f539470cd7c189312385" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984.json deleted file mode 100644 index ad57224826..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n gateway_identity_key\n FROM\n gateways\n WHERE\n id = ?", - "describe": { - "columns": [ - { - "name": "gateway_identity_key", - "ordinal": 0, - "type_info": "Text" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false - ] - }, - "hash": "06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-0cf0e4d4f30e90caecffd6255ef85dab12730e538be194438f19ed7f198bd50e.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-0cf0e4d4f30e90caecffd6255ef85dab12730e538be194438f19ed7f198bd50e.json deleted file mode 100644 index fda05c400a..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-0cf0e4d4f30e90caecffd6255ef85dab12730e538be194438f19ed7f198bd50e.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE\n testruns\n SET\n status = ?\n WHERE\n status = ?\n AND\n last_assigned_utc < ?\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "0cf0e4d4f30e90caecffd6255ef85dab12730e538be194438f19ed7f198bd50e" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-124d45b9604439584650f401607c46bdbd162c7c689f74fe9ddfdfd48f5ddc07.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-124d45b9604439584650f401607c46bdbd162c7c689f74fe9ddfdfd48f5ddc07.json index 75534f6ea7..b32a31ab5a 100644 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-124d45b9604439584650f401607c46bdbd162c7c689f74fe9ddfdfd48f5ddc07.json +++ b/nym-node-status-api/nym-node-status-api/.sqlx/query-124d45b9604439584650f401607c46bdbd162c7c689f74fe9ddfdfd48f5ddc07.json @@ -1,43 +1,43 @@ { - "db_name": "SQLite", + "db_name": "PostgreSQL", "query": "\n SELECT\n date_utc as \"date_utc!\",\n SUM(total_stake) as \"total_stake!: i64\",\n SUM(packets_received) as \"total_packets_received!: i64\",\n SUM(packets_sent) as \"total_packets_sent!: i64\",\n SUM(packets_dropped) as \"total_packets_dropped!: i64\"\n FROM (\n SELECT\n date_utc,\n n.total_stake,\n n.packets_received,\n n.packets_sent,\n n.packets_dropped\n FROM nym_node_daily_mixing_stats n\n UNION ALL\n SELECT\n m.date_utc,\n m.total_stake,\n m.packets_received,\n m.packets_sent,\n m.packets_dropped\n FROM mixnode_daily_stats m\n LEFT JOIN nym_node_daily_mixing_stats ON m.mix_id = nym_node_daily_mixing_stats.node_id\n WHERE nym_node_daily_mixing_stats.node_id IS NULL\n )\n GROUP BY date_utc\n ORDER BY date_utc ASC\n ", "describe": { "columns": [ { - "name": "date_utc!", "ordinal": 0, - "type_info": "Text" + "name": "date_utc!", + "type_info": "Varchar" }, { - "name": "total_stake!: i64", "ordinal": 1, - "type_info": "Integer" + "name": "total_stake!: i64", + "type_info": "Numeric" }, { - "name": "total_packets_received!: i64", "ordinal": 2, - "type_info": "Integer" + "name": "total_packets_received!: i64", + "type_info": "Int8" }, { - "name": "total_packets_sent!: i64", "ordinal": 3, - "type_info": "Integer" + "name": "total_packets_sent!: i64", + "type_info": "Int8" }, { - "name": "total_packets_dropped!: i64", "ordinal": 4, - "type_info": "Integer" + "name": "total_packets_dropped!: i64", + "type_info": "Int8" } ], "parameters": { - "Right": 0 + "Left": [] }, "nullable": [ - false, - false, - true, - true, - true + null, + null, + null, + null, + null ] }, "hash": "124d45b9604439584650f401607c46bdbd162c7c689f74fe9ddfdfd48f5ddc07" diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454.json deleted file mode 100644 index 810e133a8a..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n key as \"key!\",\n value_json as \"value_json!\",\n last_updated_utc as \"last_updated_utc!\"\n FROM summary", - "describe": { - "columns": [ - { - "name": "key!", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "value_json!", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "last_updated_utc!", - "ordinal": 2, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - true, - true, - false - ] - }, - "hash": "1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-21e44766729777756f6eb04bf3b81df3e591008a1e3fd664ed83ca86ac51bd8c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-21e44766729777756f6eb04bf3b81df3e591008a1e3fd664ed83ca86ac51bd8c.json deleted file mode 100644 index 52929bc782..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-21e44766729777756f6eb04bf3b81df3e591008a1e3fd664ed83ca86ac51bd8c.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO mixnode_packet_stats_raw (\n mix_id, timestamp_utc, packets_received, packets_sent, packets_dropped\n ) VALUES (?, ?, ?, ?, ?)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 5 - }, - "nullable": [] - }, - "hash": "21e44766729777756f6eb04bf3b81df3e591008a1e3fd664ed83ca86ac51bd8c" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129.json deleted file mode 100644 index 0e24c697ac..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n id,\n gateway_identity_key\n FROM gateways\n WHERE id = ?\n LIMIT 1", - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "gateway_identity_key", - "ordinal": 1, - "type_info": "Text" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false, - false - ] - }, - "hash": "2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-227539374e7473f6f9642289c5b5d1bcd636315ab23537cb5f6d2f82a2bcb7bf.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-227539374e7473f6f9642289c5b5d1bcd636315ab23537cb5f6d2f82a2bcb7bf.json deleted file mode 100644 index a6b5055677..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-227539374e7473f6f9642289c5b5d1bcd636315ab23537cb5f6d2f82a2bcb7bf.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n node_id,\n bond_info as \"bond_info: serde_json::Value\"\n FROM\n nym_nodes\n WHERE\n bond_info IS NOT NULL\n AND\n self_described IS NOT NULL\n ", - "describe": { - "columns": [ - { - "name": "node_id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "bond_info: serde_json::Value", - "ordinal": 1, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - true - ] - }, - "hash": "227539374e7473f6f9642289c5b5d1bcd636315ab23537cb5f6d2f82a2bcb7bf" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-25300e435780101fa207c8e26ef2f49ba5db84d63e89440bb494e8327fe73686.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-25300e435780101fa207c8e26ef2f49ba5db84d63e89440bb494e8327fe73686.json deleted file mode 100644 index fa5ef3a17f..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-25300e435780101fa207c8e26ef2f49ba5db84d63e89440bb494e8327fe73686.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT gateway_identity_key\n FROM gateways\n WHERE bonded = true\n ", - "describe": { - "columns": [ - { - "name": "gateway_identity_key", - "ordinal": 0, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false - ] - }, - "hash": "25300e435780101fa207c8e26ef2f49ba5db84d63e89440bb494e8327fe73686" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-283f49a65c7d70bf271702ff6a5c7ad6e68c81932d295ff18ed198c54706a57c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-283f49a65c7d70bf271702ff6a5c7ad6e68c81932d295ff18ed198c54706a57c.json deleted file mode 100644 index 5413fe16e5..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-283f49a65c7d70bf271702ff6a5c7ad6e68c81932d295ff18ed198c54706a57c.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n node_id,\n ed25519_identity_pubkey,\n total_stake,\n ip_addresses as \"ip_addresses!: serde_json::Value\",\n mix_port,\n x25519_sphinx_pubkey,\n node_role as \"node_role: serde_json::Value\",\n supported_roles as \"supported_roles: serde_json::Value\",\n entry as \"entry: serde_json::Value\",\n performance,\n self_described as \"self_described: serde_json::Value\",\n bond_info as \"bond_info: serde_json::Value\"\n FROM\n nym_nodes\n WHERE\n self_described IS NOT NULL\n AND\n bond_info IS NOT NULL\n ", - "describe": { - "columns": [ - { - "name": "node_id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "ed25519_identity_pubkey", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "total_stake", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "ip_addresses!: serde_json::Value", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "mix_port", - "ordinal": 4, - "type_info": "Integer" - }, - { - "name": "x25519_sphinx_pubkey", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "node_role: serde_json::Value", - "ordinal": 6, - "type_info": "Text" - }, - { - "name": "supported_roles: serde_json::Value", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "entry: serde_json::Value", - "ordinal": 8, - "type_info": "Text" - }, - { - "name": "performance", - "ordinal": 9, - "type_info": "Text" - }, - { - "name": "self_described: serde_json::Value", - "ordinal": 10, - "type_info": "Text" - }, - { - "name": "bond_info: serde_json::Value", - "ordinal": 11, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - true, - false, - true, - true - ] - }, - "hash": "283f49a65c7d70bf271702ff6a5c7ad6e68c81932d295ff18ed198c54706a57c" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-3243cf5646255a9430d1e6710970505d0dbcc62703f40e090e80ff48c77723c4.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-3243cf5646255a9430d1e6710970505d0dbcc62703f40e090e80ff48c77723c4.json deleted file mode 100644 index 7aeb02d48b..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-3243cf5646255a9430d1e6710970505d0dbcc62703f40e090e80ff48c77723c4.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT OR IGNORE INTO gateway_session_stats\n (gateway_identity_key, node_id, day,\n unique_active_clients, session_started, users_hashes,\n vpn_sessions, mixnet_sessions, unknown_sessions)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 9 - }, - "nullable": [] - }, - "hash": "3243cf5646255a9430d1e6710970505d0dbcc62703f40e090e80ff48c77723c4" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-3cd5cb4bfca4243925da4ddbccd811e842090e98982e1032670df77961870b32.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-3cd5cb4bfca4243925da4ddbccd811e842090e98982e1032670df77961870b32.json deleted file mode 100644 index 60ecc30618..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-3cd5cb4bfca4243925da4ddbccd811e842090e98982e1032670df77961870b32.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO mixnodes\n (mix_id, identity_key, bonded, total_stake,\n host, http_api_port, full_details,\n self_described, last_updated_utc, is_dp_delegatee)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(mix_id) DO UPDATE SET\n bonded=excluded.bonded,\n total_stake=excluded.total_stake, host=excluded.host,\n http_api_port=excluded.http_api_port,\n full_details=excluded.full_details,self_described=excluded.self_described,\n last_updated_utc=excluded.last_updated_utc,\n is_dp_delegatee = excluded.is_dp_delegatee;", - "describe": { - "columns": [], - "parameters": { - "Right": 10 - }, - "nullable": [] - }, - "hash": "3cd5cb4bfca4243925da4ddbccd811e842090e98982e1032670df77961870b32" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-3e7e987780937873cdb393b157d7708c9f01047b0689eb0d4f7a973b328c609d.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-3e7e987780937873cdb393b157d7708c9f01047b0689eb0d4f7a973b328c609d.json deleted file mode 100644 index d6b7e9be72..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-3e7e987780937873cdb393b157d7708c9f01047b0689eb0d4f7a973b328c609d.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n id as \"id!\",\n gateway_identity_key as \"gateway_identity_key!\",\n self_described as \"self_described?\",\n explorer_pretty_bond as \"explorer_pretty_bond?\"\n FROM gateways\n WHERE gateway_identity_key = ?\n AND bonded = true\n ORDER BY gateway_identity_key\n LIMIT 1", - "describe": { - "columns": [ - { - "name": "id!", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "gateway_identity_key!", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "self_described?", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "explorer_pretty_bond?", - "ordinal": 3, - "type_info": "Text" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - true, - false, - false, - true - ] - }, - "hash": "3e7e987780937873cdb393b157d7708c9f01047b0689eb0d4f7a973b328c609d" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-3eb1d8491bda3c1d6e071b6eb364b9a979f4bdb11ea81b2d0f022555bab51ecb.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-3eb1d8491bda3c1d6e071b6eb364b9a979f4bdb11ea81b2d0f022555bab51ecb.json deleted file mode 100644 index 78a970dc41..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-3eb1d8491bda3c1d6e071b6eb364b9a979f4bdb11ea81b2d0f022555bab51ecb.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n gw.gateway_identity_key as \"gateway_identity_key!\",\n gw.bonded as \"bonded: bool\",\n gw.performance as \"performance!\",\n gw.self_described as \"self_described?\",\n gw.explorer_pretty_bond as \"explorer_pretty_bond?\",\n gw.last_probe_result as \"last_probe_result?\",\n gw.last_probe_log as \"last_probe_log?\",\n gw.last_testrun_utc as \"last_testrun_utc?\",\n gw.last_updated_utc as \"last_updated_utc!\",\n COALESCE(gd.moniker, \"NA\") as \"moniker!\",\n COALESCE(gd.website, \"NA\") as \"website!\",\n COALESCE(gd.security_contact, \"NA\") as \"security_contact!\",\n COALESCE(gd.details, \"NA\") as \"details!\"\n FROM gateways gw\n LEFT JOIN gateway_description gd\n ON gw.gateway_identity_key = gd.gateway_identity_key\n ORDER BY gw.gateway_identity_key", - "describe": { - "columns": [ - { - "name": "gateway_identity_key!", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "bonded: bool", - "ordinal": 1, - "type_info": "Integer" - }, - { - "name": "performance!", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "self_described?", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "explorer_pretty_bond?", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "last_probe_result?", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "last_probe_log?", - "ordinal": 6, - "type_info": "Text" - }, - { - "name": "last_testrun_utc?", - "ordinal": 7, - "type_info": "Integer" - }, - { - "name": "last_updated_utc!", - "ordinal": 8, - "type_info": "Integer" - }, - { - "name": "moniker!", - "ordinal": 9, - "type_info": "Text" - }, - { - "name": "website!", - "ordinal": 10, - "type_info": "Text" - }, - { - "name": "security_contact!", - "ordinal": 11, - "type_info": "Text" - }, - { - "name": "details!", - "ordinal": 12, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - false, - false, - false, - true, - true, - true, - true, - false, - false, - false, - false, - false - ] - }, - "hash": "3eb1d8491bda3c1d6e071b6eb364b9a979f4bdb11ea81b2d0f022555bab51ecb" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-3fc2baabf194b147b20be2a49401cc0c100a1d7a7c347393adde2410fa6f4dfe.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-3fc2baabf194b147b20be2a49401cc0c100a1d7a7c347393adde2410fa6f4dfe.json deleted file mode 100644 index 9f31fbd172..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-3fc2baabf194b147b20be2a49401cc0c100a1d7a7c347393adde2410fa6f4dfe.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n total_stake\n FROM mixnodes\n WHERE mix_id = ?\n ", - "describe": { - "columns": [ - { - "name": "total_stake", - "ordinal": 0, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false - ] - }, - "hash": "3fc2baabf194b147b20be2a49401cc0c100a1d7a7c347393adde2410fa6f4dfe" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c.json deleted file mode 100644 index f9eb3657e7..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE testruns SET status = ? WHERE id = ?", - "describe": { - "columns": [], - "parameters": { - "Right": 2 - }, - "nullable": [] - }, - "hash": "418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08.json deleted file mode 100644 index 6a9fd9d4eb..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE gateways SET last_probe_log = ? WHERE id = ?", - "describe": { - "columns": [], - "parameters": { - "Right": 2 - }, - "nullable": [] - }, - "hash": "4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-4d865e873c9cb133883da94db72dcdebd4969e1f240def9fb1bf946f4a1f342f.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-4d865e873c9cb133883da94db72dcdebd4969e1f240def9fb1bf946f4a1f342f.json deleted file mode 100644 index 08b9e93fff..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-4d865e873c9cb133883da94db72dcdebd4969e1f240def9fb1bf946f4a1f342f.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO testruns (gateway_id, status, ip_address, created_utc, log) VALUES (?, ?, ?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 5 - }, - "nullable": [] - }, - "hash": "4d865e873c9cb133883da94db72dcdebd4969e1f240def9fb1bf946f4a1f342f" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-4fca38abbb416d9457c34a8ba4faf481a837eda4f3e1bee1d430a4eb102a5b3d.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-4fca38abbb416d9457c34a8ba4faf481a837eda4f3e1bee1d430a4eb102a5b3d.json new file mode 100644 index 0000000000..ee07d52cc2 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/.sqlx/query-4fca38abbb416d9457c34a8ba4faf481a837eda4f3e1bee1d430a4eb102a5b3d.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO gateway_session_stats\n (gateway_identity_key, node_id, day,\n unique_active_clients, session_started, users_hashes,\n vpn_sessions, mixnet_sessions, unknown_sessions)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ON CONFLICT DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Int8", + "Date", + "Int8", + "Int8", + "Varchar", + "Varchar", + "Varchar", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "4fca38abbb416d9457c34a8ba4faf481a837eda4f3e1bee1d430a4eb102a5b3d" +} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-5912ea335a957d217f5e2b3a63a25b31715c2098310fe7a9db688bc2fd36aad4.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-5912ea335a957d217f5e2b3a63a25b31715c2098310fe7a9db688bc2fd36aad4.json deleted file mode 100644 index 08bc84273c..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-5912ea335a957d217f5e2b3a63a25b31715c2098310fe7a9db688bc2fd36aad4.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO nym_node_daily_mixing_stats (\n node_id, date_utc,\n total_stake, packets_received,\n packets_sent, packets_dropped\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(node_id, date_utc) DO UPDATE SET\n total_stake = excluded.total_stake,\n packets_received = nym_node_daily_mixing_stats.packets_received + excluded.packets_received,\n packets_sent = nym_node_daily_mixing_stats.packets_sent + excluded.packets_sent,\n packets_dropped = nym_node_daily_mixing_stats.packets_dropped + excluded.packets_dropped\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "5912ea335a957d217f5e2b3a63a25b31715c2098310fe7a9db688bc2fd36aad4" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-5e9cbb39f5fb53774803270f422989e199aac4d4a71913c7074359b4bd676b02.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-5e9cbb39f5fb53774803270f422989e199aac4d4a71913c7074359b4bd676b02.json deleted file mode 100644 index 0fd89dbe54..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-5e9cbb39f5fb53774803270f422989e199aac4d4a71913c7074359b4bd676b02.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE testruns\n SET\n status = ?,\n last_assigned_utc = ?\n WHERE rowid =\n (\n SELECT rowid\n FROM testruns\n WHERE status = ?\n ORDER BY created_utc asc\n LIMIT 1\n )\n RETURNING\n id as \"id!\",\n gateway_id\n ", - "describe": { - "columns": [ - { - "name": "id!", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "gateway_id", - "ordinal": 1, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 3 - }, - "nullable": [ - false, - false - ] - }, - "hash": "5e9cbb39f5fb53774803270f422989e199aac4d4a71913c7074359b4bd676b02" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-664e059ac2c58e1115fe214376a6b326b31c93298f20019772cce2e277a194f8.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-664e059ac2c58e1115fe214376a6b326b31c93298f20019772cce2e277a194f8.json deleted file mode 100644 index 2ba3ec13fa..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-664e059ac2c58e1115fe214376a6b326b31c93298f20019772cce2e277a194f8.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO nym_nodes\n (node_id, ed25519_identity_pubkey,\n total_stake,\n ip_addresses, mix_port,\n x25519_sphinx_pubkey, node_role,\n supported_roles, entry,\n self_described,\n bond_info,\n performance, last_updated_utc\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(node_id) DO UPDATE SET\n ed25519_identity_pubkey=excluded.ed25519_identity_pubkey,\n ip_addresses=excluded.ip_addresses,\n mix_port=excluded.mix_port,\n x25519_sphinx_pubkey=excluded.x25519_sphinx_pubkey,\n node_role=excluded.node_role,\n supported_roles=excluded.supported_roles,\n entry=excluded.entry,\n self_described=excluded.self_described,\n bond_info=excluded.bond_info,\n performance=excluded.performance,\n last_updated_utc=excluded.last_updated_utc\n ;", - "describe": { - "columns": [], - "parameters": { - "Right": 13 - }, - "nullable": [] - }, - "hash": "664e059ac2c58e1115fe214376a6b326b31c93298f20019772cce2e277a194f8" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b.json index dca66fc2ad..5e60c5a545 100644 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b.json +++ b/nym-node-status-api/nym-node-status-api/.sqlx/query-670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b.json @@ -1,19 +1,19 @@ { - "db_name": "SQLite", + "db_name": "PostgreSQL", "query": "SELECT count(id) FROM mixnodes", "describe": { "columns": [ { - "name": "count(id)", "ordinal": 0, - "type_info": "Integer" + "name": "count", + "type_info": "Int8" } ], "parameters": { - "Right": 0 + "Left": [] }, "nullable": [ - false + null ] }, "hash": "670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b" diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-6a9780aad1f2f0c8ef780e51fe856679d5e28f95143f4e2a2b409009dc0f55ba.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-6a9780aad1f2f0c8ef780e51fe856679d5e28f95143f4e2a2b409009dc0f55ba.json deleted file mode 100644 index 6ff736dbbb..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-6a9780aad1f2f0c8ef780e51fe856679d5e28f95143f4e2a2b409009dc0f55ba.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO nym_node_descriptions (\n node_id, moniker, website, security_contact, details, last_updated_utc\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (node_id) DO UPDATE SET\n moniker = excluded.moniker,\n website = excluded.website,\n security_contact = excluded.security_contact,\n details = excluded.details,\n last_updated_utc = excluded.last_updated_utc\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "6a9780aad1f2f0c8ef780e51fe856679d5e28f95143f4e2a2b409009dc0f55ba" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-6de15de62c0caa545910a17877a3ac5ebe44a398b199ab0120207a5569f54d0b.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-6de15de62c0caa545910a17877a3ac5ebe44a398b199ab0120207a5569f54d0b.json new file mode 100644 index 0000000000..b9690c68ee --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/.sqlx/query-6de15de62c0caa545910a17877a3ac5ebe44a398b199ab0120207a5569f54d0b.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO testruns (gateway_id, status, ip_address, created_utc, log) VALUES ($1, $2, $3, $4, $5) RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int4", + "Int4", + "Varchar", + "Int8", + "Varchar" + ] + }, + "nullable": [ + false + ] + }, + "hash": "6de15de62c0caa545910a17877a3ac5ebe44a398b199ab0120207a5569f54d0b" +} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f.json deleted file mode 100644 index 9d93b219f9..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE gateways SET last_probe_result = ? WHERE id = ?", - "describe": { - "columns": [], - "parameters": { - "Right": 2 - }, - "nullable": [] - }, - "hash": "6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-f75af377da33db1455c6e0f612e0fa9583888f343b8b59faf37fc6799b244379.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-74b76cd0d94c1afc51c21c14c12236a2b964b981c8cbcc282117fe9bc38338dd.json similarity index 61% rename from nym-node-status-api/nym-node-status-api/.sqlx/query-f75af377da33db1455c6e0f612e0fa9583888f343b8b59faf37fc6799b244379.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-74b76cd0d94c1afc51c21c14c12236a2b964b981c8cbcc282117fe9bc38338dd.json index 7e222fb211..7fa6221256 100644 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-f75af377da33db1455c6e0f612e0fa9583888f343b8b59faf37fc6799b244379.json +++ b/nym-node-status-api/nym-node-status-api/.sqlx/query-74b76cd0d94c1afc51c21c14c12236a2b964b981c8cbcc282117fe9bc38338dd.json @@ -1,66 +1,66 @@ { - "db_name": "SQLite", - "query": "SELECT\n mn.mix_id as \"mix_id!\",\n mn.bonded as \"bonded: bool\",\n mn.is_dp_delegatee as \"is_dp_delegatee: bool\",\n mn.total_stake as \"total_stake!\",\n mn.full_details as \"full_details!\",\n mn.self_described as \"self_described\",\n mn.last_updated_utc as \"last_updated_utc!\",\n COALESCE(md.moniker, \"NA\") as \"moniker!\",\n COALESCE(md.website, \"NA\") as \"website!\",\n COALESCE(md.security_contact, \"NA\") as \"security_contact!\",\n COALESCE(md.details, \"NA\") as \"details!\"\n FROM mixnodes mn\n LEFT JOIN mixnode_description md ON mn.mix_id = md.mix_id\n ORDER BY mn.mix_id", + "db_name": "PostgreSQL", + "query": "SELECT\n mn.mix_id as \"mix_id!\",\n mn.bonded as \"bonded: bool\",\n mn.is_dp_delegatee as \"is_dp_delegatee: bool\",\n mn.total_stake as \"total_stake!\",\n mn.full_details as \"full_details!\",\n mn.self_described as \"self_described\",\n mn.last_updated_utc as \"last_updated_utc!\",\n md.moniker as \"moniker!\",\n md.website as \"website!\",\n md.security_contact as \"security_contact!\",\n md.details as \"details!\"\n FROM mixnodes mn\n LEFT JOIN mixnode_description md ON mn.mix_id = md.mix_id\n ORDER BY mn.mix_id", "describe": { "columns": [ { - "name": "mix_id!", "ordinal": 0, - "type_info": "Integer" + "name": "mix_id!", + "type_info": "Int8" }, { - "name": "bonded: bool", "ordinal": 1, - "type_info": "Integer" + "name": "bonded: bool", + "type_info": "Bool" }, { - "name": "is_dp_delegatee: bool", "ordinal": 2, - "type_info": "Integer" + "name": "is_dp_delegatee: bool", + "type_info": "Bool" }, { - "name": "total_stake!", "ordinal": 3, - "type_info": "Integer" + "name": "total_stake!", + "type_info": "Int8" }, { - "name": "full_details!", "ordinal": 4, - "type_info": "Text" + "name": "full_details!", + "type_info": "Varchar" }, { - "name": "self_described", "ordinal": 5, - "type_info": "Text" + "name": "self_described", + "type_info": "Varchar" }, { - "name": "last_updated_utc!", "ordinal": 6, - "type_info": "Integer" + "name": "last_updated_utc!", + "type_info": "Int8" }, { - "name": "moniker!", "ordinal": 7, - "type_info": "Text" + "name": "moniker!", + "type_info": "Varchar" }, { - "name": "website!", "ordinal": 8, - "type_info": "Text" + "name": "website!", + "type_info": "Varchar" }, { - "name": "security_contact!", "ordinal": 9, - "type_info": "Text" + "name": "security_contact!", + "type_info": "Varchar" }, { - "name": "details!", "ordinal": 10, - "type_info": "Text" + "name": "details!", + "type_info": "Varchar" } ], "parameters": { - "Right": 0 + "Left": [] }, "nullable": [ false, @@ -70,11 +70,11 @@ true, true, false, - false, - false, - false, - false + true, + true, + true, + true ] }, - "hash": "f75af377da33db1455c6e0f612e0fa9583888f343b8b59faf37fc6799b244379" + "hash": "74b76cd0d94c1afc51c21c14c12236a2b964b981c8cbcc282117fe9bc38338dd" } diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d.json deleted file mode 100644 index e697946d25..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n id as \"id!\",\n date as \"date!\",\n timestamp_utc as \"timestamp_utc!\",\n value_json as \"value_json!\"\n FROM summary_history\n ORDER BY date DESC\n LIMIT 30", - "describe": { - "columns": [ - { - "name": "id!", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "date!", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "timestamp_utc!", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "value_json!", - "ordinal": 3, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - true, - false, - false, - true - ] - }, - "hash": "7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-786b6a5d954e38b204cebf322711c74c8cf1c08e5e2896a1d6d5b85c91991753.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-786b6a5d954e38b204cebf322711c74c8cf1c08e5e2896a1d6d5b85c91991753.json deleted file mode 100644 index b68078e512..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-786b6a5d954e38b204cebf322711c74c8cf1c08e5e2896a1d6d5b85c91991753.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n COALESCE(packets_received, 0) as \"packets_received!: _\",\n COALESCE(packets_sent, 0) as \"packets_sent!: _\",\n COALESCE(packets_dropped, 0) as \"packets_dropped!: _\"\n FROM mixnode_packet_stats_raw\n WHERE mix_id = ?\n ORDER BY timestamp_utc DESC\n LIMIT 1 OFFSET 1\n ", - "describe": { - "columns": [ - { - "name": "packets_received!: _", - "ordinal": 0, - "type_info": "Null" - }, - { - "name": "packets_sent!: _", - "ordinal": 1, - "type_info": "Null" - }, - { - "name": "packets_dropped!: _", - "ordinal": 2, - "type_info": "Null" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - null, - null, - null - ] - }, - "hash": "786b6a5d954e38b204cebf322711c74c8cf1c08e5e2896a1d6d5b85c91991753" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578.json deleted file mode 100644 index 5252ccf2e7..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO summary_history\n (date, timestamp_utc, value_json)\n VALUES (?, ?, ?)\n ON CONFLICT(date) DO UPDATE SET\n timestamp_utc=excluded.timestamp_utc,\n value_json=excluded.value_json;", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5.json index 89c1e86878..3d922cb7f0 100644 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5.json +++ b/nym-node-status-api/nym-node-status-api/.sqlx/query-86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5.json @@ -1,19 +1,19 @@ { - "db_name": "SQLite", + "db_name": "PostgreSQL", "query": "SELECT count(id) FROM gateways", "describe": { "columns": [ { - "name": "count(id)", "ordinal": 0, - "type_info": "Integer" + "name": "count", + "type_info": "Int8" } ], "parameters": { - "Right": 0 + "Left": [] }, "nullable": [ - false + null ] }, "hash": "86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5" diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-8bdf85a61e443fa5f4835bffd0bffc8ed1011f56714fde6007e50951e569854b.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-8bdf85a61e443fa5f4835bffd0bffc8ed1011f56714fde6007e50951e569854b.json deleted file mode 100644 index b3a5353a1f..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-8bdf85a61e443fa5f4835bffd0bffc8ed1011f56714fde6007e50951e569854b.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n COALESCE(packets_received, 0) as \"packets_received!: _\",\n COALESCE(packets_sent, 0) as \"packets_sent!: _\",\n COALESCE(packets_dropped, 0) as \"packets_dropped!: _\"\n FROM nym_nodes_packet_stats_raw\n WHERE node_id = ?\n ORDER BY timestamp_utc DESC\n LIMIT 1 OFFSET 1\n ", - "describe": { - "columns": [ - { - "name": "packets_received!: _", - "ordinal": 0, - "type_info": "Null" - }, - { - "name": "packets_sent!: _", - "ordinal": 1, - "type_info": "Null" - }, - { - "name": "packets_dropped!: _", - "ordinal": 2, - "type_info": "Null" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - null, - null, - null - ] - }, - "hash": "8bdf85a61e443fa5f4835bffd0bffc8ed1011f56714fde6007e50951e569854b" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8.json index 993afdac99..d87f4c21ca 100644 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8.json +++ b/nym-node-status-api/nym-node-status-api/.sqlx/query-930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8.json @@ -1,21 +1,21 @@ { - "db_name": "SQLite", + "db_name": "PostgreSQL", "query": "SELECT\n gateway_identity_key as \"gateway_identity_key!\",\n bonded as \"bonded: bool\"\n FROM gateways\n ORDER BY last_testrun_utc", "describe": { "columns": [ { - "name": "gateway_identity_key!", "ordinal": 0, - "type_info": "Text" + "name": "gateway_identity_key!", + "type_info": "Varchar" }, { - "name": "bonded: bool", "ordinal": 1, - "type_info": "Integer" + "name": "bonded: bool", + "type_info": "Bool" } ], "parameters": { - "Right": 0 + "Left": [] }, "nullable": [ false, diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-9334f0c91252fcd7ec72558a271222615bb282e5334665700709ae475a5daea2.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-9334f0c91252fcd7ec72558a271222615bb282e5334665700709ae475a5daea2.json deleted file mode 100644 index 55968f7c3a..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-9334f0c91252fcd7ec72558a271222615bb282e5334665700709ae475a5daea2.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n node_id,\n ed25519_identity_pubkey,\n total_stake,\n ip_addresses as \"ip_addresses!: serde_json::Value\",\n mix_port,\n x25519_sphinx_pubkey,\n node_role as \"node_role: serde_json::Value\",\n supported_roles as \"supported_roles: serde_json::Value\",\n entry as \"entry: serde_json::Value\",\n performance,\n self_described as \"self_described: serde_json::Value\",\n bond_info as \"bond_info: serde_json::Value\"\n FROM\n nym_nodes\n ", - "describe": { - "columns": [ - { - "name": "node_id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "ed25519_identity_pubkey", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "total_stake", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "ip_addresses!: serde_json::Value", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "mix_port", - "ordinal": 4, - "type_info": "Integer" - }, - { - "name": "x25519_sphinx_pubkey", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "node_role: serde_json::Value", - "ordinal": 6, - "type_info": "Text" - }, - { - "name": "supported_roles: serde_json::Value", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "entry: serde_json::Value", - "ordinal": 8, - "type_info": "Text" - }, - { - "name": "performance", - "ordinal": 9, - "type_info": "Text" - }, - { - "name": "self_described: serde_json::Value", - "ordinal": 10, - "type_info": "Text" - }, - { - "name": "bond_info: serde_json::Value", - "ordinal": 11, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - true, - false, - true, - true - ] - }, - "hash": "9334f0c91252fcd7ec72558a271222615bb282e5334665700709ae475a5daea2" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-9796d354ae075eab4cbd3438839c39da94025494395ec7b093aefef696f2d0c5.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-9796d354ae075eab4cbd3438839c39da94025494395ec7b093aefef696f2d0c5.json deleted file mode 100644 index 967a2c648c..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-9796d354ae075eab4cbd3438839c39da94025494395ec7b093aefef696f2d0c5.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO gateways\n (gateway_identity_key, bonded,\n self_described, explorer_pretty_bond,\n last_updated_utc, performance)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(gateway_identity_key) DO UPDATE SET\n bonded=excluded.bonded,\n self_described=excluded.self_described,\n explorer_pretty_bond=excluded.explorer_pretty_bond,\n last_updated_utc=excluded.last_updated_utc,\n performance = excluded.performance;", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "9796d354ae075eab4cbd3438839c39da94025494395ec7b093aefef696f2d0c5" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-a1d9eb816acd1a91ed0975c801c9295c01a78861a2a0597ad28b1579a14bf008.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-a1d9eb816acd1a91ed0975c801c9295c01a78861a2a0597ad28b1579a14bf008.json deleted file mode 100644 index 7a370a2753..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-a1d9eb816acd1a91ed0975c801c9295c01a78861a2a0597ad28b1579a14bf008.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n id as \"id!\",\n gateway_id as \"gateway_id!\",\n status as \"status!\",\n created_utc as \"created_utc!\",\n ip_address as \"ip_address!\",\n log as \"log!\",\n last_assigned_utc\n FROM testruns\n WHERE gateway_id = ? AND status != 2\n ORDER BY id DESC\n LIMIT 1", - "describe": { - "columns": [ - { - "name": "id!", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "gateway_id!", - "ordinal": 1, - "type_info": "Integer" - }, - { - "name": "status!", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "created_utc!", - "ordinal": 3, - "type_info": "Integer" - }, - { - "name": "ip_address!", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "log!", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "last_assigned_utc", - "ordinal": 6, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - true - ] - }, - "hash": "a1d9eb816acd1a91ed0975c801c9295c01a78861a2a0597ad28b1579a14bf008" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-a79a61b87325f3f1d9c5a4fb386ccd585be0641e5878acb6283b879f22ed2b4c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-a79a61b87325f3f1d9c5a4fb386ccd585be0641e5878acb6283b879f22ed2b4c.json deleted file mode 100644 index d4324443b4..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-a79a61b87325f3f1d9c5a4fb386ccd585be0641e5878acb6283b879f22ed2b4c.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n COUNT(id) as \"count: i64\"\n FROM testruns\n WHERE\n status = ?\n ", - "describe": { - "columns": [ - { - "name": "count: i64", - "ordinal": 0, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false - ] - }, - "hash": "a79a61b87325f3f1d9c5a4fb386ccd585be0641e5878acb6283b879f22ed2b4c" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-aa602604c0e7b6eef41ea3cd83c16610e15be2d7ee3f6c4d3debf23f95fb9c2e.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-aa602604c0e7b6eef41ea3cd83c16610e15be2d7ee3f6c4d3debf23f95fb9c2e.json deleted file mode 100644 index 62ac2b4bb6..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-aa602604c0e7b6eef41ea3cd83c16610e15be2d7ee3f6c4d3debf23f95fb9c2e.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n nd.node_id,\n moniker,\n website,\n security_contact,\n details\n FROM\n nym_node_descriptions nd\n INNER JOIN\n nym_nodes\n WHERE\n bond_info IS NOT NULL\n ", - "describe": { - "columns": [ - { - "name": "node_id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "moniker", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "website", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "security_contact", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "details", - "ordinal": 4, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - true, - true, - true, - true - ] - }, - "hash": "aa602604c0e7b6eef41ea3cd83c16610e15be2d7ee3f6c4d3debf23f95fb9c2e" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-b68796d1d8d2384b30f1aace06269682c4ae96f774261f5c298264d3c12e5b67.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-b68796d1d8d2384b30f1aace06269682c4ae96f774261f5c298264d3c12e5b67.json deleted file mode 100644 index 383c3681f0..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-b68796d1d8d2384b30f1aace06269682c4ae96f774261f5c298264d3c12e5b67.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE nym_nodes\n SET\n self_described = NULL,\n bond_info = NULL", - "describe": { - "columns": [], - "parameters": { - "Right": 0 - }, - "nullable": [] - }, - "hash": "b68796d1d8d2384b30f1aace06269682c4ae96f774261f5c298264d3c12e5b67" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c.json deleted file mode 100644 index 8cc95e72de..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE gateways SET last_testrun_utc = ?, last_updated_utc = ? WHERE id = ?", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-c42917c9542c1d720d92035863064741aefc9f7a7d1630f6b863ebd8174b6684.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c42917c9542c1d720d92035863064741aefc9f7a7d1630f6b863ebd8174b6684.json deleted file mode 100644 index 86b8a642bd..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-c42917c9542c1d720d92035863064741aefc9f7a7d1630f6b863ebd8174b6684.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE\n mixnodes\n SET\n bonded = false\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 0 - }, - "nullable": [] - }, - "hash": "c42917c9542c1d720d92035863064741aefc9f7a7d1630f6b863ebd8174b6684" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-c7656b2b1b4328415772ce69d0568bd5438d6c8496ca9cbdcfb70bb5375b345e.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c7656b2b1b4328415772ce69d0568bd5438d6c8496ca9cbdcfb70bb5375b345e.json deleted file mode 100644 index 54fef15e56..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-c7656b2b1b4328415772ce69d0568bd5438d6c8496ca9cbdcfb70bb5375b345e.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n node_id,\n self_described as \"self_described: serde_json::Value\"\n FROM\n nym_nodes\n WHERE\n self_described IS NOT NULL\n ORDER BY\n node_id\n ", - "describe": { - "columns": [ - { - "name": "node_id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "self_described: serde_json::Value", - "ordinal": 1, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - true - ] - }, - "hash": "c7656b2b1b4328415772ce69d0568bd5438d6c8496ca9cbdcfb70bb5375b345e" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-c910788edefe64bbb34379702bcbde9ec6159c9fa03b13652e1f620dcd92125e.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c910788edefe64bbb34379702bcbde9ec6159c9fa03b13652e1f620dcd92125e.json deleted file mode 100644 index 5c88b5737e..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-c910788edefe64bbb34379702bcbde9ec6159c9fa03b13652e1f620dcd92125e.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT mix_id\n FROM mixnodes\n WHERE bonded = true\n ", - "describe": { - "columns": [ - { - "name": "mix_id", - "ordinal": 0, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false - ] - }, - "hash": "c910788edefe64bbb34379702bcbde9ec6159c9fa03b13652e1f620dcd92125e" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-c9e61180ec35dfab8623333fafa3b216b93440d0fddc0a37dd1b6c1813741f6a.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c9e61180ec35dfab8623333fafa3b216b93440d0fddc0a37dd1b6c1813741f6a.json deleted file mode 100644 index 0d313098b6..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-c9e61180ec35dfab8623333fafa3b216b93440d0fddc0a37dd1b6c1813741f6a.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "DELETE FROM gateway_session_stats WHERE day <= ?", - "describe": { - "columns": [], - "parameters": { - "Right": 1 - }, - "nullable": [] - }, - "hash": "c9e61180ec35dfab8623333fafa3b216b93440d0fddc0a37dd1b6c1813741f6a" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-d2e07d44594ca5b44a6100482ff432c39d761f2a0ac1d6515cf73416f2eb6c61.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-d2e07d44594ca5b44a6100482ff432c39d761f2a0ac1d6515cf73416f2eb6c61.json deleted file mode 100644 index 09138b5556..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-d2e07d44594ca5b44a6100482ff432c39d761f2a0ac1d6515cf73416f2eb6c61.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n total_stake\n FROM nym_nodes\n WHERE node_id = ?\n ", - "describe": { - "columns": [ - { - "name": "total_stake", - "ordinal": 0, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false - ] - }, - "hash": "d2e07d44594ca5b44a6100482ff432c39d761f2a0ac1d6515cf73416f2eb6c61" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe.json deleted file mode 100644 index 5f7443bd50..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO summary\n (key, value_json, last_updated_utc)\n VALUES (?, ?, ?)\n ON CONFLICT(key) DO UPDATE SET\n value_json=excluded.value_json,\n last_updated_utc=excluded.last_updated_utc;", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-e9790b63ebe4bff5172bb8cb7bfc288364855003cf0e4d63e95047e7b502c650.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-e9790b63ebe4bff5172bb8cb7bfc288364855003cf0e4d63e95047e7b502c650.json deleted file mode 100644 index 9f0d25204f..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-e9790b63ebe4bff5172bb8cb7bfc288364855003cf0e4d63e95047e7b502c650.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO gateway_description (\n gateway_identity_key,\n moniker,\n website,\n security_contact,\n details,\n last_updated_utc\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (gateway_identity_key) DO UPDATE SET\n moniker = excluded.moniker,\n website = excluded.website,\n security_contact = excluded.security_contact,\n details = excluded.details,\n last_updated_utc = excluded.last_updated_utc\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "e9790b63ebe4bff5172bb8cb7bfc288364855003cf0e4d63e95047e7b502c650" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-f0c64794cbaed87a1d3166251d8e6720c9a9fc929144188460849be85d915004.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-f0c64794cbaed87a1d3166251d8e6720c9a9fc929144188460849be85d915004.json deleted file mode 100644 index 7790b606af..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-f0c64794cbaed87a1d3166251d8e6720c9a9fc929144188460849be85d915004.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n id as \"id!\",\n gateway_id as \"gateway_id!\",\n status as \"status!\",\n created_utc as \"created_utc!\",\n ip_address as \"ip_address!\",\n log as \"log!\",\n last_assigned_utc\n FROM testruns\n WHERE\n id = ?\n AND\n status = ?\n ORDER BY created_utc\n LIMIT 1", - "describe": { - "columns": [ - { - "name": "id!", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "gateway_id!", - "ordinal": 1, - "type_info": "Integer" - }, - { - "name": "status!", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "created_utc!", - "ordinal": 3, - "type_info": "Integer" - }, - { - "name": "ip_address!", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "log!", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "last_assigned_utc", - "ordinal": 6, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - true - ] - }, - "hash": "f0c64794cbaed87a1d3166251d8e6720c9a9fc929144188460849be85d915004" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-f7e3fa31d68c028bf39cc95389f29f8758ec922dd2e7ea064a1e537e580c9ee5.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-f7e3fa31d68c028bf39cc95389f29f8758ec922dd2e7ea064a1e537e580c9ee5.json deleted file mode 100644 index da95053996..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-f7e3fa31d68c028bf39cc95389f29f8758ec922dd2e7ea064a1e537e580c9ee5.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE\n gateways\n SET\n bonded = false\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 0 - }, - "nullable": [] - }, - "hash": "f7e3fa31d68c028bf39cc95389f29f8758ec922dd2e7ea064a1e537e580c9ee5" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-fcb1698d9e0e3a14524c92e7c99a811588c2bbc50d4975487a0464321a1b18c9.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-fcb1698d9e0e3a14524c92e7c99a811588c2bbc50d4975487a0464321a1b18c9.json deleted file mode 100644 index 296c6389d2..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-fcb1698d9e0e3a14524c92e7c99a811588c2bbc50d4975487a0464321a1b18c9.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO nym_nodes_packet_stats_raw (\n node_id, timestamp_utc, packets_received, packets_sent, packets_dropped\n ) VALUES (?, ?, ?, ?, ?)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 5 - }, - "nullable": [] - }, - "hash": "fcb1698d9e0e3a14524c92e7c99a811588c2bbc50d4975487a0464321a1b18c9" -} diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index aa455a0ac6..8ff0de8917 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-api" -version = "3.1.2" +version = "3.2.2" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -28,11 +28,15 @@ moka = { workspace = true, features = ["future"] } # Nym API: revert after Cheddar is out nym-contracts-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } nym-mixnet-contract-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } -nym-bin-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = ["openapi"]} -nym-node-status-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } +nym-bin-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ + "openapi", +] } +nym-node-status-client = { path = "../nym-node-status-client" } nym-crypto = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } nym-http-api-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } -nym-http-api-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = ["middleware"]} +nym-http-api-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ + "middleware", +] } nym-network-defaults = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } nym-serde-helpers = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } nym-statistics-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } @@ -62,7 +66,7 @@ serde_json = { workspace = true } serde_json_path = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } -sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "time"] } +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "time"] } thiserror = { workspace = true } time = { workspace = true, features = ["formatting"] } tokio = { workspace = true, features = ["rt-multi-thread"] } @@ -77,16 +81,20 @@ utoipauto = { workspace = true } nym-node-metrics = { path = "../../nym-node/nym-node-metrics" } +[features] +default = ["pg"] +sqlite = ["sqlx/sqlite"] +pg = ["sqlx/postgres"] [build-dependencies] anyhow = { workspace = true } tokio = { workspace = true, features = ["macros"] } sqlx = { workspace = true, features = [ "runtime-tokio-rustls", - "sqlite", "macros", "migrate", ] } [dev-dependencies] +axum-test = "17.3.0" time = { workspace = true, features = ["macros"] } diff --git a/nym-node-status-api/nym-node-status-api/Dockerfile b/nym-node-status-api/nym-node-status-api/Dockerfile-pg similarity index 90% rename from nym-node-status-api/nym-node-status-api/Dockerfile rename to nym-node-status-api/nym-node-status-api/Dockerfile-pg index 6eeaf8c6ee..4a6c7b24c5 100644 --- a/nym-node-status-api/nym-node-status-api/Dockerfile +++ b/nym-node-status-api/nym-node-status-api/Dockerfile-pg @@ -2,9 +2,9 @@ FROM harbor.nymte.ch/dockerhub/rust:latest AS builder COPY ./ /usr/src/nym -WORKDIR /usr/src/nym/nym-node-status-api +WORKDIR /usr/src/nym/nym-node-status-api/nym-node-status-api/ -RUN cargo build --release +RUN cargo build --release --features pg #------------------------------------------------------------------- diff --git a/nym-node-status-api/nym-node-status-api/Dockerfile-sqlite b/nym-node-status-api/nym-node-status-api/Dockerfile-sqlite new file mode 100644 index 0000000000..c37afedb08 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/Dockerfile-sqlite @@ -0,0 +1,37 @@ +# this will only work with VPN, otherwise remove the harbor part +FROM harbor.nymte.ch/dockerhub/rust:latest AS builder + +COPY ./ /usr/src/nym +WORKDIR /usr/src/nym/nym-node-status-api/nym-node-status-api/ + +RUN cargo build --release --features sqlite --no-default-features + + +#------------------------------------------------------------------- +# The following environment variables are required at runtime: +# +# EXPLORER_API +# NYXD +# NYM_API +# DATABASE_URL +# +# And optionally: +# +# NYM_NODE_STATUS_API_NYM_HTTP_CACHE_TTL +# NYM_NODE_STATUS_API_HTTP_PORT +# NYM_API_CLIENT_TIMEOUT +# EXPLORER_CLIENT_TIMEOUT +# NODE_STATUS_API_MONITOR_REFRESH_INTERVAL +# NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL +# +# see https://github.com/nymtech/nym/blob/develop/nym-node-status-api/src/cli.rs for details +#------------------------------------------------------------------- + +FROM harbor.nymte.ch/dockerhub/ubuntu:24.04 + +RUN apt-get update && apt-get install -y ca-certificates + +WORKDIR /nym + +COPY --from=builder /usr/src/nym/target/release/nym-node-status-api ./ +ENTRYPOINT [ "/nym/nym-node-status-api" ] diff --git a/nym-node-status-api/nym-node-status-api/Makefile b/nym-node-status-api/nym-node-status-api/Makefile new file mode 100644 index 0000000000..5f2881c1b4 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/Makefile @@ -0,0 +1,117 @@ +# Makefile for nym-node-status-api database management + +# --- Configuration --- +TEST_DATABASE_URL := postgres://testuser:testpass@localhost:5433/nym_node_status_api_test + +# Docker compose service names +DB_SERVICE_NAME := postgres-test +DB_CONTAINER_NAME := nym_node_status_api_postgres_test + +# Default target +.PHONY: default +default: help + +# --- Main Targets --- +.PHONY: prepare-pg +prepare-pg: test-db-up test-db-wait test-db-migrate test-db-prepare test-db-down ## Setup PostgreSQL and prepare SQLx offline cache + +.PHONY: test-db +test-db: test-db-up test-db-wait test-db-migrate test-db-run test-db-down ## Run tests with PostgreSQL database + +.PHONY: dev-db +dev-db: test-db-up test-db-wait test-db-migrate ## Start PostgreSQL for development (keeps running) + @echo "PostgreSQL is running on port 5433" + @echo "Connection string: $(TEST_DATABASE_URL)" + +# --- Docker Compose Targets --- +.PHONY: test-db-up +test-db-up: ## Start the PostgreSQL test database in the background + @echo "Starting PostgreSQL test database..." + docker compose up -d $(DB_SERVICE_NAME) + +.PHONY: test-db-wait +test-db-wait: ## Wait for the PostgreSQL database to be healthy + @echo "Waiting for PostgreSQL database..." + @while ! docker inspect --format='{{.State.Health.Status}}' $(DB_CONTAINER_NAME) 2>/dev/null | grep -q 'healthy'; do \ + echo -n "."; \ + sleep 1; \ + done; \ + echo " Database is healthy!" + +.PHONY: test-db-down +test-db-down: ## Stop and remove the test database + @echo "Stopping PostgreSQL test database..." + docker compose down + +# --- SQLx Targets --- +.PHONY: test-db-migrate +test-db-migrate: ## Run database migrations against PostgreSQL + @echo "Running PostgreSQL migrations..." + DATABASE_URL="$(TEST_DATABASE_URL)" sqlx migrate run --source migrations_pg + +.PHONY: test-db-prepare +test-db-prepare: ## Run sqlx prepare for compile-time query verification + @echo "Running sqlx prepare for PostgreSQL..." + DATABASE_URL="$(TEST_DATABASE_URL)" cargo sqlx prepare -- --features pg + +# --- Build and Test Targets --- +.PHONY: test-db-run +test-db-run: ## Run tests with PostgreSQL feature + @echo "Running tests with PostgreSQL..." + DATABASE_URL="$(TEST_DATABASE_URL)" cargo test --features pg --no-default-features + +.PHONY: build-pg +build-pg: ## Build with PostgreSQL feature + @echo "Building with PostgreSQL feature..." + cargo build --features pg --no-default-features + +.PHONY: build-sqlite +build-sqlite: ## Build with SQLite feature (default) + @echo "Building with SQLite feature..." + cargo build --features sqlite --no-default-features + +.PHONY: check-pg +check-pg: ## Check code with PostgreSQL feature + @echo "Checking code with PostgreSQL feature..." + cargo check --features pg --no-default-features + +.PHONY: check-sqlite +check-sqlite: ## Check code with SQLite feature + @echo "Checking code with SQLite feature..." + cargo check --features sqlite --no-default-features + +.PHONY: clippy +clippy: clippy-pg clippy-sqlite + +.PHONY: clippy-pg +clippy-pg: ## Run clippy with PostgreSQL feature + @echo "Running clippy with PostgreSQL feature..." + cargo clippy --features pg --no-default-features -- -D warnings + +.PHONY: clippy-sqlite +clippy-sqlite: ## Run clippy with SQLite feature (default) + @echo "Running clippy with SQLite feature..." + cargo clippy --features sqlite --no-default-features -- -D warnings + +# --- Cleanup Targets --- +.PHONY: clean +clean: ## Clean build artifacts and SQLx cache + cargo clean + rm -rf .sqlx + +.PHONY: clean-db +clean-db: test-db-down ## Stop database and clean volumes + docker volume rm -f nym-node-status-api_postgres_test_data 2>/dev/null || true + +# --- Utility Targets --- +.PHONY: sqlx-cli +sqlx-cli: ## Install sqlx-cli if not already installed + @command -v sqlx >/dev/null 2>&1 || cargo install sqlx-cli --features postgres,sqlite + +.PHONY: psql +psql: ## Connect to the running PostgreSQL database with psql + @docker exec -it $(DB_CONTAINER_NAME) psql -U testuser -d nym_node_status_api_test + +.PHONY: help +help: ## Show help for Makefile targets + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/nym-node-status-api/nym-node-status-api/README_PG.md b/nym-node-status-api/nym-node-status-api/README_PG.md new file mode 100644 index 0000000000..8a6d2c3f0b --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/README_PG.md @@ -0,0 +1,104 @@ +# PostgreSQL Support for nym-node-status-api + +This project now supports both SQLite (default) and PostgreSQL databases. + +## Quick Start with PostgreSQL + +### 1. Install Prerequisites + +```bash +# Install sqlx-cli if not already installed +make sqlx-cli +``` + +### 2. Prepare PostgreSQL for Development + +```bash +# This will: +# - Start PostgreSQL in Docker +# - Run migrations +# - Generate SQLx offline query cache +# - Stop the database +make prepare-pg +``` + +### 3. Build with PostgreSQL + +```bash +# Build with PostgreSQL feature +make build-pg + +# Or manually: +cargo build --features pg --no-default-features +``` + +### 4. Run with PostgreSQL + +```bash +# Start PostgreSQL for development (keeps running) +make dev-db + +# In another terminal, run the application +DATABASE_URL=postgres://testuser:testpass@localhost:5433/nym_node_status_api_test \ +cargo run --features pg --no-default-features +``` + +## Database Features + +- `sqlite` (default): Uses SQLite database +- `pg`: Uses PostgreSQL database + +Only one database feature can be active at a time. + +## Migration Differences + +SQLite migrations are in `migrations/`, PostgreSQL migrations are in `migrations_pg/`. + +Key differences: +- **AUTOINCREMENT** → **SERIAL** +- **INTEGER CHECK (0,1)** → **BOOLEAN** +- **REAL** → **DOUBLE PRECISION** +- No table recreation needed for constraint changes in PostgreSQL + +## Makefile Targets + +```bash +make help # Show all available targets +make prepare-pg # Setup PostgreSQL and prepare SQLx cache +make dev-db # Start PostgreSQL for development +make test-db # Run tests with PostgreSQL +make build-pg # Build with PostgreSQL +make build-sqlite # Build with SQLite +make psql # Connect to running PostgreSQL +make clean # Clean build artifacts +make clean-db # Stop database and clean volumes +``` + +## Environment Variables + +See `.env.example` for all configuration options. Key variable: + +```bash +# For PostgreSQL: +DATABASE_URL=postgres://testuser:testpass@localhost:5433/nym_node_status_api_test + +# For SQLite: +DATABASE_URL=sqlite://nym-node-status-api.sqlite +``` + +## Troubleshooting + +### SQLx Offline Mode + +If you see "no cached data for this query" errors: + +1. Ensure PostgreSQL is running: `make dev-db` +2. Run: `make test-db-prepare` + +### Connection Refused + +If you see "Connection refused" errors: + +1. Check Docker is running: `docker ps` +2. Check PostgreSQL container: `docker ps | grep nym_node_status_api_postgres_test` +3. Restart database: `make test-db-down && make dev-db` \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/build-push-node-status-api.sh b/nym-node-status-api/nym-node-status-api/build-push-node-status-api.sh new file mode 100755 index 0000000000..8aaa823ba6 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/build-push-node-status-api.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +# Build and push Node Status API container to harbor.nymte.ch + +set -e + +# Configuration +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +WORKING_DIRECTORY="${SCRIPT_DIR}" +CONTAINER_NAME="node-status-api" +REGISTRY="harbor.nymte.ch" +NAMESPACE="nym" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to display usage +usage() { + echo "Usage: $0 [pg|sqlite|both]" + echo " pg - Build and push PostgreSQL version" + echo " sqlite - Build and push SQLite version" + echo " both - Build and push both versions (default)" + exit 1 +} + +# Parse arguments +DB_TYPE="${1:-both}" + +if [[ ! "$DB_TYPE" =~ ^(pg|sqlite|both)$ ]]; then + usage +fi + +# Get version from Cargo.toml +VERSION=$(grep "^version = " "${WORKING_DIRECTORY}/Cargo.toml" | sed -E 's/version = "(.*)"/\1/') +if [ -z "$VERSION" ]; then + echo -e "${RED}Error: Could not extract version from Cargo.toml${NC}" + exit 1 +fi +echo -e "${YELLOW}Version: ${VERSION}${NC}" + +# Login to Harbor +echo -e "${GREEN}Logging into Harbor...${NC}" +docker login "${REGISTRY}" + +# Function to build and push +build_and_push() { + local db_type=$1 + local dockerfile="Dockerfile-${db_type}" + + echo -e "${GREEN}Building ${db_type} container...${NC}" + # Build from repository root (two levels up from script location) + docker build -f "${WORKING_DIRECTORY}/${dockerfile}" "${SCRIPT_DIR}/../.." \ + -t "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:${VERSION}-${db_type}" \ + -t "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:latest-${db_type}" + + echo -e "${GREEN}Pushing ${db_type} container to Harbor...${NC}" + docker push "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:${VERSION}-${db_type}" + docker push "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:latest-${db_type}" + + echo -e "${GREEN}Successfully built and pushed ${CONTAINER_NAME}:${VERSION}-${db_type}${NC}" +} + +# Build based on selection +case "$DB_TYPE" in + pg) + build_and_push "pg" + ;; + sqlite) + build_and_push "sqlite" + ;; + both) + build_and_push "pg" + echo "" + build_and_push "sqlite" + ;; +esac + +echo -e "${GREEN}All builds completed successfully!${NC}" \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/build.rs b/nym-node-status-api/nym-node-status-api/build.rs index 9da6d48d2a..a3b6635aef 100644 --- a/nym-node-status-api/nym-node-status-api/build.rs +++ b/nym-node-status-api/nym-node-status-api/build.rs @@ -1,17 +1,20 @@ -use anyhow::{anyhow, Result}; +use anyhow::Result; +#[cfg(feature = "sqlite")] use sqlx::{Connection, SqliteConnection}; +#[cfg(feature = "sqlite")] #[cfg(target_family = "unix")] use std::fs::Permissions; +#[cfg(feature = "sqlite")] #[cfg(target_family = "unix")] use std::os::unix::fs::PermissionsExt; +#[cfg(feature = "sqlite")] use tokio::{fs::File, io::AsyncWriteExt}; +#[cfg(feature = "sqlite")] const SQLITE_DB_FILENAME: &str = "nym-node-status-api.sqlite"; -/// If you need to re-run migrations or reset the db, just run -/// cargo clean -p nym-node-status-api -#[tokio::main(flavor = "current_thread")] -async fn main() -> Result<()> { +#[cfg(feature = "sqlite")] +async fn init_db() -> Result<()> { let out_dir = read_env_var("OUT_DIR")?; let database_path = format!("{out_dir}/{SQLITE_DB_FILENAME}?mode=rwc"); @@ -30,11 +33,22 @@ async fn main() -> Result<()> { Ok(()) } +/// If you need to re-run migrations or reset the db, just run +/// cargo clean -p nym-node-status-api +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<()> { + #[cfg(feature = "sqlite")] + init_db().await?; + Ok(()) +} + +#[cfg(feature = "sqlite")] fn read_env_var(var: &str) -> Result { - std::env::var(var).map_err(|_| anyhow!("You need to set {} env var", var)) + std::env::var(var).map_err(|_| anyhow::anyhow!("You need to set {} env var", var)) } /// use `./enter_db.sh` to inspect DB +#[cfg(feature = "sqlite")] async fn write_db_path_to_file(out_dir: &str, db_filename: &str) -> anyhow::Result<()> { let mut file = File::create("settings.sql").await?; let settings = ".mode columns diff --git a/nym-node-status-api/nym-node-status-api/docker-compose.yml b/nym-node-status-api/nym-node-status-api/docker-compose.yml new file mode 100644 index 0000000000..12dbe51031 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/docker-compose.yml @@ -0,0 +1,21 @@ +services: + postgres-test: + image: postgres:16-alpine + container_name: nym_node_status_api_postgres_test + environment: + POSTGRES_DB: nym_node_status_api_test + POSTGRES_USER: testuser + POSTGRES_PASSWORD: testpass + ports: + - '5433:5432' # Map to 5433 to avoid conflicts with default PostgreSQL + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U testuser -d nym_node_status_api_test'] + interval: 5s + timeout: 5s + retries: 5 + # Optional: Add volume for persistent data during development + # volumes: + # - postgres_test_data:/var/lib/postgresql/data + +# volumes: +# postgres_test_data: \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations/008_performance_indexes.sql b/nym-node-status-api/nym-node-status-api/migrations/008_performance_indexes.sql new file mode 100644 index 0000000000..235b50e171 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations/008_performance_indexes.sql @@ -0,0 +1,16 @@ +-- Add partial indexes for NOT NULL filtering to improve performance of /explorer/v3/nodes endpoint + +-- Index for queries filtering on self_described IS NOT NULL +CREATE INDEX IF NOT EXISTS idx_nym_nodes_self_described_not_null +ON nym_nodes(node_id) +WHERE self_described IS NOT NULL; + +-- Index for queries filtering on bond_info IS NOT NULL +CREATE INDEX IF NOT EXISTS idx_nym_nodes_bond_info_not_null +ON nym_nodes(node_id) +WHERE bond_info IS NOT NULL; + +-- Composite index for queries filtering on both bond_info AND self_described +CREATE INDEX IF NOT EXISTS idx_nym_nodes_bond_self_described +ON nym_nodes(node_id) +WHERE bond_info IS NOT NULL AND self_described IS NOT NULL; \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000000_init.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000000_init.sql new file mode 100644 index 0000000000..23f84e8667 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000000_init.sql @@ -0,0 +1,113 @@ +CREATE TABLE gateways +( + id SERIAL PRIMARY KEY, + gateway_identity_key VARCHAR NOT NULL UNIQUE, + self_described VARCHAR NOT NULL, + explorer_pretty_bond VARCHAR, + last_probe_result VARCHAR, + last_probe_log VARCHAR, + config_score INTEGER NOT NULL DEFAULT 0, + config_score_successes DOUBLE PRECISION NOT NULL DEFAULT 0, + config_score_samples DOUBLE PRECISION NOT NULL DEFAULT 0, + routing_score INTEGER NOT NULL DEFAULT 0, + routing_score_successes DOUBLE PRECISION NOT NULL DEFAULT 0, + routing_score_samples DOUBLE PRECISION NOT NULL DEFAULT 0, + test_run_samples DOUBLE PRECISION NOT NULL DEFAULT 0, + last_testrun_utc BIGINT, + last_updated_utc BIGINT NOT NULL, + bonded BOOLEAN NOT NULL DEFAULT FALSE, + blacklisted BOOLEAN NOT NULL DEFAULT FALSE, + performance INTEGER NOT NULL DEFAULT 0 +); + +CREATE INDEX idx_gateway_description_gateway_identity_key ON gateways (gateway_identity_key); + + +CREATE TABLE mixnodes ( + id SERIAL PRIMARY KEY, + identity_key VARCHAR NOT NULL UNIQUE, + mix_id BIGINT NOT NULL UNIQUE, + bonded BOOLEAN NOT NULL DEFAULT FALSE, + total_stake BIGINT NOT NULL, + host VARCHAR NOT NULL, + http_api_port BIGINT NOT NULL, + blacklisted BOOLEAN NOT NULL DEFAULT FALSE, + full_details VARCHAR, + self_described VARCHAR, + last_updated_utc BIGINT NOT NULL, + is_dp_delegatee BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE INDEX idx_mixnodes_mix_id ON mixnodes (mix_id); +CREATE INDEX idx_mixnodes_identity_key ON mixnodes (identity_key); + +CREATE TABLE mixnode_description ( + id SERIAL PRIMARY KEY, + mix_id BIGINT UNIQUE NOT NULL, + moniker VARCHAR, + website VARCHAR, + security_contact VARCHAR, + details VARCHAR, + last_updated_utc BIGINT NOT NULL, + FOREIGN KEY (mix_id) REFERENCES mixnodes (mix_id) +); + +-- Indexes for description table +CREATE INDEX idx_mixnode_description_mix_id ON mixnode_description (mix_id); + + +CREATE TABLE summary +( + key VARCHAR PRIMARY KEY, + value_json VARCHAR, + last_updated_utc BIGINT NOT NULL +); + + +CREATE TABLE summary_history +( + id SERIAL PRIMARY KEY, + date VARCHAR UNIQUE NOT NULL, + timestamp_utc BIGINT NOT NULL, + value_json VARCHAR +); + +CREATE INDEX idx_summary_history_timestamp_utc ON summary_history (timestamp_utc); +CREATE INDEX idx_summary_history_date ON summary_history (date); + + +CREATE TABLE gateway_description ( + id SERIAL PRIMARY KEY, + gateway_identity_key VARCHAR UNIQUE NOT NULL, + moniker VARCHAR, + website VARCHAR, + security_contact VARCHAR, + details VARCHAR, + last_updated_utc BIGINT NOT NULL, + FOREIGN KEY (gateway_identity_key) REFERENCES gateways (gateway_identity_key) +); + + +CREATE TABLE mixnode_daily_stats ( + id SERIAL PRIMARY KEY, + mix_id BIGINT NOT NULL, + total_stake BIGINT NOT NULL, + date_utc VARCHAR NOT NULL, + packets_received INTEGER DEFAULT 0, + packets_sent INTEGER DEFAULT 0, + packets_dropped INTEGER DEFAULT 0, + FOREIGN KEY (mix_id) REFERENCES mixnodes (mix_id), + UNIQUE (mix_id, date_utc) -- This constraint automatically creates an index +); + + +CREATE TABLE testruns +( + id SERIAL PRIMARY KEY, + gateway_id INTEGER NOT NULL, + status INTEGER NOT NULL, -- 0=pending, 1=in-progress, 2=complete + timestamp_utc BIGINT NOT NULL, + ip_address VARCHAR NOT NULL, + log VARCHAR NOT NULL, + FOREIGN KEY (gateway_id) REFERENCES gateways (id) +); \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000001_last_assigned_utc.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000001_last_assigned_utc.sql new file mode 100644 index 0000000000..f5929f0034 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000001_last_assigned_utc.sql @@ -0,0 +1,5 @@ +ALTER TABLE testruns +RENAME COLUMN timestamp_utc TO created_utc; + +ALTER TABLE testruns +ADD COLUMN last_assigned_utc BIGINT; \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000002_session_stats.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000002_session_stats.sql new file mode 100644 index 0000000000..e2d8e8440c --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000002_session_stats.sql @@ -0,0 +1,16 @@ +CREATE TABLE gateway_session_stats ( + id SERIAL PRIMARY KEY, + gateway_identity_key VARCHAR NOT NULL, + node_id BIGINT NOT NULL, + day DATE NOT NULL, + unique_active_clients BIGINT NOT NULL, + session_started BIGINT NOT NULL, + users_hashes VARCHAR, + vpn_sessions VARCHAR, + mixnet_sessions VARCHAR, + unknown_sessions VARCHAR, + UNIQUE (node_id, day) -- This constraint automatically creates an index +); + +CREATE INDEX idx_gateway_session_stats_identity_key ON gateway_session_stats (gateway_identity_key); +CREATE INDEX idx_gateway_session_stats_day ON gateway_session_stats (day); \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000003_scraper_tables.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000003_scraper_tables.sql new file mode 100644 index 0000000000..ba591789d5 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000003_scraper_tables.sql @@ -0,0 +1,11 @@ +CREATE TABLE mixnode_packet_stats_raw ( + id SERIAL PRIMARY KEY, + mix_id BIGINT NOT NULL, + timestamp_utc BIGINT NOT NULL, + packets_received INTEGER, + packets_sent INTEGER, + packets_dropped INTEGER, + FOREIGN KEY (mix_id) REFERENCES mixnodes (mix_id) +); + +CREATE INDEX idx_mixnode_packet_stats_raw_mix_id_timestamp_utc ON mixnode_packet_stats_raw (mix_id, timestamp_utc); \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000004_obsolete_fields.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000004_obsolete_fields.sql new file mode 100644 index 0000000000..a473ab1190 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000004_obsolete_fields.sql @@ -0,0 +1,54 @@ +ALTER TABLE mixnodes DROP COLUMN blacklisted; +ALTER TABLE gateways DROP COLUMN blacklisted; + +CREATE TABLE nym_nodes ( + node_id INTEGER PRIMARY KEY, + ed25519_identity_pubkey VARCHAR NOT NULL UNIQUE, + total_stake BIGINT NOT NULL, + ip_addresses TEXT NOT NULL, + mix_port INTEGER NOT NULL, + x25519_sphinx_pubkey VARCHAR NOT NULL UNIQUE, + node_role TEXT NOT NULL, + supported_roles TEXT NOT NULL, + performance VARCHAR NOT NULL, + entry TEXT, + last_updated_utc INTEGER NOT NULL +); + +CREATE INDEX idx_nym_nodes_node_id ON nym_nodes (node_id); +CREATE INDEX idx_nym_nodes_ed25519_identity_pubkey ON nym_nodes (ed25519_identity_pubkey); + +CREATE TABLE nym_node_descriptions ( + id SERIAL PRIMARY KEY, + node_id INTEGER UNIQUE NOT NULL, + moniker VARCHAR, + website VARCHAR, + security_contact VARCHAR, + details VARCHAR, + last_updated_utc INTEGER NOT NULL, + FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id) +); + +CREATE TABLE nym_nodes_packet_stats_raw ( + id SERIAL PRIMARY KEY, + node_id INTEGER NOT NULL, + timestamp_utc INTEGER NOT NULL, + packets_received INTEGER, + packets_sent INTEGER, + packets_dropped INTEGER, + FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id) +); + +CREATE INDEX idx_nym_nodes_packet_stats_raw_node_id_timestamp_utc ON nym_nodes_packet_stats_raw (node_id, timestamp_utc); + +CREATE TABLE nym_node_daily_mixing_stats ( + id SERIAL PRIMARY KEY, + node_id INTEGER NOT NULL, + total_stake BIGINT NOT NULL, + date_utc VARCHAR NOT NULL, + packets_received INTEGER DEFAULT 0, + packets_sent INTEGER DEFAULT 0, + packets_dropped INTEGER DEFAULT 0, + FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id), + UNIQUE (node_id, date_utc) -- This constraint automatically creates an index +); \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000005_node_self_described.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000005_node_self_described.sql new file mode 100644 index 0000000000..8e7bc7ef10 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000005_node_self_described.sql @@ -0,0 +1,23 @@ +ALTER TABLE nym_nodes ADD COLUMN self_described TEXT; +ALTER TABLE nym_nodes ADD COLUMN bond_info TEXT; + +-- PostgreSQL doesn't need table recreation for adding ON DELETE CASCADE +-- We can drop and recreate the foreign key constraints directly + +-- Drop existing foreign key constraints +ALTER TABLE nym_node_descriptions DROP CONSTRAINT IF EXISTS nym_node_descriptions_node_id_fkey; +ALTER TABLE nym_nodes_packet_stats_raw DROP CONSTRAINT IF EXISTS nym_nodes_packet_stats_raw_node_id_fkey; +ALTER TABLE nym_node_daily_mixing_stats DROP CONSTRAINT IF EXISTS nym_node_daily_mixing_stats_node_id_fkey; + +-- Add foreign key constraints with ON DELETE CASCADE +ALTER TABLE nym_node_descriptions + ADD CONSTRAINT nym_node_descriptions_node_id_fkey + FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id) ON DELETE CASCADE; + +ALTER TABLE nym_nodes_packet_stats_raw + ADD CONSTRAINT nym_nodes_packet_stats_raw_node_id_fkey + FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id) ON DELETE CASCADE; + +ALTER TABLE nym_node_daily_mixing_stats + ADD CONSTRAINT nym_node_daily_mixing_stats_node_id_fkey + FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id) ON DELETE CASCADE; \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000006_remove_unique_constraint.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000006_remove_unique_constraint.sql new file mode 100644 index 0000000000..98a7b75ca7 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000006_remove_unique_constraint.sql @@ -0,0 +1,9 @@ +-- Removing UNIQUE constraints on nym_nodes +-- In PostgreSQL, we can drop constraints directly without recreating the table + +-- Drop the unique constraints +ALTER TABLE nym_nodes DROP CONSTRAINT IF EXISTS nym_nodes_ed25519_identity_pubkey_key; +ALTER TABLE nym_nodes DROP CONSTRAINT IF EXISTS nym_nodes_x25519_sphinx_pubkey_key; + +-- The columns and indexes remain, only the unique constraints are removed +-- The existing indexes idx_nym_nodes_node_id and idx_nym_nodes_ed25519_identity_pubkey remain unchanged \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000007_date_fix.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000007_date_fix.sql new file mode 100644 index 0000000000..64fb3064f7 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000007_date_fix.sql @@ -0,0 +1,112 @@ +-- for a couple of days after migrating chrono -> time, we stored dates as +-- 2025-June-DD instead of 2025-06-DD. This migration fixes those entries. +-- +-- Because of a UNIQUE constraint on (node_id, date_utc), we can't just rename in-place. +-- - merge (add) node stats back to the original table where conflict (node_id, date_utc) would exist +-- - delete invalid records from original table (those stats were merged into correct rows above) +-- - insert rows that did not have a conflicting (node_Id, date_utc) combo +-- Conflicts affect only the date which has both kinds of entries, +-- e.g. 2025-06-05 and 2025-June-05 (date when this change was deployed) +-- +-- This applies to both affected tables. + +-- ---------------------------------------- +-- mixnode_daily_stats +-- ---------------------------------------- + +-- First, copy over rows with invalid date to a temp table (in the correct date format) +CREATE TEMP TABLE tmp_mix AS +SELECT + mix_id, + REPLACE(date_utc,'June','06') AS new_date, + SUM(total_stake) AS total_stake_sum, + SUM(packets_received) AS packets_received_sum, + SUM(packets_sent) AS packets_sent_sum, + SUM(packets_dropped) AS packets_dropped_sum +FROM mixnode_daily_stats +WHERE date_utc LIKE '%June%' +GROUP BY mix_id, new_date; + +UPDATE mixnode_daily_stats AS m +SET + total_stake = m.total_stake, + packets_received = m.packets_received + (SELECT packets_received_sum FROM tmp_mix WHERE mix_id = m.mix_id AND new_date = m.date_utc), + packets_sent = m.packets_sent + (SELECT packets_sent_sum FROM tmp_mix WHERE mix_id = m.mix_id AND new_date = m.date_utc), + packets_dropped = m.packets_dropped + (SELECT packets_dropped_sum FROM tmp_mix WHERE mix_id = m.mix_id AND new_date = m.date_utc) +WHERE EXISTS ( + SELECT 1 FROM tmp_mix + WHERE mix_id = m.mix_id + AND new_date = m.date_utc +); + +DELETE FROM mixnode_daily_stats + WHERE date_utc LIKE '%June%'; + +INSERT INTO mixnode_daily_stats + (mix_id, date_utc, total_stake, packets_received, packets_sent, packets_dropped) +SELECT + mix_id, + new_date, + total_stake_sum, + packets_received_sum, + packets_sent_sum, + packets_dropped_sum +FROM tmp_mix AS t +-- only those whose new_date did _not_ already exist +WHERE NOT EXISTS ( + SELECT 1 FROM mixnode_daily_stats AS m + WHERE m.mix_id = t.mix_id + AND m.date_utc = t.new_date +); + +DROP TABLE tmp_mix; + + +-- ---------------------------------------- +-- nym_node_daily_mixing_stats +-- ---------------------------------------- + +CREATE TEMP TABLE tmp_nym_node_stats AS +SELECT + node_id, + REPLACE(date_utc,'June','06') AS new_date, + SUM(total_stake) AS total_stake_sum, + SUM(packets_received) AS packets_received_sum, + SUM(packets_sent) AS packets_sent_sum, + SUM(packets_dropped) AS packets_dropped_sum +FROM nym_node_daily_mixing_stats +WHERE date_utc LIKE '%June%' +GROUP BY node_id, new_date; + +UPDATE nym_node_daily_mixing_stats AS m +SET + total_stake = m.total_stake, + packets_received = m.packets_received + (SELECT packets_received_sum FROM tmp_nym_node_stats WHERE node_id = m.node_id AND new_date = m.date_utc), + packets_sent = m.packets_sent + (SELECT packets_sent_sum FROM tmp_nym_node_stats WHERE node_id = m.node_id AND new_date = m.date_utc), + packets_dropped = m.packets_dropped + (SELECT packets_dropped_sum FROM tmp_nym_node_stats WHERE node_id = m.node_id AND new_date = m.date_utc) +WHERE EXISTS ( + SELECT 1 FROM tmp_nym_node_stats + WHERE node_id = m.node_id + AND new_date = m.date_utc +); + +DELETE FROM nym_node_daily_mixing_stats + WHERE date_utc LIKE '%June%'; + +INSERT INTO nym_node_daily_mixing_stats + (node_id, date_utc, total_stake, packets_received, packets_sent, packets_dropped) +SELECT + node_id, + new_date, + total_stake_sum, + packets_received_sum, + packets_sent_sum, + packets_dropped_sum +FROM tmp_nym_node_stats AS t +WHERE NOT EXISTS ( + SELECT 1 FROM nym_node_daily_mixing_stats AS m + WHERE m.node_id = t.node_id + AND m.date_utc = t.new_date +); + +DROP TABLE tmp_nym_node_stats; \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000008_jsonb_columns.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000008_jsonb_columns.sql new file mode 100644 index 0000000000..4ebfec1595 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000008_jsonb_columns.sql @@ -0,0 +1,3 @@ +-- Convert ip_addresses column from TEXT to JSONB for better type safety +ALTER TABLE nym_nodes + ALTER COLUMN ip_addresses TYPE JSONB USING ip_addresses::JSONB; \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000009_more_jsonb_columns.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000009_more_jsonb_columns.sql new file mode 100644 index 0000000000..1dcec55fea --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000009_more_jsonb_columns.sql @@ -0,0 +1,7 @@ +-- Convert remaining TEXT columns that store JSON to JSONB +ALTER TABLE nym_nodes + ALTER COLUMN node_role TYPE JSONB USING node_role::JSONB, + ALTER COLUMN supported_roles TYPE JSONB USING supported_roles::JSONB, + ALTER COLUMN entry TYPE JSONB USING entry::JSONB, + ALTER COLUMN self_described TYPE JSONB USING self_described::JSONB, + ALTER COLUMN bond_info TYPE JSONB USING bond_info::JSONB; \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000010_performance_indexes_postgres.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000010_performance_indexes_postgres.sql new file mode 100644 index 0000000000..15bee4ac30 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000010_performance_indexes_postgres.sql @@ -0,0 +1,17 @@ +-- Add partial indexes for NOT NULL filtering to improve performance of /explorer/v3/nodes endpoint +-- PostgreSQL version + +-- Index for queries filtering on self_described IS NOT NULL +CREATE INDEX IF NOT EXISTS idx_nym_nodes_self_described_not_null +ON nym_nodes(node_id) +WHERE self_described IS NOT NULL; + +-- Index for queries filtering on bond_info IS NOT NULL +CREATE INDEX IF NOT EXISTS idx_nym_nodes_bond_info_not_null +ON nym_nodes(node_id) +WHERE bond_info IS NOT NULL; + +-- Composite index for queries filtering on both bond_info AND self_described +CREATE INDEX IF NOT EXISTS idx_nym_nodes_bond_self_described +ON nym_nodes(node_id) +WHERE bond_info IS NOT NULL AND self_described IS NOT NULL; diff --git a/nym-node-status-api/nym-node-status-api/src/db/mod.rs b/nym-node-status-api/nym-node-status-api/src/db/mod.rs index 3f7d944906..964193455e 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/mod.rs @@ -1,24 +1,52 @@ use anyhow::{anyhow, Result}; -use sqlx::{ - migrate::Migrator, - query, - sqlite::{SqliteAutoVacuum, SqliteConnectOptions, SqliteSynchronous}, - ConnectOptions, SqlitePool, -}; use std::{str::FromStr, time::Duration}; pub(crate) mod models; pub(crate) mod queries; +pub(crate) mod query_wrapper; +#[cfg(test)] +mod tests; + +// Re-export the query wrapper functions for easier access +pub(crate) use query_wrapper::query; +#[allow(unused_imports)] +pub(crate) use query_wrapper::query_as; + +#[cfg(feature = "sqlite")] +use sqlx::{ + migrate::Migrator, + sqlite::{SqliteAutoVacuum, SqliteConnectOptions, SqliteSynchronous}, + ConnectOptions, SqlitePool, +}; + +#[cfg(feature = "pg")] +use sqlx::{migrate::Migrator, postgres::PgConnectOptions, ConnectOptions, PgPool}; + +#[cfg(feature = "sqlite")] static MIGRATOR: Migrator = sqlx::migrate!("./migrations"); +#[cfg(feature = "pg")] +static MIGRATOR: Migrator = sqlx::migrate!("./migrations_pg"); + +#[cfg(feature = "sqlite")] pub(crate) type DbPool = SqlitePool; +#[cfg(feature = "pg")] +pub(crate) type DbPool = PgPool; + +#[cfg(feature = "sqlite")] +pub(crate) type DbConnection = sqlx::pool::PoolConnection; + +#[cfg(feature = "pg")] +pub(crate) type DbConnection = sqlx::pool::PoolConnection; + pub(crate) struct Storage { pool: DbPool, } impl Storage { + #[cfg(feature = "sqlite")] pub async fn init(connection_url: String, busy_timeout: Duration) -> Result { let connect_options = SqliteConnectOptions::from_str(&connection_url)? .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) @@ -41,16 +69,39 @@ impl Storage { Ok(Storage { pool }) } + #[cfg(feature = "pg")] + pub async fn init(connection_url: String, _busy_timeout: Duration) -> Result { + use std::env; + let mut connect_options = + PgConnectOptions::from_str(&connection_url)?.disable_statement_logging(); + + let ssl_cert_path = env::var("PG_CERT").ok(); + + if let Some(ssl_cert) = ssl_cert_path { + connect_options = connect_options + .ssl_mode(sqlx::postgres::PgSslMode::Require) + .ssl_root_cert(ssl_cert); + } + let pool = sqlx::PgPool::connect_with(connect_options) + .await + .map_err(|err| anyhow!("Failed to connect to {}: {}", &connection_url, err))?; + + MIGRATOR.run(&pool).await?; + + Ok(Storage { pool }) + } + /// Cloning pool is cheap, it's the same underlying set of connections pub fn pool_owned(&self) -> DbPool { self.pool.clone() } + #[cfg(feature = "sqlite")] async fn assert_busy_timeout(pool: DbPool, expected_busy_timeout_s: i64) -> Result<()> { let mut conn = pool.acquire().await?; // Sqlite stores this value as miliseconds // https://www.sqlite.org/pragma.html#pragma_busy_timeout - let busy_timeout_db = query!("PRAGMA busy_timeout;") + let busy_timeout_db = sqlx::query!("PRAGMA busy_timeout;") .fetch_one(conn.as_mut()) .await?; diff --git a/nym-node-status-api/nym-node-status-api/src/db/models.rs b/nym-node-status-api/nym-node-status-api/src/db/models.rs index b74fdabde3..0fcd5adee4 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/models.rs @@ -38,11 +38,11 @@ pub(crate) struct GatewayInsertRecord { pub(crate) performance: u8, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, FromRow)] pub(crate) struct GatewayDto { pub(crate) gateway_identity_key: String, pub(crate) bonded: bool, - pub(crate) performance: i64, + pub(crate) performance: i32, pub(crate) self_described: Option, pub(crate) explorer_pretty_bond: Option, pub(crate) last_probe_result: Option, @@ -121,7 +121,7 @@ pub(crate) struct MixnodeRecord { pub(crate) is_dp_delegatee: bool, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, FromRow)] pub(crate) struct MixnodeDto { pub(crate) mix_id: i64, pub(crate) bonded: bool, @@ -183,14 +183,14 @@ pub(crate) struct BondedStatusDto { } #[allow(unused)] -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, FromRow)] pub(crate) struct SummaryDto { pub(crate) key: String, pub(crate) value_json: String, pub(crate) last_updated_utc: i64, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, FromRow)] pub(crate) struct SummaryHistoryDto { #[allow(dead_code)] pub id: i64, @@ -287,11 +287,11 @@ pub(crate) mod gateway { } #[allow(dead_code)] // not dead code, this is SQL data model -#[derive(Debug, Clone)] +#[derive(Debug, Clone, FromRow)] pub struct TestRunDto { - pub id: i64, - pub gateway_id: i64, - pub status: i64, + pub id: i32, + pub gateway_id: i32, + pub status: i32, pub created_utc: i64, pub ip_address: String, pub log: String, @@ -313,9 +313,9 @@ pub struct GatewayIdentityDto { } #[allow(dead_code)] // it's not dead code but clippy doesn't detect usage in sqlx macros -#[derive(Debug, Clone)] +#[derive(Debug, Clone, FromRow)] pub struct GatewayInfoDto { - pub id: i64, + pub id: i32, pub gateway_identity_key: String, pub self_described: Option, pub explorer_pretty_bond: Option, @@ -379,6 +379,7 @@ impl ScrapeNodeKind { } } +#[derive(Clone)] pub(crate) struct ScraperNodeInfo { pub node_kind: ScrapeNodeKind, pub hosts: Vec, @@ -412,11 +413,11 @@ impl ScraperNodeInfo { #[allow(dead_code)] // it's not dead code but clippy doesn't detect usage in sqlx macros #[derive(FromRow, Debug)] pub(crate) struct NymNodeDto { - pub node_id: i64, + pub node_id: i32, pub ed25519_identity_pubkey: String, pub total_stake: i64, pub ip_addresses: serde_json::Value, - pub mix_port: i64, + pub mix_port: i32, pub x25519_sphinx_pubkey: String, pub node_role: serde_json::Value, pub supported_roles: serde_json::Value, @@ -429,11 +430,11 @@ pub(crate) struct NymNodeDto { #[allow(dead_code)] // it's not dead code but clippy doesn't detect usage in sqlx macros #[derive(Debug)] pub(crate) struct NymNodeInsertRecord { - pub node_id: i64, + pub node_id: i32, pub ed25519_identity_pubkey: String, pub total_stake: i64, pub ip_addresses: serde_json::Value, - pub mix_port: i64, + pub mix_port: i32, pub x25519_sphinx_pubkey: String, pub node_role: serde_json::Value, pub supported_roles: serde_json::Value, @@ -441,7 +442,7 @@ pub(crate) struct NymNodeInsertRecord { pub entry: Option, pub self_described: Option, pub bond_info: Option, - pub last_updated_utc: String, + pub last_updated_utc: i64, } impl NymNodeInsertRecord { @@ -450,7 +451,7 @@ impl NymNodeInsertRecord { bond_info: Option<&NymNodeDetails>, self_described: Option<&NymNodeDescription>, ) -> anyhow::Result { - let now = OffsetDateTime::now_utc().to_string(); + let now = OffsetDateTime::now_utc().unix_timestamp(); // if bond info is missing, set stake to 0 let total_stake = bond_info @@ -461,11 +462,11 @@ impl NymNodeInsertRecord { let self_described = serialize_opt_to_value!(self_described)?; let record = Self { - node_id: skimmed_node.node_id.into(), + node_id: skimmed_node.node_id as i32, ed25519_identity_pubkey: skimmed_node.ed25519_identity_pubkey.to_base58_string(), total_stake, ip_addresses: serde_json::to_value(&skimmed_node.ip_addresses)?, - mix_port: skimmed_node.mix_port as i64, + mix_port: skimmed_node.mix_port as i32, x25519_sphinx_pubkey: skimmed_node.x25519_sphinx_pubkey.to_base58_string(), node_role: serde_json::to_value(&skimmed_node.role)?, supported_roles: serde_json::to_value(skimmed_node.supported_roles)?, @@ -514,11 +515,11 @@ impl TryFrom for SkimmedNode { } } -#[derive(Debug, Serialize, Deserialize, sqlx::Decode)] +#[derive(Debug, Serialize, Deserialize, sqlx::Decode, FromRow)] pub struct NodeStats { - pub packets_received: i64, - pub packets_sent: i64, - pub packets_dropped: i64, + pub packets_received: i32, + pub packets_sent: i32, + pub packets_dropped: i32, } pub struct InsertStatsRecord { diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/gateways.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways.rs index 961da755b2..4b0dc2c0f9 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/gateways.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways.rs @@ -3,32 +3,32 @@ use std::collections::HashSet; use crate::{ db::{ models::{GatewayDto, GatewayInsertRecord}, - DbPool, + DbConnection, DbPool, }, http::models::Gateway, node_scraper::helpers::NodeDescriptionResponse, }; use futures_util::TryStreamExt; -use sqlx::{pool::PoolConnection, Sqlite}; +use sqlx::Row; use tracing::error; pub(crate) async fn select_gateway_identity( - conn: &mut PoolConnection, - gateway_pk: i64, + conn: &mut DbConnection, + gateway_pk: i32, ) -> anyhow::Result { - let record = sqlx::query!( + let record = crate::db::query( r#"SELECT gateway_identity_key FROM gateways WHERE id = ?"#, - gateway_pk ) + .bind(gateway_pk) .fetch_one(conn.as_mut()) .await?; - Ok(record.gateway_identity_key) + Ok(record.try_get("gateway_identity_key")?) } pub(crate) async fn update_bonded_gateways( @@ -37,7 +37,7 @@ pub(crate) async fn update_bonded_gateways( ) -> anyhow::Result<()> { let mut tx = pool.begin().await?; - sqlx::query!( + crate::db::query( r#"UPDATE gateways SET @@ -48,7 +48,7 @@ pub(crate) async fn update_bonded_gateways( .await?; for record in gateways { - sqlx::query!( + crate::db::query( "INSERT INTO gateways (gateway_identity_key, bonded, self_described, explorer_pretty_bond, @@ -60,13 +60,13 @@ pub(crate) async fn update_bonded_gateways( explorer_pretty_bond=excluded.explorer_pretty_bond, last_updated_utc=excluded.last_updated_utc, performance = excluded.performance;", - record.identity_key, - record.bonded, - record.self_described, - record.explorer_pretty_bond, - record.last_updated_utc, - record.performance ) + .bind(record.identity_key) + .bind(record.bonded) + .bind(record.self_described) + .bind(record.explorer_pretty_bond) + .bind(record.last_updated_utc) + .bind(record.performance as i32) .execute(&mut *tx) .await?; } @@ -78,22 +78,21 @@ pub(crate) async fn update_bonded_gateways( pub(crate) async fn get_all_gateways(pool: &DbPool) -> anyhow::Result> { let mut conn = pool.acquire().await?; - let items = sqlx::query_as!( - GatewayDto, + let items = crate::db::query_as::( r#"SELECT - gw.gateway_identity_key as "gateway_identity_key!", - gw.bonded as "bonded: bool", - gw.performance as "performance!", - gw.self_described as "self_described?", - gw.explorer_pretty_bond as "explorer_pretty_bond?", - gw.last_probe_result as "last_probe_result?", - gw.last_probe_log as "last_probe_log?", - gw.last_testrun_utc as "last_testrun_utc?", - gw.last_updated_utc as "last_updated_utc!", - COALESCE(gd.moniker, "NA") as "moniker!", - COALESCE(gd.website, "NA") as "website!", - COALESCE(gd.security_contact, "NA") as "security_contact!", - COALESCE(gd.details, "NA") as "details!" + gw.gateway_identity_key, + gw.bonded, + gw.performance, + gw.self_described, + gw.explorer_pretty_bond, + gw.last_probe_result, + gw.last_probe_log, + gw.last_testrun_utc, + gw.last_updated_utc, + COALESCE(gd.moniker, 'NA') as moniker, + COALESCE(gd.website, 'NA') as website, + COALESCE(gd.security_contact, 'NA') as security_contact, + COALESCE(gd.details, 'NA') as details FROM gateways gw LEFT JOIN gateway_description gd ON gw.gateway_identity_key = gd.gateway_identity_key @@ -114,29 +113,29 @@ pub(crate) async fn get_all_gateways(pool: &DbPool) -> anyhow::Result anyhow::Result> { let mut conn = pool.acquire().await?; - let items = sqlx::query!( + let items = crate::db::query( r#" SELECT gateway_identity_key FROM gateways WHERE bonded = true - "# + "#, ) .fetch_all(&mut *conn) .await? .into_iter() - .map(|record| record.gateway_identity_key) + .map(|record| record.try_get::("gateway_identity_key").unwrap()) .collect::>(); Ok(items) } pub(crate) async fn insert_gateway_description( - conn: &mut PoolConnection, - identity_key: &str, - description: &NodeDescriptionResponse, + conn: &mut DbConnection, + identity_key: String, + description: NodeDescriptionResponse, timestamp: i64, ) -> anyhow::Result<()> { - sqlx::query!( + crate::db::query( r#" INSERT INTO gateway_description ( gateway_identity_key, @@ -153,15 +152,54 @@ pub(crate) async fn insert_gateway_description( details = excluded.details, last_updated_utc = excluded.last_updated_utc "#, - identity_key, - description.moniker, - description.website, - description.security_contact, - description.details, - timestamp, ) + .bind(identity_key) + .bind(description.moniker) + .bind(description.website) + .bind(description.security_contact) + .bind(description.details) + .bind(timestamp) .execute(conn.as_mut()) .await .map(drop) .map_err(From::from) } + +pub(crate) async fn get_or_create_gateway( + conn: &mut DbConnection, + gateway_identity_key: &str, +) -> anyhow::Result { + // Try to find existing gateway + let existing = crate::db::query("SELECT id FROM gateways WHERE gateway_identity_key = ?") + .bind(gateway_identity_key.to_string()) + .fetch_optional(conn.as_mut()) + .await?; + + if let Some(row) = existing { + return Ok(row.try_get("id")?); + } + + // Create new gateway + tracing::info!("Creating new gateway record for {}", gateway_identity_key); + let now = crate::utils::now_utc().unix_timestamp(); + + let result = crate::db::query( + r#"INSERT INTO gateways ( + gateway_identity_key, + bonded, + performance, + self_described, + last_updated_utc + ) VALUES (?, ?, ?, ?, ?) + RETURNING id"#, + ) + .bind(gateway_identity_key.to_string()) + .bind(true) // Assume bonded since being tested + .bind(0) // Initial performance + .bind("null") + .bind(now) + .fetch_one(conn.as_mut()) + .await?; + + Ok(result.try_get("id")?) +} diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs index 8ba813ee1f..bf90bff06b 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs @@ -6,6 +6,7 @@ use futures_util::TryStreamExt; use time::Date; use tracing::error; +#[cfg(feature = "sqlite")] pub(crate) async fn insert_session_records( pool: &DbPool, records: Vec, @@ -36,6 +37,38 @@ pub(crate) async fn insert_session_records( Ok(()) } +#[cfg(feature = "pg")] +pub(crate) async fn insert_session_records( + pool: &DbPool, + records: Vec, +) -> anyhow::Result<()> { + let mut tx = pool.begin().await?; + for record in records { + sqlx::query!( + "INSERT INTO gateway_session_stats + (gateway_identity_key, node_id, day, + unique_active_clients, session_started, users_hashes, + vpn_sessions, mixnet_sessions, unknown_sessions) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + ON CONFLICT DO NOTHING", + record.gateway_identity_key, + record.node_id, + record.day, + record.unique_active_clients, + record.session_started, + record.users_hashes, + record.vpn_sessions, + record.mixnet_sessions, + record.unknown_sessions, + ) + .execute(&mut *tx) + .await?; + } + tx.commit().await?; + + Ok(()) +} + pub(crate) async fn get_sessions_stats(pool: &DbPool) -> anyhow::Result> { let mut conn = pool.acquire().await?; let items = sqlx::query_as( @@ -68,7 +101,8 @@ pub(crate) async fn get_sessions_stats(pool: &DbPool) -> anyhow::Result anyhow::Result<()> { let mut conn = pool.acquire().await?; - sqlx::query!("DELETE FROM gateway_session_stats WHERE day <= ?", cut_off) + crate::db::query("DELETE FROM gateway_session_stats WHERE day <= ?") + .bind(cut_off) .execute(&mut *conn) .await?; Ok(()) diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/misc.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/misc.rs index fa56a9ef75..248b556c1d 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/misc.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/misc.rs @@ -6,8 +6,8 @@ use crate::db::{models::NetworkSummary, DbPool}; /// `daily_summary` pub(crate) async fn insert_summaries( pool: &DbPool, - summaries: &[(&str, usize)], - summary: &NetworkSummary, + summaries: Vec<(String, usize)>, + summary: NetworkSummary, last_updated: UtcDateTime, ) -> anyhow::Result<()> { insert_summary(pool, summaries, last_updated).await?; @@ -19,7 +19,7 @@ pub(crate) async fn insert_summaries( async fn insert_summary( pool: &DbPool, - summaries: &[(&str, usize)], + summaries: Vec<(String, usize)>, last_updated: UtcDateTime, ) -> anyhow::Result<()> { let timestamp = last_updated.unix_timestamp(); @@ -27,17 +27,17 @@ async fn insert_summary( for (kind, value) in summaries { let value = value.to_string(); - sqlx::query!( + crate::db::query( "INSERT INTO summary (key, value_json, last_updated_utc) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value_json=excluded.value_json, last_updated_utc=excluded.last_updated_utc;", - kind, - value, - timestamp ) + .bind(kind.clone()) + .bind(value) + .bind(timestamp) .execute(&mut *tx) .await .map_err(|err| { @@ -60,7 +60,7 @@ async fn insert_summary( /// This is not aggregate data, it's a set of latest data points async fn insert_summary_history( pool: &DbPool, - summary: &NetworkSummary, + summary: NetworkSummary, last_updated: UtcDateTime, ) -> anyhow::Result<()> { let mut conn = pool.acquire().await?; @@ -70,17 +70,17 @@ async fn insert_summary_history( let date = datetime_to_only_date_str(last_updated); - sqlx::query!( + crate::db::query( "INSERT INTO summary_history (date, timestamp_utc, value_json) VALUES (?, ?, ?) ON CONFLICT(date) DO UPDATE SET timestamp_utc=excluded.timestamp_utc, value_json=excluded.value_json;", - date, - timestamp, - value_json ) + .bind(date) + .bind(timestamp) + .bind(value_json) .execute(&mut *conn) .await?; diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/mixnodes.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/mixnodes.rs index ec8cf5c36c..2f53e52857 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/mixnodes.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/mixnodes.rs @@ -1,13 +1,13 @@ use std::collections::HashSet; use futures_util::TryStreamExt; -use sqlx::{pool::PoolConnection, Sqlite}; +use sqlx::Row; use tracing::error; use crate::{ db::{ models::{MixnodeDto, MixnodeRecord}, - DbPool, + DbConnection, DbPool, }, http::models::{DailyStats, Mixnode}, node_scraper::helpers::NodeDescriptionResponse, @@ -20,7 +20,7 @@ pub(crate) async fn update_mixnodes( let mut tx = pool.begin().await?; // mark all as unbonded - sqlx::query!( + crate::db::query( r#"UPDATE mixnodes SET @@ -31,9 +31,9 @@ pub(crate) async fn update_mixnodes( .await?; // existing nodes get updated on insert - for record in mixnodes.iter() { + for record in mixnodes.into_iter() { // https://www.sqlite.org/lang_upsert.html - sqlx::query!( + crate::db::query( "INSERT INTO mixnodes (mix_id, identity_key, bonded, total_stake, host, http_api_port, full_details, @@ -46,17 +46,17 @@ pub(crate) async fn update_mixnodes( full_details=excluded.full_details,self_described=excluded.self_described, last_updated_utc=excluded.last_updated_utc, is_dp_delegatee = excluded.is_dp_delegatee;", - record.mix_id, - record.identity_key, - record.bonded, - record.total_stake, - record.host, - record.http_port, - record.full_details, - record.self_described, - record.last_updated_utc, - record.is_dp_delegatee ) + .bind(record.mix_id as i64) + .bind(record.identity_key) + .bind(record.bonded) + .bind(record.total_stake) + .bind(record.host) + .bind(record.http_port as i32) + .bind(record.full_details) + .bind(record.self_described) + .bind(record.last_updated_utc) + .bind(record.is_dp_delegatee) .execute(&mut *tx) .await?; } @@ -78,10 +78,10 @@ pub(crate) async fn get_all_mixnodes(pool: &DbPool) -> anyhow::Result anyhow::Result anyhow::Result> { let mut conn = pool.acquire().await?; - let items = sqlx::query!( + let items = crate::db::query( r#" SELECT mix_id FROM mixnodes WHERE bonded = true - "# + "#, ) .fetch_all(&mut *conn) .await? .into_iter() - .map(|record| record.mix_id) + .map(|record| record.try_get::("mix_id").unwrap()) .collect::>(); Ok(items) } pub(crate) async fn insert_mixnode_description( - conn: &mut PoolConnection, - mix_id: &i64, - description: &NodeDescriptionResponse, + conn: &mut DbConnection, + mix_id: i64, + description: NodeDescriptionResponse, timestamp: i64, ) -> anyhow::Result<()> { - sqlx::query!( + crate::db::query( r#" INSERT INTO mixnode_description ( mix_id, moniker, website, security_contact, details, last_updated_utc @@ -178,13 +178,13 @@ pub(crate) async fn insert_mixnode_description( details = excluded.details, last_updated_utc = excluded.last_updated_utc "#, - mix_id, - description.moniker, - description.website, - description.security_contact, - description.details, - timestamp, ) + .bind(mix_id) + .bind(description.moniker) + .bind(description.website) + .bind(description.security_contact) + .bind(description.details) + .bind(timestamp) .execute(conn.as_mut()) .await .map(drop) diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs index 463a5c164d..012d32ae24 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs @@ -9,7 +9,8 @@ mod summary; pub(crate) mod testruns; pub(crate) use gateways::{ - get_all_gateways, get_bonded_gateway_id_keys, select_gateway_identity, update_bonded_gateways, + get_all_gateways, get_bonded_gateway_id_keys, get_or_create_gateway, select_gateway_identity, + update_bonded_gateways, }; pub(crate) use gateways_stats::{delete_old_records, get_sessions_stats, insert_session_records}; pub(crate) use misc::insert_summaries; diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs index ec32557d8c..0a0afc1893 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs @@ -4,14 +4,14 @@ use nym_validator_client::{ client::{NodeId, NymNodeDetails}, models::NymNodeDescription, }; -use sqlx::{pool::PoolConnection, Sqlite}; +use sqlx::Row; use std::collections::HashMap; use tracing::instrument; use crate::{ db::{ models::{NymNodeDto, NymNodeInsertRecord}, - DbPool, + DbConnection, DbPool, }, node_scraper::helpers::NodeDescriptionResponse, }; @@ -19,21 +19,20 @@ use crate::{ pub(crate) async fn get_all_nym_nodes(pool: &DbPool) -> anyhow::Result> { let mut conn = pool.acquire().await?; - sqlx::query_as!( - NymNodeDto, + crate::db::query_as::( r#"SELECT node_id, ed25519_identity_pubkey, total_stake, - ip_addresses as "ip_addresses!: serde_json::Value", + ip_addresses, mix_port, x25519_sphinx_pubkey, - node_role as "node_role: serde_json::Value", - supported_roles as "supported_roles: serde_json::Value", - entry as "entry: serde_json::Value", + node_role, + supported_roles, + entry, performance, - self_described as "self_described: serde_json::Value", - bond_info as "bond_info: serde_json::Value" + self_described, + bond_info FROM nym_nodes ORDER BY @@ -56,21 +55,20 @@ pub(crate) async fn get_described_bonded_nym_nodes( ) -> anyhow::Result> { let mut conn = pool.acquire().await?; - sqlx::query_as!( - NymNodeDto, + crate::db::query_as::( r#"SELECT node_id, ed25519_identity_pubkey, total_stake, - ip_addresses as "ip_addresses!: serde_json::Value", + ip_addresses, mix_port, x25519_sphinx_pubkey, - node_role as "node_role: serde_json::Value", - supported_roles as "supported_roles: serde_json::Value", - entry as "entry: serde_json::Value", + node_role, + supported_roles, + entry, performance, - self_described as "self_described: serde_json::Value", - bond_info as "bond_info: serde_json::Value" + self_described, + bond_info FROM nym_nodes WHERE @@ -92,7 +90,7 @@ pub(crate) async fn update_nym_nodes( ) -> anyhow::Result { let mut tx = pool.begin().await?; - sqlx::query!( + crate::db::query( "UPDATE nym_nodes SET self_described = NULL, @@ -104,7 +102,7 @@ pub(crate) async fn update_nym_nodes( let inserted = node_records.len(); for record in node_records { // https://www.sqlite.org/lang_upsert.html - sqlx::query!( + crate::db::query( "INSERT INTO nym_nodes (node_id, ed25519_identity_pubkey, total_stake, @@ -129,20 +127,20 @@ pub(crate) async fn update_nym_nodes( performance=excluded.performance, last_updated_utc=excluded.last_updated_utc ;", - record.node_id, - record.ed25519_identity_pubkey, - record.total_stake, - record.ip_addresses, - record.mix_port, - record.x25519_sphinx_pubkey, - record.node_role, - record.supported_roles, - record.entry, - record.self_described, - record.bond_info, - record.performance, - record.last_updated_utc, ) + .bind(record.node_id) + .bind(record.ed25519_identity_pubkey) + .bind(record.total_stake) + .bind(record.ip_addresses) + .bind(record.mix_port) + .bind(record.x25519_sphinx_pubkey) + .bind(record.node_role) + .bind(record.supported_roles) + .bind(record.entry) + .bind(record.self_described) + .bind(record.bond_info) + .bind(record.performance) + .bind(record.last_updated_utc) .execute(&mut *tx) .await .map_err(|e| anyhow::anyhow!("Failed to INSERT node_id={}: {}", record.node_id, e))?; @@ -150,6 +148,10 @@ pub(crate) async fn update_nym_nodes( tx.commit().await?; + tracing::debug!( + "Successfully inserted/updated {} nym_nodes records", + inserted + ); Ok(inserted) } @@ -158,10 +160,10 @@ pub(crate) async fn get_described_node_bond_info( ) -> anyhow::Result> { let mut conn = pool.acquire().await?; - sqlx::query!( + crate::db::query( r#"SELECT node_id, - bond_info as "bond_info: serde_json::Value" + bond_info FROM nym_nodes WHERE @@ -176,11 +178,11 @@ pub(crate) async fn get_described_node_bond_info( records .into_iter() .filter_map(|record| { - record - .bond_info - // only return details for nodes which have details stored - .and_then(|bond_info| serde_json::from_value::(bond_info).ok()) - .map(|res| (record.node_id as NodeId, res)) + let node_id: i32 = record.try_get("node_id").ok()?; + let bond_info: serde_json::Value = record.try_get("bond_info").ok()?; + serde_json::from_value::(bond_info) + .ok() + .map(|res| (node_id as i64 as NodeId, res)) }) .collect::>() }) @@ -192,10 +194,10 @@ pub(crate) async fn get_node_self_description( ) -> anyhow::Result> { let mut conn = pool.acquire().await?; - sqlx::query!( + crate::db::query( r#"SELECT node_id, - self_described as "self_described: serde_json::Value" + self_described FROM nym_nodes WHERE @@ -210,13 +212,11 @@ pub(crate) async fn get_node_self_description( records .into_iter() .filter_map(|record| { - record - .self_described - // only return details for nodes which have details stored - .and_then(|description| { - serde_json::from_value::(description).ok() - }) - .map(|res| (record.node_id as NodeId, res)) + let node_id: i32 = record.try_get("node_id").ok()?; + let self_described: serde_json::Value = record.try_get("self_described").ok()?; + serde_json::from_value::(self_described) + .ok() + .map(|res| (node_id as i64 as NodeId, res)) }) .collect::>() }) @@ -228,7 +228,7 @@ pub(crate) async fn get_bonded_node_description( ) -> anyhow::Result> { let mut conn = pool.acquire().await?; - sqlx::query!( + crate::db::query( r#"SELECT nd.node_id, moniker, @@ -238,7 +238,7 @@ pub(crate) async fn get_bonded_node_description( FROM nym_node_descriptions nd INNER JOIN - nym_nodes + nym_nodes nn on nd.node_id = nn.node_id WHERE bond_info IS NOT NULL "#, @@ -249,14 +249,15 @@ pub(crate) async fn get_bonded_node_description( records .into_iter() .map(|elem| { - let node_id: NodeId = elem.node_id.try_into().unwrap_or_default(); + let node_id: i64 = elem.try_get("node_id").unwrap_or(0); + let node_id: NodeId = node_id.try_into().unwrap_or_default(); ( node_id, NodeDescription { - moniker: elem.moniker.unwrap_or_default(), - website: elem.website.unwrap_or_default(), - security_contact: elem.security_contact.unwrap_or_default(), - details: elem.details.unwrap_or_default(), + moniker: elem.try_get("moniker").unwrap_or_default(), + website: elem.try_get("website").unwrap_or_default(), + security_contact: elem.try_get("security_contact").unwrap_or_default(), + details: elem.try_get("details").unwrap_or_default(), }, ) }) @@ -266,12 +267,12 @@ pub(crate) async fn get_bonded_node_description( } pub(crate) async fn insert_nym_node_description( - conn: &mut PoolConnection, - node_id: &i64, - description: &NodeDescriptionResponse, + conn: &mut DbConnection, + node_id: i64, + description: NodeDescriptionResponse, timestamp: i64, ) -> anyhow::Result<()> { - sqlx::query!( + crate::db::query( r#" INSERT INTO nym_node_descriptions ( node_id, moniker, website, security_contact, details, last_updated_utc @@ -283,13 +284,13 @@ pub(crate) async fn insert_nym_node_description( details = excluded.details, last_updated_utc = excluded.last_updated_utc "#, - node_id, - description.moniker, - description.website, - description.security_contact, - description.details, - timestamp, ) + .bind(node_id) + .bind(description.moniker) + .bind(description.website) + .bind(description.security_contact) + .bind(description.details) + .bind(timestamp) .execute(conn.as_mut()) .await .map(drop) diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/packet_stats.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/packet_stats.rs index e436fef8b4..fed7f8dc49 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/packet_stats.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/packet_stats.rs @@ -59,7 +59,8 @@ pub(crate) async fn batch_store_packet_stats( .map_err(|err| anyhow::anyhow!("Failed to commit: {err}")) } -async fn insert_node_packet_stats_uncommitted( +#[cfg(feature = "sqlite")] +pub(crate) async fn insert_node_packet_stats_uncommitted( tx: &mut Transaction<'static, sqlx::Sqlite>, node_kind: &ScrapeNodeKind, stats: &NodeStats, @@ -67,35 +68,35 @@ async fn insert_node_packet_stats_uncommitted( ) -> Result<()> { match node_kind { ScrapeNodeKind::LegacyMixnode { mix_id } => { - sqlx::query!( + sqlx::query( r#" INSERT INTO mixnode_packet_stats_raw ( mix_id, timestamp_utc, packets_received, packets_sent, packets_dropped ) VALUES (?, ?, ?, ?, ?) "#, - mix_id, - timestamp_utc, - stats.packets_received, - stats.packets_sent, - stats.packets_dropped, ) + .bind(mix_id) + .bind(timestamp_utc) + .bind(stats.packets_received) + .bind(stats.packets_sent) + .bind(stats.packets_dropped) .execute(tx.as_mut()) .await?; } ScrapeNodeKind::MixingNymNode { node_id } | ScrapeNodeKind::EntryExitNymNode { node_id, .. } => { - sqlx::query!( + sqlx::query( r#" INSERT INTO nym_nodes_packet_stats_raw ( node_id, timestamp_utc, packets_received, packets_sent, packets_dropped ) VALUES (?, ?, ?, ?, ?) "#, - node_id, - timestamp_utc, - stats.packets_received, - stats.packets_sent, - stats.packets_dropped, ) + .bind(node_id) + .bind(timestamp_utc) + .bind(stats.packets_received) + .bind(stats.packets_sent) + .bind(stats.packets_dropped) .execute(tx.as_mut()) .await?; } @@ -104,6 +105,53 @@ async fn insert_node_packet_stats_uncommitted( Ok(()) } +#[cfg(feature = "pg")] +pub(crate) async fn insert_node_packet_stats_uncommitted( + tx: &mut Transaction<'static, sqlx::Postgres>, + node_kind: &ScrapeNodeKind, + stats: &NodeStats, + timestamp_utc: i64, +) -> Result<()> { + match node_kind { + ScrapeNodeKind::LegacyMixnode { mix_id } => { + sqlx::query( + r#" + INSERT INTO mixnode_packet_stats_raw ( + mix_id, timestamp_utc, packets_received, packets_sent, packets_dropped + ) VALUES ($1, $2, $3, $4, $5) + "#, + ) + .bind(mix_id) + .bind(timestamp_utc) + .bind(stats.packets_received) + .bind(stats.packets_sent) + .bind(stats.packets_dropped) + .execute(tx.as_mut()) + .await?; + } + ScrapeNodeKind::MixingNymNode { node_id } + | ScrapeNodeKind::EntryExitNymNode { node_id, .. } => { + sqlx::query( + r#" + INSERT INTO nym_nodes_packet_stats_raw ( + node_id, timestamp_utc, packets_received, packets_sent, packets_dropped + ) VALUES ($1, $2, $3, $4, $5) + "#, + ) + .bind(node_id) + .bind(timestamp_utc) + .bind(stats.packets_received) + .bind(stats.packets_sent) + .bind(stats.packets_dropped) + .execute(tx.as_mut()) + .await?; + } + } + + Ok(()) +} + +#[cfg(feature = "sqlite")] pub(crate) async fn get_raw_node_stats( tx: &mut Transaction<'static, sqlx::Sqlite>, node_kind: &ScrapeNodeKind, @@ -112,39 +160,37 @@ pub(crate) async fn get_raw_node_stats( // if no packets are found, it's fine to assume 0 because that's also // SQL default value if none provided ScrapeNodeKind::LegacyMixnode { mix_id } => { - sqlx::query_as!( - NodeStats, + sqlx::query_as::<_, NodeStats>( r#" SELECT - COALESCE(packets_received, 0) as "packets_received!: _", - COALESCE(packets_sent, 0) as "packets_sent!: _", - COALESCE(packets_dropped, 0) as "packets_dropped!: _" + COALESCE(packets_received, 0) as packets_received, + COALESCE(packets_sent, 0) as packets_sent, + COALESCE(packets_dropped, 0) as packets_dropped FROM mixnode_packet_stats_raw WHERE mix_id = ? ORDER BY timestamp_utc DESC LIMIT 1 OFFSET 1 "#, - mix_id ) + .bind(mix_id) .fetch_optional(tx.as_mut()) .await? } ScrapeNodeKind::MixingNymNode { node_id } | ScrapeNodeKind::EntryExitNymNode { node_id, .. } => { - sqlx::query_as!( - NodeStats, + sqlx::query_as::<_, NodeStats>( r#" SELECT - COALESCE(packets_received, 0) as "packets_received!: _", - COALESCE(packets_sent, 0) as "packets_sent!: _", - COALESCE(packets_dropped, 0) as "packets_dropped!: _" + COALESCE(packets_received, 0) as packets_received, + COALESCE(packets_sent, 0) as packets_sent, + COALESCE(packets_dropped, 0) as packets_dropped FROM nym_nodes_packet_stats_raw WHERE node_id = ? ORDER BY timestamp_utc DESC LIMIT 1 OFFSET 1 "#, - node_id ) + .bind(node_id) .fetch_optional(tx.as_mut()) .await? } @@ -153,6 +199,55 @@ pub(crate) async fn get_raw_node_stats( Ok(packets) } +#[cfg(feature = "pg")] +pub(crate) async fn get_raw_node_stats( + tx: &mut Transaction<'static, sqlx::Postgres>, + node_kind: &ScrapeNodeKind, +) -> Result> { + let packets = match node_kind { + // if no packets are found, it's fine to assume 0 because that's also + // SQL default value if none provided + ScrapeNodeKind::LegacyMixnode { mix_id } => { + sqlx::query_as::<_, NodeStats>( + r#" + SELECT + COALESCE(packets_received, 0) as packets_received, + COALESCE(packets_sent, 0) as packets_sent, + COALESCE(packets_dropped, 0) as packets_dropped + FROM mixnode_packet_stats_raw + WHERE mix_id = $1 + ORDER BY timestamp_utc DESC + LIMIT 1 OFFSET 1 + "#, + ) + .bind(mix_id) + .fetch_optional(tx.as_mut()) + .await? + } + ScrapeNodeKind::MixingNymNode { node_id } + | ScrapeNodeKind::EntryExitNymNode { node_id, .. } => { + sqlx::query_as::<_, NodeStats>( + r#" + SELECT + COALESCE(packets_received, 0) as packets_received, + COALESCE(packets_sent, 0) as packets_sent, + COALESCE(packets_dropped, 0) as packets_dropped + FROM nym_nodes_packet_stats_raw + WHERE node_id = $1 + ORDER BY timestamp_utc DESC + LIMIT 1 OFFSET 1 + "#, + ) + .bind(node_id) + .fetch_optional(tx.as_mut()) + .await? + } + }; + + Ok(packets) +} + +#[cfg(feature = "sqlite")] pub(crate) async fn insert_daily_node_stats_uncommitted( tx: &mut Transaction<'static, sqlx::Sqlite>, node_kind: &ScrapeNodeKind, @@ -161,19 +256,19 @@ pub(crate) async fn insert_daily_node_stats_uncommitted( ) -> Result<()> { match node_kind { ScrapeNodeKind::LegacyMixnode { mix_id } => { - let total_stake = sqlx::query_scalar!( + let total_stake = sqlx::query_scalar::<_, i64>( r#" SELECT total_stake FROM mixnodes WHERE mix_id = ? "#, - mix_id ) + .bind(mix_id) .fetch_one(tx.as_mut()) .await?; - sqlx::query!( + sqlx::query( r#" INSERT INTO mixnode_daily_stats ( mix_id, date_utc, @@ -186,31 +281,31 @@ pub(crate) async fn insert_daily_node_stats_uncommitted( packets_sent = mixnode_daily_stats.packets_sent + excluded.packets_sent, packets_dropped = mixnode_daily_stats.packets_dropped + excluded.packets_dropped "#, - mix_id, - date_utc, - total_stake, - packets.packets_received, - packets.packets_sent, - packets.packets_dropped, ) + .bind(mix_id) + .bind(date_utc) + .bind(total_stake) + .bind(packets.packets_received) + .bind(packets.packets_sent) + .bind(packets.packets_dropped) .execute(tx.as_mut()) .await?; } ScrapeNodeKind::MixingNymNode { node_id } | ScrapeNodeKind::EntryExitNymNode { node_id, .. } => { - let total_stake = sqlx::query_scalar!( + let total_stake = sqlx::query_scalar::<_, i64>( r#" SELECT total_stake FROM nym_nodes WHERE node_id = ? "#, - node_id ) + .bind(node_id) .fetch_one(tx.as_mut()) .await?; - sqlx::query!( + sqlx::query( r#" INSERT INTO nym_node_daily_mixing_stats ( node_id, date_utc, @@ -223,13 +318,99 @@ pub(crate) async fn insert_daily_node_stats_uncommitted( packets_sent = nym_node_daily_mixing_stats.packets_sent + excluded.packets_sent, packets_dropped = nym_node_daily_mixing_stats.packets_dropped + excluded.packets_dropped "#, - node_id, - date_utc, - total_stake, - packets.packets_received, - packets.packets_sent, - packets.packets_dropped, ) + .bind(node_id) + .bind(date_utc) + .bind(total_stake) + .bind(packets.packets_received) + .bind(packets.packets_sent) + .bind(packets.packets_dropped) + .execute(tx.as_mut()) + .await?; + } + } + + Ok(()) +} + +#[cfg(feature = "pg")] +pub(crate) async fn insert_daily_node_stats_uncommitted( + tx: &mut Transaction<'static, sqlx::Postgres>, + node_kind: &ScrapeNodeKind, + date_utc: &str, + packets: NodeStats, +) -> Result<()> { + match node_kind { + ScrapeNodeKind::LegacyMixnode { mix_id } => { + let total_stake = sqlx::query_scalar::<_, i64>( + r#" + SELECT + total_stake + FROM mixnodes + WHERE mix_id = $1 + "#, + ) + .bind(mix_id) + .fetch_one(tx.as_mut()) + .await?; + + sqlx::query( + r#" + INSERT INTO mixnode_daily_stats ( + mix_id, date_utc, + total_stake, packets_received, + packets_sent, packets_dropped + ) VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT(mix_id, date_utc) DO UPDATE SET + total_stake = excluded.total_stake, + packets_received = mixnode_daily_stats.packets_received + excluded.packets_received, + packets_sent = mixnode_daily_stats.packets_sent + excluded.packets_sent, + packets_dropped = mixnode_daily_stats.packets_dropped + excluded.packets_dropped + "#, + ) + .bind(mix_id) + .bind(date_utc) + .bind(total_stake) + .bind(packets.packets_received) + .bind(packets.packets_sent) + .bind(packets.packets_dropped) + .execute(tx.as_mut()) + .await?; + } + ScrapeNodeKind::MixingNymNode { node_id } + | ScrapeNodeKind::EntryExitNymNode { node_id, .. } => { + let total_stake = sqlx::query_scalar::<_, i64>( + r#" + SELECT + total_stake + FROM nym_nodes + WHERE node_id = $1 + "#, + ) + .bind(node_id) + .fetch_one(tx.as_mut()) + .await?; + + sqlx::query( + r#" + INSERT INTO nym_node_daily_mixing_stats ( + node_id, date_utc, + total_stake, packets_received, + packets_sent, packets_dropped + ) VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT(node_id, date_utc) DO UPDATE SET + total_stake = excluded.total_stake, + packets_received = nym_node_daily_mixing_stats.packets_received + excluded.packets_received, + packets_sent = nym_node_daily_mixing_stats.packets_sent + excluded.packets_sent, + packets_dropped = nym_node_daily_mixing_stats.packets_dropped + excluded.packets_dropped + "#, + ) + .bind(node_id) + .bind(date_utc) + .bind(total_stake) + .bind(packets.packets_received) + .bind(packets.packets_sent) + .bind(packets.packets_dropped) .execute(tx.as_mut()) .await?; } diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/scraper.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/scraper.rs index 9dbb5d4484..efecd3c83b 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/scraper.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/scraper.rs @@ -12,6 +12,7 @@ use crate::{ }; use anyhow::Result; use nym_validator_client::nym_api::SkimmedNode; +use sqlx::Row; pub(crate) async fn get_nodes_for_scraping(pool: &DbPool) -> Result> { let mut nodes_to_scrape = Vec::new(); @@ -68,12 +69,12 @@ pub(crate) async fn get_nodes_for_scraping(pool: &DbPool) -> Result Result Result Result<()> { let timestamp = now_utc().unix_timestamp(); let mut conn = pool.acquire().await?; @@ -138,7 +141,7 @@ pub(crate) async fn insert_scraped_node_description( node_id, identity_key, } => { - insert_nym_node_description(&mut conn, node_id, description, timestamp).await?; + insert_nym_node_description(&mut conn, node_id, description.clone(), timestamp).await?; // for historic reasons (/gateways API), store this info into gateways table as well insert_gateway_description(&mut conn, identity_key, description, timestamp).await?; } diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs index 79a32ee5bb..3695c4481b 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs @@ -23,13 +23,12 @@ use crate::{ pub(crate) async fn get_summary_history(pool: &DbPool) -> anyhow::Result> { let mut conn = pool.acquire().await?; - let items = sqlx::query_as!( - SummaryHistoryDto, + let items = crate::db::query_as::( r#"SELECT - id as "id!", - date as "date!", - timestamp_utc as "timestamp_utc!", - value_json as "value_json!" + id, + date, + timestamp_utc, + value_json FROM summary_history ORDER BY date DESC LIMIT 30"#, @@ -51,13 +50,12 @@ pub(crate) async fn get_summary_history(pool: &DbPool) -> anyhow::Result anyhow::Result> { let mut conn = pool.acquire().await?; - Ok(sqlx::query_as!( - SummaryDto, + Ok(crate::db::query_as::( r#"SELECT - key as "key!", - value_json as "value_json!", - last_updated_utc as "last_updated_utc!" - FROM summary"# + key, + value_json, + last_updated_utc + FROM summary"#, ) .fetch(&mut *conn) .try_collect::>() diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/testruns.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/testruns.rs index 469442296c..80dafc070d 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/testruns.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/testruns.rs @@ -1,40 +1,38 @@ use crate::db::models::{TestRunDto, TestRunStatus}; +use crate::db::DbConnection; use crate::db::DbPool; use crate::http::models::TestrunAssignment; use crate::utils::now_utc; -use sqlx::{pool::PoolConnection, Sqlite}; +use sqlx::Row; use time::Duration; -pub(crate) async fn count_testruns_in_progress( - conn: &mut PoolConnection, -) -> anyhow::Result { - sqlx::query_scalar!( - r#"SELECT - COUNT(id) as "count: i64" - FROM testruns - WHERE - status = ? - "#, - TestRunStatus::InProgress as i64, - ) - .fetch_one(conn.as_mut()) - .await - .map_err(anyhow::Error::from) +pub(crate) async fn count_testruns_in_progress(conn: &mut DbConnection) -> anyhow::Result { + #[cfg(feature = "sqlite")] + let sql = "SELECT COUNT(id) FROM testruns WHERE status = ?"; + + #[cfg(feature = "pg")] + let sql = "SELECT COUNT(id) FROM testruns WHERE status = $1"; + + let count: i64 = sqlx::query_scalar(sql) + .bind(TestRunStatus::InProgress as i32) + .fetch_one(conn.as_mut()) + .await?; + + Ok(count) } pub(crate) async fn get_in_progress_testrun_by_id( - conn: &mut PoolConnection, - testrun_id: i64, + conn: &mut DbConnection, + testrun_id: i32, ) -> anyhow::Result { - sqlx::query_as!( - TestRunDto, + crate::db::query_as::( r#"SELECT - id as "id!", - gateway_id as "gateway_id!", - status as "status!", - created_utc as "created_utc!", - ip_address as "ip_address!", - log as "log!", + id, + gateway_id, + status, + created_utc, + ip_address, + log, last_assigned_utc FROM testruns WHERE @@ -43,12 +41,12 @@ pub(crate) async fn get_in_progress_testrun_by_id( status = ? ORDER BY created_utc LIMIT 1"#, - testrun_id, - TestRunStatus::InProgress as i64, ) + .bind(testrun_id) + .bind(TestRunStatus::InProgress as i32) .fetch_one(conn.as_mut()) .await - .map_err(|e| anyhow::anyhow!("Couldn't retrieve testrun {testrun_id}: {e}")) + .map_err(|e| anyhow::anyhow!("Failed to retrieve in-progress testrun {testrun_id}: {e}")) } pub(crate) async fn update_testruns_assigned_before( @@ -59,7 +57,7 @@ pub(crate) async fn update_testruns_assigned_before( let previous_run = now_utc() - max_age; let cutoff_timestamp = previous_run.unix_timestamp(); - let res = sqlx::query!( + let res = crate::db::query( r#"UPDATE testruns SET @@ -69,10 +67,10 @@ pub(crate) async fn update_testruns_assigned_before( AND last_assigned_utc < ? "#, - TestRunStatus::Queued as i64, - TestRunStatus::InProgress as i64, - cutoff_timestamp ) + .bind(TestRunStatus::Queued as i32) + .bind(TestRunStatus::InProgress as i32) + .bind(cutoff_timestamp) .execute(conn.as_mut()) .await?; @@ -89,36 +87,36 @@ pub(crate) async fn update_testruns_assigned_before( } pub(crate) async fn assign_oldest_testrun( - conn: &mut PoolConnection, + conn: &mut DbConnection, ) -> anyhow::Result> { let now = now_utc().unix_timestamp(); // find & mark as "In progress" in the same transaction to avoid race conditions - let returning = sqlx::query!( + let returning = crate::db::query( r#"UPDATE testruns SET status = ?, last_assigned_utc = ? - WHERE rowid = + WHERE id = ( - SELECT rowid + SELECT id FROM testruns WHERE status = ? ORDER BY created_utc asc LIMIT 1 ) RETURNING - id as "id!", + id, gateway_id "#, - TestRunStatus::InProgress as i64, - now, - TestRunStatus::Queued as i64, ) + .bind(TestRunStatus::InProgress as i32) + .bind(now) + .bind(TestRunStatus::Queued as i32) .fetch_optional(conn.as_mut()) .await?; if let Some(testrun) = returning { - let gw_identity = sqlx::query!( + let gw_identity = crate::db::query( r#" SELECT id, @@ -126,14 +124,14 @@ pub(crate) async fn assign_oldest_testrun( FROM gateways WHERE id = ? LIMIT 1"#, - testrun.gateway_id ) + .bind(testrun.try_get::("gateway_id")?) .fetch_one(conn.as_mut()) .await?; Ok(Some(TestrunAssignment { - testrun_id: testrun.id, - gateway_identity_key: gw_identity.gateway_identity_key, + testrun_id: testrun.try_get("id")?, + gateway_identity_key: gw_identity.try_get("gateway_identity_key")?, assigned_at_utc: now, })) } else { @@ -142,67 +140,146 @@ pub(crate) async fn assign_oldest_testrun( } pub(crate) async fn update_testrun_status( - conn: &mut PoolConnection, - testrun_id: i64, + conn: &mut DbConnection, + testrun_id: i32, status: TestRunStatus, ) -> anyhow::Result<()> { - let status = status as u32; - sqlx::query!( - "UPDATE testruns SET status = ? WHERE id = ?", - status, - testrun_id - ) - .execute(conn.as_mut()) - .await?; + let status = status as i32; + crate::db::query("UPDATE testruns SET status = ? WHERE id = ?") + .bind(status) + .bind(testrun_id) + .execute(conn.as_mut()) + .await?; Ok(()) } pub(crate) async fn update_gateway_last_probe_log( - conn: &mut PoolConnection, - gateway_pk: i64, - log: &str, + conn: &mut DbConnection, + gateway_pk: i32, + log: String, ) -> anyhow::Result<()> { - sqlx::query!( - "UPDATE gateways SET last_probe_log = ? WHERE id = ?", - log, - gateway_pk - ) - .execute(conn.as_mut()) - .await - .map(drop) - .map_err(From::from) + crate::db::query("UPDATE gateways SET last_probe_log = ? WHERE id = ?") + .bind(log) + .bind(gateway_pk) + .execute(conn.as_mut()) + .await + .map(drop) + .map_err(|e| { + anyhow::anyhow!( + "Failed to update probe log for gateway {}: {}", + gateway_pk, + e + ) + }) } pub(crate) async fn update_gateway_last_probe_result( - conn: &mut PoolConnection, - gateway_pk: i64, - result: &str, + conn: &mut DbConnection, + gateway_pk: i32, + result: String, ) -> anyhow::Result<()> { - sqlx::query!( - "UPDATE gateways SET last_probe_result = ? WHERE id = ?", - result, - gateway_pk - ) - .execute(conn.as_mut()) - .await - .map(drop) - .map_err(From::from) + crate::db::query("UPDATE gateways SET last_probe_result = ? WHERE id = ?") + .bind(result) + .bind(gateway_pk) + .execute(conn.as_mut()) + .await + .map(drop) + .map_err(|e| { + anyhow::anyhow!( + "Failed to update probe result for gateway {}: {}", + gateway_pk, + e + ) + }) } pub(crate) async fn update_gateway_score( - conn: &mut PoolConnection, - gateway_pk: i64, + conn: &mut DbConnection, + gateway_pk: i32, ) -> anyhow::Result<()> { let now = now_utc().unix_timestamp(); - sqlx::query!( - "UPDATE gateways SET last_testrun_utc = ?, last_updated_utc = ? WHERE id = ?", - now, - now, - gateway_pk - ) - .execute(conn.as_mut()) - .await - .map(drop) - .map_err(From::from) + crate::db::query("UPDATE gateways SET last_testrun_utc = ?, last_updated_utc = ? WHERE id = ?") + .bind(now) + .bind(now) + .bind(gateway_pk) + .execute(conn.as_mut()) + .await + .map(drop) + .map_err(From::from) +} + +pub(crate) async fn get_testrun_by_id( + conn: &mut DbConnection, + testrun_id: i32, +) -> anyhow::Result { + crate::db::query_as::( + r#"SELECT + id, + gateway_id, + status, + created_utc, + ip_address, + log, + last_assigned_utc + FROM testruns + WHERE id = ?"#, + ) + .bind(testrun_id) + .fetch_one(conn.as_mut()) + .await + .map_err(|e| anyhow::anyhow!("Testrun {} not found: {}", testrun_id, e)) +} + +pub(crate) async fn insert_external_testrun( + conn: &mut DbConnection, + testrun_id: i32, + gateway_id: i32, + assigned_at_utc: i64, +) -> anyhow::Result<()> { + let now = crate::utils::now_utc().unix_timestamp(); + + crate::db::query( + r#"INSERT INTO testruns ( + id, + gateway_id, + status, + created_utc, + last_assigned_utc, + ip_address, + log + ) VALUES (?, ?, ?, ?, ?, ?, ?)"#, + ) + .bind(testrun_id) + .bind(gateway_id) + .bind(TestRunStatus::InProgress as i32) + .bind(now) + .bind(assigned_at_utc) + .bind("external") // Marker for external origin + .bind("") // Empty initial log + .execute(conn.as_mut()) + .await?; + + tracing::debug!( + "Created external testrun {} for gateway {}", + testrun_id, + gateway_id + ); + Ok(()) +} + +pub(crate) async fn update_testrun_status_by_gateway( + conn: &mut DbConnection, + gateway_id: i32, + status: TestRunStatus, +) -> anyhow::Result<()> { + let status = status as i32; + crate::db::query("UPDATE testruns SET status = ? WHERE gateway_id = ? AND status = ?") + .bind(status) + .bind(gateway_id) + .bind(TestRunStatus::InProgress as i32) + .execute(conn.as_mut()) + .await?; + + Ok(()) } diff --git a/nym-node-status-api/nym-node-status-api/src/db/query_wrapper.rs b/nym-node-status-api/nym-node-status-api/src/db/query_wrapper.rs new file mode 100644 index 0000000000..e061d654a1 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/db/query_wrapper.rs @@ -0,0 +1,251 @@ +use sqlx::Database; + +/// Converts SQLite-style ? placeholders to PostgreSQL $N format +#[cfg(feature = "pg")] +fn convert_placeholders(query: &str) -> String { + let mut result = String::with_capacity(query.len() + 10); + let mut placeholder_count = 0; + let mut chars = query.chars(); + let mut in_string: Option = None; + let mut escape_next = false; + + #[allow(clippy::while_let_on_iterator)] + while let Some(ch) = chars.next() { + if escape_next { + result.push(ch); + escape_next = false; + continue; + } + + if let Some(quote_char) = in_string { + result.push(ch); + if ch == quote_char { + in_string = None; + } else if ch == '\\' { + escape_next = true; + } + continue; + } + + match ch { + '\\' => { + result.push(ch); + escape_next = true; + } + '\'' | '"' => { + result.push(ch); + in_string = Some(ch); + } + '?' => { + placeholder_count += 1; + result.push_str(&format!("${placeholder_count}")); + } + _ => { + result.push(ch); + } + } + } + + result +} + +/// Creates a query that automatically handles placeholder conversion +#[cfg(feature = "sqlite")] +pub fn query( + sql: &str, +) -> sqlx::query::Query<'_, sqlx::Sqlite, ::Arguments<'_>> { + sqlx::query(sql) +} + +#[cfg(feature = "pg")] +pub fn query( + sql: &str, +) -> sqlx::query::Query<'static, sqlx::Postgres, ::Arguments<'static>> { + let converted = convert_placeholders(sql); + sqlx::query(Box::leak(converted.into_boxed_str())) +} + +/// Creates a query_as that automatically handles placeholder conversion +#[cfg(feature = "sqlite")] +pub fn query_as( + sql: &str, +) -> sqlx::query::QueryAs<'_, sqlx::Sqlite, O, ::Arguments<'_>> +where + O: for<'r> sqlx::FromRow<'r, ::Row>, +{ + sqlx::query_as(sql) +} + +#[cfg(feature = "pg")] +pub fn query_as( + sql: &str, +) -> sqlx::query::QueryAs< + 'static, + sqlx::Postgres, + O, + ::Arguments<'static>, +> +where + O: for<'r> sqlx::FromRow<'r, ::Row>, +{ + let converted = convert_placeholders(sql); + sqlx::query_as(Box::leak(converted.into_boxed_str())) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[cfg(feature = "pg")] + fn test_convert_placeholders() { + // Basic conversion + assert_eq!( + convert_placeholders(r"SELECT * FROM table WHERE id = ?"), + r"SELECT * FROM table WHERE id = $1" + ); + + // Multiple placeholders + assert_eq!( + convert_placeholders(r"INSERT INTO table (a, b, c) VALUES (?, ?, ?)"), + r"INSERT INTO table (a, b, c) VALUES ($1, $2, $3)" + ); + + // Placeholder inside string literal should be ignored + assert_eq!( + convert_placeholders(r"SELECT * FROM table WHERE name = 'test?' AND id = ?"), + r"SELECT * FROM table WHERE name = 'test?' AND id = $1" + ); + + // Update statement + assert_eq!( + convert_placeholders(r"UPDATE table SET a = ?, b = ? WHERE id = ?"), + r"UPDATE table SET a = $1, b = $2 WHERE id = $3" + ); + + // Test with 10 placeholders (like in update_mixnodes) + assert_eq!( + convert_placeholders(r"INSERT INTO t VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), + r"INSERT INTO t VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)" + ); + + // No placeholders + assert_eq!( + convert_placeholders(r"SELECT * FROM table"), + r"SELECT * FROM table" + ); + + // Placeholder at the beginning + assert_eq!(convert_placeholders(r"? AND ?"), r"$1 AND $2"); + + // Placeholder at the end + assert_eq!( + convert_placeholders(r"SELECT * FROM table WHERE id = ?"), + r"SELECT * FROM table WHERE id = $1" + ); + + // Adjacent placeholders + assert_eq!( + convert_placeholders(r"VALUES(?,? ,?)"), + r"VALUES($1,$2 ,$3)" + ); + + // Escaped single quote + assert_eq!( + convert_placeholders(r"SELECT * FROM foo WHERE bar = 'it\'s a test' AND baz = ?"), + r"SELECT * FROM foo WHERE bar = 'it\'s a test' AND baz = $1" + ); + + // Double quotes + assert_eq!( + convert_placeholders(r#"SELECT * FROM "table" WHERE "column" = ? AND name = "test?""#), + r#"SELECT * FROM "table" WHERE "column" = $1 AND name = "test?""# + ); + + // Mixed quotes + assert_eq!( + convert_placeholders( + r#"SELECT * FROM table WHERE a = 'single?' AND b = "double?" AND c = ?"# + ), + r#"SELECT * FROM table WHERE a = 'single?' AND b = "double?" AND c = $1"# + ); + + // Escaped backslash before quote + assert_eq!( + convert_placeholders(r"SELECT * FROM table WHERE path = 'C:\\?' AND id = ?"), + r"SELECT * FROM table WHERE path = 'C:\\?' AND id = $1" + ); + + // Multiple escaped quotes + assert_eq!( + convert_placeholders( + r#"INSERT INTO table (msg) VALUES ('it\'s "complex" test') WHERE id = ?"# + ), + r#"INSERT INTO table (msg) VALUES ('it\'s "complex" test') WHERE id = $1"# + ); + + // Very long query with many placeholders + let long_query = r"INSERT INTO very_long_table_name (col1, col2, col3, col4, col5, col6, col7, col8, col9, col10, col11, col12, col13, col14, col15) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + let expected = r"INSERT INTO very_long_table_name (col1, col2, col3, col4, col5, col6, col7, col8, col9, col10, col11, col12, col13, col14, col15) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)"; + assert_eq!(convert_placeholders(long_query), expected); + + // Query with comments (question marks in comments are also converted) + assert_eq!( + convert_placeholders( + r"-- This is a comment with ? + SELECT * FROM table WHERE id = ? -- another comment ?" + ), + r"-- This is a comment with $1 + SELECT * FROM table WHERE id = $2 -- another comment $3" + ); + + // Multiline strings + assert_eq!( + convert_placeholders( + r"SELECT * FROM table + WHERE description = 'This is a + multiline string with ?' + AND id = ?" + ), + r"SELECT * FROM table + WHERE description = 'This is a + multiline string with ?' + AND id = $1" + ); + + // Complex nested quotes + assert_eq!( + convert_placeholders( + r#"SELECT json_extract(data, '$.items[?(@.name=="test?")]') FROM table WHERE id = ?"# + ), + r#"SELECT json_extract(data, '$.items[?(@.name=="test?")]') FROM table WHERE id = $1"# + ); + + // Empty string + assert_eq!(convert_placeholders(""), ""); + + // Only placeholders + assert_eq!(convert_placeholders("???"), "$1$2$3"); + + // Unicode in strings + assert_eq!( + convert_placeholders(r"SELECT * FROM table WHERE name = '测试?' AND id = ?"), + r"SELECT * FROM table WHERE name = '测试?' AND id = $1" + ); + + // Test case with backslash at end of string + assert_eq!( + convert_placeholders(r"SELECT * FROM table WHERE path LIKE '%\\' AND id = ?"), + r"SELECT * FROM table WHERE path LIKE '%\\' AND id = $1" + ); + + // Mismatched quotes + assert_eq!( + convert_placeholders(r#"SELECT * FROM foo WHERE bar = "'" AND baz = ?"#), + r#"SELECT * FROM foo WHERE bar = "'" AND baz = $1"# + ); + + // Unmatched quote + assert_eq!(convert_placeholders(r"SELECT 'oops?"), r"SELECT 'oops?"); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/db/tests.rs b/nym-node-status-api/nym-node-status-api/src/db/tests.rs new file mode 100644 index 0000000000..a176e6d340 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/db/tests.rs @@ -0,0 +1,461 @@ +#[cfg(test)] +mod db_tests { + + #[test] + fn test_gateway_dto_try_from() { + let gateway_dto = crate::db::models::GatewayDto { + gateway_identity_key: "test_identity".to_string(), + bonded: true, + performance: 100, + self_described: Some("{\"key\":\"value\"}".to_string()), + explorer_pretty_bond: Some("{\"key\":\"value\"}".to_string()), + last_probe_result: Some("{\"key\":\"value\"}".to_string()), + last_probe_log: Some("log".to_string()), + last_testrun_utc: Some(1672531200), + last_updated_utc: 1672531200, + moniker: "moniker".to_string(), + security_contact: "contact".to_string(), + details: "details".to_string(), + website: "website".to_string(), + }; + + let http_gateway: crate::http::models::Gateway = gateway_dto.try_into().unwrap(); + + assert_eq!(http_gateway.gateway_identity_key, "test_identity"); + assert!(http_gateway.bonded); + assert_eq!(http_gateway.performance, 100); + assert!(http_gateway.self_described.is_some()); + assert!(http_gateway.explorer_pretty_bond.is_some()); + assert!(http_gateway.last_probe_result.is_some()); + assert_eq!(http_gateway.last_probe_log, Some("log".to_string())); + assert!(http_gateway.last_testrun_utc.is_some()); + assert!(!http_gateway.last_updated_utc.is_empty()); + assert_eq!(http_gateway.description.moniker, "moniker"); + assert_eq!(http_gateway.description.website, "website"); + assert_eq!(http_gateway.description.security_contact, "contact"); + assert_eq!(http_gateway.description.details, "details"); + } + + #[test] + fn test_mixnode_dto_try_from() { + let mixnode_dto = crate::db::models::MixnodeDto { + mix_id: 1, + bonded: true, + is_dp_delegatee: false, + total_stake: 1000000, + full_details: "{\"key\":\"value\"}".to_string(), + self_described: Some("{\"key\":\"value\"}".to_string()), + last_updated_utc: 1672531200, + moniker: "moniker".to_string(), + website: "website".to_string(), + security_contact: "contact".to_string(), + details: "details".to_string(), + }; + + let http_mixnode: crate::http::models::Mixnode = mixnode_dto.try_into().unwrap(); + + assert_eq!(http_mixnode.mix_id, 1); + assert!(http_mixnode.bonded); + assert!(!http_mixnode.is_dp_delegatee); + assert_eq!(http_mixnode.total_stake, 1000000); + assert!(http_mixnode.full_details.is_some()); + assert!(http_mixnode.self_described.is_some()); + assert!(!http_mixnode.last_updated_utc.is_empty()); + assert_eq!(http_mixnode.description.moniker, "moniker"); + assert_eq!(http_mixnode.description.website, "website"); + assert_eq!(http_mixnode.description.security_contact, "contact"); + assert_eq!(http_mixnode.description.details, "details"); + } + + #[test] + fn test_summary_history_dto_try_from() { + let summary_history_dto = crate::db::models::SummaryHistoryDto { + id: 1, + date: "2023-01-01".to_string(), + value_json: "{\"key\":\"value\"}".to_string(), + timestamp_utc: 1672531200, + }; + + let summary_history: crate::http::models::SummaryHistory = + summary_history_dto.try_into().unwrap(); + + assert_eq!(summary_history.date, "2023-01-01"); + assert!(summary_history.value_json.is_object()); + assert!(!summary_history.timestamp_utc.is_empty()); + } + + #[test] + fn test_gateway_sessions_record_try_from() { + let gateway_sessions_record = crate::db::models::GatewaySessionsRecord { + gateway_identity_key: "test_identity".to_string(), + node_id: 1, + day: time::macros::date!(2023 - 01 - 01), + unique_active_clients: 10, + session_started: 100, + users_hashes: Some("{\"key\":\"value\"}".to_string()), + vpn_sessions: Some("{\"key\":\"value\"}".to_string()), + mixnet_sessions: Some("{\"key\":\"value\"}".to_string()), + unknown_sessions: Some("{\"key\":\"value\"}".to_string()), + }; + + let session_stats: crate::http::models::SessionStats = + gateway_sessions_record.try_into().unwrap(); + + assert_eq!(session_stats.gateway_identity_key, "test_identity"); + assert_eq!(session_stats.node_id, 1); + assert_eq!(session_stats.day, time::macros::date!(2023 - 01 - 01)); + assert_eq!(session_stats.unique_active_clients, 10); + assert_eq!(session_stats.session_started, 100); + assert!(session_stats.users_hashes.is_some()); + assert!(session_stats.vpn_sessions.is_some()); + assert!(session_stats.mixnet_sessions.is_some()); + assert!(session_stats.unknown_sessions.is_some()); + } + + #[test] + fn test_nym_node_dto_try_from() { + let ed25519_pk = nym_crypto::asymmetric::ed25519::PublicKey::from_bytes(&[1; 32]).unwrap(); + let x25519_pk = nym_crypto::asymmetric::x25519::PublicKey::from_bytes(&[2; 32]).unwrap(); + + let nym_node_dto = crate::db::models::NymNodeDto { + node_id: 1, + ed25519_identity_pubkey: ed25519_pk.to_base58_string(), + total_stake: 1000000, + ip_addresses: serde_json::json!(["1.1.1.1"]), + mix_port: 1789, + x25519_sphinx_pubkey: x25519_pk.to_base58_string(), + node_role: serde_json::json!(nym_validator_client::nym_nodes::NodeRole::Mixnode { + layer: 1 + }), + supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRoles { + entry: false, + mixnode: true, + exit_nr: false, + exit_ipr: false, + }), + entry: None, + performance: "1.0".to_string(), + self_described: None, + bond_info: None, + }; + + let skimmed_node: nym_validator_client::nym_api::SkimmedNode = + nym_node_dto.try_into().unwrap(); + + assert_eq!(skimmed_node.node_id, 1); + assert_eq!(skimmed_node.ed25519_identity_pubkey, ed25519_pk); + assert_eq!( + skimmed_node.ip_addresses, + vec!["1.1.1.1".parse::().unwrap()] + ); + assert_eq!(skimmed_node.mix_port, 1789); + assert_eq!(skimmed_node.x25519_sphinx_pubkey, x25519_pk); + + match skimmed_node.role { + nym_validator_client::nym_nodes::NodeRole::Mixnode { layer } => assert_eq!(layer, 1), + _ => panic!("Unexpected node role"), + } + assert!(!skimmed_node.supported_roles.entry); + assert!(skimmed_node.supported_roles.mixnode); + assert!(!skimmed_node.supported_roles.exit_nr); + assert!(!skimmed_node.supported_roles.exit_ipr); + assert!(skimmed_node.entry.is_none()); + assert_eq!( + skimmed_node.performance, + nym_contracts_common::Percent::from_percentage_value(100).unwrap() + ); + } +} + +#[test] +fn test_nym_node_insert_record_new() { + let ed25519_pk = nym_crypto::asymmetric::ed25519::PublicKey::from_bytes(&[1; 32]).unwrap(); + let x25519_pk = nym_crypto::asymmetric::x25519::PublicKey::from_bytes(&[2; 32]).unwrap(); + + let skimmed_node = nym_validator_client::nym_api::SkimmedNode { + node_id: 1, + ed25519_identity_pubkey: ed25519_pk, + ip_addresses: vec!["1.1.1.1".parse().unwrap()], + mix_port: 1789, + x25519_sphinx_pubkey: x25519_pk, + role: nym_validator_client::nym_nodes::NodeRole::Mixnode { layer: 1 }, + supported_roles: nym_validator_client::models::DeclaredRoles { + entry: false, + mixnode: true, + exit_nr: false, + exit_ipr: false, + }, + entry: None, + performance: nym_contracts_common::Percent::from_percentage_value(100).unwrap(), + }; + + let record = crate::db::models::NymNodeInsertRecord::new(skimmed_node, None, None).unwrap(); + + assert_eq!(record.node_id, 1); + assert_eq!( + record.ed25519_identity_pubkey, + ed25519_pk.to_base58_string() + ); + assert_eq!(record.total_stake, 0); + assert_eq!(record.ip_addresses, serde_json::json!(["1.1.1.1"])); + assert_eq!(record.mix_port, 1789); + assert_eq!(record.x25519_sphinx_pubkey, x25519_pk.to_base58_string()); + assert_eq!( + record.node_role, + serde_json::json!(nym_validator_client::nym_nodes::NodeRole::Mixnode { layer: 1 }) + ); + assert_eq!( + record.supported_roles, + serde_json::json!(nym_validator_client::models::DeclaredRoles { + entry: false, + mixnode: true, + exit_nr: false, + exit_ipr: false, + }) + ); + assert_eq!(record.performance, "1"); + assert!(record.entry.is_none()); + assert!(record.self_described.is_none()); + assert!(record.bond_info.is_none()); +} + +#[test] +fn test_nym_node_insert_record_with_entry() { + let ed25519_pk = nym_crypto::asymmetric::ed25519::PublicKey::from_bytes(&[1; 32]).unwrap(); + let x25519_pk = nym_crypto::asymmetric::x25519::PublicKey::from_bytes(&[2; 32]).unwrap(); + + let skimmed_node = nym_validator_client::nym_api::SkimmedNode { + node_id: 1, + ed25519_identity_pubkey: ed25519_pk, + ip_addresses: vec!["1.1.1.1".parse().unwrap()], + mix_port: 1789, + x25519_sphinx_pubkey: x25519_pk, + role: nym_validator_client::nym_nodes::NodeRole::EntryGateway, + supported_roles: nym_validator_client::models::DeclaredRoles { + entry: true, + mixnode: false, + exit_nr: true, + exit_ipr: false, + }, + entry: Some(nym_validator_client::nym_nodes::BasicEntryInformation { + hostname: Some("gateway.example.com".to_string()), + ws_port: 9001, + wss_port: Some(9002), + }), + performance: nym_contracts_common::Percent::from_percentage_value(99).unwrap(), + }; + + let record = crate::db::models::NymNodeInsertRecord::new(skimmed_node, None, None).unwrap(); + + assert_eq!(record.node_id, 1); + assert_eq!(record.total_stake, 0); // No bond info provided + assert!(record.entry.is_some()); + assert!(record.self_described.is_none()); + assert!(record.bond_info.is_none()); + assert!(record.last_updated_utc > 0); +} + +#[test] +fn test_gateway_dto_with_null_values() { + let gateway_dto = crate::db::models::GatewayDto { + gateway_identity_key: "test_identity".to_string(), + bonded: false, + performance: 0, + self_described: None, + explorer_pretty_bond: None, + last_probe_result: None, + last_probe_log: None, + last_testrun_utc: None, + last_updated_utc: 0, + moniker: "".to_string(), + security_contact: "".to_string(), + details: "".to_string(), + website: "".to_string(), + }; + + let http_gateway: crate::http::models::Gateway = gateway_dto.try_into().unwrap(); + + assert_eq!(http_gateway.gateway_identity_key, "test_identity"); + assert!(!http_gateway.bonded); + assert_eq!(http_gateway.performance, 0); + assert!(http_gateway.self_described.is_none()); + assert!(http_gateway.explorer_pretty_bond.is_none()); + assert!(http_gateway.last_probe_result.is_none()); + assert!(http_gateway.last_probe_log.is_none()); + assert!(http_gateway.last_testrun_utc.is_none()); + assert_eq!(http_gateway.last_updated_utc, "1970-01-01T00:00:00Z"); +} + +#[test] +fn test_mixnode_dto_with_invalid_json() { + let mixnode_dto = crate::db::models::MixnodeDto { + mix_id: 1, + bonded: true, + is_dp_delegatee: false, + total_stake: 1000000, + full_details: "invalid json".to_string(), + self_described: Some("also invalid".to_string()), + last_updated_utc: 1672531200, + moniker: "moniker".to_string(), + website: "website".to_string(), + security_contact: "contact".to_string(), + details: "details".to_string(), + }; + + let http_mixnode: crate::http::models::Mixnode = mixnode_dto.try_into().unwrap(); + + // Invalid JSON should result in None + assert!(http_mixnode.full_details.is_none()); + assert_eq!(http_mixnode.self_described, Some(serde_json::Value::Null)); +} + +#[test] +fn test_summary_history_dto_with_invalid_json() { + let summary_history_dto = crate::db::models::SummaryHistoryDto { + id: 1, + date: "2023-01-01".to_string(), + value_json: "not valid json".to_string(), + timestamp_utc: 1672531200, + }; + + let summary_history: crate::http::models::SummaryHistory = + summary_history_dto.try_into().unwrap(); + + assert_eq!(summary_history.date, "2023-01-01"); + // Invalid JSON should result in default (null) + assert!(summary_history.value_json.is_null()); +} + +#[test] +fn test_gateway_sessions_record_with_all_none() { + let gateway_sessions_record = crate::db::models::GatewaySessionsRecord { + gateway_identity_key: "test_identity".to_string(), + node_id: 1, + day: time::macros::date!(2023 - 01 - 01), + unique_active_clients: 0, + session_started: 0, + users_hashes: None, + vpn_sessions: None, + mixnet_sessions: None, + unknown_sessions: None, + }; + + let session_stats: crate::http::models::SessionStats = + gateway_sessions_record.try_into().unwrap(); + + assert_eq!(session_stats.gateway_identity_key, "test_identity"); + assert_eq!(session_stats.node_id, 1); + assert_eq!(session_stats.unique_active_clients, 0); + assert_eq!(session_stats.session_started, 0); + assert!(session_stats.users_hashes.is_none()); + assert!(session_stats.vpn_sessions.is_none()); + assert!(session_stats.mixnet_sessions.is_none()); + assert!(session_stats.unknown_sessions.is_none()); +} + +#[test] +fn test_scraper_node_info_contact_addresses() { + use crate::db::models::{ScrapeNodeKind, ScraperNodeInfo}; + + let node_info = ScraperNodeInfo { + node_kind: ScrapeNodeKind::MixingNymNode { node_id: 123 }, + hosts: vec!["1.1.1.1".to_string(), "example.com".to_string()], + http_api_port: 8080, + }; + + let addresses = node_info.contact_addresses(); + + // Should generate multiple URLs for each host + // Custom port (8080) should be inserted at the beginning + assert!(addresses.contains(&"http://1.1.1.1:8080".to_string())); + assert!(addresses.contains(&"http://example.com:8080".to_string())); + assert!(addresses.contains(&"http://1.1.1.1:8000".to_string())); + assert!(addresses.contains(&"https://1.1.1.1".to_string())); + assert!(addresses.contains(&"http://example.com:8000".to_string())); + // Check that URLs follow the expected pattern + assert!(addresses.len() >= 8); // At least 4 URLs per host +} + +#[test] +fn test_scrape_node_kind_node_id() { + use crate::db::models::ScrapeNodeKind; + + let legacy = ScrapeNodeKind::LegacyMixnode { mix_id: 42 }; + assert_eq!(*legacy.node_id(), 42); + + let mixing = ScrapeNodeKind::MixingNymNode { node_id: 123 }; + assert_eq!(*mixing.node_id(), 123); + + let entry_exit = ScrapeNodeKind::EntryExitNymNode { + node_id: 456, + identity_key: "key123".to_string(), + }; + assert_eq!(*entry_exit.node_id(), 456); +} + +#[test] +fn test_nym_node_dto_with_invalid_keys() { + let nym_node_dto = crate::db::models::NymNodeDto { + node_id: 1, + ed25519_identity_pubkey: "invalid_base58".to_string(), + total_stake: 1000000, + ip_addresses: serde_json::json!(["1.1.1.1"]), + mix_port: 1789, + x25519_sphinx_pubkey: "also_invalid".to_string(), + node_role: serde_json::json!(nym_validator_client::nym_nodes::NodeRole::Mixnode { + layer: 1 + }), + supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRoles { + entry: false, + mixnode: true, + exit_nr: false, + exit_ipr: false, + }), + entry: None, + performance: "1.0".to_string(), + self_described: None, + bond_info: None, + }; + + let result: Result = nym_node_dto.try_into(); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("ed25519_identity_pubkey")); +} + +#[test] +fn test_nym_node_dto_with_invalid_performance() { + let ed25519_pk = nym_crypto::asymmetric::ed25519::PublicKey::from_bytes(&[1; 32]).unwrap(); + let x25519_pk = nym_crypto::asymmetric::x25519::PublicKey::from_bytes(&[2; 32]).unwrap(); + + let nym_node_dto = crate::db::models::NymNodeDto { + node_id: 1, + ed25519_identity_pubkey: ed25519_pk.to_base58_string(), + total_stake: 1000000, + ip_addresses: serde_json::json!(["1.1.1.1"]), + mix_port: 1789, + x25519_sphinx_pubkey: x25519_pk.to_base58_string(), + node_role: serde_json::json!(nym_validator_client::nym_nodes::NodeRole::Mixnode { + layer: 1 + }), + supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRoles { + entry: false, + mixnode: true, + exit_nr: false, + exit_ipr: false, + }), + entry: None, + performance: "invalid_percent".to_string(), + self_described: None, + bond_info: None, + }; + + let result: Result = nym_node_dto.try_into(); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("can't parse Percent")); +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/dvpn/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/dvpn/mod.rs index 1d8cd93694..5f0f4cbe19 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/dvpn/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/dvpn/mod.rs @@ -65,3 +65,36 @@ pub async fn dvpn_gateways( .await, )) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_routes_construction() { + let router = routes(); + // Verify the router builds without panic + let _routes = router; + } + + #[test] + fn test_min_node_version_query_deserialization() { + // Test with version + let json = r#"{"min_node_version": "1.2.3"}"#; + let query: MinNodeVersionQuery = serde_json::from_str(json).unwrap(); + assert_eq!(query.min_node_version, Some("1.2.3".to_string())); + + // Test without version + let json_empty = r#"{}"#; + let query_empty: MinNodeVersionQuery = serde_json::from_str(json_empty).unwrap(); + assert_eq!(query_empty.min_node_version, None); + } + + #[test] + fn test_min_supported_version() { + // Test that the lazy static initializes correctly + assert_eq!(MIN_SUPPORTED_VERSION.major, 1); + assert_eq!(MIN_SUPPORTED_VERSION.minor, 6); + assert_eq!(MIN_SUPPORTED_VERSION.patch, 2); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/gateways.rs b/nym-node-status-api/nym-node-status-api/src/http/api/gateways.rs index 80e870f198..904bcce05f 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/gateways.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/gateways.rs @@ -57,21 +57,7 @@ async fn gateways_skinny( ) -> HttpResult>> { let db = state.db_pool(); let res = state.cache().get_gateway_list(db).await; - let res: Vec = res - .iter() - .filter(|g| g.bonded) - .map(|g| GatewaySkinny { - gateway_identity_key: g.gateway_identity_key.clone(), - self_described: g.self_described.clone(), - performance: g.performance, - explorer_pretty_bond: g.explorer_pretty_bond.clone(), - last_probe_result: g.last_probe_result.clone(), - last_testrun_utc: g.last_testrun_utc.clone(), - last_updated_utc: g.last_updated_utc.clone(), - routing_score: g.routing_score, - config_score: g.config_score, - }) - .collect(); + let res: Vec = filter_bonded_gateways_to_skinny(res); Ok(Json(PagedResult::paginate(pagination, res))) } @@ -108,3 +94,126 @@ async fn get_gateway( None => Err(HttpError::invalid_input(identity_key)), } } + +// Extract filtering logic for testing +fn filter_bonded_gateways_to_skinny(gateways: Vec) -> Vec { + gateways + .iter() + .filter(|g| g.bonded) + .map(|g| GatewaySkinny { + gateway_identity_key: g.gateway_identity_key.clone(), + self_described: g.self_described.clone(), + performance: g.performance, + explorer_pretty_bond: g.explorer_pretty_bond.clone(), + last_probe_result: g.last_probe_result.clone(), + last_testrun_utc: g.last_testrun_utc.clone(), + last_updated_utc: g.last_updated_utc.clone(), + routing_score: g.routing_score, + config_score: g.config_score, + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::models::Gateway; + use nym_node_requests::api::v1::node::models::NodeDescription; + + fn create_test_gateway(identity_key: &str, bonded: bool, performance: u8) -> Gateway { + Gateway { + gateway_identity_key: identity_key.to_string(), + bonded, + performance, + self_described: Some(serde_json::json!({"test": "data"})), + explorer_pretty_bond: Some(serde_json::json!({"bond": "info"})), + description: NodeDescription { + moniker: "Test Gateway".to_string(), + website: "".to_string(), + security_contact: "".to_string(), + details: "".to_string(), + }, + last_probe_result: Some(serde_json::json!({"result": "ok"})), + last_probe_log: None, + last_testrun_utc: Some("2024-01-20T10:00:00Z".to_string()), + last_updated_utc: "2024-01-20T11:00:00Z".to_string(), + routing_score: 0.95, + config_score: 100, + } + } + + #[test] + fn test_filter_bonded_gateways_to_skinny_empty_list() { + let gateways = vec![]; + let result = filter_bonded_gateways_to_skinny(gateways); + assert_eq!(result.len(), 0); + } + + #[test] + fn test_filter_bonded_gateways_to_skinny_all_bonded() { + let gateways = vec![ + create_test_gateway("gw1", true, 90), + create_test_gateway("gw2", true, 95), + create_test_gateway("gw3", true, 85), + ]; + + let result = filter_bonded_gateways_to_skinny(gateways); + assert_eq!(result.len(), 3); + assert_eq!(result[0].gateway_identity_key, "gw1"); + assert_eq!(result[1].gateway_identity_key, "gw2"); + assert_eq!(result[2].gateway_identity_key, "gw3"); + } + + #[test] + fn test_filter_bonded_gateways_to_skinny_mixed() { + let gateways = vec![ + create_test_gateway("gw1", true, 90), + create_test_gateway("gw2", false, 95), + create_test_gateway("gw3", true, 85), + create_test_gateway("gw4", false, 100), + ]; + + let result = filter_bonded_gateways_to_skinny(gateways); + assert_eq!(result.len(), 2); + assert_eq!(result[0].gateway_identity_key, "gw1"); + assert_eq!(result[1].gateway_identity_key, "gw3"); + } + + #[test] + fn test_filter_bonded_gateways_to_skinny_none_bonded() { + let gateways = vec![ + create_test_gateway("gw1", false, 90), + create_test_gateway("gw2", false, 95), + ]; + + let result = filter_bonded_gateways_to_skinny(gateways); + assert_eq!(result.len(), 0); + } + + #[test] + fn test_gateway_to_skinny_conversion() { + let gateway = create_test_gateway("test_gw", true, 98); + let gateways = vec![gateway.clone()]; + + let result = filter_bonded_gateways_to_skinny(gateways); + assert_eq!(result.len(), 1); + + let skinny = &result[0]; + assert_eq!(skinny.gateway_identity_key, gateway.gateway_identity_key); + assert_eq!(skinny.performance, gateway.performance); + assert_eq!(skinny.self_described, gateway.self_described); + assert_eq!(skinny.explorer_pretty_bond, gateway.explorer_pretty_bond); + assert_eq!(skinny.last_probe_result, gateway.last_probe_result); + assert_eq!(skinny.last_testrun_utc, gateway.last_testrun_utc); + assert_eq!(skinny.last_updated_utc, gateway.last_updated_utc); + assert_eq!(skinny.routing_score, gateway.routing_score); + assert_eq!(skinny.config_score, gateway.config_score); + } + + #[test] + fn test_identity_key_param_deserialization() { + let json = r#"{"identity_key": "test_key_123"}"#; + let param: IdentityKeyParam = serde_json::from_str(json).unwrap(); + assert_eq!(param.identity_key, "test_key_123"); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs index 8703f92830..a3da492dcb 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs @@ -8,3 +8,15 @@ pub(crate) fn routes() -> Router { Router::new().nest("/sessions", sessions::routes()) //eventually add other metrics type } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_routes_construction() { + let router = routes(); + // Verify the router builds without panic + let _routes = router; + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/mixnodes.rs b/nym-node-status-api/nym-node-status-api/src/http/api/mixnodes.rs index cbcfe1fb85..ed18bcdb6a 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/mixnodes.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/mixnodes.rs @@ -64,17 +64,11 @@ async fn get_mixnodes( Path(MixIdParam { mix_id }): Path, State(state): State, ) -> HttpResult> { - match mix_id.parse::() { - Ok(parsed_mix_id) => { - let res = state.cache().get_mixnodes_list(state.db_pool()).await; - - match res.iter().find(|item| item.mix_id == parsed_mix_id) { - Some(res) => Ok(Json(res.clone())), - None => Err(HttpError::invalid_input(mix_id)), - } - } - Err(_e) => Err(HttpError::invalid_input(mix_id)), - } + find_mixnode_by_id( + &mix_id, + state.cache().get_mixnodes_list(state.db_pool()).await, + ) + .map(Json) } #[derive(Deserialize, IntoParams)] @@ -99,10 +93,7 @@ async fn get_stats( Query(MixStatsQueryParams { offset }): Query, State(state): State, ) -> HttpResult>> { - let offset: usize = offset - .unwrap_or(0) - .try_into() - .map_err(|_| HttpError::invalid_input("Offset must be non-negative"))?; + let offset = validate_offset(offset)?; let last_30_days = state .cache() .get_mixnode_stats(state.db_pool(), offset) @@ -110,3 +101,146 @@ async fn get_stats( Ok(Json(last_30_days)) } + +// Extract business logic for testing +fn find_mixnode_by_id(mix_id: &str, mixnodes: Vec) -> HttpResult { + match mix_id.parse::() { + Ok(parsed_mix_id) => mixnodes + .into_iter() + .find(|item| item.mix_id == parsed_mix_id) + .ok_or_else(|| HttpError::invalid_input(mix_id)), + Err(_e) => Err(HttpError::invalid_input(mix_id)), + } +} + +fn validate_offset(offset: Option) -> HttpResult { + offset + .unwrap_or(0) + .try_into() + .map_err(|_| HttpError::invalid_input("Offset must be non-negative")) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::models::{DailyStats, Mixnode}; + use nym_node_requests::api::v1::node::models::NodeDescription; + + fn create_test_mixnode(mix_id: u32, is_dp_delegatee: bool) -> Mixnode { + Mixnode { + mix_id, + bonded: true, + is_dp_delegatee, + total_stake: 100000, + full_details: Some(serde_json::json!({"test": "data"})), + self_described: Some(serde_json::json!({"version": "1.0"})), + description: NodeDescription { + moniker: format!("Mixnode {mix_id}"), + website: "".to_string(), + security_contact: "".to_string(), + details: "".to_string(), + }, + last_updated_utc: "2024-01-20T10:00:00Z".to_string(), + } + } + + #[test] + fn test_routes_construction() { + let router = routes(); + // Just verify the router builds without panic + // Actual route testing would require integration tests + let _routes = router; + } + + #[test] + fn test_find_mixnode_by_id_success() { + let mixnodes = vec![ + create_test_mixnode(1, false), + create_test_mixnode(42, true), + create_test_mixnode(100, false), + ]; + + let result = find_mixnode_by_id("42", mixnodes).unwrap(); + assert_eq!(result.mix_id, 42); + assert!(result.is_dp_delegatee); + } + + #[test] + fn test_find_mixnode_by_id_not_found() { + let mixnodes = vec![create_test_mixnode(1, false), create_test_mixnode(2, false)]; + + let result = find_mixnode_by_id("99", mixnodes); + assert!(result.is_err()); + } + + #[test] + fn test_find_mixnode_by_id_invalid_format() { + let mixnodes = vec![create_test_mixnode(1, false)]; + + // Test various invalid formats + assert!(find_mixnode_by_id("abc", mixnodes.clone()).is_err()); + assert!(find_mixnode_by_id("", mixnodes.clone()).is_err()); + assert!(find_mixnode_by_id("12.34", mixnodes.clone()).is_err()); + assert!(find_mixnode_by_id("-1", mixnodes).is_err()); + } + + #[test] + fn test_find_mixnode_by_id_edge_cases() { + let mixnodes = vec![ + create_test_mixnode(0, false), + create_test_mixnode(u32::MAX, false), + ]; + + assert!(find_mixnode_by_id("0", mixnodes.clone()).is_ok()); + assert!(find_mixnode_by_id(&u32::MAX.to_string(), mixnodes).is_ok()); + } + + #[test] + fn test_validate_offset_valid() { + assert_eq!(validate_offset(None).unwrap(), 0); + assert_eq!(validate_offset(Some(0)).unwrap(), 0); + assert_eq!(validate_offset(Some(10)).unwrap(), 10); + assert_eq!(validate_offset(Some(1000)).unwrap(), 1000); + } + + #[test] + fn test_validate_offset_invalid() { + assert!(validate_offset(Some(-1)).is_err()); + assert!(validate_offset(Some(-100)).is_err()); + assert!(validate_offset(Some(i64::MIN)).is_err()); + } + + #[test] + fn test_mix_id_param_deserialization() { + let json = r#"{"mix_id": "123"}"#; + let param: MixIdParam = serde_json::from_str(json).unwrap(); + assert_eq!(param.mix_id, "123"); + } + + #[test] + fn test_mix_stats_query_params_deserialization() { + let json = r#"{"offset": 50}"#; + let params: MixStatsQueryParams = serde_json::from_str(json).unwrap(); + assert_eq!(params.offset, Some(50)); + + let json_empty = r#"{}"#; + let params_empty: MixStatsQueryParams = serde_json::from_str(json_empty).unwrap(); + assert_eq!(params_empty.offset, None); + } + + #[test] + fn test_daily_stats_creation() { + let stats = DailyStats { + date_utc: "2024-01-20".to_string(), + total_packets_received: 1000000, + total_packets_sent: 999000, + total_packets_dropped: 1000, + total_stake: 5000000, + }; + + assert_eq!(stats.total_packets_received, 1000000); + assert_eq!(stats.total_packets_sent, 999000); + assert_eq!(stats.total_packets_dropped, 1000); + assert_eq!(stats.total_stake, 5000000); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs index bd4300275b..bc0a37d34e 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs @@ -101,3 +101,38 @@ fn setup_cors() -> CorsLayer { .allow_headers(tower_http::cors::Any) .allow_credentials(false) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cors_configuration() { + let cors = setup_cors(); + + // Test that CORS is configured (this tests that the function returns a valid CorsLayer) + // The actual CORS behavior would need integration tests + let _layer = cors; // This ensures the cors layer is valid + } + + #[test] + fn test_router_builder_creates_routes() { + let router_builder = RouterBuilder::with_default_routes(); + + // Test that the router builder has the expected structure + // The router itself is private, but we can test that the builder is created + let unfinished_router = router_builder.unfinished_router; + + // Convert to a testable format - this will compile only if routes are properly configured + let _test_router = unfinished_router; + } + + #[test] + fn test_router_builder_finalize() { + let router_builder = RouterBuilder::with_default_routes(); + let finalized = router_builder.finalize_routes(); + + // This tests that finalize_routes produces a valid Router + let _router = finalized; + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/nym_nodes.rs b/nym-node-status-api/nym-node-status-api/src/http/api/nym_nodes.rs index eebff95caf..f829082ed4 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/nym_nodes.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/nym_nodes.rs @@ -86,3 +86,33 @@ async fn node_delegations( .ok_or_else(|| HttpError::no_delegations_for_node(node_id)) .map(Json) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_routes_construction() { + let router = routes(); + // Verify the router builds without panic + let _routes = router; + } + + #[test] + fn test_node_id_param_deserialization() { + // Test valid node ID + let json = r#"{"node_id": 42}"#; + let param: NodeIdParam = serde_json::from_str(json).unwrap(); + assert_eq!(param.node_id, 42); + + // Test zero node ID + let json_zero = r#"{"node_id": 0}"#; + let param_zero: NodeIdParam = serde_json::from_str(json_zero).unwrap(); + assert_eq!(param_zero.node_id, 0); + + // Test max node ID + let json_max = format!(r#"{{"node_id": {}}}"#, u32::MAX); + let param_max: NodeIdParam = serde_json::from_str(&json_max).unwrap(); + assert_eq!(param_max.node_id, u32::MAX); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/services/json_path.rs b/nym-node-status-api/nym-node-status-api/src/http/api/services/json_path.rs index caefc6489d..6169784765 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/services/json_path.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/services/json_path.rs @@ -56,3 +56,87 @@ impl ParsedDetails { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::models::Gateway; + use nym_node_requests::api::v1::node::models::NodeDescription; + use serde_json::json; + + fn create_mock_gateway(self_described: Option) -> Gateway { + Gateway { + gateway_identity_key: "mock_identity".to_string(), + bonded: true, + performance: 100, + self_described, + explorer_pretty_bond: None, + description: NodeDescription { + moniker: "mock_moniker".to_string(), + website: "https://nymtech.net".to_string(), + security_contact: "security@nymtech.net".to_string(), + details: "mock_details".to_string(), + }, + last_probe_result: None, + last_probe_log: None, + last_testrun_utc: None, + last_updated_utc: "2025-01-01T12:00:00Z".to_string(), + routing_score: 1.0, + config_score: 100, + } + } + + #[test] + fn test_parse_json_paths() { + let paths = ParseJsonPaths::new().unwrap(); + assert_eq!( + paths.path_ip_address.to_string(), + "$.host_information.ip_address[0]" + ); + assert_eq!( + paths.path_hostname.to_string(), + "$.host_information.hostname" + ); + assert_eq!( + paths.path_service_provider_client_id.to_string(), + "$.network_requester.address" + ); + } + + #[test] + fn test_parsed_details() { + let paths = ParseJsonPaths::new().unwrap(); + + // Test with full data + let gateway1 = create_mock_gateway(Some(json!({ + "host_information": { + "ip_address": ["1.1.1.1"], + "hostname": "nymtech.net" + }, + "network_requester": { + "address": "client_address.sP" + } + }))); + let details1 = ParsedDetails::new(&paths, &gateway1); + assert_eq!(details1.ip_address, Some("1.1.1.1".to_string())); + assert_eq!(details1.hostname, Some("nymtech.net".to_string())); + assert_eq!( + details1.service_provider_client_id, + Some("client_address.sP".to_string()) + ); + + // Test with missing data + let gateway2 = create_mock_gateway(Some(json!({}))); + let details2 = ParsedDetails::new(&paths, &gateway2); + assert_eq!(details2.ip_address, None); + assert_eq!(details2.hostname, None); + assert_eq!(details2.service_provider_client_id, None); + + // Test with no self_described field + let gateway3 = create_mock_gateway(None); + let details3 = ParsedDetails::new(&paths, &gateway3); + assert_eq!(details3.ip_address, None); + assert_eq!(details3.hostname, None); + assert_eq!(details3.service_provider_client_id, None); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/services/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/services/mod.rs index 56d2f0c1b2..7d5b5d012c 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/services/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/services/mod.rs @@ -40,31 +40,39 @@ pub(crate) struct ServicesQueryParams { )] #[instrument(level = tracing::Level::DEBUG, skip(state))] async fn mixnodes( - Query(ServicesQueryParams { - size, - page, - wss, - hostname, - entry, - }): Query, + Query(params): Query, State(state): State, ) -> HttpResult>> { let db = state.db_pool(); let cache = state.cache(); - let show_only_wss = wss.unwrap_or(false); - let show_only_with_hostname = hostname.unwrap_or(false); - let show_entry_gateways_only = entry.unwrap_or(false); - let paths = ParseJsonPaths::new().map_err(|e| { tracing::error!("Invalidly configured ParseJsonPaths: {e}"); HttpError::internal() })?; let res = cache.get_gateway_list(db).await; - let res: Vec = res + let services = gateway_list_to_services(&paths, res, params.clone()); + + Ok(Json(PagedResult::paginate( + Pagination::new(params.size, params.page), + services, + ))) +} + +// Extract the conversion and filtering logic for testing +fn gateway_list_to_services( + paths: &ParseJsonPaths, + gateways: Vec, + params: ServicesQueryParams, +) -> Vec { + let show_only_wss = params.wss.unwrap_or(false); + let show_only_with_hostname = params.hostname.unwrap_or(false); + let show_entry_gateways_only = params.entry.unwrap_or(false); + + gateways .iter() .map(|g| { - let details = ParsedDetails::new(&paths, g); + let details = ParsedDetails::new(paths, g); let s = Service { gateway_identity_key: g.gateway_identity_key.clone(), @@ -86,28 +94,38 @@ async fn mixnodes( (s, f) }) .filter(|(_, f)| { - let mut keep = f.has_network_requester_sp; - - if show_entry_gateways_only { - keep = true; - } - - if show_only_wss { - keep &= f.has_wss; - } - if show_only_with_hostname { - keep &= f.has_hostname; - } - - keep + apply_service_filters( + f, + show_only_wss, + show_only_with_hostname, + show_entry_gateways_only, + ) }) .map(|(s, _)| s) - .collect(); + .collect() +} - Ok(Json(PagedResult::paginate( - Pagination::new(size, page), - res, - ))) +// Extract filter application logic +fn apply_service_filters( + filter: &ServiceFilter, + show_only_wss: bool, + show_only_with_hostname: bool, + show_entry_gateways_only: bool, +) -> bool { + let mut keep = filter.has_network_requester_sp; + + if show_entry_gateways_only { + keep = true; + } + + if show_only_wss { + keep &= filter.has_wss; + } + if show_only_with_hostname { + keep &= filter.has_hostname; + } + + keep } struct ServiceFilter { @@ -135,3 +153,253 @@ impl ServiceFilter { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::models::{Gateway, Service}; + use nym_node_requests::api::v1::node::models::NodeDescription; + use serde_json::json; + + fn create_test_gateway(key: &str, has_wss: bool, has_network_requester: bool) -> Gateway { + let mut self_described = json!({}); + if has_wss { + self_described["mixnet_websockets"] = json!({ "wss_port": 1234 }); + } + if has_network_requester { + // ParsedDetails looks for these specific paths + self_described["host_information"] = json!({ + "ip_address": ["192.168.1.1"], + "hostname": "test.nymtech.net" + }); + self_described["network_requester"] = json!({ + "address": "client123" + }); + } + + Gateway { + gateway_identity_key: key.to_string(), + bonded: true, + performance: 95, + self_described: Some(self_described), + explorer_pretty_bond: None, + description: NodeDescription { + moniker: "Test Gateway".to_string(), + website: "".to_string(), + security_contact: "".to_string(), + details: "".to_string(), + }, + last_probe_result: None, + last_probe_log: None, + last_testrun_utc: Some("2024-01-20T10:00:00Z".to_string()), + last_updated_utc: "2024-01-20T11:00:00Z".to_string(), + routing_score: 0.95, + config_score: 100, + } + } + + #[test] + fn test_service_filter() { + // Test with all fields + let service1 = Service { + gateway_identity_key: "1".to_string(), + last_updated_utc: "".to_string(), + routing_score: 1.0, + service_provider_client_id: Some("client_id".to_string()), + ip_address: Some("1.1.1.1".to_string()), + hostname: Some("nymtech.net".to_string()), + mixnet_websockets: Some(json!({ "wss_port": 1234 })), + last_successful_ping_utc: None, + }; + let filter1 = ServiceFilter::new(&service1); + assert!(filter1.has_wss); + assert!(filter1.has_network_requester_sp); + assert!(filter1.has_hostname); + + // Test with no fields + let service2 = Service { + gateway_identity_key: "2".to_string(), + last_updated_utc: "".to_string(), + routing_score: 0.0, + service_provider_client_id: None, + ip_address: None, + hostname: None, + mixnet_websockets: None, + last_successful_ping_utc: None, + }; + let filter2 = ServiceFilter::new(&service2); + assert!(!filter2.has_wss); + assert!(!filter2.has_network_requester_sp); + assert!(!filter2.has_hostname); + + // Test with some fields + let service3 = Service { + gateway_identity_key: "3".to_string(), + last_updated_utc: "".to_string(), + routing_score: 0.5, + service_provider_client_id: Some("".to_string()), + ip_address: None, + hostname: Some("nymtech.net".to_string()), + mixnet_websockets: Some(json!({})), + last_successful_ping_utc: None, + }; + let filter3 = ServiceFilter::new(&service3); + assert!(!filter3.has_wss); + assert!(!filter3.has_network_requester_sp); + assert!(filter3.has_hostname); + } + + #[test] + fn test_apply_service_filters() { + let filter_all = ServiceFilter { + has_wss: true, + has_network_requester_sp: true, + has_hostname: true, + }; + + let filter_none = ServiceFilter { + has_wss: false, + has_network_requester_sp: false, + has_hostname: false, + }; + + // Test default behavior (requires network_requester_sp) + assert!(apply_service_filters(&filter_all, false, false, false)); + assert!(!apply_service_filters(&filter_none, false, false, false)); + + // Test entry gateway mode (accepts all) + assert!(apply_service_filters(&filter_all, false, false, true)); + assert!(apply_service_filters(&filter_none, false, false, true)); + + // Test wss filter + assert!(apply_service_filters(&filter_all, true, false, false)); + assert!(!apply_service_filters(&filter_none, true, false, false)); + + // Test hostname filter + assert!(apply_service_filters(&filter_all, false, true, false)); + assert!(!apply_service_filters(&filter_none, false, true, false)); + + // Test combined filters + assert!(apply_service_filters(&filter_all, true, true, false)); + assert!(!apply_service_filters(&filter_none, true, true, false)); + + // Test entry mode does NOT override other filters - it just sets initial keep=true + // But wss and hostname filters can still exclude items + assert!(!apply_service_filters(&filter_none, true, true, true)); + } + + #[test] + fn test_gateway_list_to_services() { + let paths = ParseJsonPaths::new().unwrap(); + let gateways = vec![ + create_test_gateway("gw1", true, true), + create_test_gateway("gw2", false, true), + create_test_gateway("gw3", true, false), + create_test_gateway("gw4", false, false), + ]; + + // Test no filters - only gateways with network_requester pass + let params = ServicesQueryParams { + size: None, + page: None, + wss: None, + hostname: None, + entry: None, + }; + let services = gateway_list_to_services(&paths, gateways.clone(), params); + assert_eq!(services.len(), 2); // gw1 and gw2 have network_requester + assert!(services.iter().any(|s| s.gateway_identity_key == "gw1")); + assert!(services.iter().any(|s| s.gateway_identity_key == "gw2")); + + // Test entry mode (accepts all) + let params = ServicesQueryParams { + size: None, + page: None, + wss: None, + hostname: None, + entry: Some(true), + }; + let services = gateway_list_to_services(&paths, gateways.clone(), params); + assert_eq!(services.len(), 4); + + // Test wss filter with entry mode + let params = ServicesQueryParams { + size: None, + page: None, + wss: Some(true), + hostname: None, + entry: Some(true), + }; + let services = gateway_list_to_services(&paths, gateways.clone(), params); + assert_eq!(services.len(), 2); // gw1 and gw3 have wss + + // Test hostname filter with entry mode + let params = ServicesQueryParams { + size: None, + page: None, + wss: None, + hostname: Some(true), + entry: Some(true), + }; + let services = gateway_list_to_services(&paths, gateways.clone(), params); + assert_eq!(services.len(), 2); // gw1 and gw2 have hostname + + // Test combined filters + let params = ServicesQueryParams { + size: None, + page: None, + wss: Some(true), + hostname: Some(true), + entry: Some(true), + }; + let services = gateway_list_to_services(&paths, gateways, params); + assert_eq!(services.len(), 1); // Only gw1 has both + assert_eq!(services[0].gateway_identity_key, "gw1"); + } + + #[test] + fn test_services_query_params_defaults() { + let params = ServicesQueryParams { + size: None, + page: None, + wss: None, + hostname: None, + entry: None, + }; + + assert!(!params.wss.unwrap_or(false)); + assert!(!params.hostname.unwrap_or(false)); + assert!(!params.entry.unwrap_or(false)); + } + + #[test] + fn test_service_filter_edge_cases() { + // Test with null wss_port value + let service = Service { + gateway_identity_key: "test".to_string(), + last_updated_utc: "".to_string(), + routing_score: 1.0, + service_provider_client_id: Some("client".to_string()), + ip_address: None, + hostname: None, + mixnet_websockets: Some(json!({ "wss_port": null })), + last_successful_ping_utc: None, + }; + let filter = ServiceFilter::new(&service); + assert!(!filter.has_wss); // null port should be treated as no wss + + // Test with wss_port = 0 + let service2 = Service { + gateway_identity_key: "test2".to_string(), + last_updated_utc: "".to_string(), + routing_score: 1.0, + service_provider_client_id: None, + ip_address: None, + hostname: None, + mixnet_websockets: Some(json!({ "wss_port": 0 })), + last_successful_ping_utc: None, + }; + let filter2 = ServiceFilter::new(&service2); + assert!(filter2.has_wss); // Port 0 is still considered as having wss + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/status.rs b/nym-node-status-api/nym-node-status-api/src/http/api/status.rs index 306a7f9c2b..a269272e16 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/status.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/status.rs @@ -44,3 +44,15 @@ async fn build_information( async fn health(State(state): State) -> HttpResult> { Ok(Json(state.health())) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_routes_construction() { + let router = routes(); + // Verify the router builds without panic + let _routes = router; + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/summary.rs b/nym-node-status-api/nym-node-status-api/src/http/api/summary.rs index 729141509c..3a01ee00d2 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/summary.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/summary.rs @@ -41,3 +41,15 @@ async fn summary_history(State(state): State) -> HttpResult Router { Router::new() .route("/", axum::routing::get(request_testrun)) .route("/:testrun_id", axum::routing::post(submit_testrun)) + .route("/:testrun_id/v2", axum::routing::post(submit_testrun_v2)) .layer(DefaultBodyLimit::max(1024 * 1024 * 5)) } @@ -81,7 +83,7 @@ async fn request_testrun( #[tracing::instrument(level = "debug", skip_all)] async fn submit_testrun( - Path(submitted_testrun_id): Path, + Path(submitted_testrun_id): Path, State(state): State, Json(submitted_result): Json, ) -> HttpResult { @@ -91,7 +93,10 @@ async fn submit_testrun( let mut conn = db .acquire() .await - .map_err(HttpError::internal_with_logging)?; + .map_err(|e| { + tracing::error!(testrun_id = %submitted_testrun_id, error = %e, "Failed to acquire database connection for testrun submission"); + HttpError::internal_with_logging(e) + })?; let assigned_testrun = queries::testruns::get_in_progress_testrun_by_id(&mut conn, submitted_testrun_id) @@ -102,7 +107,10 @@ async fn submit_testrun( submitted_testrun_id, err ); - HttpError::invalid_input("Invalid testrun submitted") + HttpError::invalid_input(format!( + "Testrun {submitted_testrun_id} not found in progress state (may be already completed or expired)" + + )) })?; if Some(submitted_result.payload.assigned_at_utc) != assigned_testrun.last_assigned_utc { tracing::warn!( @@ -110,7 +118,12 @@ async fn submit_testrun( submitted_result.payload.assigned_at_utc, assigned_testrun.last_assigned_utc ); - return Err(HttpError::invalid_input("Invalid testrun submitted")); + return Err(HttpError::invalid_input(format!( + "Testrun {} timestamp mismatch: expected {:?}, got {}", + submitted_testrun_id, + assigned_testrun.last_assigned_utc, + submitted_result.payload.assigned_at_utc + ))); } let gw_identity = db::queries::select_gateway_identity(&mut conn, assigned_testrun.gateway_id) @@ -138,7 +151,7 @@ async fn submit_testrun( queries::testruns::update_gateway_last_probe_log( &mut conn, assigned_testrun.gateway_id, - &submitted_result.payload.probe_result, + submitted_result.payload.probe_result.clone(), ) .await .map_err(HttpError::internal_with_logging)?; @@ -146,7 +159,7 @@ async fn submit_testrun( queries::testruns::update_gateway_last_probe_result( &mut conn, assigned_testrun.gateway_id, - &result, + result, ) .await .map_err(HttpError::internal_with_logging)?; @@ -170,6 +183,72 @@ async fn submit_testrun( Ok(StatusCode::CREATED) } +#[tracing::instrument(level = "debug", skip_all)] +async fn submit_testrun_v2( + Path(submitted_testrun_id): Path, + State(state): State, + Json(submission): Json, +) -> HttpResult { + authenticate(&submission, &state)?; + is_fresh(&submission.payload.assigned_at_utc)?; + + let db = state.db_pool(); + let mut conn = db + .acquire() + .await + .map_err(HttpError::internal_with_logging)?; + + // Try to find existing testrun + match queries::testruns::get_testrun_by_id(&mut conn, submitted_testrun_id).await { + Ok(testrun) => { + // Validate it matches the submission + let gw_identity = queries::select_gateway_identity(&mut conn, testrun.gateway_id) + .await + .map_err(HttpError::internal_with_logging)?; + + if gw_identity != submission.payload.gateway_identity_key { + tracing::warn!( + "Gateway mismatch for testrun {}: expected {}, got {}", + submitted_testrun_id, + gw_identity, + submission.payload.gateway_identity_key + ); + return Err(HttpError::invalid_input("Gateway identity mismatch")); + } + + // Process normally using existing testrun + process_testrun_submission(testrun, submission.payload, &mut conn).await + } + Err(_) => { + // External testrun - create records + tracing::info!( + "Creating external testrun {} for gateway {}", + submitted_testrun_id, + submission.payload.gateway_identity_key + ); + + // Get or create gateway + let gateway_id = + queries::get_or_create_gateway(&mut conn, &submission.payload.gateway_identity_key) + .await + .map_err(HttpError::internal_with_logging)?; + + // Create testrun + queries::testruns::insert_external_testrun( + &mut conn, + submitted_testrun_id, + gateway_id, + submission.payload.assigned_at_utc, + ) + .await + .map_err(HttpError::internal_with_logging)?; + + // Process submission + process_testrun_submission_by_gateway(gateway_id, submission.payload, &mut conn).await + } + } +} + // TODO dz this should be middleware #[tracing::instrument(level = "debug", skip_all)] fn authenticate(request: &impl VerifiableRequest, state: &AppState) -> HttpResult<()> { @@ -186,7 +265,7 @@ fn authenticate(request: &impl VerifiableRequest, state: &AppState) -> HttpResul Ok(()) } -static FRESHNESS_CUTOFF: time::Duration = time::Duration::minutes(1); +static FRESHNESS_CUTOFF: time::Duration = time::Duration::minutes(2); fn is_fresh(request_time: &i64) -> HttpResult<()> { // if a request took longer than N minutes to reach NS API, something is very wrong @@ -197,18 +276,95 @@ fn is_fresh(request_time: &i64) -> HttpResult<()> { let cutoff_timestamp = now_utc() - FRESHNESS_CUTOFF; if request_time < cutoff_timestamp { - warn!("Request older than {}s, rejecting", cutoff_timestamp); + warn!( + "Request time {} is older than cutoff {} ({}s ago), rejecting", + request_time, + cutoff_timestamp, + FRESHNESS_CUTOFF.whole_seconds() + ); return Err(HttpError::unauthorized()); } Ok(()) } fn get_result_from_log(log: &str) -> String { - let re = regex::Regex::new(r"\n\{\s").unwrap(); - let result: Vec<_> = re.splitn(log, 2).collect(); + static RE: std::sync::LazyLock = + std::sync::LazyLock::new(|| regex::Regex::new(r"\n\{\s").expect("Invalid regex pattern")); + + let result: Vec<_> = RE.splitn(log, 2).collect(); if result.len() == 2 { let res = format!("{} {}", "{", result[1]).to_string(); return res; } "".to_string() } + +async fn process_testrun_submission( + testrun: TestRunDto, + payload: submit_results_v2::Payload, + conn: &mut DbConnection, +) -> HttpResult { + // Validate timestamp matches + if Some(payload.assigned_at_utc) != testrun.last_assigned_utc { + tracing::warn!( + "Submitted testrun timestamp mismatch: {} != {:?}, rejecting", + payload.assigned_at_utc, + testrun.last_assigned_utc + ); + return Err(HttpError::invalid_input(format!( + "Testrun timestamp mismatch: expected {:?}, got {}", + testrun.last_assigned_utc, payload.assigned_at_utc + ))); + } + + // Process the submission + process_testrun_submission_by_gateway(testrun.gateway_id, payload, conn).await +} + +async fn process_testrun_submission_by_gateway( + gateway_id: i32, + payload: submit_results_v2::Payload, + conn: &mut DbConnection, +) -> HttpResult { + let gw_identity = &payload.gateway_identity_key; + + tracing::debug!( + "Processing testrun submission for gateway {} ({} bytes)", + gw_identity, + payload.probe_result.len(), + ); + + // Update testrun status to complete + queries::testruns::update_testrun_status_by_gateway(conn, gateway_id, TestRunStatus::Complete) + .await + .map_err(HttpError::internal_with_logging)?; + + // Update gateway with results + queries::testruns::update_gateway_last_probe_log( + conn, + gateway_id, + payload.probe_result.clone(), + ) + .await + .map_err(HttpError::internal_with_logging)?; + + let result = get_result_from_log(&payload.probe_result); + queries::testruns::update_gateway_last_probe_result(conn, gateway_id, result) + .await + .map_err(HttpError::internal_with_logging)?; + + queries::testruns::update_gateway_score(conn, gateway_id) + .await + .map_err(HttpError::internal_with_logging)?; + + let assigned_at = unix_timestamp_to_utc_rfc3339(payload.assigned_at_utc); + let now = now_utc(); + tracing::info!( + "✅ Testrun for gateway {} complete (assigned at {}, current time {})", + gw_identity, + assigned_at, + now + ); + + Ok(StatusCode::CREATED) +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/error.rs b/nym-node-status-api/nym-node-status-api/src/http/error.rs index 0282958054..a58a6341e3 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/error.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/error.rs @@ -3,6 +3,7 @@ use std::fmt::Display; pub(crate) type HttpResult = Result; +#[derive(Debug)] pub(crate) struct HttpError { message: String, status: axum::http::StatusCode, @@ -55,3 +56,103 @@ impl axum::response::IntoResponse for HttpError { (self.status, self.message).into_response() } } + +#[cfg(test)] +mod tests { + use super::*; + use axum::http::StatusCode; + use axum::response::IntoResponse; + + #[test] + fn test_invalid_input_error() { + let error = HttpError::invalid_input("Invalid request data"); + assert_eq!(error.message, "Invalid request data"); + assert_eq!(error.status, StatusCode::BAD_REQUEST); + + // Test with different input types + let error2 = HttpError::invalid_input(42); + assert_eq!(error2.message, "42"); + + let error3 = HttpError::invalid_input(String::from("Dynamic string")); + assert_eq!(error3.message, "Dynamic string"); + } + + #[test] + fn test_unauthorized_error() { + let error = HttpError::unauthorized(); + assert_eq!( + error.message, + "Make sure your public key is registered with NS API" + ); + assert_eq!(error.status, StatusCode::UNAUTHORIZED); + } + + #[test] + fn test_internal_error() { + let error = HttpError::internal(); + assert_eq!(error.message, "Internal server error"); + assert_eq!(error.status, StatusCode::INTERNAL_SERVER_ERROR); + } + + #[test] + fn test_internal_with_logging() { + // This would log to error but we can still test the result + let error = HttpError::internal_with_logging("Database connection failed"); + assert_eq!(error.message, "Internal server error"); + assert_eq!(error.status, StatusCode::INTERNAL_SERVER_ERROR); + } + + #[test] + fn test_no_testruns_available() { + let error = HttpError::no_testruns_available(); + assert_eq!(error.message, "No testruns available"); + assert_eq!(error.status, StatusCode::SERVICE_UNAVAILABLE); + } + + #[test] + fn test_no_delegations_for_node() { + let node_id: NodeId = 42; + let error = HttpError::no_delegations_for_node(node_id); + assert_eq!(error.message, "No delegation data for node_id=42"); + assert_eq!(error.status, StatusCode::NOT_FOUND); + + let node_id_2: NodeId = 999; + let error2 = HttpError::no_delegations_for_node(node_id_2); + assert_eq!(error2.message, "No delegation data for node_id=999"); + } + + #[test] + fn test_into_response() { + let error = HttpError::invalid_input("Test error"); + let response = error.into_response(); + + // Extract status from response + let status = response.status(); + assert_eq!(status, StatusCode::BAD_REQUEST); + } + + #[test] + fn test_different_error_types_into_response() { + // Test each error type converts to response properly + let errors = vec![ + HttpError::invalid_input("test"), + HttpError::unauthorized(), + HttpError::internal(), + HttpError::no_testruns_available(), + HttpError::no_delegations_for_node(1), + ]; + + let expected_statuses = vec![ + StatusCode::BAD_REQUEST, + StatusCode::UNAUTHORIZED, + StatusCode::INTERNAL_SERVER_ERROR, + StatusCode::SERVICE_UNAVAILABLE, + StatusCode::NOT_FOUND, + ]; + + for (error, expected_status) in errors.into_iter().zip(expected_statuses) { + let response = error.into_response(); + assert_eq!(response.status(), expected_status); + } + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/mod.rs index 758278641a..7ea53a11d7 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/mod.rs @@ -67,3 +67,176 @@ impl Pagination { ) } } + +#[cfg(test)] +mod tests { + use super::*; + + // Use a simple type for testing instead of a custom struct + type TestItem = String; + + #[test] + fn test_pagination_default() { + let pagination = Pagination::default(); + let (size, page) = pagination.into_inner_values(); + assert_eq!(size, SIZE_DEFAULT); + assert_eq!(page, PAGE_DEFAULT); + } + + #[test] + fn test_pagination_new() { + let pagination = Pagination::new(Some(50), Some(3)); + let (size, page) = pagination.into_inner_values(); + assert_eq!(size, 50); + assert_eq!(page, 3); + } + + #[test] + fn test_pagination_max_size_limit() { + let pagination = Pagination::new(Some(1000), Some(0)); + let (size, page) = pagination.into_inner_values(); + assert_eq!(size, SIZE_MAX); + assert_eq!(page, 0); + } + + #[test] + fn test_pagination_none_values() { + let pagination = Pagination::new(None, None); + let (size, page) = pagination.into_inner_values(); + assert_eq!(size, SIZE_DEFAULT); + assert_eq!(page, PAGE_DEFAULT); + } + + #[test] + fn test_paged_result_empty_list() { + let items: Vec = vec![]; + let pagination = Pagination::new(Some(10), Some(0)); + let result = PagedResult::paginate(pagination, items); + + assert_eq!(result.page, 0); + assert_eq!(result.size, 10); + assert_eq!(result.total, 0); + assert_eq!(result.items.len(), 0); + } + + #[test] + fn test_paged_result_single_page() { + let items: Vec = (0..5).map(|i| format!("Item {i}")).collect(); + + let pagination = Pagination::new(Some(10), Some(0)); + let result = PagedResult::paginate(pagination, items.clone()); + + assert_eq!(result.page, 0); + assert_eq!(result.size, 10); + assert_eq!(result.total, 5); + assert_eq!(result.items.len(), 5); + assert_eq!(result.items[0], "Item 0"); + assert_eq!(result.items[4], "Item 4"); + } + + #[test] + fn test_paged_result_multiple_pages() { + let items: Vec = (0..25).map(|i| format!("Item {i}")).collect(); + + // First page + let pagination = Pagination::new(Some(10), Some(0)); + let result = PagedResult::paginate(pagination, items.clone()); + assert_eq!(result.page, 0); + assert_eq!(result.size, 10); + assert_eq!(result.total, 25); + assert_eq!(result.items.len(), 10); + assert_eq!(result.items[0], "Item 0"); + assert_eq!(result.items[9], "Item 9"); + + // Second page + let pagination = Pagination::new(Some(10), Some(1)); + let result = PagedResult::paginate(pagination, items.clone()); + assert_eq!(result.page, 1); + assert_eq!(result.size, 10); + assert_eq!(result.total, 25); + assert_eq!(result.items.len(), 10); + assert_eq!(result.items[0], "Item 10"); + assert_eq!(result.items[9], "Item 19"); + + // Last page (partial) + let pagination = Pagination::new(Some(10), Some(2)); + let result = PagedResult::paginate(pagination, items); + assert_eq!(result.page, 2); + assert_eq!(result.size, 10); + assert_eq!(result.total, 25); + assert_eq!(result.items.len(), 5); + assert_eq!(result.items[0], "Item 20"); + assert_eq!(result.items[4], "Item 24"); + } + + #[test] + fn test_paged_result_page_out_of_bounds() { + let items: Vec = (0..15).map(|i| format!("Item {i}")).collect(); + + // Page way out of bounds + let pagination = Pagination::new(Some(10), Some(10)); + let result = PagedResult::paginate(pagination, items); + + // Should adjust to last valid page + assert_eq!(result.page, 1); // 15 items / 10 per page = 1 (0-indexed) + assert_eq!(result.size, 10); + assert_eq!(result.total, 15); + assert_eq!(result.items.len(), 5); + assert_eq!(result.items[0], "Item 10"); + } + + #[test] + fn test_paged_result_exact_page_boundary() { + let items: Vec = (0..20).map(|i| format!("Item {i}")).collect(); + + // Exactly 2 pages of 10 items each + let pagination = Pagination::new(Some(10), Some(1)); + let result = PagedResult::paginate(pagination, items); + + assert_eq!(result.page, 1); + assert_eq!(result.size, 10); + assert_eq!(result.total, 20); + assert_eq!(result.items.len(), 10); + } + + #[test] + fn test_paged_result_single_item_per_page() { + let items: Vec = (0..5).map(|i| format!("Item {i}")).collect(); + + let pagination = Pagination::new(Some(1), Some(3)); + let result = PagedResult::paginate(pagination, items); + + assert_eq!(result.page, 3); + assert_eq!(result.size, 1); + assert_eq!(result.total, 5); + assert_eq!(result.items.len(), 1); + assert_eq!(result.items[0], "Item 3"); + } + + #[test] + fn test_pagination_serialization() { + let pagination = Pagination::new(Some(25), Some(2)); + let json = serde_json::to_string(&pagination).unwrap(); + assert!(json.contains("\"size\":25")); + assert!(json.contains("\"page\":2")); + + let deserialized: Pagination = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized.size, Some(25)); + assert_eq!(deserialized.page, Some(2)); + } + + #[test] + fn test_paged_result_serialization() { + let items = vec!["First".to_string(), "Second".to_string()]; + let pagination = Pagination::new(Some(10), Some(0)); + let result = PagedResult::paginate(pagination, items); + + let json = serde_json::to_string(&result).unwrap(); + assert!(json.contains("\"page\":0")); + assert!(json.contains("\"size\":10")); + assert!(json.contains("\"total\":2")); + assert!(json.contains("\"items\":")); + assert!(json.contains("First")); + assert!(json.contains("Second")); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/models.rs b/nym-node-status-api/nym-node-status-api/src/http/models.rs index d99da2c698..f665067814 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/models.rs @@ -236,6 +236,7 @@ fn to_percent(performance: u8) -> String { #[cfg(test)] mod test { use super::*; + use std::str::FromStr; #[test] fn to_percent_should_work() { @@ -246,6 +247,241 @@ mod test { assert_eq!(expected, to_percent(starting)); } } + + #[test] + fn to_percent_edge_cases() { + // Test edge cases + assert_eq!("0.00", to_percent(0)); + assert_eq!("1.00", to_percent(100)); + assert_eq!("2.55", to_percent(255)); // Over 100% + } + + #[test] + fn node_delegation_from_conversion() { + use cosmwasm_std::Uint128; + + let delegation = nym_mixnet_contract_common::Delegation { + node_id: 42, + amount: Coin { + denom: "unym".to_string(), + amount: Uint128::new(1000000), + }, + cumulative_reward_ratio: Decimal::from_str("1.23456789").unwrap(), + height: 12345, + owner: Addr::unchecked("owner1"), + proxy: Some(Addr::unchecked("proxy1")), + }; + + let node_delegation: NodeDelegation = delegation.clone().into(); + + assert_eq!(node_delegation.amount.denom, "unym"); + assert_eq!(node_delegation.amount.amount, Uint128::new(1000000)); + assert_eq!(node_delegation.cumulative_reward_ratio, "1.23456789"); + assert_eq!(node_delegation.block_height, 12345); + assert_eq!(node_delegation.owner, Addr::unchecked("owner1")); + assert_eq!(node_delegation.proxy, Some(Addr::unchecked("proxy1"))); + } + + #[test] + fn node_delegation_from_conversion_no_proxy() { + use cosmwasm_std::Uint128; + + let delegation = nym_mixnet_contract_common::Delegation { + node_id: 0, + amount: Coin { + denom: "uatom".to_string(), + amount: Uint128::new(0), + }, + cumulative_reward_ratio: Decimal::zero(), + height: 0, + owner: Addr::unchecked("owner2"), + proxy: None, + }; + + let node_delegation: NodeDelegation = delegation.into(); + + assert_eq!(node_delegation.amount.denom, "uatom"); + assert_eq!(node_delegation.amount.amount, Uint128::new(0)); + assert_eq!(node_delegation.cumulative_reward_ratio, "0"); + assert_eq!(node_delegation.block_height, 0); + assert_eq!(node_delegation.owner, Addr::unchecked("owner2")); + assert_eq!(node_delegation.proxy, None); + } + + #[test] + fn node_delegation_from_conversion_max_values() { + use cosmwasm_std::Uint128; + + let delegation = nym_mixnet_contract_common::Delegation { + node_id: u32::MAX, + amount: Coin { + denom: "test".to_string(), + amount: Uint128::MAX, + }, + cumulative_reward_ratio: Decimal::from_str("999999999.999999999").unwrap(), + height: u64::MAX, + owner: Addr::unchecked("owner3"), + proxy: Some(Addr::unchecked("proxy3")), + }; + + let node_delegation: NodeDelegation = delegation.into(); + + assert_eq!(node_delegation.amount.amount, Uint128::MAX); + assert_eq!( + node_delegation.cumulative_reward_ratio, + "999999999.999999999" + ); + assert_eq!(node_delegation.block_height, u64::MAX); + } + + #[test] + fn location_struct_creation() { + let location = Location { + two_letter_iso_country_code: "US".to_string(), + latitude: 40.7128, + longitude: -74.0060, + }; + + assert_eq!(location.two_letter_iso_country_code, "US"); + assert_eq!(location.latitude, 40.7128); + assert_eq!(location.longitude, -74.0060); + } + + #[test] + fn location_extreme_coordinates() { + // Test extreme coordinates + let north_pole = Location { + two_letter_iso_country_code: "XX".to_string(), + latitude: 90.0, + longitude: 0.0, + }; + + let south_pole = Location { + two_letter_iso_country_code: "AQ".to_string(), + latitude: -90.0, + longitude: 0.0, + }; + + let date_line = Location { + two_letter_iso_country_code: "FJ".to_string(), + latitude: -17.0, + longitude: 180.0, + }; + + assert_eq!(north_pole.latitude, 90.0); + assert_eq!(south_pole.latitude, -90.0); + assert_eq!(date_line.longitude, 180.0); + } + + #[test] + fn build_information_creation() { + let build_info = BuildInformation { + build_version: "1.2.3".to_string(), + commit_branch: "main".to_string(), + commit_sha: "abcdef123456".to_string(), + }; + + assert_eq!(build_info.build_version, "1.2.3"); + assert_eq!(build_info.commit_branch, "main"); + assert_eq!(build_info.commit_sha, "abcdef123456"); + } + + #[test] + fn daily_stats_creation() { + let stats = DailyStats { + date_utc: "2024-01-20".to_string(), + total_packets_received: 1_000_000, + total_packets_sent: 999_000, + total_packets_dropped: 1_000, + total_stake: 5_000_000, + }; + + assert_eq!(stats.date_utc, "2024-01-20"); + assert_eq!(stats.total_packets_received, 1_000_000); + assert_eq!(stats.total_packets_sent, 999_000); + assert_eq!(stats.total_packets_dropped, 1_000); + assert_eq!(stats.total_stake, 5_000_000); + } + + #[test] + fn daily_stats_negative_values() { + // Test with edge case values + let stats = DailyStats { + date_utc: "".to_string(), + total_packets_received: i64::MAX, + total_packets_sent: 0, + total_packets_dropped: -1, // Should this be allowed? + total_stake: i64::MIN, + }; + + assert_eq!(stats.date_utc, ""); + assert_eq!(stats.total_packets_received, i64::MAX); + assert_eq!(stats.total_packets_sent, 0); + assert_eq!(stats.total_packets_dropped, -1); + assert_eq!(stats.total_stake, i64::MIN); + } + + #[test] + fn gateway_skinny_creation() { + let gateway = GatewaySkinny { + gateway_identity_key: "gateway123".to_string(), + self_described: Some(serde_json::json!({"test": "value"})), + explorer_pretty_bond: None, + last_probe_result: Some(serde_json::json!({"status": "ok"})), + last_testrun_utc: Some("2024-01-20T10:00:00Z".to_string()), + last_updated_utc: "2024-01-20T11:00:00Z".to_string(), + routing_score: 0.95, + config_score: 100, + performance: 98, + }; + + assert_eq!(gateway.gateway_identity_key, "gateway123"); + assert!(gateway.self_described.is_some()); + assert!(gateway.explorer_pretty_bond.is_none()); + assert_eq!(gateway.performance, 98); + assert_eq!(gateway.routing_score, 0.95); + } + + #[test] + fn service_creation_with_all_fields() { + let service = Service { + gateway_identity_key: "gw123".to_string(), + last_updated_utc: "2024-01-20T10:00:00Z".to_string(), + routing_score: 0.85, + service_provider_client_id: Some("client123".to_string()), + ip_address: Some("192.168.1.1".to_string()), + hostname: Some("gateway.example.com".to_string()), + mixnet_websockets: Some(serde_json::json!({"port": 8080})), + last_successful_ping_utc: Some("2024-01-20T09:55:00Z".to_string()), + }; + + assert_eq!(service.gateway_identity_key, "gw123"); + assert_eq!(service.routing_score, 0.85); + assert_eq!(service.ip_address, Some("192.168.1.1".to_string())); + assert_eq!(service.hostname, Some("gateway.example.com".to_string())); + } + + #[test] + fn service_creation_minimal() { + let service = Service { + gateway_identity_key: "gw456".to_string(), + last_updated_utc: "2024-01-20T10:00:00Z".to_string(), + routing_score: 0.0, + service_provider_client_id: None, + ip_address: None, + hostname: None, + mixnet_websockets: None, + last_successful_ping_utc: None, + }; + + assert_eq!(service.gateway_identity_key, "gw456"); + assert_eq!(service.routing_score, 0.0); + assert!(service.service_provider_client_id.is_none()); + assert!(service.ip_address.is_none()); + assert!(service.hostname.is_none()); + assert!(service.mixnet_websockets.is_none()); + assert!(service.last_successful_ping_utc.is_none()); + } } #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] diff --git a/nym-node-status-api/nym-node-status-api/src/http/state.rs b/nym-node-status-api/nym-node-status-api/src/http/state.rs index 8d197f9a07..03f48cb0d6 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/state.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/state.rs @@ -7,7 +7,7 @@ use nym_crypto::asymmetric::ed25519::PublicKey; use nym_mixnet_contract_common::NodeId; use nym_validator_client::nym_api::SkimmedNode; use semver::Version; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::{collections::HashMap, sync::Arc, time::Duration}; use time::UtcDateTime; use tokio::sync::RwLock; @@ -181,14 +181,24 @@ impl HttpCache { // the key is missing so populate it tracing::trace!("No gateways in cache, refreshing cache from DB..."); - let gateways = crate::db::queries::get_all_gateways(db) - .await - .unwrap_or_default(); + let gateways = match crate::db::queries::get_all_gateways(db).await { + Ok(gws) => { + tracing::info!("Successfully fetched {} gateways from database", gws.len()); + if !gws.is_empty() { + self.upsert_gateway_list(gws.clone()).await; + } + gws + } + Err(err) => { + tracing::error!("CRITICAL: Failed to fetch gateways from database: {err}"); + panic!( + "Cannot read gateways table - this should never happen! Error: {err}" + ); + } + }; if gateways.is_empty() { tracing::warn!("Database: gateway list is empty"); - } else { - self.upsert_gateway_list(gateways.clone()).await; } gateways @@ -227,60 +237,64 @@ impl HttpCache { read_lock.clone() } None => { - tracing::trace!("No gateways (dVPN) in cache, refreshing from DB..."); + tracing::info!("No gateways (dVPN) in cache, refreshing from DB..."); let gateways = self.get_gateway_list(db).await; + tracing::info!("Found {} gateways in database", gateways.len()); let started_with = gateways.len(); - let Ok(skimmed_nodes) = crate::db::queries::get_described_bonded_nym_nodes(db) + let skimmed_nodes = match crate::db::queries::get_described_bonded_nym_nodes(db) .await - .map(|records| { - records - .into_iter() - .filter_map(|dto| { - SkimmedNode::try_from(dto) - .inspect_err(|err| { - error!("Failed to read SkimmedNode from DB: {err}") - }) - .ok() - }) - .map(|skimmed_node| { - ( - skimmed_node.ed25519_identity_pubkey.to_base58_string(), - skimmed_node, - ) - }) - .collect::>() - }) - .inspect_err(|err| { - // this would fail only in case of internal error: log and return gracefully - error!("Failed to get nym_nodes from DB: {err}"); - }) - else { - return Vec::new(); + { + Ok(records) => { + let mut nodes = HashMap::new(); + for dto in records { + match SkimmedNode::try_from(dto) { + Ok(skimmed_node) => { + let key = + skimmed_node.ed25519_identity_pubkey.to_base58_string(); + nodes.insert(key, skimmed_node); + } + Err(err) => { + error!("CRITICAL: Failed to convert NymNodeDto to SkimmedNode: {err}"); + panic!("Cannot convert database record to SkimmedNode - this should never happen! Error: {err}"); + } + } + } + nodes + } + Err(err) => { + error!("CRITICAL: Failed to query nym_nodes from database: {err}"); + panic!( + "Cannot read nym_nodes table - database connection issue? Error: {err}" + ); + } }; let res_gws = gateways - .into_iter() + .iter() .filter(|gw| gw.bonded) .filter_map(|gw| match skimmed_nodes.get(&gw.gateway_identity_key) { Some(skimmed_node) => Some((gw, skimmed_node)), None => { - warn!( - "No SkimmedNode data found for GW, identity_key={}", + error!( + "CRITICAL: Gateway {} exists in gateways table but not in nym_nodes table! This should not happen.", gw.gateway_identity_key ); None } }) .filter_map( - |(gw, skimmed_node)| match DVpnGateway::new(gw, skimmed_node) { + |(gw, skimmed_node)| match DVpnGateway::new(gw.clone(), skimmed_node) { Ok(gw) => Some(gw), Err(err) => { error!( - "Failed to convert GW node_id={} to dVPN form: {}", - skimmed_node.node_id, err + "CRITICAL: Failed to create DVpnGateway for node_id={}, identity_key={}: {}", + skimmed_node.node_id, + skimmed_node.ed25519_identity_pubkey.to_base58_string(), + err ); + // Don't panic here as this might be due to missing fields, but log it loudly None } }, @@ -315,9 +329,26 @@ impl HttpCache { }) .collect::>(); + let bonded_count = gateways.iter().filter(|gw| gw.bonded).count(); + tracing::info!( + "DVpn gateway filtering: {} total gateways, {} bonded, {} nym_nodes, {} final DVpn gateways", + started_with, + bonded_count, + skimmed_nodes.len(), + res_gws.len() + ); + if res_gws.is_empty() && started_with > 0 { - tracing::warn!("Started with {}, got 0 gateways", started_with); + tracing::error!( + "CRITICAL: Started with {} gateways but got 0 DVpn gateways! Min version: {}", + started_with, + min_node_version + ); } else { + tracing::info!( + "Successfully loaded {} DVpn gateways into cache", + res_gws.len() + ); self.upsert_dvpn_gateway_list(res_gws.clone()).await; } @@ -663,7 +694,7 @@ impl BinaryInfo { } } -#[derive(Serialize, ToSchema)] +#[derive(Serialize, ToSchema, Deserialize)] pub(crate) struct HealthInfo { - uptime: i64, + pub(crate) uptime: i64, } diff --git a/nym-node-status-api/nym-node-status-api/src/main.rs b/nym-node-status-api/nym-node-status-api/src/main.rs index 5abe8712cf..60ebb3bb06 100644 --- a/nym-node-status-api/nym-node-status-api/src/main.rs +++ b/nym-node-status-api/nym-node-status-api/src/main.rs @@ -5,6 +5,12 @@ use nym_task::signal::wait_for_signal; use nym_validator_client::nyxd::NyxdClient; use std::sync::Arc; +#[cfg(all(feature = "sqlite", feature = "pg"))] +compile_error!("Features 'sqlite' and 'pg' are mutually exclusive"); + +#[cfg(not(any(feature = "sqlite", feature = "pg")))] +compile_error!("Either 'sqlite' or 'pg' feature must be enabled"); + mod cli; mod db; mod http; diff --git a/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs b/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs index 42f96647e3..99cff49a06 100644 --- a/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs +++ b/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs @@ -16,3 +16,124 @@ pub enum NodeScraperError { #[error("node {node_id} with host '{host}' doesn't seem to expose its declared http port nor any of the standard API ports, i.e.: 80, 443 or {}", DEFAULT_NYM_NODE_HTTP_PORT)] NoHttpPortsAvailable { host: String, node_id: NodeId }, } + +#[cfg(test)] +mod tests { + use super::*; + use std::error::Error; + + #[test] + fn test_malformed_host_error() { + // Create a generic error to test with + let source_error = NymNodeApiClientError::GenericRequestFailure("Invalid URL".to_string()); + let error = NodeScraperError::MalformedHost { + host: "invalid-host:abc".to_string(), + node_id: 42, + source: source_error, + }; + + // Test error message formatting + let error_msg = error.to_string(); + assert!(error_msg.contains("node 42")); + assert!(error_msg.contains("invalid-host:abc")); + assert!(error_msg.contains("malformed host information")); + + // Test that source error is accessible + assert!(error.source().is_some()); + } + + #[test] + fn test_malformed_host_error_edge_cases() { + // Test with empty host + let error = NodeScraperError::MalformedHost { + host: "".to_string(), + node_id: 0, + source: NymNodeApiClientError::NotFound, + }; + + let error_msg = error.to_string(); + assert!(error_msg.contains("node 0")); + + // Test with very long host + let long_host = "x".repeat(1000); + let error = NodeScraperError::MalformedHost { + host: long_host.clone(), + node_id: u32::MAX, + source: NymNodeApiClientError::GenericRequestFailure("Too long".to_string()), + }; + + let error_msg = error.to_string(); + assert!(error_msg.contains(&format!("node {}", u32::MAX))); + assert!(error_msg.contains(&long_host)); + } + + #[test] + fn test_no_http_ports_available_error() { + let error = NodeScraperError::NoHttpPortsAvailable { + host: "example.com".to_string(), + node_id: 123, + }; + + let error_msg = error.to_string(); + assert!(error_msg.contains("node 123")); + assert!(error_msg.contains("example.com")); + assert!(error_msg.contains("doesn't seem to expose its declared http port")); + assert!(error_msg.contains("80, 443 or")); + assert!(error_msg.contains(&DEFAULT_NYM_NODE_HTTP_PORT.to_string())); + + // This error type has no source + assert!(error.source().is_none()); + } + + #[test] + fn test_no_http_ports_special_characters() { + // Test with host containing special characters + let error = NodeScraperError::NoHttpPortsAvailable { + host: "test-node_123.example.com:8080".to_string(), + node_id: 999, + }; + + let error_msg = error.to_string(); + assert!(error_msg.contains("test-node_123.example.com:8080")); + } + + #[test] + fn test_error_different_sources() { + // Test with different NymNodeApiClientError variants + let not_found_error = NymNodeApiClientError::NotFound; + let error1 = NodeScraperError::MalformedHost { + host: "host1".to_string(), + node_id: 1, + source: not_found_error, + }; + + let generic_error = NymNodeApiClientError::GenericRequestFailure("404 error".to_string()); + let error2 = NodeScraperError::MalformedHost { + host: "host2".to_string(), + node_id: 2, + source: generic_error, + }; + + // Both should format differently based on their source + assert!(error1.to_string().contains("host1")); + assert!(error2.to_string().contains("host2")); + } + + #[test] + fn test_error_trait_implementation() { + // Test that NodeScraperError implements std::error::Error properly + let error = NodeScraperError::NoHttpPortsAvailable { + host: "test.com".to_string(), + node_id: 42, + }; + + // Can be used as dyn Error + let _error_ref: &dyn std::error::Error = &error; + + // Display trait is implemented + let _display = format!("{error}"); + + // Debug trait is implemented + let _debug = format!("{error:?}"); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs index 0711299088..2bd1745a68 100644 --- a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs @@ -252,17 +252,23 @@ impl Monitor { // let nodes_summary = vec![ - (NYMNODE_COUNT, nym_node_count), - (ASSIGNED_MIXING_COUNT, assigned_mixing_count), - (MIXNODES_LEGACY_COUNT, count_legacy_mixnodes), - (NYMNODES_DESCRIBED_COUNT, described_nodes.len()), - (GATEWAYS_BONDED_COUNT, count_bonded_gateways), - (ASSIGNED_ENTRY_COUNT, assigned_entry_count), - (ASSIGNED_EXIT_COUNT, assigned_exit_count), + (NYMNODE_COUNT.to_string(), nym_node_count), + (ASSIGNED_MIXING_COUNT.to_string(), assigned_mixing_count), + (MIXNODES_LEGACY_COUNT.to_string(), count_legacy_mixnodes), + (NYMNODES_DESCRIBED_COUNT.to_string(), described_nodes.len()), + (GATEWAYS_BONDED_COUNT.to_string(), count_bonded_gateways), + (ASSIGNED_ENTRY_COUNT.to_string(), assigned_entry_count), + (ASSIGNED_EXIT_COUNT.to_string(), assigned_exit_count), // TODO dz doesn't make sense, could make sense with historical Nym // Nodes if we really need this data - (MIXNODES_HISTORICAL_COUNT, all_historical_mixnodes), - (GATEWAYS_HISTORICAL_COUNT, all_historical_gateways), + ( + MIXNODES_HISTORICAL_COUNT.to_string(), + all_historical_mixnodes, + ), + ( + GATEWAYS_HISTORICAL_COUNT.to_string(), + all_historical_gateways, + ), ]; let last_updated = now_utc(); @@ -295,7 +301,8 @@ impl Monitor { }, }; - queries::insert_summaries(&pool, &nodes_summary, &network_summary, last_updated).await?; + queries::insert_summaries(&pool, nodes_summary.clone(), network_summary, last_updated) + .await?; let mut log_lines: Vec = vec![]; for (key, value) in nodes_summary.iter() { @@ -495,15 +502,31 @@ impl Monitor { async fn historical_count(pool: &DbPool) -> anyhow::Result<(usize, usize)> { let mut conn = pool.acquire().await?; + #[cfg(feature = "sqlite")] let all_historical_gateways = sqlx::query_scalar!(r#"SELECT count(id) FROM gateways"#) .fetch_one(&mut *conn) .await? .cast_checked()?; + #[cfg(feature = "pg")] + let all_historical_gateways = sqlx::query_scalar!(r#"SELECT count(id) FROM gateways"#) + .fetch_one(&mut *conn) + .await? + .unwrap_or(0) + .cast_checked()?; + + #[cfg(feature = "sqlite")] let all_historical_mixnodes = sqlx::query_scalar!(r#"SELECT count(id) FROM mixnodes"#) .fetch_one(&mut *conn) .await? .cast_checked()?; + #[cfg(feature = "pg")] + let all_historical_mixnodes = sqlx::query_scalar!(r#"SELECT count(id) FROM mixnodes"#) + .fetch_one(&mut *conn) + .await? + .unwrap_or(0) + .cast_checked()?; + Ok((all_historical_gateways, all_historical_mixnodes)) } diff --git a/nym-node-status-api/nym-node-status-api/src/node_scraper/description.rs b/nym-node-status-api/nym-node-status-api/src/node_scraper/description.rs index 7a482f7ca3..aa4096de1f 100644 --- a/nym-node-status-api/nym-node-status-api/src/node_scraper/description.rs +++ b/nym-node-status-api/nym-node-status-api/src/node_scraper/description.rs @@ -1,6 +1,6 @@ use super::helpers::scrape_and_store_description; +use crate::db::DbPool; use anyhow::Result; -use sqlx::SqlitePool; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -17,12 +17,12 @@ static TASK_COUNTER: AtomicUsize = AtomicUsize::new(0); static TASK_ID_COUNTER: AtomicUsize = AtomicUsize::new(0); pub struct DescriptionScraper { - pool: SqlitePool, + pool: DbPool, description_queue: Arc>>, } impl DescriptionScraper { - pub fn new(pool: SqlitePool) -> Self { + pub fn new(pool: DbPool) -> Self { Self { pool, description_queue: Arc::new(Mutex::new(Vec::new())), @@ -50,7 +50,7 @@ impl DescriptionScraper { #[instrument(level = "info", name = "description_scraper", skip_all)] async fn run_description_scraper( - pool: &SqlitePool, + pool: &DbPool, queue: Arc>>, ) -> Result<()> { let nodes = get_nodes_for_scraping(pool).await?; @@ -65,7 +65,7 @@ impl DescriptionScraper { Ok(()) } - async fn process_description_queue(pool: &SqlitePool, queue: Arc>>) { + async fn process_description_queue(pool: &DbPool, queue: Arc>>) { loop { let running_tasks = TASK_COUNTER.load(Ordering::Relaxed); @@ -88,7 +88,7 @@ impl DescriptionScraper { let pool = pool.clone(); tokio::spawn(async move { - match scrape_and_store_description(&pool, &node).await { + match scrape_and_store_description(&pool, node.clone()).await { Ok(_) => debug!( "📝 ✅ Description task #{} for node {} complete", task_id, diff --git a/nym-node-status-api/nym-node-status-api/src/node_scraper/helpers.rs b/nym-node-status-api/nym-node-status-api/src/node_scraper/helpers.rs index 6a1045fd34..9b861cd4c3 100644 --- a/nym-node-status-api/nym-node-status-api/src/node_scraper/helpers.rs +++ b/nym-node-status-api/nym-node-status-api/src/node_scraper/helpers.rs @@ -1,21 +1,19 @@ use crate::{ db::{ models::{InsertStatsRecord, NodeStats, ScrapeNodeKind, ScraperNodeInfo}, - queries::{ - get_raw_node_stats, insert_daily_node_stats_uncommitted, - insert_scraped_node_description, - }, + queries::insert_scraped_node_description, + DbPool, }, utils::{generate_node_name, now_utc}, }; use ammonia::Builder; use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; -use sqlx::{SqlitePool, Transaction}; +use sqlx::Transaction; use std::time::Duration; use time::UtcDateTime; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct NodeDescriptionResponse { pub moniker: Option, pub website: Option, @@ -28,10 +26,11 @@ const DESCRIPTION_URL: &str = "/description"; const PACKET_STATS_URL: &str = "/stats"; // We need this as some of the mixnodes respond with float values for the packet statistics (?????) -pub fn get_packet_value(response: &serde_json::Value, key: &str) -> Option { +pub fn get_packet_value(response: &serde_json::Value, key: &str) -> Option { response .get(key) .and_then(|value| value.as_i64().or_else(|| value.as_f64().map(|f| f as i64))) + .map(|v| v as i32) } pub fn parse_mixnet_stats(response: serde_json::Value) -> Option { @@ -64,7 +63,7 @@ pub fn parse_mixnet_stats(response: serde_json::Value) -> Option { None } -pub fn calculate_packet_difference(current: i64, previous: i64) -> i64 { +pub fn calculate_packet_difference(current: i32, previous: i32) -> i32 { if current >= previous { current - previous } else { @@ -94,10 +93,10 @@ pub fn sanitize_description( const UNKNOWN: &str = "N/A"; let sanitize_field = |opt: Option| -> Option { - Some( - opt.filter(|s| !s.trim().is_empty()) - .map_or_else(|| UNKNOWN.to_string(), |s| sanitizer.clean(&s).to_string()), - ) + Some(opt.filter(|s| !s.trim().is_empty()).map_or_else( + || UNKNOWN.to_string(), + |s| sanitizer.clean(s.trim()).to_string().trim().to_string(), + )) }; let mut moniker = sanitize_field(description.moniker); @@ -115,7 +114,7 @@ pub fn sanitize_description( } } -pub async fn scrape_and_store_description(pool: &SqlitePool, node: &ScraperNodeInfo) -> Result<()> { +pub async fn scrape_and_store_description(pool: &DbPool, node: ScraperNodeInfo) -> Result<()> { let client = build_client()?; let urls = node.contact_addresses(); @@ -150,7 +149,7 @@ pub async fn scrape_and_store_description(pool: &SqlitePool, node: &ScraperNodeI })?; let sanitized_description = sanitize_description(description, *node.node_id()); - insert_scraped_node_description(pool, &node.node_kind, &sanitized_description).await?; + insert_scraped_node_description(pool, node.node_kind.clone(), sanitized_description).await?; Ok(()) } @@ -195,12 +194,15 @@ pub async fn scrape_packet_stats(node: &ScraperNodeInfo) -> Result, node_kind: &ScrapeNodeKind, timestamp: UtcDateTime, current_stats: &NodeStats, ) -> Result<()> { + use crate::db::queries::{get_raw_node_stats, insert_daily_node_stats_uncommitted}; + let date_utc = format!( "{:04}-{:02}-{:02}", timestamp.year(), @@ -235,3 +237,123 @@ pub async fn update_daily_stats_uncommitted( Ok(()) } + +#[cfg(feature = "pg")] +pub async fn update_daily_stats_uncommitted( + tx: &mut Transaction<'static, sqlx::Postgres>, + node_kind: &ScrapeNodeKind, + timestamp: UtcDateTime, + current_stats: &NodeStats, +) -> Result<()> { + use crate::db::queries::{get_raw_node_stats, insert_daily_node_stats_uncommitted}; + + let date_utc = format!( + "{:04}-{:02}-{:02}", + timestamp.year(), + timestamp.month() as u8, + timestamp.day() + ); + + // Get previous stats + let previous_stats = get_raw_node_stats(tx, node_kind).await?; + + let (diff_received, diff_sent, diff_dropped) = if let Some(prev) = previous_stats { + ( + calculate_packet_difference(current_stats.packets_received, prev.packets_received), + calculate_packet_difference(current_stats.packets_sent, prev.packets_sent), + calculate_packet_difference(current_stats.packets_dropped, prev.packets_dropped), + ) + } else { + (0, 0, 0) // No previous stats available + }; + + insert_daily_node_stats_uncommitted( + tx, + node_kind, + &date_utc, + NodeStats { + packets_received: diff_received, + packets_sent: diff_sent, + packets_dropped: diff_dropped, + }, + ) + .await?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_get_packet_value() { + let json = json!({ "packets": 100, "dropped": 50.5 }); + assert_eq!(get_packet_value(&json, "packets"), Some(100)); + assert_eq!(get_packet_value(&json, "dropped"), Some(50)); + assert_eq!(get_packet_value(&json, "non_existent"), None); + } + + #[test] + fn test_parse_mixnet_stats() { + let old_format = json!({ + "packets_explicitly_dropped_since_startup": 10, + "packets_sent_since_startup": 100, + "packets_received_since_startup": 200 + }); + let new_format = json!({ + "dropped_since_startup": 20, + "sent_since_startup": 150, + "received_since_startup": 250 + }); + let invalid_format = json!({ "foo": "bar" }); + + let stats1 = parse_mixnet_stats(old_format).unwrap(); + assert_eq!(stats1.packets_dropped, 10); + assert_eq!(stats1.packets_sent, 100); + assert_eq!(stats1.packets_received, 200); + + let stats2 = parse_mixnet_stats(new_format).unwrap(); + assert_eq!(stats2.packets_dropped, 20); + assert_eq!(stats2.packets_sent, 150); + assert_eq!(stats2.packets_received, 250); + + assert!(parse_mixnet_stats(invalid_format).is_none()); + } + + #[test] + fn test_calculate_packet_difference() { + assert_eq!(calculate_packet_difference(100, 50), 50); + assert_eq!(calculate_packet_difference(50, 100), 50); + assert_eq!(calculate_packet_difference(100, 100), 0); + } + + #[test] + fn test_sanitize_description() { + let desc = NodeDescriptionResponse { + moniker: Some(" Moniker ".to_string()), + website: Some("https://example.com".to_string()), + security_contact: Some("".to_string()), + details: None, + }; + + let sanitized = sanitize_description(desc, 123); + assert_eq!(sanitized.moniker, Some("Moniker".to_string())); + assert_eq!(sanitized.website, Some("https://example.com".to_string())); + assert_eq!(sanitized.security_contact, Some("N/A".to_string())); + assert_eq!(sanitized.details, Some("N/A".to_string())); + + let desc_empty = NodeDescriptionResponse { + moniker: None, + website: None, + security_contact: None, + details: None, + }; + let sanitized_empty = sanitize_description(desc_empty, 123); + assert_ne!(sanitized_empty.moniker, Some("N/A".to_string())); // should generate a name + assert_eq!(sanitized_empty.website, Some("N/A".to_string())); + assert_eq!(sanitized_empty.security_contact, Some("N/A".to_string())); + assert_eq!(sanitized_empty.details, Some("N/A".to_string())); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/node_scraper/packet_stats.rs b/nym-node-status-api/nym-node-status-api/src/node_scraper/packet_stats.rs index e76244a18f..e8a53d7ca4 100644 --- a/nym-node-status-api/nym-node-status-api/src/node_scraper/packet_stats.rs +++ b/nym-node-status-api/nym-node-status-api/src/node_scraper/packet_stats.rs @@ -1,5 +1,5 @@ use super::helpers::scrape_packet_stats; -use sqlx::SqlitePool; +use crate::db::DbPool; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; @@ -17,12 +17,12 @@ static TASK_COUNTER: AtomicUsize = AtomicUsize::new(0); static TASK_ID_COUNTER: AtomicUsize = AtomicUsize::new(0); pub struct PacketScraper { - pool: SqlitePool, + pool: DbPool, max_concurrent_tasks: usize, } impl PacketScraper { - pub fn new(pool: SqlitePool, max_concurrent_tasks: usize) -> Self { + pub fn new(pool: DbPool, max_concurrent_tasks: usize) -> Self { Self { pool, max_concurrent_tasks, @@ -50,10 +50,7 @@ impl PacketScraper { } #[instrument(level = "info", name = "packet_scraper", skip_all)] - async fn run_packet_scraper( - pool: &SqlitePool, - max_concurrent_tasks: usize, - ) -> anyhow::Result<()> { + async fn run_packet_scraper(pool: &DbPool, max_concurrent_tasks: usize) -> anyhow::Result<()> { let queue = queries::get_nodes_for_scraping(pool).await?; tracing::info!("Adding {} nodes to the queue", queue.len(),); diff --git a/nym-node-status-api/nym-node-status-api/src/test_helpers/mod.rs b/nym-node-status-api/nym-node-status-api/src/test_helpers/mod.rs new file mode 100644 index 0000000000..ee201d555d --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/test_helpers/mod.rs @@ -0,0 +1,176 @@ +#[cfg(test)] +pub mod builders { + use crate::http::models::*; + use nym_validator_client::nym_api::SkimmedNode; + use nym_validator_client::nym_nodes::NodeRole; + use nym_validator_client::models::DeclaredRoles; + use nym_contracts_common::Percent; + use nym_crypto::asymmetric::{ed25519, x25519}; + + /// Builder for creating test Gateway instances + pub struct GatewayBuilder { + gateway: Gateway, + } + + impl GatewayBuilder { + pub fn new() -> Self { + Self { + gateway: Gateway { + gateway_identity_key: "test_gateway".to_string(), + bonded: true, + performance: 95, + self_described: Some(serde_json::json!({})), + explorer_pretty_bond: Some(serde_json::json!({})), + description: nym_node_requests::api::v1::node::models::NodeDescription { + moniker: "Test Gateway".to_string(), + website: "https://example.com".to_string(), + security_contact: "admin@example.com".to_string(), + details: "Test gateway node".to_string(), + }, + last_probe_result: None, + last_probe_log: None, + last_testrun_utc: None, + last_updated_utc: "2024-01-01T00:00:00Z".to_string(), + routing_score: 0.95, + config_score: 100, + }, + } + } + + pub fn with_identity(mut self, key: &str) -> Self { + self.gateway.gateway_identity_key = key.to_string(); + self + } + + pub fn with_performance(mut self, performance: u8) -> Self { + self.gateway.performance = performance; + self + } + + pub fn unbonded(mut self) -> Self { + self.gateway.bonded = false; + self + } + + pub fn with_last_probe_result(mut self, result: serde_json::Value) -> Self { + self.gateway.last_probe_result = Some(result); + self + } + + pub fn build(self) -> Gateway { + self.gateway + } + } + + /// Builder for creating test SkimmedNode instances + pub struct SkimmedNodeBuilder { + node: SkimmedNode, + } + + impl SkimmedNodeBuilder { + pub fn new() -> Self { + let ed25519_pk = ed25519::PublicKey::from_bytes(&[1; 32]).unwrap(); + let x25519_pk = x25519::PublicKey::from_bytes(&[2; 32]).unwrap(); + + Self { + node: SkimmedNode { + node_id: 1, + ed25519_identity_pubkey: ed25519_pk, + ip_addresses: vec!["127.0.0.1".parse().unwrap()], + mix_port: 1789, + x25519_sphinx_pubkey: x25519_pk, + role: NodeRole::Mixnode { layer: 1 }, + supported_roles: DeclaredRoles { + entry: false, + mixnode: true, + exit_nr: false, + exit_ipr: false, + }, + entry: None, + performance: Percent::from_percentage_value(95).unwrap(), + }, + } + } + + pub fn with_node_id(mut self, id: u32) -> Self { + self.node.node_id = id; + self + } + + pub fn with_role(mut self, role: NodeRole) -> Self { + self.node.role = role; + self + } + + pub fn as_gateway(mut self) -> Self { + self.node.role = NodeRole::EntryGateway; + self.node.supported_roles = DeclaredRoles { + entry: true, + mixnode: false, + exit_nr: true, + exit_ipr: false, + }; + self + } + + pub fn with_performance(mut self, perf: u8) -> Self { + self.node.performance = Percent::from_percentage_value(perf as u64).unwrap(); + self + } + + pub fn build(self) -> SkimmedNode { + self.node + } + } + + /// Builder for creating test Mixnode instances + pub struct MixnodeBuilder { + mixnode: Mixnode, + } + + impl MixnodeBuilder { + pub fn new() -> Self { + Self { + mixnode: Mixnode { + mix_id: 1, + bonded: true, + is_dp_delegatee: false, + total_stake: 1_000_000, + full_details: Some(serde_json::json!({})), + self_described: Some(serde_json::json!({})), + description: nym_node_requests::api::v1::node::models::NodeDescription { + moniker: "Test Mixnode".to_string(), + website: "https://example.com".to_string(), + security_contact: "admin@example.com".to_string(), + details: "Test mixnode".to_string(), + }, + last_updated_utc: "2024-01-01T00:00:00Z".to_string(), + }, + } + } + + pub fn with_mix_id(mut self, id: u32) -> Self { + self.mixnode.mix_id = id; + self + } + + pub fn with_stake(mut self, stake: i64) -> Self { + self.mixnode.total_stake = stake; + self + } + + pub fn as_dp_delegatee(mut self) -> Self { + self.mixnode.is_dp_delegatee = true; + self + } + + pub fn unbonded(mut self) -> Self { + self.mixnode.bonded = false; + self + } + + pub fn build(self) -> Mixnode { + self.mixnode + } + } +} \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/src/testruns/models.rs b/nym-node-status-api/nym-node-status-api/src/testruns/models.rs index fe4b33384c..309cb8aea8 100644 --- a/nym-node-status-api/nym-node-status-api/src/testruns/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/testruns/models.rs @@ -9,8 +9,165 @@ pub struct GatewayIdentityDto { #[derive(Debug, Clone, Deserialize, Serialize, utoipa::ToSchema)] pub struct TestRun { - pub id: u32, + pub id: i32, pub identity_key: String, pub status: String, pub log: String, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn gateway_identity_dto_creation() { + let dto = GatewayIdentityDto { + gateway_identity_key: "gateway123".to_string(), + bonded: true, + }; + + assert_eq!(dto.gateway_identity_key, "gateway123"); + assert!(dto.bonded); + } + + #[test] + fn gateway_identity_dto_unbonded() { + let dto = GatewayIdentityDto { + gateway_identity_key: "gateway456".to_string(), + bonded: false, + }; + + assert_eq!(dto.gateway_identity_key, "gateway456"); + assert!(!dto.bonded); + } + + #[test] + fn gateway_identity_dto_clone() { + let original = GatewayIdentityDto { + gateway_identity_key: "gateway789".to_string(), + bonded: true, + }; + + let cloned = original.clone(); + + assert_eq!(cloned.gateway_identity_key, original.gateway_identity_key); + assert_eq!(cloned.bonded, original.bonded); + } + + #[test] + fn test_run_creation() { + let test_run = TestRun { + id: 1, + identity_key: "test_gateway_123".to_string(), + status: "success".to_string(), + log: "Test completed successfully".to_string(), + }; + + assert_eq!(test_run.id, 1); + assert_eq!(test_run.identity_key, "test_gateway_123"); + assert_eq!(test_run.status, "success"); + assert_eq!(test_run.log, "Test completed successfully"); + } + + #[test] + fn test_run_with_error_status() { + let test_run = TestRun { + id: 42, + identity_key: "error_gateway".to_string(), + status: "error".to_string(), + log: "Connection timeout: failed to reach gateway".to_string(), + }; + + assert_eq!(test_run.id, 42); + assert_eq!(test_run.status, "error"); + assert!(test_run.log.contains("Connection timeout")); + } + + #[test] + fn test_run_serialization() { + let test_run = TestRun { + id: 123, + identity_key: "serialization_test".to_string(), + status: "pending".to_string(), + log: "".to_string(), + }; + + // Test that it can be serialized + let serialized = serde_json::to_string(&test_run).unwrap(); + assert!(serialized.contains("\"id\":123")); + assert!(serialized.contains("\"identity_key\":\"serialization_test\"")); + assert!(serialized.contains("\"status\":\"pending\"")); + assert!(serialized.contains("\"log\":\"\"")); + + // Test deserialization + let deserialized: TestRun = serde_json::from_str(&serialized).unwrap(); + assert_eq!(deserialized.id, test_run.id); + assert_eq!(deserialized.identity_key, test_run.identity_key); + assert_eq!(deserialized.status, test_run.status); + assert_eq!(deserialized.log, test_run.log); + } + + #[test] + fn test_run_with_long_log() { + let long_log = "Error: ".to_string() + &"x".repeat(1000); + let test_run = TestRun { + id: i32::MAX, + identity_key: "long_log_test".to_string(), + status: "failed".to_string(), + log: long_log.clone(), + }; + + assert_eq!(test_run.id, i32::MAX); + assert_eq!(test_run.log.len(), 1007); // "Error: " + 1000 x's + assert_eq!(test_run.log, long_log); + } + + #[test] + fn test_run_with_special_characters() { + let test_run = TestRun { + id: 0, + identity_key: "special_chars_∞_√_π".to_string(), + status: "unknown".to_string(), + log: "Test with\nnewlines\ttabs\rand \"quotes\"".to_string(), + }; + + assert_eq!(test_run.id, 0); + assert!(test_run.identity_key.contains('∞')); + assert!(test_run.log.contains('\n')); + assert!(test_run.log.contains('\t')); + assert!(test_run.log.contains('"')); + } + + #[test] + fn test_run_clone() { + let original = TestRun { + id: 999, + identity_key: "clone_test".to_string(), + status: "running".to_string(), + log: "In progress...".to_string(), + }; + + let cloned = original.clone(); + + assert_eq!(cloned.id, original.id); + assert_eq!(cloned.identity_key, original.identity_key); + assert_eq!(cloned.status, original.status); + assert_eq!(cloned.log, original.log); + } + + #[test] + fn test_run_edge_cases() { + // Test with negative ID (edge case) + let test_run = TestRun { + id: -1, + identity_key: "".to_string(), + status: "".to_string(), + log: "".to_string(), + }; + + assert_eq!(test_run.id, -1); + assert!(test_run.identity_key.is_empty()); + assert!(test_run.status.is_empty()); + assert!(test_run.log.is_empty()); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/testruns/queue.rs b/nym-node-status-api/nym-node-status-api/src/testruns/queue.rs index e36dc548f8..8ed220c6a3 100644 --- a/nym-node-status-api/nym-node-status-api/src/testruns/queue.rs +++ b/nym-node-status-api/nym-node-status-api/src/testruns/queue.rs @@ -1,13 +1,12 @@ use crate::db::models::{GatewayInfoDto, TestRunDto, TestRunStatus}; +use crate::db::DbConnection; use crate::testruns::models::TestRun; use crate::utils::now_utc; use anyhow::anyhow; use futures_util::TryStreamExt; -use sqlx::pool::PoolConnection; -use sqlx::Sqlite; pub(crate) async fn try_queue_testrun( - conn: &mut PoolConnection, + conn: &mut DbConnection, identity_key: String, ip_address: String, ) -> anyhow::Result { @@ -15,20 +14,19 @@ pub(crate) async fn try_queue_testrun( let timestamp = now.unix_timestamp(); let timestamp_pretty = now.to_string(); - let items = sqlx::query_as!( - GatewayInfoDto, + let items = crate::db::query_as::( r#"SELECT - id as "id!", - gateway_identity_key as "gateway_identity_key!", - self_described as "self_described?", - explorer_pretty_bond as "explorer_pretty_bond?" + id, + gateway_identity_key, + self_described, + explorer_pretty_bond FROM gateways WHERE gateway_identity_key = ? AND bonded = true ORDER BY gateway_identity_key LIMIT 1"#, - identity_key, ) + .bind(identity_key.clone()) // TODO dz should call .fetch_one // TODO dz replace this in other queries as well .fetch(conn.as_mut()) @@ -48,22 +46,21 @@ pub(crate) async fn try_queue_testrun( // // check if there is already a test run for this gateway // - let items = sqlx::query_as!( - TestRunDto, + let items = crate::db::query_as::( r#"SELECT - id as "id!", - gateway_id as "gateway_id!", - status as "status!", - created_utc as "created_utc!", - ip_address as "ip_address!", - log as "log!", + id, + gateway_id, + status, + created_utc, + ip_address, + log, last_assigned_utc FROM testruns WHERE gateway_id = ? AND status != 2 ORDER BY id DESC LIMIT 1"#, - gateway_id, ) + .bind(gateway_id) .fetch(conn.as_mut()) .try_collect::>() .await?; @@ -71,8 +68,8 @@ pub(crate) async fn try_queue_testrun( if !items.is_empty() { let testrun = items.first().unwrap(); return Ok(TestRun { - id: testrun.id as u32, - identity_key, + id: testrun.id as i32, + identity_key: identity_key.clone(), status: format!( "{}", TestRunStatus::from_repr(testrun.status as u8).unwrap() @@ -84,9 +81,10 @@ pub(crate) async fn try_queue_testrun( // // save test run // - let status = TestRunStatus::Queued as u32; + let status = TestRunStatus::Queued as i32; let log = format!("Test for {identity_key} requested at {timestamp_pretty} UTC\n\n"); + #[cfg(feature = "sqlite")] let id = sqlx::query!( "INSERT INTO testruns (gateway_id, status, ip_address, created_utc, log) VALUES (?, ?, ?, ?, ?)", gateway_id, @@ -95,13 +93,29 @@ pub(crate) async fn try_queue_testrun( timestamp, log, ) - .execute(conn.as_mut()) - .await? - .last_insert_rowid(); + .execute(conn.as_mut()) + .await? + .last_insert_rowid(); + + #[cfg(feature = "pg")] + let id = { + let record = sqlx::query!( + "INSERT INTO testruns (gateway_id, status, ip_address, created_utc, log) VALUES ($1, $2, $3, $4, $5) RETURNING id", + gateway_id as i32, + status, + ip_address, + timestamp, + log, + ) + .fetch_one(conn.as_mut()) + .await?; + record.id as i64 + }; Ok(TestRun { - id: id as u32, - identity_key, + #[allow(clippy::useless_conversion)] + id: id.try_into().unwrap(), + identity_key: identity_key.clone(), status: format!("{}", TestRunStatus::Queued), log, }) diff --git a/nym-node-status-api/nym-node-status-api/src/utils.rs b/nym-node-status-api/nym-node-status-api/src/utils.rs index 9a91d0ce4b..044aee6c9d 100644 --- a/nym-node-status-api/nym-node-status-api/src/utils.rs +++ b/nym-node-status-api/nym-node-status-api/src/utils.rs @@ -22,9 +22,9 @@ pub(crate) fn generate_node_name(node_id: i64) -> String { #[allow(clippy::items_after_test_module)] #[cfg(test)] mod test { - use rand::Rng; - use super::*; + use rand::Rng; + use std::str::FromStr; #[test] fn generate_node_name_should_be_deterministic() { @@ -40,6 +40,53 @@ mod test { let node_name_same = generate_node_name(node_id); assert_eq!(node_name, node_name_same); } + + #[test] + fn test_decimal_to_i64() { + // Test with a simple decimal + let dec1 = Decimal::from_str("123.456").unwrap(); + assert_eq!(decimal_to_i64(dec1), 123); + + // Test with a decimal that has more than 6 decimal places + let dec2 = Decimal::from_str("123.456789").unwrap(); + assert_eq!(decimal_to_i64(dec2), 123); + + // Test with a decimal that rounds up + let dec3 = Decimal::from_str("123.9999999").unwrap(); + assert_eq!(decimal_to_i64(dec3), 124); + + // Test with a zero decimal + let dec4 = Decimal::zero(); + assert_eq!(decimal_to_i64(dec4), 0); + + // Test with a large decimal + let dec5 = Decimal::from_str("1234567890.123456").unwrap(); + assert_eq!(decimal_to_i64(dec5), 1234567890); + } + + #[test] + fn test_unix_timestamp_to_utc_rfc3339() { + // Test with a known timestamp + let ts1 = 1672531199; // 2022-12-31 23:59:59 UTC + assert_eq!(unix_timestamp_to_utc_rfc3339(ts1), "2022-12-31T23:59:59Z"); + + // Test with the Unix epoch + let ts2 = 0; + assert_eq!(unix_timestamp_to_utc_rfc3339(ts2), "1970-01-01T00:00:00Z"); + } + + #[test] + fn test_numerical_checked_cast() { + // Test successful cast + let val1: u32 = 123; + let res1: anyhow::Result = val1.cast_checked(); + assert_eq!(res1.unwrap(), 123u64); + + // Test failing cast + let val2: i64 = -1; + let res2: anyhow::Result = val2.cast_checked(); + assert!(res2.is_err()); + } } pub(crate) fn now_utc() -> time::UtcDateTime { diff --git a/nym-node-status-api/nym-node-status-client/Cargo.toml b/nym-node-status-api/nym-node-status-client/Cargo.toml index 2bda647607..ef56a68c8b 100644 --- a/nym-node-status-api/nym-node-status-client/Cargo.toml +++ b/nym-node-status-api/nym-node-status-client/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-client" -version = "0.1.1" +version = "0.1.2" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -16,8 +16,11 @@ readme.workspace = true [dependencies] anyhow = { workspace = true } bincode = { workspace = true } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] } -nym-http-api-client = { path = "../../common/http-api-client" } +nym-crypto = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ + "asymmetric", + "serde", +] } +nym-http-api-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } reqwest = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/nym-node-status-api/nym-node-status-client/src/api.rs b/nym-node-status-api/nym-node-status-client/src/api.rs index 46d2fb7cd4..c41b25bf89 100644 --- a/nym-node-status-api/nym-node-status-client/src/api.rs +++ b/nym-node-status-api/nym-node-status-client/src/api.rs @@ -15,4 +15,11 @@ impl ApiPaths { pub(super) fn submit_results(&self, testrun_id: impl Display) -> String { format!("{}/internal/testruns/{}", self.server_address, testrun_id) } + + pub(super) fn submit_results_v2(&self, testrun_id: impl Display) -> String { + format!( + "{}/internal/testruns/{}/v2", + self.server_address, testrun_id + ) + } } diff --git a/nym-node-status-api/nym-node-status-client/src/lib.rs b/nym-node-status-api/nym-node-status-client/src/lib.rs index 00e260e6b5..6451b88c4a 100644 --- a/nym-node-status-api/nym-node-status-client/src/lib.rs +++ b/nym-node-status-api/nym-node-status-client/src/lib.rs @@ -1,4 +1,4 @@ -use crate::models::{get_testrun, submit_results, TestrunAssignment}; +use crate::models::{get_testrun, submit_results, submit_results_v2, TestrunAssignment}; use anyhow::bail; use api::ApiPaths; use nym_crypto::asymmetric::ed25519::{PrivateKey, Signature}; @@ -94,6 +94,37 @@ impl NsApiClient { Ok(()) } + #[instrument(level = "debug", skip(self, probe_result))] + pub async fn submit_results_with_context( + &self, + testrun_id: i32, + probe_result: String, + assigned_at_utc: i64, + gateway_identity_key: String, + ) -> anyhow::Result<()> { + let target_url = self.api.submit_results_v2(testrun_id); + + let payload = submit_results_v2::Payload { + probe_result, + agent_public_key: self.auth_key.public_key(), + assigned_at_utc, + gateway_identity_key, + }; + let signature = self.sign_message(&payload)?; + let submit_results = submit_results_v2::SubmitResultsV2 { payload, signature }; + + let res = self + .client + .post(target_url) + .json(&submit_results) + .send() + .await + .and_then(|response| response.error_for_status())?; + + tracing::debug!("Submitted results with context: {})", res.status()); + Ok(()) + } + fn sign_message(&self, message: &T) -> anyhow::Result where T: serde::Serialize, diff --git a/nym-node-status-api/nym-node-status-client/src/models.rs b/nym-node-status-api/nym-node-status-client/src/models.rs index 05860259ba..c828c5371c 100644 --- a/nym-node-status-api/nym-node-status-client/src/models.rs +++ b/nym-node-status-api/nym-node-status-client/src/models.rs @@ -36,7 +36,7 @@ pub mod get_testrun { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct TestrunAssignment { - pub testrun_id: i64, + pub testrun_id: i32, pub assigned_at_utc: i64, pub gateway_identity_key: String, } @@ -74,3 +74,38 @@ pub mod submit_results { } } } + +pub mod submit_results_v2 { + use crate::auth::SignedRequest; + + use super::*; + #[derive(Debug, Clone, Deserialize, Serialize)] + pub struct Payload { + pub probe_result: String, + pub agent_public_key: PublicKey, + pub assigned_at_utc: i64, + pub gateway_identity_key: String, + } + + #[derive(Debug, Clone, Deserialize, Serialize)] + pub struct SubmitResultsV2 { + pub payload: Payload, + pub signature: Signature, + } + + impl SignedRequest for SubmitResultsV2 { + type Payload = Payload; + + fn public_key(&self) -> &PublicKey { + &self.payload.agent_public_key + } + + fn signature(&self) -> &Signature { + &self.signature + } + + fn payload(&self) -> &Self::Payload { + &self.payload + } + } +} diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 0137af06f1..690a31a04a 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -20,7 +20,7 @@ arc-swap = { workspace = true } bip39 = { workspace = true, features = ["zeroize"] } bs58.workspace = true bloomfilter = { workspace = true } -celes = { workspace = true } # country codes +celes = { workspace = true } # country codes colored = { workspace = true } csv = { workspace = true } clap = { workspace = true, features = ["cargo", "env"] } @@ -50,7 +50,9 @@ nym-bin-common = { path = "../common/bin-common", features = [ "basic_tracing", "output_format", ] } -nym-client-core-config-types = { path = "../common/client-core/config-types", features = ["disk-persistence"] } +nym-client-core-config-types = { path = "../common/client-core/config-types", features = [ + "disk-persistence", +] } nym-config = { path = "../common/config" } nym-crypto = { path = "../common/crypto", features = ["asymmetric", "rand"] } nym-nonexhaustive-delayqueue = { path = "../common/nonexhaustive-delayqueue" } @@ -86,8 +88,14 @@ tower-http = { workspace = true, features = ["fs"] } utoipa = { workspace = true, features = ["axum_extras", "time"] } utoipa-swagger-ui = { workspace = true, features = ["axum"] } -nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa", "output", "middleware"] } -nym-node-requests = { path = "nym-node-requests", default-features = false, features = ["openapi"] } +nym-http-api-common = { path = "../common/http-api-common", features = [ + "utoipa", + "output", + "middleware", +] } +nym-node-requests = { path = "nym-node-requests", default-features = false, features = [ + "openapi", +] } nym-node-metrics = { path = "nym-node-metrics" } # nodes: @@ -100,7 +108,7 @@ nym-ip-packet-router = { path = "../service-providers/ip-packet-router" } # throughput tester to recreate lioness # we don't care about particular versions - just pull whatever is used by sphinx lioness = "*" -chacha = "*" +chacha = "0.3.0" arrayref = "*" blake2 = "=0.8.1" sha2 = { workspace = true } diff --git a/nym-node/src/cli/commands/test_throughput.rs b/nym-node/src/cli/commands/test_throughput.rs index af7cc8ad42..57b6bb9d03 100644 --- a/nym-node/src/cli/commands/test_throughput.rs +++ b/nym-node/src/cli/commands/test_throughput.rs @@ -6,7 +6,6 @@ use crate::logging::granual_filtered_env; use crate::throughput_tester::test_mixing_throughput; use anyhow::bail; use humantime_serde::re::humantime; -use indicatif::ProgressStyle; use nym_bin_common::logging::default_tracing_fmt_layer; use std::env::temp_dir; use std::path::PathBuf; @@ -41,9 +40,6 @@ pub struct Args { fn init_test_logger() -> anyhow::Result<()> { let indicatif_layer = IndicatifLayer::new() - .with_progress_style(ProgressStyle::with_template( - "{span_child_prefix}{spinner} {span_fields} -- {span_name} {wide_msg}", - )?) .with_span_child_prefix_symbol("↳ ") .with_span_child_prefix_indent(" "); diff --git a/nym-node/src/throughput_tester/mod.rs b/nym-node/src/throughput_tester/mod.rs index 3371fb759c..aaa2360726 100644 --- a/nym-node/src/throughput_tester/mod.rs +++ b/nym-node/src/throughput_tester/mod.rs @@ -9,7 +9,6 @@ use crate::throughput_tester::global_stats::GlobalStatsUpdater; use crate::throughput_tester::stats::ClientStats; use futures::future::join_all; use human_repr::HumanDuration; -use indicatif::{ProgressState, ProgressStyle}; use nym_task::ShutdownToken; use rand::{thread_rng, Rng}; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; @@ -124,18 +123,6 @@ pub(crate) fn test_mixing_throughput( } let header_span = info_span!("header"); - header_span.pb_set_style( - &ProgressStyle::with_template( - "testing mixing throughput of this machine... {wide_msg} {elapsed}\n{wide_bar}", - )? - .with_key( - "elapsed", - |state: &ProgressState, writer: &mut dyn std::fmt::Write| { - let _ = writer.write_str(&format!("{}", state.elapsed().human_duration())); - }, - ) - .progress_chars("---"), - ); header_span.pb_start(); // Bit of a hack to show a full "-----" line underneath the header.