From 2004719c38c36e697d91cdf4eb31200607aebc14 Mon Sep 17 00:00:00 2001 From: Filippo Costa Date: Tue, 4 Jun 2024 11:03:59 +0200 Subject: [PATCH] Simplify repository structure; update panels to new plugin Signed-off-by: Filippo Costa --- .dockerignore | 3 +- .gitignore | 7 +- .vscode/settings.default.json | 7 + Cargo.lock | 452 ++++++++++-- Cargo.toml | 4 +- Dockerfile | 29 +- Insomnia-collection-v4.json | 1 - ... a human-readable name to a deployment.bru | 28 + .../Delete a network and all related data.bru | 23 + ...etch a divergence investigation report.bru | 21 + bruno/Launch a divergence investigation.bru | 42 ++ ...larity -a-k-a- -PoI agreement ratios--.bru | 30 + bruno/Query PoIs.bru | 38 + bruno/Query deployments.bru | 33 + bruno/Query indexers.bru | 30 + bruno/bruno.json | 9 + compose/.gitignore | 4 + {ops/compose => compose}/dependencies.yml | 4 +- .../grafana/config/grafana.ini | 0 .../provisioning/dashboards/dashboards.yml | 0 .../grafana/data/grafana.db | Bin 1445888 -> 1576960 bytes .../prometheus/prometheus.yml | 0 {ops/compose => compose}/pull_dashboards.sh | 0 .../graphix => configs}/network.graphix.yml | 4 - .../graphix => configs}/readonly.graphix.yml | 4 - {examples => configs}/testnet.graphix.yml | 14 +- crates/autogen_config_json_schema/schema.json | 39 +- .../autogen_graphql_schema/api_schema.graphql | 32 +- crates/common_types/Cargo.toml | 1 + .../src/api_key_permission_level.rs | 42 ++ crates/common_types/src/lib.rs | 2 + crates/graphix/Cargo.toml | 2 +- crates/graphix/src/main.rs | 73 +- crates/graphix/src/utils.rs | 39 - crates/graphix_lib/Cargo.toml | 2 + crates/{graphix => graphix_lib}/src/bisect.rs | 42 +- crates/graphix_lib/src/block_choice.rs | 2 +- crates/graphix_lib/src/cli.rs | 18 + crates/graphix_lib/src/config.rs | 57 +- .../graphix_lib/src/graphql_api/api_types.rs | 4 +- crates/graphix_lib/src/graphql_api/mod.rs | 31 +- .../src/graphql_api/mutation_root.rs | 35 +- .../graphix_lib/src/graphql_api/query_root.rs | 20 +- crates/graphix_lib/src/lib.rs | 3 + crates/network_sg_client/src/lib.rs | 148 ++-- crates/store/Cargo.toml | 1 + .../2023-03-30-000000_initial_schema/up.sql | 8 +- crates/store/src/lib.rs | 597 +--------------- crates/store/src/models.rs | 41 +- crates/store/src/schema.rs | 10 +- .../store/src/{ => store}/diesel_queries.rs | 0 crates/store/src/store/mod.rs | 675 ++++++++++++++++++ crates/store/tests/common.rs | 31 + crates/store/tests/common/mod.rs | 37 - crates/store/tests/tests.rs | 13 +- ops/compose/.gitignore | 5 - .../grafana/data/alerting/1/__default__.tmpl | 53 -- ops/compose/graphix.yml | 24 - 58 files changed, 1780 insertions(+), 1094 deletions(-) create mode 100644 .vscode/settings.default.json delete mode 100644 Insomnia-collection-v4.json create mode 100644 bruno/Assign a human-readable name to a deployment.bru create mode 100644 bruno/Delete a network and all related data.bru create mode 100644 bruno/Fetch a divergence investigation report.bru create mode 100644 bruno/Launch a divergence investigation.bru create mode 100644 bruno/Poll indexer-s PoIs popularity -a-k-a- -PoI agreement ratios--.bru create mode 100644 bruno/Query PoIs.bru create mode 100644 bruno/Query deployments.bru create mode 100644 bruno/Query indexers.bru create mode 100644 bruno/bruno.json create mode 100644 compose/.gitignore rename {ops/compose => compose}/dependencies.yml (93%) rename {ops/compose => compose}/grafana/config/grafana.ini (100%) rename {ops/compose => compose}/grafana/config/provisioning/dashboards/dashboards.yml (100%) rename {ops/compose => compose}/grafana/data/grafana.db (81%) rename {ops/compose => compose}/prometheus/prometheus.yml (100%) rename {ops/compose => compose}/pull_dashboards.sh (100%) rename {ops/compose/graphix => configs}/network.graphix.yml (91%) rename {ops/compose/graphix => configs}/readonly.graphix.yml (90%) rename {examples => configs}/testnet.graphix.yml (67%) create mode 100644 crates/common_types/src/api_key_permission_level.rs delete mode 100644 crates/graphix/src/utils.rs rename crates/{graphix => graphix_lib}/src/bisect.rs (91%) create mode 100644 crates/graphix_lib/src/cli.rs rename crates/store/src/{ => store}/diesel_queries.rs (100%) create mode 100644 crates/store/src/store/mod.rs create mode 100644 crates/store/tests/common.rs delete mode 100644 crates/store/tests/common/mod.rs delete mode 100644 ops/compose/.gitignore delete mode 100644 ops/compose/grafana/data/alerting/1/__default__.tmpl delete mode 100644 ops/compose/graphix.yml diff --git a/.dockerignore b/.dockerignore index 9128afa..360db2b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ -ops/ +compose/ +bruno/ target/ .git diff --git a/.gitignore b/.gitignore index c6d9f6b..f9ffaa5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ rustc-ice* ### IDEs -.vscode/ +.vscode/settings.json .idea/ ### OS @@ -9,8 +9,3 @@ rustc-ice* ### Rust /target - -### Project specific -ops/compose/data/* -/frontend/dist -frontend/graphql/api_schema.graphql diff --git a/.vscode/settings.default.json b/.vscode/settings.default.json new file mode 100644 index 0000000..20c3d87 --- /dev/null +++ b/.vscode/settings.default.json @@ -0,0 +1,7 @@ +// In case you're wondering, yes, VS Code explicity allows comments in JSON +// configuration files. See . +{ + "yaml.schemas": { + "./crates/autogen_config_json_schema/schema.json": "*.graphix.yml" + } +} diff --git a/Cargo.lock b/Cargo.lock index bda64c8..1db0a41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,7 +169,7 @@ dependencies = [ "futures-util", "handlebars", "http 1.1.0", - "indexmap", + "indexmap 2.2.6", "lru", "mime", "multer", @@ -212,7 +212,7 @@ checksum = "ae80fb7b67deeae84441a9eb156359b99be7902d2d706f43836156eec69a8226" dependencies = [ "Inflector", "async-graphql-parser", - "darling 0.20.9", + "darling", "proc-macro-crate", "proc-macro2", "quote", @@ -240,7 +240,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c224c93047a7197fe0f1d6eee98245ba6049706c6c04a372864557fb61495e94" dependencies = [ "bytes", - "indexmap", + "indexmap 2.2.6", "serde", "serde_json", ] @@ -410,13 +410,53 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bollard" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aed08d3adb6ebe0eff737115056652670ae290f177759aac19c30456135f94c" +dependencies = [ + "base64 0.22.1", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "home", + "http 1.1.0", + "http-body-util", + "hyper 1.3.1", + "hyper-named-pipe", + "hyper-rustls", + "hyper-util", + "hyperlocal-next", + "log", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror", + "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", +] + [[package]] name = "bollard-stubs" -version = "1.42.0-rc.3" +version = "1.44.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed59b5c00048f48d7af971b71f800fdf23e858844a6f9e4d32ca72e9399e7864" +checksum = "709d9aa1c37abb89d40f19f5d0ad6f0d88cb1581264e571c9350fc5bb89cf1c5" dependencies = [ "serde", + "serde_repr", "serde_with", ] @@ -510,7 +550,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", ] [[package]] @@ -600,38 +640,14 @@ dependencies = [ "typenum", ] -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core 0.13.4", - "darling_macro 0.13.4", -] - [[package]] name = "darling" version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" dependencies = [ - "darling_core 0.20.9", - "darling_macro 0.20.9", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 1.0.109", + "darling_core", + "darling_macro", ] [[package]] @@ -644,28 +660,17 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", + "strsim", "syn 2.0.66", ] -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core 0.13.4", - "quote", - "syn 1.0.109", -] - [[package]] name = "darling_macro" version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ - "darling_core 0.20.9", + "darling_core", "quote", "syn 2.0.66", ] @@ -722,6 +727,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -831,6 +837,38 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "docker_credential" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31951f49556e34d90ed28342e1df7e1cb7a229c4cab0aecc627b5d91edd41d07" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + [[package]] name = "dyn-clone" version = "1.0.17" @@ -1113,6 +1151,7 @@ dependencies = [ "schemars", "serde", "serde_json", + "strum", "uuid", ] @@ -1145,6 +1184,7 @@ dependencies = [ "async-trait", "axum", "chrono", + "clap", "derive_more", "diesel", "futures", @@ -1168,6 +1208,7 @@ dependencies = [ "serde_json", "serde_yaml", "sha2", + "thiserror", "tokio", "tower-service", "tracing", @@ -1200,6 +1241,7 @@ dependencies = [ "async-graphql", "bigdecimal", "chrono", + "derive_more", "diesel", "diesel-async", "diesel_async_migrations", @@ -1288,7 +1330,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -1309,6 +1351,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1352,6 +1400,15 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" version = "0.2.12" @@ -1461,6 +1518,41 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper 1.3.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.3.1", + "hyper-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", ] [[package]] @@ -1483,12 +1575,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d8d52be92d09acc2e01dddb7fde3ad983fc6489c7db4837e605bc3fca4cb63e" dependencies = [ "bytes", + "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.0", "hyper 1.3.1", "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "hyperlocal-next" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf569d43fa9848e510358c07b80f4adf34084ddc28c6a4a651ee8474c070dcc" +dependencies = [ + "hex", + "http-body-util", + "hyper 1.3.1", + "hyper-util", + "pin-project-lite", "tokio", + "tower-service", ] [[package]] @@ -1530,6 +1642,17 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.2.6" @@ -1537,7 +1660,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", "serde", ] @@ -1604,6 +1727,16 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1632,7 +1765,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -1865,6 +1998,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "overload" version = "0.1.1" @@ -1894,6 +2033,31 @@ dependencies = [ "windows-targets 0.52.5", ] +[[package]] +name = "parse-display" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax 0.8.3", +] + +[[package]] +name = "parse-display-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax 0.8.3", + "structmeta", + "syn 2.0.66", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2187,6 +2351,17 @@ dependencies = [ "bitflags 2.5.0", ] +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.10.4" @@ -2255,7 +2430,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", @@ -2277,6 +2452,21 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2305,6 +2495,33 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -2314,6 +2531,33 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.102.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -2476,6 +2720,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2490,24 +2745,32 @@ dependencies = [ [[package]] name = "serde_with" -version = "1.14.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.2.6", "serde", + "serde_derive", + "serde_json", "serde_with_macros", + "time", ] [[package]] name = "serde_with_macros" -version = "1.5.2" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" dependencies = [ - "darling 0.13.4", + "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.66", ] [[package]] @@ -2516,7 +2779,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -2619,15 +2882,32 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "strsim" -version = "0.11.1" +name = "structmeta" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn 2.0.66", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] [[package]] name = "strum" @@ -2726,26 +3006,35 @@ dependencies = [ [[package]] name = "testcontainers" -version = "0.15.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d2931d7f521af5bae989f716c3fa43a6af9af7ec7a5e21b59ae40878cec00" +checksum = "025e0ac563d543e0354d984540e749859a83dbe5c0afb8d458dc48d91cef2d6a" dependencies = [ + "async-trait", + "bollard", "bollard-stubs", + "bytes", + "dirs", + "docker_credential", "futures", - "hex", - "hmac", "log", - "rand", + "memchr", + "parse-display", "serde", "serde_json", - "sha2", + "serde_with", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "url", ] [[package]] name = "testcontainers-modules" -version = "0.3.7" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8debb5e215d9e89ea93255fffff00bf037ea44075d7a2669a21a8a988d6b52fd" +checksum = "87dc086b8c791e5696de6de4e0d79de2cb9f8a1ac4552d46d6211a42d81d6d0f" dependencies = [ "testcontainers", ] @@ -2905,6 +3194,17 @@ dependencies = [ "whoami", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.15" @@ -2954,7 +3254,7 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -3163,6 +3463,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.0" @@ -3537,3 +3843,9 @@ dependencies = [ "quote", "syn 2.0.66", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index f485e6c..9948c66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,8 +34,8 @@ serde_json = "1" serde_yaml = "0.9" sha2 = "0.10" strum = { version = "0.26", features = ["derive"] } -testcontainers = "0.15" -testcontainers-modules = "0.3" +testcontainers = "0.17" +testcontainers-modules = "0.5" thiserror = "1" tokio = "1.14.0" tracing = "0.1.29" diff --git a/Dockerfile b/Dockerfile index 43354e3..99d6c0b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,36 +1,13 @@ -FROM rust:slim-bullseye AS chef +FROM rust:slim-bullseye AS builder WORKDIR /app -COPY rust-toolchain.toml . -# Triggers the install of the predefined Rust toolchain. -# See . -RUN rustup show -RUN cargo install cargo-chef - -FROM chef AS planner - -COPY . . -RUN cargo chef prepare --recipe-path recipe.json - -FROM chef AS builder - -# We support both `release` and `dev`. -ARG CARGO_PROFILE=release - -COPY --from=planner /app/recipe.json recipe.json RUN apt-get update && apt-get install -y libpq-dev ca-certificates pkg-config libssl-dev -# Use cargo-chef to compile dependencies only - this will be cached by Docker. -RUN cargo chef cook --profile $CARGO_PROFILE --recipe-path recipe.json --bin graphix -# ... and then build the rest of the application. COPY . . -RUN cargo build --profile $CARGO_PROFILE --bin graphix -# Instead of calculating where the binary is located based on $CARGO_PROFILE, we -# simply try to copy both `debug` and `release` binaries. -RUN cp target/release/graphix /usr/local/bin | true && \ - cp target/debug/graphix /usr/local/bin | true +RUN cargo build --release --bin graphix +RUN cp target/release/graphix /usr/local/bin FROM debian:bullseye-slim diff --git a/Insomnia-collection-v4.json b/Insomnia-collection-v4.json deleted file mode 100644 index 5b8a44d..0000000 --- a/Insomnia-collection-v4.json +++ /dev/null @@ -1 +0,0 @@ -{"_type":"export","__export_format":4,"__export_date":"2023-09-18T03:40:47.954Z","__export_source":"insomnia.desktop.app:v2023.5.8","resources":[{"_id":"req_c0bd5013eddf42b48d9f9590a0f1ea9e","parentId":"wrk_637da6977d6446fb95dcbbf44d84f934","modified":1695008384079,"created":1693917354203,"url":"{{ _.url }}","name":"Query deployments","description":"","method":"POST","body":{"mimeType":"application/graphql","text":"{\"query\":\"{\\n\\tdeployments(\\n\\t\\tfilter: {\\n\\t\\t\\tnetwork: \\\"mainnet\\\"\\n\\t\\t\\tipfsCid: \\\"Qmadj8x9km1YEyKmRnJ6EkC2zpJZFCfTyTZpuqC3j6e1QH\\\"\\n\\t\\t\\t#name: \\\"premia.eth/premia-mainnet\\\"\\n\\t\\t\\tlimit: 100\\n\\t\\t}\\n\\t) {\\n\\t\\tid\\n\\t\\tname\\n\\t\\tnetworkName\\n\\t}\\n}\\n\"}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1693915662091,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"wrk_637da6977d6446fb95dcbbf44d84f934","parentId":null,"modified":1695006435721,"created":1695006435721,"name":"Graphix","description":"","scope":"collection","_type":"workspace"},{"_id":"req_2bc3c5517f8c4adba083e818c7d05b40","parentId":"wrk_637da6977d6446fb95dcbbf44d84f934","modified":1695008211704,"created":1693917384099,"url":"{{ _.url }}","name":"Query indexers","description":"","method":"POST","body":{"mimeType":"application/graphql","text":"{\"query\":\"{\\n\\tindexers(\\n\\t\\tfilter: {\\n\\t\\t\\taddress: \\\"0x87eba079059b75504c734820d6cf828476754b83\\\"\\n\\t\\t\\tlimit: 100\\n\\t\\t}\\n\\t) {\\n\\t\\tid\\n\\t\\tallocatedTokens\\n\\t}\\n}\\n\"}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1693915662041,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_437c542153b2433fbf9abbd34f587293","parentId":"wrk_637da6977d6446fb95dcbbf44d84f934","modified":1695008216344,"created":1693917428810,"url":"{{ _.url }}","name":"Query PoIs","description":"","method":"POST","body":{"mimeType":"application/graphql","text":"{\"query\":\"{\\n\\tproofsOfIndexing(request: {\\n\\t\\tdeployments: [\\\"Qmadj8x9km1YEyKmRnJ6EkC2zpJZFCfTyTZpuqC3j6e1QH\\\"],\\n\\t\\tblockRange: {start: 1000},\\n\\t\\tlimit: 100\\n\\t}) {\\n\\t\\thash\\n\\t\\tblock {\\n\\t\\t\\thash\\n\\t\\t\\tnumber\\n\\t\\t}\\n\\t\\tallocatedTokens\\n\\t\\tdeployment {\\n\\t\\t\\tid\\n\\t\\t}\\n\\t\\tindexer {\\n\\t\\t\\tid\\n\\t\\t}\\n\\t}\\n}\"}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1693915662028.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_47b02c4a0e5f4904ae9b4a8686198db7","parentId":"wrk_637da6977d6446fb95dcbbf44d84f934","modified":1695008220725,"created":1693917660985,"url":"{{ _.url }}","name":"Poll indexer's PoIs popularity (a.k.a. \"PoI agreement ratios\")","description":"","method":"POST","body":{"mimeType":"application/graphql","text":"{\"query\":\"{\\n\\tpoiAgreementRatios(indexerName: \\\"testnet-indexer-03\\\") {\\n\\t\\tpoi\\n\\t\\tdeployment {\\n\\t\\t\\tid\\n\\t\\t}\\n\\t\\tnAgreeingIndexers\\n\\t\\tnDisagreeingIndexers\\n\\t\\thasConsensus\\n\\t\\tinConsensus\\n\\t}\\n}\"}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1693915662022.25,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_6aa72251c43349398baa4254a96138b6","parentId":"wrk_637da6977d6446fb95dcbbf44d84f934","modified":1695008227441,"created":1693917209483,"url":"{{ _.url }}","name":"Assign a human-readable name to a deployment","description":"","method":"POST","body":{"mimeType":"application/graphql","text":"{\"query\":\"mutation {\\n\\tsetDeploymentName(\\n\\t\\tdeploymentIpfsCid: \\\"Qmadj8x9km1YEyKmRnJ6EkC2zpJZFCfTyTZpuqC3j6e1QH\\\"\\n\\t\\tname: \\\"premia.eth/premia-mainnet\\\"\\n\\t) {\\n\\t\\tid\\n\\t}\\n}\\n\"}"},"parameters":[],"headers":[{"name":"User-Agent","value":"Insomnia/2023.5.7"},{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1693915662016,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_b506f3c99d4942d38d0b8622e6ba16fc","parentId":"wrk_637da6977d6446fb95dcbbf44d84f934","modified":1695008232149,"created":1693915661991,"url":"{{ _.url }}","name":"Delete a network and all related data","description":"","method":"POST","body":{"mimeType":"application/graphql","text":"{\"query\":\"mutation {\\n\\tdeleteNetwork(network: \\\"testnet\\\")\\n}\\n\"}"},"parameters":[],"headers":[{"name":"User-Agent","value":"Insomnia/2023.5.7"},{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1693915661991,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_7802db26d1204ed697ce9b74760d02c7","parentId":"wrk_637da6977d6446fb95dcbbf44d84f934","modified":1695008249762,"created":1691402256515,"url":"{{ _.url }}","name":"Launch a divergence investigation","description":"","method":"POST","body":{"mimeType":"application/graphql","text":"{\"query\":\"mutation {\\n\\tlaunchDivergenceInvestigation(\\n\\t\\treq: {\\n\\t\\t\\tpois: [\\\"foo\\\", \\\"bar\\\"]\\n\\t\\t\\tqueryBlockCaches: true\\n\\t\\t\\tqueryEthCallCaches: false\\n\\t\\t\\tqueryEntityChanges: true\\n\\t\\t}\\n\\t) {\\n\\t\\tuuid\\n\\t\\tstatus\\n\\t\\tbisectionRuns {\\n\\t\\t\\tdivergenceBlockBounds {\\n\\t\\t\\t\\tlowerBound {\\n\\t\\t\\t\\t\\tnumber\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\tupperBound {\\n\\t\\t\\t\\t\\tnumber\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t}\\n\\t}\\n}\\n\",\"operationName\":\"Operations\"}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1691402256515,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_e8f7541acf1f49b791a451395c07e5ba","parentId":"wrk_637da6977d6446fb95dcbbf44d84f934","modified":1695008240368,"created":1691407145821,"url":"{{ _.url }}","name":"Fetch a divergence investigation report","description":"","method":"POST","body":{"mimeType":"application/graphql","text":"{\"query\":\"{\\n\\tpoiCrossCheckReport(requestId: \\\"9b587618-9f25-4f96-b435-a3b9f94dc9f2\\\")\\n}\"}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1691402256465,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_7da8f97950ed404eb19b9721036e773a","parentId":"wrk_637da6977d6446fb95dcbbf44d84f934","modified":1695008187868,"created":1691402042555,"name":"Base Environment","data":{"url":"http://127.0.0.1:3030/graphql"},"dataPropertyOrder":{"&":["url"]},"color":null,"isPrivate":false,"metaSortKey":1691402042555,"_type":"environment"},{"_id":"jar_0f179fb3e03547b9a08e195e519b926e","parentId":"wrk_637da6977d6446fb95dcbbf44d84f934","modified":1691402042557,"created":1691402042557,"name":"Default Jar","cookies":[],"_type":"cookie_jar"}]} \ No newline at end of file diff --git a/bruno/Assign a human-readable name to a deployment.bru b/bruno/Assign a human-readable name to a deployment.bru new file mode 100644 index 0000000..3c189c0 --- /dev/null +++ b/bruno/Assign a human-readable name to a deployment.bru @@ -0,0 +1,28 @@ +meta { + name: Assign a human-readable name to a deployment + type: graphql + seq: 5 +} + +post { + url: {{ _.url }} + body: graphql + auth: none +} + +headers { + User-Agent: Insomnia/2023.5.7 + Content-Type: application/json +} + +body:graphql { + mutation { + setDeploymentName( + deploymentIpfsCid: "Qmadj8x9km1YEyKmRnJ6EkC2zpJZFCfTyTZpuqC3j6e1QH" + name: "premia.eth/premia-mainnet" + ) { + id + } + } + +} diff --git a/bruno/Delete a network and all related data.bru b/bruno/Delete a network and all related data.bru new file mode 100644 index 0000000..e4b5c93 --- /dev/null +++ b/bruno/Delete a network and all related data.bru @@ -0,0 +1,23 @@ +meta { + name: Delete a network and all related data + type: graphql + seq: 6 +} + +post { + url: {{ _.url }} + body: graphql + auth: none +} + +headers { + User-Agent: Insomnia/2023.5.7 + Content-Type: application/json +} + +body:graphql { + mutation { + deleteNetwork(network: "testnet") + } + +} diff --git a/bruno/Fetch a divergence investigation report.bru b/bruno/Fetch a divergence investigation report.bru new file mode 100644 index 0000000..79d218b --- /dev/null +++ b/bruno/Fetch a divergence investigation report.bru @@ -0,0 +1,21 @@ +meta { + name: Fetch a divergence investigation report + type: graphql + seq: 8 +} + +post { + url: {{ _.url }} + body: graphql + auth: none +} + +headers { + Content-Type: application/json +} + +body:graphql { + { + poiCrossCheckReport(requestId: "9b587618-9f25-4f96-b435-a3b9f94dc9f2") + } +} diff --git a/bruno/Launch a divergence investigation.bru b/bruno/Launch a divergence investigation.bru new file mode 100644 index 0000000..16a688e --- /dev/null +++ b/bruno/Launch a divergence investigation.bru @@ -0,0 +1,42 @@ +meta { + name: Launch a divergence investigation + type: graphql + seq: 7 +} + +post { + url: {{ _.url }} + body: graphql + auth: none +} + +headers { + Content-Type: application/json +} + +body:graphql { + mutation { + launchDivergenceInvestigation( + req: { + pois: ["foo", "bar"] + queryBlockCaches: true + queryEthCallCaches: false + queryEntityChanges: true + } + ) { + uuid + status + bisectionRuns { + divergenceBlockBounds { + lowerBound { + number + } + upperBound { + number + } + } + } + } + } + +} diff --git a/bruno/Poll indexer-s PoIs popularity -a-k-a- -PoI agreement ratios--.bru b/bruno/Poll indexer-s PoIs popularity -a-k-a- -PoI agreement ratios--.bru new file mode 100644 index 0000000..e29bb5e --- /dev/null +++ b/bruno/Poll indexer-s PoIs popularity -a-k-a- -PoI agreement ratios--.bru @@ -0,0 +1,30 @@ +meta { + name: Poll indexer-s PoIs popularity -a-k-a- -PoI agreement ratios-- + type: graphql + seq: 4 +} + +post { + url: {{ _.url }} + body: graphql + auth: none +} + +headers { + Content-Type: application/json +} + +body:graphql { + { + poiAgreementRatios(indexerName: "testnet-indexer-03") { + poi + deployment { + id + } + nAgreeingIndexers + nDisagreeingIndexers + hasConsensus + inConsensus + } + } +} diff --git a/bruno/Query PoIs.bru b/bruno/Query PoIs.bru new file mode 100644 index 0000000..ae5dafd --- /dev/null +++ b/bruno/Query PoIs.bru @@ -0,0 +1,38 @@ +meta { + name: Query PoIs + type: graphql + seq: 3 +} + +post { + url: {{ _.url }} + body: graphql + auth: none +} + +headers { + Content-Type: application/json +} + +body:graphql { + { + proofsOfIndexing(request: { + deployments: ["Qmadj8x9km1YEyKmRnJ6EkC2zpJZFCfTyTZpuqC3j6e1QH"], + blockRange: {start: 1000}, + limit: 100 + }) { + hash + block { + hash + number + } + allocatedTokens + deployment { + id + } + indexer { + id + } + } + } +} diff --git a/bruno/Query deployments.bru b/bruno/Query deployments.bru new file mode 100644 index 0000000..9be4d93 --- /dev/null +++ b/bruno/Query deployments.bru @@ -0,0 +1,33 @@ +meta { + name: Query deployments + type: graphql + seq: 1 +} + +post { + url: {{ _.url }} + body: graphql + auth: none +} + +headers { + Content-Type: application/json +} + +body:graphql { + { + deployments( + filter: { + network: "mainnet" + ipfsCid: "Qmadj8x9km1YEyKmRnJ6EkC2zpJZFCfTyTZpuqC3j6e1QH" + #name: "premia.eth/premia-mainnet" + limit: 100 + } + ) { + id + name + networkName + } + } + +} diff --git a/bruno/Query indexers.bru b/bruno/Query indexers.bru new file mode 100644 index 0000000..a7f2223 --- /dev/null +++ b/bruno/Query indexers.bru @@ -0,0 +1,30 @@ +meta { + name: Query indexers + type: graphql + seq: 2 +} + +post { + url: {{ _.url }} + body: graphql + auth: none +} + +headers { + Content-Type: application/json +} + +body:graphql { + { + indexers( + filter: { + address: "0x87eba079059b75504c734820d6cf828476754b83" + limit: 100 + } + ) { + id + allocatedTokens + } + } + +} diff --git a/bruno/bruno.json b/bruno/bruno.json new file mode 100644 index 0000000..a1de707 --- /dev/null +++ b/bruno/bruno.json @@ -0,0 +1,9 @@ +{ + "version": "1", + "name": "Graphix", + "type": "collection", + "ignore": [ + "node_modules", + ".git" + ] +} \ No newline at end of file diff --git a/compose/.gitignore b/compose/.gitignore new file mode 100644 index 0000000..803665f --- /dev/null +++ b/compose/.gitignore @@ -0,0 +1,4 @@ +data/* + +grafana/data/* +!grafana/data/grafana.db diff --git a/ops/compose/dependencies.yml b/compose/dependencies.yml similarity index 93% rename from ops/compose/dependencies.yml rename to compose/dependencies.yml index 64d39fb..ef9a932 100644 --- a/ops/compose/dependencies.yml +++ b/compose/dependencies.yml @@ -2,7 +2,7 @@ version: "3" services: grafana: - image: grafana/grafana-oss + image: grafana/grafana-oss:9.3.16 restart: unless-stopped # https://community.grafana.com/t/new-docker-install-with-persistent-storage-permission-problem/10896/16 user: ":" @@ -12,8 +12,6 @@ services: ports: - "3000:3000" environment: - # Plugins: - # - https://github.com/fifemon/graphql-datasource for GraphQL data sources. - GF_INSTALL_PLUGINS=fifemon-graphql-datasource,yesoreyeram-infinity-datasource volumes: - ./grafana/config/:/etc/grafana/ diff --git a/ops/compose/grafana/config/grafana.ini b/compose/grafana/config/grafana.ini similarity index 100% rename from ops/compose/grafana/config/grafana.ini rename to compose/grafana/config/grafana.ini diff --git a/ops/compose/grafana/config/provisioning/dashboards/dashboards.yml b/compose/grafana/config/provisioning/dashboards/dashboards.yml similarity index 100% rename from ops/compose/grafana/config/provisioning/dashboards/dashboards.yml rename to compose/grafana/config/provisioning/dashboards/dashboards.yml diff --git a/ops/compose/grafana/data/grafana.db b/compose/grafana/data/grafana.db similarity index 81% rename from ops/compose/grafana/data/grafana.db rename to compose/grafana/data/grafana.db index fd0b2a8ed30b763d6548a7c6159aef6ca931ba0c..e1f78b18e7f73bd68f98405843936ba464eae253 100644 GIT binary patch delta 40523 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 delta 7279 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 diff --git a/ops/compose/prometheus/prometheus.yml b/compose/prometheus/prometheus.yml similarity index 100% rename from ops/compose/prometheus/prometheus.yml rename to compose/prometheus/prometheus.yml diff --git a/ops/compose/pull_dashboards.sh b/compose/pull_dashboards.sh similarity index 100% rename from ops/compose/pull_dashboards.sh rename to compose/pull_dashboards.sh diff --git a/ops/compose/graphix/network.graphix.yml b/configs/network.graphix.yml similarity index 91% rename from ops/compose/graphix/network.graphix.yml rename to configs/network.graphix.yml index 5eb4e44..9bea75a 100644 --- a/ops/compose/graphix/network.graphix.yml +++ b/configs/network.graphix.yml @@ -1,7 +1,3 @@ -databaseUrl: postgres://postgres:password@localhost:5433/graphix -graphql: - port: 3030 - chains: mainnet: caip2: "eip155:1" diff --git a/ops/compose/graphix/readonly.graphix.yml b/configs/readonly.graphix.yml similarity index 90% rename from ops/compose/graphix/readonly.graphix.yml rename to configs/readonly.graphix.yml index 623b37b..c55fb2c 100644 --- a/ops/compose/graphix/readonly.graphix.yml +++ b/configs/readonly.graphix.yml @@ -1,7 +1,3 @@ -databaseUrl: postgres://postgres:password@localhost:5433/graphix -graphql: - port: 3030 - chains: mainnet: caip2: "eip155:1" diff --git a/examples/testnet.graphix.yml b/configs/testnet.graphix.yml similarity index 67% rename from examples/testnet.graphix.yml rename to configs/testnet.graphix.yml index 258d28f..1852c7d 100644 --- a/examples/testnet.graphix.yml +++ b/configs/testnet.graphix.yml @@ -1,13 +1,9 @@ -graphql: - port: 3030 - -databaseUrl: postgres://postgres:password@postgres:5432/graphix sources: - # - type: networkSubgraph - # endpoint: https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-goerli - # query: byAllocations - # stakeThreshold: 0.0 - # limit: 1000 + # - type: networkSubgraph + # endpoint: https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-goerli + # query: byAllocations + # stakeThreshold: 0.0 + # limit: 1000 - type: networkSubgraph endpoint: https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet query: byAllocations diff --git a/crates/autogen_config_json_schema/schema.json b/crates/autogen_config_json_schema/schema.json index d740b0f..4123a78 100644 --- a/crates/autogen_config_json_schema/schema.json +++ b/crates/autogen_config_json_schema/schema.json @@ -3,14 +3,9 @@ "title": "Config", "description": "A [`serde`]-compatible representation of Graphix's YAML configuration file.", "type": "object", - "required": [ - "databaseUrl", - "graphql", - "sources" - ], "properties": { "blockChoicePolicy": { - "default": "maxSyncedBlocks", + "default": "earliest", "allOf": [ { "$ref": "#/definitions/BlockChoicePolicy" @@ -25,32 +20,14 @@ "$ref": "#/definitions/ChainConfig" } }, - "databaseUrl": { - "description": "The URL of the PostgreSQL database to use.", - "type": "string" - }, - "graphql": { - "description": "GraphQL API configuration.", - "allOf": [ - { - "$ref": "#/definitions/GraphQlConfig" - } - ] - }, "pollingPeriodInSeconds": { "default": 120, "type": "integer", "format": "uint64", "minimum": 0.0 }, - "prometheusPort": { - "description": "The port on which the Prometheus exporter should listen.", - "default": 9184, - "type": "integer", - "format": "uint16", - "minimum": 0.0 - }, "sources": { + "default": [], "type": "array", "items": { "$ref": "#/definitions/ConfigSource" @@ -226,18 +203,6 @@ } ] }, - "GraphQlConfig": { - "type": "object", - "properties": { - "port": { - "description": "The port on which the GraphQL API server should listen. Set it to 0 to disable the API server entirely.", - "default": 3030, - "type": "integer", - "format": "uint16", - "minimum": 0.0 - } - } - }, "HexString": { "type": "string" }, diff --git a/crates/autogen_graphql_schema/api_schema.graphql b/crates/autogen_graphql_schema/api_schema.graphql index 3a28eae..838269e 100644 --- a/crates/autogen_graphql_schema/api_schema.graphql +++ b/crates/autogen_graphql_schema/api_schema.graphql @@ -1,5 +1,15 @@ # AUTOGENERATED. DO NOT MODIFY. ALL CHANGES WILL BE LOST. +enum ApiKeyPermissionLevel { + ADMIN +} + +type ApiKeyPublicMetadata { + publicPrefix: String! + notes: String + permissionLevel: ApiKeyPermissionLevel! +} + """ Metadata that was collected during a bisection run. """ @@ -206,6 +216,11 @@ type IndexerNetworkSubgraphMetadata { scalar IpfsCid +""" +A scalar that can represent any JSON value. +""" +scalar JSON + type MutationRoot { """ Launches a divergence investigation, which is a process of comparing @@ -230,6 +245,12 @@ type MutationRoot { """ queryEntityChanges: Boolean! = true ): DivergenceInvestigationReport! + setConfiguration( + """ + The configuration file to use + """ + config: JSON! + ): Boolean! """ Create a new API key with the given permission level. You'll need to authenticate with another API key with the `admin` permission level to @@ -239,7 +260,7 @@ type MutationRoot { """ Permission level of the API key. Use `admin` for full access. """ - permissionLevel: String!, + permissionLevel: ApiKeyPermissionLevel!, """ Not-encrypted notes to store in the database alongside the API key, to be used for debugging or identification purposes. """ @@ -250,7 +271,7 @@ type MutationRoot { """ Not-encrypted notes to store in the database alongside the API key, to be used for debugging or identification purposes. """ - notes: String, permissionLevel: String! + notes: String, permissionLevel: ApiKeyPermissionLevel! ): Boolean! setDeploymentName(deploymentIpfsCid: String!, name: String!): Deployment! """ @@ -283,7 +304,7 @@ type Network { type NewlyCreatedApiKey { apiKey: String! notes: String - permissionLevel: String! + permissionLevel: ApiKeyPermissionLevel! } """ @@ -428,11 +449,16 @@ type QueryRoot { limit: Int! = 100 ): [ProofOfIndexing!]! """ + A copy of the configuration file used to run Graphix. + """ + configuration: JSON + """ Same as [`QueryRoot::proofs_of_indexing`], but only returns PoIs that are "live" i.e. they are the most recent PoI collected for their subgraph deployment. """ liveProofsOfIndexing(filter: PoisQuery!): [ProofOfIndexing!]! + apiKeys: [ApiKeyPublicMetadata!]! poiAgreementRatios(indexerAddress: HexString!): [PoiAgreementRatio!]! divergenceInvestigationReport( """ diff --git a/crates/common_types/Cargo.toml b/crates/common_types/Cargo.toml index 5f042c2..a93fdca 100644 --- a/crates/common_types/Cargo.toml +++ b/crates/common_types/Cargo.toml @@ -15,6 +15,7 @@ quickcheck = { workspace = true } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } +strum = { workspace = true } uuid = { workspace = true } [dev-dependencies] diff --git a/crates/common_types/src/api_key_permission_level.rs b/crates/common_types/src/api_key_permission_level.rs new file mode 100644 index 0000000..3dbba6d --- /dev/null +++ b/crates/common_types/src/api_key_permission_level.rs @@ -0,0 +1,42 @@ +use diesel::deserialize::{FromSql, FromSqlRow}; +use diesel::expression::AsExpression; +use diesel::pg::{Pg, PgValue}; +use diesel::serialize::ToSql; +use diesel::sql_types; + +#[derive( + Debug, + Copy, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + AsExpression, + FromSqlRow, + async_graphql::Enum, +)] +#[diesel(sql_type = sql_types::Integer)] +pub enum ApiKeyPermissionLevel { + Admin, +} + +impl ToSql for ApiKeyPermissionLevel { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, Pg>, + ) -> diesel::serialize::Result { + match self { + ApiKeyPermissionLevel::Admin => >::to_sql(&1, out), + } + } +} + +impl FromSql for ApiKeyPermissionLevel { + fn from_sql(bytes: PgValue<'_>) -> diesel::deserialize::Result { + match i32::from_sql(bytes)? { + 1 => Ok(ApiKeyPermissionLevel::Admin), + _ => Err(anyhow::anyhow!("invalid permission level").into()), + } + } +} diff --git a/crates/common_types/src/lib.rs b/crates/common_types/src/lib.rs index 9dad7e8..a8cb283 100644 --- a/crates/common_types/src/lib.rs +++ b/crates/common_types/src/lib.rs @@ -3,10 +3,12 @@ //! A few of these are shared with database models as well. Should we keep them //! separate? It would be cleaner, but at the cost of some code duplication. +mod api_key_permission_level; mod hex_string; pub mod inputs; mod ipfs_cid; +pub use api_key_permission_level::ApiKeyPermissionLevel; use async_graphql::*; use chrono::NaiveDateTime; pub use divergence_investigation::*; diff --git a/crates/graphix/Cargo.toml b/crates/graphix/Cargo.toml index 256ac3a..aa76a0a 100644 --- a/crates/graphix/Cargo.toml +++ b/crates/graphix/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" anyhow = { workspace = true } async-graphql = { workspace = true } async-trait = { workspace = true } -clap = { workspace = true, features = ["derive", "env"] } +clap = { workspace = true } futures = { workspace = true } graphix_common_types = { path = "../common_types" } graphix_indexer_client = { path = "../indexer_client" } diff --git a/crates/graphix/src/main.rs b/crates/graphix/src/main.rs index 83d5bdf..7ba7ec8 100644 --- a/crates/graphix/src/main.rs +++ b/crates/graphix/src/main.rs @@ -1,55 +1,49 @@ #![allow(clippy::type_complexity)] -mod bisect; -mod utils; - use std::collections::HashSet; +use std::env; use std::net::Ipv4Addr; -use std::path::PathBuf; +use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use clap::Parser; use graphix_indexer_client::{IndexerClient, IndexerId}; +use graphix_lib::bisect::handle_divergence_investigation_requests; use graphix_lib::config::Config; use graphix_lib::graphql_api::{axum_router, ServerState}; use graphix_lib::indexing_loop::{query_indexing_statuses, query_proofs_of_indexing}; -use graphix_lib::{config, metrics, PrometheusExporter}; +use graphix_lib::{config, metrics, CliOptions, PrometheusExporter}; use graphix_store::{models, PoiLiveness, Store}; use prometheus_exporter::prometheus; use tokio::net::TcpListener; use tokio::sync::watch; use tracing::*; -use crate::bisect::handle_divergence_investigation_requests; - -#[derive(Parser, Debug)] -struct CliOptions { - #[clap(long)] - config: PathBuf, -} - #[tokio::main] async fn main() -> anyhow::Result<()> { init_tracing(); - info!("Parse options"); let cli_options = CliOptions::parse(); - info!("Loading configuration file"); - let config = Config::read(&cli_options.config)?; + let config = if let Some(path) = cli_options.base_config { + info!("Loading configuration file"); + Config::read(&path)? + } else { + warn!("No base configuration file provided; using empty configuration"); + Config::default() + }; info!("Initialize store and running migrations"); - let store = Store::new(&config.database_url).await?; + let store = Store::new(&cli_options.database_url).await?; info!("Store initialization successful"); - if config.graphql.port != 0 { + { let config = config.clone(); tokio::spawn(async move { - // Listen to requests forever. axum::serve( - TcpListener::bind((Ipv4Addr::UNSPECIFIED, config.graphql.port)).await?, - axum_router(config).await?, + TcpListener::bind((Ipv4Addr::UNSPECIFIED, cli_options.port)).await?, + axum_router(&cli_options.database_url, config).await?, ) .await?; @@ -60,15 +54,17 @@ async fn main() -> anyhow::Result<()> { let sleep_duration = Duration::from_secs(config.polling_period_in_seconds); // Prometheus metrics. - let registry = prometheus::default_registry().clone(); - let _exporter = PrometheusExporter::start(config.prometheus_port, registry.clone()).unwrap(); + let _exporter = PrometheusExporter::start( + cli_options.prometheus_port, + prometheus::default_registry().clone(), + )?; info!("Initializing bisect request handler"); - let store_clone = store.clone(); let (tx_indexers, rx_indexers) = watch::channel(vec![]); - let ctx = ServerState::new(store_clone.clone(), config.clone()); - { + let store_clone = store.clone(); + let ctx = ServerState::new(store_clone.clone(), config.clone()); + let networks: Vec = config .chains .iter() @@ -78,13 +74,13 @@ async fn main() -> anyhow::Result<()> { }) .collect(); store_clone.create_networks_if_missing(&networks).await?; - } - tokio::spawn(async move { - handle_divergence_investigation_requests(&store_clone, rx_indexers, &ctx) - .await - .unwrap() - }); + tokio::spawn(async move { + handle_divergence_investigation_requests(&store_clone, rx_indexers, &ctx) + .await + .unwrap() + }); + } loop { info!("New main loop iteration"); @@ -124,7 +120,18 @@ async fn main() -> anyhow::Result<()> { } fn init_tracing() { - tracing_subscriber::fmt::init(); + use tracing_subscriber::prelude::*; + use tracing_subscriber::{fmt, EnvFilter}; + + tracing_subscriber::registry() + .with(fmt::layer()) + .with( + EnvFilter::from_str( + &env::var("RUST_LOG").unwrap_or_else(|_| "graphix=debug".to_string()), + ) + .unwrap(), + ) + .init(); } fn deduplicate_indexers(indexers: &[Arc]) -> Vec> { diff --git a/crates/graphix/src/utils.rs b/crates/graphix/src/utils.rs deleted file mode 100644 index b947422..0000000 --- a/crates/graphix/src/utils.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![allow(dead_code)] - -use std::collections::HashSet; -use std::hash::Hash; - -/// Creates all combinations of elements in the iterator, without duplicates. -/// Elements are never paired with themselves. -pub fn unordered_pairs_combinations(iter: impl Iterator + Clone) -> HashSet<(T, T)> -where - T: Hash + Eq + Clone, -{ - let mut pairs = HashSet::new(); - for (i, x) in iter.clone().enumerate() { - for y in iter.clone().skip(i + 1) { - pairs.insert((x.clone(), y)); - } - } - pairs -} - -#[cfg(test)] -mod unit_tests { - use super::*; - - fn test_unordered_pairs_combinations(original: Vec, combinations: Vec<(u32, u32)>) { - assert_eq!( - unordered_pairs_combinations(original.into_iter()), - HashSet::from_iter(combinations.into_iter()) - ); - } - - #[test] - fn unordered_pairs_combinations_test_cases() { - test_unordered_pairs_combinations(vec![], vec![]); - test_unordered_pairs_combinations(vec![1], vec![]); - test_unordered_pairs_combinations(vec![1, 2], vec![(1, 2)]); - test_unordered_pairs_combinations(vec![1, 2, 3], vec![(1, 2), (2, 3), (1, 3)]); - } -} diff --git a/crates/graphix_lib/Cargo.toml b/crates/graphix_lib/Cargo.toml index 493a4f6..258ce0e 100644 --- a/crates/graphix_lib/Cargo.toml +++ b/crates/graphix_lib/Cargo.toml @@ -10,6 +10,7 @@ async-graphql-axum = { workspace = true } async-trait = { workspace = true, optional = true } axum = { workspace = true } chrono = { workspace = true, features = ["serde"] } +clap = { workspace = true, features = ["derive", "env"] } derive_more = { workspace = true } diesel = { workspace = true } futures = { workspace = true } @@ -28,6 +29,7 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } serde_yaml = { workspace = true } sha2 = { workspace = true } +thiserror = "1" tokio = { workspace = true, features = ["full"] } tower-service = "0.3" tracing = { workspace = true } diff --git a/crates/graphix/src/bisect.rs b/crates/graphix_lib/src/bisect.rs similarity index 91% rename from crates/graphix/src/bisect.rs rename to crates/graphix_lib/src/bisect.rs index eafc081..d5716ea 100644 --- a/crates/graphix/src/bisect.rs +++ b/crates/graphix_lib/src/bisect.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; +use std::hash::Hash; use std::sync::Arc; use std::time::Duration; @@ -10,8 +12,6 @@ use graphix_common_types::{ use graphix_indexer_client::{ IndexerClient, IndexerId, PoiRequest, ProofOfIndexing, SubgraphDeployment, }; -use graphix_lib::graphql_api::api_types::{self, Indexer}; -use graphix_lib::graphql_api::ServerState; use graphix_store::models::DivergenceInvestigationRequest; use graphix_store::Store; use thiserror::Error; @@ -19,7 +19,8 @@ use tokio::sync::watch; use tracing::{debug, error, info}; use uuid::Uuid; -use crate::utils::unordered_pairs_combinations; +use crate::graphql_api::api_types::{self, Indexer}; +use crate::graphql_api::ServerState; pub struct DivergingBlock { pub poi1: ProofOfIndexing, @@ -427,3 +428,38 @@ async fn handle_divergence_investigation_request( report } + +/// Creates all combinations of elements in the iterator, without duplicates. +/// Elements are never paired with themselves. +pub fn unordered_pairs_combinations(iter: impl Iterator + Clone) -> HashSet<(T, T)> +where + T: Hash + Eq + Clone, +{ + let mut pairs = HashSet::new(); + for (i, x) in iter.clone().enumerate() { + for y in iter.clone().skip(i + 1) { + pairs.insert((x.clone(), y)); + } + } + pairs +} + +#[cfg(test)] +mod unit_tests { + use super::*; + + fn test_unordered_pairs_combinations(original: Vec, combinations: Vec<(u32, u32)>) { + assert_eq!( + unordered_pairs_combinations(original.into_iter()), + HashSet::from_iter(combinations.into_iter()) + ); + } + + #[test] + fn unordered_pairs_combinations_test_cases() { + test_unordered_pairs_combinations(vec![], vec![]); + test_unordered_pairs_combinations(vec![1], vec![]); + test_unordered_pairs_combinations(vec![1, 2], vec![(1, 2)]); + test_unordered_pairs_combinations(vec![1, 2, 3], vec![(1, 2), (2, 3), (1, 3)]); + } +} diff --git a/crates/graphix_lib/src/block_choice.rs b/crates/graphix_lib/src/block_choice.rs index 0ac8b61..6c9e7b1 100644 --- a/crates/graphix_lib/src/block_choice.rs +++ b/crates/graphix_lib/src/block_choice.rs @@ -6,9 +6,9 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "camelCase")] pub enum BlockChoicePolicy { // Use the earliest block that all indexers have in common + #[default] Earliest, // Use the block that maximizes the total number of blocks synced across all indexers - #[default] MaxSyncedBlocks, } diff --git a/crates/graphix_lib/src/cli.rs b/crates/graphix_lib/src/cli.rs new file mode 100644 index 0000000..2e0ff18 --- /dev/null +++ b/crates/graphix_lib/src/cli.rs @@ -0,0 +1,18 @@ +use std::path::PathBuf; + +use clap::Parser; + +#[derive(Parser, Debug)] +pub struct CliOptions { + /// The URL of the PostgreSQL database to use. + #[clap(long, env = "GRAPHIX_DB_URL")] + pub database_url: String, + #[clap(long)] + pub base_config: Option, + /// The port on which the GraphQL API server should listen. + #[clap(long, default_value_t = 8000)] + pub port: u16, + /// The port on which the Prometheus exporter should listen. + #[clap(long, default_value_t = 9184)] + pub prometheus_port: u16, +} diff --git a/crates/graphix_lib/src/config.rs b/crates/graphix_lib/src/config.rs index babb5f4..6490f33 100644 --- a/crates/graphix_lib/src/config.rs +++ b/crates/graphix_lib/src/config.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use std::collections::HashMap; -use std::fs::File; use std::path::Path; use std::sync::Arc; @@ -18,15 +17,6 @@ use url::Url; use crate::block_choice::BlockChoicePolicy; use crate::PrometheusMetrics; -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "camelCase")] -pub struct GraphQlConfig { - /// The port on which the GraphQL API server should listen. Set it to 0 to - /// disable the API server entirely. - #[serde(default = "Config::default_graphql_api_port")] - pub port: u16, -} - #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct BlockExplorerUrlTemplateForBlock(String); @@ -64,19 +54,13 @@ pub struct ChainConfig { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct Config { - /// GraphQL API configuration. - pub graphql: GraphQlConfig, - /// The URL of the PostgreSQL database to use. - pub database_url: String, - /// The port on which the Prometheus exporter should listen. - #[serde(default = "Config::default_prometheus_port")] - pub prometheus_port: u16, /// Chain-specific configuration. #[serde(default)] pub chains: HashMap, // Indexing options // ---------------- + #[serde(default)] pub sources: Vec, #[serde(default)] pub block_choice_policy: BlockChoicePolicy, @@ -84,10 +68,25 @@ pub struct Config { pub polling_period_in_seconds: u64, } +impl Default for Config { + fn default() -> Self { + Self { + chains: Default::default(), + sources: Default::default(), + block_choice_policy: Default::default(), + polling_period_in_seconds: Self::default_polling_period_in_seconds(), + } + } +} + impl Config { pub fn read(path: &Path) -> anyhow::Result { - let file = File::open(path)?; - serde_yaml::from_reader(file).context("invalid config file") + let file_contents = std::fs::read_to_string(path)?; + Self::from_str(&file_contents) + } + + pub fn from_str(s: &str) -> anyhow::Result { + serde_yaml::from_str(s).context("invalid config file") } pub fn indexers(&self) -> Vec { @@ -137,14 +136,6 @@ impl Config { fn default_polling_period_in_seconds() -> u64 { 120 } - - fn default_prometheus_port() -> u16 { - 9184 - } - - fn default_graphql_api_port() -> u16 { - 3030 - } } #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] @@ -301,3 +292,15 @@ pub async fn config_to_indexers( Ok(indexers) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_example_configs() { + Config::from_str(include_str!("../../../configs/testnet.graphix.yml")).unwrap(); + Config::from_str(include_str!("../../../configs/network.graphix.yml")).unwrap(); + Config::from_str(include_str!("../../../configs/readonly.graphix.yml")).unwrap(); + } +} diff --git a/crates/graphix_lib/src/graphql_api/api_types.rs b/crates/graphix_lib/src/graphql_api/api_types.rs index c8d9349..e31e2f5 100644 --- a/crates/graphix_lib/src/graphql_api/api_types.rs +++ b/crates/graphix_lib/src/graphql_api/api_types.rs @@ -1,6 +1,6 @@ use async_graphql::{ComplexObject, Context, Object, SimpleObject}; use common::{IndexerAddress, IpfsCid}; -use graphix_common_types as common; +use graphix_common_types::{self as common, ApiKeyPermissionLevel}; use graphix_store::models::{self, IntId}; use num_traits::cast::ToPrimitive; @@ -65,7 +65,7 @@ impl ApiKey { } #[graphql(name = "permissionLevel")] - async fn graphql_permission_level(&self) -> String { + async fn graphql_permission_level(&self) -> ApiKeyPermissionLevel { self.model.permission_level.clone() } diff --git a/crates/graphix_lib/src/graphql_api/mod.rs b/crates/graphix_lib/src/graphql_api/mod.rs index 6d89a6f..eaf13be 100644 --- a/crates/graphix_lib/src/graphql_api/mod.rs +++ b/crates/graphix_lib/src/graphql_api/mod.rs @@ -14,6 +14,7 @@ use axum::extract::State; use axum::http::header::AUTHORIZATION; use axum::http::StatusCode; use axum::Json; +use graphix_common_types::ApiKeyPermissionLevel; use graphix_store::models::ApiKey; use graphix_store::{Store, StoreLoader}; use tower_service::Service; @@ -90,10 +91,10 @@ pub fn ctx_data<'a>(ctx: &'a Context) -> &'a RequestState { .expect("Failed to get API context") } -pub async fn axum_router(config: Config) -> anyhow::Result> { +pub async fn axum_router(database_url: &str, config: Config) -> anyhow::Result> { use axum::routing::get; - let store = Store::new(config.database_url.as_str()).await?; + let store = Store::new(database_url).await?; let server_state = ServerState::new(store.clone(), config.clone()); Ok(axum::Router::new() @@ -151,3 +152,29 @@ fn api_key_error(err: impl ToString) -> (StatusCode, Json) { async fn graphiql_route() -> impl axum::response::IntoResponse { axum::response::Html(GraphiQLSource::build().endpoint("/graphql").finish()) } + +async fn require_permission_level( + ctx: &Context<'_>, + required_permission_level: ApiKeyPermissionLevel, +) -> async_graphql::Result<()> { + let ctx_data = ctx_data(ctx); + let api_key = ctx_data + .api_key + .as_ref() + .ok_or_else(|| anyhow::anyhow!("No API key provided"))?; + + let Some(actual_permission_level) = ctx_data.store.permission_level(&api_key).await? else { + return Err(anyhow::anyhow!("No permission level for API key").into()); + }; + + if actual_permission_level < required_permission_level { + return Err(anyhow::anyhow!( + "Insufficient permission level for API key: expected {:?}, got {:?}", + required_permission_level, + actual_permission_level + ) + .into()); + } + + Ok(()) +} diff --git a/crates/graphix_lib/src/graphql_api/mutation_root.rs b/crates/graphix_lib/src/graphql_api/mutation_root.rs index 9d302ba..7349d2f 100644 --- a/crates/graphix_lib/src/graphql_api/mutation_root.rs +++ b/crates/graphix_lib/src/graphql_api/mutation_root.rs @@ -1,12 +1,8 @@ -use std::str::FromStr; - use async_graphql::{Context, Object, Result}; use graphix_common_types::*; -use graphix_store::models::{ - ApiKeyPermissionLevel, DivergenceInvestigationRequest, NewlyCreatedApiKey, -}; +use graphix_store::models::{DivergenceInvestigationRequest, NewlyCreatedApiKey}; -use super::ctx_data; +use super::{ctx_data, require_permission_level}; pub struct MutationRoot; @@ -63,6 +59,21 @@ impl MutationRoot { Ok(report) } + async fn set_configuration( + &self, + ctx: &Context<'_>, + #[graphql(desc = "The configuration file to use")] config: serde_json::Value, + ) -> Result { + require_permission_level(ctx, ApiKeyPermissionLevel::Admin).await?; + + let ctx_data = ctx_data(ctx); + let store = &ctx_data.store; + + store.set_config(config).await?; + + Ok(true) + } + /// Create a new API key with the given permission level. You'll need to /// authenticate with another API key with the `admin` permission level to /// do this. @@ -70,16 +81,19 @@ impl MutationRoot { &self, ctx: &Context<'_>, #[graphql(desc = "Permission level of the API key. Use `admin` for full access.")] - permission_level: String, + permission_level: ApiKeyPermissionLevel, #[graphql( default, desc = "Not-encrypted notes to store in the database alongside the API key, to be used for debugging or identification purposes." )] notes: Option, ) -> Result { + // In order to create an API key with a certain permission level, you + // need to have that permission level yourself. + require_permission_level(ctx, permission_level).await?; + let ctx_data = ctx_data(ctx); - let permission_level = ApiKeyPermissionLevel::from_str(&permission_level)?; let api_key = ctx_data .store .create_api_key(notes.as_deref(), permission_level) @@ -104,11 +118,12 @@ impl MutationRoot { desc = "Not-encrypted notes to store in the database alongside the API key, to be used for debugging or identification purposes." )] notes: Option, - permission_level: String, + permission_level: ApiKeyPermissionLevel, ) -> Result { + require_permission_level(ctx, permission_level).await?; + let ctx_data = ctx_data(ctx); - let permission_level = ApiKeyPermissionLevel::from_str(&permission_level)?; ctx_data .store .modify_api_key(&api_key, notes.as_deref(), permission_level) diff --git a/crates/graphix_lib/src/graphql_api/query_root.rs b/crates/graphix_lib/src/graphql_api/query_root.rs index 33a1d86..0b20073 100644 --- a/crates/graphix_lib/src/graphql_api/query_root.rs +++ b/crates/graphix_lib/src/graphql_api/query_root.rs @@ -4,9 +4,10 @@ use anyhow::Context as _; use async_graphql::{Context, Object, Result}; use futures::future::try_join_all; use graphix_common_types::*; +use graphix_store::models::ApiKeyPublicMetadata; use uuid::Uuid; -use super::{api_types, ctx_data}; +use super::{api_types, ctx_data, require_permission_level}; pub struct QueryRoot; @@ -107,6 +108,16 @@ impl QueryRoot { Ok(pois.into_iter().map(Into::into).collect()) } + /// A copy of the configuration file used to run Graphix. + async fn configuration(&self, ctx: &Context<'_>) -> Result> { + require_permission_level(ctx, ApiKeyPermissionLevel::Admin).await?; + + let ctx_data = ctx_data(ctx); + let config = ctx_data.store.config().await?; + + Ok(config) + } + /// Same as [`QueryRoot::proofs_of_indexing`], but only returns PoIs that /// are "live" i.e. they are the most recent PoI collected for their /// subgraph deployment. @@ -129,6 +140,13 @@ impl QueryRoot { Ok(pois.into_iter().map(Into::into).collect()) } + async fn api_keys(&self, ctx: &Context<'_>) -> Result> { + let ctx_data = ctx_data(ctx); + let api_keys = ctx_data.store.api_keys().await?; + + Ok(api_keys) + } + async fn poi_agreement_ratios( &self, ctx: &Context<'_>, diff --git a/crates/graphix_lib/src/lib.rs b/crates/graphix_lib/src/lib.rs index e298a26..9bf95f2 100644 --- a/crates/graphix_lib/src/lib.rs +++ b/crates/graphix_lib/src/lib.rs @@ -1,4 +1,6 @@ +pub mod bisect; pub mod block_choice; +mod cli; pub mod config; pub mod graphql_api; pub mod indexing_loop; @@ -7,6 +9,7 @@ mod prometheus_metrics; #[cfg(feature = "tests")] pub mod test_utils; +pub use cli::CliOptions; pub use prometheus_metrics::{metrics, PrometheusExporter, PrometheusMetrics}; pub const GRAPHIX_VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/crates/network_sg_client/src/lib.rs b/crates/network_sg_client/src/lib.rs index 94b5e9d..5318b62 100644 --- a/crates/network_sg_client/src/lib.rs +++ b/crates/network_sg_client/src/lib.rs @@ -13,6 +13,9 @@ use serde::{Deserialize, Serialize}; use tracing::warn; use url::Url; +const PAGINATION_SIZE: usize = 100; +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30); + /// A GraphQL client that can query the network subgraph and extract useful /// data. /// @@ -30,13 +33,11 @@ pub struct NetworkSubgraphClient { } impl NetworkSubgraphClient { - const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60); - /// Creates a new [`NetworkSubgraphClient`] with the given endpoint. pub fn new(endpoint: Url, public_poi_requests: IntCounterVec) -> Self { Self { endpoint, - timeout: Self::DEFAULT_TIMEOUT, + timeout: DEFAULT_TIMEOUT, client: reqwest::Client::new(), public_poi_requests, } @@ -84,50 +85,32 @@ impl NetworkSubgraphClient { &self, limit: Option, ) -> anyhow::Result>> { - let page_size = 100; - - let mut indexers = Vec::>::new(); - loop { - let response_data: GraphqlResponseTopIndexers = self - .graphql_query_no_errors( - queries::INDEXERS_BY_ALLOCATIONS_QUERY, - vec![ - ("first".to_string(), page_size.into()), - ("skip".to_string(), indexers.len().into()), - ], - "error(s) querying indexers by allocations from the network subgraph", - ) - .await?; - - // If we got less than the page size, we're done. - let no_more_results = response_data.indexers.len() < page_size; - - for indexer in response_data.indexers { - if let Some(url) = indexer.url { - let address = str::parse::(&indexer.id) - .map_err(|e| anyhow!("invalid indexer address: {}", e))?; - let real_indexer = RealIndexer::new( - indexer.default_display_name, - address, - Url::parse(&format!("{}/status", url))?.to_string(), - self.public_poi_requests.clone(), - ); - indexers.push(Arc::new(real_indexer)); - } - } + let indexers = self + .paginate::( + queries::INDEXERS_BY_ALLOCATIONS_QUERY, + vec![], + "error(s) querying indexers by allocations from the network subgraph", + |response_data| response_data.indexers, + limit, + ) + .await?; - if no_more_results { - break; - } - if let Some(limit) = limit { - if indexers.len() > limit as usize { - indexers.truncate(limit as usize); - break; - } + let mut indexer_clients: Vec> = vec![]; + for indexer in indexers { + if let Some(url) = indexer.url { + let address = str::parse::(&indexer.id) + .map_err(|e| anyhow!("invalid indexer address: {}", e))?; + let real_indexer = RealIndexer::new( + indexer.default_display_name, + address, + Url::parse(&format!("{}/status", url))?.to_string(), + self.public_poi_requests.clone(), + ); + indexer_clients.push(Arc::new(real_indexer)); } } - Ok(indexers) + Ok(indexer_clients) } /// Instantiates a [`RealIndexer`] from the indexer with the given address, @@ -178,36 +161,15 @@ impl NetworkSubgraphClient { &self, limit: Option, ) -> anyhow::Result> { - let page_size = 100; - - let mut subgraph_deployments = vec![]; - loop { - let response_data: GraphqlResponseSgDeployments = self - .graphql_query_no_errors( - queries::DEPLOYMENTS_QUERY, - vec![ - ("first".to_string(), page_size.into()), - ("skip".to_string(), subgraph_deployments.len().into()), - ], - "error(s) querying deployments from the network subgraph", - ) - .await?; - - // If we got less than the page size, we're done. - let no_more_results = response_data.subgraph_deployments.len() < page_size; - - subgraph_deployments.extend(response_data.subgraph_deployments); - - if no_more_results { - break; - } - if let Some(limit) = limit { - if subgraph_deployments.len() > limit as usize { - subgraph_deployments.truncate(limit as usize); - break; - } - } - } + let subgraph_deployments = self + .paginate::( + queries::DEPLOYMENTS_QUERY, + vec![], + "error(s) querying deployments from the network subgraph", + |response_data| response_data.subgraph_deployments, + limit, + ) + .await?; Ok(subgraph_deployments) } @@ -258,6 +220,46 @@ impl NetworkSubgraphClient { .json() .await?) } + + async fn paginate( + &self, + query: impl ToString, + variables: Vec<(String, serde_json::Value)>, + error_msg: &str, + response_items: impl Fn(R) -> Vec, + limit: Option, + ) -> anyhow::Result> { + let page_size = PAGINATION_SIZE; + + let mut items = vec![]; + loop { + let mut variables = variables.clone(); + variables.push(("first".to_string(), page_size.into())); + variables.push(("skip".to_string(), items.len().into())); + + let response_data: R = self + .graphql_query_no_errors(query.to_string(), variables, error_msg) + .await?; + + // If we got less than the page size, we're done. + let page_items = response_items(response_data); + let no_more_results = page_items.len() < page_size; + + items.extend(page_items); + + if no_more_results { + break; + } + if let Some(limit) = limit { + if items.len() > limit as usize { + items.truncate(limit as usize); + break; + } + } + } + + Ok(items) + } } fn indexer_allocation_data_to_real_indexer( diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml index 5149ab4..e098f4e 100644 --- a/crates/store/Cargo.toml +++ b/crates/store/Cargo.toml @@ -22,6 +22,7 @@ tracing = { workspace = true } uuid = { workspace = true, features = ["v4"] } [dev-dependencies] +derive_more = { workspace = true } graphix_common_types = { path = "../common_types" } graphix_lib = { path = "../graphix_lib" } testcontainers = { workspace = true } diff --git a/crates/store/migrations/2023-03-30-000000_initial_schema/up.sql b/crates/store/migrations/2023-03-30-000000_initial_schema/up.sql index fb92fae..6d0c37e 100644 --- a/crates/store/migrations/2023-03-30-000000_initial_schema/up.sql +++ b/crates/store/migrations/2023-03-30-000000_initial_schema/up.sql @@ -143,6 +143,10 @@ CREATE TABLE graphix_api_tokens ( public_prefix TEXT PRIMARY KEY, sha256_api_key_hash BYTEA NOT NULL UNIQUE, notes TEXT, - -- We shouldn't really store permission levels as `TEXT` but... it works. - permission_level TEXT NOT NULL + permission_level INTEGER NOT NULL +); + +CREATE TABLE configs ( + id INTEGER PRIMARY KEY, + config JSONB NOT NULL ); diff --git a/crates/store/src/lib.rs b/crates/store/src/lib.rs index 2cf4fdc..7dbaf4d 100644 --- a/crates/store/src/lib.rs +++ b/crates/store/src/lib.rs @@ -1,602 +1,9 @@ //! Database access (read and write) abstractions for the Graphix backend. -mod diesel_queries; mod loader; - -use anyhow::anyhow; -use diesel_async::pooled_connection::deadpool::{Object, Pool}; -use diesel_async::pooled_connection::AsyncDieselConnectionManager; -use diesel_async::scoped_futures::ScopedFutureExt; -use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl}; -use graphix_common_types::{inputs, IndexerAddress, IpfsCid, PoiBytes}; -use models::{ - ApiKey, ApiKeyPermissionLevel, FailedQueryRow, NewIndexerNetworkSubgraphMetadata, - NewlyCreatedApiKey, SgDeployment, -}; -use uuid::Uuid; pub mod models; mod schema; +mod store; -use std::collections::HashMap; -use std::fmt::Debug; -use std::str::FromStr; -use std::sync::Arc; - -use anyhow::Error; -use diesel::prelude::*; -use diesel_async_migrations::{embed_migrations, EmbeddedMigrations}; -use graphix_indexer_client::{IndexerClient, IndexerId, WritablePoi}; pub use loader::StoreLoader; -use tracing::info; - -use crate::models::{Indexer as IndexerModel, IntId, NewNetwork, Poi}; - -/// An abstraction over all database operations. It uses [`Arc`] internally, so -/// it's cheaply cloneable. -#[derive(Clone)] -pub struct Store { - pool: Pool, -} - -impl Debug for Store { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // It might contain sensitive data, so don't print it. - f.debug_struct("Store").finish() - } -} - -impl Store { - const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); - - /// Connects to the database and runs all pending migrations. - pub async fn new(db_url: &str) -> anyhow::Result { - info!("Initializing database connection pool"); - - let manager = AsyncDieselConnectionManager::new(db_url); - let pool = Pool::builder(manager).build()?; - let store = Self { pool }; - - store.run_migrations().await?; - - Ok(store) - } - - async fn run_migrations(&self) -> anyhow::Result<()> { - let mut conn = self.pool.get().await?; - - info!("Run database migrations"); - - Self::MIGRATIONS - .run_pending_migrations(&mut conn) - .await - .map_err(|e| anyhow::anyhow!(e))?; - - Ok(()) - } - - pub async fn conn(&self) -> anyhow::Result> { - Ok(self.pool.get().await?) - } - - pub async fn conn_err_string(&self) -> Result, String> { - Ok(self.pool.get().await.map_err(|e| e.to_string())?) - } - - /// Returns subgraph deployments stored in the database that match the - /// filtering criteria. - pub async fn sg_deployments( - &self, - filter: inputs::SgDeploymentsQuery, - ) -> anyhow::Result> { - use schema::sg_deployments as sgd; - - let mut query = sgd::table - .inner_join(schema::networks::table) - .left_join(schema::sg_names::table) - .select(( - sgd::id, - sgd::ipfs_cid, - schema::sg_names::name.nullable(), - sgd::network, - sgd::created_at, - )) - .order_by(sgd::ipfs_cid.asc()) - .into_boxed(); - - if let Some(network_name) = filter.network_name { - query = query.filter(schema::networks::name.eq(network_name)); - } - if let Some(name) = filter.name { - query = query.filter(schema::sg_names::name.eq(name)); - } - if let Some(ipfs_cid) = filter.ipfs_cid { - query = query.filter(sgd::ipfs_cid.eq(ipfs_cid.to_string())); - } - if let Some(limit) = filter.limit { - query = query.limit(limit.into()); - } - - Ok(query.load::(&mut self.conn().await?).await?) - } - - pub async fn create_networks_if_missing(&self, networks: &[NewNetwork]) -> anyhow::Result<()> { - use schema::networks; - - let mut conn = self.conn().await?; - - // batch insert - diesel::insert_into(networks::table) - .values(networks) - .on_conflict_do_nothing() - .execute(&mut conn) - .await?; - - Ok(()) - } - - pub async fn create_sg_deployment( - &self, - network_name: &str, - ipfs_cid: &str, - ) -> anyhow::Result<()> { - use schema::sg_deployments as sgd; - - diesel::insert_into(sgd::table) - .values(( - sgd::ipfs_cid.eq(ipfs_cid), - sgd::network.eq(schema::networks::table - .select(schema::networks::id) - .filter(schema::networks::name.eq(network_name)) - .single_value() - .assume_not_null()), - )) - .execute(&mut self.conn().await?) - .await?; - - Ok(()) - } - - pub async fn set_deployment_name( - &self, - sg_deployment_id: &str, - name: &str, - ) -> anyhow::Result<()> { - use schema::{sg_deployments as sgd, sg_names}; - - diesel::insert_into(sg_names::table) - .values(( - sg_names::sg_deployment_id.eq(sgd::table - .select(sgd::id) - .filter(sgd::ipfs_cid.eq(sg_deployment_id)) - .single_value() - .assume_not_null()), - sg_names::name.eq(name), - )) - .on_conflict(sg_names::sg_deployment_id) - .do_update() - .set(sg_names::name.eq(name)) - .execute(&mut self.conn().await?) - .await?; - - Ok(()) - } - - /// Fetches a Poi from the database. - pub async fn poi(&self, poi: &PoiBytes) -> anyhow::Result> { - use schema::pois; - - let query = pois::table - .select(pois::all_columns) - .filter(pois::poi.eq(poi)); - - Ok(query.get_result(&mut self.conn().await?).await.optional()?) - } - - pub async fn failed_query( - &self, - indexer: &impl IndexerId, - query_name: &str, - ) -> anyhow::Result> { - use schema::failed_queries; - - let conn = &mut self.conn().await?; - let indexer_id = - diesel_queries::get_indexer_id(conn, indexer.name(), &indexer.address()).await?; - - let failed_query = failed_queries::table - .filter(failed_queries::indexer_id.eq(indexer_id)) - .filter(failed_queries::query_name.eq(query_name)) - .select(( - failed_queries::indexer_id, - failed_queries::query_name, - failed_queries::raw_query, - failed_queries::response, - failed_queries::request_timestamp, - )) - .get_result::(conn) - .await - .optional()?; - - Ok(failed_query) - } - - /// Deletes the network with the given name from the database, together with - /// **all** of its related data (indexers, deployments, etc.). - pub async fn delete_network(&self, network_name: &str) -> anyhow::Result<()> { - use schema::networks; - - diesel::delete(networks::table.filter(networks::name.eq(network_name))) - .execute(&mut self.conn().await?) - .await?; - // The `ON DELETE CASCADE`s should take care of the rest of the cleanup. - - Ok(()) - } - - pub async fn create_network(&self, network: &NewNetwork) -> anyhow::Result { - use schema::networks; - - let id = diesel::insert_into(networks::table) - .values(network) - .returning(networks::id) - .get_result(&mut self.conn().await?) - .await?; - - Ok(id) - } - - /// Returns all networks stored in the database. Filtering is not really - /// necessary here because the number of networks is expected to be small, - /// so filtering can be done client-side. - pub async fn networks(&self) -> anyhow::Result> { - use schema::networks; - - let mut conn = self.conn().await?; - Ok(networks::table - .select((networks::id, networks::name, networks::caip2)) - .load(&mut conn) - .await?) - } - - /// Returns all indexers stored in the database. - pub async fn indexers( - &self, - filter: inputs::IndexersQuery, - ) -> anyhow::Result> { - use schema::indexers; - - let mut query = indexers::table.select(indexers::all_columns).into_boxed(); - - if let Some(address) = filter.address { - query = query.filter(indexers::address.eq(address)); - } - if let Some(limit) = filter.limit { - query = query.limit(limit.into()); - } - - Ok(query.load::(&mut self.conn().await?).await?) - } - - /// Queries the database for proofs of indexing that refer to the specified - /// subgraph deployments and in the given [`inputs::BlockRange`], if given. - pub async fn pois( - &self, - sg_deployments: &[IpfsCid], - block_range: Option, - limit: Option, - ) -> anyhow::Result> { - let mut conn = self.conn().await?; - diesel_queries::pois( - &mut conn, - None, - Some(sg_deployments), - block_range, - limit, - false, - ) - .await - } - - /// Like `pois`, but only returns live pois. - pub async fn live_pois( - &self, - indexer_address: Option<&IndexerAddress>, - sg_deployments_cids: Option<&[IpfsCid]>, - block_range: Option, - limit: Option, - ) -> anyhow::Result> { - let mut conn = self.conn().await?; - diesel_queries::pois( - &mut conn, - indexer_address, - sg_deployments_cids, - block_range, - limit, - true, - ) - .await - } - - pub async fn write_pois(&self, pois: Vec, live: PoiLiveness) -> anyhow::Result<()> - where - W: WritablePoi + Send + Sync, - W::IndexerId: Send + Sync, - { - self.conn() - .await? - .transaction::<_, Error, _>(|conn| { - async move { - diesel_queries::write_pois(conn, pois, live).await?; - Ok(()) - } - .scope_boxed() - }) - .await - } - - pub async fn write_indexers( - &self, - indexers: &[impl AsRef], - ) -> anyhow::Result<()> { - let mut conn = self.conn().await?; - diesel_queries::write_indexers(&mut conn, indexers).await?; - Ok(()) - } - - pub async fn delete_indexer_network_subgraph_metadata( - &self, - indexer_id: IntId, - ) -> anyhow::Result<()> { - use schema::indexers; - - diesel::update(indexers::table.filter(indexers::id.eq(indexer_id))) - .set(indexers::network_subgraph_metadata.eq::>(None)) - .execute(&mut self.conn().await?) - .await?; - - Ok(()) - } - - pub async fn create_or_update_indexer_network_subgraph_metadata( - &self, - indexer_id: IntId, - metadata: NewIndexerNetworkSubgraphMetadata, - ) -> anyhow::Result { - use schema::{indexer_network_subgraph_metadata, indexers}; - - self.conn() - .await? - .transaction::<_, Error, _>(|conn| { - // Fetch the metadata id from indexer_id, and update it if it exists - // create a new one and set the foreign key to the indexer_id if it doesn't exist - async move { - let metadata_id = indexers::table - .select(indexers::network_subgraph_metadata) - .filter(indexers::id.eq(indexer_id)) - .get_result::>(conn) - .await?; - - let metadata_id = match metadata_id { - Some(id) => { - diesel::update( - indexer_network_subgraph_metadata::table - .filter(indexer_network_subgraph_metadata::id.eq(id)), - ) - .set(metadata) - .execute(conn) - .await?; - id - } - None => { - let metadata_id = - diesel::insert_into(indexer_network_subgraph_metadata::table) - .values(&metadata) - .returning(indexer_network_subgraph_metadata::id) - .get_result(conn) - .await?; - - diesel::update(indexers::table) - .filter(indexers::id.eq(indexer_id)) - .set(indexers::network_subgraph_metadata.eq(metadata_id)) - .execute(conn) - .await?; - - metadata_id - } - }; - - Ok(metadata_id) - } - .scope_boxed() - }) - .await?; - - Ok(indexer_id) - } - - pub async fn create_api_key( - &self, - notes: Option<&str>, - permission_level: ApiKeyPermissionLevel, - ) -> anyhow::Result { - use schema::graphix_api_tokens; - - let api_key = ApiKey::generate(); - - diesel::insert_into(graphix_api_tokens::table) - .values(( - graphix_api_tokens::public_prefix.eq(api_key.public_part_as_string()), - graphix_api_tokens::sha256_api_key_hash.eq(api_key.hash()), - graphix_api_tokens::notes.eq(notes), - graphix_api_tokens::permission_level.eq(permission_level.to_string()), - )) - .execute(&mut self.conn().await?) - .await?; - - Ok(NewlyCreatedApiKey { - api_key: api_key.to_string(), - notes: notes.map(|s| s.to_string()), - permission_level: permission_level.to_string(), - }) - } - - pub async fn modify_api_key( - &self, - api_key_s: &str, - notes: Option<&str>, - permission_level: ApiKeyPermissionLevel, - ) -> anyhow::Result<()> { - use schema::graphix_api_tokens; - - let api_key = ApiKey::from_str(api_key_s).map_err(|e| anyhow!("invalid api key: {}", e))?; - - diesel::update(graphix_api_tokens::table) - .filter(graphix_api_tokens::sha256_api_key_hash.eq(api_key.hash())) - .set(( - graphix_api_tokens::notes.eq(notes), - graphix_api_tokens::permission_level.eq(permission_level.to_string()), - )) - .execute(&mut self.conn().await?) - .await?; - - Ok(()) - } - - pub async fn delete_api_key(&self, api_key_s: &str) -> anyhow::Result<()> { - use schema::graphix_api_tokens; - - let api_key = ApiKey::from_str(api_key_s).map_err(|e| anyhow!("invalid api key: {}", e))?; - - diesel::delete(graphix_api_tokens::table) - .filter(graphix_api_tokens::sha256_api_key_hash.eq(api_key.hash())) - .execute(&mut self.conn().await?) - .await?; - - Ok(()) - } - - pub async fn write_graph_node_versions( - &self, - versions: HashMap< - Arc, - anyhow::Result, - >, - ) -> anyhow::Result<()> { - use schema::graph_node_collected_versions; - for version in versions.values() { - let conn = &mut self.conn().await?; - - let new_version = match version { - Ok(v) => models::NewGraphNodeCollectedVersion { - version_string: v.version.clone(), - version_commit: v.commit.clone(), - error_response: None, - }, - Err(err) => models::NewGraphNodeCollectedVersion { - version_string: None, - version_commit: None, - error_response: Some(err.to_string()), - }, - }; - - diesel::insert_into(graph_node_collected_versions::table) - .values(&new_version) - .execute(conn) - .await?; - } - - Ok(()) - } - - pub async fn get_first_pending_divergence_investigation_request( - &self, - ) -> anyhow::Result> { - use schema::pending_divergence_investigation_requests as requests; - - Ok(requests::table - .select((requests::uuid, requests::request)) - .first::<(Uuid, serde_json::Value)>(&mut self.conn().await?) - .await - .optional()?) - } - - pub async fn create_divergence_investigation_request( - &self, - request: serde_json::Value, - ) -> anyhow::Result { - use schema::pending_divergence_investigation_requests as requests; - - let uuid = uuid::Uuid::new_v4(); - diesel::insert_into(requests::table) - .values((requests::uuid.eq(&uuid), requests::request.eq(&request))) - .execute(&mut self.conn().await?) - .await?; - - Ok(uuid) - } - - /// Fetches the divergence investigation report with the given UUID, if it - /// exists. - pub async fn divergence_investigation_report( - &self, - uuid: &Uuid, - ) -> anyhow::Result> { - use schema::divergence_investigation_reports as reports; - - Ok(reports::table - .select(reports::report) - .filter(reports::uuid.eq(uuid)) - .first(&mut self.conn().await?) - .await - .optional()?) - } - - pub async fn create_or_update_divergence_investigation_report( - &self, - uuid: &Uuid, - report: serde_json::Value, - ) -> anyhow::Result<()> { - use schema::divergence_investigation_reports as reports; - - diesel::insert_into(reports::table) - .values((reports::uuid.eq(&uuid), reports::report.eq(&report))) - .on_conflict(reports::uuid) - .do_update() - .set(reports::report.eq(&report)) - .execute(&mut self.conn().await?) - .await?; - - Ok(()) - } - - pub async fn divergence_investigation_request_exists( - &self, - uuid: &Uuid, - ) -> anyhow::Result { - use schema::pending_divergence_investigation_requests as requests; - - let exists = requests::table - .filter(requests::uuid.eq(uuid)) - .count() - .get_result::(&mut self.conn().await?) - .await? - > 0; - Ok(exists) - } - - pub async fn delete_divergence_investigation_request(&self, uuid: &Uuid) -> anyhow::Result<()> { - use schema::pending_divergence_investigation_requests as requests; - - diesel::delete(requests::table.filter(requests::uuid.eq(uuid))) - .execute(&mut self.conn().await?) - .await?; - - Ok(()) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum PoiLiveness { - Live, - NotLive, -} +pub use store::{PoiLiveness, Store}; diff --git a/crates/store/src/models.rs b/crates/store/src/models.rs index 546c779..382d7cf 100644 --- a/crates/store/src/models.rs +++ b/crates/store/src/models.rs @@ -8,7 +8,7 @@ use diesel::deserialize::FromSql; use diesel::pg::Pg; use diesel::sql_types::Jsonb; use diesel::{AsChangeset, AsExpression, FromSqlRow, Insertable, Queryable, Selectable}; -use graphix_common_types as types; +use graphix_common_types::{self as types, ApiKeyPermissionLevel}; use graphix_indexer_client::IndexerId; use serde::{Deserialize, Serialize}; use sha2::Digest; @@ -129,6 +129,32 @@ impl IndexerId for Indexer { } } +#[derive(Debug, Clone, Insertable, Queryable, Selectable)] +#[diesel(table_name = graphix_api_tokens)] +pub struct ApiKeyDbRow { + pub public_prefix: String, + pub sha256_api_key_hash: Vec, + pub notes: Option, + pub permission_level: ApiKeyPermissionLevel, +} + +#[derive(Debug, Clone, SimpleObject)] +pub struct ApiKeyPublicMetadata { + pub public_prefix: String, + pub notes: Option, + pub permission_level: ApiKeyPermissionLevel, +} + +impl From for ApiKeyPublicMetadata { + fn from(sak: ApiKeyDbRow) -> Self { + Self { + public_prefix: sak.public_prefix, + notes: sak.notes, + permission_level: sak.permission_level, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct ApiKey { public_part: Uuid, @@ -144,7 +170,7 @@ impl ApiKey { } pub fn public_part_as_string(&self) -> String { - self.public_part.to_string() + self.public_part.as_simple().to_string() } pub fn hash(&self) -> Vec { @@ -159,7 +185,7 @@ impl std::str::FromStr for ApiKey { let parts: Vec<&str> = s.split('-').collect(); let parts: [&str; 3] = parts.try_into().map_err(|_| "invalid api key format")?; - if parts[0] != "graphix_api_key" { + if parts[0] != "graphix" { return Err("invalid api key format".to_string()); } @@ -177,7 +203,7 @@ impl std::fmt::Display for ApiKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "graphix_api_key-{}-{}", + "graphix-{}-{}", self.public_part.as_simple(), self.private_part.as_simple() ) @@ -239,16 +265,11 @@ pub struct NewIndexer { pub name: Option, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, strum::Display, strum::EnumString)] -pub enum ApiKeyPermissionLevel { - Admin, -} - #[derive(Debug, Clone, async_graphql::SimpleObject)] pub struct NewlyCreatedApiKey { pub api_key: String, pub notes: Option, - pub permission_level: String, + pub permission_level: ApiKeyPermissionLevel, } #[derive(Debug, Clone, Queryable, Serialize)] diff --git a/crates/store/src/schema.rs b/crates/store/src/schema.rs index d8bad06..b82f877 100644 --- a/crates/store/src/schema.rs +++ b/crates/store/src/schema.rs @@ -9,6 +9,13 @@ diesel::table! { } } +diesel::table! { + configs (id) { + id -> Int4, + config -> Jsonb, + } +} + diesel::table! { divergence_investigation_reports (uuid) { uuid -> Uuid, @@ -43,7 +50,7 @@ diesel::table! { public_prefix -> Text, sha256_api_key_hash -> Bytea, notes -> Nullable, - permission_level -> Text, + permission_level -> Int4, } } @@ -156,6 +163,7 @@ diesel::joinable!(sg_names -> sg_deployments (sg_deployment_id)); diesel::allow_tables_to_appear_in_same_query!( blocks, + configs, divergence_investigation_reports, failed_queries, graph_node_collected_versions, diff --git a/crates/store/src/diesel_queries.rs b/crates/store/src/store/diesel_queries.rs similarity index 100% rename from crates/store/src/diesel_queries.rs rename to crates/store/src/store/diesel_queries.rs diff --git a/crates/store/src/store/mod.rs b/crates/store/src/store/mod.rs new file mode 100644 index 0000000..4114255 --- /dev/null +++ b/crates/store/src/store/mod.rs @@ -0,0 +1,675 @@ +mod diesel_queries; + +use std::collections::HashMap; +use std::fmt::Debug; +use std::str::FromStr; +use std::sync::Arc; + +use anyhow::{anyhow, Error}; +use diesel::prelude::*; +use diesel_async::pooled_connection::deadpool::{Object, Pool}; +use diesel_async::pooled_connection::AsyncDieselConnectionManager; +use diesel_async::scoped_futures::ScopedFutureExt; +use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl}; +use diesel_async_migrations::{embed_migrations, EmbeddedMigrations}; +use graphix_common_types::{inputs, ApiKeyPermissionLevel, IndexerAddress, IpfsCid, PoiBytes}; +use graphix_indexer_client::{IndexerClient, IndexerId, WritablePoi}; +use tracing::info; +use uuid::Uuid; + +use crate::models::{ + ApiKey, ApiKeyDbRow, ApiKeyPublicMetadata, FailedQueryRow, Indexer as IndexerModel, IntId, + NewIndexerNetworkSubgraphMetadata, NewNetwork, NewlyCreatedApiKey, Poi, SgDeployment, +}; +use crate::{models, schema}; + +const SINGLETON_CONFIG_ID: i32 = 1; + +/// An abstraction over all database operations. It uses [`Arc`] internally, so +/// it's cheaply cloneable. +#[derive(Clone)] +pub struct Store { + pool: Pool, +} + +impl Debug for Store { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // It might contain sensitive data, so don't print it. + f.debug_struct("Store").finish() + } +} + +impl Store { + const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); + + /// Connects to the database and runs all pending migrations. + pub async fn new(db_url: &str) -> anyhow::Result { + info!("Initializing database connection pool"); + + let manager = AsyncDieselConnectionManager::new(db_url); + let pool = Pool::builder(manager).build()?; + let store = Self { pool }; + + store.run_migrations().await?; + + if store.api_keys().await?.is_empty() { + info!("No API keys found in database, creating master API key"); + store.create_master_api_key().await?; + } + + Ok(store) + } + + async fn run_migrations(&self) -> anyhow::Result<()> { + let mut conn = self.pool.get().await?; + + info!("Run database migrations"); + + Self::MIGRATIONS + .run_pending_migrations(&mut conn) + .await + .map_err(|e| anyhow::anyhow!(e))?; + + Ok(()) + } + + pub async fn conn(&self) -> anyhow::Result> { + Ok(self.pool.get().await?) + } + + pub async fn conn_err_string(&self) -> Result, String> { + Ok(self.pool.get().await.map_err(|e| e.to_string())?) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PoiLiveness { + Live, + NotLive, +} + +/// Getters. +impl Store { + /// Returns subgraph deployments stored in the database that match the + /// filtering criteria. + pub async fn sg_deployments( + &self, + filter: inputs::SgDeploymentsQuery, + ) -> anyhow::Result> { + use schema::sg_deployments as sgd; + + let mut query = sgd::table + .inner_join(schema::networks::table) + .left_join(schema::sg_names::table) + .select(( + sgd::id, + sgd::ipfs_cid, + schema::sg_names::name.nullable(), + sgd::network, + sgd::created_at, + )) + .order_by(sgd::ipfs_cid.asc()) + .into_boxed(); + + if let Some(network_name) = filter.network_name { + query = query.filter(schema::networks::name.eq(network_name)); + } + if let Some(name) = filter.name { + query = query.filter(schema::sg_names::name.eq(name)); + } + if let Some(ipfs_cid) = filter.ipfs_cid { + query = query.filter(sgd::ipfs_cid.eq(ipfs_cid.to_string())); + } + if let Some(limit) = filter.limit { + query = query.limit(limit.into()); + } + + Ok(query.load::(&mut self.conn().await?).await?) + } + + /// Fetches a Poi from the database. + pub async fn poi(&self, poi: &PoiBytes) -> anyhow::Result> { + use schema::pois; + + let query = pois::table + .select(pois::all_columns) + .filter(pois::poi.eq(poi)); + + Ok(query.get_result(&mut self.conn().await?).await.optional()?) + } + + pub async fn config(&self) -> anyhow::Result> { + use schema::configs; + + let query = configs::table + .select(configs::config) + .filter(configs::id.eq(SINGLETON_CONFIG_ID)); + + Ok(query.get_result(&mut self.conn().await?).await.optional()?) + } + + pub async fn failed_query( + &self, + indexer: &impl IndexerId, + query_name: &str, + ) -> anyhow::Result> { + use schema::failed_queries; + + let conn = &mut self.conn().await?; + let indexer_id = + diesel_queries::get_indexer_id(conn, indexer.name(), &indexer.address()).await?; + + let failed_query = failed_queries::table + .filter(failed_queries::indexer_id.eq(indexer_id)) + .filter(failed_queries::query_name.eq(query_name)) + .select(( + failed_queries::indexer_id, + failed_queries::query_name, + failed_queries::raw_query, + failed_queries::response, + failed_queries::request_timestamp, + )) + .get_result::(conn) + .await + .optional()?; + + Ok(failed_query) + } + + /// Returns all networks stored in the database. Filtering is not really + /// necessary here because the number of networks is expected to be small, + /// so filtering can be done client-side. + pub async fn networks(&self) -> anyhow::Result> { + use schema::networks; + + let mut conn = self.conn().await?; + Ok(networks::table + .select((networks::id, networks::name, networks::caip2)) + .load(&mut conn) + .await?) + } + + /// Returns all indexers stored in the database. + pub async fn indexers( + &self, + filter: inputs::IndexersQuery, + ) -> anyhow::Result> { + use schema::indexers; + + let mut query = indexers::table.select(indexers::all_columns).into_boxed(); + + if let Some(address) = filter.address { + query = query.filter(indexers::address.eq(address)); + } + if let Some(limit) = filter.limit { + query = query.limit(limit.into()); + } + + Ok(query.load::(&mut self.conn().await?).await?) + } + + /// Queries the database for proofs of indexing that refer to the specified + /// subgraph deployments and in the given [`inputs::BlockRange`], if given. + pub async fn pois( + &self, + sg_deployments: &[IpfsCid], + block_range: Option, + limit: Option, + ) -> anyhow::Result> { + let mut conn = self.conn().await?; + diesel_queries::pois( + &mut conn, + None, + Some(sg_deployments), + block_range, + limit, + false, + ) + .await + } + + /// Like `pois`, but only returns live pois. + pub async fn live_pois( + &self, + indexer_address: Option<&IndexerAddress>, + sg_deployments_cids: Option<&[IpfsCid]>, + block_range: Option, + limit: Option, + ) -> anyhow::Result> { + let mut conn = self.conn().await?; + diesel_queries::pois( + &mut conn, + indexer_address, + sg_deployments_cids, + block_range, + limit, + true, + ) + .await + } + + pub async fn api_keys(&self) -> anyhow::Result> { + use schema::graphix_api_tokens; + + Ok(graphix_api_tokens::table + .load::(&mut self.conn().await?) + .await? + .into_iter() + .map(ApiKeyPublicMetadata::from) + .collect()) + } + + pub async fn permission_level( + &self, + api_key: &ApiKey, + ) -> anyhow::Result> { + use schema::graphix_api_tokens; + + Ok(graphix_api_tokens::table + .select(graphix_api_tokens::permission_level) + .filter(graphix_api_tokens::sha256_api_key_hash.eq(api_key.hash())) + .get_result(&mut self.conn().await?) + .await + .optional()?) + } + + pub async fn get_first_pending_divergence_investigation_request( + &self, + ) -> anyhow::Result> { + use schema::pending_divergence_investigation_requests as requests; + + Ok(requests::table + .select((requests::uuid, requests::request)) + .first::<(Uuid, serde_json::Value)>(&mut self.conn().await?) + .await + .optional()?) + } + + /// Fetches the divergence investigation report with the given UUID, if it + /// exists. + pub async fn divergence_investigation_report( + &self, + uuid: &Uuid, + ) -> anyhow::Result> { + use schema::divergence_investigation_reports as reports; + + Ok(reports::table + .select(reports::report) + .filter(reports::uuid.eq(uuid)) + .first(&mut self.conn().await?) + .await + .optional()?) + } + + pub async fn divergence_investigation_request_exists( + &self, + uuid: &Uuid, + ) -> anyhow::Result { + use schema::pending_divergence_investigation_requests as requests; + + let exists = requests::table + .filter(requests::uuid.eq(uuid)) + .count() + .get_result::(&mut self.conn().await?) + .await? + > 0; + Ok(exists) + } +} + +/// Setters and write operations. +impl Store { + async fn create_master_api_key(&self) -> anyhow::Result<()> { + let api_key = self + .create_api_key(None, ApiKeyPermissionLevel::Admin) + .await?; + + let description = format!("Master API key created during database initialization. Use it to create a new private API key and then delete it for security reasons. `{}`", api_key.api_key.to_string()); + self.modify_api_key( + &api_key.api_key, + Some(&description), + ApiKeyPermissionLevel::Admin, + ) + .await?; + + info!(api_key = ?api_key.api_key, "Created master API key"); + + Ok(()) + } + + pub async fn create_networks_if_missing(&self, networks: &[NewNetwork]) -> anyhow::Result<()> { + use schema::networks; + + let mut conn = self.conn().await?; + + // batch insert + diesel::insert_into(networks::table) + .values(networks) + .on_conflict_do_nothing() + .execute(&mut conn) + .await?; + + Ok(()) + } + + pub async fn create_sg_deployment( + &self, + network_name: &str, + ipfs_cid: &str, + ) -> anyhow::Result<()> { + use schema::sg_deployments as sgd; + + diesel::insert_into(sgd::table) + .values(( + sgd::ipfs_cid.eq(ipfs_cid), + sgd::network.eq(schema::networks::table + .select(schema::networks::id) + .filter(schema::networks::name.eq(network_name)) + .single_value() + .assume_not_null()), + )) + .execute(&mut self.conn().await?) + .await?; + + Ok(()) + } + + pub async fn set_config(&self, config: serde_json::Value) -> anyhow::Result<()> { + use schema::configs; + + diesel::insert_into(configs::table) + .values(( + configs::id.eq(SINGLETON_CONFIG_ID), + configs::config.eq(config), + )) + .execute(&mut self.conn().await?) + .await?; + + Ok(()) + } + + pub async fn set_deployment_name( + &self, + sg_deployment_id: &str, + name: &str, + ) -> anyhow::Result<()> { + use schema::{sg_deployments as sgd, sg_names}; + + diesel::insert_into(sg_names::table) + .values(( + sg_names::sg_deployment_id.eq(sgd::table + .select(sgd::id) + .filter(sgd::ipfs_cid.eq(sg_deployment_id)) + .single_value() + .assume_not_null()), + sg_names::name.eq(name), + )) + .on_conflict(sg_names::sg_deployment_id) + .do_update() + .set(sg_names::name.eq(name)) + .execute(&mut self.conn().await?) + .await?; + + Ok(()) + } + + /// Deletes the network with the given name from the database, together with + /// **all** of its related data (indexers, deployments, etc.). + pub async fn delete_network(&self, network_name: &str) -> anyhow::Result<()> { + use schema::networks; + + diesel::delete(networks::table.filter(networks::name.eq(network_name))) + .execute(&mut self.conn().await?) + .await?; + // The `ON DELETE CASCADE`s should take care of the rest of the cleanup. + + Ok(()) + } + + pub async fn create_network(&self, network: &NewNetwork) -> anyhow::Result { + use schema::networks; + + let id = diesel::insert_into(networks::table) + .values(network) + .returning(networks::id) + .get_result(&mut self.conn().await?) + .await?; + + Ok(id) + } + + pub async fn write_pois(&self, pois: Vec, live: PoiLiveness) -> anyhow::Result<()> + where + W: WritablePoi + Send + Sync, + W::IndexerId: Send + Sync, + { + self.conn() + .await? + .transaction::<_, Error, _>(|conn| { + async move { + diesel_queries::write_pois(conn, pois, live).await?; + Ok(()) + } + .scope_boxed() + }) + .await + } + + pub async fn write_indexers( + &self, + indexers: &[impl AsRef], + ) -> anyhow::Result<()> { + let mut conn = self.conn().await?; + diesel_queries::write_indexers(&mut conn, indexers).await?; + Ok(()) + } + + pub async fn delete_indexer_network_subgraph_metadata( + &self, + indexer_id: IntId, + ) -> anyhow::Result<()> { + use schema::indexers; + + diesel::update(indexers::table.filter(indexers::id.eq(indexer_id))) + .set(indexers::network_subgraph_metadata.eq::>(None)) + .execute(&mut self.conn().await?) + .await?; + + Ok(()) + } + + pub async fn create_or_update_indexer_network_subgraph_metadata( + &self, + indexer_id: IntId, + metadata: NewIndexerNetworkSubgraphMetadata, + ) -> anyhow::Result { + use schema::{indexer_network_subgraph_metadata, indexers}; + + self.conn() + .await? + .transaction::<_, Error, _>(|conn| { + // Fetch the metadata id from indexer_id, and update it if it exists + // create a new one and set the foreign key to the indexer_id if it doesn't exist + async move { + let metadata_id = indexers::table + .select(indexers::network_subgraph_metadata) + .filter(indexers::id.eq(indexer_id)) + .get_result::>(conn) + .await?; + + let metadata_id = match metadata_id { + Some(id) => { + diesel::update( + indexer_network_subgraph_metadata::table + .filter(indexer_network_subgraph_metadata::id.eq(id)), + ) + .set(metadata) + .execute(conn) + .await?; + id + } + None => { + let metadata_id = + diesel::insert_into(indexer_network_subgraph_metadata::table) + .values(&metadata) + .returning(indexer_network_subgraph_metadata::id) + .get_result(conn) + .await?; + + diesel::update(indexers::table) + .filter(indexers::id.eq(indexer_id)) + .set(indexers::network_subgraph_metadata.eq(metadata_id)) + .execute(conn) + .await?; + + metadata_id + } + }; + + Ok(metadata_id) + } + .scope_boxed() + }) + .await?; + + Ok(indexer_id) + } + + pub async fn create_api_key( + &self, + notes: Option<&str>, + permission_level: ApiKeyPermissionLevel, + ) -> anyhow::Result { + use schema::graphix_api_tokens; + + let api_key = ApiKey::generate(); + let stored_api_key = ApiKeyDbRow { + public_prefix: api_key.public_part_as_string(), + sha256_api_key_hash: api_key.hash(), + notes: notes.map(|s| s.to_string()), + permission_level, + }; + + diesel::insert_into(graphix_api_tokens::table) + .values(&[stored_api_key]) + .execute(&mut self.conn().await?) + .await?; + + Ok(NewlyCreatedApiKey { + api_key: api_key.to_string(), + notes: notes.map(|s| s.to_string()), + permission_level, + }) + } + + pub async fn modify_api_key( + &self, + api_key_s: &str, + notes: Option<&str>, + permission_level: ApiKeyPermissionLevel, + ) -> anyhow::Result<()> { + use schema::graphix_api_tokens; + + let api_key = ApiKey::from_str(api_key_s).map_err(|e| anyhow!("invalid api key: {}", e))?; + + diesel::update(graphix_api_tokens::table) + .filter(graphix_api_tokens::sha256_api_key_hash.eq(api_key.hash())) + .set(( + graphix_api_tokens::notes.eq(notes), + graphix_api_tokens::permission_level.eq(permission_level), + )) + .execute(&mut self.conn().await?) + .await?; + + Ok(()) + } + + pub async fn delete_api_key(&self, api_key_s: &str) -> anyhow::Result<()> { + use schema::graphix_api_tokens; + + let api_key = ApiKey::from_str(api_key_s).map_err(|e| anyhow!("invalid api key: {}", e))?; + + diesel::delete(graphix_api_tokens::table) + .filter(graphix_api_tokens::sha256_api_key_hash.eq(api_key.hash())) + .execute(&mut self.conn().await?) + .await?; + + Ok(()) + } + + pub async fn write_graph_node_versions( + &self, + versions: HashMap< + Arc, + anyhow::Result, + >, + ) -> anyhow::Result<()> { + use schema::graph_node_collected_versions; + for version in versions.values() { + let conn = &mut self.conn().await?; + + let new_version = match version { + Ok(v) => models::NewGraphNodeCollectedVersion { + version_string: v.version.clone(), + version_commit: v.commit.clone(), + error_response: None, + }, + Err(err) => models::NewGraphNodeCollectedVersion { + version_string: None, + version_commit: None, + error_response: Some(err.to_string()), + }, + }; + + diesel::insert_into(graph_node_collected_versions::table) + .values(&new_version) + .execute(conn) + .await?; + } + + Ok(()) + } + + pub async fn create_divergence_investigation_request( + &self, + request: serde_json::Value, + ) -> anyhow::Result { + use schema::pending_divergence_investigation_requests as requests; + + let uuid = uuid::Uuid::new_v4(); + diesel::insert_into(requests::table) + .values((requests::uuid.eq(&uuid), requests::request.eq(&request))) + .execute(&mut self.conn().await?) + .await?; + + Ok(uuid) + } + + pub async fn create_or_update_divergence_investigation_report( + &self, + uuid: &Uuid, + report: serde_json::Value, + ) -> anyhow::Result<()> { + use schema::divergence_investigation_reports as reports; + + diesel::insert_into(reports::table) + .values((reports::uuid.eq(&uuid), reports::report.eq(&report))) + .on_conflict(reports::uuid) + .do_update() + .set(reports::report.eq(&report)) + .execute(&mut self.conn().await?) + .await?; + + Ok(()) + } + + pub async fn delete_divergence_investigation_request(&self, uuid: &Uuid) -> anyhow::Result<()> { + use schema::pending_divergence_investigation_requests as requests; + + diesel::delete(requests::table.filter(requests::uuid.eq(uuid))) + .execute(&mut self.conn().await?) + .await?; + + Ok(()) + } +} diff --git a/crates/store/tests/common.rs b/crates/store/tests/common.rs new file mode 100644 index 0000000..26bf520 --- /dev/null +++ b/crates/store/tests/common.rs @@ -0,0 +1,31 @@ +use graphix_store::Store; +use testcontainers::runners::AsyncRunner; +use testcontainers::ContainerAsync; +use testcontainers_modules::postgres::Postgres; + +const POSTGRES_PORT: u16 = 5432; + +/// A wrapper around a [`Store`] that is backed by a containerized Postgres +/// database. +#[derive(derive_more::Deref)] +pub struct EmptyStoreForTesting { + #[deref] + store: Store, + _container: ContainerAsync, +} + +impl EmptyStoreForTesting { + pub async fn new() -> anyhow::Result { + let container = Postgres::default().start().await?; + let connection_string = &format!( + "postgres://postgres:postgres@127.0.0.1:{}/postgres", + container.get_host_port_ipv4(POSTGRES_PORT).await? + ); + + let store = Store::new(connection_string).await?; + Ok(Self { + _container: container, + store, + }) + } +} diff --git a/crates/store/tests/common/mod.rs b/crates/store/tests/common/mod.rs deleted file mode 100644 index b69db0b..0000000 --- a/crates/store/tests/common/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::ops::Deref; - -use graphix_store::Store; -use testcontainers::clients::Cli; -use testcontainers::Container; - -/// A wrapper around a [`Store`] that is backed by a containerized Postgres -/// database. -pub struct EmptyStoreForTesting<'a> { - _container: Container<'a, testcontainers_modules::postgres::Postgres>, - store: Store, -} - -impl<'a> EmptyStoreForTesting<'a> { - pub async fn new(docker_client: &'a Cli) -> anyhow::Result> { - use testcontainers_modules::postgres::Postgres; - - let container = docker_client.run(Postgres::default()); - let connection_string = &format!( - "postgres://postgres:postgres@127.0.0.1:{}/postgres", - container.get_host_port_ipv4(5432) - ); - let store = Store::new(connection_string).await?; - Ok(Self { - _container: container, - store, - }) - } -} - -impl<'a> Deref for EmptyStoreForTesting<'a> { - type Target = Store; - - fn deref(&self) -> &Self::Target { - &self.store - } -} diff --git a/crates/store/tests/tests.rs b/crates/store/tests/tests.rs index df2466b..45725e6 100644 --- a/crates/store/tests/tests.rs +++ b/crates/store/tests/tests.rs @@ -2,14 +2,12 @@ mod common; use graphix_common_types::inputs::SgDeploymentsQuery; use graphix_store::models::{Network, NewNetwork}; -use testcontainers::clients::Cli; use crate::common::EmptyStoreForTesting; #[tokio::test] async fn empty_store_has_no_deployments() { - let docker_cli = Cli::default(); - let store = EmptyStoreForTesting::new(&docker_cli).await.unwrap(); + let store = EmptyStoreForTesting::new().await.unwrap(); let initial_deployments = store .sg_deployments(SgDeploymentsQuery::default()) .await @@ -19,8 +17,7 @@ async fn empty_store_has_no_deployments() { #[tokio::test] async fn create_then_delete_network() { - let docker_cli = Cli::default(); - let store = EmptyStoreForTesting::new(&docker_cli).await.unwrap(); + let store = EmptyStoreForTesting::new().await.unwrap(); assert_eq!(store.networks().await.unwrap(), vec![]); @@ -49,8 +46,7 @@ async fn create_then_delete_network() { #[tokio::test] #[should_panic] // FIXME async fn deployments_with_name() { - let docker_cli = Cli::default(); - let store = EmptyStoreForTesting::new(&docker_cli).await.unwrap(); + let store = EmptyStoreForTesting::new().await.unwrap(); let ipfs_cid1 = "QmNY7gDNXHECV8SXoEY7hbfg4BX1aDMxTBDiFuG4huaSGA"; let ipfs_cid2 = "QmYzsCjrVwwXtdsNm3PZVNziLGmb9o513GUzkq5wwhgXDT"; @@ -80,8 +76,7 @@ async fn deployments_with_name() { #[tokio::test] async fn create_divergence_investigation_request() { - let docker_cli = Cli::default(); - let store = EmptyStoreForTesting::new(&docker_cli).await.unwrap(); + let store = EmptyStoreForTesting::new().await.unwrap(); let uuid = store .create_divergence_investigation_request(serde_json::json!({})) diff --git a/ops/compose/.gitignore b/ops/compose/.gitignore deleted file mode 100644 index 0f9c1e5..0000000 --- a/ops/compose/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -data/* - -# No need to backup the Grafana plugins, they'll just get re-installed at -# startup. -grafana/data/plugins/* diff --git a/ops/compose/grafana/data/alerting/1/__default__.tmpl b/ops/compose/grafana/data/alerting/1/__default__.tmpl deleted file mode 100644 index b8633d1..0000000 --- a/ops/compose/grafana/data/alerting/1/__default__.tmpl +++ /dev/null @@ -1,53 +0,0 @@ - -{{ define "__subject" }}[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ if gt (.Alerts.Resolved | len) 0 }}, RESOLVED:{{ .Alerts.Resolved | len }}{{ end }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}{{ end }} - -{{ define "__text_values_list" }}{{ if len .Values }}{{ $first := true }}{{ range $refID, $value := .Values -}} -{{ if $first }}{{ $first = false }}{{ else }}, {{ end }}{{ $refID }}={{ $value }}{{ end -}} -{{ else }}[no value]{{ end }}{{ end }} - -{{ define "__text_alert_list" }}{{ range . }} -Value: {{ template "__text_values_list" . }} -Labels: -{{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }} -{{ end }}Annotations: -{{ range .Annotations.SortedPairs }} - {{ .Name }} = {{ .Value }} -{{ end }}{{ if gt (len .GeneratorURL) 0 }}Source: {{ .GeneratorURL }} -{{ end }}{{ if gt (len .SilenceURL) 0 }}Silence: {{ .SilenceURL }} -{{ end }}{{ if gt (len .DashboardURL) 0 }}Dashboard: {{ .DashboardURL }} -{{ end }}{{ if gt (len .PanelURL) 0 }}Panel: {{ .PanelURL }} -{{ end }}{{ end }}{{ end }} - -{{ define "default.title" }}{{ template "__subject" . }}{{ end }} - -{{ define "default.message" }}{{ if gt (len .Alerts.Firing) 0 }}**Firing** -{{ template "__text_alert_list" .Alerts.Firing }}{{ if gt (len .Alerts.Resolved) 0 }} - -{{ end }}{{ end }}{{ if gt (len .Alerts.Resolved) 0 }}**Resolved** -{{ template "__text_alert_list" .Alerts.Resolved }}{{ end }}{{ end }} - - -{{ define "__teams_text_alert_list" }}{{ range . }} -Value: {{ template "__text_values_list" . }} -Labels: -{{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }} -{{ end }} -Annotations: -{{ range .Annotations.SortedPairs }} - {{ .Name }} = {{ .Value }} -{{ end }} -{{ if gt (len .GeneratorURL) 0 }}Source: [{{ .GeneratorURL }}]({{ .GeneratorURL }}) - -{{ end }}{{ if gt (len .SilenceURL) 0 }}Silence: [{{ .SilenceURL }}]({{ .SilenceURL }}) - -{{ end }}{{ if gt (len .DashboardURL) 0 }}Dashboard: [{{ .DashboardURL }}]({{ .DashboardURL }}) - -{{ end }}{{ if gt (len .PanelURL) 0 }}Panel: [{{ .PanelURL }}]({{ .PanelURL }}) - -{{ end }} -{{ end }}{{ end }} - - -{{ define "teams.default.message" }}{{ if gt (len .Alerts.Firing) 0 }}**Firing** -{{ template "__teams_text_alert_list" .Alerts.Firing }}{{ if gt (len .Alerts.Resolved) 0 }} - -{{ end }}{{ end }}{{ if gt (len .Alerts.Resolved) 0 }}**Resolved** -{{ template "__teams_text_alert_list" .Alerts.Resolved }}{{ end }}{{ end }} diff --git a/ops/compose/graphix.yml b/ops/compose/graphix.yml deleted file mode 100644 index b03412c..0000000 --- a/ops/compose/graphix.yml +++ /dev/null @@ -1,24 +0,0 @@ -version: "3" -include: - - dependencies.yml - -services: - graphix: - image: edgeandnode/graphix - restart: on-failure - build: - context: ../.. - args: - CARGO_PROFILE: dev - depends_on: - postgres: - condition: service_healthy - environment: - RUST_LOG: graphix=debug - network_mode: host - ports: - - "9184:9184" - - "3030:3030" - volumes: - - ./graphix/:/config/ - command: ["--config", "/config/network.graphix.yml"]