Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fb9c1d1fa | |||
| 6649cbb229 | |||
| 57b9ddec41 | |||
| c6959d3e2d | |||
| 2569deb080 | |||
| 5cefa7fdd4 | |||
| 80b590d50d | |||
| f9b363648f | |||
| b73561f1c9 | |||
| 09b68a8204 | |||
| 0374626960 | |||
| cf4fe5f875 | |||
| 9f8bf2d080 | |||
| b9d1fc40e7 |
@@ -57,6 +57,12 @@ jobs:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets -- -D warnings
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
@@ -82,9 +88,3 @@ jobs:
|
||||
with:
|
||||
command: test
|
||||
args: --workspace -- --ignored
|
||||
|
||||
- name: Clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets -- -D warnings
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
name: Build and upload Node Status agent container to harbor.nymte.ch
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "nym-node-status-agent"
|
||||
CONTAINER_NAME: "node-status-agent"
|
||||
|
||||
jobs:
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
steps:
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: harbor.nymte.ch
|
||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_ROBOT_SECRET }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config --global user.email "lawrence@nymtech.net"
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.3
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
- name: Check if tag exists
|
||||
run: |
|
||||
if git rev-parse ${{ steps.get_version.outputs.value }} >/dev/null 2>&1; then
|
||||
echo "Tag ${{ steps.get_version.outputs.value }} already exists"
|
||||
fi
|
||||
|
||||
- name: Remove existing tag if exists
|
||||
run: |
|
||||
if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
|
||||
git push --delete origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
git tag -d ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
fi
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}"
|
||||
git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
@@ -1,11 +1,55 @@
|
||||
name: Build and upload Node Status API container to harbor.nymte.ch
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
WORKING_DIRECTORY: "nym-node-status-api"
|
||||
CONTAINER_NAME: "node-status-api"
|
||||
|
||||
jobs:
|
||||
my-job:
|
||||
runs-on: arc-ubuntu-22.04
|
||||
build-container:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
steps:
|
||||
- name: my-step
|
||||
run: echo "Hello World!"
|
||||
- name: Login to Harbor
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: harbor.nymte.ch
|
||||
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
|
||||
password: ${{ secrets.HARBOR_ROBOT_SECRET }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config --global user.email "lawrence@nymtech.net"
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.3
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
- name: Check if tag exists
|
||||
run: |
|
||||
if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
|
||||
echo "Tag ${{ steps.get_version.outputs.result }} already exists"
|
||||
fi
|
||||
|
||||
- name: Remove existing tag if exists
|
||||
run: |
|
||||
if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
|
||||
git push --delete origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
git tag -d ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
fi
|
||||
|
||||
- name: Create tag
|
||||
run: |
|
||||
git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}"
|
||||
git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
|
||||
Generated
+235
-7
@@ -318,10 +318,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener",
|
||||
"event-listener 2.5.3",
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
|
||||
dependencies = [
|
||||
"event-listener 5.3.1",
|
||||
"event-listener-strategy",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.5"
|
||||
@@ -455,6 +466,7 @@ checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core 0.4.5",
|
||||
"axum-macros",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
@@ -553,6 +565,17 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-test"
|
||||
version = "16.2.0"
|
||||
@@ -2331,6 +2354,15 @@ dependencies = [
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "envy"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
@@ -2373,6 +2405,27 @@ version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
|
||||
dependencies = [
|
||||
"event-listener 5.3.1",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.1.41"
|
||||
@@ -3593,7 +3646,7 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
"curl",
|
||||
"curl-sys",
|
||||
"event-listener",
|
||||
"event-listener 2.5.3",
|
||||
"futures-lite",
|
||||
"http 0.2.12",
|
||||
"log",
|
||||
@@ -4075,6 +4128,30 @@ dependencies = [
|
||||
"wasm-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moka"
|
||||
version = "0.12.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32cf62eb4dd975d2dde76432fb1075c49e3ee2331cf36f1f8fd4b66550d32b6f"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"async-trait",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"event-listener 5.3.1",
|
||||
"futures-util",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"quanta",
|
||||
"rustc_version 0.4.0",
|
||||
"smallvec",
|
||||
"tagptr",
|
||||
"thiserror",
|
||||
"triomphe",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multer"
|
||||
version = "2.1.0"
|
||||
@@ -4920,6 +4997,13 @@ dependencies = [
|
||||
"nym-multisig-contract-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-common-models"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-compact-ecash"
|
||||
version = "0.1.0"
|
||||
@@ -5235,12 +5319,12 @@ dependencies = [
|
||||
name = "nym-explorer-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"nym-explorer-api-requests",
|
||||
"reqwest 0.12.4",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -5894,6 +5978,61 @@ dependencies = [
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-agent"
|
||||
version = "0.1.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.5.18",
|
||||
"nym-bin-common",
|
||||
"nym-common-models",
|
||||
"reqwest 0.12.4",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-api"
|
||||
version = "0.1.6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.7",
|
||||
"chrono",
|
||||
"clap 4.5.18",
|
||||
"cosmwasm-std",
|
||||
"envy",
|
||||
"futures-util",
|
||||
"moka",
|
||||
"nym-bin-common",
|
||||
"nym-common-models",
|
||||
"nym-explorer-client",
|
||||
"nym-network-defaults",
|
||||
"nym-node-requests",
|
||||
"nym-task",
|
||||
"nym-validator-client",
|
||||
"regex",
|
||||
"reqwest 0.12.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json_path",
|
||||
"sqlx",
|
||||
"strum 0.26.3",
|
||||
"strum_macros 0.26.4",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-log 0.2.0",
|
||||
"tracing-subscriber",
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
"utoipauto",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-tester-utils"
|
||||
version = "0.1.0"
|
||||
@@ -6488,7 +6627,6 @@ dependencies = [
|
||||
"flate2",
|
||||
"futures",
|
||||
"itertools 0.13.0",
|
||||
"log",
|
||||
"nym-api-requests",
|
||||
"nym-coconut-bandwidth-contract-common",
|
||||
"nym-coconut-dkg-common",
|
||||
@@ -6512,6 +6650,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"ts-rs",
|
||||
"url",
|
||||
"wasmtimer",
|
||||
@@ -7421,6 +7560,21 @@ dependencies = [
|
||||
"psl-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quanta"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"raw-cpuid",
|
||||
"wasi",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
@@ -7512,6 +7666,15 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-cpuid"
|
||||
version = "11.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
@@ -8361,9 +8524,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.128"
|
||||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -8371,6 +8534,59 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json_path"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bc0207b6351893eafa1e39aa9aea452abb6425ca7b02dd64faf29109e7a33ba"
|
||||
dependencies = [
|
||||
"inventory",
|
||||
"nom",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json_path_core",
|
||||
"serde_json_path_macros",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json_path_core"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3d64fe53ce1aaa31bea2b2b46d3b6ab6a37e61854bedcbd9f174e188f3f7d79"
|
||||
dependencies = [
|
||||
"inventory",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json_path_macros"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a31e8177a443fd3e94917f12946ae7891dfb656e6d4c5e79b8c5d202fbcb723"
|
||||
dependencies = [
|
||||
"inventory",
|
||||
"once_cell",
|
||||
"serde_json_path_core",
|
||||
"serde_json_path_macros_internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json_path_macros_internal"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75dde5a1d2ed78dfc411fc45592f72d3694436524d3353683ecb3d22009731dc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.16"
|
||||
@@ -8735,7 +8951,7 @@ dependencies = [
|
||||
"crc",
|
||||
"crossbeam-queue",
|
||||
"either",
|
||||
"event-listener",
|
||||
"event-listener 2.5.3",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-intrusive",
|
||||
@@ -9145,6 +9361,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tagptr"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
@@ -9917,6 +10139,12 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "triomphe"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3"
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
|
||||
+13
-1
@@ -60,6 +60,7 @@ members = [
|
||||
"common/ip-packet-requests",
|
||||
"common/ledger",
|
||||
"common/mixnode-common",
|
||||
"common/models",
|
||||
"common/network-defaults",
|
||||
"common/node-tester-utils",
|
||||
"common/nonexhaustive-delayqueue",
|
||||
@@ -119,6 +120,8 @@ members = [
|
||||
"nym-node",
|
||||
"nym-node/nym-node-http-api",
|
||||
"nym-node/nym-node-requests",
|
||||
"nym-node-status-api",
|
||||
"nym-node-status-agent",
|
||||
"nym-outfox",
|
||||
"nym-validator-rewarder",
|
||||
"tools/echo-server",
|
||||
@@ -146,13 +149,16 @@ members = [
|
||||
default-members = [
|
||||
"clients/native",
|
||||
"clients/socks5",
|
||||
"common/models",
|
||||
"explorer-api",
|
||||
"gateway",
|
||||
"mixnode",
|
||||
"nym-api",
|
||||
"nym-data-observatory",
|
||||
"nym-node",
|
||||
"nym-node-status-api",
|
||||
"nym-validator-rewarder",
|
||||
"nym-node-status-api",
|
||||
"service-providers/authenticator",
|
||||
"service-providers/ip-packet-router",
|
||||
"service-providers/network-requester",
|
||||
@@ -234,10 +240,12 @@ dotenvy = "0.15.6"
|
||||
ecdsa = "0.16"
|
||||
ed25519-dalek = "2.1"
|
||||
etherparse = "0.13.0"
|
||||
envy = "0.4"
|
||||
eyre = "0.6.9"
|
||||
fastrand = "2.1.1"
|
||||
flate2 = "1.0.34"
|
||||
futures = "0.3.28"
|
||||
futures-util = "0.3"
|
||||
generic-array = "0.14.7"
|
||||
getrandom = "0.2.10"
|
||||
getset = "0.1.3"
|
||||
@@ -267,6 +275,7 @@ ledger-transport-hid = "0.10.0"
|
||||
log = "0.4"
|
||||
maxminddb = "0.23.0"
|
||||
mime = "0.3.17"
|
||||
moka = { version = "0.12", features = ["future"] }
|
||||
nix = "0.27.1"
|
||||
notify = "5.1.0"
|
||||
okapi = "0.7.0"
|
||||
@@ -299,7 +308,8 @@ semver = "1.0.23"
|
||||
serde = "1.0.210"
|
||||
serde_bytes = "0.11.15"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0.128"
|
||||
serde_json = "1.0.132"
|
||||
serde_json_path = "0.6.7"
|
||||
serde_repr = "0.1"
|
||||
serde_with = "3.9.0"
|
||||
serde_yaml = "0.9.25"
|
||||
@@ -308,6 +318,7 @@ si-scale = "0.2.3"
|
||||
sphinx-packet = "0.1.1"
|
||||
sqlx = "0.7.4"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
subtle-encoding = "0.5"
|
||||
syn = "1"
|
||||
sysinfo = "0.30.13"
|
||||
@@ -329,6 +340,7 @@ tracing = "0.1.37"
|
||||
tracing-opentelemetry = "0.19.0"
|
||||
tracing-subscriber = "0.3.16"
|
||||
tracing-tree = "0.2.2"
|
||||
tracing-log = "0.2"
|
||||
ts-rs = "10.0.0"
|
||||
tungstenite = { version = "0.20.1", default-features = false }
|
||||
url = "2.5"
|
||||
|
||||
@@ -27,7 +27,7 @@ pub type HmacSha256 = Hmac<Sha256>;
|
||||
pub type Nonce = u64;
|
||||
pub type Taken = Option<SystemTime>;
|
||||
|
||||
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
|
||||
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct InitMessage {
|
||||
|
||||
@@ -45,3 +45,4 @@ tracing = [
|
||||
"opentelemetry",
|
||||
]
|
||||
clap = [ "dep:clap", "dep:clap_complete", "dep:clap_complete_fig" ]
|
||||
models = []
|
||||
|
||||
@@ -25,7 +25,7 @@ serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
nym-http-api-client = { path = "../../../common/http-api-client" }
|
||||
thiserror = { workspace = true }
|
||||
log = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
tokio = { workspace = true, features = ["sync", "time"] }
|
||||
time = { workspace = true, features = ["formatting"] }
|
||||
|
||||
@@ -18,7 +18,7 @@ use nym_api_requests::ecash::{
|
||||
PartialExpirationDateSignatureResponse, VerificationKeyResponse,
|
||||
};
|
||||
use nym_api_requests::models::{
|
||||
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
|
||||
ApiHealthResponse, GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
|
||||
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||
};
|
||||
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
||||
@@ -192,6 +192,8 @@ impl<C, S> Client<C, S> {
|
||||
}
|
||||
|
||||
// validator-api wrappers
|
||||
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
|
||||
#[allow(deprecated)]
|
||||
impl<C, S> Client<C, S> {
|
||||
pub fn api_url(&self) -> &Url {
|
||||
self.nym_api.current_url()
|
||||
@@ -201,46 +203,54 @@ impl<C, S> Client<C, S> {
|
||||
self.nym_api.change_base_url(new_endpoint)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnodes_detailed().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_mixnodes_detailed_unfiltered(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnodes_detailed_unfiltered().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_rewarded_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_rewarded_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_rewarded_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_rewarded_mixnodes_detailed().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_active_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_active_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_active_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_active_mixnodes_detailed().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_gateways().await?)
|
||||
}
|
||||
@@ -304,6 +314,8 @@ pub struct NymApiClient {
|
||||
// we could re-implement the communication with the REST API on port 1317
|
||||
}
|
||||
|
||||
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
|
||||
#[allow(deprecated)]
|
||||
impl NymApiClient {
|
||||
pub fn new(api_url: Url) -> Self {
|
||||
let nym_api = nym_api::Client::new(api_url, None);
|
||||
@@ -311,6 +323,13 @@ impl NymApiClient {
|
||||
NymApiClient { nym_api }
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self {
|
||||
let nym_api = nym_api::Client::new(api_url, Some(timeout));
|
||||
|
||||
NymApiClient { nym_api }
|
||||
}
|
||||
|
||||
pub fn new_with_user_agent(api_url: Url, user_agent: UserAgent) -> Self {
|
||||
let nym_api = nym_api::Client::builder::<_, ValidatorClientError>(api_url)
|
||||
.expect("invalid api url")
|
||||
@@ -417,6 +436,38 @@ impl NymApiClient {
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
/// retrieve basic information for nodes are capable of operating as a mixnode
|
||||
/// this includes legacy mixnodes and nym-nodes
|
||||
pub async fn get_all_basic_mixing_capable_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut nodes = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_mixing_capable_nodes(
|
||||
semver_compatibility.clone(),
|
||||
false,
|
||||
Some(page),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() < res.nodes.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
/// retrieve basic information for all bonded nodes on the network
|
||||
pub async fn get_all_basic_nodes(
|
||||
&self,
|
||||
@@ -443,26 +494,35 @@ impl NymApiClient {
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
pub async fn health(&self) -> Result<ApiHealthResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.health().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_active_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_active_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_rewarded_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_rewarded_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_gateways().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_described_gateways(
|
||||
&self,
|
||||
) -> Result<Vec<LegacyDescribedGateway>, ValidatorClientError> {
|
||||
@@ -511,6 +571,7 @@ impl NymApiClient {
|
||||
Ok(bonds)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_gateway_core_status_count(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
@@ -522,6 +583,7 @@ impl NymApiClient {
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_mixnode_core_status_count(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -533,6 +595,7 @@ impl NymApiClient {
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_mixnode_status(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -540,6 +603,7 @@ impl NymApiClient {
|
||||
Ok(self.nym_api.get_mixnode_status(mix_id).await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_mixnode_reward_estimation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -547,6 +611,7 @@ impl NymApiClient {
|
||||
Ok(self.nym_api.get_mixnode_reward_estimation(mix_id).await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_mixnode_stake_saturation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -578,6 +643,7 @@ impl NymApiClient {
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn spent_credentials_filter(
|
||||
&self,
|
||||
) -> Result<SpentCredentialsResponse, ValidatorClientError> {
|
||||
|
||||
@@ -121,36 +121,36 @@ async fn test_nyxd_connection(
|
||||
{
|
||||
Ok(Err(NyxdError::TendermintErrorRpc(e))) => {
|
||||
// If we get a tendermint-rpc error, we classify the node as not contactable
|
||||
log::warn!("Checking: nyxd url: {url}: {}: {}", "failed".red(), e);
|
||||
tracing::warn!("Checking: nyxd url: {url}: {}: {}", "failed".red(), e);
|
||||
false
|
||||
}
|
||||
Ok(Err(NyxdError::AbciError { code, log, .. })) => {
|
||||
// We accept the mixnet contract not found as ok from a connection standpoint. This happens
|
||||
// for example on a pre-launch network.
|
||||
log::debug!(
|
||||
tracing::debug!(
|
||||
"Checking: nyxd url: {url}: {}, but with abci error: {code}: {log}",
|
||||
"success".green()
|
||||
);
|
||||
code == 18
|
||||
}
|
||||
Ok(Err(error @ NyxdError::NoContractAddressAvailable(_))) => {
|
||||
log::warn!("Checking: nyxd url: {url}: {}: {error}", "failed".red());
|
||||
tracing::warn!("Checking: nyxd url: {url}: {}: {error}", "failed".red());
|
||||
false
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
// For any other error, we're optimistic and just try anyway.
|
||||
log::warn!(
|
||||
tracing::warn!(
|
||||
"Checking: nyxd_url: {url}: {}, but with error: {e}",
|
||||
"success".green()
|
||||
);
|
||||
true
|
||||
}
|
||||
Ok(Ok(_)) => {
|
||||
log::debug!("Checking: nyxd_url: {url}: {}", "success".green());
|
||||
tracing::debug!("Checking: nyxd_url: {url}: {}", "success".green());
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Checking: nyxd_url: {url}: {}: {e}", "failed".red());
|
||||
tracing::warn!("Checking: nyxd_url: {url}: {}: {e}", "failed".red());
|
||||
false
|
||||
}
|
||||
};
|
||||
@@ -164,20 +164,20 @@ async fn test_nym_api_connection(
|
||||
) -> ConnectionResult {
|
||||
let result = match timeout(
|
||||
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
|
||||
client.get_cached_mixnodes(),
|
||||
client.health(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(_)) => {
|
||||
log::debug!("Checking: api_url: {url}: {}", "success".green());
|
||||
tracing::debug!("Checking: api_url: {url}: {}", "success".green());
|
||||
true
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
|
||||
tracing::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
|
||||
tracing::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11,7 +11,8 @@ use nym_api_requests::ecash::models::{
|
||||
};
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
use nym_api_requests::models::{
|
||||
AnnotationResponse, LegacyDescribedMixNode, NodePerformanceResponse, NymNodeDescription,
|
||||
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
||||
NymNodeDescription,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
|
||||
use nym_api_requests::pagination::PaginatedResponse;
|
||||
@@ -42,6 +43,7 @@ use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails};
|
||||
use time::format_description::BorrowedFormatItem;
|
||||
use time::Date;
|
||||
use tracing::instrument;
|
||||
|
||||
pub mod error;
|
||||
pub mod routes;
|
||||
@@ -53,11 +55,27 @@ pub fn rfc_3339_date() -> Vec<BorrowedFormatItem<'static>> {
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait NymApiClientExt: ApiClient {
|
||||
async fn health(&self) -> Result<ApiHealthResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::API_STATUS_ROUTES,
|
||||
routes::HEALTH,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
@@ -71,6 +89,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways_detailed(&self) -> Result<Vec<GatewayBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
@@ -84,6 +104,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_detailed_unfiltered(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
@@ -99,11 +121,15 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
|
||||
self.get_json(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways_described(&self) -> Result<Vec<LegacyDescribedGateway>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::GATEWAYS, routes::DESCRIBED],
|
||||
@@ -112,6 +138,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_described(&self) -> Result<Vec<LegacyDescribedMixNode>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::MIXNODES, routes::DESCRIBED],
|
||||
@@ -120,6 +148,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_nodes_described(
|
||||
&self,
|
||||
page: Option<u32>,
|
||||
@@ -139,6 +168,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_nym_nodes(
|
||||
&self,
|
||||
page: Option<u32>,
|
||||
@@ -158,6 +188,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_basic_mixnodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
@@ -181,6 +213,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_gateways(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
@@ -206,6 +240,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
/// retrieve basic information for nodes are capable of operating as an entry gateway
|
||||
/// this includes legacy gateways and nym-nodes
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_entry_assigned_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
@@ -247,6 +282,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
/// retrieve basic information for nodes that got assigned 'mixing' node in this epoch
|
||||
/// this includes legacy mixnodes and nym-nodes
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_active_mixing_assigned_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
@@ -286,6 +322,49 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
/// retrieve basic information for nodes that got assigned 'mixing' node in this epoch
|
||||
/// this includes legacy mixnodes and nym-nodes
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_mixing_capable_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if let Some(arg) = &semver_compatibility {
|
||||
params.push(("semver_compatibility", arg.clone()))
|
||||
}
|
||||
|
||||
if no_legacy {
|
||||
params.push(("no_legacy", "true".to_string()))
|
||||
}
|
||||
|
||||
if let Some(page) = page {
|
||||
params.push(("page", page.to_string()))
|
||||
}
|
||||
|
||||
if let Some(per_page) = per_page {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"unstable",
|
||||
"nym-nodes",
|
||||
"skimmed",
|
||||
"mixnodes",
|
||||
"all",
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
|
||||
async fn get_basic_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
@@ -318,6 +397,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
|
||||
@@ -326,6 +407,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_active_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
@@ -340,6 +423,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::MIXNODES, routes::REWARDED],
|
||||
@@ -348,6 +433,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_report(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -365,6 +452,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateway_report(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
@@ -382,6 +471,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_history(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -399,6 +490,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateway_history(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
@@ -416,6 +509,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_rewarded_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
@@ -432,6 +527,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateway_core_status_count(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
@@ -463,6 +560,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_core_status_count(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -495,6 +594,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_status(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -512,6 +613,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_reward_estimation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -529,6 +632,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn compute_mixnode_reward_estimation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -548,6 +653,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_stake_saturation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -565,6 +672,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_inclusion_probability(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
@@ -582,22 +691,40 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_current_node_performance(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
) -> Result<NodePerformanceResponse, NymAPIError> {
|
||||
self.get_json_from(format!("/v1/nym-nodes/performance/{node_id}"))
|
||||
.await
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"nym-nodes",
|
||||
"performance",
|
||||
&node_id.to_string(),
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_node_annotation(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
) -> Result<AnnotationResponse, NymAPIError> {
|
||||
self.get_json_from(format!("/v1/nym-nodes/annotation/{node_id}"))
|
||||
.await
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"nym-nodes",
|
||||
"annotation",
|
||||
&node_id.to_string(),
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
async fn get_mixnode_avg_uptime(&self, mix_id: NodeId) -> Result<UptimeResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
@@ -612,6 +739,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_blacklisted(&self) -> Result<Vec<NodeId>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::MIXNODES, routes::BLACKLISTED],
|
||||
@@ -620,6 +749,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways_blacklisted(&self) -> Result<Vec<IdentityKey>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::GATEWAYS, routes::BLACKLISTED],
|
||||
@@ -628,6 +759,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, request_body))]
|
||||
async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
@@ -644,6 +776,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, request_body))]
|
||||
async fn verify_ecash_ticket(
|
||||
&self,
|
||||
request_body: &VerifyEcashTicketBody,
|
||||
@@ -660,6 +793,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, request_body))]
|
||||
async fn batch_redeem_ecash_tickets(
|
||||
&self,
|
||||
request_body: &BatchRedeemTicketsBody,
|
||||
@@ -676,6 +810,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn double_spending_filter_v1(&self) -> Result<SpentCredentialsResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
@@ -688,6 +824,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn partial_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
@@ -711,6 +848,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn partial_coin_indices_signatures(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
@@ -731,6 +869,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn global_expiration_date_signatures(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
@@ -754,6 +893,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn global_coin_indices_signatures(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
@@ -774,6 +914,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn master_verification_key(
|
||||
&self,
|
||||
epoch_id: Option<EpochId>,
|
||||
@@ -793,6 +934,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn epoch_credentials(
|
||||
&self,
|
||||
dkg_epoch: EpochId,
|
||||
@@ -809,6 +951,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn issued_credential(
|
||||
&self,
|
||||
credential_id: i64,
|
||||
@@ -825,6 +968,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn issued_credentials(
|
||||
&self,
|
||||
credential_ids: Vec<i64>,
|
||||
|
||||
@@ -36,6 +36,8 @@ pub mod ecash {
|
||||
}
|
||||
|
||||
pub const STATUS_ROUTES: &str = "status";
|
||||
pub const API_STATUS_ROUTES: &str = "api-status";
|
||||
pub const HEALTH: &str = "health";
|
||||
pub const MIXNODE: &str = "mixnode";
|
||||
pub const GATEWAY: &str = "gateway";
|
||||
pub const NYM_NODES: &str = "nym-nodes";
|
||||
|
||||
@@ -8,9 +8,9 @@ use crate::nyxd::CosmWasmClient;
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::AccountId;
|
||||
use cosmwasm_std::Addr;
|
||||
use log::trace;
|
||||
use nym_coconut_dkg_common::types::{ChunkIndex, NodeIndex, StateAdvanceResponse};
|
||||
use serde::Deserialize;
|
||||
use tracing::trace;
|
||||
|
||||
use nym_coconut_dkg_common::dealer::RegisteredDealerDetails;
|
||||
pub use nym_coconut_dkg_common::{
|
||||
|
||||
@@ -10,10 +10,10 @@ use cosmrs::AccountId;
|
||||
use nym_contracts_common::signing::Nonce;
|
||||
use nym_mixnet_contract_common::gateway::{PreassignedGatewayIdsResponse, PreassignedId};
|
||||
use nym_mixnet_contract_common::nym_node::{
|
||||
EpochAssignmentResponse, NodeDetailsByIdentityResponse, NodeOwnershipResponse,
|
||||
NodeRewardingDetailsResponse, PagedNymNodeBondsResponse, PagedNymNodeDetailsResponse,
|
||||
PagedUnbondedNymNodesResponse, Role, RolesMetadataResponse, StakeSaturationResponse,
|
||||
UnbondedNodeResponse, UnbondedNymNode,
|
||||
EpochAssignmentResponse, NodeDetailsByIdentityResponse, NodeDetailsResponse,
|
||||
NodeOwnershipResponse, NodeRewardingDetailsResponse, PagedNymNodeBondsResponse,
|
||||
PagedNymNodeDetailsResponse, PagedUnbondedNymNodesResponse, Role, RolesMetadataResponse,
|
||||
StakeSaturationResponse, UnbondedNodeResponse, UnbondedNymNode,
|
||||
};
|
||||
use nym_mixnet_contract_common::reward_params::WorkFactor;
|
||||
use nym_mixnet_contract_common::{
|
||||
@@ -316,10 +316,7 @@ pub trait MixnetQueryClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_nymnode_details(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
) -> Result<NodeOwnershipResponse, NyxdError> {
|
||||
async fn get_nymnode_details(&self, node_id: NodeId) -> Result<NodeDetailsResponse, NyxdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetNymNodeDetails { node_id })
|
||||
.await
|
||||
}
|
||||
|
||||
+3
-4
@@ -29,7 +29,6 @@ use cosmrs::proto::cosmwasm::wasm::v1::{
|
||||
};
|
||||
use cosmrs::tendermint::{block, chain, Hash};
|
||||
use cosmrs::{AccountId, Coin as CosmosCoin, Tx};
|
||||
use log::trace;
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -68,7 +67,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
Res: Message + Default,
|
||||
{
|
||||
if let Some(ref abci_path) = path {
|
||||
trace!("performing query on abci path {abci_path}")
|
||||
tracing::trace!("performing query on abci path {abci_path}")
|
||||
}
|
||||
let mut buf = Vec::with_capacity(req.encoded_len());
|
||||
req.encode(&mut buf)?;
|
||||
@@ -297,7 +296,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
|
||||
let start = Instant::now();
|
||||
loop {
|
||||
log::debug!(
|
||||
tracing::debug!(
|
||||
"Polling for result of including {} in a block...",
|
||||
broadcasted.hash
|
||||
);
|
||||
@@ -522,7 +521,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
.make_abci_query::<_, QuerySmartContractStateResponse>(path, req)
|
||||
.await?;
|
||||
|
||||
trace!("raw query response: {}", String::from_utf8_lossy(&res.data));
|
||||
tracing::trace!("raw query response: {}", String::from_utf8_lossy(&res.data));
|
||||
Ok(serde_json::from_slice(&res.data)?)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -25,12 +25,12 @@ use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
|
||||
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
|
||||
use cosmrs::tx::{self, Msg};
|
||||
use cosmrs::{cosmwasm, AccountId, Any, Tx};
|
||||
use log::debug;
|
||||
use serde::Serialize;
|
||||
use sha2::Digest;
|
||||
use sha2::Sha256;
|
||||
use std::time::SystemTime;
|
||||
use tendermint_rpc::endpoint::broadcast;
|
||||
use tracing::debug;
|
||||
|
||||
fn empty_fee() -> tx::Fee {
|
||||
tx::Fee {
|
||||
|
||||
@@ -7,9 +7,9 @@ use base64::Engine;
|
||||
use cosmrs::abci::TxMsgData;
|
||||
use cosmrs::cosmwasm::MsgExecuteContractResponse;
|
||||
use cosmrs::proto::cosmos::base::query::v1beta1::{PageRequest, PageResponse};
|
||||
use log::error;
|
||||
use prost::bytes::Bytes;
|
||||
use tendermint_rpc::endpoint::broadcast;
|
||||
use tracing::error;
|
||||
|
||||
pub use cosmrs::abci::MsgResponse;
|
||||
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::QueryClientWithNyxd;
|
||||
use crate::utils::{pretty_cosmwasm_coin, show_error};
|
||||
use crate::utils::show_error;
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -15,12 +14,11 @@ pub struct Args {
|
||||
}
|
||||
|
||||
pub async fn query(args: Args, client: &QueryClientWithNyxd) {
|
||||
match client.nym_api.get_gateways().await {
|
||||
match client.get_all_cached_described_nodes().await {
|
||||
Ok(res) => match args.identity_key {
|
||||
Some(identity_key) => {
|
||||
let node = res.iter().find(|node| {
|
||||
node.gateway
|
||||
.identity_key
|
||||
node.ed25519_identity_key()
|
||||
.to_string()
|
||||
.eq_ignore_ascii_case(&identity_key)
|
||||
});
|
||||
@@ -32,14 +30,16 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) {
|
||||
None => {
|
||||
let mut table = Table::new();
|
||||
|
||||
table.set_header(vec!["Identity Key", "Owner", "Host", "Bond", "Version"]);
|
||||
for node in res {
|
||||
table.set_header(vec!["Node Id", "Identity Key", "Version", "Is Legacy"]);
|
||||
for node in res
|
||||
.into_iter()
|
||||
.filter(|node| node.description.declared_role.entry)
|
||||
{
|
||||
table.add_row(vec![
|
||||
node.gateway.identity_key.to_string(),
|
||||
node.owner.to_string(),
|
||||
node.gateway.host.to_string(),
|
||||
pretty_cosmwasm_coin(&node.pledge_amount),
|
||||
node.gateway.version.clone(),
|
||||
node.node_id.to_string(),
|
||||
node.ed25519_identity_key().to_base58_string(),
|
||||
node.description.build_information.build_version,
|
||||
(!node.contract_node_type.is_nym_node()).to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::QueryClientWithNyxd;
|
||||
use crate::utils::{pretty_decimal_with_denom, show_error};
|
||||
use crate::utils::show_error;
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -15,13 +14,11 @@ pub struct Args {
|
||||
}
|
||||
|
||||
pub async fn query(args: Args, client: &QueryClientWithNyxd) {
|
||||
match client.nym_api.get_mixnodes().await {
|
||||
match client.get_all_cached_described_nodes().await {
|
||||
Ok(res) => match args.identity_key {
|
||||
Some(identity_key) => {
|
||||
let node = res.iter().find(|node| {
|
||||
node.bond_information
|
||||
.mix_node
|
||||
.identity_key
|
||||
node.ed25519_identity_key()
|
||||
.to_string()
|
||||
.eq_ignore_ascii_case(&identity_key)
|
||||
});
|
||||
@@ -33,25 +30,16 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) {
|
||||
None => {
|
||||
let mut table = Table::new();
|
||||
|
||||
table.set_header(vec![
|
||||
"Mix id",
|
||||
"Identity Key",
|
||||
"Owner",
|
||||
"Host",
|
||||
"Bond",
|
||||
"Total Delegations",
|
||||
"Version",
|
||||
]);
|
||||
for node in res {
|
||||
let denom = &node.bond_information.original_pledge().denom;
|
||||
table.set_header(vec!["Node Id", "Identity Key", "Version", "Is Legacy"]);
|
||||
for node in res
|
||||
.into_iter()
|
||||
.filter(|node| node.description.declared_role.mixnode)
|
||||
{
|
||||
table.add_row(vec![
|
||||
node.mix_id().to_string(),
|
||||
node.bond_information.mix_node.identity_key.clone(),
|
||||
node.bond_information.owner.clone().into_string(),
|
||||
node.bond_information.mix_node.host.clone(),
|
||||
pretty_decimal_with_denom(node.rewarding_details.operator, denom),
|
||||
pretty_decimal_with_denom(node.rewarding_details.delegates, denom),
|
||||
node.bond_information.mix_node.version,
|
||||
node.node_id.to_string(),
|
||||
node.ed25519_identity_key().to_base58_string(),
|
||||
node.description.build_information.build_version,
|
||||
(!node.contract_node_type.is_nym_node()).to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||
use time::{Date, OffsetDateTime};
|
||||
use tracing::*;
|
||||
|
||||
use nym_credentials::ecash::utils::{ecash_today, EcashTime};
|
||||
use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime};
|
||||
use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType};
|
||||
use nym_gateway_requests::models::CredentialSpendingRequest;
|
||||
use nym_gateway_storage::Storage;
|
||||
@@ -131,7 +131,7 @@ impl<S: Storage + Clone + 'static> CredentialVerifier<S> {
|
||||
let bandwidth = Bandwidth::ticket_amount(credential_type.into());
|
||||
|
||||
self.bandwidth_storage_manager
|
||||
.increase_bandwidth(bandwidth, spend_date)
|
||||
.increase_bandwidth(bandwidth, cred_exp_date())
|
||||
.await?;
|
||||
|
||||
Ok(self
|
||||
|
||||
@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use tracing::warn;
|
||||
use tracing::{instrument, warn};
|
||||
use url::Url;
|
||||
|
||||
pub use reqwest::IntoUrl;
|
||||
@@ -205,6 +205,7 @@ impl Client {
|
||||
self.reqwest_client.get(url)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, fields(path=?path))]
|
||||
async fn send_get_request<K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
@@ -215,6 +216,7 @@ impl Client {
|
||||
V: AsRef<str>,
|
||||
E: Display,
|
||||
{
|
||||
tracing::trace!("Sending GET request");
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -280,6 +282,7 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn get_json<T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
@@ -312,6 +315,7 @@ impl Client {
|
||||
parse_response(res, true).await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn get_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
@@ -515,12 +519,14 @@ pub fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
|
||||
url
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
E: DeserializeOwned + Display,
|
||||
{
|
||||
let status = res.status();
|
||||
tracing::debug!("Status: {} (success: {})", &status, status.is_success());
|
||||
|
||||
if !allow_empty {
|
||||
if let Some(0) = res.content_length() {
|
||||
@@ -529,11 +535,19 @@ where
|
||||
}
|
||||
|
||||
if res.status().is_success() {
|
||||
let text = res.text().await?;
|
||||
match serde_json::from_str(&text) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(source) => Err(HttpClientError::ResponseDeserialisationFailure { source }),
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let text = res.text().await.inspect_err(|err| {
|
||||
tracing::error!("Couldn't even get response text: {err}");
|
||||
})?;
|
||||
tracing::trace!("Result:\n{:#?}", text);
|
||||
|
||||
serde_json::from_str(&text)
|
||||
.map_err(|err| HttpClientError::GenericRequestFailure(err.to_string()))
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
Ok(res.json().await?)
|
||||
} else if res.status() == StatusCode::NOT_FOUND {
|
||||
Err(HttpClientError::NotFound)
|
||||
} else {
|
||||
|
||||
@@ -14,7 +14,6 @@ use nym_task::TaskClient;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::task::JoinHandle;
|
||||
@@ -313,7 +312,7 @@ impl VerlocMeasurer {
|
||||
info!("Starting verloc measurements");
|
||||
// TODO: should we also measure gateways?
|
||||
|
||||
let all_mixes = match self.validator_client.get_cached_mixnodes().await {
|
||||
let all_mixes = match self.validator_client.get_all_described_nodes().await {
|
||||
Ok(nodes) => nodes,
|
||||
Err(err) => {
|
||||
error!(
|
||||
@@ -332,22 +331,14 @@ impl VerlocMeasurer {
|
||||
// we only care about address and identity
|
||||
let tested_nodes = all_mixes
|
||||
.into_iter()
|
||||
.filter(|n| n.description.declared_role.mixnode)
|
||||
.filter_map(|node| {
|
||||
let mix_node = node.bond_information.mix_node;
|
||||
// check if the node has sufficient version to be able to understand the packets
|
||||
let node_version = parse_version(&mix_node.version).ok()?;
|
||||
if node_version < self.config.minimum_compatible_node_version {
|
||||
return None;
|
||||
}
|
||||
|
||||
// try to parse the identity and host
|
||||
let node_identity =
|
||||
identity::PublicKey::from_base58_string(mix_node.identity_key).ok()?;
|
||||
let node_identity = node.ed25519_identity_key();
|
||||
|
||||
let verloc_host = (&*mix_node.host, mix_node.verloc_port)
|
||||
.to_socket_addrs()
|
||||
.ok()?
|
||||
.next()?;
|
||||
let ip = node.description.host_information.ip_address.first()?;
|
||||
let verloc_port = node.description.verloc_port();
|
||||
let verloc_host = SocketAddr::new(*ip, verloc_port);
|
||||
|
||||
// TODO: possible problem in the future, this does name resolution and theoretically
|
||||
// if a lot of nodes maliciously mis-configured themselves, it might take a while to resolve them all
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "nym-common-models"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
@@ -0,0 +1 @@
|
||||
pub mod ns_api;
|
||||
@@ -0,0 +1,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct TestrunAssignment {
|
||||
pub testrun_id: i64,
|
||||
pub gateway_identity_key: String,
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use crate::deprecated::DelegationEvent;
|
||||
use crate::error::TypesError;
|
||||
use crate::mixnode::NodeCostParams;
|
||||
use cosmwasm_std::Decimal;
|
||||
use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, NodeId};
|
||||
use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, NodeId, NodeRewarding};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -70,6 +70,14 @@ pub struct DelegationWithEverything {
|
||||
pub mixnode_is_unbonding: Option<bool>,
|
||||
}
|
||||
|
||||
pub struct NodeInformation {
|
||||
pub owner: String,
|
||||
pub mix_id: NodeId,
|
||||
pub node_identity: String,
|
||||
pub rewarding_details: NodeRewarding,
|
||||
pub is_unbonding: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
|
||||
@@ -99,12 +99,25 @@ pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>
|
||||
let peers = all_peers
|
||||
.into_iter()
|
||||
.map(Peer::try_from)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.map(|mut peer| {
|
||||
// since WGApi doesn't set those values on init, let's set them to 0
|
||||
peer.rx_bytes = 0;
|
||||
peer.tx_bytes = 0;
|
||||
peer
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for peer in peers.iter() {
|
||||
let bandwidth_manager =
|
||||
PeerController::generate_bandwidth_manager(storage.clone(), &peer.public_key)
|
||||
.await?
|
||||
.map(|bw_m| Arc::new(RwLock::new(bw_m)));
|
||||
// Update storage with *x_bytes set to 0, as in kernel peers we can't set those values
|
||||
// so we need to restart counting. Hopefully the bandwidth was counted in available_bandwidth
|
||||
storage
|
||||
.insert_wireguard_peer(peer, bandwidth_manager.is_some())
|
||||
.await?;
|
||||
peer_bandwidth_managers.insert(peer.public_key.clone(), bandwidth_manager);
|
||||
}
|
||||
wg_api.create_interface()?;
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::peer_controller::PeerControlRequest;
|
||||
use defguard_wireguard_rs::host::Peer;
|
||||
use defguard_wireguard_rs::{host::Host, key::Key};
|
||||
use futures::channel::oneshot;
|
||||
use nym_authenticator_requests::v2::registration::BANDWIDTH_CAP_PER_DAY;
|
||||
use nym_authenticator_requests::latest::registration::BANDWIDTH_CAP_PER_DAY;
|
||||
use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager;
|
||||
use nym_gateway_storage::models::WireguardPeer;
|
||||
use nym_gateway_storage::Storage;
|
||||
@@ -18,7 +18,7 @@ use tokio::sync::{mpsc, RwLock};
|
||||
use tokio_stream::{wrappers::IntervalStream, StreamExt};
|
||||
|
||||
pub(crate) type SharedBandwidthStorageManager<St> = Arc<RwLock<BandwidthStorageManager<St>>>;
|
||||
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24); // 24 hours
|
||||
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24 * 30); // 30 days
|
||||
|
||||
pub struct PeerHandle<St> {
|
||||
storage: St,
|
||||
@@ -75,8 +75,8 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
|
||||
async fn active_peer(
|
||||
&mut self,
|
||||
storage_peer: WireguardPeer,
|
||||
kernel_peer: Peer,
|
||||
storage_peer: &WireguardPeer,
|
||||
kernel_peer: &Peer,
|
||||
) -> Result<bool, Error> {
|
||||
if let Some(bandwidth_manager) = &self.bandwidth_storage_manager {
|
||||
let spent_bandwidth = (kernel_peer.rx_bytes + kernel_peer.tx_bytes)
|
||||
@@ -98,7 +98,7 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
} else {
|
||||
if SystemTime::now().duration_since(self.startup_timestamp)? >= AUTO_REMOVE_AFTER {
|
||||
log::debug!(
|
||||
"Peer {} has been present for 24 hours, removing it",
|
||||
"Peer {} has been present for 30 days, removing it",
|
||||
self.public_key.to_string()
|
||||
);
|
||||
let success = self.remove_peer().await?;
|
||||
@@ -136,9 +136,12 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
log::debug!("Peer {:?} not in storage anymore, shutting down handle", self.public_key);
|
||||
return Ok(());
|
||||
};
|
||||
if !self.active_peer(storage_peer, kernel_peer).await? {
|
||||
if !self.active_peer(&storage_peer, &kernel_peer).await? {
|
||||
log::debug!("Peer {:?} doesn't have bandwidth anymore, shutting down handle", self.public_key);
|
||||
return Ok(());
|
||||
} else {
|
||||
// Update storage values
|
||||
self.storage.insert_wireguard_peer(&kernel_peer, self.bandwidth_storage_manager.is_some()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -19,5 +19,5 @@ MULTISIG_CONTRACT_ADDRESS=n1zwv6feuzhy6a9wekh96cd57lsarmqlwxdypdsplw6zhfncqw6ftq
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=n1aakfpghcanxtc45gpqlx8j3rq0zcpyf49qmhm9mdjrfx036h4z5sy2vfh9
|
||||
|
||||
EXPLORER_API=https://canary-explorer.performance.nymte.ch/api
|
||||
NYXD="https://canary-validator.performance.nymte.ch"
|
||||
NYM_API="https://canary-api.performance.nymte.ch/api"
|
||||
NYXD=https://canary-validator.performance.nymte.ch
|
||||
NYM_API=https://canary-api.performance.nymte.ch/api
|
||||
|
||||
+3
-3
@@ -21,8 +21,8 @@ COCONUT_DKG_CONTRACT_ADDRESS=n19604yflqggs9mk2z26mqygq43q2kr3n932egxx630svywd5mp
|
||||
|
||||
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
|
||||
NYXD="https://rpc.nymtech.net"
|
||||
NYM_API="https://validator.nymtech.net/api/"
|
||||
NYXD=https://rpc.nymtech.net
|
||||
NYM_API=https://validator.nymtech.net/api/
|
||||
NYXD_WS="wss://rpc.nymtech.net/websocket"
|
||||
EXPLORER_API="https://explorer.nymtech.net/api/"
|
||||
EXPLORER_API=https://explorer.nymtech.net/api/
|
||||
NYM_VPN_API="https://nymvpn.com/api"
|
||||
|
||||
+2
-2
@@ -19,5 +19,5 @@ VESTING_CONTRACT_ADDRESS=n1jlzdxnyces4hrhqz68dqk28mrw5jgwtcfq0c2funcwrmw0dx9l9s8
|
||||
REWARDING_VALIDATOR_ADDRESS=n1rfvpsynktze6wvn6ldskj8xgwfzzk5v6pnff39
|
||||
|
||||
EXPLORER_API=https://qa-network-explorer.qa.nymte.ch/api
|
||||
NYXD="https://qa-validator.qa.nymte.ch"
|
||||
NYM_API="https://qa-nym-api.qa.nymte.ch/api"
|
||||
NYXD=https://qa-validator.qa.nymte.ch
|
||||
NYM_API=https://qa-nym-api.qa.nymte.ch/api
|
||||
|
||||
+3
-3
@@ -20,6 +20,6 @@ ECASH_CONTRACT_ADDRESS=n1v3vydvs2ued84yv3khqwtgldmgwn0elljsdh08dr5s2j9x4rc5fs9jl
|
||||
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0"
|
||||
EXPLORER_API=https://sandbox-explorer.nymtech.net/api
|
||||
NYXD="https://rpc.sandbox.nymtech.net"
|
||||
NYXD_WS="wss://rpc.sandbox.nymtech.net/websocket"
|
||||
NYM_API="https://sandbox-nym-api1.nymtech.net/api"
|
||||
NYXD=https://rpc.sandbox.nymtech.net
|
||||
NYXD_WS=wss://rpc.sandbox.nymtech.net/websocket
|
||||
NYM_API=https://sandbox-nym-api1.nymtech.net/api
|
||||
|
||||
@@ -7,12 +7,12 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log.workspace = true
|
||||
nym-explorer-api-requests = { path = "../explorer-api-requests" }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
serde.workspace = true
|
||||
thiserror.workspace = true
|
||||
url.workspace = true
|
||||
tracing = {workspace = true, features = ["attributes"]}
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::time::Duration;
|
||||
|
||||
use reqwest::StatusCode;
|
||||
use thiserror::Error;
|
||||
use tracing::instrument;
|
||||
use url::Url;
|
||||
|
||||
// Re-export request types
|
||||
@@ -49,27 +50,28 @@ impl ExplorerClient {
|
||||
Ok(Self { client, url })
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn new(url: url::Url) -> Result<Self, ExplorerApiError> {
|
||||
let client = reqwest::Client::builder().build()?;
|
||||
Ok(Self { client, url })
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn new_with_timeout(url: url::Url, timeout: Duration) -> Result<Self, ExplorerApiError> {
|
||||
let client = reqwest::Client::builder().timeout(timeout).build()?;
|
||||
Ok(Self { client, url })
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn new(url: url::Url) -> Result<Self, ExplorerApiError> {
|
||||
let client = reqwest::Client::builder().build()?;
|
||||
Ok(Self { client, url })
|
||||
}
|
||||
|
||||
async fn send_get_request(
|
||||
&self,
|
||||
paths: &[&str],
|
||||
) -> Result<reqwest::Response, ExplorerApiError> {
|
||||
let url = combine_url(self.url.clone(), paths)?;
|
||||
log::trace!("Sending GET request {url:?}");
|
||||
tracing::debug!("Sending GET request");
|
||||
Ok(self.client.get(url).send().await?)
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip_all, fields(paths=?paths))]
|
||||
async fn query_explorer_api<T>(&self, paths: &[&str]) -> Result<T, ExplorerApiError>
|
||||
where
|
||||
T: std::fmt::Debug,
|
||||
@@ -78,12 +80,14 @@ impl ExplorerClient {
|
||||
let response = self.send_get_request(paths).await?;
|
||||
if response.status().is_success() {
|
||||
let res = response.json::<T>().await?;
|
||||
log::trace!("Got response: {res:?}");
|
||||
tracing::trace!("Got response: {res:?}");
|
||||
Ok(res)
|
||||
} else if response.status() == StatusCode::NOT_FOUND {
|
||||
Err(ExplorerApiError::NotFound)
|
||||
} else {
|
||||
Err(ExplorerApiError::RequestFailure(response.text().await?))
|
||||
let status = response.status();
|
||||
let err_msg = format!("{}: {}", response.text().await?, status);
|
||||
Err(ExplorerApiError::RequestFailure(err_msg))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ use nym_contracts_common::truncate_decimal;
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
|
||||
// use deprecated method as hopefully this whole API will be sunset soon-enough...
|
||||
// and we're only getting info for legacy node so the relevant data should still exist
|
||||
#[allow(deprecated)]
|
||||
pub(crate) async fn retrieve_mixnode_econ_stats(
|
||||
client: &ThreadsafeValidatorClient,
|
||||
mix_id: NodeId,
|
||||
|
||||
@@ -17,6 +17,8 @@ pub(crate) struct ExplorerApiTasks {
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
// allow usage of deprecated methods here as we actually want to be explicitly querying for legacy data
|
||||
#[allow(deprecated)]
|
||||
impl ExplorerApiTasks {
|
||||
pub(crate) fn new(state: ExplorerApiStateContext, shutdown: TaskClient) -> Self {
|
||||
ExplorerApiTasks { state, shutdown }
|
||||
|
||||
@@ -622,7 +622,7 @@ impl<St> Gateway<St> {
|
||||
// TODO: if anything, this should be getting data directly from the contract
|
||||
// as opposed to the validator API
|
||||
let validator_client = self.random_api_client()?;
|
||||
let existing_nodes = match validator_client.get_cached_gateways().await {
|
||||
let existing_nodes = match validator_client.get_all_basic_nodes(None).await {
|
||||
Ok(nodes) => nodes,
|
||||
Err(err) => {
|
||||
error!("failed to grab initial network gateways - {err}\n Please try to startup again in few minutes");
|
||||
@@ -630,9 +630,9 @@ impl<St> Gateway<St> {
|
||||
}
|
||||
};
|
||||
|
||||
Ok(existing_nodes.iter().any(|node| {
|
||||
node.gateway.identity_key == self.identity_keypair.public_key().to_base58_string()
|
||||
}))
|
||||
Ok(existing_nodes
|
||||
.iter()
|
||||
.any(|node| &node.ed25519_identity_pubkey == self.identity_keypair.public_key()))
|
||||
}
|
||||
|
||||
pub async fn run(mut self) -> Result<(), GatewayError>
|
||||
|
||||
@@ -234,7 +234,7 @@ impl MixNode {
|
||||
// TODO: if anything, this should be getting data directly from the contract
|
||||
// as opposed to the validator API
|
||||
let validator_client = self.random_api_client();
|
||||
let existing_nodes = match validator_client.get_cached_mixnodes().await {
|
||||
let existing_nodes = match validator_client.get_all_basic_nodes(None).await {
|
||||
Ok(nodes) => nodes,
|
||||
Err(err) => {
|
||||
error!(
|
||||
@@ -245,10 +245,9 @@ impl MixNode {
|
||||
}
|
||||
};
|
||||
|
||||
existing_nodes.iter().any(|node| {
|
||||
node.bond_information.mix_node.identity_key
|
||||
== self.identity_keypair.public_key().to_base58_string()
|
||||
})
|
||||
existing_nodes
|
||||
.iter()
|
||||
.any(|node| &node.ed25519_identity_pubkey == self.identity_keypair.public_key())
|
||||
}
|
||||
|
||||
async fn wait_for_interrupt(&self, shutdown: TaskHandle) {
|
||||
|
||||
@@ -897,6 +897,12 @@ pub enum DescribedNodeType {
|
||||
NymNode,
|
||||
}
|
||||
|
||||
impl DescribedNodeType {
|
||||
pub fn is_nym_node(&self) -> bool {
|
||||
matches!(self, DescribedNodeType::NymNode)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
|
||||
Generated
+34
-5
@@ -214,6 +214,17 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-client-ip"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9eefda7e2b27e1bda4d6fa8a06b50803b8793769045918bc37ad062d48a6efac"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"forwarded-header-value",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.5"
|
||||
@@ -1311,6 +1322,16 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
|
||||
|
||||
[[package]]
|
||||
name = "forwarded-header-value"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"
|
||||
dependencies = [
|
||||
"nonempty",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
@@ -2147,6 +2168,12 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nonempty"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
@@ -2315,7 +2342,7 @@ dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ff",
|
||||
"group",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.13.0",
|
||||
"nym-network-defaults",
|
||||
"nym-pemstore",
|
||||
"rand",
|
||||
@@ -2355,7 +2382,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-credential-proxy"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -2540,6 +2567,7 @@ name = "nym-http-api-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum-client-ip",
|
||||
"bytes",
|
||||
"colored",
|
||||
"mime",
|
||||
@@ -2663,7 +2691,6 @@ dependencies = [
|
||||
"flate2",
|
||||
"futures",
|
||||
"itertools 0.13.0",
|
||||
"log",
|
||||
"nym-api-requests",
|
||||
"nym-coconut-bandwidth-contract-common",
|
||||
"nym-coconut-dkg-common",
|
||||
@@ -2687,6 +2714,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"wasmtimer",
|
||||
"zeroize",
|
||||
@@ -3763,9 +3791,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.128"
|
||||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -4889,6 +4917,7 @@ dependencies = [
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 2.0.79",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -10,7 +10,7 @@ use schemars::schema::Schema;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use time::Date;
|
||||
use time::{Date, OffsetDateTime};
|
||||
|
||||
#[cfg(feature = "query-types")]
|
||||
use nym_http_api_common::Output;
|
||||
@@ -236,22 +236,28 @@ pub struct TicketbookWalletSharesAsyncResponse {
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlindedWalletSharesResponse {
|
||||
pub struct WebhookTicketbookWalletShares {
|
||||
pub id: i64,
|
||||
pub status: String,
|
||||
pub device_id: String,
|
||||
pub credential_id: String,
|
||||
pub data: Option<TicketbookWalletSharesResponse>,
|
||||
pub error_message: Option<String>,
|
||||
pub created: String,
|
||||
pub updated: String,
|
||||
|
||||
#[schemars(with = "String")]
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub created: OffsetDateTime,
|
||||
|
||||
#[schemars(with = "String")]
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub updated: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WebhookBlindedSharesResponse {
|
||||
pub blinded_shares: BlindedWalletSharesResponse,
|
||||
pub struct WebhookTicketbookWalletSharesRequest {
|
||||
pub ticketbook_wallet_shares: WebhookTicketbookWalletShares,
|
||||
pub secret: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-credential-proxy"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
|
||||
use crate::error::VpnApiError;
|
||||
use crate::http::state::ApiState;
|
||||
use crate::storage::models::BlindedShares;
|
||||
use futures::{stream, StreamExt};
|
||||
use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
TicketbookAsyncRequest, TicketbookObtainQueryParams, TicketbookRequest,
|
||||
TicketbookWalletSharesResponse, WalletShare,
|
||||
TicketbookWalletSharesResponse, WalletShare, WebhookTicketbookWalletShares,
|
||||
WebhookTicketbookWalletSharesRequest,
|
||||
};
|
||||
use nym_credentials::IssuanceTicketBook;
|
||||
use nym_credentials_interface::Base58;
|
||||
@@ -217,11 +219,13 @@ async fn try_obtain_blinded_ticketbook_async_inner(
|
||||
requested_on: OffsetDateTime,
|
||||
request_data: TicketbookAsyncRequest,
|
||||
params: TicketbookObtainQueryParams,
|
||||
pending: &BlindedShares,
|
||||
) -> Result<(), VpnApiError> {
|
||||
let epoch_id = state.current_epoch_id().await?;
|
||||
|
||||
let device_id = &request_data.device_id;
|
||||
let credential_id = &request_data.credential_id;
|
||||
let secret = request_data.secret.clone();
|
||||
|
||||
// 1. try to obtain global data
|
||||
let (
|
||||
@@ -259,19 +263,70 @@ async fn try_obtain_blinded_ticketbook_async_inner(
|
||||
error!(uuid = %request, "failed to update db with issued information: {err}")
|
||||
}
|
||||
|
||||
// 4. build the response
|
||||
let response = TicketbookWalletSharesResponse {
|
||||
// 4. build the webhook request body
|
||||
let data = Some(TicketbookWalletSharesResponse {
|
||||
epoch_id,
|
||||
shares,
|
||||
master_verification_key,
|
||||
aggregated_coin_index_signatures,
|
||||
aggregated_expiration_date_signatures,
|
||||
});
|
||||
|
||||
let ticketbook_wallet_shares = WebhookTicketbookWalletShares {
|
||||
id: pending.id,
|
||||
status: pending.status.to_string(),
|
||||
device_id: device_id.clone(),
|
||||
credential_id: credential_id.clone(),
|
||||
data,
|
||||
error_message: None,
|
||||
created: pending.created,
|
||||
updated: pending.updated,
|
||||
};
|
||||
|
||||
let webhook_request = WebhookTicketbookWalletSharesRequest {
|
||||
ticketbook_wallet_shares,
|
||||
secret,
|
||||
};
|
||||
|
||||
// 5. call the webhook
|
||||
state
|
||||
.zk_nym_web_hook()
|
||||
.try_trigger(request, &response)
|
||||
.try_trigger(request, &webhook_request)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_trigger_webhook_request_for_error(
|
||||
state: &ApiState,
|
||||
request: Uuid,
|
||||
request_data: TicketbookAsyncRequest,
|
||||
pending: &BlindedShares,
|
||||
error_message: String,
|
||||
) -> Result<(), VpnApiError> {
|
||||
let device_id = &request_data.device_id;
|
||||
let credential_id = &request_data.credential_id;
|
||||
let secret = request_data.secret.clone();
|
||||
|
||||
let ticketbook_wallet_shares = WebhookTicketbookWalletShares {
|
||||
id: pending.id,
|
||||
status: "error".to_string(),
|
||||
device_id: device_id.clone(),
|
||||
credential_id: credential_id.clone(),
|
||||
data: None,
|
||||
error_message: Some(error_message),
|
||||
created: pending.created,
|
||||
updated: pending.updated,
|
||||
};
|
||||
|
||||
let webhook_request = WebhookTicketbookWalletSharesRequest {
|
||||
ticketbook_wallet_shares,
|
||||
secret,
|
||||
};
|
||||
|
||||
state
|
||||
.zk_nym_web_hook()
|
||||
.try_trigger(request, &webhook_request)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
@@ -285,16 +340,30 @@ pub(crate) async fn try_obtain_blinded_ticketbook_async(
|
||||
requested_on: OffsetDateTime,
|
||||
request_data: TicketbookAsyncRequest,
|
||||
params: TicketbookObtainQueryParams,
|
||||
pending: BlindedShares,
|
||||
) {
|
||||
if let Err(err) = try_obtain_blinded_ticketbook_async_inner(
|
||||
&state,
|
||||
request,
|
||||
requested_on,
|
||||
request_data,
|
||||
request_data.clone(),
|
||||
params,
|
||||
&pending,
|
||||
)
|
||||
.await
|
||||
{
|
||||
// post to the webhook to notify of errors on this side
|
||||
if let Err(webhook_err) = try_trigger_webhook_request_for_error(
|
||||
&state,
|
||||
request,
|
||||
request_data,
|
||||
&pending,
|
||||
format!("Failed to get ticketbook: {err}"),
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!(uuid = %request, "failed to make webhook request to report error: {webhook_err}")
|
||||
}
|
||||
error!(uuid = %request, "failed to resolve the blinded ticketbook issuance: {err}")
|
||||
} else {
|
||||
info!(uuid = %request, "managed to resolve the blinded ticketbook issuance")
|
||||
|
||||
@@ -84,8 +84,8 @@ pub(crate) struct ApiDoc;
|
||||
api_requests::v1::ticketbook::models::WalletShare,
|
||||
api_requests::v1::ticketbook::models::TicketbookWalletSharesResponse,
|
||||
api_requests::v1::ticketbook::models::TicketbookWalletSharesAsyncResponse,
|
||||
api_requests::v1::ticketbook::models::BlindedWalletSharesResponse,
|
||||
api_requests::v1::ticketbook::models::WebhookBlindedSharesResponse,
|
||||
api_requests::v1::ticketbook::models::WebhookTicketbookWalletShares,
|
||||
api_requests::v1::ticketbook::models::WebhookTicketbookWalletSharesRequest,
|
||||
api_requests::v1::ticketbook::models::TicketbookObtainQueryParams,
|
||||
api_requests::v1::ticketbook::models::SharesQueryParams,
|
||||
api_requests::v1::ticketbook::models::PlaceholderJsonSchemaImpl,
|
||||
|
||||
@@ -192,6 +192,7 @@ pub(crate) async fn obtain_ticketbook_shares_async(
|
||||
}
|
||||
Ok(pending) => pending,
|
||||
};
|
||||
let id = pending.id;
|
||||
|
||||
// 3. try to spawn a new task attempting to resolve the request
|
||||
if state
|
||||
@@ -201,6 +202,7 @@ pub(crate) async fn obtain_ticketbook_shares_async(
|
||||
requested_on,
|
||||
payload,
|
||||
params,
|
||||
pending,
|
||||
))
|
||||
.is_none()
|
||||
{
|
||||
@@ -213,10 +215,7 @@ pub(crate) async fn obtain_ticketbook_shares_async(
|
||||
}
|
||||
|
||||
// 4. in the meantime, return the id to the user
|
||||
Ok(output.to_response(TicketbookWalletSharesAsyncResponse {
|
||||
id: pending.id,
|
||||
uuid,
|
||||
}))
|
||||
Ok(output.to_response(TicketbookWalletSharesAsyncResponse { id, uuid }))
|
||||
}
|
||||
|
||||
/// Obtain the current value of the bandwidth voucher deposit
|
||||
|
||||
@@ -68,6 +68,7 @@ warning: no queries found; do you have the `offline` feature enabled
|
||||
### Possible solutions
|
||||
|
||||
- does your `sqlx-cli` version match `sqlx` version from `Cargo.toml`?
|
||||
+ `cargo install -f sqlx-cli --version <specific version>`
|
||||
```
|
||||
cargo install sqlx-cli --version <exact semver version as sqlx> --force
|
||||
```
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
nym-gateway-probe
|
||||
@@ -0,0 +1,27 @@
|
||||
# Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
|
||||
[package]
|
||||
name = "nym-node-status-agent"
|
||||
version = "0.1.5"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true}
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
nym-bin-common = { path = "../common/bin-common", features = ["models"]}
|
||||
nym-common-models = { path = "../common/models" }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "process"] }
|
||||
tokio-util = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
serde_json = { workspace = true }
|
||||
@@ -0,0 +1,28 @@
|
||||
FROM rust:latest AS builder
|
||||
|
||||
RUN apt update && apt install -yy libdbus-1-dev pkg-config libclang-dev
|
||||
|
||||
# Install go
|
||||
RUN wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz -O go.tar.gz
|
||||
RUN tar -xzvf go.tar.gz -C /usr/local
|
||||
|
||||
RUN git clone https://github.com/nymtech/nym-vpn-client /usr/src/nym-vpn-client
|
||||
ENV PATH=/go/bin:/usr/local/go/bin:$PATH
|
||||
WORKDIR /usr/src/nym-vpn-client/nym-vpn-core
|
||||
RUN cargo build --release --package nym-gateway-probe
|
||||
|
||||
COPY ./ /usr/src/nym
|
||||
WORKDIR /usr/src/nym/nym-node-status-agent
|
||||
RUN cargo build --release
|
||||
|
||||
FROM ubuntu:24.04
|
||||
|
||||
RUN apt-get update && apt-get install -y ca-certificates
|
||||
|
||||
WORKDIR /nym
|
||||
|
||||
COPY --from=builder /usr/src/nym/target/release/nym-node-status-agent ./
|
||||
COPY --from=builder /usr/src/nym-vpn-client/nym-vpn-core/target/release/nym-gateway-probe ./
|
||||
|
||||
ENV NODE_STATUS_AGENT_PROBE_PATH=/nym/nym-gateway-probe
|
||||
ENTRYPOINT [ "/nym/nym-node-status-agent" ]
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
environment="qa"
|
||||
|
||||
source ../envs/${environment}.env
|
||||
|
||||
export RUST_LOG="debug"
|
||||
|
||||
crate_root=$(dirname $(realpath "$0"))
|
||||
gateway_probe_src=$(dirname $(dirname "$crate_root"))/nym-vpn-client/nym-vpn-core
|
||||
echo "gateway_probe_src=$gateway_probe_src"
|
||||
echo "crate_root=$crate_root"
|
||||
|
||||
export NODE_STATUS_AGENT_PROBE_PATH="$crate_root/nym-gateway-probe"
|
||||
|
||||
# build & copy over GW probe
|
||||
function copy_gw_probe() {
|
||||
pushd $gateway_probe_src
|
||||
git switch main
|
||||
git pull
|
||||
cargo build --release --package nym-gateway-probe
|
||||
cp target/release/nym-gateway-probe "$crate_root"
|
||||
$crate_root/nym-gateway-probe --version
|
||||
popd
|
||||
}
|
||||
|
||||
function build_agent() {
|
||||
cargo build --package nym-node-status-agent --release
|
||||
}
|
||||
|
||||
function swarm() {
|
||||
local workers=$1
|
||||
echo "Running $workers in parallel"
|
||||
|
||||
build_agent
|
||||
|
||||
for ((i = 1; i <= $workers; i++)); do
|
||||
../target/release/nym-node-status-agent run-probe &
|
||||
done
|
||||
|
||||
wait
|
||||
|
||||
echo "All agents completed"
|
||||
}
|
||||
|
||||
export NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1"
|
||||
export NODE_STATUS_AGENT_SERVER_PORT="8000"
|
||||
|
||||
copy_gw_probe
|
||||
|
||||
swarm 8
|
||||
|
||||
# cargo run -- run-probe
|
||||
@@ -0,0 +1,118 @@
|
||||
use anyhow::bail;
|
||||
use clap::{Parser, Subcommand};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_common_models::ns_api::TestrunAssignment;
|
||||
use std::sync::OnceLock;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::probe::GwProbe;
|
||||
|
||||
// Helper for passing LONG_VERSION to clap
|
||||
fn pretty_build_info_static() -> &'static str {
|
||||
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
|
||||
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
|
||||
pub(crate) struct Args {
|
||||
#[command(subcommand)]
|
||||
pub(crate) command: Command,
|
||||
#[arg(short, long, env = "NODE_STATUS_AGENT_SERVER_ADDRESS")]
|
||||
pub(crate) server_address: String,
|
||||
|
||||
#[arg(short = 'p', long, env = "NODE_STATUS_AGENT_SERVER_PORT")]
|
||||
pub(crate) server_port: u16,
|
||||
// TODO dz accept keypair for identification / auth
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub(crate) enum Command {
|
||||
RunProbe {
|
||||
/// path of binary to run
|
||||
#[arg(long, env = "NODE_STATUS_AGENT_PROBE_PATH")]
|
||||
probe_path: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Args {
|
||||
pub(crate) async fn execute(&self) -> anyhow::Result<()> {
|
||||
match &self.command {
|
||||
Command::RunProbe { probe_path } => self.run_probe(probe_path).await?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_probe(&self, probe_path: &str) -> anyhow::Result<()> {
|
||||
let server_address = format!("{}:{}", &self.server_address, self.server_port);
|
||||
|
||||
let probe = GwProbe::new(probe_path.to_string());
|
||||
|
||||
let version = probe.version().await;
|
||||
tracing::info!("Probe version:\n{}", version);
|
||||
|
||||
if let Some(testrun) = request_testrun(&server_address).await? {
|
||||
let log = probe.run_and_get_log(&Some(testrun.gateway_identity_key));
|
||||
|
||||
submit_results(&server_address, testrun.testrun_id, log).await?;
|
||||
} else {
|
||||
tracing::info!("No testruns available, exiting")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const URL_BASE: &str = "internal/testruns";
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
async fn request_testrun(server_addr: &str) -> anyhow::Result<Option<TestrunAssignment>> {
|
||||
let target_url = format!("{}/{}", server_addr, URL_BASE);
|
||||
let client = reqwest::Client::new();
|
||||
let res = client.get(target_url).send().await?;
|
||||
let status = res.status();
|
||||
let response_text = res.text().await?;
|
||||
|
||||
if status.is_client_error() {
|
||||
bail!("{}: {}", status, response_text);
|
||||
} else if status.is_server_error() {
|
||||
if matches!(status, reqwest::StatusCode::SERVICE_UNAVAILABLE)
|
||||
&& response_text.contains("No testruns available")
|
||||
{
|
||||
return Ok(None);
|
||||
} else {
|
||||
bail!("{}: {}", status, response_text);
|
||||
}
|
||||
}
|
||||
|
||||
serde_json::from_str(&response_text)
|
||||
.map(|testrun| {
|
||||
tracing::info!("Received testrun assignment: {:?}", testrun);
|
||||
testrun
|
||||
})
|
||||
.map_err(|err| {
|
||||
tracing::error!("err");
|
||||
err.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(probe_outcome))]
|
||||
async fn submit_results(
|
||||
server_addr: &str,
|
||||
testrun_id: i64,
|
||||
probe_outcome: String,
|
||||
) -> anyhow::Result<()> {
|
||||
let target_url = format!("{}/{}/{}", server_addr, URL_BASE, testrun_id);
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let res = client
|
||||
.post(target_url)
|
||||
.body(probe_outcome)
|
||||
.send()
|
||||
.await
|
||||
.and_then(|response| response.error_for_status())?;
|
||||
|
||||
tracing::debug!("Submitted results: {})", res.status());
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
use crate::cli::Args;
|
||||
use clap::Parser;
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing_subscriber::{filter::Directive, EnvFilter};
|
||||
|
||||
mod cli;
|
||||
mod probe;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
setup_tracing();
|
||||
let args = Args::parse();
|
||||
|
||||
let server_addr = format!("{}:{}", args.server_address, args.server_port);
|
||||
test_ns_api_conn(&server_addr).await?;
|
||||
|
||||
args.execute().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn test_ns_api_conn(server_addr: &str) -> anyhow::Result<()> {
|
||||
reqwest::get(server_addr)
|
||||
.await
|
||||
.map(|res| {
|
||||
tracing::info!(
|
||||
"Testing connection to NS API at {server_addr}: {}",
|
||||
res.status()
|
||||
);
|
||||
})
|
||||
.map_err(|err| anyhow::anyhow!("Couldn't connect to server on {}: {}", server_addr, err))
|
||||
}
|
||||
|
||||
pub(crate) fn setup_tracing() {
|
||||
fn directive_checked(directive: impl Into<String>) -> Directive {
|
||||
directive
|
||||
.into()
|
||||
.parse()
|
||||
.expect("Failed to parse log directive")
|
||||
}
|
||||
|
||||
let log_builder = tracing_subscriber::fmt()
|
||||
// Use a more compact, abbreviated log format
|
||||
.compact()
|
||||
// Display source code file paths
|
||||
.with_file(true)
|
||||
// Display source code line numbers
|
||||
.with_line_number(true)
|
||||
.with_thread_ids(true)
|
||||
// Don't display the event's target (module path)
|
||||
.with_target(false);
|
||||
|
||||
let mut filter = EnvFilter::builder()
|
||||
// if RUST_LOG isn't set, set default level
|
||||
.with_default_directive(LevelFilter::INFO.into())
|
||||
.from_env_lossy();
|
||||
// these crates are more granularly filtered
|
||||
let filter_crates = [
|
||||
"reqwest",
|
||||
"rustls",
|
||||
"hyper",
|
||||
"sqlx",
|
||||
"h2",
|
||||
"tendermint_rpc",
|
||||
"tower_http",
|
||||
"axum",
|
||||
];
|
||||
for crate_name in filter_crates {
|
||||
filter = filter.add_directive(directive_checked(format!("{}=warn", crate_name)));
|
||||
}
|
||||
|
||||
filter = filter.add_directive(directive_checked("nym_bin_common=debug"));
|
||||
filter = filter.add_directive(directive_checked("nym_explorer_client=debug"));
|
||||
filter = filter.add_directive(directive_checked("nym_network_defaults=debug"));
|
||||
filter = filter.add_directive(directive_checked("nym_validator_client=debug"));
|
||||
|
||||
log_builder.with_env_filter(filter).init();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use tracing::error;
|
||||
|
||||
pub(crate) struct GwProbe {
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl GwProbe {
|
||||
pub(crate) fn new(probe_path: String) -> Self {
|
||||
Self { path: probe_path }
|
||||
}
|
||||
|
||||
pub(crate) async fn version(&self) -> String {
|
||||
let mut command = tokio::process::Command::new(&self.path);
|
||||
command.stdout(std::process::Stdio::piped());
|
||||
command.arg("--version");
|
||||
|
||||
match command.spawn() {
|
||||
Ok(child) => {
|
||||
if let Ok(output) = child.wait_with_output().await {
|
||||
return String::from_utf8(output.stdout)
|
||||
.unwrap_or("Unable to get log from test run".to_string());
|
||||
}
|
||||
"Unable to get probe version".to_string()
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to get probe version: {}", e);
|
||||
"Failed to get probe version".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_and_get_log(&self, gateway_key: &Option<String>) -> String {
|
||||
let mut command = std::process::Command::new(&self.path);
|
||||
command.stdout(std::process::Stdio::piped());
|
||||
|
||||
if let Some(gateway_id) = gateway_key {
|
||||
command.arg("--gateway").arg(gateway_id);
|
||||
}
|
||||
|
||||
match command.spawn() {
|
||||
Ok(child) => {
|
||||
if let Ok(output) = child.wait_with_output() {
|
||||
if !output.status.success() {
|
||||
let out = String::from_utf8_lossy(&output.stdout);
|
||||
let err = String::from_utf8_lossy(&output.stderr);
|
||||
tracing::error!("Probe exited with {:?}:\n{}\n{}", output.status, out, err);
|
||||
}
|
||||
|
||||
return String::from_utf8(output.stdout)
|
||||
.unwrap_or("Unable to get log from test run".to_string());
|
||||
}
|
||||
"Unable to get log from test run".to_string()
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to spawn test: {}", e);
|
||||
"Failed to spawn test run task".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
data/
|
||||
enter_db.sh
|
||||
nym-gateway-probe
|
||||
nym-node-status-api
|
||||
*.sqlite
|
||||
*.sqlite-journal
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n gateway_identity_key\n FROM\n gateways\n WHERE\n id = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "gateway_identity_key",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984"
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n key as \"key!\",\n value_json as \"value_json!\",\n last_updated_utc as \"last_updated_utc!\"\n FROM summary",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "key!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "value_json!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "last_updated_utc!",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "UPDATE mixnodes\n SET bonded = ?, last_updated_utc = ?\n WHERE id = ?;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "18abc8fde56cf86baed7b4afa38f2c63cdf90f2f3b6d81afb9000bb0968dcaea"
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n id,\n gateway_identity_key\n FROM gateways\n WHERE id = ?\n LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "gateway_identity_key",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "INSERT INTO testruns (gateway_id, status, ip_address, timestamp_utc, log) VALUES (?, ?, ?, ?, ?)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 5
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3c584e211d07c511644c8079187965acf3bcfb3f84ba8d24ed645d79976cf784"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "UPDATE gateways\n SET bonded = ?, last_updated_utc = ?\n WHERE id = ?;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3d3a1fa429e3090741c6b6a8e82e692afc04b51e8782bcbf59f1eb4116112536"
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n id as \"id!\",\n gateway_identity_key as \"gateway_identity_key!\",\n self_described as \"self_described?\",\n explorer_pretty_bond as \"explorer_pretty_bond?\"\n FROM gateways\n WHERE gateway_identity_key = ?\n ORDER BY gateway_identity_key\n LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "gateway_identity_key!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "self_described?",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "explorer_pretty_bond?",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "3d5fc502f976f5081f01352856b8632c29c81bfafb043bb8744129cf9e0266ad"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "UPDATE testruns SET status = ? WHERE id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c"
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n id as \"id!\",\n gateway_id as \"gateway_id!\",\n status as \"status!\",\n timestamp_utc as \"timestamp_utc!\",\n ip_address as \"ip_address!\",\n log as \"log!\"\n FROM testruns\n WHERE gateway_id = ? AND status != 2\n ORDER BY id DESC\n LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "gateway_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "status!",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "timestamp_utc!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "ip_address!",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "log!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "46d76bc6d3fba2dae3b21511a36289dd776749dd7a20cda61b0480f2fba60889"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "UPDATE gateways SET last_probe_log = ? WHERE id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08"
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n id as \"id!\",\n gateway_identity_key as \"identity_key!\",\n bonded as \"bonded: bool\"\n FROM gateways\n WHERE bonded = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "identity_key!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "bonded: bool",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4b61a4bc32333c92a8f5ad4ad0017b40dc01845f554b5479f37855d89b309e6f"
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT count(id) FROM mixnodes",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "count(id)",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b"
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n id as \"id!\",\n gateway_id as \"gateway_id!\",\n status as \"status!\",\n timestamp_utc as \"timestamp_utc!\",\n ip_address as \"ip_address!\",\n log as \"log!\"\n FROM testruns\n WHERE\n id = ?\n AND\n status = ?\n ORDER BY timestamp_utc",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "gateway_id!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "status!",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "timestamp_utc!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "ip_address!",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "log!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6d7967b831b355d5f2c77950abc56f816956b0824c66a25da611dce688105d36"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "INSERT INTO mixnodes\n (mix_id, identity_key, bonded, total_stake,\n host, http_api_port, blacklisted, full_details,\n self_described, last_updated_utc, is_dp_delegatee)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(mix_id) DO UPDATE SET\n bonded=excluded.bonded,\n total_stake=excluded.total_stake, host=excluded.host,\n http_api_port=excluded.http_api_port,blacklisted=excluded.blacklisted,\n full_details=excluded.full_details,self_described=excluded.self_described,\n last_updated_utc=excluded.last_updated_utc,\n is_dp_delegatee = excluded.is_dp_delegatee;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 11
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6eb1a682cf13205cf701590021cdf795147ac3724e89df5b2f24f7215d87dce1"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "UPDATE gateways SET last_probe_result = ? WHERE id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f"
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n gw.gateway_identity_key as \"gateway_identity_key!\",\n gw.bonded as \"bonded: bool\",\n gw.blacklisted as \"blacklisted: bool\",\n gw.performance as \"performance!\",\n gw.self_described as \"self_described?\",\n gw.explorer_pretty_bond as \"explorer_pretty_bond?\",\n gw.last_probe_result as \"last_probe_result?\",\n gw.last_probe_log as \"last_probe_log?\",\n gw.last_testrun_utc as \"last_testrun_utc?\",\n gw.last_updated_utc as \"last_updated_utc!\",\n COALESCE(gd.moniker, \"NA\") as \"moniker!\",\n COALESCE(gd.website, \"NA\") as \"website!\",\n COALESCE(gd.security_contact, \"NA\") as \"security_contact!\",\n COALESCE(gd.details, \"NA\") as \"details!\"\n FROM gateways gw\n LEFT JOIN gateway_description gd\n ON gw.gateway_identity_key = gd.gateway_identity_key\n ORDER BY gw.gateway_identity_key",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "gateway_identity_key!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "bonded: bool",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "blacklisted: bool",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "performance!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "self_described?",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "explorer_pretty_bond?",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "last_probe_result?",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "last_probe_log?",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "last_testrun_utc?",
|
||||
"ordinal": 8,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "last_updated_utc!",
|
||||
"ordinal": 9,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "moniker!",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "website!",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "security_contact!",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "details!",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "71a455c705f9c25d3843ff2fb8629d1320a5eb10797cdb5a435455d22c6aeac1"
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n id as \"id!\",\n date as \"date!\",\n timestamp_utc as \"timestamp_utc!\",\n value_json as \"value_json!\"\n FROM summary_history\n ORDER BY date DESC\n LIMIT 30",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "date!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "timestamp_utc!",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "value_json!",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "INSERT INTO summary_history\n (date, timestamp_utc, value_json)\n VALUES (?, ?, ?)\n ON CONFLICT(date) DO UPDATE SET\n timestamp_utc=excluded.timestamp_utc,\n value_json=excluded.value_json;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "UPDATE gateways\n SET blacklisted = true\n WHERE gateway_identity_key = ?;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8571faad2f66e08f24acfbfe036d17ca6eb090df7f6d52ef89c5d51564f8b45c"
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT count(id) FROM gateways",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "count(id)",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5"
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n gateway_identity_key as \"gateway_identity_key!\",\n bonded as \"bonded: bool\"\n FROM gateways\n ORDER BY last_testrun_utc",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "gateway_identity_key!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "bonded: bool",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "UPDATE gateways SET last_testrun_utc = ?, last_updated_utc = ? WHERE id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c"
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n date_utc as \"date_utc!\",\n packets_received as \"total_packets_received!: i64\",\n packets_sent as \"total_packets_sent!: i64\",\n packets_dropped as \"total_packets_dropped!: i64\",\n total_stake as \"total_stake!: i64\"\n FROM (\n SELECT\n date_utc,\n SUM(packets_received) as packets_received,\n SUM(packets_sent) as packets_sent,\n SUM(packets_dropped) as packets_dropped,\n SUM(total_stake) as total_stake\n FROM mixnode_daily_stats\n GROUP BY date_utc\n ORDER BY date_utc DESC\n LIMIT 30\n )\n GROUP BY date_utc\n ORDER BY date_utc\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "date_utc!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "total_packets_received!: i64",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "total_packets_sent!: i64",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "total_packets_dropped!: i64",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "total_stake!: i64",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c5e3cd7284b334df5aa979b1627ea1f6dc2aed00cedde25f2be3567e47064351"
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n id as \"id!\",\n identity_key as \"identity_key!\",\n bonded as \"bonded: bool\"\n FROM mixnodes\n WHERE bonded = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "identity_key!",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "bonded: bool",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c7ba2621becb9ac4b5dee0ce303dadfcf19095935867a51cbd5b8362d1505fcc"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "INSERT INTO gateways\n (gateway_identity_key, bonded, blacklisted,\n self_described, explorer_pretty_bond,\n last_updated_utc, performance)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(gateway_identity_key) DO UPDATE SET\n bonded=excluded.bonded,\n blacklisted=excluded.blacklisted,\n self_described=excluded.self_described,\n explorer_pretty_bond=excluded.explorer_pretty_bond,\n last_updated_utc=excluded.last_updated_utc,\n performance = excluded.performance;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 7
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d8ea93e781666e6267902170709ee2aa37f6163525bbdce1a4cebef4a285f8d9"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "INSERT INTO summary\n (key, value_json, last_updated_utc)\n VALUES (?, ?, ?)\n ON CONFLICT(key) DO UPDATE SET\n value_json=excluded.value_json,\n last_updated_utc=excluded.last_updated_utc;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe"
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT\n mn.mix_id as \"mix_id!\",\n mn.bonded as \"bonded: bool\",\n mn.blacklisted as \"blacklisted: bool\",\n mn.is_dp_delegatee as \"is_dp_delegatee: bool\",\n mn.total_stake as \"total_stake!\",\n mn.full_details as \"full_details!\",\n mn.self_described as \"self_described\",\n mn.last_updated_utc as \"last_updated_utc!\",\n COALESCE(md.moniker, \"NA\") as \"moniker!\",\n COALESCE(md.website, \"NA\") as \"website!\",\n COALESCE(md.security_contact, \"NA\") as \"security_contact!\",\n COALESCE(md.details, \"NA\") as \"details!\"\n FROM mixnodes mn\n LEFT JOIN mixnode_description md ON mn.mix_id = md.mix_id\n ORDER BY mn.mix_id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "mix_id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "bonded: bool",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "blacklisted: bool",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "is_dp_delegatee: bool",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "total_stake!",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "full_details!",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "self_described",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "last_updated_utc!",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "moniker!",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "website!",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "security_contact!",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "details!",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f0a4316081d1be9444a87b95d933d31cb4bcc4071d31d8d2f7755e2d2c2e3e35"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "UPDATE\n testruns\n SET\n status = ?\n WHERE\n status = ?\n AND\n timestamp_utc < ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f5048d9926a5f5329f7f3b96d43b925e033ceec4f8112258feb4ac9e96fc5924"
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "UPDATE testruns\n SET status = ?\n WHERE rowid =\n (\n SELECT rowid\n FROM testruns\n WHERE status = ?\n ORDER BY timestamp_utc asc\n LIMIT 1\n )\n RETURNING\n id as \"id!\",\n gateway_id\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id!",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "gateway_id",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ff9334ba7b670b218b2f9100e9ab5d2f2d08b2e53203aab9f07ea9b52acbd407"
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
# Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
[package]
|
||||
name = "nym-node-status-api"
|
||||
version = "0.1.6"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
axum = { workspace = true, features = ["tokio", "macros"] }
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true, features = ["cargo", "derive", "env", "string"] }
|
||||
cosmwasm-std = { workspace = true }
|
||||
envy = { workspace = true }
|
||||
futures-util = { workspace = true }
|
||||
moka = { workspace = true, features = ["future"] }
|
||||
nym-bin-common = { path = "../common/bin-common", features = ["models"]}
|
||||
nym-common-models = { path = "../common/models" }
|
||||
nym-explorer-client = { path = "../explorer-api/explorer-client" }
|
||||
nym-network-defaults = { path = "../common/network-defaults" }
|
||||
nym-validator-client = { path = "../common/client-libs/validator-client" }
|
||||
nym-task = { path = "../common/task" }
|
||||
nym-node-requests = { path = "../nym-node/nym-node-requests", features = ["openapi"] }
|
||||
regex = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
serde_json_path = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite"] }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread"] }
|
||||
tokio-util = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
tracing-log = { workspace = true }
|
||||
tower-http = { workspace = true, features = ["cors", "trace"] }
|
||||
utoipa = { workspace = true, features = ["axum_extras", "time"] }
|
||||
utoipa-swagger-ui = { workspace = true, features = ["axum"] }
|
||||
# TODO dz `cargo update async-trait`
|
||||
# for automatic schema detection, which was merged, but not released yet
|
||||
# https://github.com/ProbablyClem/utoipauto/pull/38
|
||||
# utoipauto = { git = "https://github.com/ProbablyClem/utoipauto", rev = "eb04cba" }
|
||||
utoipauto = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
] }
|
||||
@@ -0,0 +1,15 @@
|
||||
FROM rust:latest AS builder
|
||||
|
||||
COPY ./ /usr/src/nym
|
||||
WORKDIR /usr/src/nym/nym-node-status-api
|
||||
|
||||
RUN cargo build --release
|
||||
|
||||
FROM ubuntu:24.04
|
||||
|
||||
RUN apt-get update && apt-get install -y ca-certificates
|
||||
|
||||
WORKDIR /nym
|
||||
|
||||
COPY --from=builder /usr/src/nym/target/release/nym-node-status-api ./
|
||||
ENTRYPOINT [ "/nym/nym-node-status-api" ]
|
||||
@@ -0,0 +1,8 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
RUN apt-get update && apt-get install -y ca-certificates
|
||||
|
||||
WORKDIR /nym
|
||||
|
||||
COPY nym-node-status-api/nym-node-status-api ./
|
||||
ENTRYPOINT [ "/nym/nym-node-status-api" ]
|
||||
@@ -0,0 +1,45 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::fs::Permissions;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use tokio::{fs::File, io::AsyncWriteExt};
|
||||
|
||||
const SQLITE_DB_FILENAME: &str = "nym-node-status-api.sqlite";
|
||||
|
||||
/// If you need to re-run migrations or reset the db, just run
|
||||
/// cargo clean -p nym-node-status-api
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<()> {
|
||||
let out_dir = read_env_var("OUT_DIR")?;
|
||||
let database_path = format!("{}/{}?mode=rwc", out_dir, SQLITE_DB_FILENAME);
|
||||
|
||||
write_db_path_to_file(&out_dir, SQLITE_DB_FILENAME).await?;
|
||||
let mut conn = SqliteConnection::connect(&database_path).await?;
|
||||
sqlx::migrate!("./migrations").run(&mut conn).await?;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
println!("cargo::rustc-env=DATABASE_URL=sqlite://{}", &database_path);
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
// for some strange reason we need to add a leading `/` to the windows path even though it's
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo::rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_env_var(var: &str) -> Result<String> {
|
||||
std::env::var(var).map_err(|_| anyhow!("You need to set {} env var", var))
|
||||
}
|
||||
|
||||
/// use `./enter_db.sh` to inspect DB
|
||||
async fn write_db_path_to_file(out_dir: &str, db_filename: &str) -> anyhow::Result<()> {
|
||||
let mut file = File::create("enter_db.sh").await?;
|
||||
let _ = file.write(b"#!/bin/bash\n").await?;
|
||||
file.write_all(format!("sqlite3 {}/{}", out_dir, db_filename).as_bytes())
|
||||
.await?;
|
||||
|
||||
file.set_permissions(Permissions::from_mode(0o755))
|
||||
.await
|
||||
.map_err(From::from)
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
export RUST_LOG=${RUST_LOG:-debug}
|
||||
|
||||
export NYM_API_CLIENT_TIMEOUT=60
|
||||
export EXPLORER_CLIENT_TIMEOUT=60
|
||||
export NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL=60
|
||||
|
||||
export ENVIRONMENT="qa.env"
|
||||
|
||||
function run_bare() {
|
||||
# export necessary env vars
|
||||
set -a
|
||||
source ../envs/$ENVIRONMENT
|
||||
set +a
|
||||
export RUST_LOG=debug
|
||||
|
||||
# --conection-url is provided in build.rs
|
||||
cargo run --package nym-node-status-api
|
||||
}
|
||||
|
||||
function run_docker() {
|
||||
cargo build --package nym-node-status-api --release
|
||||
cp ../target/release/nym-node-status-api .
|
||||
|
||||
cd ..
|
||||
docker build -t node-status-api -f nym-node-status-api/Dockerfile.dev .
|
||||
docker run --env-file envs/${ENVIRONMENT} \
|
||||
-e EXPLORER_CLIENT_TIMEOUT=$EXPLORER_CLIENT_TIMEOUT \
|
||||
-e NYM_API_CLIENT_TIMEOUT=$NYM_API_CLIENT_TIMEOUT \
|
||||
-e DATABASE_URL="sqlite://node-status-api.sqlite?mode=rwc" \
|
||||
-e RUST_LOG=${RUST_LOG} node-status-api
|
||||
|
||||
}
|
||||
|
||||
run_bare
|
||||
|
||||
# run_docker
|
||||
@@ -0,0 +1,112 @@
|
||||
CREATE TABLE gateways
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
gateway_identity_key VARCHAR NOT NULL UNIQUE,
|
||||
self_described VARCHAR NOT NULL,
|
||||
explorer_pretty_bond VARCHAR,
|
||||
last_probe_result VARCHAR,
|
||||
last_probe_log VARCHAR,
|
||||
config_score INTEGER NOT NULL DEFAULT (0),
|
||||
config_score_successes REAL NOT NULL DEFAULT (0),
|
||||
config_score_samples REAL NOT NULL DEFAULT (0),
|
||||
routing_score INTEGER NOT NULL DEFAULT (0),
|
||||
routing_score_successes REAL NOT NULL DEFAULT (0),
|
||||
routing_score_samples REAL NOT NULL DEFAULT (0),
|
||||
test_run_samples REAL NOT NULL DEFAULT (0),
|
||||
last_testrun_utc INTEGER,
|
||||
last_updated_utc INTEGER NOT NULL,
|
||||
bonded INTEGER CHECK (bonded in (0, 1)) NOT NULL DEFAULT 0,
|
||||
blacklisted INTEGER CHECK (bonded in (0, 1)) NOT NULL DEFAULT 0,
|
||||
performance INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX idx_gateway_description_gateway_identity_key ON gateways (gateway_identity_key);
|
||||
|
||||
|
||||
CREATE TABLE mixnodes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
identity_key VARCHAR NOT NULL UNIQUE,
|
||||
mix_id INTEGER NOT NULL UNIQUE,
|
||||
bonded INTEGER CHECK (bonded in (0, 1)) NOT NULL DEFAULT 0,
|
||||
total_stake INTEGER NOT NULL,
|
||||
host VARCHAR NOT NULL,
|
||||
http_api_port INTEGER NOT NULL,
|
||||
blacklisted INTEGER CHECK (blacklisted in (0, 1)) NOT NULL DEFAULT 0,
|
||||
full_details VARCHAR,
|
||||
self_described VARCHAR,
|
||||
last_updated_utc INTEGER NOT NULL
|
||||
, is_dp_delegatee INTEGER CHECK (is_dp_delegatee IN (0, 1)) NOT NULL DEFAULT 0);
|
||||
CREATE INDEX idx_mixnodes_mix_id ON mixnodes (mix_id);
|
||||
CREATE INDEX idx_mixnodes_identity_key ON mixnodes (identity_key);
|
||||
|
||||
CREATE TABLE
|
||||
mixnode_description (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
mix_id INTEGER UNIQUE NOT NULL,
|
||||
moniker VARCHAR,
|
||||
website VARCHAR,
|
||||
security_contact VARCHAR,
|
||||
details VARCHAR,
|
||||
last_updated_utc INTEGER NOT NULL,
|
||||
FOREIGN KEY (mix_id) REFERENCES mixnodes (mix_id)
|
||||
);
|
||||
|
||||
-- Indexes for description table
|
||||
CREATE INDEX idx_mixnode_description_mix_id ON mixnode_description (mix_id);
|
||||
|
||||
|
||||
CREATE TABLE summary
|
||||
(
|
||||
key VARCHAR PRIMARY KEY,
|
||||
value_json VARCHAR,
|
||||
last_updated_utc INTEGER NOT NULL
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE summary_history
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
date VARCHAR UNIQUE NOT NULL,
|
||||
timestamp_utc INTEGER NOT NULL,
|
||||
value_json VARCHAR
|
||||
);
|
||||
CREATE INDEX idx_summary_history_timestamp_utc ON summary_history (timestamp_utc);
|
||||
CREATE INDEX idx_summary_history_date ON summary_history (date);
|
||||
|
||||
|
||||
CREATE TABLE gateway_description (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
gateway_identity_key VARCHAR UNIQUE NOT NULL,
|
||||
moniker VARCHAR,
|
||||
website VARCHAR,
|
||||
security_contact VARCHAR,
|
||||
details VARCHAR,
|
||||
last_updated_utc INTEGER NOT NULL,
|
||||
FOREIGN KEY (gateway_identity_key) REFERENCES gateways (gateway_identity_key)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE
|
||||
mixnode_daily_stats (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
mix_id INTEGER NOT NULL,
|
||||
total_stake BIGINT NOT NULL,
|
||||
date_utc VARCHAR NOT NULL,
|
||||
packets_received INTEGER DEFAULT 0,
|
||||
packets_sent INTEGER DEFAULT 0,
|
||||
packets_dropped INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (mix_id) REFERENCES mixnodes (mix_id),
|
||||
UNIQUE (mix_id, date_utc) -- This constraint automatically creates an index
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE testruns
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
gateway_id INTEGER NOT NULL,
|
||||
status INTEGER NOT NULL, -- 0=pending, 1=in-progress, 2=complete
|
||||
timestamp_utc INTEGER NOT NULL,
|
||||
ip_address VARCHAR NOT NULL,
|
||||
log VARCHAR NOT NULL,
|
||||
FOREIGN KEY (gateway_id) REFERENCES gateways (id)
|
||||
);
|
||||
@@ -0,0 +1,77 @@
|
||||
use clap::Parser;
|
||||
use nym_bin_common::bin_info;
|
||||
use reqwest::Url;
|
||||
use std::{sync::OnceLock, time::Duration};
|
||||
|
||||
// Helper for passing LONG_VERSION to clap
|
||||
fn pretty_build_info_static() -> &'static str {
|
||||
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
|
||||
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
|
||||
pub(crate) struct Cli {
|
||||
/// Network name for the network to which we're connecting.
|
||||
#[clap(long, env = "NETWORK_NAME")]
|
||||
pub(crate) network_name: String,
|
||||
|
||||
/// Explorer api url.
|
||||
#[clap(short, long, env = "EXPLORER_API")]
|
||||
pub(crate) explorer_api: String,
|
||||
|
||||
/// Nym api url.
|
||||
#[clap(short, long, env = "NYM_API")]
|
||||
pub(crate) nym_api: String,
|
||||
|
||||
/// TTL for the http cache.
|
||||
#[clap(
|
||||
long,
|
||||
default_value_t = 30,
|
||||
env = "NYM_NODE_STATUS_API_NYM_HTTP_CACHE_TTL"
|
||||
)]
|
||||
pub(crate) nym_http_cache_ttl: u64,
|
||||
|
||||
/// HTTP port on which to run node status api.
|
||||
#[clap(long, default_value_t = 8000, env = "NYM_NODE_STATUS_API_HTTP_PORT")]
|
||||
pub(crate) http_port: u16,
|
||||
|
||||
/// Nyxd address.
|
||||
#[clap(long, env = "NYXD")]
|
||||
pub(crate) nyxd_addr: Url,
|
||||
|
||||
/// Nym api client timeout.
|
||||
#[clap(long, default_value = "15", env = "NYM_API_CLIENT_TIMEOUT")]
|
||||
#[arg(value_parser = parse_duration)]
|
||||
pub(crate) nym_api_client_timeout: Duration,
|
||||
|
||||
/// Explorer api client timeout.
|
||||
#[clap(long, default_value = "15", env = "EXPLORER_CLIENT_TIMEOUT")]
|
||||
#[arg(value_parser = parse_duration)]
|
||||
pub(crate) explorer_client_timeout: Duration,
|
||||
|
||||
/// Connection url for the database.
|
||||
#[clap(long, env = "DATABASE_URL")]
|
||||
pub(crate) database_url: String,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
default_value = "600",
|
||||
env = "NODE_STATUS_API_MONITOR_REFRESH_INTERVAL"
|
||||
)]
|
||||
#[arg(value_parser = parse_duration)]
|
||||
pub(crate) monitor_refresh_interval: Duration,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
default_value = "600",
|
||||
env = "NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL"
|
||||
)]
|
||||
#[arg(value_parser = parse_duration)]
|
||||
pub(crate) testruns_refresh_interval: Duration,
|
||||
}
|
||||
|
||||
fn parse_duration(arg: &str) -> Result<std::time::Duration, std::num::ParseIntError> {
|
||||
let seconds = arg.parse()?;
|
||||
Ok(std::time::Duration::from_secs(seconds))
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use sqlx::{migrate::Migrator, sqlite::SqliteConnectOptions, ConnectOptions, SqlitePool};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub(crate) mod models;
|
||||
pub(crate) mod queries;
|
||||
|
||||
static MIGRATOR: Migrator = sqlx::migrate!("./migrations");
|
||||
|
||||
pub(crate) type DbPool = SqlitePool;
|
||||
|
||||
pub(crate) struct Storage {
|
||||
pool: DbPool,
|
||||
}
|
||||
|
||||
impl Storage {
|
||||
pub async fn init(connection_url: String) -> Result<Self> {
|
||||
let connect_options = SqliteConnectOptions::from_str(&connection_url)?
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
|
||||
let pool = sqlx::SqlitePool::connect_with(connect_options)
|
||||
.await
|
||||
.map_err(|err| anyhow!("Failed to connect to {}: {}", &connection_url, err))?;
|
||||
|
||||
MIGRATOR.run(&pool).await?;
|
||||
|
||||
Ok(Storage { pool })
|
||||
}
|
||||
|
||||
/// Cloning pool is cheap, it's the same underlying set of connections
|
||||
pub fn pool_owned(&self) -> DbPool {
|
||||
self.pool.clone()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
use crate::{
|
||||
http::{self, models::SummaryHistory},
|
||||
monitor::NumericalCheckedCast,
|
||||
};
|
||||
use nym_node_requests::api::v1::node::models::NodeDescription;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::{EnumString, FromRepr};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub(crate) struct GatewayRecord {
|
||||
pub(crate) identity_key: String,
|
||||
pub(crate) bonded: bool,
|
||||
pub(crate) blacklisted: bool,
|
||||
pub(crate) self_described: String,
|
||||
pub(crate) explorer_pretty_bond: Option<String>,
|
||||
pub(crate) last_updated_utc: i64,
|
||||
pub(crate) performance: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct GatewayDto {
|
||||
pub(crate) gateway_identity_key: String,
|
||||
pub(crate) bonded: bool,
|
||||
pub(crate) blacklisted: bool,
|
||||
pub(crate) performance: i64,
|
||||
pub(crate) self_described: Option<String>,
|
||||
pub(crate) explorer_pretty_bond: Option<String>,
|
||||
pub(crate) last_probe_result: Option<String>,
|
||||
pub(crate) last_probe_log: Option<String>,
|
||||
pub(crate) last_testrun_utc: Option<i64>,
|
||||
pub(crate) last_updated_utc: i64,
|
||||
pub(crate) moniker: String,
|
||||
pub(crate) security_contact: String,
|
||||
pub(crate) details: String,
|
||||
pub(crate) website: String,
|
||||
}
|
||||
|
||||
impl TryFrom<GatewayDto> for http::models::Gateway {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: GatewayDto) -> Result<Self, Self::Error> {
|
||||
// Instead of using routing_score_successes / routing_score_samples, we use the
|
||||
// number of successful testruns in the last 24h.
|
||||
let routing_score = 0f32;
|
||||
let config_score = 0u32;
|
||||
let last_updated_utc =
|
||||
timestamp_as_utc(value.last_updated_utc.cast_checked()?).to_rfc3339();
|
||||
let last_testrun_utc = value
|
||||
.last_testrun_utc
|
||||
.and_then(|i| i.cast_checked().ok())
|
||||
.map(|t| timestamp_as_utc(t).to_rfc3339());
|
||||
|
||||
let self_described = value.self_described.clone().unwrap_or("null".to_string());
|
||||
let explorer_pretty_bond = value
|
||||
.explorer_pretty_bond
|
||||
.clone()
|
||||
.unwrap_or("null".to_string());
|
||||
let last_probe_result = value
|
||||
.last_probe_result
|
||||
.clone()
|
||||
.unwrap_or("null".to_string());
|
||||
let last_probe_log = value.last_probe_log.clone();
|
||||
|
||||
let self_described = serde_json::from_str(&self_described).unwrap_or(None);
|
||||
let explorer_pretty_bond = serde_json::from_str(&explorer_pretty_bond).unwrap_or(None);
|
||||
let last_probe_result = serde_json::from_str(&last_probe_result).unwrap_or(None);
|
||||
|
||||
let bonded = value.bonded;
|
||||
let blacklisted = value.blacklisted;
|
||||
let performance = value.performance as u8;
|
||||
|
||||
let description = NodeDescription {
|
||||
moniker: value.moniker.clone(),
|
||||
website: value.website.clone(),
|
||||
security_contact: value.security_contact.clone(),
|
||||
details: value.details.clone(),
|
||||
};
|
||||
|
||||
Ok(http::models::Gateway {
|
||||
gateway_identity_key: value.gateway_identity_key.clone(),
|
||||
bonded,
|
||||
blacklisted,
|
||||
performance,
|
||||
self_described,
|
||||
explorer_pretty_bond,
|
||||
description,
|
||||
last_probe_result,
|
||||
last_probe_log,
|
||||
routing_score,
|
||||
config_score,
|
||||
last_testrun_utc,
|
||||
last_updated_utc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn timestamp_as_utc(unix_timestamp: u64) -> chrono::DateTime<chrono::Utc> {
|
||||
let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(unix_timestamp);
|
||||
d.into()
|
||||
}
|
||||
|
||||
pub(crate) struct MixnodeRecord {
|
||||
pub(crate) mix_id: u32,
|
||||
pub(crate) identity_key: String,
|
||||
pub(crate) bonded: bool,
|
||||
pub(crate) total_stake: i64,
|
||||
pub(crate) host: String,
|
||||
pub(crate) http_port: u16,
|
||||
pub(crate) blacklisted: bool,
|
||||
pub(crate) full_details: String,
|
||||
pub(crate) self_described: Option<String>,
|
||||
pub(crate) last_updated_utc: i64,
|
||||
pub(crate) is_dp_delegatee: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct MixnodeDto {
|
||||
pub(crate) mix_id: i64,
|
||||
pub(crate) bonded: bool,
|
||||
pub(crate) blacklisted: bool,
|
||||
pub(crate) is_dp_delegatee: bool,
|
||||
pub(crate) total_stake: i64,
|
||||
pub(crate) full_details: String,
|
||||
pub(crate) self_described: Option<String>,
|
||||
pub(crate) last_updated_utc: i64,
|
||||
pub(crate) moniker: String,
|
||||
pub(crate) website: String,
|
||||
pub(crate) security_contact: String,
|
||||
pub(crate) details: String,
|
||||
}
|
||||
|
||||
impl TryFrom<MixnodeDto> for http::models::Mixnode {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: MixnodeDto) -> Result<Self, Self::Error> {
|
||||
let mix_id = value.mix_id.cast_checked()?;
|
||||
let full_details = value.full_details.clone();
|
||||
let full_details = serde_json::from_str(&full_details).unwrap_or(None);
|
||||
|
||||
let self_described = value
|
||||
.self_described
|
||||
.clone()
|
||||
.map(|v| serde_json::from_str(&v).unwrap_or(serde_json::Value::Null));
|
||||
|
||||
let last_updated_utc =
|
||||
timestamp_as_utc(value.last_updated_utc.cast_checked()?).to_rfc3339();
|
||||
let blacklisted = value.blacklisted;
|
||||
let is_dp_delegatee = value.is_dp_delegatee;
|
||||
let moniker = value.moniker.clone();
|
||||
let website = value.website.clone();
|
||||
let security_contact = value.security_contact.clone();
|
||||
let details = value.details.clone();
|
||||
|
||||
Ok(http::models::Mixnode {
|
||||
mix_id,
|
||||
bonded: value.bonded,
|
||||
blacklisted,
|
||||
is_dp_delegatee,
|
||||
total_stake: value.total_stake,
|
||||
full_details,
|
||||
description: NodeDescription {
|
||||
moniker,
|
||||
website,
|
||||
security_contact,
|
||||
details,
|
||||
},
|
||||
self_described,
|
||||
last_updated_utc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct BondedStatusDto {
|
||||
pub(crate) id: i64,
|
||||
pub(crate) identity_key: String,
|
||||
pub(crate) bonded: bool,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct SummaryDto {
|
||||
pub(crate) key: String,
|
||||
pub(crate) value_json: String,
|
||||
pub(crate) last_updated_utc: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct SummaryHistoryDto {
|
||||
#[allow(dead_code)]
|
||||
pub id: i64,
|
||||
pub date: String,
|
||||
pub value_json: String,
|
||||
pub timestamp_utc: i64,
|
||||
}
|
||||
|
||||
impl TryFrom<SummaryHistoryDto> for SummaryHistory {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: SummaryHistoryDto) -> Result<Self, Self::Error> {
|
||||
let value_json = serde_json::from_str(&value.value_json).unwrap_or_default();
|
||||
Ok(SummaryHistory {
|
||||
value_json,
|
||||
date: value.date.clone(),
|
||||
timestamp_utc: timestamp_as_utc(value.timestamp_utc.cast_checked()?).to_rfc3339(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const MIXNODES_BONDED_COUNT: &str = "mixnodes.bonded.count";
|
||||
pub(crate) const MIXNODES_BONDED_ACTIVE: &str = "mixnodes.bonded.active";
|
||||
pub(crate) const MIXNODES_BONDED_INACTIVE: &str = "mixnodes.bonded.inactive";
|
||||
pub(crate) const MIXNODES_BONDED_RESERVE: &str = "mixnodes.bonded.reserve";
|
||||
pub(crate) const MIXNODES_BLACKLISTED_COUNT: &str = "mixnodes.blacklisted.count";
|
||||
|
||||
pub(crate) const GATEWAYS_BONDED_COUNT: &str = "gateways.bonded.count";
|
||||
pub(crate) const GATEWAYS_EXPLORER_COUNT: &str = "gateways.explorer.count";
|
||||
pub(crate) const GATEWAYS_BLACKLISTED_COUNT: &str = "gateways.blacklisted.count";
|
||||
|
||||
pub(crate) const MIXNODES_HISTORICAL_COUNT: &str = "mixnodes.historical.count";
|
||||
pub(crate) const GATEWAYS_HISTORICAL_COUNT: &str = "gateways.historical.count";
|
||||
|
||||
// `utoipa`` goes crazy if you use module-qualified prefix as field type so we
|
||||
// have to import it
|
||||
use gateway::GatewaySummary;
|
||||
use mixnode::MixnodeSummary;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub(crate) struct NetworkSummary {
|
||||
pub(crate) mixnodes: MixnodeSummary,
|
||||
pub(crate) gateways: GatewaySummary,
|
||||
}
|
||||
|
||||
pub(crate) mod mixnode {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub(crate) struct MixnodeSummary {
|
||||
pub(crate) bonded: MixnodeSummaryBonded,
|
||||
pub(crate) blacklisted: MixnodeSummaryBlacklisted,
|
||||
pub(crate) historical: MixnodeSummaryHistorical,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub(crate) struct MixnodeSummaryBonded {
|
||||
pub(crate) count: i32,
|
||||
pub(crate) active: i32,
|
||||
pub(crate) inactive: i32,
|
||||
pub(crate) reserve: i32,
|
||||
pub(crate) last_updated_utc: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub(crate) struct MixnodeSummaryBlacklisted {
|
||||
pub(crate) count: i32,
|
||||
pub(crate) last_updated_utc: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub(crate) struct MixnodeSummaryHistorical {
|
||||
pub(crate) count: i32,
|
||||
pub(crate) last_updated_utc: String,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod gateway {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub(crate) struct GatewaySummary {
|
||||
pub(crate) bonded: GatewaySummaryBonded,
|
||||
pub(crate) blacklisted: GatewaySummaryBlacklisted,
|
||||
pub(crate) historical: GatewaySummaryHistorical,
|
||||
pub(crate) explorer: GatewaySummaryExplorer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub(crate) struct GatewaySummaryExplorer {
|
||||
pub(crate) count: i32,
|
||||
pub(crate) last_updated_utc: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub(crate) struct GatewaySummaryBonded {
|
||||
pub(crate) count: i32,
|
||||
pub(crate) last_updated_utc: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub(crate) struct GatewaySummaryHistorical {
|
||||
pub(crate) count: i32,
|
||||
pub(crate) last_updated_utc: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
pub(crate) struct GatewaySummaryBlacklisted {
|
||||
pub(crate) count: i32,
|
||||
pub(crate) last_updated_utc: String,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // not dead code, this is SQL data model
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TestRunDto {
|
||||
pub id: i64,
|
||||
pub gateway_id: i64,
|
||||
pub status: i64,
|
||||
pub timestamp_utc: i64,
|
||||
pub ip_address: String,
|
||||
pub log: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, strum_macros::Display, EnumString, FromRepr, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub(crate) enum TestRunStatus {
|
||||
Complete = 2,
|
||||
InProgress = 1,
|
||||
Queued = 0,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GatewayIdentityDto {
|
||||
pub gateway_identity_key: String,
|
||||
pub bonded: bool,
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // it's not dead code but clippy doesn't detect usage in sqlx macros
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GatewayInfoDto {
|
||||
pub id: i64,
|
||||
pub gateway_identity_key: String,
|
||||
pub self_described: Option<String>,
|
||||
pub explorer_pretty_bond: Option<String>,
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
use crate::{
|
||||
db::{
|
||||
models::{BondedStatusDto, GatewayDto, GatewayRecord},
|
||||
DbPool,
|
||||
},
|
||||
http::models::Gateway,
|
||||
};
|
||||
use futures_util::TryStreamExt;
|
||||
use nym_validator_client::models::NymNodeDescription;
|
||||
use sqlx::{pool::PoolConnection, Sqlite};
|
||||
use tracing::error;
|
||||
|
||||
pub(crate) async fn select_gateway_identity(
|
||||
conn: &mut PoolConnection<Sqlite>,
|
||||
gateway_pk: i64,
|
||||
) -> anyhow::Result<String> {
|
||||
let record = sqlx::query!(
|
||||
r#"SELECT
|
||||
gateway_identity_key
|
||||
FROM
|
||||
gateways
|
||||
WHERE
|
||||
id = ?"#,
|
||||
gateway_pk
|
||||
)
|
||||
.fetch_one(conn.as_mut())
|
||||
.await?;
|
||||
|
||||
Ok(record.gateway_identity_key)
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_gateways(
|
||||
pool: &DbPool,
|
||||
gateways: Vec<GatewayRecord>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut db = pool.acquire().await?;
|
||||
for record in gateways {
|
||||
sqlx::query!(
|
||||
"INSERT INTO gateways
|
||||
(gateway_identity_key, bonded, blacklisted,
|
||||
self_described, explorer_pretty_bond,
|
||||
last_updated_utc, performance)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(gateway_identity_key) DO UPDATE SET
|
||||
bonded=excluded.bonded,
|
||||
blacklisted=excluded.blacklisted,
|
||||
self_described=excluded.self_described,
|
||||
explorer_pretty_bond=excluded.explorer_pretty_bond,
|
||||
last_updated_utc=excluded.last_updated_utc,
|
||||
performance = excluded.performance;",
|
||||
record.identity_key,
|
||||
record.bonded,
|
||||
record.blacklisted,
|
||||
record.self_described,
|
||||
record.explorer_pretty_bond,
|
||||
record.last_updated_utc,
|
||||
record.performance
|
||||
)
|
||||
.execute(&mut *db)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn write_blacklisted_gateways_to_db<'a, I>(
|
||||
pool: &DbPool,
|
||||
gateways: I,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
I: Iterator<Item = &'a String>,
|
||||
{
|
||||
let mut conn = pool.acquire().await?;
|
||||
for gateway_identity_key in gateways {
|
||||
sqlx::query!(
|
||||
"UPDATE gateways
|
||||
SET blacklisted = true
|
||||
WHERE gateway_identity_key = ?;",
|
||||
gateway_identity_key,
|
||||
)
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure all gateways that are set as bonded, are still bonded
|
||||
pub(crate) async fn ensure_gateways_still_bonded(
|
||||
pool: &DbPool,
|
||||
gateways: &[&NymNodeDescription],
|
||||
) -> anyhow::Result<usize> {
|
||||
let bonded_gateways_rows = get_all_bonded_gateways_row_ids_by_status(pool, true).await?;
|
||||
let unbonded_gateways_rows = bonded_gateways_rows.iter().filter(|v| {
|
||||
!gateways
|
||||
.iter()
|
||||
.any(|bonded| *bonded.ed25519_identity_key().to_base58_string() == v.identity_key)
|
||||
});
|
||||
|
||||
let recently_unbonded_gateways = unbonded_gateways_rows.to_owned().count();
|
||||
let last_updated_utc = chrono::offset::Utc::now().timestamp();
|
||||
let mut transaction = pool.begin().await?;
|
||||
for row in unbonded_gateways_rows {
|
||||
sqlx::query!(
|
||||
"UPDATE gateways
|
||||
SET bonded = ?, last_updated_utc = ?
|
||||
WHERE id = ?;",
|
||||
false,
|
||||
last_updated_utc,
|
||||
row.id,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(recently_unbonded_gateways)
|
||||
}
|
||||
|
||||
async fn get_all_bonded_gateways_row_ids_by_status(
|
||||
pool: &DbPool,
|
||||
status: bool,
|
||||
) -> anyhow::Result<Vec<BondedStatusDto>> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
let items = sqlx::query_as!(
|
||||
BondedStatusDto,
|
||||
r#"SELECT
|
||||
id as "id!",
|
||||
gateway_identity_key as "identity_key!",
|
||||
bonded as "bonded: bool"
|
||||
FROM gateways
|
||||
WHERE bonded = ?"#,
|
||||
status,
|
||||
)
|
||||
.fetch(&mut *conn)
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_all_gateways(pool: &DbPool) -> anyhow::Result<Vec<Gateway>> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
let items = sqlx::query_as!(
|
||||
GatewayDto,
|
||||
r#"SELECT
|
||||
gw.gateway_identity_key as "gateway_identity_key!",
|
||||
gw.bonded as "bonded: bool",
|
||||
gw.blacklisted as "blacklisted: bool",
|
||||
gw.performance as "performance!",
|
||||
gw.self_described as "self_described?",
|
||||
gw.explorer_pretty_bond as "explorer_pretty_bond?",
|
||||
gw.last_probe_result as "last_probe_result?",
|
||||
gw.last_probe_log as "last_probe_log?",
|
||||
gw.last_testrun_utc as "last_testrun_utc?",
|
||||
gw.last_updated_utc as "last_updated_utc!",
|
||||
COALESCE(gd.moniker, "NA") as "moniker!",
|
||||
COALESCE(gd.website, "NA") as "website!",
|
||||
COALESCE(gd.security_contact, "NA") as "security_contact!",
|
||||
COALESCE(gd.details, "NA") as "details!"
|
||||
FROM gateways gw
|
||||
LEFT JOIN gateway_description gd
|
||||
ON gw.gateway_identity_key = gd.gateway_identity_key
|
||||
ORDER BY gw.gateway_identity_key"#,
|
||||
)
|
||||
.fetch(&mut *conn)
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
let items: Vec<Gateway> = items
|
||||
.into_iter()
|
||||
.map(|item| item.try_into())
|
||||
.collect::<anyhow::Result<Vec<_>>>()
|
||||
.map_err(|e| {
|
||||
error!("Conversion from DTO failed: {e}. Invalidly stored data?");
|
||||
e
|
||||
})?;
|
||||
tracing::trace!("Fetched {} gateways from DB", items.len());
|
||||
Ok(items)
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use crate::db::{models::NetworkSummary, DbPool};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
/// take `last_updated` instead of calculating it so that `summary` matches
|
||||
/// `daily_summary`
|
||||
pub(crate) async fn insert_summaries(
|
||||
pool: &DbPool,
|
||||
summaries: &[(&str, &usize)],
|
||||
summary: &NetworkSummary,
|
||||
last_updated: DateTime<Utc>,
|
||||
) -> anyhow::Result<()> {
|
||||
insert_summary(pool, summaries, last_updated).await?;
|
||||
|
||||
insert_summary_history(pool, summary, last_updated).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn insert_summary(
|
||||
pool: &DbPool,
|
||||
summaries: &[(&str, &usize)],
|
||||
last_updated: DateTime<Utc>,
|
||||
) -> anyhow::Result<()> {
|
||||
let timestamp = last_updated.timestamp();
|
||||
let mut tx = pool.begin().await?;
|
||||
|
||||
for (kind, value) in summaries {
|
||||
let value = value.to_string();
|
||||
sqlx::query!(
|
||||
"INSERT INTO summary
|
||||
(key, value_json, last_updated_utc)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(key) DO UPDATE SET
|
||||
value_json=excluded.value_json,
|
||||
last_updated_utc=excluded.last_updated_utc;",
|
||||
kind,
|
||||
value,
|
||||
timestamp
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
tracing::error!("Failed to insert data for {kind}: {err}, aborting transaction",);
|
||||
err
|
||||
})?;
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// For `<date_N>`, `summary_history` is updated with fresh data on every
|
||||
/// iteration.
|
||||
///
|
||||
/// After UTC midnight, summary is inserted for `<date_N+1>` and last entry for
|
||||
/// `<date_N>` stays there forever.
|
||||
///
|
||||
/// This is not aggregate data, it's a set of latest data points
|
||||
async fn insert_summary_history(
|
||||
pool: &DbPool,
|
||||
summary: &NetworkSummary,
|
||||
last_updated: DateTime<Utc>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
|
||||
let value_json = serde_json::to_string(&summary)?;
|
||||
let timestamp = last_updated.timestamp();
|
||||
let now_rfc3339 = last_updated.to_rfc3339();
|
||||
// YYYY-MM-DD, without time
|
||||
let date = &now_rfc3339[..10];
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO summary_history
|
||||
(date, timestamp_utc, value_json)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(date) DO UPDATE SET
|
||||
timestamp_utc=excluded.timestamp_utc,
|
||||
value_json=excluded.value_json;",
|
||||
date,
|
||||
timestamp,
|
||||
value_json
|
||||
)
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
use futures_util::TryStreamExt;
|
||||
use nym_validator_client::models::MixNodeBondAnnotated;
|
||||
use tracing::error;
|
||||
|
||||
use crate::{
|
||||
db::{
|
||||
models::{BondedStatusDto, MixnodeDto, MixnodeRecord},
|
||||
DbPool,
|
||||
},
|
||||
http::models::{DailyStats, Mixnode},
|
||||
};
|
||||
|
||||
pub(crate) async fn insert_mixnodes(
|
||||
pool: &DbPool,
|
||||
mixnodes: Vec<MixnodeRecord>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
|
||||
for record in mixnodes.iter() {
|
||||
// https://www.sqlite.org/lang_upsert.html
|
||||
sqlx::query!(
|
||||
"INSERT INTO mixnodes
|
||||
(mix_id, identity_key, bonded, total_stake,
|
||||
host, http_api_port, blacklisted, full_details,
|
||||
self_described, last_updated_utc, is_dp_delegatee)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(mix_id) DO UPDATE SET
|
||||
bonded=excluded.bonded,
|
||||
total_stake=excluded.total_stake, host=excluded.host,
|
||||
http_api_port=excluded.http_api_port,blacklisted=excluded.blacklisted,
|
||||
full_details=excluded.full_details,self_described=excluded.self_described,
|
||||
last_updated_utc=excluded.last_updated_utc,
|
||||
is_dp_delegatee = excluded.is_dp_delegatee;",
|
||||
record.mix_id,
|
||||
record.identity_key,
|
||||
record.bonded,
|
||||
record.total_stake,
|
||||
record.host,
|
||||
record.http_port,
|
||||
record.blacklisted,
|
||||
record.full_details,
|
||||
record.self_described,
|
||||
record.last_updated_utc,
|
||||
record.is_dp_delegatee
|
||||
)
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_all_mixnodes(pool: &DbPool) -> anyhow::Result<Vec<Mixnode>> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
let items = sqlx::query_as!(
|
||||
MixnodeDto,
|
||||
r#"SELECT
|
||||
mn.mix_id as "mix_id!",
|
||||
mn.bonded as "bonded: bool",
|
||||
mn.blacklisted as "blacklisted: bool",
|
||||
mn.is_dp_delegatee as "is_dp_delegatee: bool",
|
||||
mn.total_stake as "total_stake!",
|
||||
mn.full_details as "full_details!",
|
||||
mn.self_described as "self_described",
|
||||
mn.last_updated_utc as "last_updated_utc!",
|
||||
COALESCE(md.moniker, "NA") as "moniker!",
|
||||
COALESCE(md.website, "NA") as "website!",
|
||||
COALESCE(md.security_contact, "NA") as "security_contact!",
|
||||
COALESCE(md.details, "NA") as "details!"
|
||||
FROM mixnodes mn
|
||||
LEFT JOIN mixnode_description md ON mn.mix_id = md.mix_id
|
||||
ORDER BY mn.mix_id"#
|
||||
)
|
||||
.fetch(&mut *conn)
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
let items = items
|
||||
.into_iter()
|
||||
.map(|item| item.try_into())
|
||||
.collect::<anyhow::Result<Vec<_>>>()
|
||||
.map_err(|e| {
|
||||
error!("Conversion from DTO failed: {e}. Invalidly stored data?");
|
||||
e
|
||||
})?;
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
/// We fetch the latest 30 days of data as a subquery and then
|
||||
/// return it in ascending order, so we don't break existing UI
|
||||
pub(crate) async fn get_daily_stats(pool: &DbPool) -> anyhow::Result<Vec<DailyStats>> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
let items = sqlx::query_as!(
|
||||
DailyStats,
|
||||
r#"
|
||||
SELECT
|
||||
date_utc as "date_utc!",
|
||||
packets_received as "total_packets_received!: i64",
|
||||
packets_sent as "total_packets_sent!: i64",
|
||||
packets_dropped as "total_packets_dropped!: i64",
|
||||
total_stake as "total_stake!: i64"
|
||||
FROM (
|
||||
SELECT
|
||||
date_utc,
|
||||
SUM(packets_received) as packets_received,
|
||||
SUM(packets_sent) as packets_sent,
|
||||
SUM(packets_dropped) as packets_dropped,
|
||||
SUM(total_stake) as total_stake
|
||||
FROM mixnode_daily_stats
|
||||
GROUP BY date_utc
|
||||
ORDER BY date_utc DESC
|
||||
LIMIT 30
|
||||
)
|
||||
GROUP BY date_utc
|
||||
ORDER BY date_utc
|
||||
"#
|
||||
)
|
||||
.fetch(&mut *conn)
|
||||
.try_collect::<Vec<DailyStats>>()
|
||||
.await?;
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
/// Ensure all mixnodes that are set as bonded, are still bonded
|
||||
pub(crate) async fn ensure_mixnodes_still_bonded(
|
||||
pool: &DbPool,
|
||||
mixnodes: &[MixNodeBondAnnotated],
|
||||
) -> anyhow::Result<usize> {
|
||||
let bonded_mixnodes_rows = get_all_bonded_mixnodes_row_ids_by_status(pool, true).await?;
|
||||
let unbonded_mixnodes_rows = bonded_mixnodes_rows.iter().filter(|v| {
|
||||
!mixnodes
|
||||
.iter()
|
||||
.any(|bonded| *bonded.mixnode_details.bond_information.identity() == v.identity_key)
|
||||
});
|
||||
|
||||
let recently_unbonded_mixnodes = unbonded_mixnodes_rows.to_owned().count();
|
||||
let last_updated_utc = chrono::offset::Utc::now().timestamp();
|
||||
let mut transaction = pool.begin().await?;
|
||||
for row in unbonded_mixnodes_rows {
|
||||
sqlx::query!(
|
||||
"UPDATE mixnodes
|
||||
SET bonded = ?, last_updated_utc = ?
|
||||
WHERE id = ?;",
|
||||
false,
|
||||
last_updated_utc,
|
||||
row.id,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(recently_unbonded_mixnodes)
|
||||
}
|
||||
|
||||
async fn get_all_bonded_mixnodes_row_ids_by_status(
|
||||
pool: &DbPool,
|
||||
status: bool,
|
||||
) -> anyhow::Result<Vec<BondedStatusDto>> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
let items = sqlx::query_as!(
|
||||
BondedStatusDto,
|
||||
r#"SELECT
|
||||
id as "id!",
|
||||
identity_key as "identity_key!",
|
||||
bonded as "bonded: bool"
|
||||
FROM mixnodes
|
||||
WHERE bonded = ?"#,
|
||||
status,
|
||||
)
|
||||
.fetch(&mut *conn)
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
mod gateways;
|
||||
mod misc;
|
||||
mod mixnodes;
|
||||
mod summary;
|
||||
pub(crate) mod testruns;
|
||||
|
||||
pub(crate) use gateways::{
|
||||
ensure_gateways_still_bonded, get_all_gateways, insert_gateways, select_gateway_identity,
|
||||
write_blacklisted_gateways_to_db,
|
||||
};
|
||||
pub(crate) use misc::insert_summaries;
|
||||
pub(crate) use mixnodes::{
|
||||
ensure_mixnodes_still_bonded, get_all_mixnodes, get_daily_stats, insert_mixnodes,
|
||||
};
|
||||
pub(crate) use summary::{get_summary, get_summary_history};
|
||||
@@ -0,0 +1,209 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use futures_util::TryStreamExt;
|
||||
use std::collections::HashMap;
|
||||
use tracing::error;
|
||||
|
||||
use crate::{
|
||||
db::{
|
||||
models::{
|
||||
gateway::{
|
||||
GatewaySummary, GatewaySummaryBlacklisted, GatewaySummaryBonded,
|
||||
GatewaySummaryExplorer, GatewaySummaryHistorical,
|
||||
},
|
||||
mixnode::{
|
||||
MixnodeSummary, MixnodeSummaryBlacklisted, MixnodeSummaryBonded,
|
||||
MixnodeSummaryHistorical,
|
||||
},
|
||||
NetworkSummary, SummaryDto, SummaryHistoryDto,
|
||||
},
|
||||
DbPool,
|
||||
},
|
||||
http::{
|
||||
error::{HttpError, HttpResult},
|
||||
models::SummaryHistory,
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) async fn get_summary_history(pool: &DbPool) -> anyhow::Result<Vec<SummaryHistory>> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
let items = sqlx::query_as!(
|
||||
SummaryHistoryDto,
|
||||
r#"SELECT
|
||||
id as "id!",
|
||||
date as "date!",
|
||||
timestamp_utc as "timestamp_utc!",
|
||||
value_json as "value_json!"
|
||||
FROM summary_history
|
||||
ORDER BY date DESC
|
||||
LIMIT 30"#,
|
||||
)
|
||||
.fetch(&mut *conn)
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
let items = items
|
||||
.into_iter()
|
||||
.map(|item| item.try_into())
|
||||
.collect::<anyhow::Result<Vec<_>>>()
|
||||
.map_err(|e| {
|
||||
error!("Conversion from DTO failed: {e}. Invalidly stored data?");
|
||||
e
|
||||
})?;
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
async fn get_summary_dto(pool: &DbPool) -> anyhow::Result<Vec<SummaryDto>> {
|
||||
let mut conn = pool.acquire().await?;
|
||||
Ok(sqlx::query_as!(
|
||||
SummaryDto,
|
||||
r#"SELECT
|
||||
key as "key!",
|
||||
value_json as "value_json!",
|
||||
last_updated_utc as "last_updated_utc!"
|
||||
FROM summary"#
|
||||
)
|
||||
.fetch(&mut *conn)
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_summary(pool: &DbPool) -> HttpResult<NetworkSummary> {
|
||||
let items = get_summary_dto(pool).await.map_err(|err| {
|
||||
tracing::error!("Couldn't get Summary from DB: {err}");
|
||||
HttpError::internal()
|
||||
})?;
|
||||
from_summary_dto(items).await
|
||||
}
|
||||
|
||||
async fn from_summary_dto(items: Vec<SummaryDto>) -> HttpResult<NetworkSummary> {
|
||||
const MIXNODES_BONDED_COUNT: &str = "mixnodes.bonded.count";
|
||||
const MIXNODES_BONDED_ACTIVE: &str = "mixnodes.bonded.active";
|
||||
const MIXNODES_BONDED_INACTIVE: &str = "mixnodes.bonded.inactive";
|
||||
const MIXNODES_BONDED_RESERVE: &str = "mixnodes.bonded.reserve";
|
||||
const MIXNODES_BLACKLISTED_COUNT: &str = "mixnodes.blacklisted.count";
|
||||
const GATEWAYS_BONDED_COUNT: &str = "gateways.bonded.count";
|
||||
const GATEWAYS_EXPLORER_COUNT: &str = "gateways.explorer.count";
|
||||
const GATEWAYS_BLACKLISTED_COUNT: &str = "gateways.blacklisted.count";
|
||||
const MIXNODES_HISTORICAL_COUNT: &str = "mixnodes.historical.count";
|
||||
const GATEWAYS_HISTORICAL_COUNT: &str = "gateways.historical.count";
|
||||
|
||||
// convert database rows into a map by key
|
||||
let mut map = HashMap::new();
|
||||
for item in items {
|
||||
map.insert(item.key.clone(), item);
|
||||
}
|
||||
|
||||
// check we have all the keys we are expecting, and build up a map of errors for missing one
|
||||
let keys = [
|
||||
GATEWAYS_BONDED_COUNT,
|
||||
GATEWAYS_EXPLORER_COUNT,
|
||||
GATEWAYS_HISTORICAL_COUNT,
|
||||
GATEWAYS_BLACKLISTED_COUNT,
|
||||
MIXNODES_BLACKLISTED_COUNT,
|
||||
MIXNODES_BONDED_ACTIVE,
|
||||
MIXNODES_BONDED_COUNT,
|
||||
MIXNODES_BONDED_INACTIVE,
|
||||
MIXNODES_BONDED_RESERVE,
|
||||
MIXNODES_HISTORICAL_COUNT,
|
||||
];
|
||||
|
||||
let mut errors: Vec<&str> = vec![];
|
||||
for key in keys {
|
||||
if !map.contains_key(key) {
|
||||
errors.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
// return an error if anything is missing, with a nice list
|
||||
if !errors.is_empty() {
|
||||
tracing::error!("Summary value missing: {}", errors.join(", "));
|
||||
return Err(HttpError::internal());
|
||||
}
|
||||
|
||||
// strip the options and use default values (anything missing is trapped above)
|
||||
let mixnodes_bonded_count: SummaryDto =
|
||||
map.get(MIXNODES_BONDED_COUNT).cloned().unwrap_or_default();
|
||||
let mixnodes_bonded_active: SummaryDto =
|
||||
map.get(MIXNODES_BONDED_ACTIVE).cloned().unwrap_or_default();
|
||||
let mixnodes_bonded_inactive: SummaryDto = map
|
||||
.get(MIXNODES_BONDED_INACTIVE)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let mixnodes_bonded_reserve: SummaryDto = map
|
||||
.get(MIXNODES_BONDED_RESERVE)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let mixnodes_blacklisted_count: SummaryDto = map
|
||||
.get(MIXNODES_BLACKLISTED_COUNT)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let gateways_bonded_count: SummaryDto =
|
||||
map.get(GATEWAYS_BONDED_COUNT).cloned().unwrap_or_default();
|
||||
let gateways_explorer_count: SummaryDto = map
|
||||
.get(GATEWAYS_EXPLORER_COUNT)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let mixnodes_historical_count: SummaryDto = map
|
||||
.get(MIXNODES_HISTORICAL_COUNT)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let gateways_historical_count: SummaryDto = map
|
||||
.get(GATEWAYS_HISTORICAL_COUNT)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let gateways_blacklisted_count: SummaryDto = map
|
||||
.get(GATEWAYS_BLACKLISTED_COUNT)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(NetworkSummary {
|
||||
mixnodes: MixnodeSummary {
|
||||
bonded: MixnodeSummaryBonded {
|
||||
count: to_count_i32(&mixnodes_bonded_count),
|
||||
active: to_count_i32(&mixnodes_bonded_active),
|
||||
reserve: to_count_i32(&mixnodes_bonded_reserve),
|
||||
inactive: to_count_i32(&mixnodes_bonded_inactive),
|
||||
last_updated_utc: to_timestamp(&mixnodes_bonded_count),
|
||||
},
|
||||
blacklisted: MixnodeSummaryBlacklisted {
|
||||
count: to_count_i32(&mixnodes_blacklisted_count),
|
||||
last_updated_utc: to_timestamp(&mixnodes_blacklisted_count),
|
||||
},
|
||||
historical: MixnodeSummaryHistorical {
|
||||
count: to_count_i32(&mixnodes_historical_count),
|
||||
last_updated_utc: to_timestamp(&mixnodes_historical_count),
|
||||
},
|
||||
},
|
||||
gateways: GatewaySummary {
|
||||
bonded: GatewaySummaryBonded {
|
||||
count: to_count_i32(&gateways_bonded_count),
|
||||
last_updated_utc: to_timestamp(&gateways_bonded_count),
|
||||
},
|
||||
blacklisted: GatewaySummaryBlacklisted {
|
||||
count: to_count_i32(&gateways_blacklisted_count),
|
||||
last_updated_utc: to_timestamp(&gateways_blacklisted_count),
|
||||
},
|
||||
historical: GatewaySummaryHistorical {
|
||||
count: to_count_i32(&gateways_historical_count),
|
||||
last_updated_utc: to_timestamp(&gateways_historical_count),
|
||||
},
|
||||
explorer: GatewaySummaryExplorer {
|
||||
count: to_count_i32(&gateways_explorer_count),
|
||||
last_updated_utc: to_timestamp(&gateways_explorer_count),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn to_count_i32(value: &SummaryDto) -> i32 {
|
||||
value.value_json.parse::<i32>().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn to_timestamp(value: &SummaryDto) -> String {
|
||||
timestamp_as_utc(value.last_updated_utc as u64).to_rfc3339()
|
||||
}
|
||||
|
||||
fn timestamp_as_utc(unix_timestamp: u64) -> DateTime<Utc> {
|
||||
let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(unix_timestamp);
|
||||
d.into()
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
use crate::db::DbPool;
|
||||
use crate::http::models::TestrunAssignment;
|
||||
use crate::{
|
||||
db::models::{TestRunDto, TestRunStatus},
|
||||
testruns::now_utc,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use chrono::Duration;
|
||||
use sqlx::{pool::PoolConnection, Sqlite};
|
||||
|
||||
pub(crate) async fn get_in_progress_testrun_by_id(
|
||||
conn: &mut PoolConnection<Sqlite>,
|
||||
testrun_id: i64,
|
||||
) -> anyhow::Result<TestRunDto> {
|
||||
sqlx::query_as!(
|
||||
TestRunDto,
|
||||
r#"SELECT
|
||||
id as "id!",
|
||||
gateway_id as "gateway_id!",
|
||||
status as "status!",
|
||||
timestamp_utc as "timestamp_utc!",
|
||||
ip_address as "ip_address!",
|
||||
log as "log!"
|
||||
FROM testruns
|
||||
WHERE
|
||||
id = ?
|
||||
AND
|
||||
status = ?
|
||||
ORDER BY timestamp_utc"#,
|
||||
testrun_id,
|
||||
TestRunStatus::InProgress as i64,
|
||||
)
|
||||
.fetch_one(conn.as_mut())
|
||||
.await
|
||||
.context(format!("Couldn't retrieve testrun {testrun_id}"))
|
||||
}
|
||||
|
||||
pub(crate) async fn update_testruns_older_than(db: &DbPool, age: Duration) -> anyhow::Result<u64> {
|
||||
let mut conn = db.acquire().await?;
|
||||
let previous_run = now_utc() - age;
|
||||
let cutoff_timestamp = previous_run.timestamp();
|
||||
|
||||
let res = sqlx::query!(
|
||||
r#"UPDATE
|
||||
testruns
|
||||
SET
|
||||
status = ?
|
||||
WHERE
|
||||
status = ?
|
||||
AND
|
||||
timestamp_utc < ?
|
||||
"#,
|
||||
TestRunStatus::Queued as i64,
|
||||
TestRunStatus::InProgress as i64,
|
||||
cutoff_timestamp
|
||||
)
|
||||
.execute(conn.as_mut())
|
||||
.await?;
|
||||
|
||||
let stale_testruns = res.rows_affected();
|
||||
if stale_testruns > 0 {
|
||||
tracing::debug!(
|
||||
"Refreshed {} stale testruns, scheduled before {} but not yet finished",
|
||||
stale_testruns,
|
||||
previous_run
|
||||
);
|
||||
}
|
||||
|
||||
Ok(stale_testruns)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_oldest_testrun_and_make_it_pending(
|
||||
conn: &mut PoolConnection<Sqlite>,
|
||||
) -> anyhow::Result<Option<TestrunAssignment>> {
|
||||
// find & mark as "In progress" in the same transaction to avoid race conditions
|
||||
let returning = sqlx::query!(
|
||||
r#"UPDATE testruns
|
||||
SET status = ?
|
||||
WHERE rowid =
|
||||
(
|
||||
SELECT rowid
|
||||
FROM testruns
|
||||
WHERE status = ?
|
||||
ORDER BY timestamp_utc asc
|
||||
LIMIT 1
|
||||
)
|
||||
RETURNING
|
||||
id as "id!",
|
||||
gateway_id
|
||||
"#,
|
||||
TestRunStatus::InProgress as i64,
|
||||
TestRunStatus::Queued as i64,
|
||||
)
|
||||
.fetch_optional(conn.as_mut())
|
||||
.await?;
|
||||
|
||||
if let Some(testrun) = returning {
|
||||
let gw_identity = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
gateway_identity_key
|
||||
FROM gateways
|
||||
WHERE id = ?
|
||||
LIMIT 1"#,
|
||||
testrun.gateway_id
|
||||
)
|
||||
.fetch_one(conn.as_mut())
|
||||
.await?;
|
||||
|
||||
Ok(Some(TestrunAssignment {
|
||||
testrun_id: testrun.id,
|
||||
gateway_identity_key: gw_identity.gateway_identity_key,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn update_testrun_status(
|
||||
conn: &mut PoolConnection<Sqlite>,
|
||||
testrun_id: i64,
|
||||
status: TestRunStatus,
|
||||
) -> anyhow::Result<()> {
|
||||
let status = status as u32;
|
||||
sqlx::query!(
|
||||
"UPDATE testruns SET status = ? WHERE id = ?",
|
||||
status,
|
||||
testrun_id
|
||||
)
|
||||
.execute(conn.as_mut())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn update_gateway_last_probe_log(
|
||||
conn: &mut PoolConnection<Sqlite>,
|
||||
gateway_pk: i64,
|
||||
log: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
"UPDATE gateways SET last_probe_log = ? WHERE id = ?",
|
||||
log,
|
||||
gateway_pk
|
||||
)
|
||||
.execute(conn.as_mut())
|
||||
.await
|
||||
.map(drop)
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
pub(crate) async fn update_gateway_last_probe_result(
|
||||
conn: &mut PoolConnection<Sqlite>,
|
||||
gateway_pk: i64,
|
||||
result: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
"UPDATE gateways SET last_probe_result = ? WHERE id = ?",
|
||||
result,
|
||||
gateway_pk
|
||||
)
|
||||
.execute(conn.as_mut())
|
||||
.await
|
||||
.map(drop)
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
pub(crate) async fn update_gateway_score(
|
||||
conn: &mut PoolConnection<Sqlite>,
|
||||
gateway_pk: i64,
|
||||
) -> anyhow::Result<()> {
|
||||
let now = now_utc().timestamp();
|
||||
sqlx::query!(
|
||||
"UPDATE gateways SET last_testrun_utc = ?, last_updated_utc = ? WHERE id = ?",
|
||||
now,
|
||||
now,
|
||||
gateway_pk
|
||||
)
|
||||
.execute(conn.as_mut())
|
||||
.await
|
||||
.map(drop)
|
||||
.map_err(From::from)
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
Json, Router,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::http::{
|
||||
error::{HttpError, HttpResult},
|
||||
models::{Gateway, GatewaySkinny},
|
||||
state::AppState,
|
||||
PagedResult, Pagination,
|
||||
};
|
||||
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", axum::routing::get(gateways))
|
||||
.route("/skinny", axum::routing::get(gateways_skinny))
|
||||
.route("/:identity_key", axum::routing::get(get_gateway))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Gateways",
|
||||
get,
|
||||
params(
|
||||
Pagination
|
||||
),
|
||||
path = "/v2/gateways",
|
||||
responses(
|
||||
(status = 200, body = PagedGateway)
|
||||
)
|
||||
)]
|
||||
async fn gateways(
|
||||
Query(pagination): Query<Pagination>,
|
||||
State(state): State<AppState>,
|
||||
) -> HttpResult<Json<PagedResult<Gateway>>> {
|
||||
let db = state.db_pool();
|
||||
let res = state.cache().get_gateway_list(db).await;
|
||||
|
||||
Ok(Json(PagedResult::paginate(pagination, res)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Gateways",
|
||||
get,
|
||||
params(
|
||||
Pagination
|
||||
),
|
||||
path = "/v2/gateways/skinny",
|
||||
responses(
|
||||
(status = 200, body = PagedGatewaySkinny)
|
||||
)
|
||||
)]
|
||||
async fn gateways_skinny(
|
||||
Query(pagination): Query<Pagination>,
|
||||
State(state): State<AppState>,
|
||||
) -> HttpResult<Json<PagedResult<GatewaySkinny>>> {
|
||||
let db = state.db_pool();
|
||||
let res = state.cache().get_gateway_list(db).await;
|
||||
let res: Vec<GatewaySkinny> = res
|
||||
.iter()
|
||||
.filter(|g| g.bonded)
|
||||
.map(|g| GatewaySkinny {
|
||||
gateway_identity_key: g.gateway_identity_key.clone(),
|
||||
self_described: g.self_described.clone(),
|
||||
performance: g.performance,
|
||||
explorer_pretty_bond: g.explorer_pretty_bond.clone(),
|
||||
last_probe_result: g.last_probe_result.clone(),
|
||||
last_testrun_utc: g.last_testrun_utc.clone(),
|
||||
last_updated_utc: g.last_updated_utc.clone(),
|
||||
routing_score: g.routing_score,
|
||||
config_score: g.config_score,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(PagedResult::paginate(pagination, res)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in = Path)]
|
||||
struct IdentityKeyParam {
|
||||
identity_key: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Gateways",
|
||||
get,
|
||||
params(
|
||||
IdentityKeyParam
|
||||
),
|
||||
path = "/v2/gateways/{identity_key}",
|
||||
responses(
|
||||
(status = 200, body = Gateway)
|
||||
)
|
||||
)]
|
||||
async fn get_gateway(
|
||||
Path(IdentityKeyParam { identity_key }): Path<IdentityKeyParam>,
|
||||
State(state): State<AppState>,
|
||||
) -> HttpResult<Json<Gateway>> {
|
||||
let db = state.db_pool();
|
||||
let res = state.cache().get_gateway_list(db).await;
|
||||
|
||||
match res
|
||||
.iter()
|
||||
.find(|item| item.gateway_identity_key == identity_key)
|
||||
{
|
||||
Some(res) => Ok(Json(res.clone())),
|
||||
None => Err(HttpError::invalid_input(identity_key)),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
Json, Router,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tracing::instrument;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::http::{
|
||||
error::{HttpError, HttpResult},
|
||||
models::{DailyStats, Mixnode},
|
||||
state::AppState,
|
||||
PagedResult, Pagination,
|
||||
};
|
||||
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", axum::routing::get(mixnodes))
|
||||
.route("/:mix_id", axum::routing::get(get_mixnodes))
|
||||
.route("/stats", axum::routing::get(get_stats))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Mixnodes",
|
||||
get,
|
||||
params(
|
||||
Pagination
|
||||
),
|
||||
path = "/v2/mixnodes",
|
||||
responses(
|
||||
(status = 200, body = PagedMixnode)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::DEBUG, skip_all, fields(page=pagination.page, size=pagination.size))]
|
||||
async fn mixnodes(
|
||||
Query(pagination): Query<Pagination>,
|
||||
State(state): State<AppState>,
|
||||
) -> HttpResult<Json<PagedResult<Mixnode>>> {
|
||||
let db = state.db_pool();
|
||||
let res = state.cache().get_mixnodes_list(db).await;
|
||||
|
||||
Ok(Json(PagedResult::paginate(pagination, res)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in = Path)]
|
||||
struct MixIdParam {
|
||||
mix_id: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Mixnodes",
|
||||
get,
|
||||
params(
|
||||
MixIdParam
|
||||
),
|
||||
path = "/v2/mixnodes/{mix_id}",
|
||||
responses(
|
||||
(status = 200, body = Mixnode)
|
||||
)
|
||||
)]
|
||||
#[instrument(level = tracing::Level::DEBUG, skip_all, fields(mix_id = mix_id))]
|
||||
async fn get_mixnodes(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
State(state): State<AppState>,
|
||||
) -> HttpResult<Json<Mixnode>> {
|
||||
match mix_id.parse::<u32>() {
|
||||
Ok(parsed_mix_id) => {
|
||||
let res = state.cache().get_mixnodes_list(state.db_pool()).await;
|
||||
|
||||
match res.iter().find(|item| item.mix_id == parsed_mix_id) {
|
||||
Some(res) => Ok(Json(res.clone())),
|
||||
None => Err(HttpError::invalid_input(mix_id)),
|
||||
}
|
||||
}
|
||||
Err(_e) => Err(HttpError::invalid_input(mix_id)),
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Mixnodes",
|
||||
get,
|
||||
path = "/v2/mixnodes/stats",
|
||||
responses(
|
||||
(status = 200, body = Vec<DailyStats>)
|
||||
)
|
||||
)]
|
||||
async fn get_stats(State(state): State<AppState>) -> HttpResult<Json<Vec<DailyStats>>> {
|
||||
let stats = state.cache().get_mixnode_stats(state.db_pool()).await;
|
||||
Ok(Json(stats))
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user