From baad7e8467e87530ac5f6db159c658022ec38155 Mon Sep 17 00:00:00 2001 From: 0xterminator Date: Mon, 23 Dec 2024 19:56:30 +0200 Subject: [PATCH] feat(repo): Merge nats s3 hybrid publishing into main (#364) * feat(publisher): Extend NATS publishing by storing payloads in S3 (#348) * feat(publisher): Publish streams to S3 * feat(publisher): add Localstack for testing s3 locally * feat(publisher): Ignore localstack data * feat(publisher): Fix build errors from integrating S3Client * feat(publisher): Ensure published objects can be streamed Also update tests * feat(publisher): Consistently hash static payload for s3 path * feat(publisher): Use testnet or mainnet buckets based on the env * feat(publisher): Distinct env vars for S3 * fix(publisher): Remove unused cli arg (#353) * fix(publisher): Remove trailing hyphen in bucket name (#354) * fix(publisher): Don't defer error logging (#355) * build(repo): Update Cargo.lock * feat(repo): Added ws streamer service (#345) * feat(repo): Added draft ws version * feat(repo): Fixes after AJs merge * feat(repo): Added docker-compose script for creating S3 bucket * fix(repo): Added better deserialization * fix(publisher): re-allow ctrl c shutdown publisher process * refactor(repo): Disallow nested Cargo.lock * refactor(publisher): Include default S3 bucket for local development * feat(repo): Improved serializations * feat(repo): Removed binary for ws-streamer * feat(repo): Pass any wildcard to stream filtering * feat(repo): Added deliver policy to ws pattern for historical data * feat(repo): Switched to tokio-tungestenite * feat(repo): Small change to websocket subscriptions --------- Co-authored-by: AJ * feat(repo): Added webserver service and added docker build step * fix(repo): use cli instead of config.toml * fix(ws): remove duplicated nats clients * fix(ws): use nats_url instead of FuelNetwork * feat(repo): Removed unused args by infra for ws streamer * feat: Adjusts to release S3 + WebServer (#361) * fix(repo): Solving publisher slowness (#352) * feat(repo): add sv-emitter package * build(repo): adjust run-publisher * feat(repo): first version of consumer * feat(repo): add the rest of payloads * refactor(repo): reuse executors * fix(repo): tests * build(repo): configure devops to emitter/consumer * build(repo): update chart version * fix(repo): consumer cli argument * build(repo): docker action * build(repo): fix consumer chart * build(repo): fix consumer chart * build(repo): fix consumer chart * build(repo): chart * fix(repo): general stuff * build(repo): docker action * ci(repo): fix * fix(repo): chain config * fix(repo): docker images * feat(repo): improve docker build * refactor(repo): a lot of improvements * fix(repo): fix helm chart nats config * feat(repo): worked using mirror * fix(repo): adjust nats cluster config * build(repo): adjust github action * build(repo): update chart version * build(repo): fix docker images * build(repo): lint warnings * ci(repo): adjust docker action * build(repo): fix docker file * fix(repo): stream creation * build(repo): fix tls for nats * build(repo): fix chart * build(repo): fix chart * build(repo): fix chart * build(repo): fix chart * fix(repo): use sts instead of deployment for consumer * fix(repo): fix chart * fix(repo): remove initcontainer * fix(repo): update chart * fix(repo): fix resources on consumer * fix(repo): bump chart * fix(repo): bump chart * fix(repo): bump chart * build(repo): fix makefile * build(repo): add publisher into one single Dockerfile * ci(repo): fix docker publish action * build(repo): fix build docker * ci(repo): fix docker action * ci(repo): fix tests * ci(repo): fix ci.yaml * build(repo): adjust fuel dependencies features * build(repo): Add DNS/TLS configuration for Nats (#357) * build(repo): Add DNS/TLS configuration for Nats * build(repo): bump chart version * build(repo): fix chart * build(repo): fix chart * build(repo): fix secretName on nats client websocket config * build(repo): Update NATS dependency on chart (#358) * build(repo): Update NATS dependency on chart * build(repo): Bump chart version * refactor(repo): remove FuelNetwork from nats * refactor(repo): remove unused benches * refactor(repo): general improvements * fix(repo): general fixes * build(repo): adjust chart for new architecture * build(repo): adjust chart * build(repo): fix chart * build(repo): fix chart * build(repo): fix chart * build(repo): fix chart * build(repo): docker image * build(repo): bump chart * build(repo): fix emitter Dockerimage * build(repo): adjust workflow dispatch for docker * build(repo): docker action * build(repo): fix publisher config * build(repo): bump chart * build(repo): general fixes and adjustments * build(repo): bump chart * fix(repo): s3 client workaround * refactor(repo): rename crates to be consistent * build(repo): bump chart * build(repo): some adjusts * fix(consumer): remove max_messages * refactor(repo): change s3 path * build(repo): bump chart * refactor(repo): perf improvements * refactor(webserver): send decoded payload direct on websocket * feat(repo): integrate rust sdk with websockets * build(repo): fix tests * docs(repo): adjust main readme --------- Co-authored-by: AJ <34186192+Jurshsmith@users.noreply.github.com> Co-authored-by: Pedro Nauck Co-authored-by: AJ --- .env.sample | 28 +- .github/CODEOWNERS | 2 +- .github/actions/setup-rust/action.yaml | 12 +- .github/workflows/ci.yaml | 99 +- .github/workflows/docker_publish.yaml | 26 +- .github/workflows/helm_publish.yaml | 97 +- .github/workflows/publish_release.yaml | 12 +- .github/workflows/update_deps.yaml | 119 - .gitignore | 7 +- .typos.toml | 3 +- Cargo.lock | 1464 ++-- Cargo.toml | 23 +- Makefile | 86 +- README.md | 31 +- Tiltfile | 76 +- benches/bench-consumers/Cargo.toml | 30 - benches/bench-consumers/README.md | 13 - benches/bench-consumers/benches/consumers.rs | 43 - benches/bench-consumers/src/lib.rs | 1 - benches/bench-consumers/src/main.rs | 10 - .../src/runners/benchmark_results.rs | 103 - benches/bench-consumers/src/runners/mod.rs | 5 - .../bench-consumers/src/runners/runner_all.rs | 25 - .../src/runners/runner_consumer.rs | 55 - .../src/runners/runner_kv_watcher.rs | 40 - .../src/runners/runner_subscription.rs | 35 - benches/data-parser/benches/deserialize.rs | 1 - benches/load-tester/Cargo.toml | 22 - benches/load-tester/README.md | 9 - benches/load-tester/src/lib.rs | 1 - benches/load-tester/src/main.rs | 16 - benches/load-tester/src/runners/cli.rs | 33 - benches/load-tester/src/runners/mod.rs | 4 - benches/load-tester/src/runners/results.rs | 118 - benches/load-tester/src/runners/runner_all.rs | 247 - .../src/runners/runner_streamable.rs | 42 - benches/nats-publisher/Cargo.toml | 28 - benches/nats-publisher/README.md | 18 - benches/nats-publisher/config/nats.conf | 5 - benches/nats-publisher/src/lib.rs | 2 - benches/nats-publisher/src/main.rs | 78 - benches/nats-publisher/src/utils/blocks.rs | 93 - benches/nats-publisher/src/utils/mod.rs | 3 - benches/nats-publisher/src/utils/nats.rs | 130 - benches/nats-publisher/src/utils/tx.rs | 129 - .../charts/fuel-streams-publisher/.helmignore | 23 - .../charts/fuel-streams-publisher/Chart.yaml | 21 - .../templates/_helpers.tpl | 62 - .../fuel-streams-publisher/templates/hpa.yaml | 32 - .../templates/service.yaml | 17 - .../templates/serviceaccount.yaml | 13 - .../templates/statefulset.yaml | 147 - .../charts/fuel-streams-publisher/values.yaml | 124 - cluster/charts/fuel-streams/Chart.yaml | 7 +- .../charts/fuel-streams/templates/_blocks.tpl | 6 +- .../fuel-streams/templates/_helpers.tpl | 2 +- .../fuel-streams/templates/common-config.yaml | 21 + .../templates/consumer/statefulset.yaml | 25 +- .../templates/publisher/statefulset.yaml | 136 +- .../templates/webserver/deployment.yaml | 29 +- .../service.yaml} | 29 +- .../fuel-streams/tests/certificate_test.yaml | 2 +- .../tests/consumer/deployment_test.yaml | 8 +- .../tests/publisher/statefulset.yaml | 14 +- .../tests/webserver/deployment_test.yaml | 20 +- cluster/charts/fuel-streams/values-local.yaml | 104 +- cluster/charts/fuel-streams/values.yaml | 313 +- cluster/docker/docker-compose.yml | 62 +- cluster/docker/init-localstack.sh | 8 + cluster/docker/nats-config/accounts.conf | 15 + cluster/docker/nats-config/client.conf | 18 + cluster/docker/nats-config/core.conf | 13 + cluster/docker/nats-config/publisher.conf | 18 + cluster/docker/nats.conf | 46 - cluster/docker/sv-consumer.Dockerfile | 3 +- cluster/docker/sv-publisher.Dockerfile | 81 + cluster/docker/sv-webserver.Dockerfile | 75 + cluster/scripts/build_docker.sh | 17 +- cluster/scripts/build_streamer.sh | 16 + cluster/scripts/gen_env_secret.sh | 17 +- cluster/scripts/setup_k8s.sh | 39 - cluster/scripts/setup_minikube.sh | 50 + crates/fuel-data-parser/src/lib.rs | 45 +- crates/fuel-streams-core/Cargo.toml | 5 +- crates/fuel-streams-core/README.md | 15 +- .../fuel-streams-core/src/fuel_core_like.rs | 2 +- crates/fuel-streams-core/src/inputs/mod.rs | 1 + crates/fuel-streams-core/src/lib.rs | 11 +- crates/fuel-streams-core/src/nats/mod.rs | 15 - .../src/nats/nats_client_opts.rs | 205 - crates/fuel-streams-core/src/receipts/mod.rs | 18 +- crates/fuel-streams-core/src/stream/error.rs | 21 +- .../src/stream/fuel_streams.rs | 118 +- .../src/stream/stream_encoding.rs | 33 +- .../src/stream/stream_impl.rs | 255 +- crates/fuel-streams-core/src/types.rs | 4 +- crates/fuel-streams-executors/src/blocks.rs | 43 +- crates/fuel-streams-executors/src/lib.rs | 43 +- .../src/transactions.rs | 86 +- crates/fuel-streams-nats/Cargo.toml | 29 + .../nats => fuel-streams-nats/src}/error.rs | 0 crates/fuel-streams-nats/src/lib.rs | 15 + .../src}/nats_client.rs | 14 +- .../fuel-streams-nats/src/nats_client_opts.rs | 227 + .../src}/nats_namespace.rs | 6 +- .../nats => fuel-streams-nats/src}/types.rs | 2 +- crates/fuel-streams-publisher/Cargo.lock | 7553 ----------------- crates/fuel-streams-publisher/src/lib.rs | 9 - crates/fuel-streams-publisher/src/main.rs | 67 - .../src/publisher/blocks_streams.rs | 397 - .../src/publisher/mod.rs | 166 - .../src/publisher/shutdown.rs | 89 - .../fuel-streams-publisher/src/server/http.rs | 121 - .../fuel-streams-publisher/src/server/mod.rs | 2 - .../src/telemetry/publisher.rs | 423 - crates/fuel-streams-storage/Cargo.toml | 31 + crates/fuel-streams-storage/src/lib.rs | 3 + crates/fuel-streams-storage/src/s3/mod.rs | 5 + .../fuel-streams-storage/src/s3/s3_client.rs | 289 + .../src/s3/s3_client_opts.rs | 120 + crates/fuel-streams/Cargo.toml | 23 +- crates/fuel-streams/README.md | 164 +- crates/fuel-streams/src/client/client_impl.rs | 167 +- crates/fuel-streams/src/client/connection.rs | 139 + crates/fuel-streams/src/client/error.rs | 32 +- crates/fuel-streams/src/client/mod.rs | 4 +- crates/fuel-streams/src/client/types.rs | 17 +- crates/fuel-streams/src/error.rs | 10 +- crates/fuel-streams/src/lib.rs | 12 +- crates/fuel-streams/src/networks/mod.rs | 93 + crates/fuel-streams/src/stream/error.rs | 17 - crates/fuel-streams/src/stream/mod.rs | 11 - crates/fuel-streams/src/stream/stream_impl.rs | 212 - crates/sv-consumer/Cargo.toml | 4 +- crates/sv-consumer/src/cli.rs | 10 +- crates/sv-consumer/src/lib.rs | 10 +- crates/sv-consumer/src/main.rs | 114 +- crates/sv-emitter/src/main.rs | 9 +- crates/sv-publisher/Cargo.toml | 51 + .../src/cli.rs | 23 +- crates/sv-publisher/src/lib.rs | 2 + crates/sv-publisher/src/main.rs | 213 + crates/sv-publisher/src/shutdown.rs | 104 + .../Cargo.toml | 40 +- .../README.md | 34 +- crates/sv-webserver/src/cli.rs | 54 + crates/sv-webserver/src/config.rs | 71 + crates/sv-webserver/src/lib.rs | 16 + crates/sv-webserver/src/main.rs | 46 + crates/sv-webserver/src/server/api.rs | 88 + crates/sv-webserver/src/server/auth.rs | 357 + crates/sv-webserver/src/server/context.rs | 69 + .../sv-webserver/src/server/http/handlers.rs | 73 + crates/sv-webserver/src/server/http/mod.rs | 2 + crates/sv-webserver/src/server/http/models.rs | 17 + .../src/server/middlewares/auth.rs | 109 + .../src/server/middlewares/mod.rs | 1 + crates/sv-webserver/src/server/mod.rs | 7 + .../src/server/state.rs | 16 +- crates/sv-webserver/src/server/ws/errors.rs | 20 + crates/sv-webserver/src/server/ws/mod.rs | 4 + crates/sv-webserver/src/server/ws/models.rs | 106 + crates/sv-webserver/src/server/ws/socket.rs | 378 + crates/sv-webserver/src/server/ws/state.rs | 55 + .../src/telemetry/elastic_search.rs | 0 crates/sv-webserver/src/telemetry/metrics.rs | 190 + .../src/telemetry/mod.rs | 113 +- .../src/telemetry/runtime.rs | 0 .../src/telemetry/system.rs | 0 examples/Cargo.toml | 5 - examples/blocks.rs | 30 +- examples/inputs.rs | 30 +- examples/logs.rs | 30 +- examples/multiple-streams.rs | 360 - examples/outputs.rs | 30 +- examples/receipts.rs | 103 +- examples/transactions.rs | 33 +- examples/utxos.rs | 30 +- knope.toml | 21 +- scripts/run_publisher.sh | 13 +- scripts/run_webserver.sh | 91 + scripts/set_env.sh | 6 +- tarpaulin.toml | 4 +- tests/Cargo.toml | 11 - tests/src/lib.rs | 39 +- tests/src/main.rs | 142 - tests/tests/client.rs | 657 +- tests/tests/publisher.rs | 826 +- tests/tests/stream.rs | 495 +- 189 files changed, 7015 insertions(+), 14793 deletions(-) delete mode 100644 .github/workflows/update_deps.yaml delete mode 100644 benches/bench-consumers/Cargo.toml delete mode 100644 benches/bench-consumers/README.md delete mode 100644 benches/bench-consumers/benches/consumers.rs delete mode 100644 benches/bench-consumers/src/lib.rs delete mode 100644 benches/bench-consumers/src/main.rs delete mode 100644 benches/bench-consumers/src/runners/benchmark_results.rs delete mode 100644 benches/bench-consumers/src/runners/mod.rs delete mode 100644 benches/bench-consumers/src/runners/runner_all.rs delete mode 100644 benches/bench-consumers/src/runners/runner_consumer.rs delete mode 100644 benches/bench-consumers/src/runners/runner_kv_watcher.rs delete mode 100644 benches/bench-consumers/src/runners/runner_subscription.rs delete mode 100644 benches/load-tester/Cargo.toml delete mode 100644 benches/load-tester/README.md delete mode 100644 benches/load-tester/src/lib.rs delete mode 100644 benches/load-tester/src/main.rs delete mode 100644 benches/load-tester/src/runners/cli.rs delete mode 100644 benches/load-tester/src/runners/mod.rs delete mode 100644 benches/load-tester/src/runners/results.rs delete mode 100644 benches/load-tester/src/runners/runner_all.rs delete mode 100644 benches/load-tester/src/runners/runner_streamable.rs delete mode 100644 benches/nats-publisher/Cargo.toml delete mode 100644 benches/nats-publisher/README.md delete mode 100644 benches/nats-publisher/config/nats.conf delete mode 100644 benches/nats-publisher/src/lib.rs delete mode 100644 benches/nats-publisher/src/main.rs delete mode 100644 benches/nats-publisher/src/utils/blocks.rs delete mode 100644 benches/nats-publisher/src/utils/mod.rs delete mode 100644 benches/nats-publisher/src/utils/nats.rs delete mode 100644 benches/nats-publisher/src/utils/tx.rs delete mode 100644 cluster/charts/fuel-streams-publisher/.helmignore delete mode 100644 cluster/charts/fuel-streams-publisher/Chart.yaml delete mode 100644 cluster/charts/fuel-streams-publisher/templates/_helpers.tpl delete mode 100644 cluster/charts/fuel-streams-publisher/templates/hpa.yaml delete mode 100644 cluster/charts/fuel-streams-publisher/templates/service.yaml delete mode 100644 cluster/charts/fuel-streams-publisher/templates/serviceaccount.yaml delete mode 100644 cluster/charts/fuel-streams-publisher/templates/statefulset.yaml delete mode 100644 cluster/charts/fuel-streams-publisher/values.yaml create mode 100644 cluster/charts/fuel-streams/templates/common-config.yaml rename cluster/charts/fuel-streams/templates/{nats/external-service.yaml => webserver/service.yaml} (64%) create mode 100755 cluster/docker/init-localstack.sh create mode 100644 cluster/docker/nats-config/accounts.conf create mode 100644 cluster/docker/nats-config/client.conf create mode 100644 cluster/docker/nats-config/core.conf create mode 100644 cluster/docker/nats-config/publisher.conf delete mode 100644 cluster/docker/nats.conf create mode 100644 cluster/docker/sv-publisher.Dockerfile create mode 100644 cluster/docker/sv-webserver.Dockerfile create mode 100755 cluster/scripts/build_streamer.sh delete mode 100755 cluster/scripts/setup_k8s.sh delete mode 100644 crates/fuel-streams-core/src/nats/mod.rs delete mode 100644 crates/fuel-streams-core/src/nats/nats_client_opts.rs create mode 100644 crates/fuel-streams-nats/Cargo.toml rename crates/{fuel-streams-core/src/nats => fuel-streams-nats/src}/error.rs (100%) create mode 100644 crates/fuel-streams-nats/src/lib.rs rename crates/{fuel-streams-core/src/nats => fuel-streams-nats/src}/nats_client.rs (88%) create mode 100644 crates/fuel-streams-nats/src/nats_client_opts.rs rename crates/{fuel-streams-core/src/nats => fuel-streams-nats/src}/nats_namespace.rs (92%) rename crates/{fuel-streams-core/src/nats => fuel-streams-nats/src}/types.rs (92%) delete mode 100644 crates/fuel-streams-publisher/Cargo.lock delete mode 100644 crates/fuel-streams-publisher/src/lib.rs delete mode 100644 crates/fuel-streams-publisher/src/main.rs delete mode 100644 crates/fuel-streams-publisher/src/publisher/blocks_streams.rs delete mode 100644 crates/fuel-streams-publisher/src/publisher/mod.rs delete mode 100644 crates/fuel-streams-publisher/src/publisher/shutdown.rs delete mode 100644 crates/fuel-streams-publisher/src/server/http.rs delete mode 100644 crates/fuel-streams-publisher/src/server/mod.rs delete mode 100644 crates/fuel-streams-publisher/src/telemetry/publisher.rs create mode 100644 crates/fuel-streams-storage/Cargo.toml create mode 100644 crates/fuel-streams-storage/src/lib.rs create mode 100644 crates/fuel-streams-storage/src/s3/mod.rs create mode 100644 crates/fuel-streams-storage/src/s3/s3_client.rs create mode 100644 crates/fuel-streams-storage/src/s3/s3_client_opts.rs create mode 100644 crates/fuel-streams/src/client/connection.rs create mode 100644 crates/fuel-streams/src/networks/mod.rs delete mode 100644 crates/fuel-streams/src/stream/error.rs delete mode 100644 crates/fuel-streams/src/stream/mod.rs delete mode 100644 crates/fuel-streams/src/stream/stream_impl.rs create mode 100644 crates/sv-publisher/Cargo.toml rename crates/{fuel-streams-publisher => sv-publisher}/src/cli.rs (63%) create mode 100644 crates/sv-publisher/src/lib.rs create mode 100644 crates/sv-publisher/src/main.rs create mode 100644 crates/sv-publisher/src/shutdown.rs rename crates/{fuel-streams-publisher => sv-webserver}/Cargo.toml (69%) rename crates/{fuel-streams-publisher => sv-webserver}/README.md (65%) create mode 100644 crates/sv-webserver/src/cli.rs create mode 100644 crates/sv-webserver/src/config.rs create mode 100644 crates/sv-webserver/src/lib.rs create mode 100644 crates/sv-webserver/src/main.rs create mode 100644 crates/sv-webserver/src/server/api.rs create mode 100755 crates/sv-webserver/src/server/auth.rs create mode 100644 crates/sv-webserver/src/server/context.rs create mode 100644 crates/sv-webserver/src/server/http/handlers.rs create mode 100644 crates/sv-webserver/src/server/http/mod.rs create mode 100644 crates/sv-webserver/src/server/http/models.rs create mode 100644 crates/sv-webserver/src/server/middlewares/auth.rs create mode 100644 crates/sv-webserver/src/server/middlewares/mod.rs create mode 100644 crates/sv-webserver/src/server/mod.rs rename crates/{fuel-streams-publisher => sv-webserver}/src/server/state.rs (90%) create mode 100644 crates/sv-webserver/src/server/ws/errors.rs create mode 100644 crates/sv-webserver/src/server/ws/mod.rs create mode 100644 crates/sv-webserver/src/server/ws/models.rs create mode 100644 crates/sv-webserver/src/server/ws/socket.rs create mode 100644 crates/sv-webserver/src/server/ws/state.rs rename crates/{fuel-streams-publisher => sv-webserver}/src/telemetry/elastic_search.rs (100%) create mode 100644 crates/sv-webserver/src/telemetry/metrics.rs rename crates/{fuel-streams-publisher => sv-webserver}/src/telemetry/mod.rs (66%) rename crates/{fuel-streams-publisher => sv-webserver}/src/telemetry/runtime.rs (100%) rename crates/{fuel-streams-publisher => sv-webserver}/src/telemetry/system.rs (100%) delete mode 100644 examples/multiple-streams.rs create mode 100755 scripts/run_webserver.sh delete mode 100644 tests/src/main.rs diff --git a/.env.sample b/.env.sample index 0f57a6f3..c111ace9 100644 --- a/.env.sample +++ b/.env.sample @@ -1,11 +1,31 @@ -# Common Configuration +# Authentication & Security KEYPAIR=generated-p2p-secret +JWT_AUTH_SECRET=generated-secret + +# AWS S3 Configuration +AWS_ACCESS_KEY_ID=test +AWS_SECRET_ACCESS_KEY=test +AWS_ENDPOINT_URL=http://localhost:4566 +AWS_REGION=us-east-1 +AWS_S3_ENABLED=false +AWS_S3_BUCKET_NAME=fuel-streams-local + +# NATS Configuration NATS_URL=nats://localhost:4222 -NATS_ADMIN_PASS=generated-secret -NATS_SYSTEM_PASS=generated-secret +NATS_PUBLISHER_URL=nats://localhost:4333 +NATS_SYSTEM_USER=sys +NATS_SYSTEM_PASS=sys +NATS_ADMIN_USER=admin +NATS_ADMIN_PASS=admin +NATS_PUBLIC_USER=default_user +NATS_PUBLIC_PASS="" + +# Monitoring & Logging USE_ELASTIC_LOGGING=false -USE_PUBLISHER_METRICS=true +USE_METRICS=true PUBLISHER_MAX_THREADS=16 + +# Elasticsearch Configuration ELASTICSEARCH_URL=http://127.0.0.1:9200 ELASTICSEARCH_USERNAME=elastic ELASTICSEARCH_PASSWORD=generated-secret diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5e47a289..b0ae8132 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @luizstacio @pedronauck @jurshsmith @0xterminator +* @luizstacio @pedronauck @jurshsmith @0xterminator @cue8it diff --git a/.github/actions/setup-rust/action.yaml b/.github/actions/setup-rust/action.yaml index 11d83426..600f16ef 100644 --- a/.github/actions/setup-rust/action.yaml +++ b/.github/actions/setup-rust/action.yaml @@ -21,8 +21,16 @@ runs: - name: Create .env file with NATS environment variables shell: bash run: | - echo "NATS_ADMIN_PASS=${NATS_ADMIN_PASS:-default_pass}" >> .env - echo "NATS_PUBLIC_PASS=${NATS_PUBLIC_PASS:-temp-public-pass}" >> .env + set_env_var() { + echo "$1=${!1:-$2}" >> $GITHUB_ENV + echo "$1=${!1:-$2}" >> .env + } + set_env_var "NATS_SYSTEM_USER" "sys" + set_env_var "NATS_SYSTEM_PASS" "sys" + set_env_var "NATS_ADMIN_USER" "admin" + set_env_var "NATS_ADMIN_PASS" "admin" + set_env_var "NATS_PUBLIC_USER" "default_user" + set_env_var "NATS_PUBLIC_PASS" "" - name: Install Rust uses: dtolnay/rust-toolchain@master diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fd142d16..631c8a37 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -43,10 +43,13 @@ jobs: deps release core - publisher data-parser + networks fuel-streams macros + publisher + storage + ws lockfile: name: Validate Lockfile @@ -206,18 +209,28 @@ jobs: runs-on: ubuntu-latest env: NATS_URL: nats://127.0.0.1:4222 - NATS_ADMIN_PASS: secret - NATS_PUBLIC_PASS: secret + NATS_PUBLISHER_URL: nats://127.0.0.1:4333 + NATS_SYSTEM_USER: sys + NATS_SYSTEM_PASSWORD: sys + NATS_ADMIN_USER: admin + NATS_ADMIN_PASS: admin + NATS_PUBLIC_USER: default_user + NATS_PUBLIC_PASS: "" + AWS_ACCESS_KEY_ID: test + AWS_SECRET_ACCESS_KEY: test + AWS_REGION: us-east-1 + AWS_ENDPOINT_URL: http://localhost:4566 + AWS_S3_BUCKET_NAME: fuel-streams-test strategy: fail-fast: false matrix: package: - # - fuel-data-parser + - fuel-data-parser - fuel-streams - fuel-streams-core - fuel-streams-macros - - fuel-streams-publisher - + - sv-webserver + - sv-publisher steps: - uses: actions/checkout@v4 @@ -251,7 +264,9 @@ jobs: fail-fast: false matrix: package: - - fuel-streams-publisher + - sv-consumer + - sv-publisher + - sv-webserver is_release: - ${{ github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' }} platform: @@ -320,34 +335,34 @@ jobs: rustup target add ${{ matrix.platform.target }} cargo build --release --locked --target ${{ matrix.platform.target }} --package ${{ matrix.package }} - - name: Strip binaries - run: ./scripts/strip-binary.sh "${{ matrix.platform.target }}" - - - name: Set Artifact Name - id: artifact-name - shell: bash - run: | - echo "value=${{ matrix.package }}-${{ matrix.platform.os_name }}" >> $GITHUB_OUTPUT - - - name: Package as archive - shell: bash - run: | - cd target/${{ matrix.platform.target }}/release - tar czvf ../../../${{ steps.artifact-name.outputs.value }}.tar.gz ${{ matrix.package }} - cd - - - - name: Publish release artifacts - uses: actions/upload-artifact@v4 - if: >- - (github.event_name == 'push' && - github.ref == 'refs/heads/main' && - contains(github.event.head_commit.message, 'ci(release): Preparing')) || - github.event_name == 'workflow_dispatch' - with: - name: ${{ steps.artifact-name.outputs.value }} - path: ${{ matrix.package }}-* - if-no-files-found: error - retention-days: 30 + # - name: Strip binaries + # run: ./scripts/strip-binary.sh "${{ matrix.platform.target }}" + + # - name: Set Artifact Name + # id: artifact-name + # shell: bash + # run: | + # echo "value=${{ matrix.package }}-${{ matrix.platform.os_name }}" >> $GITHUB_OUTPUT + + # - name: Package as archive + # shell: bash + # run: | + # cd target/${{ matrix.platform.target }}/release + # tar czvf ../../../${{ steps.artifact-name.outputs.value }}.tar.gz ${{ matrix.package }} + # cd - + + # - name: Publish release artifacts + # uses: actions/upload-artifact@v4 + # if: >- + # (github.event_name == 'push' && + # github.ref == 'refs/heads/main' && + # contains(github.event.head_commit.message, 'ci(release): Preparing')) || + # github.event_name == 'workflow_dispatch' + # with: + # name: ${{ steps.artifact-name.outputs.value }} + # path: ${{ matrix.package }}-* + # if-no-files-found: error + # retention-days: 30 release: name: Create Release with Knope @@ -368,14 +383,14 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: Download Artifacts - uses: actions/download-artifact@v4 - with: - path: artifacts - merge-multiple: true + # - name: Download Artifacts + # uses: actions/download-artifact@v4 + # with: + # path: artifacts + # merge-multiple: true - - name: List Artifacts - run: ls -R artifacts + # - name: List Artifacts + # run: ls -R artifacts - name: Run Knope Action uses: knope-dev/action@v2.1.0 diff --git a/.github/workflows/docker_publish.yaml b/.github/workflows/docker_publish.yaml index ed2d0ba1..6aff171b 100644 --- a/.github/workflows/docker_publish.yaml +++ b/.github/workflows/docker_publish.yaml @@ -2,6 +2,17 @@ name: Build and publish Docker image on: workflow_dispatch: + inputs: + package: + type: choice + description: "Package to build and publish" + default: "all" + required: true + options: + - all + - sv-publisher + - sv-webserver + - sv-consumer push: branches: - main @@ -23,10 +34,10 @@ jobs: strategy: matrix: package: - - name: fuel-streams-publisher - image: cluster/docker/fuel-core.Dockerfile - - name: sv-emitter - image: cluster/docker/fuel-core.Dockerfile + - name: sv-webserver + image: cluster/docker/sv-webserver.Dockerfile + - name: sv-publisher + image: cluster/docker/sv-publisher.Dockerfile - name: sv-consumer image: cluster/docker/sv-consumer.Dockerfile steps: @@ -36,7 +47,10 @@ jobs: id: sha run: echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - - name: Build and push Docker (${{ steps.sha.outputs.short_sha }}) + - name: Build and push Docker for ${matrix.package.name} (${{ steps.sha.outputs.short_sha }}) + if: | + (github.event_name == 'workflow_dispatch' && (github.event.inputs.package == 'all' || github.event.inputs.package == matrix.package.name)) || + github.event_name != 'workflow_dispatch' uses: ./.github/actions/docker-publish id: publish with: @@ -44,5 +58,3 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} image: ghcr.io/fuellabs/${{ matrix.package.name }} dockerfile: ${{ matrix.package.image }} - build-args: |- - PACKAGE_NAME=${{ matrix.package.name }} diff --git a/.github/workflows/helm_publish.yaml b/.github/workflows/helm_publish.yaml index 932d54f6..8dc9de93 100644 --- a/.github/workflows/helm_publish.yaml +++ b/.github/workflows/helm_publish.yaml @@ -2,87 +2,41 @@ name: Build and Publish Helm Chart on: workflow_dispatch: - inputs: - chart: - description: "Select the Helm chart to deploy" - required: true - type: choice - options: - - fuel-streams-publisher - - fuel-streams push: branches: - main - paths: - - cluster/charts/fuel-streams-publisher/Chart.yaml - - cluster/charts/fuel-streams/Chart.yaml + release: + types: + - published + +env: + CHART_NAME: fuel-streams + CHART_PATH: cluster/charts/fuel-streams permissions: contents: read + packages: write + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: helm-release: - name: Build Helm Charts runs-on: ubuntu-latest - if: | - github.event_name == 'workflow_dispatch' || - (github.event_name == 'release' && github.event.action == 'published') || - github.ref == 'refs/heads/main' || - github.event_name == 'pull_request' - permissions: - contents: read - packages: write steps: - - name: Check out code - uses: actions/checkout@v4 - - - name: Determine charts to process - id: charts - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "charts=${{ github.event.inputs.chart }}" >> $GITHUB_OUTPUT - else - echo "charts=fuel-streams-publisher fuel-streams" >> $GITHUB_OUTPUT - fi - - - name: Validate Chart Names - run: | - for chart in ${{ steps.charts.outputs.charts }}; do - if [ ! -d "cluster/charts/$chart" ]; then - echo "Error: Chart '$chart' does not exist." - exit 1 - fi - done - + - uses: actions/checkout@v4 - name: Helm Dependencies Update run: | - set -e - for chart in ${{ steps.charts.outputs.charts }}; do - echo "Updating dependencies for $chart" - helm dependency update cluster/charts/$chart - done + cd ${{ env.CHART_PATH }} && helm dependency update - - name: Get chart versions - id: versions + - name: Get chart version + id: version run: | - publisher_version=$(awk '/^version:/ {print $2}' cluster/charts/fuel-streams-publisher/Chart.yaml) - streams_version=$(awk '/^version:/ {print $2}' cluster/charts/fuel-streams/Chart.yaml) - echo "publisher_version=$publisher_version" >> $GITHUB_OUTPUT - echo "streams_version=$streams_version" >> $GITHUB_OUTPUT - - - name: "Build chart: [fuel-streams-publisher v${{ steps.versions.outputs.publisher_version }}]" - if: contains(steps.charts.outputs.charts, 'fuel-streams-publisher') - uses: bsord/helm-push@v4.1.0 - with: - useOCIRegistry: true - registry-url: oci://ghcr.io/fuellabs/helmcharts - username: ${{ github.repository_owner }} - access-token: ${{ secrets.GITHUB_TOKEN }} - force: true - chart-folder: ./cluster/charts/fuel-streams-publisher + version=$(awk '/^version:/ {print $2}' ${{ env.CHART_PATH }}/Chart.yaml) + echo "version=$version" >> $GITHUB_OUTPUT - - name: "Build chart: [fuel-streams v${{ steps.versions.outputs.streams_version }}]" - if: contains(steps.charts.outputs.charts, 'fuel-streams') + - name: "Build chart: [${{ env.CHART_NAME }} v${{ steps.version.outputs.version }}]" uses: bsord/helm-push@v4.1.0 with: useOCIRegistry: true @@ -90,20 +44,11 @@ jobs: username: ${{ github.repository_owner }} access-token: ${{ secrets.GITHUB_TOKEN }} force: true - chart-folder: ./cluster/charts/fuel-streams + chart-folder: ${{ env.CHART_PATH }} - name: Build Summary run: |- echo "### Helm Charts Build Summary 📊" >> $GITHUB_STEP_SUMMARY echo "| Chart | Version | Status |" >> $GITHUB_STEP_SUMMARY echo "|-------|---------|--------|" >> $GITHUB_STEP_SUMMARY - - for chart in ${{ steps.charts.outputs.charts }}; do - version="" - if [ "$chart" = "fuel-streams-publisher" ]; then - version="${{ steps.versions.outputs.publisher_version }}" - elif [ "$chart" = "fuel-streams" ]; then - version="${{ steps.versions.outputs.streams_version }}" - fi - echo "| $chart | $version | ✅ Published |" >> $GITHUB_STEP_SUMMARY - done + echo "| ${{ env.CHART_NAME }} | ${{ steps.version.outputs.version }} | ✅ Published |" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/publish_release.yaml b/.github/workflows/publish_release.yaml index 4dbc4f19..bd4edc6a 100644 --- a/.github/workflows/publish_release.yaml +++ b/.github/workflows/publish_release.yaml @@ -19,17 +19,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set NATS environment variables - shell: bash - run: | - set_env_var() { - echo "$1=${!1:-$2}" >> $GITHUB_ENV - echo "$1=${!1:-$2}" >> .env - } - set_env_var "NATS_ADMIN_PASS" "null" - - name: Install toolchain - uses: dtolnay/rust-toolchain@master + - name: Install Rust + uses: ./.github/actions/setup-rust with: toolchain: ${{ env.RUST_VERSION }} target: x86_64-unknown-linux-gnu,wasm32-unknown-unknown diff --git a/.github/workflows/update_deps.yaml b/.github/workflows/update_deps.yaml deleted file mode 100644 index 17f444b7..00000000 --- a/.github/workflows/update_deps.yaml +++ /dev/null @@ -1,119 +0,0 @@ -name: Bump dependencies in Cargo.lock - -on: - schedule: - # Run weekly - - cron: 0 0 * * Sun - workflow_dispatch: - # Allows manual triggering of the workflow - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -permissions: - contents: write - pull-requests: write - -defaults: - run: - shell: bash - -env: - # Prevents cargo from complaining about unstable features - RUSTC_BOOTSTRAP: 1 - RUST_VERSION: 1.81.0 - PR_TITLE: "chore(deps): weekly `cargo update`" - PR_MESSAGE: | - Automation to keep dependencies in `Cargo.lock` current. - The following is the output from `cargo update`: - COMMIT_MESSAGE: "chore(deps): update dependencies\n\n" - -jobs: - update: - name: Update Dependencies - runs-on: ubuntu-latest - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Install Rust - uses: ./.github/actions/setup-rust - with: - toolchain: stable - - - name: Update Cargo Dependencies - run: | - cargo update 2>&1 | sed '/crates.io index/d' | tee -a cargo_update.log - if [ ! -s cargo_update.log ]; then - echo "No updates found" > cargo_update.log - fi - - - name: Cargo audit dependencies - uses: actions-rs/audit-check@v1.2.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Upload Cargo.lock Artifact - uses: actions/upload-artifact@v4 - with: - name: Cargo-lock - path: Cargo.lock - retention-days: 1 - - - name: Upload Cargo Update Log Artifact - uses: actions/upload-artifact@v4 - with: - name: cargo-updates - path: cargo_update.log - retention-days: 1 - - pr: - name: Create or Update Pull Request - needs: update - runs-on: ubuntu-latest - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Download Cargo.lock from Update Job - uses: actions/download-artifact@v4 - with: - name: Cargo-lock - - - name: Download Cargo Update Log from Update Job - uses: actions/download-artifact@v4 - with: - name: cargo-updates - - - name: Craft PR Body and Commit Message - run: | - echo "${{ env.COMMIT_MESSAGE }}" > commit.txt - cat cargo_update.log >> commit.txt - echo "${{ env.PR_MESSAGE }}" > body.md - echo '```txt' >> body.md - cat cargo_update.log >> body.md - echo '```' >> body.md - - - name: Commit Changes - run: | - git config user.name github-actions - git config user.email github-actions@github.com - git switch --force-create cargo_update - git add ./Cargo.lock - git commit --no-verify --file=commit.txt - - - name: Create or Update Pull Request - uses: peter-evans/create-pull-request@v6 - with: - commit-message: ${{ env.COMMIT_MESSAGE }} - title: ${{ env.PR_TITLE }} - body: | - ${{ env.PR_MESSAGE }} - ```txt - $(cat cargo_update.log) - ``` - branch: cargo_update - labels: dependencies - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 5e8b6a25..8607f4ff 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,8 @@ profile.json coverage/ docs/ **/**/charts/**.tgz -values-publisher-secrets.yaml -.vscode/launch.json +values-secrets.yaml +values-publisher-env.yaml +localstack-data +.vscode +**/Cargo.lock diff --git a/.typos.toml b/.typos.toml index e8e34136..87a65555 100644 --- a/.typos.toml +++ b/.typos.toml @@ -1,8 +1,7 @@ [files] extend-exclude = [ "pnpm-lock.yaml", - "crates/fuel-streams-publisher/README.md", - "crates/fuel-streams-publisher/src/elastic.rs", + "crates/sv-webserver/README.md", "docker/chain-config", "docker/monitoring", "cluster", diff --git a/Cargo.lock b/Cargo.lock index 890561a6..e68b52c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -210,6 +210,20 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "actix-ws" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3a1fb4f9f2794b0aadaf2ba5f14a6f034c7e86957b458c506a8cb75953f2d99" +dependencies = [ + "actix-codec", + "actix-http", + "actix-web", + "bytestring", + "futures-core", + "tokio", +] + [[package]] name = "addr2line" version = "0.24.2" @@ -396,15 +410,6 @@ version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - [[package]] name = "arbitrary" version = "1.4.1" @@ -477,12 +482,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" -[[package]] -name = "assert_matches" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" - [[package]] name = "async-compression" version = "0.4.18" @@ -505,9 +504,9 @@ dependencies = [ [[package]] name = "async-graphql" -version = "7.0.11" +version = "7.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba6d24703c5adc5ba9116901b92ee4e4c0643c01a56c4fd303f3818638d7449" +checksum = "59fd6bd734afb8b6e4d0f84a3e77305ce0a7ccc60d70f6001cb5e1c3f38d8ff1" dependencies = [ "async-graphql-derive", "async-graphql-parser", @@ -524,7 +523,6 @@ dependencies = [ "mime", "multer", "num-traits", - "once_cell", "pin-project-lite", "regex", "serde", @@ -538,9 +536,9 @@ dependencies = [ [[package]] name = "async-graphql-derive" -version = "7.0.11" +version = "7.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a94c2d176893486bd37cd1b6defadd999f7357bf5804e92f510c08bcf16c538f" +checksum = "ac38b4dd452d529d6c0248b51df23603f0a875770352e26ae8c346ce6c149b3e" dependencies = [ "Inflector", "async-graphql-parser", @@ -555,9 +553,9 @@ dependencies = [ [[package]] name = "async-graphql-parser" -version = "7.0.11" +version = "7.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79272bdbf26af97866e149f05b2b546edb5c00e51b5f916289931ed233e208ad" +checksum = "42d271ddda2f55b13970928abbcbc3423cfc18187c60e8769b48f21a93b7adaa" dependencies = [ "async-graphql-value", "pest", @@ -567,9 +565,9 @@ dependencies = [ [[package]] name = "async-graphql-value" -version = "7.0.11" +version = "7.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef5ec94176a12a8cbe985cd73f2e54dc9c702c88c766bdef12f1f3a67cedbee1" +checksum = "aefe909173a037eaf3281b046dc22580b59a38b765d7b8d5116f2ffef098048d" dependencies = [ "bytes", "indexmap 2.7.0", @@ -767,6 +765,389 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "aws-config" +version = "1.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json 0.60.7", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 0.2.12", + "ring 0.17.8", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-runtime" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5ac934720fbb46206292d2c75b57e67acfc56fe7dfd34fb9a02334af08409ea" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-s3" +version = "1.65.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ba2c5c0f2618937ce3d4a5ad574b86775576fa24006bcb3128c6e2cbf3c34e" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json 0.61.1", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac 0.12.1", + "http 0.2.12", + "http-body 0.4.6", + "lru", + "once_cell", + "percent-encoding", + "regex-lite", + "sha2 0.10.8", + "tracing", + "url", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ca43a4ef210894f93096039ef1d6fa4ad3edfabb3be92b80908b9f2e4b4eab" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json 0.61.1", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abaf490c2e48eed0bb8e2da2fb08405647bd7f253996e0f93b981958ea0f73b0" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json 0.61.1", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b68fde0d69c8bfdc1060ea7da21df3e39f6014da316783336deff0a9ec28f4bf" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json 0.61.1", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3820e0c08d0737872ff3c7c1f21ebbb6693d832312d6152bf18ef50a5471c2" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint 0.5.5", + "form_urlencoded", + "hex", + "hmac 0.12.1", + "http 0.2.12", + "http 1.2.0", + "once_cell", + "p256 0.11.1", + "percent-encoding", + "ring 0.17.8", + "sha2 0.10.8", + "subtle", + "time", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aa8ff1492fd9fb99ae28e8467af0dbbb7c31512b16fabf1a0f10d7bb6ef78bb" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-checksums" +version = "0.60.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1a71073fca26775c8b5189175ea8863afb1c9ea2cceb02a5de5ad9dfbaa795" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc32c", + "crc32fast", + "hex", + "http 0.2.12", + "http-body 0.4.6", + "md-5", + "pin-project-lite", + "sha1", + "sha2 0.10.8", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef7d0a272725f87e51ba2bf89f8c21e4df61b9e49ae1ac367a6d69916ef7c90" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.60.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8bc3e8fdc6b8d07d976e301c02fe553f72a39b7a9fea820e023268467d7ab6" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4e69cc50921eb913c6b662f8d909131bb3e6ad6cb6090d3a39b66fc5c52095" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f20685047ca9d6f17b994a07f629c813f08b5bce65523e47124879e60103d45" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "http-body 1.0.1", + "httparse", + "hyper 0.14.32", + "hyper-rustls 0.24.2", + "once_cell", + "pin-project-lite", + "pin-utils", + "rustls 0.21.12", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.2.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.2.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5221b91b3e441e6675310829fd8984801b772cb1546ef6c0e54dec9f1ac13fef" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + [[package]] name = "axum" version = "0.5.17" @@ -780,7 +1161,7 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.31", + "hyper 0.14.32", "itoa", "matchit 0.5.0", "memchr", @@ -811,7 +1192,7 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.31", + "hyper 0.14.32", "itoa", "matchit 0.7.3", "memchr", @@ -848,7 +1229,7 @@ dependencies = [ "rustversion", "serde", "sync_wrapper 1.0.2", - "tower 0.5.1", + "tower 0.5.2", "tower-layer", "tower-service", ] @@ -928,6 +1309,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base16ct" version = "0.2.0" @@ -952,6 +1339,16 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "base64ct" version = "1.6.0" @@ -964,22 +1361,6 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" -[[package]] -name = "bench-consumers" -version = "0.0.13" -dependencies = [ - "anyhow", - "async-nats", - "chrono", - "criterion", - "fuel-core-types 0.40.2", - "fuel-streams-core", - "futures", - "nats-publisher", - "statrs", - "tokio", -] - [[package]] name = "bincode" version = "1.3.3" @@ -1179,12 +1560,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "bytemuck" -version = "1.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" - [[package]] name = "byteorder" version = "1.5.0" @@ -1195,9 +1570,19 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" name = "bytes" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +dependencies = [ + "serde", +] + +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" dependencies = [ - "serde", + "bytes", + "either", ] [[package]] @@ -1270,9 +1655,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.2" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" dependencies = [ "jobserver", "libc", @@ -1326,9 +1711,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1552,15 +1937,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "unicode-width 0.1.14", - "windows-sys 0.52.0", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", ] [[package]] @@ -1584,18 +1969,18 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" dependencies = [ "proc-macro2", "quote", @@ -1739,13 +2124,13 @@ checksum = "210fbe6f98594963b46cc980f126a9ede5db9a3848ca65b71303bebdb01afcd9" dependencies = [ "bip32", "cosmos-sdk-proto", - "ecdsa", + "ecdsa 0.16.9", "eyre", "k256", "rand_core", "serde", "serde_json", - "signature", + "signature 2.2.0", "subtle-encoding", "tendermint 0.40.0", "thiserror 1.0.69", @@ -1900,6 +2285,15 @@ dependencies = [ "wasmtime-types", ] +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -1955,9 +2349,9 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1974,9 +2368,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" @@ -1984,6 +2378,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -2204,7 +2610,7 @@ dependencies = [ [[package]] name = "data-parser" -version = "0.0.13" +version = "0.0.14" dependencies = [ "criterion", "fuel-core-types 0.40.2", @@ -2229,6 +2635,16 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "der" version = "0.7.9" @@ -2434,18 +2850,30 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature 1.6.4", +] + [[package]] name = "ecdsa" version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der", + "der 0.7.9", "digest 0.10.7", - "elliptic-curve", - "rfc6979", - "signature", - "spki", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", + "signature 2.2.0", + "spki 0.7.3", ] [[package]] @@ -2454,8 +2882,8 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "pkcs8", - "signature", + "pkcs8 0.10.2", + "signature 2.2.0", ] [[package]] @@ -2482,7 +2910,7 @@ dependencies = [ "rand_core", "serde", "sha2 0.10.8", - "signature", + "signature 2.2.0", "subtle", "zeroize", ] @@ -2514,21 +2942,41 @@ dependencies = [ "void", ] +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct 0.1.1", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest 0.10.7", + "ff 0.12.1", + "generic-array", + "group 0.12.1", + "pkcs8 0.9.0", + "rand_core", + "sec1 0.3.0", + "subtle", + "zeroize", +] + [[package]] name = "elliptic-curve" version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct", - "crypto-bigint", + "base16ct 0.2.0", + "crypto-bigint 0.5.5", "digest 0.10.7", - "ff", + "ff 0.13.0", "generic-array", - "group", - "pkcs8", + "group 0.13.0", + "pkcs8 0.10.2", "rand_core", - "sec1", + "sec1 0.7.3", "subtle", "zeroize", ] @@ -2547,9 +2995,9 @@ checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" @@ -2741,7 +3189,7 @@ dependencies = [ "cargo_metadata", "chrono", "const-hex", - "elliptic-curve", + "elliptic-curve 0.13.8", "ethabi", "generic-array", "k256", @@ -2780,7 +3228,7 @@ dependencies = [ "hashers", "http 0.2.12", "instant", - "jsonwebtoken", + "jsonwebtoken 8.3.0", "once_cell", "pin-project", "reqwest 0.11.27", @@ -2788,7 +3236,7 @@ dependencies = [ "serde_json", "thiserror 1.0.69", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.20.1", "tracing", "tracing-futures", "url", @@ -2843,9 +3291,19 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "ff" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core", + "subtle", +] [[package]] name = "ff" @@ -2930,9 +3388,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "foreign-types" @@ -3034,7 +3492,7 @@ dependencies = [ "fuel-core-upgradable-executor", "futures", "hex", - "hyper 0.14.31", + "hyper 0.14.32", "indicatif", "itertools 0.12.1", "num_cpus", @@ -3389,7 +3847,7 @@ dependencies = [ "fuel-vm 0.58.2", "impl-tools", "itertools 0.12.1", - "mockall 0.11.4", + "mockall", "num_enum", "paste", "postcard", @@ -3513,11 +3971,11 @@ version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33548590131674e8f272a3e056be4dbaa1de7cb364eab2b17987cd5c0dc31cb0" dependencies = [ - "ecdsa", + "ecdsa 0.16.9", "ed25519-dalek", "fuel-types 0.56.0", "k256", - "p256", + "p256 0.13.2", "serde", "sha2 0.10.8", "zeroize", @@ -3531,12 +3989,12 @@ checksum = "65e318850ca64890ff123a99b6b866954ef49da94ab9bc6827cf6ee045568585" dependencies = [ "coins-bip32", "coins-bip39", - "ecdsa", + "ecdsa 0.16.9", "ed25519-dalek", "fuel-types 0.58.2", "k256", "lazy_static", - "p256", + "p256 0.13.2", "rand", "secp256k1", "serde", @@ -3546,7 +4004,7 @@ dependencies = [ [[package]] name = "fuel-data-parser" -version = "0.0.13" +version = "0.0.14" dependencies = [ "async-compression", "async-trait", @@ -3559,7 +4017,7 @@ dependencies = [ "serde_json", "strum 0.26.3", "strum_macros 0.26.4", - "thiserror 2.0.4", + "thiserror 2.0.8", "tokio", ] @@ -3653,26 +4111,30 @@ checksum = "2d0c46b5d76b3e11197bd31e036cd8b1cb46c4d822cacc48836638080c6d2b76" [[package]] name = "fuel-streams" -version = "0.0.13" +version = "0.0.14" dependencies = [ "displaydoc", "fuel-streams-core", "futures", - "thiserror 2.0.4", + "reqwest 0.12.9", + "serde", + "serde_json", + "sv-webserver", + "thiserror 2.0.8", "tokio", + "tokio-tungstenite 0.26.1", + "url", ] [[package]] name = "fuel-streams-core" -version = "0.0.13" +version = "0.0.14" dependencies = [ "anyhow", "async-nats", "async-trait", "chrono", - "clap 4.5.23", "displaydoc", - "dotenvy", "fuel-core", "fuel-core-bin", "fuel-core-client", @@ -3682,13 +4144,14 @@ dependencies = [ "fuel-core-types 0.40.2", "fuel-data-parser", "fuel-streams-macros", + "fuel-streams-nats", + "fuel-streams-storage", "futures", "hex", "pretty_assertions", - "rand", "serde", "serde_json", - "thiserror 2.0.4", + "thiserror 2.0.8", "tokio", "tracing", ] @@ -3698,7 +4161,6 @@ name = "fuel-streams-examples" version = "0.0.13" dependencies = [ "anyhow", - "fuel-core-types 0.40.2", "fuel-streams", "futures", "tokio", @@ -3706,7 +4168,7 @@ dependencies = [ [[package]] name = "fuel-streams-executors" -version = "0.0.13" +version = "0.0.14" dependencies = [ "anyhow", "async-nats", @@ -3718,58 +4180,48 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", - "thiserror 2.0.4", + "thiserror 2.0.8", "tokio", "tracing", ] [[package]] name = "fuel-streams-macros" -version = "0.0.13" +version = "0.0.14" dependencies = [ "subject-derive", ] [[package]] -name = "fuel-streams-publisher" -version = "0.0.13" +name = "fuel-streams-nats" +version = "0.0.14" dependencies = [ - "actix-cors", - "actix-server", - "actix-web", - "anyhow", - "assert_matches", "async-nats", - "async-trait", - "chrono", - "clap 4.5.23", - "derive_more 1.0.0", "displaydoc", "dotenvy", - "elasticsearch", - "fuel-core", - "fuel-core-bin", - "fuel-core-services", - "fuel-streams-core", - "fuel-streams-executors", - "futures", - "mockall 0.13.1", - "mockall_double", - "openssl", - "parking_lot", - "prometheus", + "pretty_assertions", "rand", - "rust_decimal", - "serde", "serde_json", - "serde_prometheus", - "sysinfo", - "thiserror 2.0.4", + "thiserror 2.0.8", + "tokio", + "tracing", +] + +[[package]] +name = "fuel-streams-storage" +version = "0.0.14" +dependencies = [ + "aws-config", + "aws-sdk-s3", + "aws-smithy-runtime-api", + "aws-smithy-types", + "dotenvy", + "pretty_assertions", + "rand", + "serde_json", + "thiserror 2.0.8", "tokio", - "tokio-stream", "tracing", - "tracing-actix-web", - "url", ] [[package]] @@ -3999,7 +4451,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.23.19", + "rustls 0.23.20", "rustls-pki-types", ] @@ -4142,13 +4594,24 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "rand_core", + "subtle", +] + [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff", + "ff 0.13.0", "rand_core", "subtle", ] @@ -4342,9 +4805,9 @@ checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" [[package]] name = "hickory-proto" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512" +checksum = "447afdcdb8afb9d0a852af6dc65d9b285ce720ed7a59e42a8bf2e931c67bc1b5" dependencies = [ "async-trait", "cfg-if", @@ -4353,7 +4816,7 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna 0.4.0", + "idna 1.0.3", "ipnet", "once_cell", "rand", @@ -4367,9 +4830,9 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243" +checksum = "0a2e2aba9c389ce5267d31cf1e4dace82390ae276b0b364ea55630b1fa1b44b4" dependencies = [ "cfg-if", "futures-util", @@ -4518,9 +4981,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.31" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", @@ -4542,9 +5005,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" dependencies = [ "bytes", "futures-channel", @@ -4569,23 +5032,25 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.31", + "hyper 0.14.32", + "log", "rustls 0.21.12", + "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", ] [[package]] name = "hyper-rustls" -version = "0.27.3" +version = "0.27.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +checksum = "f6884a48c6826ec44f524c7456b163cebe9e55a18d7b5e307cb4f100371cc767" dependencies = [ "futures-util", "http 1.2.0", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-util", - "rustls 0.23.19", + "rustls 0.23.20", "rustls-pki-types", "tokio", "tokio-rustls 0.26.1", @@ -4599,7 +5064,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.31", + "hyper 0.14.32", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -4611,7 +5076,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.5.1", + "hyper 1.5.2", "hyper-util", "pin-project-lite", "tokio", @@ -4626,7 +5091,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-util", "native-tls", "tokio", @@ -4645,7 +5110,7 @@ dependencies = [ "futures-util", "http 1.2.0", "http-body 1.0.1", - "hyper 1.5.1", + "hyper 1.5.2", "pin-project-lite", "socket2", "tokio", @@ -4816,16 +5281,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "1.0.3" @@ -4891,7 +5346,7 @@ dependencies = [ "bytes", "futures", "http 0.2.12", - "hyper 0.14.31", + "hyper 0.14.32", "log", "rand", "tokio", @@ -4934,9 +5389,9 @@ dependencies = [ [[package]] name = "impl-tools" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a84bc8d2baf8da56e93b4247067d918e1a44829bbbe3e4b875aaf8d7d3c7bc9" +checksum = "b4739bc9af85c18969eba5e4db90dbf26be140ff2e5628593693f18559e9e5fe" dependencies = [ "autocfg", "impl-tools-lib", @@ -4946,9 +5401,9 @@ dependencies = [ [[package]] name = "impl-tools-lib" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a795a1e201125947a063b967c79de6ae152143ab522f481d4f493c44835ba37a" +checksum = "798fe18a7e727001b30a029ab9cdd485afd325801d4df846f0bb5338b2986a2c" dependencies = [ "proc-macro-error2", "proc-macro2", @@ -5004,7 +5459,7 @@ dependencies = [ "console", "number_prefix", "portable-atomic", - "unicode-width 0.2.0", + "unicode-width", "web-time", ] @@ -5111,9 +5566,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", @@ -5139,6 +5594,21 @@ dependencies = [ "simple_asn1", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem 3.0.4", + "ring 0.17.8", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "k256" version = "0.13.4" @@ -5146,11 +5616,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", - "ecdsa", - "elliptic-curve", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", "once_cell", "sha2 0.10.8", - "signature", + "signature 2.2.0", ] [[package]] @@ -5188,9 +5658,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libflate" @@ -5536,7 +6006,7 @@ dependencies = [ "quinn", "rand", "ring 0.17.8", - "rustls 0.23.19", + "rustls 0.23.20", "socket2", "thiserror 1.0.69", "tokio", @@ -5628,7 +6098,7 @@ dependencies = [ "libp2p-identity", "rcgen", "ring 0.17.8", - "rustls 0.23.19", + "rustls 0.23.20", "rustls-webpki 0.101.7", "thiserror 1.0.69", "x509-parser", @@ -5795,21 +6265,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" -[[package]] -name = "load-tester" -version = "0.0.13" -dependencies = [ - "anyhow", - "async-nats", - "chrono", - "clap 4.5.23", - "fuel-streams", - "fuel-streams-core", - "futures", - "statrs", - "tokio", -] - [[package]] name = "local-channel" version = "0.1.5" @@ -5920,13 +6375,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] -name = "matrixmultiply" -version = "0.3.9" +name = "md-5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ - "autocfg", - "rawpointer", + "cfg-if", + "digest 0.10.7", ] [[package]] @@ -5967,9 +6422,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", ] @@ -5996,22 +6451,8 @@ dependencies = [ "downcast", "fragile", "lazy_static", - "mockall_derive 0.11.4", - "predicates 2.1.5", - "predicates-tree", -] - -[[package]] -name = "mockall" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" -dependencies = [ - "cfg-if", - "downcast", - "fragile", - "mockall_derive 0.13.1", - "predicates 3.1.2", + "mockall_derive", + "predicates", "predicates-tree", ] @@ -6027,30 +6468,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "mockall_derive" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "mockall_double" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ca96e5ac35256ae3e13536edd39b172b88f41615e1d7b653c8ad24524113e8" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "multer" version = "3.1.0" @@ -6100,9 +6517,9 @@ dependencies = [ [[package]] name = "multihash" -version = "0.19.2" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc41f430805af9d1cf4adae4ed2149c759b877b01d909a1f40256188d09345d2" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" dependencies = [ "core2", "unsigned-varint 0.8.0", @@ -6128,23 +6545,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577" -[[package]] -name = "nalgebra" -version = "0.33.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" -dependencies = [ - "approx", - "matrixmultiply", - "num-complex", - "num-rational", - "num-traits", - "rand", - "rand_distr", - "simba", - "typenum", -] - [[package]] name = "names" version = "0.14.0" @@ -6172,25 +6572,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nats-publisher" -version = "0.0.13" -dependencies = [ - "anyhow", - "async-nats", - "clap 4.5.23", - "criterion", - "fuel-core", - "fuel-core-bin", - "fuel-core-importer", - "fuel-core-storage", - "fuel-core-types 0.40.2", - "fuel-data-parser", - "fuel-streams-core", - "tokio", - "tracing", -] - [[package]] name = "netlink-packet-core" version = "0.7.0" @@ -6245,9 +6626,9 @@ dependencies = [ [[package]] name = "netlink-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" +checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23" dependencies = [ "bytes", "futures", @@ -6336,18 +6717,9 @@ dependencies = [ name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ + "num-integer", "num-traits", ] @@ -6384,7 +6756,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -6557,20 +6928,37 @@ version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +[[package]] +name = "outref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" + [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", + "sha2 0.10.8", +] + [[package]] name = "p256" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ - "ecdsa", - "elliptic-curve", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", "primeorder", "sha2 0.10.8", ] @@ -6715,12 +7103,12 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 1.0.69", + "thiserror 2.0.8", "ucd-trie", ] @@ -6776,14 +7164,24 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.9", + "spki 0.7.3", ] [[package]] @@ -6926,27 +7324,17 @@ dependencies = [ "regex", ] -[[package]] -name = "predicates" -version = "3.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" -dependencies = [ - "anstyle", - "predicates-core", -] - [[package]] name = "predicates-core" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" [[package]] name = "predicates-tree" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" dependencies = [ "predicates-core", "termtree", @@ -6978,7 +7366,7 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ - "elliptic-curve", + "elliptic-curve 0.13.8", ] [[package]] @@ -7047,6 +7435,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", + "syn 2.0.90", ] [[package]] @@ -7123,9 +7512,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ "bitflags 2.6.0", "lazy_static", @@ -7336,9 +7725,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.1.0", - "rustls 0.23.19", + "rustls 0.23.20", "socket2", - "thiserror 2.0.4", + "thiserror 2.0.8", "tokio", "tracing", ] @@ -7354,10 +7743,10 @@ dependencies = [ "rand", "ring 0.17.8", "rustc-hash 2.1.0", - "rustls 0.23.19", + "rustls 0.23.20", "rustls-pki-types", "slab", - "thiserror 2.0.4", + "thiserror 2.0.8", "tinyvec", "tracing", "web-time", @@ -7365,9 +7754,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.7" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" dependencies = [ "cfg_aliases", "libc", @@ -7422,16 +7811,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_distr" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" -dependencies = [ - "num-traits", - "rand", -] - [[package]] name = "rand_xorshift" version = "0.3.0" @@ -7441,12 +7820,6 @@ dependencies = [ "rand_core", ] -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - [[package]] name = "rayon" version = "1.10.0" @@ -7481,9 +7854,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -7587,7 +7960,7 @@ dependencies = [ "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.31", + "hyper 0.14.32", "hyper-rustls 0.24.2", "ipnet", "js-sys", @@ -7626,13 +7999,15 @@ dependencies = [ "bytes", "cookie 0.18.1", "cookie_store 0.21.1", + "encoding_rs", "futures-core", "futures-util", + "h2 0.4.7", "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.1", - "hyper-rustls 0.27.3", + "hyper 1.5.2", + "hyper-rustls 0.27.4", "hyper-tls", "hyper-util", "ipnet", @@ -7644,13 +8019,14 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.19", + "rustls 0.23.20", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 1.0.2", + "system-configuration 0.6.1", "tokio", "tokio-native-tls", "tokio-rustls 0.26.1", @@ -7674,6 +8050,17 @@ dependencies = [ "quick-error", ] +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac 0.12.1", + "zeroize", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -7877,15 +8264,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7902,9 +8289,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.19" +version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ "once_cell", "ring 0.17.8", @@ -7948,7 +8335,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.0.1", + "security-framework 3.1.0", ] [[package]] @@ -7971,9 +8358,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" dependencies = [ "web-time", ] @@ -8022,15 +8409,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" -[[package]] -name = "safe_arch" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" -dependencies = [ - "bytemuck", -] - [[package]] name = "same-file" version = "1.0.6" @@ -8121,16 +8499,30 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct 0.1.1", + "der 0.6.1", + "generic-array", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + [[package]] name = "sec1" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct", - "der", + "base16ct 0.2.0", + "der 0.7.9", "generic-array", - "pkcs8", + "pkcs8 0.10.2", "subtle", "zeroize", ] @@ -8178,9 +8570,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1415a607e92bec364ea2cf9264646dcce0f91e6d65281bd6f2819cca3bf39c8" +checksum = "81d3f8c9bfcc3cbb6b0179eb57042d75b1582bdc65c3cb95f3fa999509c03cbc" dependencies = [ "bitflags 2.6.0", "core-foundation 0.10.0", @@ -8191,9 +8583,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" dependencies = [ "core-foundation-sys", "libc", @@ -8201,9 +8593,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" dependencies = [ "serde", ] @@ -8222,9 +8614,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] @@ -8240,9 +8632,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", @@ -8430,33 +8822,30 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1e303f8205714074f6068773f0e29527e0453937fe837c9717d066635b65f31" dependencies = [ - "pkcs8", + "pkcs8 0.10.2", "rand_core", - "signature", + "signature 2.2.0", "zeroize", ] [[package]] name = "signature" -version = "2.2.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ "digest 0.10.7", "rand_core", ] [[package]] -name = "simba" -version = "0.9.0" +name = "signature" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "approx", - "num-complex", - "num-traits", - "paste", - "wide", + "digest 0.10.7", + "rand_core", ] [[package]] @@ -8558,6 +8947,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der 0.6.1", +] + [[package]] name = "spki" version = "0.7.3" @@ -8565,7 +8964,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.9", ] [[package]] @@ -8592,33 +8991,14 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" -[[package]] -name = "statrs" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" -dependencies = [ - "approx", - "nalgebra", - "num-traits", - "rand", -] - [[package]] name = "streams-tests" -version = "0.0.13" +version = "0.0.14" dependencies = [ - "anyhow", - "async-trait", "fuel-core", - "fuel-core-importer", - "fuel-core-types 0.40.2", "fuel-streams", "fuel-streams-core", - "fuel-streams-publisher", - "futures", "pretty_assertions", - "rand", "tokio", ] @@ -8702,7 +9082,7 @@ dependencies = [ [[package]] name = "subject-derive" -version = "0.0.13" +version = "0.0.14" dependencies = [ "proc-macro2", "quote", @@ -8732,19 +9112,21 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "sv-consumer" -version = "0.0.13" +version = "0.0.14" dependencies = [ "anyhow", "async-nats", "clap 4.5.23", + "dotenvy", "fuel-core", "fuel-streams-core", "fuel-streams-executors", "futures", + "num_cpus", "openssl", "serde_json", - "sv-emitter", - "thiserror 2.0.4", + "sv-publisher", + "thiserror 2.0.8", "tokio", "tokio-util", "tracing", @@ -8753,7 +9135,27 @@ dependencies = [ [[package]] name = "sv-emitter" -version = "0.0.13" +version = "0.0.14" +dependencies = [ + "anyhow", + "async-nats", + "clap 4.5.23", + "fuel-core", + "fuel-core-bin", + "fuel-core-types 0.40.2", + "fuel-streams-core", + "fuel-streams-executors", + "futures", + "openssl", + "thiserror 2.0.8", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "sv-publisher" +version = "0.0.14" dependencies = [ "anyhow", "async-nats", @@ -8765,17 +9167,63 @@ dependencies = [ "fuel-streams-executors", "futures", "openssl", - "thiserror 2.0.4", + "thiserror 2.0.8", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "sv-webserver" +version = "0.0.14" +dependencies = [ + "actix-cors", + "actix-server", + "actix-service", + "actix-web", + "actix-ws", + "anyhow", + "async-nats", + "bytestring", + "chrono", + "clap 4.5.23", + "derive_more 1.0.0", + "displaydoc", + "dotenvy", + "elasticsearch", + "fuel-streams-core", + "fuel-streams-nats", + "fuel-streams-storage", + "futures", + "futures-util", + "jsonwebtoken 9.3.0", + "num_cpus", + "openssl", + "parking_lot", + "prometheus", + "rand", + "rust_decimal", + "serde", + "serde_json", + "serde_prometheus", + "sysinfo", + "thiserror 2.0.8", + "time", + "tokio", + "tracing", + "tracing-actix-web", + "tracing-subscriber", + "url", + "urlencoding", + "uuid", + "validator", +] + [[package]] name = "symbolic-common" -version = "12.12.3" +version = "12.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ba5365997a4e375660bed52f5b42766475d5bc8ceb1bb13fea09c469ea0f49" +checksum = "cd33e73f154e36ec223c18013f7064a2c120f1162fc086ac9933542def186b00" dependencies = [ "debugid", "memmap2", @@ -8785,9 +9233,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.12.3" +version = "12.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beff338b2788519120f38c59ff4bb15174f52a183e547bac3d6072c2c0aa48aa" +checksum = "89e51191290147f071777e37fe111800bb82a9059f9c95b19d2dd41bfeddf477" dependencies = [ "cpp_demangle", "rustc-demangle", @@ -8954,7 +9402,7 @@ dependencies = [ "serde_json", "serde_repr", "sha2 0.10.8", - "signature", + "signature 2.2.0", "subtle", "subtle-encoding", "tendermint-proto 0.36.0", @@ -8984,7 +9432,7 @@ dependencies = [ "serde_json", "serde_repr", "sha2 0.10.8", - "signature", + "signature 2.2.0", "subtle", "subtle-encoding", "tendermint-proto 0.40.0", @@ -9081,9 +9529,9 @@ dependencies = [ [[package]] name = "termtree" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "textwrap" @@ -9102,11 +9550,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.4" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" +checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" dependencies = [ - "thiserror-impl 2.0.4", + "thiserror-impl 2.0.8", ] [[package]] @@ -9122,9 +9570,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.4" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" +checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" dependencies = [ "proc-macro2", "quote", @@ -9313,7 +9761,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls 0.23.19", + "rustls 0.23.20", "tokio", ] @@ -9340,10 +9788,22 @@ dependencies = [ "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", - "tungstenite", + "tungstenite 0.20.1", "webpki-roots 0.25.4", ] +[[package]] +name = "tokio-tungstenite" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4bf6fecd69fcdede0ec680aaf474cdab988f9de6bc73d3758f0160e3b7025a" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.26.1", +] + [[package]] name = "tokio-util" version = "0.7.13" @@ -9426,7 +9886,7 @@ dependencies = [ "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.31", + "hyper 0.14.32", "hyper-timeout 0.4.1", "percent-encoding", "pin-project", @@ -9454,7 +9914,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-timeout 0.5.2", "hyper-util", "percent-encoding", @@ -9491,14 +9951,14 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", ] @@ -9698,6 +10158,24 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.2.0", + "httparse", + "log", + "rand", + "sha1", + "thiserror 2.0.8", + "utf-8", +] + [[package]] name = "typenum" version = "1.17.0" @@ -9730,9 +10208,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-bidi" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" @@ -9755,12 +10233,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unicode-width" version = "0.2.0" @@ -9822,6 +10294,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -9853,6 +10331,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", + "serde", +] + +[[package]] +name = "validator" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0b4a29d8709210980a09379f27ee31549b73292c87ab9899beee1c0d3be6303" +dependencies = [ + "idna 1.0.3", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac855a2ce6f843beb229757e6e570a42e837bcb15e5f449dd48d5747d41bf77" +dependencies = [ + "darling 0.20.10", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] @@ -9879,6 +10388,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "walkdir" version = "2.5.0" @@ -9906,9 +10421,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -9917,13 +10432,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.90", @@ -9932,9 +10446,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.47" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", @@ -9945,9 +10459,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9955,9 +10469,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -9968,9 +10482,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-encoder" @@ -10202,9 +10716,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -10235,16 +10749,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "wide" -version = "0.7.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e6db2670d2be78525979e9a5f9c69d296fd7d670549fe9ebf70f8708cb5019" -dependencies = [ - "bytemuck", - "safe_arch", -] - [[package]] name = "widestring" version = "1.1.0" @@ -10610,6 +11114,12 @@ version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432" +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "xmltree" version = "0.10.3" diff --git a/Cargo.toml b/Cargo.toml index 9e36651f..d3f08951 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ homepage = "https://fuel.network/" license = "Apache-2.0" repository = "https://github.com/fuellabs/data-systems" rust-version = "1.81.0" -version = "0.0.13" +version = "0.0.14" [workspace.dependencies] actix-cors = "0.7" @@ -69,15 +69,18 @@ tracing-subscriber = "0.3" tracing-actix-web = "0.7" thiserror = "2.0" -fuel-streams = { path = "crates/fuel-streams" } -fuel-data-parser = { version = "0.0.13", path = "crates/fuel-data-parser" } -fuel-streams-core = { version = "0.0.13", path = "crates/fuel-streams-core" } -fuel-streams-publisher = { version = "0.0.13", path = "crates/fuel-streams-publisher" } -fuel-streams-macros = { version = "0.0.13", path = "crates/fuel-streams-macros" } -subject-derive = { version = "0.0.13", path = "crates/fuel-streams-macros/subject-derive" } -fuel-streams-executors = { version = "0.0.13", path = "crates/fuel-streams-executors" } -sv-emitter = { version = "0.0.13", path = "crates/sv-emitter" } -sv-consumer = { version = "0.0.13", path = "crates/sv-consumer" } +fuel-streams = { version = "0.0.14", path = "crates/fuel-streams" } +fuel-data-parser = { version = "0.0.14", path = "crates/fuel-data-parser" } +fuel-streams-core = { version = "0.0.14", path = "crates/fuel-streams-core" } +fuel-streams-macros = { version = "0.0.14", path = "crates/fuel-streams-macros" } +fuel-streams-nats = { version = "0.0.14", path = "crates/fuel-streams-nats" } +fuel-streams-storage = { version = "0.0.14", path = "crates/fuel-streams-storage" } +fuel-streams-executors = { version = "0.0.14", path = "crates/fuel-streams-executors" } +subject-derive = { version = "0.0.14", path = "crates/fuel-streams-macros/subject-derive" } +sv-publisher = { version = "0.0.14", path = "crates/sv-publisher" } +sv-consumer = { version = "0.0.14", path = "crates/sv-consumer" } +sv-emitter = { version = "0.0.14", path = "crates/sv-emitter" } +sv-webserver = { version = "0.0.14", path = "crates/sv-webserver" } # Workspace projects [workspace.metadata.cargo-machete] diff --git a/Makefile b/Makefile index 51aa1ac1..a7e8dd9f 100644 --- a/Makefile +++ b/Makefile @@ -140,7 +140,7 @@ test: fi bench: - cargo bench -p data-parser -p nats-publisher -p bench-consumers + cargo bench -p data-parser helm-test: helm unittest -f "tests/**/*.yaml" -f "tests/*.yaml" cluster/charts/fuel-streams @@ -209,7 +209,6 @@ load-test: # ------------------------------------------------------------ run-publisher: NETWORK="testnet" -run-publisher: PACKAGE="sv-emitter" run-publisher: MODE="dev" run-publisher: PORT="4000" run-publisher: TELEMETRY_PORT="8080" @@ -218,16 +217,16 @@ run-publisher: EXTRA_ARGS="" run-publisher: check-network @./scripts/run_publisher.sh -run-mainnet-dev: +run-publisher-mainnet-dev: $(MAKE) run-publisher NETWORK=mainnet MODE=dev -run-mainnet-profiling: +run-publisher-mainnet-profiling: $(MAKE) run-publisher NETWORK=mainnet MODE=profiling -run-testnet-dev: +run-publisher-testnet-dev: $(MAKE) run-publisher NETWORK=testnet MODE=dev -run-testnet-profiling: +run-publisher-testnet-profiling: $(MAKE) run-publisher NETWORK=testnet MODE=profiling run-consumer: NATS_CORE_URL="localhost:4222" @@ -237,25 +236,72 @@ run-consumer: --nats-core-url $(NATS_CORE_URL) \ --nats-publisher-url $(NATS_PUBLISHER_URL) +# ------------------------------------------------------------ +# Consumer Run Commands +# ------------------------------------------------------------ + +run-consumer: NATS_URL="localhost:4222" +run-consumer: NATS_PUBLISHER_URL="localhost:4333" +run-consumer: + cargo run --package sv-consumer --profile dev -- \ + --nats-url $(NATS_URL) \ + --nats-publisher-url $(NATS_PUBLISHER_URL) + +# ------------------------------------------------------------ +# Streamer Run Commands +# ------------------------------------------------------------ + +run-webserver: NETWORK="testnet" +run-webserver: MODE="dev" +run-webserver: PORT="9003" +run-webserver: NATS_URL="nats://localhost:4222" +run-webserver: EXTRA_ARGS="" +run-webserver: check-network + @./scripts/run_webserver.sh --mode $(MODE) --port $(PORT) --nats-url $(NATS_URL) --extra-args $(EXTRA_ARGS) + +run-webserver-mainnet-dev: + $(MAKE) run-webserver NETWORK=mainnet MODE=dev + +run-webserver-mainnet-profiling: + $(MAKE) run-webserver NETWORK=mainnet MODE=profiling + +run-webserver-testnet-dev: + $(MAKE) run-webserver NETWORK=testnet MODE=dev + +run-webserver-testnet-profiling: + $(MAKE) run-webserver NETWORK=testnet MODE=profiling + # ------------------------------------------------------------ # Docker Compose # ------------------------------------------------------------ +# Define service profiles +DOCKER_SERVICES := nats localstack docker + +run-docker-compose: PROFILE="all" run-docker-compose: @./scripts/set_env.sh - @docker compose -f cluster/docker/docker-compose.yml --env-file .env $(COMMAND) + @docker compose -f cluster/docker/docker-compose.yml --profile $(PROFILE) --env-file .env $(COMMAND) + +# Common docker-compose commands +define make-docker-commands +start-$(1): + $(MAKE) run-docker-compose PROFILE="$(if $(filter docker,$(1)),all,$(1))" COMMAND="up -d" -start-nats: - $(MAKE) run-docker-compose COMMAND="up -d" +stop-$(1): + $(MAKE) run-docker-compose PROFILE="$(if $(filter docker,$(1)),all,$(1))" COMMAND="down" -stop-nats: - $(MAKE) run-docker-compose COMMAND="down" +restart-$(1): + $(MAKE) run-docker-compose PROFILE="$(if $(filter docker,$(1)),all,$(1))" COMMAND="restart" -restart-nats: - $(MAKE) run-docker-compose COMMAND="restart" +clean-$(1): + $(MAKE) run-docker-compose PROFILE="$(if $(filter docker,$(1)),all,$(1))" COMMAND="down -v --remove-orphans" -clean-nats: - $(MAKE) run-docker-compose COMMAND="down -v --rmi all --remove-orphans" +reset-$(1): clean-$(1) start-$(1) +endef + +# Generate targets for each service +$(foreach service,$(DOCKER_SERVICES),$(eval $(call make-docker-commands,$(service)))) reset-nats: clean-nats start-nats @@ -287,15 +333,10 @@ minikube-delete: @echo "Deleting minikube..." @minikube delete -k8s-setup: - @echo "Setting up k8s..." - @./cluster/scripts/setup_k8s.sh $(NAMESPACE) - helm-setup: @cd cluster/charts/fuel-streams && helm dependency update - @cd cluster/charts/fuel-streams-publisher && helm dependency update -cluster-setup: minikube-setup k8s-setup helm-setup +cluster-setup: minikube-setup helm-setup pre-cluster: @./scripts/set_env.sh @@ -308,5 +349,4 @@ cluster-up: pre-cluster cluster-down: pre-cluster CLUSTER_MODE=$(MODE) tilt --file ./Tiltfile down -cluster-reset: pre-cluster - CLUSTER_MODE=$(MODE) tilt --file ./Tiltfile reset +cluster-reset: cluster-down cluster-up diff --git a/README.md b/README.md index 2a07a7ce..dcb22bda 100644 --- a/README.md +++ b/README.md @@ -51,21 +51,26 @@ With Fuel Data Systems, developers can build sophisticated applications that lev 2. Create a new Rust file (e.g., `src/main.rs`) with the following code to subscribe to new blocks: ```rust - use fuel_streams::client::Client; - use fuel_streams::types::FuelNetwork; - use fuel_streams::stream::{Stream, StreamEncoder}; - use fuel_streams::blocks::Block; + use fuel_streams::prelude::*; use futures::StreamExt; #[tokio::main] - async fn main() -> Result<(), fuel_streams::Error> { - let client = Client::connect(FuelNetwork::Mainnet).await?; - let stream = fuel_streams::Stream::::new(&client).await; - - let mut subscription = stream.subscribe().await?; - while let Some(entry) = subscription.next().await { - let entry = entry.unwrap().clone(); - let block = Block::decode(entry).await; + async fn main() -> Result<(), Box> { + // Create a client and establish connection + let mut client = Client::new(FuelNetwork::Local).await?; + let mut connection = client.connect().await?; + + println!("Listening for blocks..."); + + // Create a subject for all blocks + let subject = BlocksSubject::new(); + + // Subscribe to blocks with last delivery policy + let mut stream = connection + .subscribe::(subject, DeliverPolicy::Last) + .await?; + + while let Some(block) = stream.next().await { println!("Received block: {:?}", block); } @@ -78,7 +83,7 @@ With Fuel Data Systems, developers can build sophisticated applications that lev cargo run ``` -This example connects to the Fuel Network's NATS server and listens for new blocks. You can customize the data types or apply filters based on your specific requirements. +This example connects to the Fuel Network and listens for new blocks. You can customize the data types or apply filters based on your specific requirements. For advanced usage, including custom filters and delivery policies, refer to the [`fuel-streams` documentation](https://docs.rs/fuel-streams). diff --git a/Tiltfile b/Tiltfile index 1e6622e7..8b9d1653 100755 --- a/Tiltfile +++ b/Tiltfile @@ -12,20 +12,18 @@ dotenv() allow_k8s_contexts('minikube') -# Build sv-emitter +# Build sv-publisher custom_build( - ref='sv-emitter:latest', + ref='sv-publisher:latest', command=[ './cluster/scripts/build_docker.sh', - '--image-name', 'sv-emitter', - '--dockerfile', './cluster/docker/fuel-core.Dockerfile', - '--build-args', '--build-arg PACKAGE_NAME=sv-emitter' + '--dockerfile', './cluster/docker/sv-publisher.Dockerfile' ], deps=[ './src', './Cargo.toml', './Cargo.lock', - './cluster/docker/fuel-core.Dockerfile' + './cluster/docker/sv-publisher.Dockerfile' ], live_update=[ sync('./src', '/usr/src'), @@ -33,16 +31,15 @@ custom_build( sync('./Cargo.lock', '/usr/src/Cargo.lock'), run('cargo build', trigger=['./src', './Cargo.toml', './Cargo.lock']) ], - skips_local_docker=True, ignore=['./target'] ) # Build sv-consumer custom_build( ref='sv-consumer:latest', + image_deps=['sv-publisher:latest'], command=[ './cluster/scripts/build_docker.sh', - '--image-name', 'sv-consumer', '--dockerfile', './cluster/docker/sv-consumer.Dockerfile' ], deps=[ @@ -57,20 +54,58 @@ custom_build( sync('./Cargo.lock', '/usr/src/Cargo.lock'), run('cargo build', trigger=['./src', './Cargo.toml', './Cargo.lock']) ], - skips_local_docker=True, ignore=['./target'] ) +# Build streamer ws image with proper configuration for Minikube +custom_build( + ref='sv-webserver:latest', + image_deps=['sv-consumer:latest', 'sv-publisher:latest'], + command=[ + './cluster/scripts/build_docker.sh', + '--dockerfile', './cluster/docker/sv-webserver.Dockerfile' + ], + deps=[ + './src', + './Cargo.toml', + './Cargo.lock', + './cluster/docker/sv-webserver.Dockerfile' + ], + live_update=[ + sync('./src', '/usr/src'), + sync('./Cargo.toml', '/usr/src/Cargo.toml'), + sync('./Cargo.lock', '/usr/src/Cargo.lock'), + run('cargo build', trigger=['./src', './Cargo.toml', './Cargo.lock']) + ], + ignore=['./target'] +) + +# Deploy the Helm chart with values from .env # Get deployment mode from environment variable, default to 'full' config_mode = os.getenv('CLUSTER_MODE', 'full') # Resource configurations RESOURCES = { 'publisher': { - 'name': 'fuel-streams-publisher', - 'ports': ['4000:4000', '8080:8080'], + 'name': 'fuel-streams-sv-publisher', + 'ports': ['8080:8080'], 'labels': 'publisher', - 'config_mode': ['minimal', 'full'] + 'config_mode': ['minimal', 'full'], + 'deps': ['fuel-streams-nats-core', 'fuel-streams-nats-publisher'] + }, + 'consumer': { + 'name': 'fuel-streams-sv-consumer', + 'ports': ['8081:8080'], + 'labels': 'consumer', + 'config_mode': ['minimal', 'full'], + 'deps': ['fuel-streams-nats-core', 'fuel-streams-nats-publisher', 'fuel-streams-sv-publisher'] + }, + 'sv-webserver': { + 'name': 'fuel-streams-sv-webserver', + 'ports': ['9003:9003'], + 'labels': 'ws', + 'config_mode': ['minimal', 'full'], + 'deps': ['fuel-streams-nats-core', 'fuel-streams-nats-publisher'] }, 'consumer': { 'name': 'fuel-streams-sv-consumer', @@ -84,18 +119,13 @@ RESOURCES = { 'labels': 'nats', 'config_mode': ['minimal', 'full'] }, - 'nats-client': { - 'name': 'fuel-streams-nats-client', - 'ports': ['14222:4222', '17422:7422', '8443:8443'], - 'labels': 'nats', - 'config_mode': ['minimal', 'full'] - }, 'nats-publisher': { 'name': 'fuel-streams-nats-publisher', - 'ports': ['24222:4222', '27422:7422'], + 'ports': ['4333:4222', '6222:6222', '7433:7422'], 'labels': 'nats', - 'config_mode': ['minimal', 'full'] - } + 'config_mode': ['minimal', 'full'], + 'deps': ['fuel-streams-nats-core'] + }, } k8s_yaml(helm( @@ -103,9 +133,9 @@ k8s_yaml(helm( name='fuel-streams', namespace='fuel-streams', values=[ - 'cluster/charts/fuel-streams/values-publisher-secrets.yaml', 'cluster/charts/fuel-streams/values.yaml', - 'cluster/charts/fuel-streams/values-local.yaml' + 'cluster/charts/fuel-streams/values-local.yaml', + 'cluster/charts/fuel-streams/values-secrets.yaml' ] )) diff --git a/benches/bench-consumers/Cargo.toml b/benches/bench-consumers/Cargo.toml deleted file mode 100644 index fb0dfb5b..00000000 --- a/benches/bench-consumers/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "bench-consumers" -authors = { workspace = true } -keywords = { workspace = true } -edition = { workspace = true } -homepage = { workspace = true } -license = { workspace = true } -repository = { workspace = true } -version = { workspace = true } -rust-version = { workspace = true } -publish = false - -[[bench]] -name = "consumers" -harness = false -path = "benches/consumers.rs" - -[dependencies] -anyhow = { workspace = true } -async-nats = { workspace = true } -chrono = { workspace = true } -fuel-core-types = { workspace = true } -fuel-streams-core = { workspace = true, features = ["bench-helpers"] } -futures = { workspace = true } -nats-publisher = { path = "../nats-publisher" } -statrs = "0.18" -tokio = { workspace = true } - -[dev-dependencies] -criterion = { version = "0.5", features = ["html_reports", "async_tokio"] } diff --git a/benches/bench-consumers/README.md b/benches/bench-consumers/README.md deleted file mode 100644 index ad650983..00000000 --- a/benches/bench-consumers/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Running - -1. After running the [`nats-publisher`](../nats-publisher/README.md) locally, just execute the run on this project: - - ```sh - cargo run - ``` - -2. You can also run benchmarks with cargo: - - ```sh - cargo bench --bench consumers - ``` diff --git a/benches/bench-consumers/benches/consumers.rs b/benches/bench-consumers/benches/consumers.rs deleted file mode 100644 index 8b66907f..00000000 --- a/benches/bench-consumers/benches/consumers.rs +++ /dev/null @@ -1,43 +0,0 @@ -use bench_consumers::runners::{ - runner_consumer::run_blocks_consumer, - runner_kv_watcher::run_watch_kv_blocks, - runner_subscription::run_subscriptions, -}; -use criterion::{criterion_group, criterion_main, Criterion}; -use nats_publisher::utils::nats::NatsHelper; -use tokio::runtime::Runtime; - -static MSGS_LIMIT: usize = 10000; - -fn benchmark_all(c: &mut Criterion) { - let rt = Runtime::new().unwrap(); - let mut group = c.benchmark_group("NATS Benchmarks"); - let nats = rt.block_on(async { NatsHelper::connect(false).await.unwrap() }); - - group.bench_function("consume_blocks_ack_none", |b| { - b.to_async(&rt).iter(|| async { - run_blocks_consumer(&nats, MSGS_LIMIT).await.unwrap() - }); - }); - - group.bench_function("watch_kv_blocks", |b| { - b.to_async(&rt).iter(|| async { - run_watch_kv_blocks(&nats, MSGS_LIMIT).await.unwrap() - }); - }); - - group.bench_function("subscriptions", |b| { - b.to_async(&rt).iter(|| async { - run_subscriptions(&nats, MSGS_LIMIT).await.unwrap() - }); - }); - - group.finish(); -} - -criterion_group!( - name = benches; - config = Criterion::default().sample_size(10); // Adjust sample size as needed - targets = benchmark_all -); -criterion_main!(benches); diff --git a/benches/bench-consumers/src/lib.rs b/benches/bench-consumers/src/lib.rs deleted file mode 100644 index 0edfafa9..00000000 --- a/benches/bench-consumers/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod runners; diff --git a/benches/bench-consumers/src/main.rs b/benches/bench-consumers/src/main.rs deleted file mode 100644 index a4bc6b64..00000000 --- a/benches/bench-consumers/src/main.rs +++ /dev/null @@ -1,10 +0,0 @@ -use runners::runner_all::run_all_benchmarks; - -mod runners; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - println!("Running benchmarks"); - run_all_benchmarks().await?; - Ok(()) -} diff --git a/benches/bench-consumers/src/runners/benchmark_results.rs b/benches/bench-consumers/src/runners/benchmark_results.rs deleted file mode 100644 index e4aaaf42..00000000 --- a/benches/bench-consumers/src/runners/benchmark_results.rs +++ /dev/null @@ -1,103 +0,0 @@ -use core::fmt; -use std::time::{Duration, Instant}; - -use chrono::{DateTime, Utc}; -use statrs::statistics::{Data, Distribution}; - -#[derive(Debug, Clone)] -pub struct BenchmarkResult { - pub name: String, - pub message_count: usize, - pub error_count: usize, - start_time: Instant, - pub elapsed_time: Option, - pub messages_per_second: Option, - pub publish_times: Vec, - pub mean_publish_time: Option, - pub messages_limit: usize, -} - -impl fmt::Display for BenchmarkResult { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "\n{}\nBenchmark Results: {}\n{}\nTotal Messages: {}\nTotal Errors: {}\nElapsed Time: {:?}\nMessages per Second: {:.2}\nMean Publish Time: {:?}\n{}", - "=".repeat(50), - self.name, - "=".repeat(50), - self.message_count, - self.error_count, - self.elapsed_time.unwrap_or_default(), - self.messages_per_second.unwrap_or_default(), - self.mean_publish_time.unwrap_or_default(), - "=".repeat(50) - ) - } -} - -impl BenchmarkResult { - pub fn new(name: String, messages_limit: usize) -> Self { - Self { - name, - message_count: 0, - error_count: 0, - start_time: Instant::now(), - elapsed_time: None, - messages_per_second: None, - publish_times: vec![], - mean_publish_time: None, - messages_limit, - } - } - - pub fn increment_message_count(&mut self) { - self.message_count += 1; - } - - pub fn increment_error_count(&mut self) { - self.error_count += 1; - } - - pub fn finalize(&mut self) -> &mut Self { - self.calculate_mean_publish_time(); - let elapsed = self.start_time.elapsed(); - self.elapsed_time = Some(elapsed); - self.messages_per_second = - Some(self.message_count as f64 / elapsed.as_secs_f64()); - self - } - - pub fn is_complete(&self) -> bool { - self.message_count + self.error_count >= self.messages_limit - } - - pub fn add_publish_time(&mut self, timestamp: u128) -> &mut Self { - let current_time = Utc::now(); - let publish_time = - DateTime::::from_timestamp_millis(timestamp as i64) - .expect("Invalid timestamp"); - let duration = current_time - .signed_duration_since(publish_time) - .to_std() - .expect("Duration calculation failed"); - - self.publish_times.push(duration); - self - } - - pub fn calculate_mean_publish_time(&mut self) { - if self.publish_times.is_empty() { - return; - } - - let times_ns: Vec = self - .publish_times - .iter() - .map(|d| d.as_nanos() as f64) - .collect(); - - let data = Data::new(times_ns); - let mean_ns = data.mean().unwrap(); - self.mean_publish_time = Some(Duration::from_nanos(mean_ns as u64)); - } -} diff --git a/benches/bench-consumers/src/runners/mod.rs b/benches/bench-consumers/src/runners/mod.rs deleted file mode 100644 index b1918cbe..00000000 --- a/benches/bench-consumers/src/runners/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod benchmark_results; -pub mod runner_all; -pub mod runner_consumer; -pub mod runner_kv_watcher; -pub mod runner_subscription; diff --git a/benches/bench-consumers/src/runners/runner_all.rs b/benches/bench-consumers/src/runners/runner_all.rs deleted file mode 100644 index 410e4570..00000000 --- a/benches/bench-consumers/src/runners/runner_all.rs +++ /dev/null @@ -1,25 +0,0 @@ -use anyhow::Result; -use nats_publisher::utils::nats::NatsHelper; -use tokio::try_join; - -static MSGS_LIMIT: usize = 5000; - -use super::{ - runner_consumer::run_blocks_consumer, - runner_kv_watcher::run_watch_kv_blocks, - runner_subscription::run_subscriptions, -}; - -#[allow(dead_code)] -pub async fn run_all_benchmarks() -> Result<()> { - let use_nats_compression = false; // adjust as needed - let nats = NatsHelper::connect(use_nats_compression).await?; - - let _ = try_join!( - run_subscriptions(&nats, MSGS_LIMIT), - run_watch_kv_blocks(&nats, MSGS_LIMIT), - run_blocks_consumer(&nats, MSGS_LIMIT), - ); - - Ok(()) -} diff --git a/benches/bench-consumers/src/runners/runner_consumer.rs b/benches/bench-consumers/src/runners/runner_consumer.rs deleted file mode 100644 index bbf02f23..00000000 --- a/benches/bench-consumers/src/runners/runner_consumer.rs +++ /dev/null @@ -1,55 +0,0 @@ -use anyhow::Result; -use async_nats::jetstream::consumer::AckPolicy; -pub use async_nats::jetstream::consumer::{ - pull::Config as PullConsumerConfig, - DeliverPolicy, -}; -use fuel_core_types::blockchain::block::Block; -use fuel_streams_core::prelude::StreamData; -use futures::StreamExt; -use nats_publisher::utils::nats::NatsHelper; - -use super::benchmark_results::BenchmarkResult; - -pub async fn run_blocks_consumer( - nats: &NatsHelper, - limit: usize, -) -> Result<()> { - let mut result = BenchmarkResult::new( - "Blocks Consumer (Ephemeral + AckNone)".into(), - limit, - ); - - let consumer = nats - .stream_blocks - .create_consumer(PullConsumerConfig { - deliver_policy: DeliverPolicy::New, - ack_policy: AckPolicy::None, - ..Default::default() - }) - .await?; - - let mut messages = consumer.messages().await?; - while let Some(message) = messages.next().await { - let msg = message?; - match nats - .data_parser() - .decode::>(&msg.payload) - .await - { - Err(_) => result.increment_error_count(), - Ok(decoded) => { - result - .add_publish_time(decoded.ts_as_millis()) - .increment_message_count(); - if result.is_complete() { - result.finalize(); - println!("{}", result); - break; - } - } - } - } - - Ok(()) -} diff --git a/benches/bench-consumers/src/runners/runner_kv_watcher.rs b/benches/bench-consumers/src/runners/runner_kv_watcher.rs deleted file mode 100644 index a4db8d73..00000000 --- a/benches/bench-consumers/src/runners/runner_kv_watcher.rs +++ /dev/null @@ -1,40 +0,0 @@ -use anyhow::Result; -use fuel_core_types::blockchain::block::Block; -use fuel_streams_core::prelude::StreamData; -use futures::StreamExt; -use nats_publisher::utils::nats::NatsHelper; - -use super::benchmark_results::BenchmarkResult; - -#[allow(dead_code)] -pub async fn run_watch_kv_blocks( - nats: &NatsHelper, - limit: usize, -) -> Result<()> { - let mut result = - BenchmarkResult::new("KV Blocks Watcher".to_string(), limit); - let mut watch = nats.kv_blocks.watch_all().await?; - - while let Some(message) = watch.next().await { - let item = message?; - match nats - .data_parser() - .decode::>(&item.value) - .await - { - Err(_) => result.increment_error_count(), - Ok(decoded) => { - result - .add_publish_time(decoded.ts_as_millis()) - .increment_message_count(); - if result.is_complete() { - result.finalize(); - println!("{}", result); - break; - } - } - } - } - - Ok(()) -} diff --git a/benches/bench-consumers/src/runners/runner_subscription.rs b/benches/bench-consumers/src/runners/runner_subscription.rs deleted file mode 100644 index 1ec3a7f6..00000000 --- a/benches/bench-consumers/src/runners/runner_subscription.rs +++ /dev/null @@ -1,35 +0,0 @@ -use anyhow::Result; -use fuel_core_types::blockchain::block::Block; -use fuel_streams_core::prelude::StreamData; -use futures::StreamExt; -use nats_publisher::utils::nats::NatsHelper; - -use super::benchmark_results::BenchmarkResult; - -#[allow(dead_code)] -pub async fn run_subscriptions(nats: &NatsHelper, limit: usize) -> Result<()> { - let mut result = BenchmarkResult::new("Pub/Sub".to_string(), limit); - let mut subscriber = nats.client.subscribe("blocks.sub.*").await?; - while let Some(message) = subscriber.next().await { - let payload = message.payload; - match nats - .data_parser() - .decode::>(&payload) - .await - { - Err(_) => result.increment_error_count(), - Ok(decoded) => { - result - .add_publish_time(decoded.ts_as_millis()) - .increment_message_count(); - if result.is_complete() { - result.finalize(); - println!("{}", result); - break; - } - } - } - } - - Ok(()) -} diff --git a/benches/data-parser/benches/deserialize.rs b/benches/data-parser/benches/deserialize.rs index 0a1ce5d2..7df9efdd 100644 --- a/benches/data-parser/benches/deserialize.rs +++ b/benches/data-parser/benches/deserialize.rs @@ -59,7 +59,6 @@ fn bench_deserialize(c: &mut Criterion) { let result = runtime.block_on(async { data_parser .deserialize::>(&serialized) - .await .expect("deserialization failed") }); // Use black_box to make sure 'result' is considered used by the compiler diff --git a/benches/load-tester/Cargo.toml b/benches/load-tester/Cargo.toml deleted file mode 100644 index 32f93f06..00000000 --- a/benches/load-tester/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "load-tester" -authors = { workspace = true } -keywords = { workspace = true } -edition = { workspace = true } -homepage = { workspace = true } -license = { workspace = true } -repository = { workspace = true } -version = { workspace = true } -rust-version = { workspace = true } -publish = false - -[dependencies] -anyhow = { workspace = true } -async-nats = { workspace = true } -chrono = { workspace = true } -clap = { workspace = true } -fuel-streams = { workspace = true } -fuel-streams-core = { workspace = true, features = ["bench-helpers"] } -futures = { workspace = true } -statrs = "0.18.0" -tokio = { workspace = true } diff --git a/benches/load-tester/README.md b/benches/load-tester/README.md deleted file mode 100644 index 984a28a6..00000000 --- a/benches/load-tester/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Running - -To run the load-test suite: - - ```sh - cargo run -- --network testnet --max-subscriptions 10 --step-size 1 - ``` - -Adjustments are to be applied based on the network, max-subscriptions and step-size. diff --git a/benches/load-tester/src/lib.rs b/benches/load-tester/src/lib.rs deleted file mode 100644 index 0edfafa9..00000000 --- a/benches/load-tester/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod runners; diff --git a/benches/load-tester/src/main.rs b/benches/load-tester/src/main.rs deleted file mode 100644 index b13a223c..00000000 --- a/benches/load-tester/src/main.rs +++ /dev/null @@ -1,16 +0,0 @@ -use clap::Parser; -use load_tester::runners::{cli::Cli, runner_all::LoadTesterEngine}; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let cli = Cli::parse(); - println!("Running load test ..."); - let load_tester = LoadTesterEngine::new( - cli.network, - cli.max_subscriptions, - cli.step_size, - ); - load_tester.run().await?; - println!("Finished load testing!"); - Ok(()) -} diff --git a/benches/load-tester/src/runners/cli.rs b/benches/load-tester/src/runners/cli.rs deleted file mode 100644 index 46cb07ad..00000000 --- a/benches/load-tester/src/runners/cli.rs +++ /dev/null @@ -1,33 +0,0 @@ -use clap::Parser; -use fuel_streams::types::FuelNetwork; - -#[derive(Clone, Parser)] -pub struct Cli { - /// Fuel Network to connect to. - #[arg( - long, - value_name = "NETWORK", - env = "NETWORK", - default_value = "Local", - value_parser = clap::value_parser!(FuelNetwork) - )] - pub network: FuelNetwork, - /// Maximum subscriptions for load testing - #[arg( - long, - value_name = "MAXS", - env = "MAX_SUBS", - default_value = "10", - help = "Maximum subscriptions for load testing." - )] - pub max_subscriptions: u16, - /// Maximum step size for load testing - #[arg( - long, - value_name = "SSIZE", - env = "STEP_SIZE", - default_value = "1", - help = "Maximum step size for load testing." - )] - pub step_size: u16, -} diff --git a/benches/load-tester/src/runners/mod.rs b/benches/load-tester/src/runners/mod.rs deleted file mode 100644 index cc84e6f4..00000000 --- a/benches/load-tester/src/runners/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod cli; -pub mod results; -pub mod runner_all; -pub mod runner_streamable; diff --git a/benches/load-tester/src/runners/results.rs b/benches/load-tester/src/runners/results.rs deleted file mode 100644 index fe1b56b8..00000000 --- a/benches/load-tester/src/runners/results.rs +++ /dev/null @@ -1,118 +0,0 @@ -use core::fmt; -use std::{ - sync::{ - atomic::{AtomicUsize, Ordering}, - RwLock, - }, - time::{Duration, Instant}, -}; - -use chrono::{DateTime, Utc}; -use statrs::statistics::{Data, Distribution}; - -#[derive(Debug)] -pub struct LoadTestTracker { - pub name: String, - pub message_count: AtomicUsize, - pub error_count: AtomicUsize, - start_time: Instant, - pub elapsed_time: RwLock>, - pub messages_per_second: RwLock>, - pub publish_times: RwLock>, - pub mean_publish_time: RwLock>, -} - -impl fmt::Display for LoadTestTracker { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "\n{}\nLoadTest Results: {}\n{}\nTotal Messages: {}\nTotal Errors: {}\nElapsed Time: {:?}\nMessages per Second: {:.2}\nMean Publish Time: {:?}\n{}", - "=".repeat(50), - self.name, - "=".repeat(50), - self.message_count.load(Ordering::Relaxed), - self.error_count.load(Ordering::Relaxed), - self.elapsed_time.read().unwrap().unwrap_or_default(), - self.messages_per_second.read().unwrap().unwrap_or_default(), - self.mean_publish_time.read().unwrap().unwrap_or_default(), - "=".repeat(50) - ) - } -} - -impl LoadTestTracker { - pub fn new(name: String) -> Self { - Self { - name, - message_count: AtomicUsize::new(0), - error_count: AtomicUsize::new(0), - start_time: Instant::now(), - elapsed_time: RwLock::new(None), - messages_per_second: RwLock::new(None), - publish_times: RwLock::new(vec![]), - mean_publish_time: RwLock::new(None), - } - } - - pub fn increment_message_count(&self) { - self.message_count.fetch_add(1, Ordering::Relaxed); - } - - pub fn increment_error_count(&self) { - self.error_count.fetch_add(1, Ordering::Relaxed); - } - - pub fn refresh(&self) -> &Self { - self.calculate_mean_publish_time(); - - let elapsed = self.start_time.elapsed(); - let message_count = self.message_count.load(Ordering::Relaxed); - - if let Ok(mut elapsed_time) = self.elapsed_time.write() { - *elapsed_time = Some(elapsed); - } - - if let Ok(mut messages_per_second) = self.messages_per_second.write() { - *messages_per_second = - Some(message_count as f64 / elapsed.as_secs_f64()); - } - - self - } - - pub fn add_publish_time(&self, timestamp: u128) -> &Self { - let current_time = Utc::now(); - let publish_time = - DateTime::::from_timestamp_millis(timestamp as i64) - .expect("Invalid timestamp"); - let duration = current_time - .signed_duration_since(publish_time) - .to_std() - .expect("Duration calculation failed"); - - if let Ok(mut times) = self.publish_times.write() { - times.push(duration); - } - self - } - - pub fn calculate_mean_publish_time(&self) { - // Lock the mutex to access publish_times - let times = self.publish_times.read().unwrap(); - - if times.is_empty() { - return; - } - - let times_ns: Vec = - times.iter().map(|d| d.as_nanos() as f64).collect(); - drop(times); - - let data = Data::new(times_ns); - let mean_ns = data.mean().unwrap(); - - if let Ok(mut mean_publish_time) = self.mean_publish_time.write() { - *mean_publish_time = Some(Duration::from_nanos(mean_ns as u64)); - } - } -} diff --git a/benches/load-tester/src/runners/runner_all.rs b/benches/load-tester/src/runners/runner_all.rs deleted file mode 100644 index 21ee7815..00000000 --- a/benches/load-tester/src/runners/runner_all.rs +++ /dev/null @@ -1,247 +0,0 @@ -use std::{sync::Arc, time::Duration}; - -use anyhow::Result; -use fuel_streams::client::Client; -use fuel_streams_core::prelude::*; -use tokio::task::JoinHandle; - -use super::{ - results::LoadTestTracker, - runner_streamable::run_streamable_consumer, -}; - -pub struct LoadTesterEngine { - max_subscriptions: u16, - step_size: u16, - fuel_network: FuelNetwork, -} - -impl LoadTesterEngine { - pub fn new( - fuel_network: FuelNetwork, - max_subscriptions: u16, - step_size: u16, - ) -> Self { - Self { - fuel_network, - max_subscriptions, - step_size, - } - } -} - -impl LoadTesterEngine { - pub async fn run(&self) -> Result<(), anyhow::Error> { - let client = Client::connect(self.fuel_network).await?; - let mut handles: Vec> = vec![]; - // blocks - let blocks_test_tracker = - Arc::new(LoadTestTracker::new("Blocks Consumer".into())); - let blocks_test_tracker_printer = Arc::clone(&blocks_test_tracker); - - // inputs - let inputs_test_tracker = - Arc::new(LoadTestTracker::new("Inputs Consumer".into())); - let inputs_test_tracker_printer = Arc::clone(&inputs_test_tracker); - - // txs - let txs_test_tracker = - Arc::new(LoadTestTracker::new("Txs Consumer".into())); - let txs_test_tracker_printer = Arc::clone(&txs_test_tracker); - - // receipts - let receipts_test_tracker = - Arc::new(LoadTestTracker::new("Receipts Consumer".into())); - let receipts_test_tracker_printer = Arc::clone(&receipts_test_tracker); - - // utxos - let utxos_test_tracker = - Arc::new(LoadTestTracker::new("Utxos Consumer".into())); - let utxos_test_tracker_printer = Arc::clone(&utxos_test_tracker); - - // logs - let logs_test_tracker = - Arc::new(LoadTestTracker::new("Logs Consumer".into())); - let logs_test_tracker_printer = Arc::clone(&logs_test_tracker); - - // outputs - let outputs_test_tracker = - Arc::new(LoadTestTracker::new("Outputs Consumer".into())); - let outputs_test_tracker_printer = Arc::clone(&outputs_test_tracker); - - // print regularly the tracked metrics - handles.push(tokio::spawn(async move { - loop { - // blocks - blocks_test_tracker_printer.refresh(); - println!("{}", blocks_test_tracker_printer); - - // inputs - inputs_test_tracker_printer.refresh(); - println!("{}", inputs_test_tracker_printer); - - // txs - txs_test_tracker_printer.refresh(); - println!("{}", txs_test_tracker_printer); - - // utxos - utxos_test_tracker_printer.refresh(); - println!("{}", utxos_test_tracker_printer); - - // receipts - receipts_test_tracker_printer.refresh(); - println!("{}", receipts_test_tracker_printer); - - // outputs - outputs_test_tracker_printer.refresh(); - println!("{}", outputs_test_tracker_printer); - - // logs - logs_test_tracker_printer.refresh(); - println!("{}", logs_test_tracker_printer); - - // do a short pause - tokio::time::sleep(Duration::from_secs(5)).await; - } - })); - - // Incrementally increase subscriptions - for current_subs in - (1..=self.max_subscriptions).step_by(self.step_size as usize) - { - let client = client.clone(); - let blocks_test_tracker = Arc::clone(&blocks_test_tracker); - for _ in 0..current_subs { - // blocks - { - let client = client.clone(); - let blocks_test_tracker = Arc::clone(&blocks_test_tracker); - handles.push(tokio::spawn(async move { - if let Err(e) = run_streamable_consumer::( - &client, - blocks_test_tracker, - ) - .await - { - eprintln!( - "Error in blocks subscriptions - {:?}", - e - ); - } - })); - } - // logs - { - let client = client.clone(); - let logs_test_tracker = Arc::clone(&logs_test_tracker); - handles.push(tokio::spawn(async move { - if let Err(e) = run_streamable_consumer::( - &client, - logs_test_tracker, - ) - .await - { - eprintln!("Error in logs subscriptions - {:?}", e); - } - })); - } - // inputs - { - let client = client.clone(); - let inputs_test_tracker = Arc::clone(&inputs_test_tracker); - handles.push(tokio::spawn(async move { - if let Err(e) = run_streamable_consumer::( - &client, - inputs_test_tracker, - ) - .await - { - eprintln!( - "Error in inputs subscriptions - {:?}", - e - ); - } - })); - } - // txs - { - let client = client.clone(); - let txs_test_tracker = Arc::clone(&txs_test_tracker); - handles.push(tokio::spawn(async move { - if let Err(e) = run_streamable_consumer::( - &client, - txs_test_tracker, - ) - .await - { - eprintln!("Error in txs subscriptions - {:?}", e); - } - })); - } - // outputs - { - let client = client.clone(); - let outputs_test_tracker = - Arc::clone(&outputs_test_tracker); - handles.push(tokio::spawn(async move { - if let Err(e) = run_streamable_consumer::( - &client, - outputs_test_tracker, - ) - .await - { - eprintln!( - "Error in outputs subscriptions - {:?}", - e - ); - } - })); - } - // utxos - { - let client = client.clone(); - let utxos_test_tracker = Arc::clone(&utxos_test_tracker); - handles.push(tokio::spawn(async move { - if let Err(e) = run_streamable_consumer::( - &client, - utxos_test_tracker, - ) - .await - { - eprintln!("Error in utxos subscriptions - {:?}", e); - } - })); - } - // receipts - { - let client = client.clone(); - let receipts_test_tracker = - Arc::clone(&receipts_test_tracker); - handles.push(tokio::spawn(async move { - if let Err(e) = run_streamable_consumer::( - &client, - receipts_test_tracker, - ) - .await - { - eprintln!( - "Error in receipts subscriptions - {:?}", - e - ); - } - })); - } - } - - // Small pause between test iterations - tokio::time::sleep(Duration::from_secs(5)).await; - } - - // cleanup - for handle in handles.iter() { - handle.abort(); - } - - Ok(()) - } -} diff --git a/benches/load-tester/src/runners/runner_streamable.rs b/benches/load-tester/src/runners/runner_streamable.rs deleted file mode 100644 index 54092720..00000000 --- a/benches/load-tester/src/runners/runner_streamable.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::sync::Arc; - -use anyhow::Result; -pub use async_nats::jetstream::consumer::DeliverPolicy; -use fuel_streams::{client::Client, StreamConfig}; -use fuel_streams_core::prelude::*; -use futures::StreamExt; - -use super::results::LoadTestTracker; - -pub async fn run_streamable_consumer( - client: &Client, - load_test_tracker: Arc, -) -> Result<()> { - // Create a new stream for blocks - let stream = fuel_streams::Stream::::new(client).await; - - // Configure the stream to start from the last published block - let config = StreamConfig { - deliver_policy: DeliverPolicy::Last, - }; - - // Subscribe to the block stream with the specified configuration - let mut sub = stream.subscribe_with_config(config).await?; - - // Process incoming blocks - while let Some(bytes) = sub.next().await { - match bytes.as_ref() { - Err(_) => load_test_tracker.increment_error_count(), - Ok(message) => { - load_test_tracker.increment_message_count(); - let decoded_msg = S::decode_raw(message.payload.to_vec()).await; - let ts_millis = decoded_msg.ts_as_millis(); - load_test_tracker - .add_publish_time(ts_millis) - .increment_message_count(); - } - } - } - - Ok(()) -} diff --git a/benches/nats-publisher/Cargo.toml b/benches/nats-publisher/Cargo.toml deleted file mode 100644 index 365a8df3..00000000 --- a/benches/nats-publisher/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "nats-publisher" -authors = { workspace = true } -keywords = { workspace = true } -edition = { workspace = true } -homepage = { workspace = true } -license = { workspace = true } -repository = { workspace = true } -version = { workspace = true } -rust-version = { workspace = true } -publish = false - -[dependencies] -anyhow = { workspace = true } -async-nats = { workspace = true } -clap = { workspace = true } -fuel-core = { workspace = true } -fuel-core-bin = { workspace = true } -fuel-core-importer = { workspace = true } -fuel-core-storage = { workspace = true } -fuel-core-types = { workspace = true } -fuel-data-parser = { workspace = true } -fuel-streams-core = { workspace = true } -tokio = { workspace = true } -tracing = { workspace = true } - -[dev-dependencies] -criterion = { version = "0.5", features = ["html_reports", "async_tokio"] } diff --git a/benches/nats-publisher/README.md b/benches/nats-publisher/README.md deleted file mode 100644 index 1dbe8ad5..00000000 --- a/benches/nats-publisher/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Running - -1. First make sure you have your `.env` configured properly: - - ```sh - make create-env - ``` - -2. Make sure you have NATS server running within the workspace root: - - ```sh - make start-nats - ``` - -3. The, you can start local node and start publishing on NATS: - ```sh - make run-publisher - ``` diff --git a/benches/nats-publisher/config/nats.conf b/benches/nats-publisher/config/nats.conf deleted file mode 100644 index e2b7d425..00000000 --- a/benches/nats-publisher/config/nats.conf +++ /dev/null @@ -1,5 +0,0 @@ -jetstream: { - max_mem_store: 64MiB, - max_file_store: 10GiB -} -max_payload = 8388608 diff --git a/benches/nats-publisher/src/lib.rs b/benches/nats-publisher/src/lib.rs deleted file mode 100644 index 3bed9398..00000000 --- a/benches/nats-publisher/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[allow(unused)] -pub mod utils; diff --git a/benches/nats-publisher/src/main.rs b/benches/nats-publisher/src/main.rs deleted file mode 100644 index 62050a44..00000000 --- a/benches/nats-publisher/src/main.rs +++ /dev/null @@ -1,78 +0,0 @@ -mod utils; - -use clap::Parser; -use fuel_core_importer::ports::ImporterDatabase; -use fuel_streams_core::prelude::*; -use utils::{blocks::BlockHelper, nats::NatsHelper, tx::TxHelper}; - -#[derive(Parser)] -pub struct Cli { - #[command(flatten)] - fuel_core_config: fuel_core_bin::cli::run::Command, -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - fuel_core_bin::cli::init_logging(); - - let cli = Cli::parse(); - let service = - fuel_core_bin::cli::run::get_service(cli.fuel_core_config).await?; - let chain_config = service.shared.config.snapshot_reader.chain_config(); - let chain_id = chain_config.consensus_parameters.chain_id(); - let block_importer = service.shared.block_importer.block_importer.clone(); - let database = service.shared.database.clone(); - - service - .start_and_await() - .await - .expect("Fuel core service startup failed"); - - // ------------------------------------------------------------------------ - // NATS - // ------------------------------------------------------------------------ - let nats = NatsHelper::connect(true).await?; - let block_helper = BlockHelper::new(nats.to_owned(), &database); - let tx_helper = TxHelper::new(nats.to_owned(), &chain_id, &database); - - // ------------------------------------------------------------------------ - // OLD BLOCKS - // ------------------------------------------------------------------------ - tokio::task::spawn({ - let database = database.clone(); - let block_helper = block_helper.clone(); - let _tx_helper = tx_helper.clone(); - let last_height = database.on_chain().latest_block_height()?.unwrap(); - async move { - for height in 0..*last_height { - let height = height.into(); - let block = block_helper.find_by_height(height); - let block = - Block::new(&block, Consensus::default(), Vec::new()); - - block_helper.publish(&block).await?; - // for (index, tx) in block.transactions().iter().enumerate() { - // tx_helper.publish(&block, tx, index).await?; - // } - } - Ok::<(), async_nats::Error>(()) - } - }); - - // ------------------------------------------------------------------------ - // NEW BLOCKS - // ------------------------------------------------------------------------ - let mut subscription = block_importer.subscribe(); - while let Ok(result) = subscription.recv().await { - let result = &**result; - let block = &result.sealed_block.entity; - let block = Block::new(block, Consensus::default(), Vec::new()); - - block_helper.publish(&block).await?; - // for (index, tx) in block.transactions().iter().enumerate() { - // tx_helper.publish(block, tx, index).await?; - // } - } - - Ok(()) -} diff --git a/benches/nats-publisher/src/utils/blocks.rs b/benches/nats-publisher/src/utils/blocks.rs deleted file mode 100644 index 3f192f7c..00000000 --- a/benches/nats-publisher/src/utils/blocks.rs +++ /dev/null @@ -1,93 +0,0 @@ -use async_nats::jetstream::context::Publish; -use fuel_core::combined_database::CombinedDatabase; -use fuel_core_storage::transactional::AtomicView; -use fuel_streams_core::prelude::*; -use tokio::try_join; -use tracing::info; - -use super::nats::NatsHelper; - -#[derive(Clone)] -pub struct BlockHelper { - nats: NatsHelper, - database: CombinedDatabase, -} - -impl BlockHelper { - pub fn new(nats: NatsHelper, database: &CombinedDatabase) -> Self { - Self { - nats, - database: database.to_owned(), - } - } - - pub fn find_by_height(&self, height: FuelCoreBlockHeight) -> FuelCoreBlock { - self.database - .on_chain() - .latest_view() - .unwrap() - .get_sealed_block_by_height(&height) - .unwrap() - .unwrap_or_else(|| { - panic!("NATS Publisher: no block at height {height}") - }) - .entity - } - - pub async fn publish(&self, block: &Block) -> anyhow::Result<()> { - try_join!( - self.publish_core(block), - self.publish_encoded(block), - self.publish_to_kv(block) - )?; - Ok(()) - } -} - -/// Publisher -impl BlockHelper { - async fn publish_core(&self, block: &Block) -> anyhow::Result<()> { - let subject: BlocksSubject = block.into(); - let payload = self.nats.data_parser().encode(block).await?; - self.nats - .context - .publish(subject.parse(), payload.into()) - .await?; - - Ok(()) - } - async fn publish_encoded(&self, block: &Block) -> anyhow::Result<()> { - let height = block.height; - let subject: BlocksSubject = block.into(); - let payload = self.nats.data_parser().encode(block).await?; - let nats_payload = Publish::build() - .message_id(subject.parse()) - .payload(payload.into()); - - self.nats - .context - .send_publish(subject.parse(), nats_payload) - .await? - .await?; - - info!( - "NATS: publishing block {} encoded to stream \"blocks_encoded\"", - height - ); - Ok(()) - } - - async fn publish_to_kv(&self, block: &Block) -> anyhow::Result<()> { - let height = block.height; - let subject: BlocksSubject = block.into(); - - let payload = self.nats.data_parser().encode(block).await?; - self.nats - .kv_blocks - .put(subject.parse(), payload.into()) - .await?; - - info!("NATS: publishing block {} to kv store \"blocks\"", height); - Ok(()) - } -} diff --git a/benches/nats-publisher/src/utils/mod.rs b/benches/nats-publisher/src/utils/mod.rs deleted file mode 100644 index 642417eb..00000000 --- a/benches/nats-publisher/src/utils/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod blocks; -pub mod nats; -pub mod tx; diff --git a/benches/nats-publisher/src/utils/nats.rs b/benches/nats-publisher/src/utils/nats.rs deleted file mode 100644 index a1d66c78..00000000 --- a/benches/nats-publisher/src/utils/nats.rs +++ /dev/null @@ -1,130 +0,0 @@ -use async_nats::{ - jetstream::{ - kv::{self, Store}, - stream::{self, Compression, Stream}, - Context, - }, - ConnectOptions, -}; -use fuel_data_parser::DataParser; -use fuel_streams_core::nats::FuelNetwork; - -#[allow(dead_code)] -#[derive(Clone)] -pub struct NatsHelper { - pub client: async_nats::Client, - pub kv_blocks: Store, - pub kv_transactions: Store, - pub context: Context, - pub stream_blocks: Stream, - pub stream_transactions: Stream, - pub use_nats_compression: bool, - pub data_parser: DataParser, -} - -impl NatsHelper { - pub async fn connect(use_nats_compression: bool) -> anyhow::Result { - let client = connect().await?; - let ( - context, - kv_blocks, - kv_transactions, - stream_blocks, - stream_transactions, - ) = create_resources(&client, use_nats_compression).await?; - // adjust as needed - let data_parser = DataParser::default(); - - Ok(Self { - client, - context, - kv_blocks, - kv_transactions, - stream_blocks, - stream_transactions, - use_nats_compression, - data_parser, - }) - } - - #[allow(dead_code)] - pub fn data_parser(&self) -> &DataParser { - &self.data_parser - } - - #[allow(dead_code)] - pub fn data_parser_mut(&mut self) -> &mut DataParser { - &mut self.data_parser - } -} - -pub async fn connect() -> anyhow::Result { - Ok(ConnectOptions::new() - .user_and_password("admin".into(), "secret".into()) - .connect(FuelNetwork::Local.to_url()) - .await?) -} - -async fn create_resources( - client: &async_nats::Client, - use_nats_compression: bool, -) -> anyhow::Result<(Context, Store, Store, Stream, Stream)> { - let jetstream = async_nats::jetstream::new(client.clone()); - - // ------------------------------------------------------------------------ - // BLOCKS - // ------------------------------------------------------------------------ - let stream_blocks = jetstream - .get_or_create_stream(stream::Config { - name: "blocks_encoded".into(), - subjects: vec!["blocks.>".into()], - compression: if use_nats_compression { - Some(Compression::S2) - } else { - None - }, - ..Default::default() - }) - .await?; - - // TRANSACTIONS - // ------------------------------------------------------------------------ - let stream_transactions = jetstream - .get_or_create_stream(stream::Config { - name: "transactions_encoded".into(), - subjects: vec!["transactions.>".into()], - compression: if use_nats_compression { - Some(Compression::S2) - } else { - None - }, - ..Default::default() - }) - .await?; - - // KV STORE - // ------------------------------------------------------------------------ - let kv_blocks = jetstream - .create_key_value(kv::Config { - compression: use_nats_compression, - bucket: "blocks".into(), - ..Default::default() - }) - .await?; - - let kv_transactions = jetstream - .create_key_value(kv::Config { - compression: use_nats_compression, - bucket: "transactions".into(), - ..Default::default() - }) - .await?; - - Ok(( - jetstream, - kv_blocks, - kv_transactions, - stream_blocks, - stream_transactions, - )) -} diff --git a/benches/nats-publisher/src/utils/tx.rs b/benches/nats-publisher/src/utils/tx.rs deleted file mode 100644 index 49da5f26..00000000 --- a/benches/nats-publisher/src/utils/tx.rs +++ /dev/null @@ -1,129 +0,0 @@ -use async_nats::jetstream::context::Publish; -use fuel_core::combined_database::CombinedDatabase; -use fuel_core_types::fuel_types::ChainId; -use fuel_streams_core::prelude::*; -use tokio::try_join; -use tracing::info; - -use super::nats::NatsHelper; - -#[allow(unused)] -#[derive(Clone)] -pub struct TxHelper { - nats: NatsHelper, - chain_id: ChainId, - database: CombinedDatabase, -} - -#[allow(unused)] -/// Public -impl TxHelper { - pub fn new( - nats: NatsHelper, - chain_id: &ChainId, - database: &CombinedDatabase, - ) -> Self { - Self { - nats, - chain_id: chain_id.to_owned(), - database: database.to_owned(), - } - } - - pub async fn publish( - &self, - block: &Block, - tx: &Transaction, - index: usize, - ) -> anyhow::Result<()> { - try_join!( - self.publish_core(block, tx, index), - self.publish_encoded(block, tx, index), - self.publish_to_kv(block, tx, index) - )?; - Ok(()) - } -} - -/// Publishers -impl TxHelper { - async fn publish_core( - &self, - block: &Block, - tx: &Transaction, - index: usize, - ) -> anyhow::Result<()> { - let subject = &self.get_subject(tx, block, index); - let payload = self.nats.data_parser().encode(block).await?; - self.nats - .context - .publish(subject.parse(), payload.into()) - .await?; - Ok(()) - } - - async fn publish_encoded( - &self, - block: &Block, - tx: &Transaction, - index: usize, - ) -> anyhow::Result<()> { - let tx_id = &tx.id; - let subject = self.get_subject(tx, block, index); - let payload = self.nats.data_parser().encode(block).await?; - let nats_payload = Publish::build() - .message_id(subject.parse()) - .payload(payload.into()); - - self.nats - .context - .send_publish(subject.parse(), nats_payload) - .await? - .await?; - - info!( - "NATS: publishing transaction {} json to stream \"transactions_encoded\"", - tx_id - ); - Ok(()) - } - - async fn publish_to_kv( - &self, - block: &Block, - tx: &Transaction, - index: usize, - ) -> anyhow::Result<()> { - let tx_id = &tx.id; - let subject = self.get_subject(tx, block, index); - let payload = self.nats.data_parser().encode(block).await?; - self.nats - .kv_transactions - .put(subject.parse(), payload.into()) - .await?; - - info!( - "NATS: publishing transaction {} to kv store \"transactions\"", - tx_id - ); - Ok(()) - } -} - -/// Getters -impl TxHelper { - fn get_subject( - &self, - tx: &Transaction, - block: &Block, - index: usize, - ) -> TransactionsSubject { - // construct tx subject - let mut subject: TransactionsSubject = tx.into(); - subject = subject - .with_index(Some(index)) - .with_block_height(Some(BlockHeight::from(block.height))) - .with_status(Some(tx.status.clone())); - subject - } -} diff --git a/cluster/charts/fuel-streams-publisher/.helmignore b/cluster/charts/fuel-streams-publisher/.helmignore deleted file mode 100644 index 0e8a0eb3..00000000 --- a/cluster/charts/fuel-streams-publisher/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/cluster/charts/fuel-streams-publisher/Chart.yaml b/cluster/charts/fuel-streams-publisher/Chart.yaml deleted file mode 100644 index c3ed7724..00000000 --- a/cluster/charts/fuel-streams-publisher/Chart.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: v2 -name: fuel-streams-publisher -description: A Helm chart for Kubernetes deployment of Fuel Core NATS -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.4.7 -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: 0.2.0 diff --git a/cluster/charts/fuel-streams-publisher/templates/_helpers.tpl b/cluster/charts/fuel-streams-publisher/templates/_helpers.tpl deleted file mode 100644 index 0bc5f455..00000000 --- a/cluster/charts/fuel-streams-publisher/templates/_helpers.tpl +++ /dev/null @@ -1,62 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "fuel-streams-publisher.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "fuel-streams-publisher.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "fuel-streams-publisher.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "fuel-streams-publisher.labels" -}} -helm.sh/chart: {{ include "fuel-streams-publisher.chart" . }} -{{ include "fuel-streams-publisher.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "fuel-streams-publisher.selectorLabels" -}} -app.kubernetes.io/name: {{ include "fuel-streams-publisher.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "fuel-streams-publisher.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "fuel-streams-publisher.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} diff --git a/cluster/charts/fuel-streams-publisher/templates/hpa.yaml b/cluster/charts/fuel-streams-publisher/templates/hpa.yaml deleted file mode 100644 index b2759de2..00000000 --- a/cluster/charts/fuel-streams-publisher/templates/hpa.yaml +++ /dev/null @@ -1,32 +0,0 @@ -{{- if .Values.autoscaling.enabled }} -apiVersion: autoscaling/v2 -kind: HorizontalPodAutoscaler -metadata: - name: {{ include "fuel-streams-publisher.fullname" . }} - labels: - {{- include "fuel-streams-publisher.labels" . | nindent 4 }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ include "fuel-streams-publisher.fullname" . }} - minReplicas: {{ .Values.autoscaling.minReplicas }} - maxReplicas: {{ .Values.autoscaling.maxReplicas }} - metrics: - {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} - - type: Resource - resource: - name: cpu - target: - type: Utilization - averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} - {{- end }} - {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} - - type: Resource - resource: - name: memory - target: - type: Utilization - averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} - {{- end }} -{{- end }} \ No newline at end of file diff --git a/cluster/charts/fuel-streams-publisher/templates/service.yaml b/cluster/charts/fuel-streams-publisher/templates/service.yaml deleted file mode 100644 index 49c7f6fe..00000000 --- a/cluster/charts/fuel-streams-publisher/templates/service.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Service - -metadata: - name: {{ include "fuel-streams-publisher.fullname" . }} - labels: - {{- include "fuel-streams-publisher.labels" . | nindent 4 }} - -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.port }} - targetPort: http - protocol: TCP - name: http - selector: - {{- include "fuel-streams-publisher.selectorLabels" . | nindent 4 }} diff --git a/cluster/charts/fuel-streams-publisher/templates/serviceaccount.yaml b/cluster/charts/fuel-streams-publisher/templates/serviceaccount.yaml deleted file mode 100644 index b3c456bb..00000000 --- a/cluster/charts/fuel-streams-publisher/templates/serviceaccount.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{- if .Values.serviceAccount.create -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "fuel-streams-publisher.serviceAccountName" . }} - labels: - {{- include "fuel-streams-publisher.labels" . | nindent 4 }} - {{- with .Values.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -automountServiceAccountToken: {{ .Values.serviceAccount.automount }} -{{- end }} diff --git a/cluster/charts/fuel-streams-publisher/templates/statefulset.yaml b/cluster/charts/fuel-streams-publisher/templates/statefulset.yaml deleted file mode 100644 index b0d6b039..00000000 --- a/cluster/charts/fuel-streams-publisher/templates/statefulset.yaml +++ /dev/null @@ -1,147 +0,0 @@ -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ include "fuel-streams-publisher.fullname" . }} - labels: - {{- include "fuel-streams-publisher.labels" . | nindent 4 }} -spec: - # Define the headless service that governs this StatefulSet - serviceName: {{ include "fuel-streams-publisher.fullname" . | quote }} - # Handle replica count unless autoscaling is enabled - {{- if not .Values.autoscaling.enabled }} - replicas: {{ .Values.config.replicaCount }} - {{- end }} - selector: - matchLabels: - {{- include "fuel-streams-publisher.selectorLabels" . | nindent 6 }} - template: - metadata: - annotations: - # Prometheus scraping configuration - {{- if .Values.prometheus.enabled }} - prometheus.io/scrape: {{ .Values.prometheus.scrape | quote }} - prometheus.io/port: {{ .Values.service.port | quote }} - prometheus.io/path: {{ .Values.prometheus.path | quote }} - {{- end }} - # Add checksums to force pod restart when configs change - {{/* checksum/config: {{ include (print $.Template.BasePath "/env-configmap.yaml") . | sha256sum }} */}} - {{/* checksum/secrets: {{ include (print $.Template.BasePath "/env-secrets.yaml") . | sha256sum }} */}} - {{- with .Values.config.annotations }} - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "fuel-streams-publisher.labels" . | nindent 8 }} - {{- with .Values.config.labels }} - {{- toYaml . | nindent 8 }} - {{- end }} - spec: - {{- with .Values.config.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "fuel-streams-publisher.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.config.podSecurityContext | nindent 8 }} - # Initialize persistent volumes with correct permissions - {{- if .Values.persistence.enabled }} - initContainers: - - name: {{ .Values.persistence.data.containerName }} - image: alpine:latest - imagePullPolicy: IfNotPresent - command: ["/bin/chown"] - args: ["-R", "1000:1000", "{{ .Values.persistence.data.mountPath }}"] - volumeMounts: - - name: {{ .Values.persistence.data.name }} - mountPath: {{ .Values.persistence.data.mountPath }} - - name: {{ .Values.persistence.temp.containerName }} - image: alpine:latest - imagePullPolicy: IfNotPresent - command: ["/bin/chown"] - args: ["-R", "1000:1000", "{{ .Values.persistence.temp.mountPath }}"] - volumeMounts: - - name: {{ .Values.persistence.temp.name }} - mountPath: {{ .Values.persistence.temp.mountPath }} - {{- end }} - # Main application container - containers: - - name: {{ .Chart.Name }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - # Define container ports for application and metrics - ports: - - name: http - containerPort: {{ int .Values.service.port }} - protocol: TCP - # Health check probes - livenessProbe: - {{- toYaml .Values.livenessProbe | nindent 12 }} - readinessProbe: - {{- toYaml .Values.readinessProbe | nindent 12 }} - resources: - {{- toYaml .Values.config.resources | nindent 12 }} - env: - {{- range $key, $value := .Values.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - {{- if .Values.extraEnv }} - {{- toYaml .Values.extraEnv | nindent 12 }} - {{- end }} - envFrom: - - configMapRef: - name: {{ include "fuel-streams-publisher.fullname" . }} - optional: true - - secretRef: - name: {{ include "fuel-streams-publisher.fullname" . }} - optional: true - {{- if .Values.envFrom }} - {{- toYaml .Values.envFrom | nindent 12 }} - {{- end }} - - # Mount persistent volumes if enabled - {{- if .Values.persistence.enabled }} - volumeMounts: - - name: {{ .Values.persistence.data.name }} - mountPath: {{ .Values.persistence.data.mountPath }} - readOnly: false - - name: {{ .Values.persistence.temp.name }} - mountPath: {{ .Values.persistence.temp.mountPath }} - readOnly: false - {{- end }} - # Node assignment rules - {{- with .Values.config.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.config.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.config.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} - # Persistent volume claims configuration - {{- if .Values.persistence.enabled }} - volumeClaimTemplates: - - metadata: - name: {{ .Values.persistence.data.name }} - spec: - accessModes: - - {{ .Values.persistence.data.accessMode }} - storageClassName: {{ .Values.persistence.data.storageClass }} - resources: - requests: - storage: {{ .Values.persistence.data.size }} - - metadata: - name: {{ .Values.persistence.temp.name }} - spec: - accessModes: - - {{ .Values.persistence.temp.accessMode }} - storageClassName: {{ .Values.persistence.temp.storageClass }} - resources: - requests: - storage: {{ .Values.persistence.temp.size }} - {{- end }} diff --git a/cluster/charts/fuel-streams-publisher/values.yaml b/cluster/charts/fuel-streams-publisher/values.yaml deleted file mode 100644 index 91ca6eef..00000000 --- a/cluster/charts/fuel-streams-publisher/values.yaml +++ /dev/null @@ -1,124 +0,0 @@ -# Default values for fuel-streams-publisher -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -# These are custom resource definitions that can be overridden by the user -# nameOverride: "" -# fullnameOverride: "" - -# general configurations -config: - replicaCount: 1 - imagePullSecrets: [] - annotations: {} - labels: {} - nodeSelector: {} - tolerations: [] - affinity: {} - resources: {} - podSecurityContext: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # resources: - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -image: - repository: ghcr.io/fuellabs/fuel-streams-publisher - pullPolicy: Always - tag: "latest" - -serviceAccount: - create: true - automount: true - # annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - # name: "" - -service: - type: ClusterIP - port: 8080 - -prometheus: - enabled: true - scrape: true - path: /metrics - -securityContext: - capabilities: - drop: [ALL] - readOnlyRootFilesystem: true - runAsNonRoot: true - runAsUser: 1000 - -livenessProbe: {} -readinessProbe: {} - -autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 3 - targetCPUUtilizationPercentage: 80 - targetMemoryUtilizationPercentage: 80 - -persistence: - enabled: true - data: - name: rocks-db-vol - containerName: update-rocks-db-vol - mountPath: /mnt/db - size: 500Gi - storageClass: gp3-generic - accessMode: ReadWriteOnce - temp: - name: tmp-vol - containerName: update-tmp-vol - mountPath: /tmp - size: 5Gi - storageClass: gp3-generic - accessMode: ReadWriteOnce - -# Additional environment variables with complex structures -# extraEnv: -# - name: RELAYER -# valueFrom: -# secretKeyRef: -# name: fuel-streams-publisher -# key: RELAYER -# - name: KEYPAIR -# valueFrom: -# secretKeyRef: -# name: fuel-streams-publisher -# key: KEYPAIR -# - name: NATS_ADMIN_PASS -# valueFrom: -# secretKeyRef: -# name: fuel-streams-publisher -# key: NATS_ADMIN_PASS -# Optional: Bulk environment references -# envFrom: -# - configMapRef: -# name: additional-config -# - secretRef: -# name: additional-secrets - -env: - RELAYER_V2_LISTENING_CONTRACTS: "0xAEB0c00D0125A8a788956ade4f4F12Ead9f65DDf" - RELAYER_DA_DEPLOY_HEIGHT: "20620434" - RELAYER_LOG_PAGE_SIZE: "2000" - SYNC_HEADER_BATCH_SIZE: "100" - P2P_PORT: "30333" - RESERVED_NODES: "/dnsaddr/mainnet.fuel.network" - CHAIN_CONFIG: "mainnet" - PUBLISHER_MAX_THREADS: "32" - DB_PATH: "/mnt/db/" - POA_INSTANT: "false" - SERVICE_NAME: "NATS Publisher Node" - NATS_URL: "nats:4222" diff --git a/cluster/charts/fuel-streams/Chart.yaml b/cluster/charts/fuel-streams/Chart.yaml index 61e1bdf8..228de229 100755 --- a/cluster/charts/fuel-streams/Chart.yaml +++ b/cluster/charts/fuel-streams/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 appVersion: "1.0" description: A Helm chart for Kubernetes name: fuel-streams -version: 0.5.7 +version: 0.7.2 dependencies: - name: nats version: 1.2.8 @@ -14,8 +14,3 @@ dependencies: repository: https://nats-io.github.io/k8s/helm/charts/ alias: nats-publisher condition: nats-publisher.enabled - - name: nats - version: 1.2.8 - repository: https://nats-io.github.io/k8s/helm/charts/ - alias: nats-client - condition: nats-client.enabled diff --git a/cluster/charts/fuel-streams/templates/_blocks.tpl b/cluster/charts/fuel-streams/templates/_blocks.tpl index 55e3bf07..580c16e6 100644 --- a/cluster/charts/fuel-streams/templates/_blocks.tpl +++ b/cluster/charts/fuel-streams/templates/_blocks.tpl @@ -68,13 +68,13 @@ data: accounts { SYS: { users: [ - {user: $NATS_SYS_USER, password: $NATS_SYS_PASSWORD} + {user: $NATS_SYSTEM_USER, password: $NATS_SYSTEM_PASS} ] } ADMIN: { jetstream: enabled users: [ - {user: $NATS_ADMIN_USER, password: $NATS_ADMIN_PASSWORD} + {user: $NATS_ADMIN_USER, password: $NATS_ADMIN_PASS} ] } PUBLIC: { @@ -82,7 +82,7 @@ data: users: [ { user: $NATS_PUBLIC_USER - password: $NATS_PUBLIC_PASSWORD + password: $NATS_PUBLIC_PASS permissions: { subscribe: ">" publish: { diff --git a/cluster/charts/fuel-streams/templates/_helpers.tpl b/cluster/charts/fuel-streams/templates/_helpers.tpl index 24026951..e3de60d9 100644 --- a/cluster/charts/fuel-streams/templates/_helpers.tpl +++ b/cluster/charts/fuel-streams/templates/_helpers.tpl @@ -217,4 +217,4 @@ Returns: Value if it exists and is not empty {{- if and $value (not (empty $value)) (not (eq (kindOf $value) "invalid")) }} {{- toYaml $value | nindent 0 }} {{- end }} -{{- end }} +{{- end }} \ No newline at end of file diff --git a/cluster/charts/fuel-streams/templates/common-config.yaml b/cluster/charts/fuel-streams/templates/common-config.yaml new file mode 100644 index 00000000..0ffe75dd --- /dev/null +++ b/cluster/charts/fuel-streams/templates/common-config.yaml @@ -0,0 +1,21 @@ +{{- if .Values.commonConfigMap.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: fuel-streams-config + labels: + app.kubernetes.io/instance: fuel-streams +data: + {{ .Values.commonConfigMap.data | toYaml | nindent 2 }} +{{- end }} +{{- if .Values.localSecrets.enabled }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: fuel-streams-keys + labels: + app.kubernetes.io/instance: fuel-streams +stringData: + {{ .Values.localSecrets.data | toYaml | nindent 2 }} +{{- end }} diff --git a/cluster/charts/fuel-streams/templates/consumer/statefulset.yaml b/cluster/charts/fuel-streams/templates/consumer/statefulset.yaml index 5f15bb02..a1f73522 100644 --- a/cluster/charts/fuel-streams/templates/consumer/statefulset.yaml +++ b/cluster/charts/fuel-streams/templates/consumer/statefulset.yaml @@ -40,15 +40,22 @@ spec: - name: consumer image: "{{ $consumer.image.repository }}:{{ $consumer.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ $consumer.image.pullPolicy }} + command: ["/usr/src/sv-consumer"] args: - {{- toYaml $consumer.image.args | nindent 12 }} + - "--nats-url" + - "$(NATS_URL)" + - "--nats-publisher-url" + - "$(NATS_PUBLISHER_URL)" + {{- with $consumer.image.args }} + {{- toYaml . | nindent 10 }} + {{- end }} ports: - name: consumer containerPort: {{ $consumer.port }} protocol: TCP - {{- if $consumer.ports }} - {{- toYaml $consumer.ports | nindent 12 }} + {{- with $consumer.config.ports }} + {{- toYaml . | nindent 12 }} {{- end }} {{- include "set-field-and-value" (dict "context" $consumer "field" "resources" "path" "config.resources") | nindent 10 }} @@ -56,20 +63,18 @@ spec: {{- include "k8s.container-security-context" (dict "context" . "service" "consumer") | nindent 10 }} env: - {{- range $key, $value := $consumer.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - {{- with $consumer.extraEnv }} + - name: PORT + value: {{ $consumer.port | quote }} + {{- with $consumer.env }} {{- toYaml . | nindent 12 }} {{- end }} envFrom: - configMapRef: - name: {{ include "fuel-streams.fullname" $ }}-consumer + name: {{ include "fuel-streams.fullname" $ }}-config optional: true - secretRef: - name: {{ include "fuel-streams.fullname" $ }}-consumer + name: {{ include "fuel-streams.fullname" $ }}-keys optional: true {{- with $consumer.envFrom }} {{- toYaml . | nindent 12 }} diff --git a/cluster/charts/fuel-streams/templates/publisher/statefulset.yaml b/cluster/charts/fuel-streams/templates/publisher/statefulset.yaml index 85de7960..9220c741 100644 --- a/cluster/charts/fuel-streams/templates/publisher/statefulset.yaml +++ b/cluster/charts/fuel-streams/templates/publisher/statefulset.yaml @@ -77,70 +77,74 @@ spec: - name: publisher image: "{{ $publisher.image.repository }}:{{ $publisher.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ $publisher.image.pullPolicy }} + command: ["/usr/src/sv-publisher"] args: - # Common arguments - - "--enable-relayer" - - "--enable-p2p" - - "--keypair" - - "$(KEYPAIR)" - - "--relayer" - - "$(RELAYER)" - - "--ip" - - "0.0.0.0" - - "--port" - - "$(PORT)" - - "--db-path" - - "$(DB_PATH)" - - "--nats-url" - - "$(NATS_URL)" - - "--sync-header-batch-size" - - "100" - - "--relayer-log-page-size" - - "2000" - - "--sync-block-stream-buffer-size" - - "50" - - "--max-database-cache-size" - - "17179869184" - - "--state-rewind-duration" - - "136y" - - "--request-timeout" - - "60" - - "--graphql-max-complexity" - - "1000000000" - {{- if eq $publisher.network "mainnet" }} - # Mainnet specific args - - "--service-name" - - "Publisher Node (Mainnet)" - - "--snapshot" - - "./chain-config/mainnet" - - "--reserved-nodes" - - "/dnsaddr/mainnet.fuel.network" - - "--relayer-v2-listening-contracts" - - "0xAEB0c00D0125A8a788956ade4f4F12Ead9f65DDf" - - "--relayer-da-deploy-height" - - "20620434" - {{- else if eq $publisher.network "testnet" }} - # Testnet specific args - - "--service-name" - - "Publisher Node (Testnet)" - - "--snapshot" - - "./chain-config/testnet" - - "--reserved-nodes" - - "/dns4/p2p-testnet.fuel.network/tcp/30333/p2p/16Uiu2HAmDxoChB7AheKNvCVpD4PHJwuDGn8rifMBEHmEynGHvHrf,/dns4/p2p-testnet.fuel.network/tcp/30333/p2p/16Uiu2HAmHnANNk4HjAxQV66BNCRxd2MBUU89ijboZkE69aLuSn1g,/dns4/p2p-testnet.fuel.network/tcp/30333/p2p/16Uiu2HAmVE468rpkh2X1kzz8qQXmqNFiPxU5Lrya28nZdbRUdVJX" - - "--relayer-v2-listening-contracts" - - "0x01855B78C1f8868DE70e84507ec735983bf262dA" - - "--relayer-da-deploy-height" - - "5827607" - {{- end }} - {{- if $publisher.image.extraArgs }} - {{- toYaml $publisher.image.extraArgs | nindent 12 }} - {{- end }} + # Common arguments + - "--enable-relayer" + - "--enable-p2p" + - "--keypair" + - "$(KEYPAIR)" + - "--relayer" + - "$(RELAYER)" + - "--ip" + - "0.0.0.0" + - "--port" + - "$(PORT)" + - "--peering-port" + - "30333" + - "--utxo-validation" + - "--poa-instant" + - "false" + - "--db-path" + - "$(DB_PATH)" + - "--nats-url" + - "$(NATS_URL)" + - "--sync-header-batch-size" + - "100" + - "--relayer-log-page-size" + - "2000" + - "--sync-block-stream-buffer-size" + - "50" + - "--max-database-cache-size" + - "17179869184" + - "--state-rewind-duration" + - "136y" + - "--request-timeout" + - "60" + - "--graphql-max-complexity" + - "1000000000" + {{- if eq $publisher.network "mainnet" }} + # Mainnet specific args + - "--service-name" + - "Publisher Node (Mainnet)" + - "--snapshot" + - "./chain-config/mainnet" + - "--reserved-nodes" + - "/dnsaddr/mainnet.fuel.network" + - "--relayer-v2-listening-contracts" + - "0xAEB0c00D0125A8a788956ade4f4F12Ead9f65DDf" + - "--relayer-da-deploy-height" + - "20620434" + {{- else if eq $publisher.network "testnet" }} + # Testnet specific args + - "--service-name" + - "Publisher Node (Testnet)" + - "--snapshot" + - "./chain-config/testnet" + - "--reserved-nodes" + - "/dns4/p2p-testnet.fuel.network/tcp/30333/p2p/16Uiu2HAmDxoChB7AheKNvCVpD4PHJwuDGn8rifMBEHmEynGHvHrf,/dns4/p2p-testnet.fuel.network/tcp/30333/p2p/16Uiu2HAmHnANNk4HjAxQV66BNCRxd2MBUU89ijboZkE69aLuSn1g,/dns4/p2p-testnet.fuel.network/tcp/30333/p2p/16Uiu2HAmVE468rpkh2X1kzz8qQXmqNFiPxU5Lrya28nZdbRUdVJX" + - "--relayer-v2-listening-contracts" + - "0x01855B78C1f8868DE70e84507ec735983bf262dA" + - "--relayer-da-deploy-height" + - "5827607" + {{- end }} + ports: - name: http - containerPort: {{ int $publisher.service.port }} + containerPort: {{ int $publisher.port }} protocol: TCP - {{- if $publisher.ports }} - {{- toYaml $publisher.ports | nindent 12 }} + {{- with $publisher.config.ports }} + {{- toYaml . | nindent 12 }} {{- end }} {{- include "set-field-and-value" (dict "context" $publisher "field" "resources" "path" "config.resources") | nindent 10 }} @@ -152,20 +156,18 @@ spec: value: "/var/fuel-streams/tmp" - name: DB_PATH value: {{ $publisher.storage.mountPath | default "/mnt/db" | quote }} - {{- range $key, $value := $publisher.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - {{- with $publisher.extraEnv }} + - name: PORT + value: {{ $publisher.port | quote }} + {{- with $publisher.env }} {{- toYaml . | nindent 12 }} {{- end }} envFrom: - configMapRef: - name: {{ include "fuel-streams.fullname" $ }}-publisher + name: {{ include "fuel-streams.fullname" $ }}-config optional: true - secretRef: - name: {{ include "fuel-streams.fullname" $ }}-publisher + name: {{ include "fuel-streams.fullname" $ }}-keys optional: true {{- with $publisher.envFrom }} {{- toYaml . | nindent 12 }} diff --git a/cluster/charts/fuel-streams/templates/webserver/deployment.yaml b/cluster/charts/fuel-streams/templates/webserver/deployment.yaml index 57b7dad3..687996be 100644 --- a/cluster/charts/fuel-streams/templates/webserver/deployment.yaml +++ b/cluster/charts/fuel-streams/templates/webserver/deployment.yaml @@ -1,4 +1,5 @@ {{- $webserver := .Values.webserver -}} +{{- $service := $webserver.service -}} {{- if $webserver.enabled -}} apiVersion: apps/v1 kind: Deployment @@ -9,6 +10,7 @@ metadata: labels: {{- include "fuel-streams.labels" (dict "name" "webserver" "context" .) | nindent 4 }} {{- include "set-value" (dict "context" $webserver "path" "config.labels") | nindent 4 }} + app.kubernetes.io/component: webserver spec: {{- if not $webserver.autoscaling.enabled }} replicas: {{ $webserver.config.replicaCount }} @@ -24,6 +26,7 @@ spec: labels: {{- include "fuel-streams.labels" (dict "name" "webserver" "context" .) | nindent 8 }} {{- include "set-value" (dict "context" $webserver "path" "config.labels") | nindent 8 }} + app.kubernetes.io/component: webserver spec: {{- if .Values.serviceAccount.create }} @@ -43,22 +46,30 @@ spec: ports: - name: webserver - containerPort: {{ $webserver.port }} + containerPort: {{ $service.port }} protocol: TCP - {{- if $webserver.ports }} - {{- toYaml $webserver.ports | nindent 12 }} - {{- end }} {{- include "set-field-and-value" (dict "context" $webserver "field" "resources" "path" "config.resources") | nindent 10 }} {{- include "k8s.probes" (dict "context" . "service" "webserver") | nindent 10 }} {{- include "k8s.container-security-context" (dict "context" . "service" "webserver") | nindent 10 }} - env: - {{- range $key, $value := $webserver.env }} - - name: {{ $key }} - value: {{ $value | quote }} + envFrom: + - configMapRef: + name: {{ include "fuel-streams.fullname" $ }}-config + optional: true + - secretRef: + name: {{ include "fuel-streams.fullname" $ }}-keys + optional: true + {{- with $webserver.envFrom }} + {{- toYaml . | nindent 12 }} {{- end }} - {{- with $webserver.extraEnv }} + + env: + - name: NETWORK + value: {{ $webserver.network | quote }} + - name: PORT + value: {{ $service.port | quote }} + {{- with $webserver.env }} {{- toYaml . | nindent 12 }} {{- end }} diff --git a/cluster/charts/fuel-streams/templates/nats/external-service.yaml b/cluster/charts/fuel-streams/templates/webserver/service.yaml similarity index 64% rename from cluster/charts/fuel-streams/templates/nats/external-service.yaml rename to cluster/charts/fuel-streams/templates/webserver/service.yaml index 22231bbc..345c03a4 100644 --- a/cluster/charts/fuel-streams/templates/nats/external-service.yaml +++ b/cluster/charts/fuel-streams/templates/webserver/service.yaml @@ -1,12 +1,14 @@ -{{- $service := .Values.natsExternalService.service }} -{{- if and .Values.natsExternalService.enabled $service.dns }} +{{- $service := .Values.webserver.service }} +{{- if and .Values.webserver.enabled $service.enabled }} apiVersion: v1 kind: Service metadata: - {{- include "k8s.metadata" (dict "context" . "suffix" "-nats-client-nlb") | nindent 2 }} + {{- include "k8s.metadata" (dict "context" . "suffix" "-webserver-nlb") | nindent 2 }} annotations: + {{- if $service.dns }} external-dns.alpha.kubernetes.io/hostname: {{ $service.dns }} external-dns.alpha.kubernetes.io/cloudflare-proxied: "false" + {{- end }} service.beta.kubernetes.io/aws-load-balancer-attributes: load_balancing.cross_zone.enabled=true service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip @@ -16,25 +18,20 @@ metadata: service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: "WebSocket=true" {{- include "set-value" (dict "context" $service "path" "annotations") | nindent 2 }} labels: - {{- include "fuel-streams.labels" (dict "name" "nats-client" "context" .) | nindent 4 }} + {{- include "fuel-streams.labels" (dict "name" "webserver" "context" .) | nindent 4 }} {{- include "set-value" (dict "context" $service "path" "labels") | nindent 4 }} - app.kubernetes.io/component: nats + app.kubernetes.io/component: webserver spec: - type: LoadBalancer + type: {{ $service.type }} loadBalancerClass: service.k8s.aws/nlb externalTrafficPolicy: Local ports: - appProtocol: tcp - name: nats - port: 4222 + name: websocket + port: {{ $service.port }} protocol: TCP - targetPort: nats - - appProtocol: tcp - name: wss - port: 8443 - protocol: TCP - targetPort: websocket + targetPort: {{ $service.port }} selector: - {{- include "fuel-streams.selectorLabels" (dict "name" "nats-client" "context" .) | nindent 4 }} - app.kubernetes.io/component: nats + {{- include "fuel-streams.selectorLabels" (dict "name" "webserver" "context" .) | nindent 4 }} + app.kubernetes.io/component: webserver {{- end }} diff --git a/cluster/charts/fuel-streams/tests/certificate_test.yaml b/cluster/charts/fuel-streams/tests/certificate_test.yaml index 179f8f4c..0c93d1ad 100644 --- a/cluster/charts/fuel-streams/tests/certificate_test.yaml +++ b/cluster/charts/fuel-streams/tests/certificate_test.yaml @@ -53,7 +53,7 @@ tests: documentIndex: 1 - equal: path: metadata.name - value: RELEASE-NAME-fuel-streams-ws-cert + value: RELEASE-NAME-sv-webserver-cert documentIndex: 1 - equal: path: spec.dnsNames[0] diff --git a/cluster/charts/fuel-streams/tests/consumer/deployment_test.yaml b/cluster/charts/fuel-streams/tests/consumer/deployment_test.yaml index 03334a73..534914e2 100644 --- a/cluster/charts/fuel-streams/tests/consumer/deployment_test.yaml +++ b/cluster/charts/fuel-streams/tests/consumer/deployment_test.yaml @@ -48,13 +48,13 @@ tests: - it: should set image configuration correctly set: consumer.enabled: true - consumer.image.repository: ghcr.io/fuellabs/fuel-streams-webserver + consumer.image.repository: ghcr.io/fuellabs/sv-webserver consumer.image.tag: latest consumer.image.pullPolicy: Always asserts: - equal: path: spec.template.spec.containers[0].image - value: ghcr.io/fuellabs/fuel-streams-webserver:latest + value: ghcr.io/fuellabs/sv-webserver:latest documentIndex: 0 - equal: path: spec.template.spec.containers[0].imagePullPolicy @@ -64,14 +64,14 @@ tests: - it: should use chart version when tag is not specified set: consumer.enabled: true - consumer.image.repository: ghcr.io/fuellabs/fuel-streams-webserver + consumer.image.repository: ghcr.io/fuellabs/sv-webserver consumer.image.tag: null Chart: Version: "1.0" asserts: - equal: path: spec.template.spec.containers[0].image - value: ghcr.io/fuellabs/fuel-streams-webserver:1.0 + value: ghcr.io/fuellabs/sv-webserver:1.0 documentIndex: 0 - it: should configure ports correctly diff --git a/cluster/charts/fuel-streams/tests/publisher/statefulset.yaml b/cluster/charts/fuel-streams/tests/publisher/statefulset.yaml index fd3c3f6b..eaa6af79 100644 --- a/cluster/charts/fuel-streams/tests/publisher/statefulset.yaml +++ b/cluster/charts/fuel-streams/tests/publisher/statefulset.yaml @@ -10,36 +10,36 @@ tests: of: StatefulSet - equal: path: metadata.name - value: RELEASE-NAME-fuel-streams-publisher + value: RELEASE-NAME-sv-publisher - it: should set correct image and tag set: publisher.enabled: true - publisher.image.repository: ghcr.io/fuellabs/fuel-streams-publisher + publisher.image.repository: ghcr.io/fuellabs/sv-publisher publisher.image.tag: latest asserts: - equal: path: spec.template.spec.containers[0].image - value: ghcr.io/fuellabs/fuel-streams-publisher:latest + value: ghcr.io/fuellabs/sv-publisher:latest - it: should use chart version when tag is not specified set: publisher.enabled: true - publisher.image.repository: ghcr.io/fuellabs/fuel-streams-publisher + publisher.image.repository: ghcr.io/fuellabs/sv-publisher publisher.image.tag: null Chart: Version: "1.0" asserts: - equal: path: spec.template.spec.containers[0].image - value: ghcr.io/fuellabs/fuel-streams-publisher:1.0 + value: ghcr.io/fuellabs/sv-publisher:1.0 - it: should merge environment variables correctly set: publisher.enabled: true publisher.env: - CHAIN_CONFIG: "testnet" # Override default - NEW_VAR: "new-value" # Add new var + CHAIN_CONFIG: "testnet" # Override default + NEW_VAR: "new-value" # Add new var publisher.extraEnv: - name: SIMPLE_VAR value: "simple-value" diff --git a/cluster/charts/fuel-streams/tests/webserver/deployment_test.yaml b/cluster/charts/fuel-streams/tests/webserver/deployment_test.yaml index 71aee4ac..9d5a148d 100644 --- a/cluster/charts/fuel-streams/tests/webserver/deployment_test.yaml +++ b/cluster/charts/fuel-streams/tests/webserver/deployment_test.yaml @@ -19,7 +19,7 @@ tests: of: apps/v1 - equal: path: metadata.name - value: RELEASE-NAME-fuel-streams-webserver + value: RELEASE-NAME-sv-webserver - equal: path: metadata.labels["app.kubernetes.io/component"] value: webserver @@ -40,13 +40,13 @@ tests: - it: should set image configuration correctly set: webserver.enabled: true - webserver.image.repository: ghcr.io/fuellabs/fuel-streams-webserver + webserver.image.repository: ghcr.io/fuellabs/sv-webserver webserver.image.tag: latest webserver.image.pullPolicy: Always asserts: - equal: path: spec.template.spec.containers[0].image - value: ghcr.io/fuellabs/fuel-streams-webserver:latest + value: ghcr.io/fuellabs/sv-webserver:latest - equal: path: spec.template.spec.containers[0].imagePullPolicy value: Always @@ -54,22 +54,22 @@ tests: - it: should use chart version when tag is not specified set: webserver.enabled: true - webserver.image.repository: ghcr.io/fuellabs/fuel-streams-webserver + webserver.image.repository: ghcr.io/fuellabs/sv-webserver webserver.image.tag: null Chart: Version: "1.0" asserts: - equal: path: spec.template.spec.containers[0].image - value: ghcr.io/fuellabs/fuel-streams-webserver:1.0 + value: ghcr.io/fuellabs/sv-webserver:1.0 - it: should configure ports correctly set: webserver.enabled: true - webserver.service.port: 8082 + webserver.service.port: 9003 webserver.ports: - name: metrics - containerPort: 9090 + containerPort: 9003 protocol: TCP asserts: - lengthEqual: @@ -79,13 +79,13 @@ tests: path: spec.template.spec.containers[0].ports content: name: webserver - containerPort: 8082 + containerPort: 9003 protocol: TCP - contains: path: spec.template.spec.containers[0].ports content: name: metrics - containerPort: 9090 + containerPort: 9003 protocol: TCP - it: should set replicas when autoscaling is disabled @@ -186,4 +186,4 @@ tests: value: 50m - equal: path: spec.template.spec.containers[0].resources.requests.memory - value: 64Mi \ No newline at end of file + value: 64Mi diff --git a/cluster/charts/fuel-streams/values-local.yaml b/cluster/charts/fuel-streams/values-local.yaml index dc5af796..46254d83 100644 --- a/cluster/charts/fuel-streams/values-local.yaml +++ b/cluster/charts/fuel-streams/values-local.yaml @@ -2,8 +2,30 @@ config: createRoles: true healthChecks: true +commonConfigMap: + enabled: true + data: + AWS_S3_BUCKET_NAME: "fuel-streams-staging" + AWS_ENDPOINT_URL: "https://localhost:9000" + AWS_REGION: "us-east-1" + AWS_S3_ENABLED: "false" + USE_METRICS: "false" + NATS_URL: "fuel-streams-nats-core:4222" + NATS_PUBLISHER_URL: "fuel-streams-nats-publisher:4222" + NATS_SYSTEM_USER: "sys" + NATS_SYSTEM_PASS: "sys" + NATS_ADMIN_USER: "admin" + NATS_ADMIN_PASS: "admin" + NATS_PUBLIC_USER: "default_user" + NATS_PUBLIC_PASS: "" + # Reduce storage requirements for local development publisher: + image: + repository: sv-publisher + pullPolicy: IfNotPresent + tag: latest + storage: size: 10Gi storageClass: "standard" # Use default storage class @@ -18,12 +40,13 @@ publisher: cpu: 500m memory: 512Mi - env: - PORT: 8080 - PUBLISHER_MAX_THREADS: "12" - NATS_URL: "fuel-streams-nats-publisher:4222" - consumer: + enabled: true + image: + repository: sv-consumer + pullPolicy: IfNotPresent + tag: latest + config: replicaCount: 1 resources: @@ -34,12 +57,30 @@ consumer: cpu: 500m memory: 512Mi +webserver: + enabled: true + image: + repository: sv-webserver + pullPolicy: IfNotPresent + tag: latest + + service: + enabled: true + port: 9003 + + tls: + enabled: false + # NATS Core configuration for local development nats-core: + enabled: true container: env: GOMEMLIMIT: 1GiB merge: + envFrom: + - configMapRef: + name: fuel-streams-config resources: requests: cpu: 100m @@ -50,7 +91,7 @@ nats-core: config: cluster: - replicas: 1 # Single replica for local development + replicas: 3 jetstream: fileStore: @@ -65,10 +106,14 @@ nats-core: # NATS Publisher configuration for local development nats-publisher: + enabled: true container: env: GOMEMLIMIT: 1GiB merge: + envFrom: + - configMapRef: + name: fuel-streams-config resources: requests: cpu: 100m @@ -88,50 +133,3 @@ nats-publisher: jetstream: max_file_store: << 10GiB >> max_memory_store: << 1GiB >> - -# NATS Client configuration for local development -nats-client: - container: - env: - GOMEMLIMIT: 1GiB - merge: - resources: - requests: - cpu: 100m - memory: 512Mi - limits: - cpu: 500m - memory: 1Gi - - config: - jetstream: - fileStore: - pvc: - size: 10Gi - storageClassName: "standard" - - merge: - jetstream: - max_file_store: << 10GiB >> - max_memory_store: << 1GiB >> - -# Disable external service for local development -natsExternalService: - enabled: false - -# Use simple passwords for local development -natsAccountsSecret: - enabled: true - data: - - name: NATS_SYS_USER - value: sys - - name: NATS_SYS_PASS - value: sys - - name: NATS_ADMIN_USER - value: admin - - name: NATS_ADMIN_PASS - value: admin - - name: NATS_PUBLISHER_USER - value: default_user - - name: NATS_PUBLISHER_PASS - value: "" diff --git a/cluster/charts/fuel-streams/values.yaml b/cluster/charts/fuel-streams/values.yaml index 7741dc0b..3c44142d 100755 --- a/cluster/charts/fuel-streams/values.yaml +++ b/cluster/charts/fuel-streams/values.yaml @@ -69,6 +69,33 @@ startupProbe: failureThreshold: 6 successThreshold: 1 +# ------------------------------------------------------------------------------------------------- +# Global ConfigMap +# ------------------------------------------------------------------------------------------------- + +commonConfigMap: + enabled: true + data: + AWS_S3_BUCKET_NAME: "fuel-streams-staging" + AWS_ENDPOINT_URL: "https://s3.us-east-1.amazonaws.com" + AWS_REGION: "us-east-1" + AWS_S3_ENABLED: "true" + USE_METRICS: "false" + NATS_URL: "fuel-streams-nats-core:4222" + NATS_PUBLISHER_URL: "fuel-streams-nats-publisher:4222" + NATS_SYSTEM_USER: "sys" + NATS_SYSTEM_PASS: "sys" + NATS_ADMIN_USER: "admin" + NATS_ADMIN_PASS: "admin" + NATS_PUBLIC_USER: "default_user" + NATS_PUBLIC_PASS: "" + +# This is a secret that is used for local development +# It is not used in production +localSecrets: + enabled: false + data: {} + # ------------------------------------------------------------------------------------------------- # Monitoring # ------------------------------------------------------------------------------------------------- @@ -83,16 +110,18 @@ monitoring: publisher: enabled: true network: mainnet + port: 8080 image: - repository: ghcr.io/fuellabs/sv-emitter + repository: ghcr.io/fuellabs/sv-publisher pullPolicy: Always - tag: "latest" - extraArgs: [] + tag: latest + args: [] - service: - type: ClusterIP - port: 8080 + # You can override the env variables for the container here + # using a map or an array of key-value pairs + env: [] + envFrom: [] prometheus: enabled: false @@ -140,35 +169,6 @@ publisher: podValue: 4 periodSeconds: 15 - # Additional environment variables with complex structures - # extraEnv: [] - # - name: RELAYER - # valueFrom: - # secretKeyRef: - # name: fuel-streams-publisher - # key: RELAYER - # - name: KEYPAIR - # valueFrom: - # secretKeyRef: - # name: fuel-streams-publisher - # key: KEYPAIR - # - name: NATS_ADMIN_PASS - # valueFrom: - # secretKeyRef: - # name: fuel-streams-publisher - # key: NATS_ADMIN_PASS - # Optional: Bulk environment references - # envFrom: {} - # - configMapRef: - # name: additional-config - # - secretRef: - # name: additional-secrets - - env: - PORT: 8080 - PUBLISHER_MAX_THREADS: "32" - NATS_URL: "fuel-streams-nats-publisher:4222" - # ------------------------------------------------------------------------------------------------- # Consumer configuration # ------------------------------------------------------------------------------------------------- @@ -179,17 +179,13 @@ consumer: image: repository: ghcr.io/fuellabs/sv-consumer pullPolicy: Always - tag: "latest" - args: - - --nats-core-url - - $(NATS_CORE_URL) - - --nats-publisher-url - - $(NATS_PUBLISHER_URL) + tag: latest + args: [] - env: - PORT: 8080 - NATS_CORE_URL: "fuel-streams-nats-core:4222" - NATS_PUBLISHER_URL: "fuel-streams-nats-publisher:4222" + # You can override the env variables for the container here + # using a map or an array of key-value pairs + env: [] + envFrom: [] config: replicaCount: 3 @@ -209,7 +205,7 @@ consumer: resources: {} autoscaling: - enabled: true + enabled: false minReplicas: 1 maxReplicas: 3 targetCPUUtilizationPercentage: 80 @@ -225,28 +221,78 @@ consumer: podValue: 4 periodSeconds: 15 + env: + PORT: 8080 + PUBLISHER_MAX_THREADS: "32" + NATS_URL: "fuel-streams-nats-publisher:4222" + # ------------------------------------------------------------------------------------------------- -# NATS Common Configuration +# Consumer configuration # ------------------------------------------------------------------------------------------------- -natsExternalService: - enabled: true - certificate: +webserver: + enabled: false + network: mainnet + + image: + repository: ghcr.io/fuellabs/sv-webserver + pullPolicy: Always + tag: latest + + service: + enabled: true + type: LoadBalancer + port: 9003 + dns: "stream-staging.fuel.network" + annotations: {} + labels: {} + + tls: + enabled: true issuer: "letsencrypt-prod" duration: "2160h" renewBefore: "360h" annotations: {} labels: {} - service: - dns: "stream-dev.fuel.network" + + # You can override the env variables for the container here + # using a map or an array of key-value pairs + env: [] + envFrom: [] + + config: + replicaCount: 3 labels: {} annotations: {} + podAnnotations: {} + nodeSelector: {} + tolerations: [] + affinity: {} + imagePullSecrets: [] + ports: [] + livenessProbe: {} + readinessProbe: {} + startupProbe: {} + securityContext: {} + containerSecurityContext: {} + resources: {} -# This is just need to run locally, for production you need to -# create a secret with the correct values named fuel-streams-nats-accounts -natsAccountsSecret: - enabled: false - data: [] + autoscaling: + enabled: true + minReplicas: 1 + maxReplicas: 5 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + behavior: + scaleDown: + stabilizationWindowSeconds: 300 + percentValue: 100 + periodSeconds: 15 + scaleUp: + stabilizationWindowSeconds: 0 + percentValue: 100 + podValue: 4 + periodSeconds: 15 # ------------------------------------------------------------------------------------------------- # NATS Core configuration @@ -418,156 +464,3 @@ nats-publisher: merge: $tplYaml: | {{- include "nats-accounts" . | nindent 8 }} - -# ------------------------------------------------------------------------------------------------- -# NATS Client configuration -# ------------------------------------------------------------------------------------------------- - -nats-client: - enabled: true - - natsBox: - enabled: false - - statefulSet: - merge: - spec: - replicas: 3 - - container: - image: - repository: nats - tag: 2.10.24-alpine - env: - GOMEMLIMIT: 7GiB - merge: - resources: - requests: - cpu: 2 - memory: 8Gi - - service: - enabled: true - ports: - nats: - enabled: true - websocket: - enabled: true - leafnodes: - enabled: true - monitor: - enabled: false - mqtt: - enabled: false - - config: - jetstream: - enabled: true - fileStore: - dir: /data - pvc: - enabled: true - size: 100Gi - storageClassName: "gp3-generic" - - leafnodes: - enabled: true - port: 7422 - merge: - remotes: - - urls: ["nats-leaf://admin:admin@fuel-streams-nats-core:7422"] - account: ADMIN - - websocket: - enabled: true - port: 8443 - # This is just enable if the natsExternalService is enabled - # and the DNS is set to the correct value - tls: - enabled: true - dir: /etc/nats-certs/websocket - cert: tls.crt - key: tls.key - secretName: fuel-streams-nats-tls - merge: - no_tls: false - same_origin: false - compression: false - handshake_timeout: "20s" - no_auth_user: default_user - - monitor: - enabled: false - port: 8222 - - merge: - max_payload: << 32MiB >> - jetstream: - domain: CLIENT - sync_interval: << 30s >> - max_outstanding_catchup: << 512MiB >> - max_file_store: << 100GiB >> - max_memory_store: << 7GiB >> - system_account: SYS - $include: auth.conf - - configMap: - merge: - $tplYaml: | - {{- include "nats-accounts" . | nindent 8 }} - -# ------------------------------------------------------------------------------------------------- -# WebServer configuration -# ------------------------------------------------------------------------------------------------- - -webserver: - enabled: false - port: 8443 - - image: - repository: fuel-streams-webserver - pullPolicy: Never - tag: "latest" - - config: - replicaCount: 1 - labels: {} - annotations: {} - podAnnotations: {} - nodeSelector: {} - tolerations: [] - affinity: {} - imagePullSecrets: [] - ports: [] - livenessProbe: {} - readinessProbe: {} - startupProbe: {} - securityContext: {} - containerSecurityContext: {} - resources: - requests: - cpu: 100m - memory: 64Mi - limits: - cpu: 500m - memory: 256Mi - - autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 5 - targetCPUUtilizationPercentage: 80 - targetMemoryUtilizationPercentage: 80 - behavior: - scaleDown: - stabilizationWindowSeconds: 300 - percentValue: 100 - periodSeconds: 15 - scaleUp: - stabilizationWindowSeconds: 0 - percentValue: 100 - podValue: 4 - periodSeconds: 15 - - env: - PORT: 8443 diff --git a/cluster/docker/docker-compose.yml b/cluster/docker/docker-compose.yml index 185bc0c4..34b76756 100644 --- a/cluster/docker/docker-compose.yml +++ b/cluster/docker/docker-compose.yml @@ -1,20 +1,66 @@ services: - nats: + nats-core: + profiles: + - all + - nats image: nats:latest - container_name: nats + container_name: nats-core restart: always ports: - 4222:4222 - - 8222:8222 - - 8443:8443 volumes: - - ./nats.conf:/etc/nats/nats.conf + - ./nats-config/core.conf:/etc/nats/nats.conf + - ./nats-config/accounts.conf:/etc/nats/accounts.conf command: - - -m - - "8222" - - --name=fuel-streams-publisher-server + - --name=fuel-streams-nats-core - --js - --config=/etc/nats/nats.conf - -D env_file: - ./../../.env + + nats-publisher: + profiles: + - all + - nats + image: nats:latest + container_name: nats-publisher + restart: always + ports: + - 4333:4222 + volumes: + - ./nats-config/publisher.conf:/etc/nats/nats.conf + - ./nats-config/accounts.conf:/etc/nats/accounts.conf + command: + - --name=fuel-streams-nats-publisher + - --js + - --config=/etc/nats/nats.conf + - -D + env_file: + - ./../../.env + depends_on: + - nats-core + + localstack: + profiles: + - all + - localstack + image: localstack/localstack:latest + container_name: localstack + restart: always + ports: + - "4566:4566" # LocalStack main gateway port + - "4572:4572" # S3 service port (optional) + environment: + - SERVICES=s3 # Enable just S3 service + - DEBUG=1 + - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} + - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + - DEFAULT_REGION=${AWS_REGION} + - DEFAULT_BUCKETS=${AWS_S3_BUCKET_NAME} + volumes: + - ./localstack-data:/var/lib/localstack + - /var/run/docker.sock:/var/run/docker.sock + - ./init-localstack.sh:/etc/localstack/init/ready.d/init-localstack.sh + env_file: + - ./../../.env diff --git a/cluster/docker/init-localstack.sh b/cluster/docker/init-localstack.sh new file mode 100755 index 00000000..befa0901 --- /dev/null +++ b/cluster/docker/init-localstack.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +echo "Creating S3 bucket in LocalStack..." + +BUCKET_NAME=${AWS_S3_BUCKET_NAME:-fuel-streams-test} +awslocal s3 mb "s3://${BUCKET_NAME}" +echo "Bucket created: ${BUCKET_NAME}" diff --git a/cluster/docker/nats-config/accounts.conf b/cluster/docker/nats-config/accounts.conf new file mode 100644 index 00000000..59a035c8 --- /dev/null +++ b/cluster/docker/nats-config/accounts.conf @@ -0,0 +1,15 @@ +accounts { + SYS: { + users: [{user: $NATS_SYSTEM_USER, password: $NATS_SYSTEM_PASS}] + }, + ADMIN: { + users: [{user: $NATS_ADMIN_USER, password: $NATS_ADMIN_PASS}] + jetstream: enabled + }, + PUBLIC: { + users: [{user: $NATS_PUBLIC_USER, password: $NATS_PUBLIC_PASS}] + jetstream: enabled + } +} + +system_account: SYS diff --git a/cluster/docker/nats-config/client.conf b/cluster/docker/nats-config/client.conf new file mode 100644 index 00000000..08d9e2b3 --- /dev/null +++ b/cluster/docker/nats-config/client.conf @@ -0,0 +1,18 @@ +port: 4222 +server_name: client-server + +jetstream { + store_dir: "./data/store_client" + domain: CLIENT +} + +leafnodes { + remotes: [ + { + urls: ["nats://admin:admin@nats-core:7422"] + account: "ADMIN" + } + ] +} + +include ./accounts.conf diff --git a/cluster/docker/nats-config/core.conf b/cluster/docker/nats-config/core.conf new file mode 100644 index 00000000..2e8f11f4 --- /dev/null +++ b/cluster/docker/nats-config/core.conf @@ -0,0 +1,13 @@ +port: 4222 +server_name: core-server + +jetstream { + store_dir: "./data/core" + domain: CORE +} + +leafnodes { + port: 7422 +} + +include ./accounts.conf diff --git a/cluster/docker/nats-config/publisher.conf b/cluster/docker/nats-config/publisher.conf new file mode 100644 index 00000000..52de2113 --- /dev/null +++ b/cluster/docker/nats-config/publisher.conf @@ -0,0 +1,18 @@ +port: 4222 +server_name: leaf-server + +jetstream { + store_dir: "./data/store_leaf" + domain: LEAF +} + +leafnodes { + remotes: [ + { + urls: ["nats://admin:admin@nats-core:7422"] + account: "ADMIN" + } + ] +} + +include ./accounts.conf diff --git a/cluster/docker/nats.conf b/cluster/docker/nats.conf deleted file mode 100644 index cf5af290..00000000 --- a/cluster/docker/nats.conf +++ /dev/null @@ -1,46 +0,0 @@ -# Core settings -port = 4222 -http_port = 8222 - -# Jetstream settings -jetstream { - max_file_store = 536870912000 # 500GB - max_memory_store = 7516192768 # ~7.1GB -} - -# Max payload setting -max_payload = 8388608 # 8MB - -# Authorization settings -authorization { - timeout = 5 - ADMIN = { - publish = ">" - subscribe = ">" - } - default_permissions = { - subscribe = ">" - publish = { - deny = [ - "*.blocks.>", - "*.transactions.>", - "*.inputs.>", - "*.outputs.>", - "*.receipts.>", - "*.logs.>", - "*.utxos.>", - "$JS.API.STREAM.CREATE.>", - "$JS.API.STREAM.UPDATE.>", - "$JS.API.STREAM.DELETE.>", - "$JS.API.STREAM.PURGE.>", - "$JS.API.STREAM.RESTORE.>", - "$JS.API.STREAM.MSG.DELETE.>", - "$JS.API.CONSUMER.DURABLE.CREATE.>" - ] - } - } - users = [ - { user = admin, password = $NATS_ADMIN_PASS, permissions = $ADMIN } - { user = default_user } - ] -} diff --git a/cluster/docker/sv-consumer.Dockerfile b/cluster/docker/sv-consumer.Dockerfile index 533e8459..5b20d1cd 100644 --- a/cluster/docker/sv-consumer.Dockerfile +++ b/cluster/docker/sv-consumer.Dockerfile @@ -72,4 +72,5 @@ COPY --from=builder /root/sv-consumer.d . EXPOSE ${PORT} -ENTRYPOINT ["./sv-consumer"] +WORKDIR /usr/src +CMD ["./sv-consumer"] diff --git a/cluster/docker/sv-publisher.Dockerfile b/cluster/docker/sv-publisher.Dockerfile new file mode 100644 index 00000000..de9b042f --- /dev/null +++ b/cluster/docker/sv-publisher.Dockerfile @@ -0,0 +1,81 @@ +# Stage 1: Build +FROM --platform=$BUILDPLATFORM tonistiigi/xx AS xx +FROM --platform=$BUILDPLATFORM rust:1.81.0 AS chef + +# Add package name as build argument +ARG TARGETPLATFORM + +RUN cargo install cargo-chef && rustup target add wasm32-unknown-unknown +WORKDIR /build/ + +COPY --from=xx / / + +# hadolint ignore=DL3008 +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + lld \ + clang \ + libclang-dev \ + && xx-apt-get update \ + && xx-apt-get install -y libc6-dev g++ binutils \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +FROM chef AS planner +ENV CARGO_NET_GIT_FETCH_WITH_CLI=true +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +FROM chef AS builder +ARG PACKAGE_NAME +ARG DEBUG_SYMBOLS=false +ENV CARGO_NET_GIT_FETCH_WITH_CLI=true +ENV CARGO_PROFILE_RELEASE_DEBUG=$DEBUG_SYMBOLS +COPY --from=planner /build/recipe.json recipe.json +RUN echo $CARGO_PROFILE_RELEASE_DEBUG +# Build our project dependencies, not our application! +RUN \ + --mount=type=cache,target=/usr/local/cargo/registry/index \ + --mount=type=cache,target=/usr/local/cargo/registry/cache \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + --mount=type=cache,target=/build/target \ + xx-cargo chef cook --release --no-default-features -p sv-publisher --recipe-path recipe.json +# Up to this point, if our dependency tree stays the same, +# all layers should be cached. +COPY . . +# build application +RUN \ + --mount=type=cache,target=/usr/local/cargo/registry/index \ + --mount=type=cache,target=/usr/local/cargo/registry/cache \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + --mount=type=cache,target=/build/target \ + xx-cargo build --release --no-default-features -p sv-publisher \ + && xx-verify ./target/$(xx-cargo --print-target-triple)/release/sv-publisher \ + && cp ./target/$(xx-cargo --print-target-triple)/release/sv-publisher /root/sv-publisher \ + && cp ./target/$(xx-cargo --print-target-triple)/release/sv-publisher.d /root/sv-publisher.d + +# Stage 2: Run +FROM ubuntu:22.04 AS run + +ARG PORT=4000 +ARG P2P_PORT=30333 +ARG DB_PATH=/mnt/db +ENV PORT="${PORT}" + +WORKDIR /usr/src + +RUN apt-get update -y \ + && apt-get install -y --no-install-recommends ca-certificates curl \ + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /root/sv-publisher . +COPY --from=builder /root/sv-publisher.d . + +COPY /cluster/chain-config ./chain-config +EXPOSE ${PORT} +EXPOSE ${P2P_PORT} + +WORKDIR /usr/src +CMD ["./sv-publisher", "--port", "${PORT}", "--peering-port", "${P2P_PORT}", "--db-path", "${DB_PATH}"] diff --git a/cluster/docker/sv-webserver.Dockerfile b/cluster/docker/sv-webserver.Dockerfile new file mode 100644 index 00000000..75140bd4 --- /dev/null +++ b/cluster/docker/sv-webserver.Dockerfile @@ -0,0 +1,75 @@ +# Stage 1: Build +FROM --platform=$BUILDPLATFORM tonistiigi/xx AS xx +FROM --platform=$BUILDPLATFORM rust:1.81.0 AS chef + +ARG TARGETPLATFORM +RUN cargo install cargo-chef && rustup target add wasm32-unknown-unknown +WORKDIR /build/ + +COPY --from=xx / / + +# hadolint ignore=DL3008 +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + lld \ + clang \ + libclang-dev \ + && xx-apt-get update \ + && xx-apt-get install -y libc6-dev g++ binutils \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + + +FROM chef AS planner +ENV CARGO_NET_GIT_FETCH_WITH_CLI=true +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + + +FROM chef AS builder +ARG DEBUG_SYMBOLS=false +ENV CARGO_NET_GIT_FETCH_WITH_CLI=true +ENV CARGO_PROFILE_RELEASE_DEBUG=$DEBUG_SYMBOLS +COPY --from=planner /build/recipe.json recipe.json +RUN echo $CARGO_PROFILE_RELEASE_DEBUG +# Build our project dependencies, not our application! +RUN \ + --mount=type=cache,target=/usr/local/cargo/registry/index \ + --mount=type=cache,target=/usr/local/cargo/registry/cache \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + --mount=type=cache,target=/build/target \ + xx-cargo chef cook --release --no-default-features -p sv-webserver --recipe-path recipe.json +# Up to this point, if our dependency tree stays the same, +# all layers should be cached. +COPY . . +# build application +RUN \ + --mount=type=cache,target=/usr/local/cargo/registry/index \ + --mount=type=cache,target=/usr/local/cargo/registry/cache \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + --mount=type=cache,target=/build/target \ + xx-cargo build --release --no-default-features -p sv-webserver \ + && xx-verify ./target/$(xx-cargo --print-target-triple)/release/sv-webserver \ + && cp ./target/$(xx-cargo --print-target-triple)/release/sv-webserver /root/sv-webserver \ + && cp ./target/$(xx-cargo --print-target-triple)/release/sv-webserver.d /root/sv-webserver.d + +# Stage 2: Run +FROM ubuntu:22.04 AS run + +ARG PORT=9003 +ENV PORT=$PORT + +WORKDIR /usr/src + +RUN apt-get update -y \ + && apt-get install -y --no-install-recommends ca-certificates curl \ + # Clean up + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /root/sv-webserver . +COPY --from=builder /root/sv-webserver.d . + +EXPOSE ${PORT} +CMD ["./sv-webserver"] diff --git a/cluster/scripts/build_docker.sh b/cluster/scripts/build_docker.sh index ef274b27..0a5eebb7 100755 --- a/cluster/scripts/build_docker.sh +++ b/cluster/scripts/build_docker.sh @@ -11,8 +11,7 @@ Usage: $(basename "$0") [OPTIONS] Build a Docker image using specified parameters. Options: - --image-name Name for the Docker image (default: fuel-streams-publisher) - --dockerfile Path to Dockerfile (default: cluster/docker/fuel-core.Dockerfile) + --dockerfile Path to Dockerfile (default: cluster/docker/sv-publisher.Dockerfile) --build-args Additional Docker build arguments (optional) -h, --help Show this help message @@ -21,8 +20,8 @@ Environment variables: DOCKER_HOST Docker daemon socket (optional) Examples: - $(basename "$0") --image-name my-image --dockerfile ./Dockerfile - $(basename "$0") --image-name my-image --dockerfile ./Dockerfile --build-args "--build-arg KEY=VALUE" + $(basename "$0") --dockerfile ./Dockerfile + $(basename "$0") --dockerfile ./Dockerfile --build-args "--build-arg KEY=VALUE" EOF exit 1 } @@ -33,18 +32,14 @@ if [[ $# -eq 0 ]] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then fi # Default values -IMAGE_NAME="fuel-streams-publisher" -DOCKERFILE="cluster/docker/fuel-core.Dockerfile" +DOCKERFILE="cluster/docker/sv-publisher.Dockerfile" +IMAGE_NAME=${EXPECTED_IMAGE:-"sv-publisher"} +TAG=${EXPECTED_TAG:-"latest"} BUILD_ARGS="" -TAG=${TAG:-"latest"} # From environment variable with default # Parse named arguments while [[ $# -gt 0 ]]; do case $1 in - --image-name) - IMAGE_NAME="$2" - shift 2 - ;; --dockerfile) DOCKERFILE="$2" shift 2 diff --git a/cluster/scripts/build_streamer.sh b/cluster/scripts/build_streamer.sh new file mode 100755 index 00000000..90d90edd --- /dev/null +++ b/cluster/scripts/build_streamer.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Use environment variables provided by Tilt if available +IMAGE_NAME=${EXPECTED_IMAGE:-"sv-webserver"} +TAG=${EXPECTED_TAG:-"latest"} +DOCKERFILE="docker/sv-webserver.Dockerfile" + +# Ensure we're using minikube's docker daemon if not already set +if [ -z "${DOCKER_HOST:-}" ]; then + eval $(minikube docker-env) +fi + +# Build the docker image +docker build -t ${IMAGE_NAME}:${TAG} -f ${DOCKERFILE} . diff --git a/cluster/scripts/gen_env_secret.sh b/cluster/scripts/gen_env_secret.sh index 2d4a1e6c..46a589f8 100755 --- a/cluster/scripts/gen_env_secret.sh +++ b/cluster/scripts/gen_env_secret.sh @@ -4,15 +4,12 @@ source .env # Generate the YAML configuration -cat << EOF > cluster/charts/fuel-streams/values-publisher-secrets.yaml -publisher: - extraEnv: - - name: RELAYER - value: "${RELAYER:-}" - - name: KEYPAIR - value: "${KEYPAIR:-}" - - name: NATS_ADMIN_PASS - value: "${NATS_ADMIN_PASS:-}" +cat << EOF > cluster/charts/fuel-streams/values-secrets.yaml +localSecrets: + enabled: true + data: + RELAYER: "${RELAYER:-}" + KEYPAIR: "${KEYPAIR:-}" EOF -echo "Generated values-publisher-secrets.yaml with environment variables" +echo "Generated values-secrets.yaml with environment variables" diff --git a/cluster/scripts/setup_k8s.sh b/cluster/scripts/setup_k8s.sh deleted file mode 100755 index 791c8595..00000000 --- a/cluster/scripts/setup_k8s.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash - -[[ $DEBUG = true ]] && set -x -set -euo pipefail - -# Parse command line arguments -NAMESPACE="${1:-fuel-streams}" # Use first argument, default to "fuel-streams" if not provided - -# Configure namespace and context -echo -e "\n\033[1;33mConfiguring ${NAMESPACE} namespace and context:\033[0m" - -# Check if namespace exists -if kubectl get namespace ${NAMESPACE} &> /dev/null; then - echo "Namespace ${NAMESPACE} already exists" -else - echo "Creating namespace ${NAMESPACE}..." - kubectl create namespace ${NAMESPACE} -fi - -# Switch to minikube context -if ! kubectl config current-context | grep -q "minikube"; then - echo "Switching to minikube context..." - kubectl config use-context minikube -else - echo "Already in minikube context" -fi - -# Set namespace for current context -CURRENT_NAMESPACE=$(kubectl config view --minify --output 'jsonpath={..namespace}') -if [ "$CURRENT_NAMESPACE" != "${NAMESPACE}" ]; then - echo "Setting current namespace to ${NAMESPACE}..." - kubectl config set-context --current --cluster=minikube --namespace=${NAMESPACE} -else - echo "Context namespace is already set to ${NAMESPACE}" -fi - -# Verify context configuration -echo -e "\n\033[1;33mVerifying cluster context:\033[0m" -kubectl config get-contexts diff --git a/cluster/scripts/setup_minikube.sh b/cluster/scripts/setup_minikube.sh index 55c63555..cf4009cd 100755 --- a/cluster/scripts/setup_minikube.sh +++ b/cluster/scripts/setup_minikube.sh @@ -28,6 +28,56 @@ minikube start \ --memory="$MEMORY" \ --cpus 8 +minikube addons enable metrics-server +minikube addons enable registry + +# Remove existing registry proxy container if running +if docker ps -a | grep -q "minikube-registry-proxy"; then + echo "Removing existing registry proxy container..." + docker rm -f minikube-registry-proxy +fi + +# Forward minikube registry to localhost +docker run --rm -d \ + --network=host \ + --name minikube-registry-proxy \ + alpine ash -c "apk add socat && socat TCP-LISTEN:5000,reuseaddr,fork TCP:$(minikube ip):5000" + # Display minikube status echo -e "\n\033[1;33mMinikube Status:\033[0m" minikube status + +# Parse command line arguments +NAMESPACE="${1:-fuel-streams}" # Use first argument, default to "fuel-streams" if not provided + +# Configure namespace and context +echo -e "\n\033[1;33mConfiguring ${NAMESPACE} namespace and context:\033[0m" + +# Check if namespace exists +if kubectl get namespace ${NAMESPACE} &> /dev/null; then + echo "Namespace ${NAMESPACE} already exists" +else + echo "Creating namespace ${NAMESPACE}..." + kubectl create namespace ${NAMESPACE} +fi + +# Switch to minikube context +if ! kubectl config current-context | grep -q "minikube"; then + echo "Switching to minikube context..." + kubectl config use-context minikube +else + echo "Already in minikube context" +fi + +# Set namespace for current context +CURRENT_NAMESPACE=$(kubectl config view --minify --output 'jsonpath={..namespace}') +if [ "$CURRENT_NAMESPACE" != "${NAMESPACE}" ]; then + echo "Setting current namespace to ${NAMESPACE}..." + kubectl config set-context --current --cluster=minikube --namespace=${NAMESPACE} +else + echo "Context namespace is already set to ${NAMESPACE}" +fi + +# Verify context configuration +echo -e "\n\033[1;33mVerifying cluster context:\033[0m" +kubectl config get-contexts diff --git a/crates/fuel-data-parser/src/lib.rs b/crates/fuel-data-parser/src/lib.rs index 1ac52c3b..78bc227f 100644 --- a/crates/fuel-data-parser/src/lib.rs +++ b/crates/fuel-data-parser/src/lib.rs @@ -25,7 +25,13 @@ pub enum SerializationType { /// Traits required for a data type to be parseable pub trait DataParseable: - serde::Serialize + serde::de::DeserializeOwned + Clone + Send + Sync + Debug + serde::Serialize + + serde::de::DeserializeOwned + + Clone + + Send + + Sync + + Debug + + std::marker::Sized { } @@ -196,6 +202,13 @@ impl DataParser { }) } + pub fn encode_json( + &self, + data: &T, + ) -> Result, Error> { + self.serialize_json(data) + } + /// Serializes the provided data according to the selected `SerializationType`. /// /// # Arguments @@ -220,6 +233,14 @@ impl DataParser { } } + fn serialize_json( + &self, + raw_data: &T, + ) -> Result, Error> { + serde_json::to_vec(&raw_data) + .map_err(|e| Error::Serde(SerdeError::Json(e))) + } + /// Decodes the provided data by deserializing and optionally decompressing it. /// /// # Arguments @@ -259,10 +280,17 @@ impl DataParser { Some(strategy) => strategy.decompress(data).await?, None => data.to_vec(), }; - let decoded_data = self.deserialize(&data[..]).await?; + let decoded_data = self.deserialize(&data[..])?; Ok(decoded_data) } + pub fn decode_json( + &self, + data: &[u8], + ) -> Result { + self.deserialize_json(data) + } + /// Deserializes the provided data according to the selected `SerializationType`. /// /// # Arguments @@ -273,7 +301,7 @@ impl DataParser { /// /// A `Result` containing either the deserialized data structure, /// or an `Error` if deserialization fails. - pub async fn deserialize<'a, T: serde::Deserialize<'a>>( + pub fn deserialize<'a, T: serde::Deserialize<'a>>( &self, raw_data: &'a [u8], ) -> Result { @@ -282,10 +310,17 @@ impl DataParser { .map_err(|e| Error::Serde(SerdeError::Bincode(*e))), SerializationType::Postcard => postcard::from_bytes(raw_data) .map_err(|e| Error::Serde(SerdeError::Postcard(e))), - SerializationType::Json => serde_json::from_slice(raw_data) - .map_err(|e| Error::Serde(SerdeError::Json(e))), + SerializationType::Json => self.deserialize_json(raw_data), } } + + pub fn deserialize_json<'a, T: serde::Deserialize<'a>>( + &self, + raw_data: &'a [u8], + ) -> Result { + serde_json::from_slice(raw_data) + .map_err(|e| Error::Serde(SerdeError::Json(e))) + } } #[cfg(test)] diff --git a/crates/fuel-streams-core/Cargo.toml b/crates/fuel-streams-core/Cargo.toml index 69a28916..2087376f 100644 --- a/crates/fuel-streams-core/Cargo.toml +++ b/crates/fuel-streams-core/Cargo.toml @@ -15,9 +15,7 @@ anyhow = { workspace = true } async-nats = { workspace = true } async-trait = { workspace = true } chrono = { workspace = true } -clap = { workspace = true } displaydoc = { workspace = true } -dotenvy = { workspace = true } fuel-core = { workspace = true, default-features = false, features = [ "p2p", "relayer", @@ -36,10 +34,11 @@ fuel-core-storage = { workspace = true } fuel-core-types = { workspace = true, default-features = false, features = ["std", "serde"] } fuel-data-parser = { workspace = true } fuel-streams-macros = { workspace = true } +fuel-streams-nats = { workspace = true } +fuel-streams-storage = { workspace = true } futures = { workspace = true } hex = { workspace = true } pretty_assertions = { workspace = true, optional = true } -rand = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/fuel-streams-core/README.md b/crates/fuel-streams-core/README.md index 0304865e..69684062 100644 --- a/crates/fuel-streams-core/README.md +++ b/crates/fuel-streams-core/README.md @@ -54,25 +54,28 @@ fuel-streams-core = "*" Here's a simple example to get you started with Fuel Streams Core: ```rust,no_run +use std::sync::Arc; use fuel_streams_core::prelude::*; use futures::StreamExt; #[tokio::main] async fn main() -> BoxedResult<()> { // Connect to NATS server - let opts = NatsClientOpts::new(Some(FuelNetwork::Local)); - let client = NatsClient::connect(&opts).await?; + let nats_opts = NatsClientOpts::admin_opts(); + let nats_client = NatsClient::connect(&nats_opts).await?; + + let s3_opts = S3ClientOpts::new(S3Env::Local, S3Role::Admin); + let s3_client = Arc::new(S3Client::new(&s3_opts).await?); // Create a stream for blocks - let stream = Stream::::new(&client).await; + let stream = Stream::::new(&nats_client, &s3_client).await; // Subscribe to the stream let wildcard = BlocksSubject::wildcard(None, None); // blocks.*.* - let mut subscription = stream.subscribe(&wildcard).await?; + let mut subscription = stream.subscribe(None).await?; // Process incoming blocks - while let Some(bytes) = subscription.next().await { - let block = Block::decode_raw(bytes.unwrap()).await; + while let Some(block) = subscription.next().await { dbg!(block); } diff --git a/crates/fuel-streams-core/src/fuel_core_like.rs b/crates/fuel-streams-core/src/fuel_core_like.rs index 93a395a7..85d2458f 100644 --- a/crates/fuel-streams-core/src/fuel_core_like.rs +++ b/crates/fuel-streams-core/src/fuel_core_like.rs @@ -17,7 +17,7 @@ use tokio::{sync::broadcast::Receiver, time::sleep}; use crate::types::*; /// Interface for `fuel-core` related logic. -/// This was introduced to simplify mocking and testing the `fuel-streams-publisher` crate. +/// This was introduced to simplify mocking and testing the `sv-publisher` crate. #[async_trait::async_trait] pub trait FuelCoreLike: Sync + Send { async fn start(&self) -> anyhow::Result<()>; diff --git a/crates/fuel-streams-core/src/inputs/mod.rs b/crates/fuel-streams-core/src/inputs/mod.rs index c18fb4e1..4e2e7db9 100644 --- a/crates/fuel-streams-core/src/inputs/mod.rs +++ b/crates/fuel-streams-core/src/inputs/mod.rs @@ -13,5 +13,6 @@ impl Streamable for Input { InputsCoinSubject::WILDCARD, InputsContractSubject::WILDCARD, InputsMessageSubject::WILDCARD, + InputsByIdSubject::WILDCARD, ]; } diff --git a/crates/fuel-streams-core/src/lib.rs b/crates/fuel-streams-core/src/lib.rs index bea378e9..219576fc 100644 --- a/crates/fuel-streams-core/src/lib.rs +++ b/crates/fuel-streams-core/src/lib.rs @@ -8,9 +8,15 @@ pub mod receipts; pub mod transactions; pub mod utxos; -pub mod nats; -pub mod stream; +pub mod nats { + pub use fuel_streams_nats::*; +} +pub mod s3 { + pub use fuel_streams_storage::s3::*; +} + +pub mod stream; pub mod subjects; pub mod fuel_core_like; @@ -27,6 +33,7 @@ pub mod prelude { pub use crate::{ fuel_core_like::*, nats::*, + s3::*, stream::*, subjects::*, types::*, diff --git a/crates/fuel-streams-core/src/nats/mod.rs b/crates/fuel-streams-core/src/nats/mod.rs deleted file mode 100644 index c15e193b..00000000 --- a/crates/fuel-streams-core/src/nats/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -/// Houses shared APIs for interacting with NATS for fuel-streams-publisher and fuel-streams crates -/// As much as possible, the public interface/APIS should be agnostic of NATS. These can then be extended -/// in the fuel-streams-publisher and fuel-streams crates to provide a more opinionated API towards -/// their specific use-cases. -mod error; -mod nats_client; -mod nats_client_opts; -mod nats_namespace; - -pub mod types; - -pub use error::*; -pub use nats_client::*; -pub use nats_client_opts::*; -pub use nats_namespace::*; diff --git a/crates/fuel-streams-core/src/nats/nats_client_opts.rs b/crates/fuel-streams-core/src/nats/nats_client_opts.rs deleted file mode 100644 index ffef16fa..00000000 --- a/crates/fuel-streams-core/src/nats/nats_client_opts.rs +++ /dev/null @@ -1,205 +0,0 @@ -use std::time::Duration; - -use async_nats::ConnectOptions; - -use super::NatsNamespace; - -#[derive(Debug, Clone, Default)] -pub enum NatsUserRole { - Admin, - #[default] - Default, -} - -#[derive(Debug, Copy, Clone, Default, clap::ValueEnum)] -pub enum FuelNetwork { - Local, - #[default] - Testnet, - Mainnet, -} - -impl FuelNetwork { - pub fn to_url(&self) -> String { - match self { - FuelNetwork::Local => "nats://localhost:4222".to_string(), - FuelNetwork::Testnet => { - "nats://stream-testnet.fuel.network:4222".to_string() - } - FuelNetwork::Mainnet => { - "nats://stream.fuel.network:4222".to_string() - } - } - } -} - -/// Represents options for configuring a NATS client. -/// -/// # Examples -/// -/// Creating a new `NatsClientOpts` instance: -/// -/// ``` -/// use fuel_streams_core::nats::{NatsClientOpts, FuelNetwork}; -/// -/// let opts = NatsClientOpts::new(Some(FuelNetwork::Local)); -/// ``` -/// -/// Creating a public `NatsClientOpts`: -/// -/// ``` -/// use fuel_streams_core::nats::{NatsClientOpts, FuelNetwork}; -/// -/// let opts = NatsClientOpts::default_opts(Some(FuelNetwork::Local)); -/// ``` -/// -/// Modifying `NatsClientOpts`: -/// -/// ``` -/// use fuel_streams_core::nats::{NatsClientOpts, NatsUserRole, FuelNetwork}; -/// -/// let opts = NatsClientOpts::new(Some(FuelNetwork::Local)) -/// .with_role(NatsUserRole::Admin) -/// .with_timeout(10); -/// ``` -#[derive(Debug, Clone)] -pub struct NatsClientOpts { - /// The URL of the NATS server to connect to. - url: String, - /// The role of the user connecting to the NATS server (Admin or Public). - pub(crate) role: NatsUserRole, - /// The namespace used as a prefix for NATS streams, consumers, and subject names. - pub(crate) namespace: NatsNamespace, - /// The timeout in seconds for NATS operations. - pub(crate) timeout_secs: u64, - /// The domain to use for the NATS client. - pub(crate) domain: Option, - /// The user to use for the NATS client. - pub(crate) user: Option, - /// The password to use for the NATS client. - pub(crate) password: Option, -} - -impl NatsClientOpts { - pub fn new(network: Option) -> Self { - Self { - url: network.unwrap_or_default().to_url(), - role: NatsUserRole::default(), - namespace: NatsNamespace::default(), - timeout_secs: 5, - domain: None, - user: None, - password: None, - } - } - - pub fn default_opts(network: Option) -> Self { - Self::new(network).with_role(NatsUserRole::Default) - } - - #[cfg(any(test, feature = "test-helpers"))] - pub fn admin_opts(network: Option) -> Self { - Self::new(network).with_role(NatsUserRole::Admin) - } - - pub fn with_role(self, role: NatsUserRole) -> Self { - Self { role, ..self } - } - pub fn get_url(&self) -> &str { - &self.url - } - - pub fn with_fuel_network(self, network: FuelNetwork) -> Self { - Self { - url: network.to_url(), - ..self - } - } - - pub fn with_domain(self, domain: String) -> Self { - Self { - domain: Some(domain), - ..self - } - } - - pub fn with_user(self, user: String) -> Self { - Self { - user: Some(user), - ..self - } - } - - pub fn with_password(self, password: String) -> Self { - Self { - password: Some(password), - ..self - } - } - - pub fn with_custom_url(self, url: String) -> Self { - Self { url, ..self } - } - - #[cfg(any(test, feature = "test-helpers"))] - pub fn with_rdn_namespace(self) -> Self { - let namespace = format!(r"namespace-{}", Self::random_int()); - self.with_namespace(&namespace) - } - - #[cfg(any(test, feature = "test-helpers"))] - pub fn with_namespace(self, namespace: &str) -> Self { - let namespace = NatsNamespace::Custom(namespace.to_string()); - Self { namespace, ..self } - } - - pub fn with_timeout(self, secs: u64) -> Self { - Self { - timeout_secs: secs, - ..self - } - } - - pub(super) fn connect_opts(&self) -> ConnectOptions { - let (user, pass) = match self.role { - NatsUserRole::Admin => ( - Some("admin".to_string()), - Some( - dotenvy::var("NATS_ADMIN_PASS") - .expect("`NATS_ADMIN_PASS` env must be set"), - ), - ), - NatsUserRole::Default => { - (Some("default_user".to_string()), Some("".to_string())) - } - }; - - let (user, pass) = match (self.user.clone(), self.password.clone()) { - (Some(user), Some(pass)) => (Some(user), Some(pass)), - _ => (user, pass), - }; - - match (user, pass) { - (Some(user), Some(pass)) => { - ConnectOptions::with_user_and_password(user, pass) - .connection_timeout(Duration::from_secs(self.timeout_secs)) - .max_reconnects(1) - .name(Self::conn_id()) - } - _ => ConnectOptions::new() - .connection_timeout(Duration::from_secs(self.timeout_secs)) - .max_reconnects(1) - .name(Self::conn_id()), - } - } - - // This will be useful for debugging and monitoring connections - fn conn_id() -> String { - format!(r"connection-{}", Self::random_int()) - } - - fn random_int() -> u32 { - use rand::Rng; - rand::thread_rng().gen() - } -} diff --git a/crates/fuel-streams-core/src/receipts/mod.rs b/crates/fuel-streams-core/src/receipts/mod.rs index 1da02fd6..c2ba4f4a 100644 --- a/crates/fuel-streams-core/src/receipts/mod.rs +++ b/crates/fuel-streams-core/src/receipts/mod.rs @@ -9,6 +9,20 @@ use crate::{StreamEncoder, Streamable}; impl StreamEncoder for Receipt {} impl Streamable for Receipt { const NAME: &'static str = "receipts"; - const WILDCARD_LIST: &'static [&'static str] = - &[ReceiptsCallSubject::WILDCARD, ReceiptsByIdSubject::WILDCARD]; + const WILDCARD_LIST: &'static [&'static str] = &[ + ReceiptsCallSubject::WILDCARD, + ReceiptsByIdSubject::WILDCARD, + ReceiptsBurnSubject::WILDCARD, + ReceiptsLogSubject::WILDCARD, + ReceiptsMintSubject::WILDCARD, + ReceiptsPanicSubject::WILDCARD, + ReceiptsReturnSubject::WILDCARD, + ReceiptsRevertSubject::WILDCARD, + ReceiptsLogDataSubject::WILDCARD, + ReceiptsTransferSubject::WILDCARD, + ReceiptsMessageOutSubject::WILDCARD, + ReceiptsReturnDataSubject::WILDCARD, + ReceiptsTransferOutSubject::WILDCARD, + ReceiptsScriptResultSubject::WILDCARD, + ]; } diff --git a/crates/fuel-streams-core/src/stream/error.rs b/crates/fuel-streams-core/src/stream/error.rs index e3e6f31e..168155da 100644 --- a/crates/fuel-streams-core/src/stream/error.rs +++ b/crates/fuel-streams-core/src/stream/error.rs @@ -12,34 +12,37 @@ use thiserror::Error; #[derive(Error, DisplayDoc, Debug)] pub enum StreamError { - /// Failed to publish to stream: {subject_name} + /// Failed to publish to stream: {subject_name}, error: {source} PublishFailed { subject_name: String, #[source] source: error::Error, }, - /// Failed to retrieve last published message from stream + /// Failed to publish to S3: {0} + S3PublishError(#[from] fuel_streams_storage::s3::S3ClientError), + + /// Failed to retrieve last published message from stream: {0} GetLastPublishedFailed(#[from] error::Error), - /// Failed to create Key-Value Store + /// Failed to create Key-Value Store: {0} StoreCreation(#[from] error::Error), - /// Failed to publish item to Key-Value Store + /// Failed to publish item to Key-Value Store: {0} StorePublish(#[from] PutError), - /// Failed to subscribe to subject in Key-Value Store + /// Failed to subscribe to subject in Key-Value Store: {0} StoreSubscribe(#[from] error::Error), - /// Failed to publish item to stream + /// Failed to publish item to stream: {0} StreamPublish(#[from] CreateError), - /// Failed to create stream + /// Failed to create stream: {0} StreamCreation(#[from] error::Error), - /// Failed to create consumer for stream + /// Failed to create consumer for stream: {0} ConsumerCreate(#[from] error::Error), - /// Failed to consume messages from stream + /// Failed to consume messages from stream: {0} ConsumerMessages(#[from] error::Error), } diff --git a/crates/fuel-streams-core/src/stream/fuel_streams.rs b/crates/fuel-streams-core/src/stream/fuel_streams.rs index c7e8ba08..5e8781ba 100644 --- a/crates/fuel-streams-core/src/stream/fuel_streams.rs +++ b/crates/fuel-streams-core/src/stream/fuel_streams.rs @@ -1,6 +1,10 @@ use std::sync::Arc; -use async_nats::{jetstream::stream::State as StreamState, RequestErrorKind}; +use async_nats::{ + jetstream::{context::CreateStreamErrorKind, stream::State as StreamState}, + RequestErrorKind, +}; +use futures::stream::BoxStream; use crate::prelude::*; @@ -15,28 +19,93 @@ pub struct FuelStreams { pub logs: Stream, } +pub struct FuelStreamsUtils; +impl FuelStreamsUtils { + pub fn is_within_subject_names(subject_name: &str) -> bool { + let subject_names = Self::subjects_names(); + subject_names.contains(&subject_name) + } + + pub fn subjects_names() -> &'static [&'static str] { + &[ + Transaction::NAME, + Block::NAME, + Input::NAME, + Receipt::NAME, + Utxo::NAME, + Log::NAME, + ] + } + + pub fn wildcards() -> Vec<&'static str> { + let nested_wildcards = [ + Transaction::WILDCARD_LIST, + Block::WILDCARD_LIST, + Input::WILDCARD_LIST, + Receipt::WILDCARD_LIST, + Utxo::WILDCARD_LIST, + Log::WILDCARD_LIST, + ]; + nested_wildcards + .into_iter() + .flatten() + .copied() + .collect::>() + } +} + impl FuelStreams { - pub async fn new(nats_client: &NatsClient) -> Self { + pub async fn new( + nats_client: &NatsClient, + s3_client: &Arc, + ) -> Self { Self { - transactions: Stream::::new(nats_client).await, - blocks: Stream::::new(nats_client).await, - inputs: Stream::::new(nats_client).await, - outputs: Stream::::new(nats_client).await, - receipts: Stream::::new(nats_client).await, - utxos: Stream::::new(nats_client).await, - logs: Stream::::new(nats_client).await, + transactions: Stream::::new(nats_client, s3_client) + .await, + blocks: Stream::::new(nats_client, s3_client).await, + inputs: Stream::::new(nats_client, s3_client).await, + outputs: Stream::::new(nats_client, s3_client).await, + receipts: Stream::::new(nats_client, s3_client).await, + utxos: Stream::::new(nats_client, s3_client).await, + logs: Stream::::new(nats_client, s3_client).await, } } pub async fn setup_all( core_client: &NatsClient, publisher_client: &NatsClient, + s3_client: &Arc, ) -> (Self, Self) { - let core_stream = Self::new(core_client).await; - let publisher_stream = Self::new(publisher_client).await; + let core_stream = Self::new(core_client, s3_client).await; + let publisher_stream = Self::new(publisher_client, s3_client).await; (core_stream, publisher_stream) } + pub async fn subscribe( + &self, + sub_subject: &str, + subscription_config: Option, + ) -> Result>, StreamError> { + match sub_subject { + Transaction::NAME => { + self.transactions.subscribe_raw(subscription_config).await + } + Block::NAME => self.blocks.subscribe_raw(subscription_config).await, + Input::NAME => self.inputs.subscribe_raw(subscription_config).await, + Output::NAME => { + self.outputs.subscribe_raw(subscription_config).await + } + Receipt::NAME => { + self.receipts.subscribe_raw(subscription_config).await + } + Utxo::NAME => self.utxos.subscribe_raw(subscription_config).await, + Log::NAME => self.logs.subscribe_raw(subscription_config).await, + _ => Err(StreamError::StreamCreation( + CreateStreamErrorKind::InvalidStreamName.into(), + )), + } + } + pub fn arc(self) -> Arc { Arc::new(self) } @@ -54,33 +123,6 @@ pub trait FuelStreamsExt: Sync + Send { async fn get_last_published_block(&self) -> anyhow::Result>; - fn subjects_wildcards(&self) -> &[&'static str] { - &[ - TransactionsSubject::WILDCARD, - BlocksSubject::WILDCARD, - InputsByIdSubject::WILDCARD, - InputsCoinSubject::WILDCARD, - InputsMessageSubject::WILDCARD, - InputsContractSubject::WILDCARD, - ReceiptsLogSubject::WILDCARD, - ReceiptsBurnSubject::WILDCARD, - ReceiptsByIdSubject::WILDCARD, - ReceiptsCallSubject::WILDCARD, - ReceiptsMintSubject::WILDCARD, - ReceiptsPanicSubject::WILDCARD, - ReceiptsReturnSubject::WILDCARD, - ReceiptsRevertSubject::WILDCARD, - ReceiptsLogDataSubject::WILDCARD, - ReceiptsTransferSubject::WILDCARD, - ReceiptsMessageOutSubject::WILDCARD, - ReceiptsReturnDataSubject::WILDCARD, - ReceiptsTransferOutSubject::WILDCARD, - ReceiptsScriptResultSubject::WILDCARD, - UtxosSubject::WILDCARD, - LogsSubject::WILDCARD, - ] - } - async fn get_consumers_and_state( &self, ) -> Result, StreamState)>, RequestErrorKind>; diff --git a/crates/fuel-streams-core/src/stream/stream_encoding.rs b/crates/fuel-streams-core/src/stream/stream_encoding.rs index e2f29215..31c7e0b2 100644 --- a/crates/fuel-streams-core/src/stream/stream_encoding.rs +++ b/crates/fuel-streams-core/src/stream/stream_encoding.rs @@ -40,23 +40,40 @@ where #[async_trait] pub trait StreamEncoder: DataParseable { - async fn encode(&self, subject: &str) -> Vec { + // TODO: Should we remove the `StreamData` type and encode/decode the raw data only + fn encode(&self, subject: &str) -> Vec { let data = StreamData::new(subject, self.clone()); Self::data_parser() - .encode(&data) - .await + .encode_json(&data) .expect("Streamable must encode correctly") } - async fn decode(encoded: Vec) -> Self { - Self::decode_raw(encoded).await.payload + fn encode_self(&self) -> Vec { + Self::data_parser() + .encode_json(self) + .expect("Streamable must encode correctly") + } + + fn decode(encoded: Vec) -> Result { + Ok(Self::decode_raw(encoded)?.payload) + } + + fn decode_or_panic(encoded: Vec) -> Self { + Self::decode_raw(encoded) + .expect("Streamable must decode correctly") + .payload + } + + fn decode_raw( + encoded: Vec, + ) -> Result, fuel_data_parser::Error> { + Self::data_parser().decode_json(&encoded) } - async fn decode_raw(encoded: Vec) -> StreamData { + fn decode_raw_or_panic(encoded: Vec) -> StreamData { Self::data_parser() - .decode(&encoded) - .await + .decode_json(&encoded) .expect("Streamable must decode correctly") } diff --git a/crates/fuel-streams-core/src/stream/stream_impl.rs b/crates/fuel-streams-core/src/stream/stream_impl.rs index e2d690ed..bc5690cb 100644 --- a/crates/fuel-streams-core/src/stream/stream_impl.rs +++ b/crates/fuel-streams-core/src/stream/stream_impl.rs @@ -1,5 +1,3 @@ -#[cfg(any(test, feature = "test-helpers"))] -use std::pin::Pin; use std::{fmt::Debug, sync::Arc}; use async_nats::{ @@ -12,16 +10,12 @@ use async_nats::{ }; use async_trait::async_trait; use fuel_streams_macros::subject::IntoSubject; -use futures::{StreamExt, TryStreamExt}; +use futures::{stream::BoxStream, StreamExt, TryStreamExt}; use tokio::sync::OnceCell; -use super::{error::StreamError, stream_encoding::StreamEncoder}; -use crate::{nats::types::*, prelude::NatsClient}; +use crate::prelude::*; -pub const FUEL_BLOCK_TIME_SECS: u64 = 1; -pub const MAX_RETENTION_BLOCKS: u64 = 100; - -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct PublishPacket { pub subject: Arc, pub payload: Arc, @@ -34,6 +28,11 @@ impl PublishPacket { subject, } } + + pub fn get_s3_path(&self) -> String { + let subject = self.subject.parse(); + subject.replace('.', "/").to_string() + } } /// Trait for types that can be streamed. @@ -58,7 +57,7 @@ impl PublishPacket { /// } /// ``` #[async_trait] -pub trait Streamable: StreamEncoder { +pub trait Streamable: StreamEncoder + std::marker::Sized { const NAME: &'static str; const WILDCARD_LIST: &'static [&'static str]; @@ -72,6 +71,7 @@ pub trait Streamable: StreamEncoder { /// # Examples /// /// ```no_run +/// use std::sync::Arc; /// use fuel_streams_core::prelude::*; /// use fuel_streams_macros::subject::IntoSubject; /// use futures::StreamExt; @@ -89,17 +89,16 @@ pub trait Streamable: StreamEncoder { /// const WILDCARD_LIST: &'static [&'static str] = &["*"]; /// } /// -/// async fn example(client: &NatsClient) { -/// let stream = Stream::::new(client).await; +/// async fn example(nats_client: &NatsClient, s3_client: &Arc) { +/// let stream = Stream::::new(nats_client, s3_client).await; /// /// // Publish -/// let subject = BlocksSubject::new().with_height(Some(23.into())); -/// let payload = MyStreamable { data: "foo".into() }; -/// stream.publish(&subject, &payload).await.unwrap(); +/// let subject = BlocksSubject::new().with_height(Some(23.into())).arc(); +/// let packet = MyStreamable { data: "foo".into() }.to_packet(subject); +/// stream.publish(&packet).await.unwrap(); /// /// // Subscribe -/// let wildcard = BlocksSubject::WILDCARD; -/// let mut subscription = stream.subscribe(wildcard).await.unwrap(); +/// let mut subscription = stream.subscribe(None).await.unwrap(); /// while let Some(message) = subscription.next().await { /// // Process message /// } @@ -110,7 +109,8 @@ pub trait Streamable: StreamEncoder { /// TODO: Rename as FuelStream? #[derive(Debug, Clone)] pub struct Stream { - store: kv::Store, + store: Arc, + s3_client: Arc, _marker: std::marker::PhantomData, } @@ -118,17 +118,24 @@ impl Stream { #[allow(clippy::declare_interior_mutable_const)] const INSTANCE: OnceCell = OnceCell::const_new(); - pub async fn get_or_init(client: &NatsClient) -> Self { + pub async fn get_or_init( + nats_client: &NatsClient, + s3_client: &Arc, + ) -> Self { let cell = Self::INSTANCE; - cell.get_or_init(|| async { Self::new(client).await.to_owned() }) - .await - .to_owned() + cell.get_or_init(|| async { + Self::new(nats_client, s3_client).await.to_owned() + }) + .await + .to_owned() } - pub async fn new(client: &NatsClient) -> Self { - let namespace = &client.namespace; + pub async fn new( + nats_client: &NatsClient, + s3_client: &Arc, + ) -> Self { + let namespace = &nats_client.namespace; let bucket_name = namespace.stream_name(S::NAME); - let config = kv::Config { bucket: bucket_name.to_owned(), storage: stream::StorageType::File, @@ -137,40 +144,46 @@ impl Stream { ..Default::default() }; - let store = client + let store = nats_client .get_or_create_kv_store(config) .await .expect("Streams must be created"); Self { - store, + store: Arc::new(store), + s3_client: Arc::clone(s3_client), _marker: std::marker::PhantomData, } } pub async fn publish( &self, - subject: &dyn IntoSubject, - payload: &S, + packet: &PublishPacket, ) -> Result { - let subject_name = &subject.parse(); - self.publish_raw(subject_name, payload).await + let payload = &packet.payload; + let s3_path = packet.get_s3_path(); + let subject_name = &packet.subject.parse(); + + self.s3_client + .put_object(&s3_path, payload.encode(subject_name)) + .await?; + + self.publish_s3_path_to_nats(subject_name, &s3_path).await } - /// Publish with subject name with no static guarantees of the subject - pub async fn publish_raw( + async fn publish_s3_path_to_nats( &self, subject_name: &str, - payload: &S, + s3_path: &str, ) -> Result { - let data = payload.encode(subject_name).await; + tracing::debug!("S3 path published: {:?}", s3_path); + let data = s3_path.to_string().into_bytes(); let data_size = data.len(); let result = self.store.create(subject_name, data.into()).await; match result { Ok(_) => Ok(data_size), Err(e) if e.kind() == CreateErrorKind::AlreadyExists => { - // This is a workaround to avoid publish two times the same message Ok(data_size) } Err(e) => Err(StreamError::PublishFailed { @@ -199,17 +212,93 @@ impl Stream { self.store.stream_name.as_str() } - // TODO: This should probably be `subscribe_raw` since it returns pure bytes + // Less performant due to our hybrid use of NATS and S3 + pub async fn subscribe_raw( + &self, + subscription_config: Option, + ) -> Result>, StreamError> { + let mut config = PullConsumerConfig { + filter_subjects: vec![S::WILDCARD_LIST[0].to_string()], + deliver_policy: NatsDeliverPolicy::All, + ack_policy: AckPolicy::None, + ..Default::default() + }; + + if let Some(subscription_config) = subscription_config { + config.filter_subjects = subscription_config.filter_subjects; + config.deliver_policy = subscription_config.deliver_policy; + } + + let config = self.prefix_filter_subjects(config); + let consumer = self.store.stream.create_consumer(config).await?; + + Ok(consumer + .messages() + .await? + .then(|message| { + let s3_client = Arc::clone(&self.s3_client); + + async move { + let nats_payload = message + .expect("Message must be valid") + .payload + .to_vec(); + + // TODO: Bubble up the error to users + let s3_path = String::from_utf8(nats_payload) + .expect("Must be S3 path"); + + s3_client + .get_object(&s3_path) + .await + .expect("S3 object must exist") + } + }) + .boxed()) + } + pub async fn subscribe( &self, - // TODO: Allow encapsulating Subject to return wildcard token type - wildcard: &str, - ) -> Result>>, StreamError> { - Ok(self.store.watch(&wildcard).await.map(|stream| { - stream.map(|entry| { - entry.ok().map(|entry_item| entry_item.value.to_vec()) + subscription_config: Option, + ) -> Result, StreamError> { + let mut config = PullConsumerConfig { + filter_subjects: vec![S::WILDCARD_LIST[0].to_string()], + deliver_policy: NatsDeliverPolicy::All, + ack_policy: AckPolicy::None, + ..Default::default() + }; + + if let Some(subscription_config) = subscription_config { + config.filter_subjects = subscription_config.filter_subjects; + config.deliver_policy = subscription_config.deliver_policy; + } + + let config = self.prefix_filter_subjects(config); + let consumer = self.store.stream.create_consumer(config).await?; + + Ok(consumer + .messages() + .await? + .map(|item| { + String::from_utf8( + item.expect("Message must be valid").payload.to_vec(), + ) + .expect("Must be S3 path") + }) + .then(|s3_path| { + let s3_client = Arc::clone(&self.s3_client); + + async move { + // TODO: Bubble up the error? + S::decode_or_panic( + s3_client + .get_object(&s3_path) + .await + .expect("Could not get S3 object"), + ) + } }) - })?) + .boxed()) } #[cfg(feature = "test-helpers")] @@ -217,49 +306,34 @@ impl Stream { pub async fn catchup( &self, number_of_messages: usize, - ) -> Result< - Pin> + Send>>, - StreamError, - > { + ) -> Result>, StreamError> { let config = PullConsumerConfig { filter_subjects: self.all_filter_subjects(), - deliver_policy: DeliverPolicy::All, + deliver_policy: NatsDeliverPolicy::All, ack_policy: AckPolicy::None, ..Default::default() }; let config = self.prefix_filter_subjects(config); let consumer = self.store.stream.create_consumer(config).await?; - let stream = consumer.messages().await?.take(number_of_messages).then( - |message| async { + let stream = consumer + .messages() + .await? + .take(number_of_messages) + .then(|message| async { if let Ok(message) = message { - Some(S::decode(message.payload.to_vec()).await) + Some( + self.get_payload_from_s3(message.payload.to_vec()) + .await + .unwrap(), + ) } else { None } - }, - ); - - // Use Box::pin to pin the stream on the heap - Ok(Box::pin(stream)) - } - - // TODO: Make this interface more Stream-like and Nats agnostic - // TODO: This should probably be removed in favor of `subscribe` - pub async fn subscribe_consumer( - &self, - config: SubscribeConsumerConfig, - ) -> Result { - let config = PullConsumerConfig { - filter_subjects: config.filter_subjects, - deliver_policy: config.deliver_policy, - ack_policy: AckPolicy::None, - ..Default::default() - }; + }) + .boxed(); - let config = self.prefix_filter_subjects(config); - let consumer = self.store.stream.create_consumer(config).await?; - Ok(consumer.messages().await?) + Ok(stream) } // TODO: Make this interface more Stream-like and Nats agnostic @@ -298,10 +372,9 @@ impl Stream { .await; match message { - Ok(message) => { - let payload = S::decode(message.payload.to_vec()).await; - Ok(Some(payload)) - } + Ok(message) => Ok(Some( + self.get_payload_from_s3(message.payload.to_vec()).await?, + )), Err(error) => match &error.kind() { LastRawMessageErrorKind::NoMessageFound => Ok(None), _ => Err(error.into()), @@ -309,6 +382,20 @@ impl Stream { } } + async fn get_payload_from_s3( + &self, + nats_payload: Vec, + ) -> Result { + let s3_path = String::from_utf8(nats_payload).expect("Must be S3 path"); + let s3_object = self + .s3_client + .get_object(&s3_path) + .await + .expect("S3 object must exist"); + + Ok(S::decode_or_panic(s3_object)) + } + #[cfg(any(test, feature = "test-helpers"))] pub async fn assert_has_stream( &self, @@ -354,16 +441,16 @@ impl Stream { /// # Examples /// /// ``` -/// use fuel_streams_core::stream::SubscribeConsumerConfig; -/// use async_nats::jetstream::consumer::DeliverPolicy; +/// use fuel_streams_core::stream::SubscriptionConfig; +/// use async_nats::jetstream::consumer::DeliverPolicy as NatsDeliverPolicy; /// -/// let config = SubscribeConsumerConfig { +/// let config = SubscriptionConfig { /// filter_subjects: vec!["example.*".to_string()], -/// deliver_policy: DeliverPolicy::All, +/// deliver_policy: NatsDeliverPolicy::All, /// }; /// ``` #[derive(Debug, Clone, Default)] -pub struct SubscribeConsumerConfig { +pub struct SubscriptionConfig { pub filter_subjects: Vec, - pub deliver_policy: DeliverPolicy, + pub deliver_policy: NatsDeliverPolicy, } diff --git a/crates/fuel-streams-core/src/types.rs b/crates/fuel-streams-core/src/types.rs index 4eb3a874..9d1c880f 100644 --- a/crates/fuel-streams-core/src/types.rs +++ b/crates/fuel-streams-core/src/types.rs @@ -1,5 +1,3 @@ -use std::error::Error; - pub use crate::{ blocks::types::*, fuel_core_types::*, @@ -16,5 +14,5 @@ pub use crate::{ // ------------------------------------------------------------------------ // General // ------------------------------------------------------------------------ -pub type BoxedError = Box; +pub type BoxedError = Box; pub type BoxedResult = Result; diff --git a/crates/fuel-streams-executors/src/blocks.rs b/crates/fuel-streams-executors/src/blocks.rs index b24ce9d6..5a145e2d 100644 --- a/crates/fuel-streams-executors/src/blocks.rs +++ b/crates/fuel-streams-executors/src/blocks.rs @@ -1,7 +1,7 @@ -use std::{sync::Arc, time::Instant}; +use std::sync::Arc; use fuel_streams_core::prelude::*; -use futures::{future::try_join_all, stream::FuturesUnordered}; +use futures::stream::FuturesUnordered; use tokio::task::JoinHandle; use crate::*; @@ -23,13 +23,11 @@ impl Executor { self.publish(&packet) } - pub async fn process_all( + pub fn process_all( payload: Arc, fuel_streams: &Arc, - ) -> Result<(), ExecutorError> { - let start_time = Instant::now(); - let metadata = Arc::new(payload.metadata().clone()); - + semaphore: &Arc, + ) -> FuturesUnordered>> { let block_stream = fuel_streams.blocks().arc(); let tx_stream = fuel_streams.transactions().arc(); let input_stream = fuel_streams.inputs().arc(); @@ -38,13 +36,15 @@ impl Executor { let log_stream = fuel_streams.logs().arc(); let utxo_stream = fuel_streams.utxos().arc(); - let block_executor = Executor::new(&payload, &block_stream); - let tx_executor = Executor::new(&payload, &tx_stream); - let input_executor = Executor::new(&payload, &input_stream); - let output_executor = Executor::new(&payload, &output_stream); - let receipt_executor = Executor::new(&payload, &receipt_stream); - let log_executor = Executor::new(&payload, &log_stream); - let utxo_executor = Executor::new(&payload, &utxo_stream); + let block_executor = Executor::new(&payload, &block_stream, semaphore); + let tx_executor = Executor::new(&payload, &tx_stream, semaphore); + let input_executor = Executor::new(&payload, &input_stream, semaphore); + let output_executor = + Executor::new(&payload, &output_stream, semaphore); + let receipt_executor = + Executor::new(&payload, &receipt_stream, semaphore); + let log_executor = Executor::new(&payload, &log_stream, semaphore); + let utxo_executor = Executor::new(&payload, &utxo_stream, semaphore); let transactions = payload.transactions.to_owned(); let tx_tasks = @@ -63,19 +63,8 @@ impl Executor { }); let block_task = block_executor.process(); - let all_tasks = std::iter::once(block_task) + std::iter::once(block_task) .chain(tx_tasks.into_iter().flatten()) - .collect::>(); - - try_join_all(all_tasks).await?; - - let elapsed = start_time.elapsed(); - let height = metadata.block_height.clone(); - tracing::info!( - "Published streams for BlockHeight: {height} in {:?}", - elapsed - ); - - Ok(()) + .collect::>() } } diff --git a/crates/fuel-streams-executors/src/lib.rs b/crates/fuel-streams-executors/src/lib.rs index 1fa507f9..a227758d 100644 --- a/crates/fuel-streams-executors/src/lib.rs +++ b/crates/fuel-streams-executors/src/lib.rs @@ -20,12 +20,10 @@ use tokio::task::JoinHandle; pub static PUBLISHER_MAX_THREADS: LazyLock = LazyLock::new(|| { let available_cpus = num_cpus::get(); - let default_threads = (available_cpus / 3).max(1); // Use 1/3 of CPUs, minimum 1 - env::var("PUBLISHER_MAX_THREADS") .ok() .and_then(|val| val.parse().ok()) - .unwrap_or(default_threads) + .unwrap_or(available_cpus) }); pub fn sha256(bytes: &[u8]) -> Bytes32 { @@ -194,14 +192,20 @@ impl TryFrom for Publish { pub struct Executor { pub stream: Arc>, payload: Arc, + semaphore: Arc, __marker: PhantomData, } impl Executor { - pub fn new(payload: &Arc, stream: &Arc>) -> Self { + pub fn new( + payload: &Arc, + stream: &Arc>, + semaphore: &Arc, + ) -> Self { Self { payload: payload.to_owned(), stream: stream.to_owned(), + semaphore: semaphore.to_owned(), __marker: PhantomData, } } @@ -209,27 +213,28 @@ impl Executor { fn publish( &self, packet: &PublishPacket, - // _opts: &Arc, ) -> JoinHandle> { - let payload = Arc::clone(&packet.payload); - let subject = Arc::clone(&packet.subject); let wildcard = packet.subject.parse(); let stream = Arc::clone(&self.stream); - let max_threads = *PUBLISHER_MAX_THREADS; - let semaphore = Arc::new(tokio::sync::Semaphore::new(max_threads)); - let permit = Arc::clone(&semaphore); + let permit = Arc::clone(&self.semaphore); // TODO: add telemetry back again - tokio::spawn(async move { - let _permit = permit.acquire().await?; - match stream.publish(&*subject, &payload).await { - Ok(_) => { - tracing::info!( - "Successfully published for stream: {wildcard}" - ); - Ok(()) + let packet = packet.clone(); + tokio::spawn({ + async move { + let _permit = permit.acquire().await?; + match stream.publish(&packet).await { + Ok(_) => { + tracing::debug!( + "Successfully published for stream: {wildcard}" + ); + Ok(()) + } + Err(e) => { + tracing::error!("Failed to publish for stream: {wildcard}, error: {e}"); + Err(ExecutorError::PublishFailed(e.to_string())) + } } - Err(e) => Err(ExecutorError::PublishFailed(e.to_string())), } }) } diff --git a/crates/fuel-streams-executors/src/transactions.rs b/crates/fuel-streams-executors/src/transactions.rs index 76c42f28..396364ca 100644 --- a/crates/fuel-streams-executors/src/transactions.rs +++ b/crates/fuel-streams-executors/src/transactions.rs @@ -21,65 +21,47 @@ fn packets_from_tx( (index, tx): (usize, &Transaction), block_height: &BlockHeight, ) -> Vec> { + let estimated_capacity = + 1 + tx.inputs.len() + tx.outputs.len() + tx.receipts.len(); let tx_id = tx.id.clone(); let tx_status = tx.status.clone(); let receipts = tx.receipts.clone(); - let main_subject = TransactionsSubject { - block_height: Some(block_height.to_owned()), - index: Some(index), - tx_id: Some(tx_id.to_owned()), - status: Some(tx_status.to_owned()), - kind: Some(tx.kind.to_owned()), - } - .arc(); - let mut packets = vec![tx.to_packet(main_subject)]; - packets.extend( - identifiers(tx, &tx.kind, &tx_id, index as u8) - .into_par_iter() - .map(|identifier| identifier.into()) - .map(|subject: TransactionsByIdSubject| subject.arc()) - .map(|subject| tx.to_packet(subject)) - .collect::>(), + // Main subject + let mut packets = Vec::with_capacity(estimated_capacity); + packets.push( + tx.to_packet( + TransactionsSubject { + block_height: Some(block_height.to_owned()), + index: Some(index), + tx_id: Some(tx_id.to_owned()), + status: Some(tx_status), + kind: Some(tx.kind.to_owned()), + } + .arc(), + ), ); - let packets_from_inputs: Vec> = tx - .inputs - .par_iter() - .flat_map(|input| { - inputs::identifiers(input, &tx_id, index as u8) - .into_par_iter() - .map(|identifier| identifier.into()) - .map(|subject: TransactionsByIdSubject| subject.arc()) - .map(|subject| tx.to_packet(subject)) - }) - .collect(); - packets.extend(packets_from_inputs); - - let packets_from_outputs: Vec> = tx - .outputs - .par_iter() - .flat_map(|output| { - outputs::identifiers(output, tx, &tx_id, index as u8) - .into_par_iter() - .map(|identifier| identifier.into()) - .map(|subject: TransactionsByIdSubject| subject.arc()) - .map(|subject| tx.to_packet(subject)) - }) - .collect(); - packets.extend(packets_from_outputs); + let index_u8 = index as u8; + let mut additional_packets: Vec> = + rayon::iter::once(&tx.kind) + .flat_map(|kind| identifiers(tx, kind, &tx_id, index_u8)) + .chain( + tx.inputs.par_iter().flat_map(|input| { + inputs::identifiers(input, &tx_id, index_u8) + }), + ) + .chain(tx.outputs.par_iter().flat_map(|output| { + outputs::identifiers(output, tx, &tx_id, index_u8) + })) + .chain(receipts.par_iter().flat_map(|receipt| { + receipts::identifiers(receipt, &tx_id, index_u8) + })) + .map(|identifier| TransactionsByIdSubject::from(identifier).arc()) + .map(|subject| tx.to_packet(subject)) + .collect(); - let packets_from_receipts: Vec> = receipts - .par_iter() - .flat_map(|receipt| { - receipts::identifiers(receipt, &tx_id, index as u8) - .into_par_iter() - .map(|identifier| identifier.into()) - .map(|subject: TransactionsByIdSubject| subject.arc()) - .map(|subject| tx.to_packet(subject)) - }) - .collect(); - packets.extend(packets_from_receipts); + packets.append(&mut additional_packets); packets } diff --git a/crates/fuel-streams-nats/Cargo.toml b/crates/fuel-streams-nats/Cargo.toml new file mode 100644 index 00000000..60254899 --- /dev/null +++ b/crates/fuel-streams-nats/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "fuel-streams-nats" +description = "Strategies and adapters for storing fuel streams in NATS" +authors = { workspace = true } +keywords = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } +rust-version = { workspace = true } + +[dependencies] +async-nats = { workspace = true } +displaydoc = { workspace = true } +dotenvy = { workspace = true } +rand = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } + +[dev-dependencies] +pretty_assertions = { workspace = true } +serde_json = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread", "macros", "test-util"] } + +[features] +default = [] +test-helpers = [] +bench-helpers = [] diff --git a/crates/fuel-streams-core/src/nats/error.rs b/crates/fuel-streams-nats/src/error.rs similarity index 100% rename from crates/fuel-streams-core/src/nats/error.rs rename to crates/fuel-streams-nats/src/error.rs diff --git a/crates/fuel-streams-nats/src/lib.rs b/crates/fuel-streams-nats/src/lib.rs new file mode 100644 index 00000000..762ae550 --- /dev/null +++ b/crates/fuel-streams-nats/src/lib.rs @@ -0,0 +1,15 @@ +/// Houses shared APIs for interacting with NATS for sv-publisher and fuel-streams crates +/// As much as possible, the public interface/APIS should be agnostic of NATS. These can then be extended +/// in the sv-publisher and fuel-streams crates to provide a more opinionated API towards +/// their specific use-cases. +pub mod error; +pub mod nats_client; +pub mod nats_client_opts; +pub mod nats_namespace; +pub mod types; + +pub use error::*; +pub use nats_client::*; +pub use nats_client_opts::*; +pub use nats_namespace::*; +pub use types::*; diff --git a/crates/fuel-streams-core/src/nats/nats_client.rs b/crates/fuel-streams-nats/src/nats_client.rs similarity index 88% rename from crates/fuel-streams-core/src/nats/nats_client.rs rename to crates/fuel-streams-nats/src/nats_client.rs index 294e9a1d..fd3474fa 100644 --- a/crates/fuel-streams-core/src/nats/nats_client.rs +++ b/crates/fuel-streams-nats/src/nats_client.rs @@ -14,10 +14,10 @@ use super::{types::*, NatsClientOpts, NatsError, NatsNamespace}; /// Creating a new `NatsClient`: /// /// ```no_run -/// use fuel_streams_core::prelude::*; +/// use fuel_streams_nats::*; /// -/// async fn example() -> BoxedResult<()> { -/// let opts = NatsClientOpts::new(Some(FuelNetwork::Local)); +/// async fn example() -> Result<(), Box> { +/// let opts = NatsClientOpts::public_opts(); /// let client = NatsClient::connect(&opts).await?; /// Ok(()) /// } @@ -26,11 +26,11 @@ use super::{types::*, NatsClientOpts, NatsError, NatsNamespace}; /// Creating a key-value store: /// /// ```no_run -/// use fuel_streams_core::prelude::*; +/// use fuel_streams_nats::*; /// use async_nats::jetstream::kv; /// -/// async fn example() -> BoxedResult<()> { -/// let opts = NatsClientOpts::new(Some(FuelNetwork::Local)); +/// async fn example() -> Result<(), Box> { +/// let opts = NatsClientOpts::public_opts(); /// let client = NatsClient::connect(&opts).await?; /// let kv_config = kv::Config { /// bucket: "my-bucket".into(), @@ -55,7 +55,7 @@ pub struct NatsClient { impl NatsClient { pub async fn connect(opts: &NatsClientOpts) -> Result { - let url = opts.get_url(); + let url = &opts.get_url(); let namespace = opts.namespace.clone(); let nats_client = opts.connect_opts().connect(url).await.map_err(|e| { diff --git a/crates/fuel-streams-nats/src/nats_client_opts.rs b/crates/fuel-streams-nats/src/nats_client_opts.rs new file mode 100644 index 00000000..f6aaa0a3 --- /dev/null +++ b/crates/fuel-streams-nats/src/nats_client_opts.rs @@ -0,0 +1,227 @@ +use std::time::Duration; + +use async_nats::ConnectOptions; + +use super::NatsNamespace; + +#[derive(Debug, Clone, Eq, PartialEq, Default)] +pub enum NatsAuth { + Admin, + System, + #[default] + Public, + Custom(String, String), +} + +impl NatsAuth { + fn credentials_from_env(&self) -> (String, String) { + match self { + NatsAuth::Admin => ( + dotenvy::var("NATS_ADMIN_USER") + .expect("NATS_ADMIN_USER must be set"), + dotenvy::var("NATS_ADMIN_PASS") + .expect("NATS_ADMIN_PASS must be set"), + ), + NatsAuth::System => ( + dotenvy::var("NATS_SYSTEM_USER") + .expect("NATS_SYSTEM_USER must be set"), + dotenvy::var("NATS_SYSTEM_PASS") + .expect("NATS_SYSTEM_PASS must be set"), + ), + NatsAuth::Public => ("default_user".to_string(), "".to_string()), + NatsAuth::Custom(user, pass) => { + (user.to_string(), pass.to_string()) + } + } + } +} + +/// Configuration options for connecting to NATS +/// +/// # Examples +/// +/// ```no_run +/// use fuel_streams_nats::*; +/// +/// // Create with URL +/// let opts = NatsClientOpts::new("nats://localhost:4222".to_string(), Some(NatsAuth::Admin)); +/// +/// // Create with admin credentials from environment +/// let opts = NatsClientOpts::admin_opts(); +/// +/// // Create with system credentials from environment +/// let opts = NatsClientOpts::system_opts(); +/// +/// // Create with public credentials +/// let opts = NatsClientOpts::public_opts(); +/// ``` +/// +/// Customize options: +/// +/// ```no_run +/// use fuel_streams_nats::*; +/// +/// let opts = NatsClientOpts::new("nats://localhost:4222".to_string(), Some(NatsAuth::Admin)) +/// .with_domain("mydomain") +/// .with_user("myuser") +/// .with_password("mypass") +/// .with_timeout(10); +/// ``` +#[derive(Debug, Clone)] +pub struct NatsClientOpts { + /// The URL of the NATS server. + pub(crate) url: String, + /// The namespace used as a prefix for NATS streams, consumers, and subject names. + pub(crate) namespace: NatsNamespace, + /// The timeout in seconds for NATS operations. + pub(crate) timeout_secs: u64, + /// The domain to use for the NATS client. + pub(crate) domain: Option, + /// The user to use for the NATS client. + pub(crate) user: Option, + /// The password to use for the NATS client. + pub(crate) password: Option, +} + +impl NatsClientOpts { + pub fn new(url: String, auth: Option) -> Self { + let (user, pass) = auth.unwrap_or_default().credentials_from_env(); + Self { + url, + namespace: NatsNamespace::default(), + timeout_secs: 5, + domain: None, + user: Some(user), + password: Some(pass), + } + } + + pub fn from_env(auth: Option) -> Self { + let url = dotenvy::var("NATS_URL").expect("NATS_URL must be set"); + Self::new(url, auth) + } + pub fn admin_opts() -> Self { + Self::from_env(Some(NatsAuth::Admin)) + } + pub fn system_opts() -> Self { + Self::from_env(Some(NatsAuth::System)) + } + pub fn public_opts() -> Self { + Self::from_env(Some(NatsAuth::Public)) + } + + pub fn get_url(&self) -> String { + self.url.clone() + } + + pub fn with_url>(self, url: S) -> Self { + Self { + url: url.into(), + ..self + } + } + + pub fn with_domain>(self, domain: S) -> Self { + Self { + domain: Some(domain.into()), + ..self + } + } + + pub fn with_user>(self, user: S) -> Self { + Self { + user: Some(user.into()), + ..self + } + } + + pub fn with_password>(self, password: S) -> Self { + Self { + password: Some(password.into()), + ..self + } + } + + #[cfg(any(test, feature = "test-helpers"))] + pub fn with_rdn_namespace(self) -> Self { + let namespace = format!(r"namespace-{}", Self::random_int()); + self.with_namespace(&namespace) + } + + #[cfg(any(test, feature = "test-helpers"))] + pub fn with_namespace(self, namespace: &str) -> Self { + let namespace = NatsNamespace::Custom(namespace.to_string()); + Self { namespace, ..self } + } + + pub fn with_timeout(self, secs: u64) -> Self { + Self { + timeout_secs: secs, + ..self + } + } + + pub(super) fn connect_opts(&self) -> ConnectOptions { + let opts = match (self.user.clone(), self.password.clone()) { + (Some(user), Some(pass)) => { + ConnectOptions::with_user_and_password(user, pass) + } + _ => ConnectOptions::new(), + }; + + opts.connection_timeout(Duration::from_secs(self.timeout_secs)) + .max_reconnects(1) + .name(Self::conn_id()) + } + + // This will be useful for debugging and monitoring connections + fn conn_id() -> String { + format!(r"connection-{}", Self::random_int()) + } + + fn random_int() -> u32 { + use rand::Rng; + rand::thread_rng().gen() + } +} + +#[cfg(test)] +mod tests { + use std::env; + + use super::*; + + #[test] + fn test_role_credentials() { + // Setup + env::set_var("NATS_ADMIN_USER", "admin"); + env::set_var("NATS_ADMIN_PASS", "admin_pass"); + + // Test Admin role credentials + let (user, pass) = NatsAuth::Admin.credentials_from_env(); + assert_eq!(user, "admin"); + assert_eq!(pass, "admin_pass"); + + // Cleanup + env::remove_var("NATS_ADMIN_USER"); + env::remove_var("NATS_ADMIN_PASS"); + } + + #[test] + fn test_from_env_with_role() { + // Setup + env::set_var("NATS_URL", "nats://localhost:4222"); + env::set_var("NATS_ADMIN_USER", "admin"); + env::set_var("NATS_ADMIN_PASS", "admin_pass"); + + // Test Admin role + let opts = NatsClientOpts::from_env(Some(NatsAuth::Admin)); + assert_eq!(opts.user, Some("admin".to_string())); + assert_eq!(opts.password, Some("admin_pass".to_string())); + + // Cleanup + env::remove_var("NATS_URL"); + env::remove_var("NATS_ADMIN_USER"); + env::remove_var("NATS_ADMIN_PASS"); + } +} diff --git a/crates/fuel-streams-core/src/nats/nats_namespace.rs b/crates/fuel-streams-nats/src/nats_namespace.rs similarity index 92% rename from crates/fuel-streams-core/src/nats/nats_namespace.rs rename to crates/fuel-streams-nats/src/nats_namespace.rs index 8aa7da89..947e8760 100644 --- a/crates/fuel-streams-core/src/nats/nats_namespace.rs +++ b/crates/fuel-streams-nats/src/nats_namespace.rs @@ -7,7 +7,7 @@ static DEFAULT_NAMESPACE: &str = "fuel"; /// # Examples /// /// ``` -/// use fuel_streams_core::nats::NatsNamespace; +/// use fuel_streams_nats::NatsNamespace; /// /// let default_namespace = NatsNamespace::default(); /// assert_eq!(default_namespace.to_string(), "fuel"); @@ -44,7 +44,7 @@ impl NatsNamespace { /// # Examples /// /// ``` - /// use fuel_streams_core::nats::NatsNamespace; + /// use fuel_streams_nats::NatsNamespace; /// /// let namespace = NatsNamespace::default(); /// assert_eq!(namespace.subject_name("test"), "fuel.test"); @@ -61,7 +61,7 @@ impl NatsNamespace { /// # Examples /// /// ``` - /// use fuel_streams_core::nats::NatsNamespace; + /// use fuel_streams_nats::NatsNamespace; /// /// let namespace = NatsNamespace::default(); /// assert_eq!(namespace.stream_name("test"), "fuel_test"); diff --git a/crates/fuel-streams-core/src/nats/types.rs b/crates/fuel-streams-nats/src/types.rs similarity index 92% rename from crates/fuel-streams-core/src/nats/types.rs rename to crates/fuel-streams-nats/src/types.rs index eb121ffe..70ac9d02 100644 --- a/crates/fuel-streams-core/src/nats/types.rs +++ b/crates/fuel-streams-nats/src/types.rs @@ -10,7 +10,7 @@ pub use async_nats::{ AckPolicy, Config as ConsumerConfig, Consumer as NatsConsumer, - DeliverPolicy, + DeliverPolicy as NatsDeliverPolicy, }, kv::Config as KvStoreConfig, stream::Config as NatsStreamConfig, diff --git a/crates/fuel-streams-publisher/Cargo.lock b/crates/fuel-streams-publisher/Cargo.lock deleted file mode 100644 index 046abc4a..00000000 --- a/crates/fuel-streams-publisher/Cargo.lock +++ /dev/null @@ -1,7553 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstream" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" - -[[package]] -name = "anstyle-parse" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" -dependencies = [ - "anstyle", - "windows-sys 0.52.0", -] - -[[package]] -name = "anyhow" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" - -[[package]] -name = "arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" - -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "asn1-rs" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror", - "time", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "synstructure", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "asn1_der" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" - -[[package]] -name = "async-graphql" -version = "4.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9ed522678d412d77effe47b3c82314ac36952a35e6e852093dd48287c421f80" -dependencies = [ - "async-graphql-derive", - "async-graphql-parser", - "async-graphql-value", - "async-stream", - "async-trait", - "base64 0.13.1", - "bytes", - "fnv", - "futures-util", - "http", - "indexmap 1.9.3", - "mime", - "multer", - "num-traits", - "once_cell", - "pin-project-lite", - "regex", - "serde", - "serde_json", - "serde_urlencoded", - "static_assertions", - "tempfile", - "thiserror", - "tracing", - "tracing-futures", -] - -[[package]] -name = "async-graphql-derive" -version = "4.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c121a894495d7d3fc3d4e15e0a9843e422e4d1d9e3c514d8062a1c94b35b005d" -dependencies = [ - "Inflector", - "async-graphql-parser", - "darling 0.14.4", - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", - "thiserror", -] - -[[package]] -name = "async-graphql-parser" -version = "4.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b6c386f398145c6180206c1869c2279f5a3d45db5be4e0266148c6ac5c6ad68" -dependencies = [ - "async-graphql-value", - "pest", - "serde", - "serde_json", -] - -[[package]] -name = "async-graphql-value" -version = "4.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a941b499fead4a3fb5392cabf42446566d18c86313f69f2deab69560394d65f" -dependencies = [ - "bytes", - "indexmap 1.9.3", - "serde", - "serde_json", -] - -[[package]] -name = "async-io" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" -dependencies = [ - "async-lock", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "async-lock" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-nats" -version = "0.35.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8df97cb8fc4a884af29ab383e9292ea0939cfcdd7d2a17179086dc6c427e7f" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures", - "memchr", - "nkeys", - "nuid", - "once_cell", - "portable-atomic", - "rand", - "regex", - "ring 0.17.8", - "rustls-native-certs", - "rustls-pemfile 2.1.2", - "rustls-webpki 0.102.4", - "serde", - "serde_json", - "serde_nanos", - "serde_repr", - "thiserror", - "time", - "tokio", - "tokio-rustls 0.26.0", - "tracing", - "tryhard", - "url", -] - -[[package]] -name = "async-stream" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "async-trait" -version = "0.1.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "async_io_stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" -dependencies = [ - "futures", - "pharos", - "rustc_version", -] - -[[package]] -name = "asynchronous-codec" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057f2c32adbb2fc158e22fb38433c8e9bbf76b75a4732c7c0cbaf695fb65568" -dependencies = [ - "bytes", - "futures-sink", - "futures-util", - "memchr", - "pin-project-lite", -] - -[[package]] -name = "asynchronous-codec" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" -dependencies = [ - "bytes", - "futures-sink", - "futures-util", - "memchr", - "pin-project-lite", -] - -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - -[[package]] -name = "attohttpc" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" -dependencies = [ - "http", - "log", - "url", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - -[[package]] -name = "auto_impl" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "autocfg" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" - -[[package]] -name = "axum" -version = "0.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" -dependencies = [ - "async-trait", - "axum-core", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http", - "http-body", - "hyper", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-http", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-core" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "mime", - "tower-layer", - "tower-service", -] - -[[package]] -name = "backtrace" -version = "0.3.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "serde", -] - -[[package]] -name = "base-x" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bech32" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bindgen" -version = "0.65.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" -dependencies = [ - "bitflags 1.3.2", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.66", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" -dependencies = [ - "serde", -] - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "sha2 0.10.8", - "tinyvec", -] - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "byte-slice-cast" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" -dependencies = [ - "serde", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "camino" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "cc" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" -dependencies = [ - "jobserver", - "libc", - "once_cell", -] - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher", - "poly1305", - "zeroize", -] - -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "serde", - "windows-targets 0.52.5", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", - "zeroize", -] - -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "clap" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" -dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_derive 3.2.25", - "clap_lex 0.2.4", - "indexmap 1.9.3", - "once_cell", - "strsim 0.10.0", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap" -version = "4.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" -dependencies = [ - "clap_builder", - "clap_derive 4.5.4", -] - -[[package]] -name = "clap_builder" -version = "4.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" -dependencies = [ - "anstream", - "anstyle", - "clap_lex 0.7.0", - "strsim 0.11.1", -] - -[[package]] -name = "clap_derive" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" -dependencies = [ - "heck 0.4.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "clap_derive" -version = "4.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "clap_lex" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" - -[[package]] -name = "cobs" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" - -[[package]] -name = "coins-bip32" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b6be4a5df2098cd811f3194f64ddb96c267606bffd9689ac7b0160097b01ad3" -dependencies = [ - "bs58", - "coins-core", - "digest 0.10.7", - "hmac 0.12.1", - "k256", - "serde", - "sha2 0.10.8", - "thiserror", -] - -[[package]] -name = "coins-bip39" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" -dependencies = [ - "bitvec", - "coins-bip32", - "hmac 0.12.1", - "once_cell", - "pbkdf2", - "rand", - "sha2 0.10.8", - "thiserror", -] - -[[package]] -name = "coins-core" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" -dependencies = [ - "base64 0.21.7", - "bech32", - "bs58", - "digest 0.10.7", - "generic-array", - "hex", - "ripemd", - "serde", - "serde_derive", - "sha2 0.10.8", - "sha3", - "thiserror", -] - -[[package]] -name = "colorchoice" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "console" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" -dependencies = [ - "encode_unicode", - "lazy_static", - "libc", - "unicode-width", - "windows-sys 0.52.0", -] - -[[package]] -name = "const-hex" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" -dependencies = [ - "cfg-if", - "cpufeatures", - "hex", - "proptest", - "serde", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "const_format" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" - -[[package]] -name = "core2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" -dependencies = [ - "memchr", -] - -[[package]] -name = "cpp_demangle" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "cpufeatures" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" -dependencies = [ - "libc", -] - -[[package]] -name = "cranelift-bforest" -version = "0.105.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496c993b62bdfbe9b4c518b8b3e1fdba9f89ef89fcccc050ab61d91dfba9fbaf" -dependencies = [ - "cranelift-entity", -] - -[[package]] -name = "cranelift-codegen" -version = "0.105.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b922abb6be41fc383f5e9da65b58d32d0d0a32c87dfe3bbbcb61a09119506c" -dependencies = [ - "bumpalo", - "cranelift-bforest", - "cranelift-codegen-meta", - "cranelift-codegen-shared", - "cranelift-control", - "cranelift-entity", - "cranelift-isle", - "gimli", - "hashbrown 0.14.5", - "log", - "regalloc2", - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cranelift-codegen-meta" -version = "0.105.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634c2ed9ef8a04ca42535a3e2e7917e4b551f2f306f4df2d935a6e71e346c167" -dependencies = [ - "cranelift-codegen-shared", -] - -[[package]] -name = "cranelift-codegen-shared" -version = "0.105.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00cde1425b4da28bb0d5ff010030ea9cc9be7aded342ae099b394284f17cefce" - -[[package]] -name = "cranelift-control" -version = "0.105.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1622125c99f1864aaf44e57971770c4a918d081d4b4af0bb597bdf624660ed66" -dependencies = [ - "arbitrary", -] - -[[package]] -name = "cranelift-entity" -version = "0.105.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea97887aca1c0cbe7f8513874dc3603e9744fb1cfa78840ca8897bd2766bd35b" -dependencies = [ - "serde", - "serde_derive", -] - -[[package]] -name = "cranelift-frontend" -version = "0.105.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cdade4c14183fe41482071ed77d6a38cb95a17c7a0a05e629152e6292c4f8cb" -dependencies = [ - "cranelift-codegen", - "log", - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cranelift-isle" -version = "0.105.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbbe4d3ad7bd4bf4a8d916c8460b441cf92417f5cdeacce4dd1d96eee70b18a2" - -[[package]] -name = "cranelift-native" -version = "0.105.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c46be4ed1fc8f36df4e2a442b8c30a39d8c03c1868182978f4c04ba2c25c9d4f" -dependencies = [ - "cranelift-codegen", - "libc", - "target-lexicon", -] - -[[package]] -name = "cranelift-wasm" -version = "0.105.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d4c4a785a7866da89d20df159e3c4f96a5f14feb83b1f5998cfd5fe2e74d06" -dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", - "itertools 0.10.5", - "log", - "smallvec", - "wasmparser", - "wasmtime-types", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "critical-section" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" - -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "rand_core", - "typenum", -] - -[[package]] -name = "crypto-mac" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" -dependencies = [ - "generic-array", - "subtle", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "curve25519-dalek" -version = "4.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "digest 0.10.7", - "fiat-crypto", - "platforms", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core 0.14.4", - "darling_macro 0.14.4", -] - -[[package]] -name = "darling" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" -dependencies = [ - "darling_core 0.20.9", - "darling_macro 0.20.9", -] - -[[package]] -name = "darling_core" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 1.0.109", -] - -[[package]] -name = "darling_core" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.11.1", - "syn 2.0.66", -] - -[[package]] -name = "darling_macro" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" -dependencies = [ - "darling_core 0.14.4", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" -dependencies = [ - "darling_core 0.20.9", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "data-encoding" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" - -[[package]] -name = "data-encoding-macro" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" -dependencies = [ - "data-encoding", - "data-encoding-macro-internal", -] - -[[package]] -name = "data-encoding-macro-internal" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" -dependencies = [ - "data-encoding", - "syn 1.0.109", -] - -[[package]] -name = "debugid" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" -dependencies = [ - "uuid", -] - -[[package]] -name = "der" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "der-parser" -version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", - "serde", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.109", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer 0.10.4", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "directories-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "displaydoc" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "dtoa" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" - -[[package]] -name = "dunce" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest 0.10.7", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" -dependencies = [ - "curve25519-dalek", - "ed25519", - "rand_core", - "serde", - "sha2 0.10.8", - "signature", - "subtle", - "zeroize", -] - -[[package]] -name = "either" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest 0.10.7", - "ff", - "generic-array", - "group", - "pkcs8", - "rand_core", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" - -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - -[[package]] -name = "encoding_rs" -version = "0.8.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "enr" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a3d8dc56e02f954cac8eb489772c552c473346fc34f67412bb6244fd647f7e4" -dependencies = [ - "base64 0.21.7", - "bytes", - "hex", - "k256", - "log", - "rand", - "rlp", - "serde", - "sha3", - "zeroize", -] - -[[package]] -name = "enum-as-inner" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "enum-iterator" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd242f399be1da0a5354aa462d57b4ab2b4ee0683cc552f7c007d2d12d36e94" -dependencies = [ - "enum-iterator-derive", -] - -[[package]] -name = "enum-iterator-derive" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "ethabi" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" -dependencies = [ - "ethereum-types", - "hex", - "once_cell", - "regex", - "serde", - "serde_json", - "sha3", - "thiserror", - "uint", -] - -[[package]] -name = "ethbloom" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" -dependencies = [ - "crunchy", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "tiny-keccak", -] - -[[package]] -name = "ethereum-types" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" -dependencies = [ - "ethbloom", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "primitive-types", - "scale-info", - "uint", -] - -[[package]] -name = "ethers-contract" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fceafa3578c836eeb874af87abacfb041f92b4da0a78a5edd042564b8ecdaaa" -dependencies = [ - "const-hex", - "ethers-contract-abigen", - "ethers-contract-derive", - "ethers-core", - "futures-util", - "once_cell", - "pin-project", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "ethers-contract-abigen" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04ba01fbc2331a38c429eb95d4a570166781f14290ef9fdb144278a90b5a739b" -dependencies = [ - "Inflector", - "const-hex", - "dunce", - "ethers-core", - "eyre", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "serde", - "serde_json", - "syn 2.0.66", - "toml 0.8.13", - "walkdir", -] - -[[package]] -name = "ethers-contract-derive" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87689dcabc0051cde10caaade298f9e9093d65f6125c14575db3fd8c669a168f" -dependencies = [ - "Inflector", - "const-hex", - "ethers-contract-abigen", - "ethers-core", - "proc-macro2", - "quote", - "serde_json", - "syn 2.0.66", -] - -[[package]] -name = "ethers-core" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f" -dependencies = [ - "arrayvec", - "bytes", - "cargo_metadata", - "chrono", - "const-hex", - "elliptic-curve", - "ethabi", - "generic-array", - "k256", - "num_enum", - "once_cell", - "open-fastrlp", - "rand", - "rlp", - "serde", - "serde_json", - "strum 0.26.2", - "syn 2.0.66", - "tempfile", - "thiserror", - "tiny-keccak", - "unicode-xid", -] - -[[package]] -name = "ethers-providers" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6434c9a33891f1effc9c75472e12666db2fa5a0fec4b29af6221680a6fe83ab2" -dependencies = [ - "async-trait", - "auto_impl", - "base64 0.21.7", - "bytes", - "const-hex", - "enr", - "ethers-core", - "futures-channel", - "futures-core", - "futures-timer", - "futures-util", - "hashers", - "http", - "instant", - "jsonwebtoken", - "once_cell", - "pin-project", - "reqwest", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-tungstenite", - "tracing", - "tracing-futures", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "ws_stream_wasm", -] - -[[package]] -name = "ethnum" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" - -[[package]] -name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fastrand" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" - -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core", - "subtle", -] - -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - -[[package]] -name = "findshlibs" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" -dependencies = [ - "cc", - "lazy_static", - "libc", - "winapi", -] - -[[package]] -name = "fixed-hash" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" -dependencies = [ - "byteorder", - "rand", - "rustc-hex", - "static_assertions", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fuel-asm" -version = "0.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42df651415e443094f86102473b7f9fa23633ab6c3c98dd3f713adde251acf0f" -dependencies = [ - "bitflags 2.5.0", - "fuel-types", - "serde", - "strum 0.24.1", -] - -[[package]] -name = "fuel-core" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b030e12851d70598e12722886b899e28884d168367fc20d9a809951dd599004" -dependencies = [ - "anyhow", - "async-graphql", - "async-trait", - "axum", - "clap 4.5.4", - "derive_more", - "enum-iterator", - "fuel-core-chain-config", - "fuel-core-consensus-module", - "fuel-core-database", - "fuel-core-executor", - "fuel-core-importer", - "fuel-core-metrics", - "fuel-core-p2p", - "fuel-core-poa", - "fuel-core-producer", - "fuel-core-relayer", - "fuel-core-services", - "fuel-core-storage", - "fuel-core-sync", - "fuel-core-txpool", - "fuel-core-types", - "fuel-core-upgradable-executor", - "futures", - "hex", - "hyper", - "indicatif", - "itertools 0.12.1", - "num_cpus", - "rand", - "rocksdb", - "serde", - "serde_json", - "strum 0.25.0", - "strum_macros 0.25.3", - "tempfile", - "thiserror", - "tokio", - "tokio-stream", - "tokio-util", - "tower-http", - "tracing", - "uuid", -] - -[[package]] -name = "fuel-core-bin" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5ff2b6ce36b11e79f338b22fe436962d8d587b60a8d751c6bef2b7cb5d89bb" -dependencies = [ - "anyhow", - "async-trait", - "clap 4.5.4", - "const_format", - "dirs", - "dotenvy", - "fuel-core", - "fuel-core-chain-config", - "fuel-core-types", - "hex", - "humantime", - "pyroscope", - "pyroscope_pprofrs", - "serde_json", - "tikv-jemallocator", - "tokio", - "tokio-util", - "tracing", - "tracing-subscriber", - "url", -] - -[[package]] -name = "fuel-core-chain-config" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d202fe1dfeb98882bdc5a0206a58e469d76fd09d952c4050bb979102bd690398" -dependencies = [ - "anyhow", - "bech32", - "derivative", - "fuel-core-storage", - "fuel-core-types", - "itertools 0.12.1", - "postcard", - "serde", - "serde_json", - "serde_with", - "tracing", -] - -[[package]] -name = "fuel-core-consensus-module" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f99179c08972efffe7628f0ff8d59028218b126347a6f9eba86f71e20966eeb" -dependencies = [ - "anyhow", - "fuel-core-chain-config", - "fuel-core-poa", - "fuel-core-storage", - "fuel-core-types", -] - -[[package]] -name = "fuel-core-database" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5b1fd08a72609ebf0c8106359a37a4b205055be15e9f4fc30a4c0b5f0644c6b" -dependencies = [ - "anyhow", - "derive_more", - "fuel-core-storage", - "fuel-core-types", -] - -[[package]] -name = "fuel-core-executor" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f98d89798007bc781d56e02681144683f5c645ee0725e7717e38694e8e5e31d" -dependencies = [ - "anyhow", - "fuel-core-storage", - "fuel-core-types", - "hex", - "parking_lot", - "serde", - "tracing", -] - -[[package]] -name = "fuel-core-importer" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51837a53f2d8b78a701aee61b99c7f1873f23e864f01f4b4d0644a06e1f7c41" -dependencies = [ - "anyhow", - "derive_more", - "fuel-core-metrics", - "fuel-core-storage", - "fuel-core-types", - "tokio", - "tokio-rayon", - "tracing", -] - -[[package]] -name = "fuel-core-metrics" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bacc62bc4fec2fe6a818a1a7145b892bd486d69266190ca8dd31a036a3a327b7" -dependencies = [ - "axum", - "once_cell", - "pin-project-lite", - "prometheus-client", - "regex", - "tracing", -] - -[[package]] -name = "fuel-streams-publisher" -version = "0.26.0" -dependencies = [ - "anyhow", - "async-nats", - "clap 4.5.4", - "fuel-core", - "fuel-core-bin", - "fuel-core-services", - "fuel-core-types", - "serde_json", - "tokio", - "tracing", -] - -[[package]] -name = "fuel-core-p2p" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6496068f0f5736f9e51bba8f8bb04cb83f68df2f6142e410fe62854b47621b3" -dependencies = [ - "anyhow", - "async-trait", - "fuel-core-chain-config", - "fuel-core-metrics", - "fuel-core-services", - "fuel-core-storage", - "fuel-core-types", - "futures", - "hex", - "ip_network", - "libp2p", - "libp2p-mplex", - "postcard", - "prometheus-client", - "quick-protobuf", - "quick-protobuf-codec", - "rand", - "serde", - "serde_with", - "sha2 0.10.8", - "thiserror", - "tokio", - "tracing", - "void", -] - -[[package]] -name = "fuel-core-poa" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d189ecd635688ddc896b44c8497b29c04bb4a3719a24eea0ca9691a6f76d5e" -dependencies = [ - "anyhow", - "async-trait", - "fuel-core-chain-config", - "fuel-core-services", - "fuel-core-storage", - "fuel-core-types", - "tokio", - "tokio-stream", - "tracing", -] - -[[package]] -name = "fuel-core-producer" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d2901a7ba2c0e724bbb88a3111fdb9844f5faf9f0bd4005944f61f093730b4d" -dependencies = [ - "anyhow", - "async-trait", - "derive_more", - "fuel-core-storage", - "fuel-core-types", - "tokio", - "tokio-rayon", - "tracing", -] - -[[package]] -name = "fuel-core-relayer" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da1aa686ec4a05b77f4c4dfae68e6f17701b5bc21a49a8a505c6d9dc6dcf9183" -dependencies = [ - "anyhow", - "async-trait", - "enum-iterator", - "ethers-contract", - "ethers-core", - "ethers-providers", - "fuel-core-services", - "fuel-core-storage", - "fuel-core-types", - "futures", - "once_cell", - "strum 0.25.0", - "strum_macros 0.25.3", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "fuel-core-services" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2ab4d3931b8cafdb2e69fe8ca97918a168d74c73c070481ca0e552cc37bb93" -dependencies = [ - "anyhow", - "async-trait", - "fuel-core-metrics", - "futures", - "parking_lot", - "tokio", - "tracing", -] - -[[package]] -name = "fuel-core-storage" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e039c1c6ebef314c74c34728e1f2199dcf9ede041d6f5c6e11479517c8f4d320" -dependencies = [ - "anyhow", - "derive_more", - "enum-iterator", - "fuel-core-types", - "fuel-vm", - "impl-tools", - "itertools 0.12.1", - "num_enum", - "paste", - "postcard", - "primitive-types", - "serde", - "strum 0.25.0", - "strum_macros 0.25.3", -] - -[[package]] -name = "fuel-core-sync" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7059ee4c3dbf1e9340d7cd88eccf2e10c631306d8038ab20160e6434deb79c1b" -dependencies = [ - "anyhow", - "async-trait", - "fuel-core-services", - "fuel-core-types", - "futures", - "rand", - "tokio", - "tracing", -] - -[[package]] -name = "fuel-core-txpool" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985684e2d67d5018e9227a4f9ed79cac02b23b207e457ee95833ab047769c2ac" -dependencies = [ - "anyhow", - "async-trait", - "fuel-core-metrics", - "fuel-core-services", - "fuel-core-storage", - "fuel-core-types", - "futures", - "parking_lot", - "tokio", - "tokio-rayon", - "tokio-stream", - "tracing", -] - -[[package]] -name = "fuel-core-types" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf038dd8df8d3aa665a13295c9ef888ba8118600cccdf8fb4587410e0e102fdf" -dependencies = [ - "anyhow", - "bs58", - "derivative", - "derive_more", - "fuel-vm", - "secrecy", - "serde", - "tai64", - "thiserror", - "zeroize", -] - -[[package]] -name = "fuel-core-upgradable-executor" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc54c84a7dc13f76930761ebca391b167caa096dc2bdb2413b5a2400bf65f99d" -dependencies = [ - "anyhow", - "fuel-core-executor", - "fuel-core-storage", - "fuel-core-types", - "fuel-core-wasm-executor", - "parking_lot", - "postcard", - "tracing", - "wasmtime", -] - -[[package]] -name = "fuel-core-wasm-executor" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a201e4fa5f94efb36cda172947875483d90a88b060cb9c6f9a496313d171aae8" -dependencies = [ - "anyhow", - "fuel-core-executor", - "fuel-core-storage", - "fuel-core-types", - "postcard", - "serde", -] - -[[package]] -name = "fuel-crypto" -version = "0.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71cef93970fb8a26d3a683ae211833c6bbf391066887f501bd5859f29992b59a" -dependencies = [ - "coins-bip32", - "coins-bip39", - "ecdsa", - "ed25519-dalek", - "fuel-types", - "k256", - "lazy_static", - "p256", - "rand", - "secp256k1", - "serde", - "sha2 0.10.8", - "zeroize", -] - -[[package]] -name = "fuel-derive" -version = "0.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b85e8e508b26d088262075fcfe9921b7009c931fef1cc55fe1dafb116c99884" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "synstructure", -] - -[[package]] -name = "fuel-merkle" -version = "0.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5198b4eab5a19b0034971da88199dae7dd61806ebd8df366d6af1f17cda2e151" -dependencies = [ - "derive_more", - "digest 0.10.7", - "fuel-storage", - "hashbrown 0.13.2", - "hex", - "serde", - "sha2 0.10.8", -] - -[[package]] -name = "fuel-storage" -version = "0.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa738e9c244f3f312af09faef108ec9a285f02afcefbc579c19c242cea742dd0" - -[[package]] -name = "fuel-tx" -version = "0.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e4b4ea79ffe711af7bbf363b25f383fc6e481e652cf55a5ef8b5a458fcf4ef9" -dependencies = [ - "bitflags 2.5.0", - "derivative", - "derive_more", - "fuel-asm", - "fuel-crypto", - "fuel-merkle", - "fuel-types", - "hashbrown 0.14.5", - "itertools 0.10.5", - "postcard", - "rand", - "serde", - "serde_json", - "strum 0.24.1", - "strum_macros 0.24.3", -] - -[[package]] -name = "fuel-types" -version = "0.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455cf5275d96f6907e81ed1825c4e6a9dd79f7c1c37a4e15134562f83024c7e7" -dependencies = [ - "fuel-derive", - "hex", - "serde", -] - -[[package]] -name = "fuel-vm" -version = "0.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8811f949db8ce61cc68dcf81644047df4ee23be55879efcfe9f1aa5adc378965" -dependencies = [ - "async-trait", - "backtrace", - "bitflags 2.5.0", - "derivative", - "derive_more", - "ethnum", - "fuel-asm", - "fuel-crypto", - "fuel-merkle", - "fuel-storage", - "fuel-tx", - "fuel-types", - "hashbrown 0.14.5", - "itertools 0.10.5", - "libm", - "paste", - "percent-encoding", - "primitive-types", - "serde", - "serde_with", - "sha3", - "static_assertions", - "strum 0.24.1", - "tai64", -] - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-bounded" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e2774cc104e198ef3d3e1ff4ab40f86fa3245d6cb6a3a46174f21463cee173" -dependencies = [ - "futures-timer", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", - "num_cpus", -] - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-lite" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "futures-rustls" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd3cf68c183738046838e300353e4716c674dc5e56890de4826801a6622a28" -dependencies = [ - "futures-io", - "rustls 0.21.12", -] - -[[package]] -name = "futures-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" -dependencies = [ - "futures-io", - "rustls 0.23.8", - "rustls-pki-types", -] - -[[package]] -name = "futures-sink" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" - -[[package]] -name = "futures-task" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" - -[[package]] -name = "futures-ticker" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9763058047f713632a52e916cc7f6a4b3fc6e9fc1ff8c5b1dc49e5a89041682e" -dependencies = [ - "futures", - "futures-timer", - "instant", -] - -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" -dependencies = [ - "gloo-timers", - "send_wrapper 0.4.0", -] - -[[package]] -name = "futures-util" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" -dependencies = [ - "fallible-iterator", - "indexmap 2.2.6", - "stable_deref_trait", -] - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "gloo-timers" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core", - "subtle", -] - -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap 2.2.6", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", - "serde", -] - -[[package]] -name = "hashers" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2bca93b15ea5a746f220e56587f71e73c6165eab783df9e26590069953e3c30" -dependencies = [ - "fxhash", -] - -[[package]] -name = "heapless" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" -dependencies = [ - "atomic-polyfill", - "hash32", - "rustc_version", - "serde", - "spin 0.9.8", - "stable_deref_trait", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] - -[[package]] -name = "hex_fmt" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" - -[[package]] -name = "hickory-proto" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.4.0", - "ipnet", - "once_cell", - "rand", - "socket2", - "thiserror", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "hickory-resolver" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243" -dependencies = [ - "cfg-if", - "futures-util", - "hickory-proto", - "ipconfig", - "lru-cache", - "once_cell", - "parking_lot", - "rand", - "resolv-conf", - "smallvec", - "thiserror", - "tokio", - "tracing", -] - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac 0.12.1", -] - -[[package]] -name = "hmac" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" -dependencies = [ - "crypto-mac", - "digest 0.9.0", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "hmac-drbg" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" -dependencies = [ - "digest 0.9.0", - "generic-array", - "hmac 0.8.1", -] - -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "http-range-header" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "hyper" -version = "0.14.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http", - "hyper", - "rustls 0.21.12", - "tokio", - "tokio-rustls 0.24.1", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core 0.52.0", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "if-addrs" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "if-watch" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e" -dependencies = [ - "async-io", - "core-foundation", - "fnv", - "futures", - "if-addrs", - "ipnet", - "log", - "rtnetlink", - "system-configuration", - "tokio", - "windows", -] - -[[package]] -name = "igd-next" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064d90fec10d541084e7b39ead8875a5a80d9114a2b18791565253bae25f49e4" -dependencies = [ - "async-trait", - "attohttpc", - "bytes", - "futures", - "http", - "hyper", - "log", - "rand", - "tokio", - "url", - "xmltree", -] - -[[package]] -name = "impl-codec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-rlp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" -dependencies = [ - "rlp", -] - -[[package]] -name = "impl-serde" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" -dependencies = [ - "serde", -] - -[[package]] -name = "impl-tools" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82c305b1081f1a99fda262883c788e50ab57d36c00830bdd7e0a82894ad965c" -dependencies = [ - "autocfg", - "impl-tools-lib", - "proc-macro-error", - "syn 2.0.66", -] - -[[package]] -name = "impl-tools-lib" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85d3946d886eaab0702fa0c6585adcced581513223fa9df7ccfabbd9fa331a88" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "impl-trait-for-tuples" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" -dependencies = [ - "equivalent", - "hashbrown 0.14.5", - "serde", -] - -[[package]] -name = "indicatif" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" -dependencies = [ - "console", - "instant", - "number_prefix", - "portable-atomic", - "unicode-width", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ip_network" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" - -[[package]] -name = "ipconfig" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" -dependencies = [ - "socket2", - "widestring", - "windows-sys 0.48.0", - "winreg", -] - -[[package]] -name = "ipnet" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "jobserver" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "json" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" - -[[package]] -name = "jsonwebtoken" -version = "8.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" -dependencies = [ - "base64 0.21.7", - "pem 1.1.1", - "ring 0.16.20", - "serde", - "serde_json", - "simple_asn1", -] - -[[package]] -name = "k256" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "once_cell", - "sha2 0.10.8", - "signature", -] - -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "leb128" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" - -[[package]] -name = "libc" -version = "0.2.155" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" - -[[package]] -name = "libflate" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18" -dependencies = [ - "adler32", - "crc32fast", - "libflate_lz77", -] - -[[package]] -name = "libflate_lz77" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf" -dependencies = [ - "rle-decode-fast", -] - -[[package]] -name = "libloading" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" -dependencies = [ - "cfg-if", - "windows-targets 0.52.5", -] - -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "libp2p" -version = "0.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681fb3f183edfbedd7a57d32ebe5dcdc0b9f94061185acf3c30249349cc6fc99" -dependencies = [ - "bytes", - "either", - "futures", - "futures-timer", - "getrandom", - "instant", - "libp2p-allow-block-list", - "libp2p-connection-limits", - "libp2p-core", - "libp2p-dns", - "libp2p-gossipsub", - "libp2p-identify", - "libp2p-identity", - "libp2p-kad", - "libp2p-mdns", - "libp2p-metrics", - "libp2p-noise", - "libp2p-quic", - "libp2p-request-response", - "libp2p-swarm", - "libp2p-tcp", - "libp2p-upnp", - "libp2p-websocket", - "libp2p-yamux", - "multiaddr", - "pin-project", - "rw-stream-sink", - "thiserror", -] - -[[package]] -name = "libp2p-allow-block-list" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107b238b794cb83ab53b74ad5dcf7cca3200899b72fe662840cfb52f5b0a32e6" -dependencies = [ - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", - "void", -] - -[[package]] -name = "libp2p-connection-limits" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7cd50a78ccfada14de94cbacd3ce4b0138157f376870f13d3a8422cd075b4fd" -dependencies = [ - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", - "void", -] - -[[package]] -name = "libp2p-core" -version = "0.41.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8130a8269e65a2554d55131c770bdf4bcd94d2b8d4efb24ca23699be65066c05" -dependencies = [ - "either", - "fnv", - "futures", - "futures-timer", - "instant", - "libp2p-identity", - "multiaddr", - "multihash", - "multistream-select", - "once_cell", - "parking_lot", - "pin-project", - "quick-protobuf", - "rand", - "rw-stream-sink", - "smallvec", - "thiserror", - "tracing", - "unsigned-varint 0.8.0", - "void", -] - -[[package]] -name = "libp2p-dns" -version = "0.41.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17cbcf7160ff35c3e8e560de4a068fe9d6cb777ea72840e48eb76ff9576c4b6" -dependencies = [ - "async-trait", - "futures", - "hickory-resolver", - "libp2p-core", - "libp2p-identity", - "parking_lot", - "smallvec", - "tracing", -] - -[[package]] -name = "libp2p-gossipsub" -version = "0.46.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d665144a616dadebdc5fff186b1233488cdcd8bfb1223218ff084b6d052c94f7" -dependencies = [ - "asynchronous-codec 0.7.0", - "base64 0.21.7", - "byteorder", - "bytes", - "either", - "fnv", - "futures", - "futures-ticker", - "getrandom", - "hex_fmt", - "instant", - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", - "prometheus-client", - "quick-protobuf", - "quick-protobuf-codec", - "rand", - "regex", - "sha2 0.10.8", - "smallvec", - "tracing", - "void", -] - -[[package]] -name = "libp2p-identify" -version = "0.44.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5d635ebea5ca0c3c3e77d414ae9b67eccf2a822be06091b9c1a0d13029a1e2f" -dependencies = [ - "asynchronous-codec 0.7.0", - "either", - "futures", - "futures-bounded", - "futures-timer", - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", - "lru", - "quick-protobuf", - "quick-protobuf-codec", - "smallvec", - "thiserror", - "tracing", - "void", -] - -[[package]] -name = "libp2p-identity" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999ec70441b2fb35355076726a6bc466c932e9bdc66f6a11c6c0aa17c7ab9be0" -dependencies = [ - "asn1_der", - "bs58", - "ed25519-dalek", - "hkdf", - "libsecp256k1", - "multihash", - "quick-protobuf", - "rand", - "sha2 0.10.8", - "thiserror", - "tracing", - "zeroize", -] - -[[package]] -name = "libp2p-kad" -version = "0.45.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc5767727d062c4eac74dd812c998f0e488008e82cce9c33b463d38423f9ad2" -dependencies = [ - "arrayvec", - "asynchronous-codec 0.7.0", - "bytes", - "either", - "fnv", - "futures", - "futures-bounded", - "futures-timer", - "instant", - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", - "quick-protobuf", - "quick-protobuf-codec", - "rand", - "sha2 0.10.8", - "smallvec", - "thiserror", - "tracing", - "uint", - "void", -] - -[[package]] -name = "libp2p-mdns" -version = "0.45.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49007d9a339b3e1d7eeebc4d67c05dbf23d300b7d091193ec2d3f26802d7faf2" -dependencies = [ - "data-encoding", - "futures", - "hickory-proto", - "if-watch", - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", - "rand", - "smallvec", - "socket2", - "tokio", - "tracing", - "void", -] - -[[package]] -name = "libp2p-metrics" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdac91ae4f291046a3b2660c039a2830c931f84df2ee227989af92f7692d3357" -dependencies = [ - "futures", - "instant", - "libp2p-core", - "libp2p-gossipsub", - "libp2p-identify", - "libp2p-identity", - "libp2p-kad", - "libp2p-swarm", - "pin-project", - "prometheus-client", -] - -[[package]] -name = "libp2p-mplex" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e895765e27e30217b25f7cb7ac4686dad1ff80bf2fdeffd1d898566900a924" -dependencies = [ - "asynchronous-codec 0.6.2", - "bytes", - "futures", - "libp2p-core", - "libp2p-identity", - "nohash-hasher", - "parking_lot", - "rand", - "smallvec", - "tracing", - "unsigned-varint 0.7.2", -] - -[[package]] -name = "libp2p-noise" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecd0545ce077f6ea5434bcb76e8d0fe942693b4380aaad0d34a358c2bd05793" -dependencies = [ - "asynchronous-codec 0.7.0", - "bytes", - "curve25519-dalek", - "futures", - "libp2p-core", - "libp2p-identity", - "multiaddr", - "multihash", - "once_cell", - "quick-protobuf", - "rand", - "sha2 0.10.8", - "snow", - "static_assertions", - "thiserror", - "tracing", - "x25519-dalek", - "zeroize", -] - -[[package]] -name = "libp2p-quic" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c67296ad4e092e23f92aea3d2bdb6f24eab79c0929ed816dfb460ea2f4567d2b" -dependencies = [ - "bytes", - "futures", - "futures-timer", - "if-watch", - "libp2p-core", - "libp2p-identity", - "libp2p-tls", - "parking_lot", - "quinn", - "rand", - "ring 0.17.8", - "rustls 0.23.8", - "socket2", - "thiserror", - "tokio", - "tracing", -] - -[[package]] -name = "libp2p-request-response" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6946e5456240b3173187cc37a17cb40c3cd1f7138c76e2c773e0d792a42a8de1" -dependencies = [ - "async-trait", - "futures", - "futures-bounded", - "futures-timer", - "instant", - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", - "rand", - "smallvec", - "tracing", - "void", -] - -[[package]] -name = "libp2p-swarm" -version = "0.44.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80cae6cb75f89dbca53862f9ebe0b9f463aa7b302762fcfaafb9e51dcc9b0f7e" -dependencies = [ - "either", - "fnv", - "futures", - "futures-timer", - "instant", - "libp2p-core", - "libp2p-identity", - "libp2p-swarm-derive", - "lru", - "multistream-select", - "once_cell", - "rand", - "smallvec", - "tokio", - "tracing", - "void", -] - -[[package]] -name = "libp2p-swarm-derive" -version = "0.34.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5daceb9dd908417b6dfcfe8e94098bc4aac54500c282e78120b885dadc09b999" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "libp2p-tcp" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2460fc2748919adff99ecbc1aab296e4579e41f374fb164149bd2c9e529d4c" -dependencies = [ - "futures", - "futures-timer", - "if-watch", - "libc", - "libp2p-core", - "libp2p-identity", - "socket2", - "tokio", - "tracing", -] - -[[package]] -name = "libp2p-tls" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "251b17aebdd29df7e8f80e4d94b782fae42e934c49086e1a81ba23b60a8314f2" -dependencies = [ - "futures", - "futures-rustls 0.26.0", - "libp2p-core", - "libp2p-identity", - "rcgen", - "ring 0.17.8", - "rustls 0.23.8", - "rustls-webpki 0.101.7", - "thiserror", - "x509-parser", - "yasna", -] - -[[package]] -name = "libp2p-upnp" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccf04b0e3ff3de52d07d5fd6c3b061d0e7f908ffc683c32d9638caedce86fc8" -dependencies = [ - "futures", - "futures-timer", - "igd-next", - "libp2p-core", - "libp2p-swarm", - "tokio", - "tracing", - "void", -] - -[[package]] -name = "libp2p-websocket" -version = "0.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4846d51afd08180e164291c3754ba30dd4fbac6fac65571be56403c16431a5e" -dependencies = [ - "either", - "futures", - "futures-rustls 0.24.0", - "libp2p-core", - "libp2p-identity", - "parking_lot", - "pin-project-lite", - "rw-stream-sink", - "soketto", - "tracing", - "url", - "webpki-roots", -] - -[[package]] -name = "libp2p-yamux" -version = "0.45.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200cbe50349a44760927d50b431d77bed79b9c0a3959de1af8d24a63434b71e5" -dependencies = [ - "either", - "futures", - "libp2p-core", - "thiserror", - "tracing", - "yamux 0.12.1", - "yamux 0.13.2", -] - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.5.0", - "libc", -] - -[[package]] -name = "librocksdb-sys" -version = "0.11.0+8.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3386f101bcb4bd252d8e9d2fb41ec3b0862a15a62b478c355b2982efa469e3e" -dependencies = [ - "bindgen", - "bzip2-sys", - "cc", - "glob", - "libc", - "libz-sys", - "lz4-sys", -] - -[[package]] -name = "libsecp256k1" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" -dependencies = [ - "arrayref", - "base64 0.13.1", - "digest 0.9.0", - "hmac-drbg", - "libsecp256k1-core", - "libsecp256k1-gen-ecmult", - "libsecp256k1-gen-genmult", - "rand", - "serde", - "sha2 0.9.9", - "typenum", -] - -[[package]] -name = "libsecp256k1-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" -dependencies = [ - "crunchy", - "digest 0.9.0", - "subtle", -] - -[[package]] -name = "libsecp256k1-gen-ecmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" -dependencies = [ - "libsecp256k1-core", -] - -[[package]] -name = "libsecp256k1-gen-genmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" -dependencies = [ - "libsecp256k1-core", -] - -[[package]] -name = "libz-sys" -version = "1.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" - -[[package]] -name = "lru" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" -dependencies = [ - "hashbrown 0.14.5", -] - -[[package]] -name = "lru-cache" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -dependencies = [ - "linked-hash-map", -] - -[[package]] -name = "lz4-sys" -version = "1.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "matchit" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" - -[[package]] -name = "memchr" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" - -[[package]] -name = "memfd" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" -dependencies = [ - "rustix", -] - -[[package]] -name = "memmap2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.48.0", -] - -[[package]] -name = "multer" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "log", - "memchr", - "mime", - "spin 0.9.8", - "version_check", -] - -[[package]] -name = "multiaddr" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b852bc02a2da5feed68cd14fa50d0774b92790a5bdbfa932a813926c8472070" -dependencies = [ - "arrayref", - "byteorder", - "data-encoding", - "libp2p-identity", - "multibase", - "multihash", - "percent-encoding", - "serde", - "static_assertions", - "unsigned-varint 0.7.2", - "url", -] - -[[package]] -name = "multibase" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" -dependencies = [ - "base-x", - "data-encoding", - "data-encoding-macro", -] - -[[package]] -name = "multihash" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492" -dependencies = [ - "core2", - "unsigned-varint 0.7.2", -] - -[[package]] -name = "multistream-select" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0df8e5eec2298a62b326ee4f0d7fe1a6b90a09dfcf9df37b38f947a8c42f19" -dependencies = [ - "bytes", - "futures", - "log", - "pin-project", - "smallvec", - "unsigned-varint 0.7.2", -] - -[[package]] -name = "names" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc" -dependencies = [ - "clap 3.2.25", - "rand", -] - -[[package]] -name = "netlink-packet-core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297" -dependencies = [ - "anyhow", - "byteorder", - "libc", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-route" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" -dependencies = [ - "anyhow", - "bitflags 1.3.2", - "byteorder", - "libc", - "netlink-packet-core", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-utils" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" -dependencies = [ - "anyhow", - "byteorder", - "paste", - "thiserror", -] - -[[package]] -name = "netlink-proto" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" -dependencies = [ - "bytes", - "futures", - "log", - "netlink-packet-core", - "netlink-sys", - "thiserror", - "tokio", -] - -[[package]] -name = "netlink-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" -dependencies = [ - "bytes", - "futures", - "libc", - "log", - "tokio", -] - -[[package]] -name = "nix" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", -] - -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", -] - -[[package]] -name = "nkeys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc522a19199a0795776406619aa6aa78e1e55690fbeb3181b8db5265fd0e89ce" -dependencies = [ - "data-encoding", - "ed25519", - "ed25519-dalek", - "getrandom", - "log", - "rand", - "signatory", -] - -[[package]] -name = "nohash-hasher" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "nuid" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc895af95856f929163a0aa20c26a78d26bfdc839f51b9d5aa7a5b79e52b7e83" -dependencies = [ - "rand", -] - -[[package]] -name = "num-bigint" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" -dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "crc32fast", - "hashbrown 0.14.5", - "indexmap 2.2.6", - "memchr", -] - -[[package]] -name = "oid-registry" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d" -dependencies = [ - "asn1-rs", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "open-fastrlp" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" -dependencies = [ - "arrayvec", - "auto_impl", - "bytes", - "ethereum-types", - "open-fastrlp-derive", -] - -[[package]] -name = "open-fastrlp-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" -dependencies = [ - "bytes", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2 0.10.8", -] - -[[package]] -name = "parity-scale-codec" -version = "3.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" -dependencies = [ - "arrayvec", - "bitvec", - "byte-slice-cast", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "serde", -] - -[[package]] -name = "parity-scale-codec-derive" -version = "3.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" -dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "parking" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.5", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest 0.10.7", - "hmac 0.12.1", -] - -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - -[[package]] -name = "pem" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" -dependencies = [ - "base64 0.13.1", -] - -[[package]] -name = "pem" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" -dependencies = [ - "base64 0.22.1", - "serde", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pest" -version = "2.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pharos" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" -dependencies = [ - "futures", - "rustc_version", -] - -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[package]] -name = "platforms" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" - -[[package]] -name = "polling" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.3.9", - "pin-project-lite", - "rustix", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "portable-atomic" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" - -[[package]] -name = "postcard" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" -dependencies = [ - "cobs", - "embedded-io", - "heapless", - "serde", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "pprof" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978385d59daf9269189d052ca8a84c1acfd0715c0599a5d5188d4acc078ca46a" -dependencies = [ - "backtrace", - "cfg-if", - "findshlibs", - "libc", - "log", - "nix 0.26.4", - "once_cell", - "parking_lot", - "smallvec", - "symbolic-demangle", - "tempfile", - "thiserror", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "prettyplease" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" -dependencies = [ - "proc-macro2", - "syn 2.0.66", -] - -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - -[[package]] -name = "primitive-types" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" -dependencies = [ - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "uint", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" -dependencies = [ - "toml_edit 0.21.1", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prometheus-client" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ca959da22a332509f2a73ae9e5f23f9dcfc31fd3a54d71f159495bd5909baa" -dependencies = [ - "dtoa", - "itoa", - "parking_lot", - "prometheus-client-derive-encode", -] - -[[package]] -name = "prometheus-client-derive-encode" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "proptest" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" -dependencies = [ - "bitflags 2.5.0", - "lazy_static", - "num-traits", - "rand", - "rand_chacha", - "rand_xorshift", - "regex-syntax 0.8.3", - "unarray", -] - -[[package]] -name = "prost" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" -dependencies = [ - "anyhow", - "itertools 0.10.5", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "psm" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" -dependencies = [ - "cc", -] - -[[package]] -name = "pyroscope" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8a53ce01af1087eaeee6ce7c4fbf50ea4040ab1825c0115c4bafa039644ba9" -dependencies = [ - "json", - "libc", - "libflate", - "log", - "names", - "prost", - "reqwest", - "thiserror", - "url", - "winapi", -] - -[[package]] -name = "pyroscope_pprofrs" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f010b2a981a7f8449a650f25f309e520b5206ea2d89512dcb146aaa5518ff4" -dependencies = [ - "log", - "pprof", - "pyroscope", - "thiserror", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quick-protobuf" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" -dependencies = [ - "byteorder", -] - -[[package]] -name = "quick-protobuf-codec" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15a0580ab32b169745d7a39db2ba969226ca16738931be152a3209b409de2474" -dependencies = [ - "asynchronous-codec 0.7.0", - "bytes", - "quick-protobuf", - "thiserror", - "unsigned-varint 0.8.0", -] - -[[package]] -name = "quinn" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904e3d3ba178131798c6d9375db2b13b34337d489b089fc5ba0825a2ff1bee73" -dependencies = [ - "bytes", - "futures-io", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls 0.23.8", - "thiserror", - "tokio", - "tracing", -] - -[[package]] -name = "quinn-proto" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e974563a4b1c2206bbc61191ca4da9c22e4308b4c455e8906751cc7828393f08" -dependencies = [ - "bytes", - "rand", - "ring 0.17.8", - "rustc-hash", - "rustls 0.23.8", - "slab", - "thiserror", - "tinyvec", - "tracing", -] - -[[package]] -name = "quinn-udp" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4f0def2590301f4f667db5a77f9694fb004f82796dc1a8b1508fafa3d0e8b72" -dependencies = [ - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "rcgen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c4f3084aa3bc7dfbba4eff4fab2a54db4324965d8872ab933565e6fbd83bc6" -dependencies = [ - "pem 3.0.4", - "ring 0.16.20", - "time", - "yasna", -] - -[[package]] -name = "redox_syscall" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" -dependencies = [ - "bitflags 2.5.0", -] - -[[package]] -name = "redox_users" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - -[[package]] -name = "regalloc2" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" -dependencies = [ - "hashbrown 0.13.2", - "log", - "rustc-hash", - "slice-group-by", - "smallvec", -] - -[[package]] -name = "regex" -version = "1.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.8.3", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" - -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-rustls 0.24.1", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", - "winreg", -] - -[[package]] -name = "resolv-conf" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac 0.12.1", - "subtle", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin 0.9.8", - "untrusted 0.9.0", - "windows-sys 0.52.0", -] - -[[package]] -name = "ripemd" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "rle-decode-fast" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" - -[[package]] -name = "rlp" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" -dependencies = [ - "bytes", - "rlp-derive", - "rustc-hex", -] - -[[package]] -name = "rlp-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "rocksdb" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb6f170a4041d50a0ce04b0d2e14916d6ca863ea2e422689a5b694395d299ffe" -dependencies = [ - "libc", - "librocksdb-sys", -] - -[[package]] -name = "rtnetlink" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" -dependencies = [ - "futures", - "log", - "netlink-packet-route", - "netlink-proto", - "nix 0.24.3", - "thiserror", - "tokio", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hex" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom", -] - -[[package]] -name = "rustix" -version = "0.38.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" -dependencies = [ - "bitflags 2.5.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring 0.17.8", - "rustls-webpki 0.101.7", - "sct", -] - -[[package]] -name = "rustls" -version = "0.23.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79adb16721f56eb2d843e67676896a61ce7a0fa622dc18d3e372477a029d2740" -dependencies = [ - "once_cell", - "ring 0.17.8", - "rustls-pki-types", - "rustls-webpki 0.102.4", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" -dependencies = [ - "openssl-probe", - "rustls-pemfile 2.1.2", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - -[[package]] -name = "rustls-pemfile" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" -dependencies = [ - "base64 0.22.1", - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" - -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", -] - -[[package]] -name = "rustls-webpki" -version = "0.102.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" -dependencies = [ - "ring 0.17.8", - "rustls-pki-types", - "untrusted 0.9.0", -] - -[[package]] -name = "rustversion" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" - -[[package]] -name = "rw-stream-sink" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1" -dependencies = [ - "futures", - "pin-project", - "static_assertions", -] - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scale-info" -version = "2.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" -dependencies = [ - "cfg-if", - "derive_more", - "parity-scale-codec", - "scale-info-derive", -] - -[[package]] -name = "scale-info-derive" -version = "2.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" -dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", -] - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - -[[package]] -name = "secp256k1" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4124a35fe33ae14259c490fd70fa199a32b9ce9502f2ee6bc4f81ec06fa65894" -dependencies = [ - "rand", - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" -dependencies = [ - "cc", -] - -[[package]] -name = "secrecy" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" -dependencies = [ - "zeroize", -] - -[[package]] -name = "security-framework" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" -dependencies = [ - "bitflags 2.5.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] - -[[package]] -name = "send_wrapper" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" - -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" - -[[package]] -name = "serde" -version = "1.0.203" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.203" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "serde_json" -version = "1.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_nanos" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93142f0367a4cc53ae0fead1bcda39e85beccfad3dcd717656cacab94b12985" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "serde_spanned" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.2.6", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" -dependencies = [ - "darling 0.20.9", - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "sha-1" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest 0.10.7", - "keccak", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "signatory" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e303f8205714074f6068773f0e29527e0453937fe837c9717d066635b65f31" -dependencies = [ - "pkcs8", - "rand_core", - "signature", - "zeroize", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest 0.10.7", - "rand_core", -] - -[[package]] -name = "simple_asn1" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror", - "time", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "slice-group-by" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "snow" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" -dependencies = [ - "aes-gcm", - "blake2", - "chacha20poly1305", - "curve25519-dalek", - "rand_core", - "ring 0.17.8", - "rustc_version", - "sha2 0.10.8", - "subtle", -] - -[[package]] -name = "socket2" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "soketto" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" -dependencies = [ - "base64 0.13.1", - "bytes", - "futures", - "httparse", - "log", - "rand", - "sha-1", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sptr" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" -dependencies = [ - "strum_macros 0.24.3", -] - -[[package]] -name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" -dependencies = [ - "strum_macros 0.25.3", -] - -[[package]] -name = "strum" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" -dependencies = [ - "strum_macros 0.26.2", -] - -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", -] - -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.66", -] - -[[package]] -name = "strum_macros" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.66", -] - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "symbolic-common" -version = "12.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cccfffbc6bb3bb2d3a26cd2077f4d055f6808d266f9d4d158797a4c60510dfe" -dependencies = [ - "debugid", - "memmap2", - "stable_deref_trait", - "uuid", -] - -[[package]] -name = "symbolic-demangle" -version = "12.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a99812da4020a67e76c4eb41f08c87364c14170495ff780f30dd519c221a68" -dependencies = [ - "cpp_demangle", - "rustc-demangle", - "symbolic-common", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tai64" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7401421025f4132e6c1f7af5e7f8287383969f36e6628016cd509b8d3da9dc" -dependencies = [ - "serde", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "target-lexicon" -version = "0.12.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" - -[[package]] -name = "tempfile" -version = "3.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" -dependencies = [ - "cfg-if", - "fastrand", - "rustix", - "windows-sys 0.52.0", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" - -[[package]] -name = "thiserror" -version = "1.0.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "tikv-jemalloc-sys" -version = "0.5.4+5.3.0-patched" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9402443cb8fd499b6f327e40565234ff34dbda27460c5b47db0db77443dd85d1" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "tikv-jemallocator" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965fe0c26be5c56c94e38ba547249074803efd52adfb66de62107d95aab3eaca" -dependencies = [ - "libc", - "tikv-jemalloc-sys", -] - -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "num_cpus", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-macros" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "tokio-rayon" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cf33a76e0b1dd03b778f83244137bd59887abf25c0e87bc3e7071105f457693" -dependencies = [ - "rayon", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" -dependencies = [ - "rustls 0.23.8", - "rustls-pki-types", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" -dependencies = [ - "futures-util", - "log", - "rustls 0.21.12", - "tokio", - "tokio-rustls 0.24.1", - "tungstenite", - "webpki-roots", -] - -[[package]] -name = "tokio-util" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "toml" -version = "0.8.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.13", -] - -[[package]] -name = "toml_datetime" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.2.6", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap 2.2.6", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" -dependencies = [ - "indexmap 2.2.6", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.6.9", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" -dependencies = [ - "bitflags 1.3.2", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-range-header", - "pin-project-lite", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "futures", - "futures-task", - "pin-project", - "tracing", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "tryhard" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9f0a709784e86923586cff0d872dba54cd2d2e116b3bc57587d15737cfce9d" -dependencies = [ - "futures", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand", - "rustls 0.21.12", - "sha1", - "thiserror", - "url", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-width" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - -[[package]] -name = "unsigned-varint" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" -dependencies = [ - "asynchronous-codec 0.6.2", - "bytes", -] - -[[package]] -name = "unsigned-varint" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" -dependencies = [ - "form_urlencoded", - "idna 0.5.0", - "percent-encoding", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - -[[package]] -name = "uuid" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" -dependencies = [ - "getrandom", -] - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.66", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" - -[[package]] -name = "wasm-encoder" -version = "0.41.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "972f97a5d8318f908dded23594188a90bcd09365986b1163e66d70170e5287ae" -dependencies = [ - "leb128", -] - -[[package]] -name = "wasmparser" -version = "0.121.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab" -dependencies = [ - "bitflags 2.5.0", - "indexmap 2.2.6", - "semver", -] - -[[package]] -name = "wasmtime" -version = "18.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69472708b96ee90579a482bdbb908ce97e53a9e5ebbcab59cc29c3977bcab512" -dependencies = [ - "anyhow", - "bincode", - "bumpalo", - "cfg-if", - "gimli", - "indexmap 2.2.6", - "libc", - "log", - "object", - "once_cell", - "paste", - "rayon", - "rustix", - "serde", - "serde_derive", - "serde_json", - "target-lexicon", - "wasmparser", - "wasmtime-cache", - "wasmtime-cranelift", - "wasmtime-environ", - "wasmtime-jit-icache-coherence", - "wasmtime-runtime", - "windows-sys 0.52.0", -] - -[[package]] -name = "wasmtime-asm-macros" -version = "18.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86292d6a9bf30c669582a40c4a4b8e0b8640e951f3635ee8e0acf7f87809961e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "wasmtime-cache" -version = "18.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a180017db1233c902b992fea9484640d265f2fedf03db60eed57894cb2effcc" -dependencies = [ - "anyhow", - "base64 0.21.7", - "bincode", - "directories-next", - "log", - "rustix", - "serde", - "serde_derive", - "sha2 0.10.8", - "toml 0.5.11", - "windows-sys 0.52.0", - "zstd", -] - -[[package]] -name = "wasmtime-cranelift" -version = "18.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b57d58e220ae223855c5d030ef20753377bc716d0c81b34c1fe74c9f44268774" -dependencies = [ - "anyhow", - "cfg-if", - "cranelift-codegen", - "cranelift-control", - "cranelift-entity", - "cranelift-frontend", - "cranelift-native", - "cranelift-wasm", - "gimli", - "log", - "object", - "target-lexicon", - "thiserror", - "wasmparser", - "wasmtime-cranelift-shared", - "wasmtime-environ", - "wasmtime-versioned-export-macros", -] - -[[package]] -name = "wasmtime-cranelift-shared" -version = "18.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba2cfdfdbde42f0f3baeddb62f3555524dee9f836c96da8d466e299f75f5eee" -dependencies = [ - "anyhow", - "cranelift-codegen", - "cranelift-control", - "cranelift-native", - "gimli", - "object", - "target-lexicon", - "wasmtime-environ", -] - -[[package]] -name = "wasmtime-environ" -version = "18.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abbf3075d9ee7eb1263dc67949aced64d0f0bf27be8098d34d8e5826cf0ff0f2" -dependencies = [ - "anyhow", - "bincode", - "cranelift-entity", - "gimli", - "indexmap 2.2.6", - "log", - "object", - "serde", - "serde_derive", - "target-lexicon", - "thiserror", - "wasmparser", - "wasmtime-types", -] - -[[package]] -name = "wasmtime-jit-icache-coherence" -version = "18.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dacd2aa30fb20fd8cd0eb4e664024a1ab28a02958529fa05bf52117532a098fc" -dependencies = [ - "cfg-if", - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "wasmtime-runtime" -version = "18.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d14e97c4bb36d91bcdd194745446d595e67ce8b89916806270fdbee640c747fd" -dependencies = [ - "anyhow", - "cc", - "cfg-if", - "indexmap 2.2.6", - "libc", - "log", - "mach", - "memfd", - "memoffset", - "paste", - "psm", - "rustix", - "sptr", - "wasm-encoder", - "wasmtime-asm-macros", - "wasmtime-environ", - "wasmtime-versioned-export-macros", - "wasmtime-wmemcheck", - "windows-sys 0.52.0", -] - -[[package]] -name = "wasmtime-types" -version = "18.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "530b94c627a454d24f520173d3145112d1b807c44c82697a57e1d8e28390cde4" -dependencies = [ - "cranelift-entity", - "serde", - "serde_derive", - "thiserror", - "wasmparser", -] - -[[package]] -name = "wasmtime-versioned-export-macros" -version = "18.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5399c175ddba4a471b9da45105dea3493059d52b2d54860eadb0df04c813948d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "wasmtime-wmemcheck" -version = "18.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1711f429111e782fac0537e0b3eb2ab6f821613cf1ec3013f2a0ff3fde41745" - -[[package]] -name = "web-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - -[[package]] -name = "widestring" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" -dependencies = [ - "windows-core 0.51.1", - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-core" -version = "0.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.5", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.5", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" -dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - -[[package]] -name = "ws_stream_wasm" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" -dependencies = [ - "async_io_stream", - "futures", - "js-sys", - "log", - "pharos", - "rustc_version", - "send_wrapper 0.6.0", - "thiserror", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "x25519-dalek" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" -dependencies = [ - "curve25519-dalek", - "rand_core", - "serde", - "zeroize", -] - -[[package]] -name = "x509-parser" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" -dependencies = [ - "asn1-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "rusticata-macros", - "thiserror", - "time", -] - -[[package]] -name = "xml-rs" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" - -[[package]] -name = "xmltree" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" -dependencies = [ - "xml-rs", -] - -[[package]] -name = "yamux" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed0164ae619f2dc144909a9f082187ebb5893693d8c0196e8085283ccd4b776" -dependencies = [ - "futures", - "log", - "nohash-hasher", - "parking_lot", - "pin-project", - "rand", - "static_assertions", -] - -[[package]] -name = "yamux" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f97202f6b125031b95d83e01dc57292b529384f80bfae4677e4bbc10178cf72" -dependencies = [ - "futures", - "instant", - "log", - "nohash-hasher", - "parking_lot", - "pin-project", - "rand", - "static_assertions", -] - -[[package]] -name = "yasna" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" -dependencies = [ - "time", -] - -[[package]] -name = "zerocopy" -version = "0.7.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/crates/fuel-streams-publisher/src/lib.rs b/crates/fuel-streams-publisher/src/lib.rs deleted file mode 100644 index e2ba33f7..00000000 --- a/crates/fuel-streams-publisher/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod cli; -pub mod publisher; -pub mod server; -pub mod telemetry; -pub use publisher::*; - -#[cfg(test)] -#[macro_use] -extern crate assert_matches; diff --git a/crates/fuel-streams-publisher/src/main.rs b/crates/fuel-streams-publisher/src/main.rs deleted file mode 100644 index b78b40be..00000000 --- a/crates/fuel-streams-publisher/src/main.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::{ - net::{Ipv4Addr, SocketAddrV4}, - sync::Arc, -}; - -use clap::Parser; -use fuel_streams_core::prelude::*; -use fuel_streams_publisher::{ - cli::Cli, - publisher::shutdown::ShutdownController, - server::{http::create_web_server, state::ServerState}, - telemetry::Telemetry, -}; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let cli = Cli::parse(); - let historical = cli.clone().historical; - - let fuel_core: Arc = - FuelCore::new(cli.fuel_core_config).await?; - fuel_core.start().await?; - - let telemetry = Telemetry::new().await?; - telemetry.start().await?; - - let publisher = fuel_streams_publisher::Publisher::new( - Arc::clone(&fuel_core), - cli.nats_url, - telemetry.clone(), - ) - .await?; - - let state = ServerState::new(publisher.clone()).await; - // create the actix webserver - let server_addr = std::net::SocketAddr::V4(SocketAddrV4::new( - Ipv4Addr::UNSPECIFIED, - cli.telemetry_port, - )); - let server = create_web_server(state, server_addr)?; - // get server handle - let server_handle = server.handle(); - // spawn the server in the background - tokio::spawn(async move { - if let Err(err) = server.await { - tracing::error!("Actix Web server error: {:?}", err); - } - }); - tracing::info!("Publisher started."); - - let shutdown_controller = ShutdownController::new().arc(); - let shutdown_token = shutdown_controller.get_token(); - ShutdownController::spawn_signal_listener(shutdown_controller); - - // run publisher until shutdown signal intercepted - if let Err(err) = publisher.run(shutdown_token, historical).await { - tracing::error!("Publisher encountered an error: {:?}", err); - } - tracing::info!("Publisher stopped"); - - // Await the Actix server shutdown - tracing::info!("Stopping actix server ..."); - server_handle.stop(true).await; - tracing::info!("Actix server stopped. Goodbye!"); - - Ok(()) -} diff --git a/crates/fuel-streams-publisher/src/publisher/blocks_streams.rs b/crates/fuel-streams-publisher/src/publisher/blocks_streams.rs deleted file mode 100644 index eb222cd2..00000000 --- a/crates/fuel-streams-publisher/src/publisher/blocks_streams.rs +++ /dev/null @@ -1,397 +0,0 @@ -use std::{cmp::max, sync::Arc}; - -use fuel_streams_core::prelude::*; -use futures::{ - stream::{self, BoxStream}, - StreamExt, - TryStreamExt, -}; -use tokio_stream::wrappers::BroadcastStream; - -pub fn build_blocks_stream<'a>( - fuel_streams: &'a Arc, - fuel_core: &'a Arc, - max_retained_blocks: u32, -) -> BoxStream<'a, anyhow::Result> { - #[derive(Debug, Default, Clone)] - struct State { - has_published_latest: bool, - has_reached_new_blocks_stream: bool, - } - let stream_state = State::default(); - - stream::try_unfold(stream_state, move |mut stream_state| { - let fuel_core = Arc::clone(fuel_core); - let fuel_streams = Arc::clone(fuel_streams); - - async move { - let latest_block_height = fuel_core.get_latest_block_height()?; - - let last_published_block_height = get_last_published_block_height( - fuel_streams, - latest_block_height, - max_retained_blocks, - ) - .await?; - - stream_state.has_published_latest = - latest_block_height == last_published_block_height; - - match stream_state { - State { - has_published_latest: false, - has_reached_new_blocks_stream: false, - } => { - let old_blocks_stream = stream::iter( - last_published_block_height..latest_block_height, - ) - .map({ - let fuel_core = fuel_core.clone(); - - move |height| { - fuel_core.get_sealed_block_by_height(height) - } - }) - .map(Ok) - .boxed(); - - anyhow::Ok(Some((old_blocks_stream, stream_state.clone()))) - } - State { - has_published_latest: true, - has_reached_new_blocks_stream: false, - } => { - let new_blocks_stream = - BroadcastStream::new(fuel_core.blocks_subscription()) - .map(|import_result| { - import_result - .expect("Must get ImporterResult") - .sealed_block - .clone() - }) - .map(Ok) - .boxed(); - - stream_state.has_reached_new_blocks_stream = true; - anyhow::Ok(Some((new_blocks_stream, stream_state.clone()))) - } - State { - has_reached_new_blocks_stream: true, - .. - } => anyhow::Ok(None), - } - } - }) - .try_flatten() - .boxed() -} - -async fn get_last_published_block_height( - fuel_streams: Arc, - latest_block_height: u32, - max_retained_blocks: u32, -) -> anyhow::Result { - let max_last_published_block_height = - max(0, latest_block_height as i32 - max_retained_blocks as i32) as u32; - - Ok(fuel_streams - .get_last_published_block() - .await? - .map(|block| block.height) - .map(|block_height: u32| { - max(block_height, max_last_published_block_height) - }) - .unwrap_or(max_last_published_block_height)) -} - -#[cfg(test)] -mod tests { - use std::{sync::Arc, time::Duration}; - - // TODO: Fix this leaky abstraction - use async_nats::{ - jetstream::stream::State as StreamState, - RequestErrorKind, - }; - use fuel_core::combined_database::CombinedDatabase; - use futures::StreamExt; - use mockall::{ - mock, - predicate::{self, *}, - }; - use tokio::{ - sync::broadcast, - time::{error::Elapsed, timeout}, - }; - - use super::*; - - #[tokio::test] - async fn test_no_old_blocks() { - let mut mock_fuel_core = MockFuelCoreLike::new(); - let mut mock_fuel_streams = MockFuelStreams::default(); - - mock_fuel_core - .expect_get_latest_block_height() - .returning(|| Ok(100)); - mock_fuel_streams - .expect_get_last_published_block() - .returning(|| Ok(Some(create_mock_block(100)))); // No old blocks - - mock_fuel_core - .expect_blocks_subscription() - .returning(move || { - let (empty_tx, rx) = broadcast::channel(1); - drop(empty_tx); - rx - }); - - let fuel_core: Arc = Arc::new(mock_fuel_core); - let fuel_streams: Arc = Arc::new(mock_fuel_streams); - - let mut stream = build_blocks_stream(&fuel_streams, &fuel_core, 10); - - assert!(stream.next().await.is_none()); - } - - #[tokio::test] - async fn test_old_blocks_stream() { - let mut mock_fuel_core = MockFuelCoreLike::new(); - let mut mock_fuel_streams = MockFuelStreams::default(); - - mock_fuel_core - .expect_get_latest_block_height() - .returning(|| Ok(105)); - mock_fuel_streams - .expect_get_last_published_block() - .returning(|| Ok(Some(create_mock_block(100)))); - for height in 100..105 { - mock_fuel_core - .expect_get_sealed_block_by_height() - .with(predicate::eq(height as u32)) - .returning(move |height| { - create_mock_fuel_core_sealed_block(height as u64) - }); - } - - let fuel_core: Arc = Arc::new(mock_fuel_core); - let fuel_streams: Arc = Arc::new(mock_fuel_streams); - - let mut stream = build_blocks_stream(&fuel_streams, &fuel_core, 10); - - for height in 100..105 { - let block = stream.next().await.unwrap().unwrap(); - assert_eq!(block.entity.header().consensus().height, height.into()); - } - } - - #[tokio::test] - async fn test_infinite_new_blocks_streams() { - let mut mock_fuel_core = MockFuelCoreLike::new(); - let mut mock_fuel_streams = MockFuelStreams::default(); - - mock_fuel_core - .expect_get_latest_block_height() - .returning(|| Ok(100)); - mock_fuel_streams - .expect_get_last_published_block() - .returning(|| Ok(Some(create_mock_block(100)))); // has published latest block already - - let (tx, _) = broadcast::channel(4); - - mock_fuel_core - .expect_blocks_subscription() - .returning(move || tx.clone().subscribe()); - - let fuel_core: Arc = Arc::new(mock_fuel_core); - let fuel_streams: Arc = Arc::new(mock_fuel_streams); - - let mut blocks_stream = - build_blocks_stream(&fuel_streams, &fuel_core, 10); - - assert_matches!( - timeout(Duration::from_secs(1), async { - blocks_stream.next().await - }) - .await, - Err(Elapsed { .. }) - ); - } - - #[tokio::test] - async fn test_new_blocks_streams_that_ends() { - let mut mock_fuel_core = MockFuelCoreLike::new(); - let mut mock_fuel_streams = MockFuelStreams::default(); - - mock_fuel_core - .expect_get_latest_block_height() - .returning(|| Ok(100)); - mock_fuel_streams - .expect_get_last_published_block() - .returning(|| Ok(Some(create_mock_block(100)))); // has published latest block already - - let (tx, _) = broadcast::channel(4); - - mock_fuel_core - .expect_blocks_subscription() - .returning(move || { - let tx = tx.clone(); - let subscription = tx.subscribe(); - - tx.send(create_mock_importer_result(101)).ok(); - tx.send(create_mock_importer_result(102)).ok(); - - subscription - }); - - let fuel_core: Arc = Arc::new(mock_fuel_core); - let fuel_streams: Arc = Arc::new(mock_fuel_streams); - - let mut stream = build_blocks_stream(&fuel_streams, &fuel_core, 10); - - for height in 101..=102 { - let block = stream.next().await.unwrap().unwrap(); - assert_eq!(block.entity.header().consensus().height, height.into()); - } - } - - #[tokio::test] - async fn test_get_last_published_block_height() { - let mut mock_fuel_streams = MockFuelStreams::default(); - - // Case 1: `get_last_published_block` returns Some(block) - mock_fuel_streams - .expect_get_last_published_block() - .returning(|| Ok(Some(create_mock_block(50)))); - - let fuel_streams = Arc::new(mock_fuel_streams); - - let result = - get_last_published_block_height(fuel_streams.clone(), 100, 40) - .await - .unwrap(); - assert_eq!(result, 60); // max(50, max_last_published_block_height=60) - - // Case 2: `get_last_published_block` returns None - let mut mock_fuel_streams = MockFuelStreams::default(); - mock_fuel_streams - .expect_get_last_published_block() - .returning(|| Ok(None)); - - let fuel_streams = Arc::new(mock_fuel_streams); - - let result = - get_last_published_block_height(fuel_streams.clone(), 100, 40) - .await - .unwrap(); - assert_eq!(result, 60); // No block, fallback to max_last_published_block_height - - // Case 3: `get_last_published_block` returns an error - let mut mock_fuel_streams = MockFuelStreams::default(); - mock_fuel_streams - .expect_get_last_published_block() - .returning(|| Err(anyhow::anyhow!("Error fetching block"))); - - let fuel_streams = Arc::new(mock_fuel_streams); - - let result = - get_last_published_block_height(fuel_streams.clone(), 100, 40) - .await; - assert!(result.is_err()); - assert_eq!(result.unwrap_err().to_string(), "Error fetching block"); - - // Case 4: `get_last_published_block` returns Some(block) where block.height < max_last_published_block_height - let mut mock_fuel_streams = MockFuelStreams::default(); - mock_fuel_streams - .expect_get_last_published_block() - .returning(|| Ok(Some(create_mock_block(30)))); - - let fuel_streams = Arc::new(mock_fuel_streams); - - let result = - get_last_published_block_height(fuel_streams.clone(), 100, 40) - .await - .unwrap(); - assert_eq!(result, 60); // max(30, max_last_published_block_height=60) - } - - mock! { - FuelCoreLike {} - - #[async_trait::async_trait] - impl FuelCoreLike for FuelCoreLike { - fn get_latest_block_height(&self) -> anyhow::Result; - fn get_sealed_block_by_height(&self, height: u32) -> FuelCoreSealedBlock; - fn blocks_subscription(&self) -> broadcast::Receiver; - async fn start(&self) -> anyhow::Result<()>; - fn is_started(&self) -> bool; - async fn await_synced_at_least_once(&self, historical: bool) -> anyhow::Result<()>; - async fn stop(&self); - fn fuel_service(&self) -> &fuel_core_bin::FuelService; - fn base_asset_id(&self) -> &FuelCoreAssetId; - fn chain_id(&self) -> &FuelCoreChainId; - fn database(&self) -> &CombinedDatabase; - async fn await_offchain_db_sync( - &self, - block_id: &FuelCoreBlockId, - ) -> anyhow::Result<()>; - fn get_receipts( - &self, - tx_id: &FuelCoreBytes32, - ) -> anyhow::Result>>; - fn get_tx_status( - &self, - tx_id: &FuelCoreBytes32, - ) -> anyhow::Result>; - } - } - - mock! { - FuelStreams {} - - #[async_trait::async_trait] - impl FuelStreamsExt for FuelStreams { - async fn get_last_published_block(&self) -> anyhow::Result>; - fn blocks(&self) -> &Stream; - fn transactions(&self) -> &Stream; - fn inputs(&self) -> &Stream; - fn outputs(&self) -> &Stream; - fn receipts(&self) -> &Stream; - fn utxos(&self) -> &Stream; - fn logs(&self) -> &Stream; - async fn get_consumers_and_state( - &self, - ) -> Result, StreamState)>, RequestErrorKind> ; - #[cfg(feature = "test-helpers")] - async fn is_empty(&self) -> bool; - } - } - - fn create_mock_importer_result(height: u64) -> FuelCoreImporterResult { - FuelCoreImporterResult { - shared_result: Arc::new(FuelCoreImportResult { - sealed_block: create_mock_fuel_core_sealed_block(height), - ..Default::default() - }), - #[cfg(feature = "test-helpers")] - changes: Arc::new(std::collections::HashMap::new()), - } - } - - fn create_mock_block(height: u64) -> Block { - Block::new( - &create_mock_fuel_core_sealed_block(height).entity, - FuelCoreConsensus::default().into(), - vec![], - ) - } - - fn create_mock_fuel_core_sealed_block(height: u64) -> FuelCoreSealedBlock { - let mut block = FuelCoreSealedBlock::default(); - - block.entity.header_mut().consensus_mut().height = - FuelCoreBlockHeight::new(height as u32); - - block - } -} diff --git a/crates/fuel-streams-publisher/src/publisher/mod.rs b/crates/fuel-streams-publisher/src/publisher/mod.rs deleted file mode 100644 index 6851b301..00000000 --- a/crates/fuel-streams-publisher/src/publisher/mod.rs +++ /dev/null @@ -1,166 +0,0 @@ -pub mod shutdown; - -mod blocks_streams; - -use std::sync::Arc; - -use anyhow::Context; -use blocks_streams::build_blocks_stream; -use fuel_streams_core::prelude::*; -use fuel_streams_executors::*; -use futures::StreamExt; - -use super::{ - shutdown::{ShutdownToken, GRACEFUL_SHUTDOWN_TIMEOUT}, - telemetry::Telemetry, -}; - -#[derive(Clone)] -pub struct Publisher { - pub fuel_core: Arc, - pub nats_client: NatsClient, - pub fuel_streams: Arc, - pub telemetry: Arc, -} - -impl Publisher { - pub async fn new( - fuel_core: Arc, - nats_url: String, - telemetry: Arc, - ) -> anyhow::Result { - let nats_client_opts = - NatsClientOpts::admin_opts(None).with_custom_url(nats_url); - let nats_client = NatsClient::connect(&nats_client_opts).await?; - let fuel_streams = Arc::new(FuelStreams::new(&nats_client).await); - - telemetry.record_streams_count( - fuel_core.chain_id(), - fuel_streams.subjects_wildcards().len(), - ); - - Ok(Publisher { - fuel_core, - fuel_streams, - nats_client, - telemetry, - }) - } - - pub fn is_healthy(&self) -> bool { - // TODO: Update this condition to include more health checks - self.fuel_core.is_started() && self.nats_client.is_connected() - } - - #[cfg(feature = "test-helpers")] - pub async fn default( - nats_client: &NatsClient, - fuel_core: Arc, - ) -> anyhow::Result { - Ok(Publisher { - fuel_core, - fuel_streams: Arc::new(FuelStreams::new(nats_client).await), - nats_client: nats_client.clone(), - telemetry: Telemetry::new().await?, - }) - } - - #[cfg(feature = "test-helpers")] - pub fn get_fuel_streams(&self) -> &Arc<(dyn FuelStreamsExt + 'static)> { - &self.fuel_streams - } - - async fn shutdown_services_with_timeout(&self) -> anyhow::Result<()> { - tokio::time::timeout(GRACEFUL_SHUTDOWN_TIMEOUT, async { - Publisher::flush_await_all_streams(&self.nats_client).await; - self.fuel_core.stop().await; - }) - .await?; - - Ok(()) - } - - async fn flush_await_all_streams(nats_client: &NatsClient) { - tracing::info!("Flushing in-flight messages to nats ..."); - match nats_client.nats_client.flush().await { - Ok(_) => { - tracing::info!("Flushed all streams successfully!"); - } - Err(e) => { - tracing::error!("Failed to flush all streams: {:?}", e); - } - } - } - - const MAX_RETAINED_BLOCKS: u32 = 100; - pub async fn run( - &self, - shutdown_token: ShutdownToken, - historical: bool, - ) -> anyhow::Result<()> { - tracing::info!("Awaiting FuelCore Sync..."); - - self.fuel_core - .await_synced_at_least_once(historical) - .await?; - - tracing::info!("FuelCore has synced successfully!"); - tracing::info!("Publishing started..."); - - let mut blocks_stream = build_blocks_stream( - &self.fuel_streams, - &self.fuel_core, - Self::MAX_RETAINED_BLOCKS, - ); - - loop { - tokio::select! { - Some(sealed_block) = blocks_stream.next() => { - let sealed_block = sealed_block.context("block streams failed to produce sealed block")?; - - tracing::info!("Processing blocks stream"); - - let fuel_core = &self.fuel_core; - let (block, block_producer) = - fuel_core.get_block_and_producer(sealed_block.to_owned()); - - // TODO: Avoid awaiting Offchain DB sync for all streams by grouping in their own service - fuel_core - .await_offchain_db_sync(&block.id()) - .await - .context("Failed to await Offchain DB sync")?; - - if let Err(err) = self.publish(sealed_block).await { - tracing::error!("Failed to publish block data: {}", err); - self.telemetry.record_failed_publishing(self.fuel_core.chain_id(), &block_producer); - } - - }, - shutdown = shutdown_token.wait_for_shutdown() => { - if shutdown { - tracing::info!("Shutdown signal received. Stopping services ..."); - self.shutdown_services_with_timeout().await?; - break; - } - }, - }; - } - - tracing::info!("Publishing stopped successfully!"); - Ok(()) - } - - async fn publish( - &self, - sealed_block: FuelCoreSealedBlock, - ) -> anyhow::Result<()> { - let metadata = Metadata::new(&self.fuel_core, &sealed_block); - let payload = Arc::new(BlockPayload::new( - Arc::clone(&self.fuel_core), - &sealed_block, - &metadata, - )?); - Executor::::process_all(payload, &self.fuel_streams).await?; - Ok(()) - } -} diff --git a/crates/fuel-streams-publisher/src/publisher/shutdown.rs b/crates/fuel-streams-publisher/src/publisher/shutdown.rs deleted file mode 100644 index 606fe97d..00000000 --- a/crates/fuel-streams-publisher/src/publisher/shutdown.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::{sync::Arc, time::Duration}; - -use tokio::{ - signal::unix::{signal, SignalKind}, - sync::{broadcast, OnceCell}, -}; - -// TODO: move into publisher module along with subjects - -pub const GRACEFUL_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(90); - -// First, let's create a ShutdownToken that can be shared -#[derive(Debug, Clone)] -pub struct ShutdownToken { - receiver: Arc>, -} - -impl ShutdownToken { - pub async fn wait_for_shutdown(&self) -> bool { - let mut rx = self.receiver.resubscribe(); - rx.recv().await.is_ok() - } -} - -#[derive(Debug)] -pub struct ShutdownController { - sender: broadcast::Sender<()>, - token: ShutdownToken, - shutdown_initiated: OnceCell<()>, -} - -impl Default for ShutdownController { - fn default() -> Self { - Self::new() - } -} - -impl ShutdownController { - pub fn new() -> Self { - let (sender, receiver) = broadcast::channel(1); - let token = ShutdownToken { - receiver: Arc::new(receiver), - }; - - Self { - sender, - token, - shutdown_initiated: OnceCell::new(), - } - } - pub fn arc(self) -> Arc { - Arc::new(self) - } - - pub fn get_token(&self) -> ShutdownToken { - self.token.clone() - } - - pub fn spawn_signal_listener(self: Arc) { - let sender = self.sender.clone(); - tokio::spawn(async move { - let mut sigint = - signal(SignalKind::interrupt()).expect("shutdown_listener"); - let mut sigterm = - signal(SignalKind::terminate()).expect("shutdown_listener"); - - tokio::select! { - _ = sigint.recv() => { - tracing::info!("Received SIGINT ..."); - let _ = sender.send(()); - } - _ = sigterm.recv() => { - tracing::info!("Received SIGTERM ..."); - let _ = sender.send(()); - } - } - }); - } - - pub fn initiate_shutdown( - &self, - ) -> Result> { - if self.shutdown_initiated.set(()).is_ok() { - self.sender.send(()) - } else { - Ok(0) // Shutdown already initiated - } - } -} diff --git a/crates/fuel-streams-publisher/src/server/http.rs b/crates/fuel-streams-publisher/src/server/http.rs deleted file mode 100644 index c86eea36..00000000 --- a/crates/fuel-streams-publisher/src/server/http.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::net::SocketAddr; - -use actix_cors::Cors; -use actix_server::Server; -use actix_web::{http, web, App, HttpResponse, HttpServer}; -use tracing_actix_web::TracingLogger; - -use super::state::ServerState; - -// We are keeping this low to give room for more -// Publishing processing power. This is fine since the -// the latency tolerance when fetching /health and /metrics -// is trivial -const MAX_WORKERS: usize = 2; - -pub fn create_web_server( - state: ServerState, - actix_server_addr: SocketAddr, -) -> anyhow::Result { - let server = HttpServer::new(move || { - // create cors - let cors = Cors::default() - .allow_any_origin() - .allowed_methods(vec!["GET", "POST"]) - .allowed_headers(vec![ - http::header::AUTHORIZATION, - http::header::ACCEPT, - ]) - .allowed_header(http::header::CONTENT_TYPE) - .max_age(3600); - - App::new() - .app_data(web::Data::new(state.clone())) - .wrap(TracingLogger::default()) - .wrap(cors) - .service(web::resource("/health").route(web::get().to( - |state: web::Data| async move { - if !state.is_healthy() { - return HttpResponse::ServiceUnavailable() - .body("Service Unavailable"); - } - HttpResponse::Ok().json(state.get_health().await) - }, - ))) - .service(web::resource("/metrics").route(web::get().to( - |state: web::Data| async move { - HttpResponse::Ok() - .body(state.publisher.telemetry.get_metrics().await) - }, - ))) - }) - .bind(actix_server_addr)? - .workers(MAX_WORKERS) - .shutdown_timeout(20) - .run(); - - Ok(server) -} - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use actix_web::{http, test, web, App, HttpResponse}; - use fuel_core::service::Config; - use fuel_core_bin::FuelService; - use fuel_core_services::State; - use fuel_streams_core::fuel_core_like::FuelCore; - - use crate::{ - server::state::{HealthResponse, ServerState}, - telemetry::Telemetry, - Publisher, - }; - - #[actix_web::test] - async fn test_health_check() { - let fuel_service = - FuelService::new_node(Config::local_node()).await.unwrap(); - assert_eq!(fuel_service.state(), State::Started); - - let telemetry = Telemetry::new().await.unwrap(); - - let fuel_core = FuelCore::from(fuel_service); - let publisher = Publisher::new( - fuel_core.arc(), - "nats://localhost:4222".to_string(), - telemetry, - ) - .await - .unwrap(); - let state = ServerState::new(publisher).await; - assert!(state.publisher.nats_client.is_connected()); - - let app = test::init_service( - App::new().app_data(web::Data::new(state.clone())).route( - "/health", - web::get().to(|state: web::Data| async move { - if !state.is_healthy() { - return HttpResponse::ServiceUnavailable() - .body("Service Unavailable"); - } - HttpResponse::Ok().json(state.get_health().await) - }), - ), - ) - .await; - - let uptime = Duration::from_secs(2); - tokio::time::sleep(uptime).await; - - let req = test::TestRequest::get().uri("/health").to_request(); - let resp = test::call_service(&app, req).await; - - assert_eq!(resp.status(), http::StatusCode::OK); - - let result: HealthResponse = test::read_body_json(resp).await; - assert!(result.uptime >= uptime.as_secs()); - assert!(!result.streams_info.is_empty()); - } -} diff --git a/crates/fuel-streams-publisher/src/server/mod.rs b/crates/fuel-streams-publisher/src/server/mod.rs deleted file mode 100644 index 8ebfa9ca..00000000 --- a/crates/fuel-streams-publisher/src/server/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod http; -pub mod state; diff --git a/crates/fuel-streams-publisher/src/telemetry/publisher.rs b/crates/fuel-streams-publisher/src/telemetry/publisher.rs deleted file mode 100644 index 338d46b5..00000000 --- a/crates/fuel-streams-publisher/src/telemetry/publisher.rs +++ /dev/null @@ -1,423 +0,0 @@ -use std::sync::Arc; - -use chrono::Utc; -use fuel_core::database::database_description::DatabaseHeight; -use fuel_streams_core::prelude::*; -use prometheus::{ - register_histogram_vec, - register_int_counter_vec, - register_int_gauge_vec, - HistogramVec, - IntCounterVec, - IntGaugeVec, - Registry, -}; - -#[derive(Clone, Debug)] -pub struct PublisherMetrics { - pub registry: Registry, - pub total_subs: IntGaugeVec, - pub total_published_messages: IntCounterVec, - pub total_failed_messages: IntCounterVec, - pub last_published_block_height: IntGaugeVec, - pub last_published_block_timestamp: IntGaugeVec, - pub published_messages_throughput: IntCounterVec, - pub publishing_latency_histogram: HistogramVec, - pub message_size_histogram: HistogramVec, - pub error_rates: IntCounterVec, -} - -impl Default for PublisherMetrics { - fn default() -> Self { - PublisherMetrics::new(None) - .expect("Failed to create default PublisherMetrics") - } -} - -impl PublisherMetrics { - pub fn new(prefix: Option) -> anyhow::Result { - let metric_prefix = prefix - .clone() - .map(|p| format!("{}_", p)) - .unwrap_or_default(); - - let total_subs = register_int_gauge_vec!( - format!("{}publisher_metrics_total_subscriptions", metric_prefix), - "A metric counting the number of active subscriptions", - &["chain_id"], - ) - .expect("metric must be created"); - - let total_published_messages = register_int_counter_vec!( - format!( - "{}publisher_metrics_total_published_messages", - metric_prefix - ), - "A metric counting the number of published messages", - &["chain_id", "block_producer"], - ) - .expect("metric must be created"); - - let total_failed_messages = register_int_counter_vec!( - format!("{}publisher_metrics_total_failed_messages", metric_prefix), - "A metric counting the number of unpublished and failed messages", - &["chain_id", "block_producer"], - ) - .expect("metric must be created"); - - let last_published_block_height = register_int_gauge_vec!( - format!( - "{}publisher_metrics_last_published_block_height", - metric_prefix - ), - "A metric that represents the last published block height", - &["chain_id", "block_producer"], - ) - .expect("metric must be created"); - - let last_published_block_timestamp = register_int_gauge_vec!( - format!( - "{}publisher_metrics_last_published_block_timestamp", - metric_prefix - ), - "A metric that represents the last published transaction timestamp", - &["chain_id", "block_producer"], - ) - .expect("metric must be created"); - - let published_messages_throughput = register_int_counter_vec!( - format!("{}publisher_metrics_published_messages_throughput", metric_prefix), - "A metric counting the number of published messages per subject wildcard", - &["chain_id", "block_producer", "subject_wildcard"], - ) - .expect("metric must be created"); - - // New histogram metric for block latency - let publishing_latency_histogram = register_histogram_vec!( - format!("{}publisher_metrics_block_latency_seconds", metric_prefix), - "Histogram of latencies between receiving and publishing a block", - &["chain_id", "block_producer", "subject_wildcard"], - // buckets for latency measurement (e.g., 0.1s, 0.5s, 1s, 5s, 10s) - vec![0.1, 0.5, 1.0, 5.0, 10.0], - ) - .expect("metric must be created"); - - let message_size_histogram = register_histogram_vec!( - format!("{}publisher_metrics_message_size_bytes", metric_prefix), - "Histogram of message sizes in bytes", - &["chain_id", "block_producer", "subject_wildcard"], - vec![100.0, 500.0, 1000.0, 5000.0, 10000.0, 100000.0, 1000000.0] - ) - .expect("metric must be created"); - - let error_rates = - register_int_counter_vec!( - format!("{}publisher_metrics_error_rates", metric_prefix), - "A metric counting errors or failures during message processing", - &["chain_id", "block_producer", "subject_wildcard", "error_type"], - ) - .expect("metric must be created"); - - let registry = - Registry::new_custom(prefix, None).expect("registry to be created"); - registry.register(Box::new(total_subs.clone()))?; - registry.register(Box::new(total_published_messages.clone()))?; - registry.register(Box::new(total_failed_messages.clone()))?; - registry.register(Box::new(last_published_block_height.clone()))?; - registry.register(Box::new(last_published_block_timestamp.clone()))?; - registry.register(Box::new(published_messages_throughput.clone()))?; - registry.register(Box::new(publishing_latency_histogram.clone()))?; - registry.register(Box::new(message_size_histogram.clone()))?; - registry.register(Box::new(error_rates.clone()))?; - - Ok(Self { - registry, - total_subs, - total_published_messages, - total_failed_messages, - last_published_block_height, - last_published_block_timestamp, - published_messages_throughput, - publishing_latency_histogram, - message_size_histogram, - error_rates, - }) - } -} - -#[allow(dead_code)] -// TODO: Will this be useful in the future? -pub fn add_block_metrics( - chain_id: &FuelCoreChainId, - block: &FuelCoreBlock, - block_producer: &Address, - metrics: &Arc, -) -> anyhow::Result> { - let latency = Utc::now().timestamp() - block.header().time().to_unix(); - - metrics - .publishing_latency_histogram - .with_label_values(&[ - &chain_id.to_string(), - &block_producer.to_string(), - BlocksSubject::WILDCARD, - ]) - .observe(latency as f64); - - metrics - .last_published_block_timestamp - .with_label_values(&[ - &chain_id.to_string(), - &block_producer.to_string(), - ]) - .set(block.header().time().to_unix()); - - metrics - .last_published_block_height - .with_label_values(&[ - &chain_id.to_string(), - &block_producer.to_string(), - ]) - .set(block.header().consensus().height.as_u64() as i64); - - Ok(metrics.to_owned()) -} - -#[cfg(test)] -mod tests { - use prometheus::{gather, Encoder, TextEncoder}; - - use super::*; - - impl PublisherMetrics { - pub fn random() -> Self { - use rand::{distributions::Alphanumeric, Rng}; - - let prefix = rand::thread_rng() - .sample_iter(&Alphanumeric) - .filter(|c| c.is_ascii_alphabetic()) - .take(6) - .map(char::from) - .collect(); - - PublisherMetrics::new(Some(prefix)) - .expect("Failed to create random PublisherMetrics") - } - } - - #[test] - fn test_total_published_messages_metric() { - let metrics = PublisherMetrics::random(); - - metrics - .total_published_messages - .with_label_values(&["chain_id_1", "block_producer_1"]) - .inc_by(5); - - let metric_families = gather(); - let mut buffer = Vec::new(); - let encoder = TextEncoder::new(); - encoder.encode(&metric_families, &mut buffer).unwrap(); - - let output = String::from_utf8(buffer.clone()).unwrap(); - - assert!(output.contains("publisher_metrics_total_published_messages")); - assert!(output.contains("chain_id_1")); - assert!(output.contains("block_producer_1")); - assert!(output.contains("5")); - } - - #[test] - fn test_latency_histogram_metric() { - let metrics = PublisherMetrics::random(); - - metrics - .publishing_latency_histogram - .with_label_values(&["chain_id_1", "block_producer_1", "topic_1"]) - .observe(0.75); - - let metric_families = gather(); - let mut buffer = Vec::new(); - let encoder = TextEncoder::new(); - encoder.encode(&metric_families, &mut buffer).unwrap(); - - let output = String::from_utf8(buffer.clone()).unwrap(); - - assert!(output.contains("publisher_metrics_block_latency_seconds")); - assert!(output.contains("chain_id_1")); - assert!(output.contains("block_producer_1")); - assert!(output.contains("topic_1")); - assert!(output.contains("0.75")); - } - - #[test] - fn test_message_size_histogram_metric() { - let metrics = PublisherMetrics::random(); - - metrics - .message_size_histogram - .with_label_values(&["chain_id_1", "block_producer_1", "topic_1"]) - .observe(1500.1); - - let metric_families = gather(); - let mut buffer = Vec::new(); - let encoder = TextEncoder::new(); - encoder.encode(&metric_families, &mut buffer).unwrap(); - - let output = String::from_utf8(buffer.clone()).unwrap(); - - assert!(output.contains("publisher_metrics_message_size_bytes")); - assert!(output.contains("chain_id_1")); - assert!(output.contains("block_producer_1")); - assert!(output.contains("topic_1")); - assert!(output.contains("1500.1")); - } - - #[test] - fn test_total_failed_messages_metric() { - let metrics = PublisherMetrics::random(); - - metrics - .total_failed_messages - .with_label_values(&["chain_id_1", "block_producer_1"]) - .inc_by(3); - - // Gather all the metrics - let metric_families = gather(); - let mut buffer = Vec::new(); - let encoder = TextEncoder::new(); - encoder.encode(&metric_families, &mut buffer).unwrap(); - - // Convert the gathered output to a string - let output = String::from_utf8(buffer.clone()).unwrap(); - - // Assert that the output contains the correct failed message metric - assert!(output.contains("publisher_metrics_total_failed_messages")); - assert!(output.contains("chain_id_1")); - assert!(output.contains("block_producer_1")); - assert!(output.contains("3")); - } - - #[test] - fn test_total_subs_metric() { - let metrics = PublisherMetrics::random(); - - metrics - .total_subs - .with_label_values(&["chain_id_1"]) - .set(10); - - let metric_families = gather(); - let mut buffer = Vec::new(); - let encoder = TextEncoder::new(); - encoder.encode(&metric_families, &mut buffer).unwrap(); - - let output = String::from_utf8(buffer.clone()).unwrap(); - - assert!(output.contains("publisher_metrics_total_subscriptions")); - assert!(output.contains("chain_id_1")); - assert!(output.contains("10")); - } - - #[test] - fn test_last_published_block_height_metric() { - let metrics = PublisherMetrics::random(); - - metrics - .last_published_block_height - .with_label_values(&["chain_id_1", "block_producer_1"]) - .set(1234); - - let metric_families = gather(); - let mut buffer = Vec::new(); - let encoder = TextEncoder::new(); - encoder.encode(&metric_families, &mut buffer).unwrap(); - - let output = String::from_utf8(buffer.clone()).unwrap(); - - assert!( - output.contains("publisher_metrics_last_published_block_height") - ); - assert!(output.contains("chain_id_1")); - assert!(output.contains("block_producer_1")); - assert!(output.contains("1234")); - } - - #[test] - fn test_last_published_block_timestamp_metric() { - let metrics = PublisherMetrics::random(); - - metrics - .last_published_block_timestamp - .with_label_values(&["chain_id_1", "block_producer_1"]) - .set(1633046400); - - let metric_families = gather(); - let mut buffer = Vec::new(); - let encoder = TextEncoder::new(); - encoder.encode(&metric_families, &mut buffer).unwrap(); - - let output = String::from_utf8(buffer.clone()).unwrap(); - - assert!( - output.contains("publisher_metrics_last_published_block_timestamp") - ); - assert!(output.contains("chain_id_1")); - assert!(output.contains("block_producer_1")); - assert!(output.contains("1633046400")); - } - - #[test] - fn test_published_messages_throughput_metric() { - let metrics = PublisherMetrics::random(); - - metrics - .published_messages_throughput - .with_label_values(&["chain_id_1", "block_producer_1", "topic_1"]) - .inc_by(10); - - let metric_families = gather(); - let mut buffer = Vec::new(); - let encoder = TextEncoder::new(); - encoder.encode(&metric_families, &mut buffer).unwrap(); - - let output = String::from_utf8(buffer.clone()).unwrap(); - - assert!( - output.contains("publisher_metrics_published_messages_throughput") - ); - assert!(output.contains("chain_id_1")); - assert!(output.contains("block_producer_1")); - assert!(output.contains("topic_1")); - assert!(output.contains("10")); - } - - #[test] - fn test_error_rates_metric() { - let metrics = PublisherMetrics::random(); - - metrics - .error_rates - .with_label_values(&[ - "chain_id_1", - "block_producer_1", - "topic_1", - "timeout", - ]) - .inc_by(1); - - let metric_families = gather(); - let mut buffer = Vec::new(); - let encoder = TextEncoder::new(); - encoder.encode(&metric_families, &mut buffer).unwrap(); - - let output = String::from_utf8(buffer.clone()).unwrap(); - - assert!(output.contains("publisher_metrics_error_rates")); - assert!(output.contains("chain_id_1")); - assert!(output.contains("block_producer_1")); - assert!(output.contains("topic_1")); - assert!(output.contains("timeout")); - assert!(output.contains("1")); - } -} diff --git a/crates/fuel-streams-storage/Cargo.toml b/crates/fuel-streams-storage/Cargo.toml new file mode 100644 index 00000000..2aefacfb --- /dev/null +++ b/crates/fuel-streams-storage/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "fuel-streams-storage" +description = "Srategies and adapters for storing fuel streams in transient and file storage systems (i.e. NATS and S3)" +authors = { workspace = true } +keywords = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } +rust-version = { workspace = true } + +[dependencies] +aws-config = { version = "1.5.10", features = ["behavior-version-latest"] } +aws-sdk-s3 = "1.65.0" +aws-smithy-runtime-api = "1.7.3" +aws-smithy-types = "=1.2.9" +dotenvy = { workspace = true } +rand = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } + +[dev-dependencies] +pretty_assertions = { workspace = true } +serde_json = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread", "macros", "test-util"] } + +[features] +default = [] +test-helpers = [] +bench-helpers = [] diff --git a/crates/fuel-streams-storage/src/lib.rs b/crates/fuel-streams-storage/src/lib.rs new file mode 100644 index 00000000..f5cf85aa --- /dev/null +++ b/crates/fuel-streams-storage/src/lib.rs @@ -0,0 +1,3 @@ +// TODO: Introduce Adapters for Transient and FileStorage (NATS and S3 clients would implement those) +pub mod s3; +pub use s3::*; diff --git a/crates/fuel-streams-storage/src/s3/mod.rs b/crates/fuel-streams-storage/src/s3/mod.rs new file mode 100644 index 00000000..ff459c80 --- /dev/null +++ b/crates/fuel-streams-storage/src/s3/mod.rs @@ -0,0 +1,5 @@ +mod s3_client; +mod s3_client_opts; + +pub use s3_client::*; +pub use s3_client_opts::*; diff --git a/crates/fuel-streams-storage/src/s3/s3_client.rs b/crates/fuel-streams-storage/src/s3/s3_client.rs new file mode 100644 index 00000000..dad2ec35 --- /dev/null +++ b/crates/fuel-streams-storage/src/s3/s3_client.rs @@ -0,0 +1,289 @@ +use aws_config::{BehaviorVersion, Region}; +use aws_sdk_s3::{ + config::http::HttpResponse, + operation::{ + create_bucket::CreateBucketError, + delete_bucket::DeleteBucketError, + delete_object::DeleteObjectError, + get_object::GetObjectError, + put_bucket_policy::PutBucketPolicyError, + put_object::PutObjectError, + put_public_access_block::PutPublicAccessBlockError, + }, + Client, +}; +use aws_smithy_runtime_api::client::result::SdkError; +use aws_smithy_types::byte_stream::error::Error as BytesStreamError; +use thiserror::Error; + +use super::s3_client_opts::S3ClientOpts; + +#[derive(Error, Debug)] +pub enum S3ClientError { + #[error("AWS SDK Create Error: {0}")] + CreateBucketError(#[from] SdkError), + #[error("AWS SDK Delete bucket Error: {0}")] + DeleteBucketError(#[from] SdkError), + #[error("AWS SDK Put Error: {0}")] + PutObjectError(#[from] SdkError), + #[error("AWS SDK Get Error: {0}")] + GetObjectError(#[from] SdkError), + #[error("Error aggregating bytes from S3: {0}")] + BuildObjectAfterGettingError(#[from] BytesStreamError), + #[error("AWS SDK Delete object Error: {0}")] + DeleteObjectError(#[from] SdkError), + #[error("Environment variable missing: {0}")] + MissingEnvVar(String), + #[error("Failed to stream objects because: {0}")] + StreamingError(String), + #[error("Failed to put bucket policy: {0}")] + PutBucketPolicyError(#[from] SdkError), + #[error("Failed to put public access block: {0}")] + PutPublicAccessBlockError( + #[from] SdkError, + ), + #[error("IO Error: {0}")] + IoError(#[from] std::io::Error), +} + +#[derive(Debug, Clone)] +pub struct S3Client { + client: Client, + bucket: String, +} + +impl S3Client { + pub async fn new(opts: &S3ClientOpts) -> Result { + let config = aws_config::defaults(BehaviorVersion::latest()) + .endpoint_url(opts.endpoint_url().to_string()) + .region(Region::new(opts.region().to_string())) + // TODO: Remove this once we have a proper S3 bucket created + // for now this is a workaround to avoid signing requests + .no_credentials() + .load() + .await; + + // Create S3 config without signing + let s3_config = aws_sdk_s3::config::Builder::from(&config) + .force_path_style(true) + .disable_s3_express_session_auth(true) + .build(); + + let client = aws_sdk_s3::Client::from_conf(s3_config); + let s3_client = Self { + client, + bucket: opts.bucket(), + }; + + Ok(s3_client) + } + + pub fn arc(self) -> std::sync::Arc { + std::sync::Arc::new(self) + } + + pub fn client(&self) -> &Client { + &self.client + } + + pub fn bucket(&self) -> &str { + &self.bucket + } + + pub async fn put_object( + &self, + key: &str, + object: Vec, + ) -> Result<(), S3ClientError> { + match self + .client + .put_object() + .bucket(&self.bucket) + .key(key) + .body(object.into()) + .send() + .await + { + Ok(_) => Ok(()), + Err(error) => match error { + SdkError::ServiceError(error) => { + tracing::error!( + "Failed to put object in S3 bucket={} key={}: {}", + self.bucket, + key, + error.err() + ); + Err(S3ClientError::PutObjectError(SdkError::ServiceError( + error, + ))) + } + SdkError::ConstructionFailure(error) => { + tracing::error!( + "Failed to construct S3 request for bucket={} key={}", + self.bucket, + key, + ); + Err(S3ClientError::PutObjectError( + SdkError::ConstructionFailure(error), + )) + } + SdkError::TimeoutError(error) => { + tracing::error!( + "Timeout putting object in S3 bucket={} key={}", + self.bucket, + key, + ); + Err(S3ClientError::PutObjectError(SdkError::TimeoutError( + error, + ))) + } + SdkError::DispatchFailure(error) => { + tracing::error!( + "Failed to dispatch S3 request for bucket={} key={}: {}", + self.bucket, + key, + error.as_connector_error().unwrap() + ); + Err(S3ClientError::PutObjectError( + SdkError::DispatchFailure(error), + )) + } + SdkError::ResponseError(error) => { + tracing::error!( + "Invalid response from S3 for bucket={} key={}", + self.bucket, + key, + ); + Err(S3ClientError::PutObjectError(SdkError::ResponseError( + error, + ))) + } + _ => { + tracing::error!( + "Failed to put object in S3 bucket={} key={}: {:?}", + self.bucket, + key, + error + ); + Err(S3ClientError::PutObjectError(error)) + } + }, + } + } + + pub async fn get_object( + &self, + key: &str, + ) -> Result, S3ClientError> { + let result = self + .client + .get_object() + .bucket(&self.bucket) + .key(key) + .send() + .await?; + + Ok(result.body.collect().await?.into_bytes().to_vec()) + } + + /// Delete a single object from S3. + pub async fn delete_object(&self, key: &str) -> Result<(), S3ClientError> { + self.client + .delete_object() + .bucket(&self.bucket) + .key(key) + .send() + .await?; + + Ok(()) + } + + #[cfg(any(test, feature = "test-helpers"))] + pub async fn create_bucket(&self) -> Result<(), S3ClientError> { + // Create bucket + self.client + .create_bucket() + .bucket(&self.bucket) + .send() + .await?; + + Ok(()) + } + + #[cfg(any(test, feature = "test-helpers"))] + pub async fn new_for_testing() -> Self { + dotenvy::dotenv().expect(".env file not found"); + + let s3_client = Self::new(&S3ClientOpts::new( + crate::S3Env::Local, + crate::S3Role::Admin, + )) + .await + .expect( + "S3Client creation failed. Check AWS Env vars and Localstack setup", + ); + + s3_client + .create_bucket() + .await + .expect("Failed to create bucket"); + + s3_client + } + + #[cfg(any(test, feature = "test-helpers"))] + pub async fn cleanup_after_testing(&self) { + let client = &self.client; + let bucket = &self.bucket; + + let objects = client + .list_objects_v2() + .bucket(bucket) + .send() + .await + .unwrap(); + + for object in objects.contents() { + if let Some(key) = object.key() { + client + .delete_object() + .bucket(bucket) + .key(key) + .send() + .await + .unwrap(); + } + } + + client.delete_bucket().bucket(bucket).send().await.unwrap(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_put_and_get_object() { + let s3_client = S3Client::new_for_testing().await; + + // Put object + let key = "test-key"; + let content = b"Hello, LocalStack!".to_vec(); + s3_client + .put_object(key, content.clone()) + .await + .expect("Failed to put object"); + + // Get object + let result = s3_client + .get_object(key) + .await + .expect("Failed to get object"); + + assert_eq!(result, content); + + // Cleanup + s3_client.cleanup_after_testing().await; + } +} diff --git a/crates/fuel-streams-storage/src/s3/s3_client_opts.rs b/crates/fuel-streams-storage/src/s3/s3_client_opts.rs new file mode 100644 index 00000000..468efa30 --- /dev/null +++ b/crates/fuel-streams-storage/src/s3/s3_client_opts.rs @@ -0,0 +1,120 @@ +use std::str::FromStr; + +#[derive(Debug, Clone, Default)] +pub enum S3Role { + Admin, + #[default] + Public, +} + +#[derive(Debug, Clone, Default)] +pub enum S3Env { + #[default] + Local, + Testnet, + Mainnet, +} + +impl FromStr for S3Env { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "local" => Ok(S3Env::Local), + "testnet" => Ok(S3Env::Testnet), + "mainnet" => Ok(S3Env::Mainnet), + _ => Err(format!("unknown S3 type: {}", s)), + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct S3ClientOpts { + pub s3_env: S3Env, + pub role: S3Role, + pub namespace: Option, +} + +impl S3ClientOpts { + pub fn new(s3_env: S3Env, role: S3Role) -> Self { + Self { + s3_env, + role, + namespace: None, + } + } + + pub fn from_env(role: Option) -> Self { + let s3_env = std::env::var("NETWORK") + .map(|s| S3Env::from_str(&s).unwrap_or_default()) + .unwrap_or_default(); + + Self { + s3_env, + role: role.unwrap_or_default(), + namespace: None, + } + } + + pub fn admin_opts() -> Self { + Self::from_env(Some(S3Role::Admin)) + } + + pub fn public_opts() -> Self { + Self::from_env(Some(S3Role::Public)) + } + + pub fn endpoint_url(&self) -> String { + match self.role { + S3Role::Admin => dotenvy::var("AWS_ENDPOINT_URL") + .expect("AWS_ENDPOINT_URL must be set for admin role"), + S3Role::Public => { + match self.s3_env { + S3Env::Local => "http://localhost:4566".to_string(), + S3Env::Testnet | S3Env::Mainnet => { + let bucket = self.bucket(); + let region = self.region(); + format!("https://{bucket}.s3-website-{region}.amazonaws.com") + } + } + } + } + } + + pub fn region(&self) -> String { + match &self.role { + S3Role::Admin => dotenvy::var("AWS_REGION") + .expect("AWS_REGION must be set for admin role"), + S3Role::Public => "us-east-1".to_string(), + } + } + + #[cfg(any(test, feature = "test-helpers"))] + pub fn with_random_namespace(mut self) -> Self { + let random_namespace = { + use rand::Rng; + let random_int: u32 = rand::thread_rng().gen(); + format!("namespace-{}", random_int) + }; + self.namespace = Some(random_namespace); + self + } + + pub fn bucket(&self) -> String { + if matches!(self.role, S3Role::Admin) { + return dotenvy::var("AWS_S3_BUCKET_NAME") + .expect("AWS_S3_BUCKET_NAME must be set for admin role"); + } + + let base_bucket = match self.s3_env { + S3Env::Local => "fuel-streams-local", + S3Env::Testnet => "fuel-streams-testnet", + S3Env::Mainnet => "fuel-streams", + }; + + self.namespace + .as_ref() + .map(|ns| format!("{base_bucket}-{ns}")) + .unwrap_or(base_bucket.to_string()) + } +} diff --git a/crates/fuel-streams/Cargo.toml b/crates/fuel-streams/Cargo.toml index 7a907181..654081a3 100644 --- a/crates/fuel-streams/Cargo.toml +++ b/crates/fuel-streams/Cargo.toml @@ -1,20 +1,27 @@ [package] name = "fuel-streams" description = "A library for working with streams of Fuel blockchain data" -authors = ["Fuel Labs "] -keywords = ["data-stream", "blockchain", "cryptocurrencies"] -edition = "2021" -homepage = "https://fuel.network/" -license = "Apache-2.0" -repository = "https://github.com/fuellabs/data-systems" -rust-version = "1.81" -version = "0.0.13" +authors = { workspace = true } +keywords = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } +rust-version = { workspace = true } [dependencies] displaydoc = { workspace = true } fuel-streams-core = { workspace = true } futures = { workspace = true } +reqwest = "0.12.9" +serde = { workspace = true } +serde_json = { workspace = true } +sv-webserver = { workspace = true } thiserror = { workspace = true } +tokio = { workspace = true } +tokio-tungstenite = "0.26.1" +url = "2.5.4" [dev-dependencies] tokio = { workspace = true } diff --git a/crates/fuel-streams/README.md b/crates/fuel-streams/README.md index c293a78e..9e6dbaa4 100644 --- a/crates/fuel-streams/README.md +++ b/crates/fuel-streams/README.md @@ -60,162 +60,106 @@ cargo add fuel-streams futures tokio Here are some examples to get you started with Fuel Streams: -### Subscribing to all new blocks +### Basic Connection and Subscription ```rust,no_run -use fuel_streams::types::FuelNetwork; -use fuel_streams::client::Client; -use fuel_streams::stream::{Stream, StreamEncoder}; -use fuel_streams::blocks::Block; +use fuel_streams::prelude::*; use futures::StreamExt; #[tokio::main] -async fn main() -> Result<(), fuel_streams::Error> { - let client = Client::connect(FuelNetwork::Local).await?; - let stream = fuel_streams::Stream::::new(&client).await; +async fn main() -> Result<(), Box> { + // Create a client and establish connection + let mut client = Client::new(FuelNetwork::Local).await?; + let mut connection = client.connect().await?; - let mut subscription = stream.subscribe().await?; - while let Some(bytes) = subscription.next().await { - let block = Block::decode(bytes.unwrap()).await; - println!("Received block: {:?}", block); - } - - Ok(()) -} -``` - -### Subscribing to all transactions (Filtering by block height) + println!("Listening for blocks..."); -```rust,no_run -use fuel_streams::types::FuelNetwork; -use fuel_streams::client::Client; -use fuel_streams::stream::{Filter, Stream, StreamEncoder, StreamConfig}; -use fuel_streams::transactions::{Transaction, TransactionKind, TransactionsSubject}; -use futures::StreamExt; - -#[tokio::main] -async fn main() -> Result<(), fuel_streams::Error> { - let client = Client::connect(FuelNetwork::Local).await?; - let mut stream = fuel_streams::Stream::::new(&client).await; + // Create a subject for all blocks + let subject = BlocksSubject::new(); - // Filter transactions from block height 5 - let filter = Filter::::build() - .with_block_height(Some(5.into())); - - let mut subscription = stream - .with_filter(filter) - .subscribe_with_config(StreamConfig::default()) + // Subscribe to blocks with last delivery policy + let mut stream = connection + .subscribe::(subject, DeliverPolicy::Last) .await?; - while let Some(message) = subscription.next().await { - let payload = message?.payload.clone(); - let transaction = Transaction::decode(payload.into()).await; - println!("Received transaction: {:?}", transaction); + while let Some(block) = stream.next().await { + println!("Received block: {:?}", block); } Ok(()) } ``` -## Advanced - -### `DeliverPolicy` - -The `DeliverPolicy` provides fine-grained control over message delivery in your stream. This powerful feature allows you to customize how and when messages are received. Below is an illustrative example demonstrating how to subscribe to all blocks from the first block until the last block in the stream: +### Custom Connection Options ```rust,no_run -use fuel_streams::types::FuelNetwork; -use fuel_streams::client::Client; -use fuel_streams::stream::{Stream, StreamConfig, StreamEncoder, Filter}; -use fuel_streams::blocks::{Block, BlocksSubject}; -use fuel_streams::types::DeliverPolicy; -use futures::StreamExt; +use fuel_streams::prelude::*; #[tokio::main] -async fn main() -> Result<(), fuel_streams::Error> { - let client = Client::connect(FuelNetwork::Local).await?; - let mut stream = fuel_streams::Stream::::new(&client).await; - - let filter = Filter::::build(); - let mut subscription = stream - .with_filter(filter) - .subscribe_with_config(StreamConfig { - // Set the deliver policy to `All` to receive all blocks - // from the first block until the last block in the stream - deliver_policy: DeliverPolicy::All, - }) - .await?; - - while let Some(message) = subscription.next().await { - let payload = message?.payload.clone(); - let block = Block::decode(payload.into()).await; - println!("Received block: {:?}", block); - } +async fn main() -> Result<(), Box> { + // Create client with custom connection options + let client = Client::with_opts(ConnectionOpts { + network: FuelNetwork::Local, + username: "custom_user".to_string(), + password: "custom_pass".to_string(), + }).await?; Ok(()) } ``` -Available `DeliverPolicy` options: +### Subject Types and Filtering -- `All`: Delivers all messages in the stream. -- `Last`: Delivers the last message for the selected subjects. -- `New`: Delivers only new messages that are received after the subscription is created. -- `ByStartSequence(u64)`: Delivers messages starting from a specific sequence number. -- `ByStartTime(DateTime)`: Delivers messages starting from a specific time. - -Choose the appropriate `DeliverPolicy` based on your application's requirements for historical data processing or real-time updates. - -### Filters - -Filters allow you to narrow down the data you receive from a stream based on specific criteria. This is particularly useful when you're only interested in a subset of the data. The `Stream` struct provides a `with_filter` method that allows you to apply filters to your subscription. - -Here's an example of how to use filters with a stream of transactions: +Each data type has its own subject builder for filtering. Here's an example using transaction filtering: ```rust,no_run -use fuel_streams::types::FuelNetwork; -use fuel_streams::client::Client; -use fuel_streams::stream::{Stream, StreamConfig, StreamEncoder, Filter}; -use fuel_streams::transactions::{Transaction, TransactionsSubject, TransactionKind}; -use fuel_streams::types::Address; +use fuel_streams::prelude::*; use futures::StreamExt; #[tokio::main] -async fn main() -> Result<(), fuel_streams::Error> { - let client = Client::connect(FuelNetwork::Local).await?; - let mut stream = fuel_streams::Stream::::new(&client).await; +async fn main() -> Result<(), Box> { + let mut client = Client::new(FuelNetwork::Testnet).await?; + let mut connection = client.connect().await?; - // Create a filter for transactions from a specific block height and kind - let filter = Filter::::build() - .with_block_height(Some(1000.into())) + println!("Listening for transactions..."); + + // Create a subject for script transactions + let subject = TransactionsSubject::new() .with_kind(Some(TransactionKind::Script)); - let mut subscription = stream - .with_filter(filter) - .subscribe_with_config(StreamConfig::default()) + // Subscribe to the filtered transaction stream + let mut stream = connection + .subscribe::(subject, DeliverPolicy::Last) .await?; - while let Some(message) = subscription.next().await { - let payload = message?.payload.clone(); - let transaction = Transaction::decode(payload.into()).await; - println!("Received filtered transaction: {:?}", transaction); + while let Some(transaction) = stream.next().await { + println!("Received transaction: {:?}", transaction); } Ok(()) } ``` -In this example, we're creating a filter that will only return transactions from a specific kind (`TransactionKind::Script`) and from a specific block height (1000). +Available subject builders include: + +- `BlocksSubject::new()` +- `TransactionsSubject::new()` +- `InputsSubject::new()` +- `OutputsSubject::new()` +- `LogsSubject::new()` +- `UtxosSubject::new()` -Available filter methods depend on the subject type. The project currently supports subjects for the following data types: +Each subject builder provides specific filtering methods relevant to its data type. For example, `TransactionsSubject` allows filtering by transaction kind using the `with_kind()` method. -- [Blocks](../fuel-streams-core/src/blocks/subjects.rs) -- [Transactions](../fuel-streams-core/src/transactions/subjects.rs) +### `DeliverPolicy` Options -Filters can be combined to create more specific queries. Each filter method narrows down the results further. +The `DeliverPolicy` enum provides control over message delivery in your subscriptions: -> [!NOTE] -> Remember that the effectiveness of filters depends on how the data is structured in the NATS streams. Filters are applied on the client side, so they can help reduce the amount of data your application needs to process, but they don't reduce the amount of data transferred over the network. +- `All`: Delivers all messages in the stream +- `Last`: Delivers only the last message for the selected subjects +- `New`: Delivers only new messages that arrive after subscription +- `ByStartSequence(u64)`: Delivers messages starting from a specific sequence number +- `ByStartTime(DateTime)`: Delivers messages starting from a specific time ## 🤝 Contributing diff --git a/crates/fuel-streams/src/client/client_impl.rs b/crates/fuel-streams/src/client/client_impl.rs index f3773e6c..e7bfdf11 100644 --- a/crates/fuel-streams/src/client/client_impl.rs +++ b/crates/fuel-streams/src/client/client_impl.rs @@ -1,73 +1,116 @@ -use fuel_streams_core::prelude::*; +use reqwest::{ + header::{ + ACCEPT, + AUTHORIZATION, + CONNECTION, + CONTENT_TYPE, + HOST, + SEC_WEBSOCKET_KEY, + SEC_WEBSOCKET_VERSION, + UPGRADE, + }, + Client as HttpClient, +}; +use tokio_tungstenite::tungstenite::{ + client::IntoClientRequest, + handshake::client::generate_key, +}; -use super::ClientError; +use super::{ + error::ClientError, + Connection, + ConnectionOpts, + LoginRequest, + LoginResponse, +}; +use crate::FuelNetwork; -/// A client for connecting to a NATS server. -/// -/// This struct represents a connected NATS client. #[derive(Debug, Clone)] pub struct Client { - /// The underlying NATS client connection. - pub conn: NatsClient, + pub opts: ConnectionOpts, + pub jwt_token: Option, } impl Client { - /// Connects to a NATS server using the provided URL. - /// - /// # Parameters - /// - /// * `network`: An enum variant representing the fuel network we are connecting to. - /// - /// # Returns - /// - /// Returns a `Result` containing the connected client on success, or an error on failure. - /// - /// # Examples - /// - /// ```no_run - /// use fuel_streams::client::{Client, FuelNetwork}; - /// - /// # async fn example() -> Result<(), fuel_streams::Error> { - /// let client = Client::connect(FuelNetwork::Local).await?; - /// # Ok(()) - /// # } - /// ``` - pub async fn connect(network: FuelNetwork) -> Result { - let opts = NatsClientOpts::new(Some(network)); - let conn = NatsClient::connect(&opts) - .await - .map_err(ClientError::ConnectionFailed)?; - Ok(Self { conn }) + pub async fn new(network: FuelNetwork) -> Result { + Self::with_opts(ConnectionOpts { + network, + ..Default::default() + }) + .await } - /// Connects to a NATS server using the provided options. - /// - /// # Parameters - /// - /// * `opts`: A reference to `NatsClientOpts` containing the connection options. - /// - /// # Returns - /// - /// Returns a `ConnectionResult` containing the connected client on success, or an error on failure. - /// - /// # Examples - /// - /// ```no_run - /// use fuel_streams::client::{Client, FuelNetwork}; - /// use fuel_streams_core::nats::NatsClientOpts; - /// - /// # async fn example() -> Result<(), fuel_streams::Error> { - /// let opts = NatsClientOpts::new(Some(FuelNetwork::Local)); - /// let client = Client::with_opts(&opts).await?; - /// # Ok(()) - /// # } - /// ``` - pub async fn with_opts( - opts: &NatsClientOpts, - ) -> Result { - let conn = NatsClient::connect(opts) - .await - .map_err(ClientError::ConnectionFailed)?; - Ok(Self { conn }) + pub async fn with_opts(opts: ConnectionOpts) -> Result { + let jwt_token = + Self::fetch_jwt(opts.network, &opts.username, &opts.password) + .await?; + Ok(Self { + opts, + jwt_token: Some(jwt_token), + }) + } + + pub async fn connect(&mut self) -> Result { + let ws_url = self.opts.network.to_ws_url().join("/api/v1/ws")?; + let host = ws_url + .host_str() + .ok_or_else(|| ClientError::HostParseFailed)?; + + let jwt_token = + self.jwt_token.clone().ok_or(ClientError::MissingJwtToken)?; + + let bearer_token = format!("Bearer {}", jwt_token); + let mut request = ws_url.as_str().into_client_request()?; + let headers_map = request.headers_mut(); + headers_map.insert(AUTHORIZATION, bearer_token.parse()?); + headers_map.insert(HOST, host.parse()?); + headers_map.insert(UPGRADE, "websocket".parse()?); + headers_map.insert(CONNECTION, "Upgrade".parse().unwrap()); + headers_map.insert(SEC_WEBSOCKET_KEY, generate_key().parse()?); + headers_map.insert(SEC_WEBSOCKET_VERSION, "13".parse()?); + Connection::new(request).await + } + + async fn fetch_jwt( + network: FuelNetwork, + username: &str, + password: &str, + ) -> Result { + let client = HttpClient::new(); + let json_body = serde_json::to_string(&LoginRequest { + username: username.to_string(), + password: password.to_string(), + })?; + + let api_url = network.to_web_url().join("/api/v1/jwt")?; + let response = client + .get(api_url) + .header(ACCEPT, "application/json") + .header(CONTENT_TYPE, "application/json") + .body(json_body) + .send() + .await?; + + if response.status().is_success() { + let json_body = response.json::().await?; + Ok(json_body.jwt_token) + } else { + Err(ClientError::ApiResponse( + response.error_for_status_ref().unwrap_err(), + )) + } + } + + pub async fn refresh_jwt_and_connect( + &mut self, + ) -> Result { + let jwt_token = Self::fetch_jwt( + self.opts.network, + &self.opts.username, + &self.opts.password, + ) + .await?; + self.jwt_token = Some(jwt_token); + self.connect().await } } diff --git a/crates/fuel-streams/src/client/connection.rs b/crates/fuel-streams/src/client/connection.rs new file mode 100644 index 00000000..808c519d --- /dev/null +++ b/crates/fuel-streams/src/client/connection.rs @@ -0,0 +1,139 @@ +use fuel_streams_core::{subjects::IntoSubject, Streamable}; +use futures::{ + stream::{SplitSink, SplitStream}, + SinkExt, + Stream, + StreamExt, +}; +use tokio::sync::RwLock; +use tokio_tungstenite::{ + connect_async, + tungstenite::{http::Request, protocol::Message}, + MaybeTlsStream, +}; + +use super::{ + error::ClientError, + types::{ClientMessage, DeliverPolicy, ServerMessage, SubscriptionPayload}, +}; +use crate::FuelNetwork; + +#[derive(Debug, Clone)] +pub struct ConnectionOpts { + pub network: FuelNetwork, + pub username: String, + pub password: String, +} + +impl Default for ConnectionOpts { + fn default() -> Self { + Self { + network: FuelNetwork::Local, + username: "admin".to_string(), + password: "admin".to_string(), + } + } +} + +type ReadStream = SplitStream< + tokio_tungstenite::WebSocketStream>, +>; +type WriteSink = RwLock< + SplitSink< + tokio_tungstenite::WebSocketStream< + MaybeTlsStream, + >, + Message, + >, +>; + +#[derive(Debug)] +pub struct Connection { + pub read_stream: ReadStream, + pub write_sink: WriteSink, +} + +impl Connection { + pub async fn new(req: Request<()>) -> Result { + let (socket, _response) = connect_async(req).await?; + let (write, read) = socket.split(); + + Ok(Self { + read_stream: read, + write_sink: RwLock::new(write), + }) + } + + async fn send_client_message( + &self, + message: ClientMessage, + ) -> Result<(), ClientError> { + let mut write_guard = self.write_sink.write().await; + let serialized = serde_json::to_vec(&message)?; + write_guard.send(Message::Binary(serialized.into())).await?; + Ok(()) + } + + pub async fn subscribe( + &mut self, + subject: impl IntoSubject, + deliver_policy: DeliverPolicy, + ) -> Result + '_ + Send + Unpin, ClientError> { + let message = ClientMessage::Subscribe(SubscriptionPayload { + wildcard: subject.parse(), + deliver_policy, + }); + self.send_client_message(message).await?; + + let stream = self.read_stream.by_ref().filter_map(|msg| async move { + match msg { + Ok(Message::Binary(bin)) => { + match serde_json::from_slice::(&bin) { + Ok(ServerMessage::Response(value)) => { + match serde_json::from_value::(value) { + Ok(parsed) => Some(parsed), + Err(e) => { + eprintln!("Failed to parse value: {:?}", e); + None + } + } + } + Ok(ServerMessage::Error(e)) => { + eprintln!("Server error: {}", e); + None + } + Ok(_) => None, + Err(e) => { + eprintln!("Unparsable server message: {:?}", e); + None + } + } + } + Ok(Message::Close(_)) => None, + Ok(msg) => { + println!("Received message: {:?}", msg); + None + } + Err(e) => { + eprintln!("WebSocket error: {:?}", e); + None + } + } + }); + + Ok(Box::pin(stream)) + } + + pub async fn unsubscribe( + &self, + subject: S, + deliver_policy: DeliverPolicy, + ) -> Result<(), ClientError> { + let message = ClientMessage::Unsubscribe(SubscriptionPayload { + wildcard: subject.parse(), + deliver_policy, + }); + self.send_client_message(message).await?; + Ok(()) + } +} diff --git a/crates/fuel-streams/src/client/error.rs b/crates/fuel-streams/src/client/error.rs index fa89767c..a4f78938 100644 --- a/crates/fuel-streams/src/client/error.rs +++ b/crates/fuel-streams/src/client/error.rs @@ -1,9 +1,35 @@ use displaydoc::Display as DisplayDoc; -use fuel_streams_core::nats::NatsError; use thiserror::Error; #[derive(Debug, Error, DisplayDoc)] pub enum ClientError { - /// Failed to establish connection with the NATS server: {0} - ConnectionFailed(#[from] NatsError), + /// Failed to convert JSON to string: {0} + JsonToString(#[from] serde_json::Error), + + /// Failed to parse URL: {0} + UrlParse(#[from] url::ParseError), + + /// Failed to API response: {0} + ApiResponse(#[from] reqwest::Error), + + /// Failed to connect to WebSocket: {0} + WebSocketConnect(#[from] tokio_tungstenite::tungstenite::Error), + + /// Invalid header value: {0} + InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue), + + /// Failed to parse host from URL + HostParseFailed, + + /// Missing JWT token + MissingJwtToken, + + /// Missing write sink + MissingWriteSink, + + /// Missing read stream + MissingReadStream, + + /// Missing WebSocket connection + MissingWebSocketConnection, } diff --git a/crates/fuel-streams/src/client/mod.rs b/crates/fuel-streams/src/client/mod.rs index 09883f4c..ba21bcb3 100644 --- a/crates/fuel-streams/src/client/mod.rs +++ b/crates/fuel-streams/src/client/mod.rs @@ -1,7 +1,9 @@ -mod client_impl; +pub mod client_impl; +pub mod connection; pub mod error; pub mod types; pub use client_impl::*; +pub use connection::*; pub use error::*; pub use types::*; diff --git a/crates/fuel-streams/src/client/types.rs b/crates/fuel-streams/src/client/types.rs index d3e2632e..938aa620 100644 --- a/crates/fuel-streams/src/client/types.rs +++ b/crates/fuel-streams/src/client/types.rs @@ -1,8 +1,9 @@ -pub use fuel_streams_core::nats::FuelNetwork; - -#[derive(Debug, Clone, Eq, PartialEq, Default)] -pub enum ClientStatus { - #[default] - Pending, - Connected, -} +pub use sv_webserver::server::{ + http::models::{LoginRequest, LoginResponse}, + ws::models::{ + ClientMessage, + DeliverPolicy, + ServerMessage, + SubscriptionPayload, + }, +}; diff --git a/crates/fuel-streams/src/error.rs b/crates/fuel-streams/src/error.rs index 96e0b86b..46b5cf8f 100644 --- a/crates/fuel-streams/src/error.rs +++ b/crates/fuel-streams/src/error.rs @@ -3,12 +3,6 @@ use thiserror::Error as ThisError; #[derive(Debug, ThisError, DisplayDoc)] pub enum Error { - /// An error occurred in the client - ClientError(#[from] crate::client::ClientError), - - /// An error occurred in the stream - StreamError(#[from] crate::stream::StreamError), - - /// Consuming messages error - MessagesError(#[from] fuel_streams_core::types::MessagesError), + /// WebSocket client error: {0} + Client(#[from] crate::client::error::ClientError), } diff --git a/crates/fuel-streams/src/lib.rs b/crates/fuel-streams/src/lib.rs index c86c099f..f4e965f8 100644 --- a/crates/fuel-streams/src/lib.rs +++ b/crates/fuel-streams/src/lib.rs @@ -2,20 +2,18 @@ pub mod client; pub mod error; -pub mod stream; +pub mod networks; +pub use client::*; pub use error::*; -pub use stream::*; +pub use networks::*; pub mod subjects { pub use fuel_streams_core::subjects::*; } pub mod types { - pub use fuel_streams_core::{ - nats::{types::*, FuelNetwork, NatsClientOpts}, - types::*, - }; + pub use fuel_streams_core::types::*; pub use crate::client::types::*; } @@ -40,5 +38,5 @@ export_module!(utxos, subjects, types); #[cfg(any(test, feature = "test-helpers"))] pub mod prelude { - pub use crate::{client::*, error::*, stream::*, types::*}; + pub use crate::{client::*, error::*, networks::*, subjects::*, types::*}; } diff --git a/crates/fuel-streams/src/networks/mod.rs b/crates/fuel-streams/src/networks/mod.rs new file mode 100644 index 00000000..98085076 --- /dev/null +++ b/crates/fuel-streams/src/networks/mod.rs @@ -0,0 +1,93 @@ +use std::str::FromStr; + +/// FuelStreamsNetworks; shortened to FuelNetworks for brievity and public familiarity +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Debug, Clone, Default)] +pub enum FuelNetworkUserRole { + Admin, + #[default] + Default, +} + +#[derive(Debug, Copy, Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum FuelNetwork { + #[default] + Local, + Testnet, + Mainnet, +} + +impl FromStr for FuelNetwork { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "local" => Ok(FuelNetwork::Local), + "testnet" => Ok(FuelNetwork::Testnet), + "mainnet" => Ok(FuelNetwork::Mainnet), + _ => Err(format!("unknown network: {}", s)), + } + } +} + +impl std::fmt::Display for FuelNetwork { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FuelNetwork::Local => write!(f, "local"), + FuelNetwork::Testnet => write!(f, "testnet"), + FuelNetwork::Mainnet => write!(f, "mainnet"), + } + } +} + +impl FuelNetwork { + pub fn load_from_env() -> Self { + match std::env::var("NETWORK").as_deref() { + Ok("testnet") => FuelNetwork::Testnet, + Ok("mainnet") => FuelNetwork::Mainnet, + _ => FuelNetwork::Local, + } + } + + pub fn to_nats_url(&self) -> String { + match self { + FuelNetwork::Local => "nats://localhost:4222", + FuelNetwork::Testnet => "nats://stream-testnet.fuel.network:4222", + FuelNetwork::Mainnet => "nats://stream.fuel.network:4222", + } + .to_string() + } + + pub fn to_web_url(&self) -> Url { + match self { + FuelNetwork::Local => { + Url::parse("http://localhost:9003").expect("working url") + } + FuelNetwork::Testnet => { + Url::parse("http://stream-testnet.fuel.network:9003") + .expect("working url") + } + FuelNetwork::Mainnet => { + Url::parse("http://stream.fuel.network:9003") + .expect("working url") + } + } + } + + pub fn to_ws_url(&self) -> Url { + match self { + FuelNetwork::Local => { + Url::parse("ws://0.0.0.0:9003").expect("working url") + } + FuelNetwork::Testnet => { + Url::parse("ws://stream-testnet.fuel.network:9003") + .expect("working url") + } + FuelNetwork::Mainnet => Url::parse("ws://stream.fuel.network:9003") + .expect("working url"), + } + } +} diff --git a/crates/fuel-streams/src/stream/error.rs b/crates/fuel-streams/src/stream/error.rs deleted file mode 100644 index 00e5857d..00000000 --- a/crates/fuel-streams/src/stream/error.rs +++ /dev/null @@ -1,17 +0,0 @@ -use displaydoc::Display as DisplayDoc; -use thiserror::Error; - -#[derive(Debug, Error, DisplayDoc)] -pub enum StreamError { - /// Failed to subscribe to the stream - Subscribe { - #[source] - source: fuel_streams_core::StreamError, - }, - - /// Failed to subscribe to the stream with custom options - SubscribeWithOpts { - #[source] - source: fuel_streams_core::StreamError, - }, -} diff --git a/crates/fuel-streams/src/stream/mod.rs b/crates/fuel-streams/src/stream/mod.rs deleted file mode 100644 index 511a2b19..00000000 --- a/crates/fuel-streams/src/stream/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod error; -mod stream_impl; - -pub use error::*; -pub use fuel_streams_core::stream::{ - StreamData, - StreamEncoder, - Streamable, - SubscribeConsumerConfig, -}; -pub use stream_impl::*; diff --git a/crates/fuel-streams/src/stream/stream_impl.rs b/crates/fuel-streams/src/stream/stream_impl.rs deleted file mode 100644 index c10d78ab..00000000 --- a/crates/fuel-streams/src/stream/stream_impl.rs +++ /dev/null @@ -1,212 +0,0 @@ -use fuel_streams_core::{ - prelude::{IntoSubject, SubjectBuildable}, - types::{DeliverPolicy, PullConsumerStream}, - Streamable, - SubscribeConsumerConfig, -}; - -use crate::{client::Client, stream::StreamError}; - -/// A filter for stream subjects. -/// -/// This struct is used to build and represent filters for stream subjects. -#[derive(Debug, Clone)] -pub struct Filter { - /// The subject to filter on. - pub subject: S, -} - -impl Filter { - /// Builds a new subject filter. - /// - /// # Returns - /// - /// Returns a new instance of the subject type `S`. - /// - /// # Examples - /// - /// ```no_run - /// use fuel_streams::stream::Filter; - /// use fuel_streams::blocks::BlocksSubject; - /// - /// let filter = Filter::::build(); - /// ``` - pub fn build() -> S { - S::new() - } -} - -/// Configuration options for a stream. -#[derive(Debug, Clone, Default)] -pub struct StreamConfig { - /// The delivery policy for the stream. - pub deliver_policy: DeliverPolicy, -} - -/// Represents a stream of data. -/// -/// This struct wraps a `fuel_streams_core::Stream` and provides methods for -/// subscribing to and filtering the stream. -#[derive(Debug, Clone)] -pub struct Stream { - stream: fuel_streams_core::Stream, - filter_subjects: Vec, -} - -impl Stream { - /// Creates a new `Stream` instance. - /// - /// # Parameters - /// - /// * `client`: A reference to a `Client` instance used to establish the connection. - /// - /// # Returns - /// - /// Returns a new `Stream` instance. - /// - /// # Examples - /// - /// ```no_run - /// use fuel_streams::types::FuelNetwork; - /// use fuel_streams::client::Client; - /// use fuel_streams::stream::Stream; - /// use fuel_streams::blocks::Block; - /// - /// # async fn example() -> Result<(), fuel_streams::Error> { - /// let client = Client::connect(FuelNetwork::Local).await?; - /// let stream = Stream::::new(&client).await; - /// # Ok(()) - /// # } - /// ``` - pub async fn new(client: &Client) -> Self { - let stream = - fuel_streams_core::Stream::::get_or_init(&client.conn).await; - Self { - stream, - filter_subjects: Vec::new(), - } - } - - /// Adds a filter to the stream. - /// - /// # Parameters - /// - /// * `filter`: An object that can be converted into a subject filter. - /// - /// # Returns - /// - /// Returns a reference to the `Stream` instance for method chaining. - /// - /// # Examples - /// - /// ```no_run - /// use fuel_streams::types::FuelNetwork; - /// use fuel_streams::client::Client; - /// use fuel_streams::stream::{Stream, Filter}; - /// use fuel_streams::blocks::{Block, BlocksSubject}; - /// use fuel_streams::types::Address; - /// - /// # async fn example() -> Result<(), fuel_streams::Error> { - /// # let client = Client::connect(FuelNetwork::Local).await?; - /// # let mut stream = Stream::::new(&client).await; - /// let filter = Filter::::build() - /// .with_producer(Some(Address::zeroed())) - /// .with_height(Some(5.into())); - /// stream.with_filter(filter); - /// # Ok(()) - /// # } - /// ``` - pub fn with_filter(&mut self, filter: impl IntoSubject) -> &Self { - self.filter_subjects.push(filter.parse()); - self - } - - /// Subscribes to the stream. - /// - /// # Returns - /// - /// Returns a `Result` containing a `futures::Stream` of byte vectors on success, - /// or a `StreamError` on failure. - /// - /// # Examples - /// - /// ```no_run - /// use fuel_streams::types::FuelNetwork; - /// use fuel_streams::client::Client; - /// use fuel_streams::stream::Stream; - /// use fuel_streams::blocks::Block; - /// - /// # async fn example() -> Result<(), fuel_streams::Error> { - /// # let client = Client::connect(FuelNetwork::Local).await?; - /// # let stream = Stream::::new(&client).await; - /// let subscription = stream.subscribe().await?; - /// # Ok(()) - /// # } - /// ``` - pub async fn subscribe( - &self, - ) -> Result>>, StreamError> { - // TODO: Why implicitly select a stream for the user? - // TODO: Should this be a combination of streams - self.stream - // TODO: Improve DX by ensuring the stream returns the streamable entity directly - .subscribe(S::WILDCARD_LIST[0]) - .await - .map_err(|source| StreamError::Subscribe { source }) - } - - /// Subscribes to the stream with custom configuration options. - /// - /// # Parameters - /// - /// * `opts`: A `StreamConfig` instance containing custom configuration options. - /// - /// # Returns - /// - /// Returns a `Result` containing a `PullConsumerStream` on success, - /// or a `StreamError` on failure. - /// - /// # Examples - /// - /// ```no_run - /// use fuel_streams::types::FuelNetwork; - /// use fuel_streams::client::Client; - /// use fuel_streams::stream::{Stream, StreamConfig}; - /// use fuel_streams::blocks::Block; - /// use fuel_streams::types::DeliverPolicy; - /// - /// # async fn example() -> Result<(), fuel_streams::Error> { - /// # let client = Client::connect(FuelNetwork::Local).await?; - /// # let stream = Stream::::new(&client).await; - /// let config = StreamConfig { - /// deliver_policy: DeliverPolicy::All, - /// }; - /// let subscription = stream.subscribe_with_config(config).await?; - /// # Ok(()) - /// # } - /// ``` - pub async fn subscribe_with_config( - &self, - opts: StreamConfig, - ) -> Result { - self.stream - .subscribe_consumer(SubscribeConsumerConfig { - deliver_policy: opts.deliver_policy, - filter_subjects: self.filter_subjects.to_owned(), - }) - .await - .map_err(|source| StreamError::SubscribeWithOpts { source }) - } - - /// Returns a reference to the underlying `fuel_streams_core::Stream`. - /// - /// This method is only available when compiled with the `test` or `test-helpers` feature. - /// - /// # Returns - /// - /// Returns a reference to the underlying `fuel_streams_core::Stream`. - #[cfg(any(test, feature = "test-helpers"))] - pub fn stream(&self) -> &fuel_streams_core::Stream { - &self.stream - } -} diff --git a/crates/sv-consumer/Cargo.toml b/crates/sv-consumer/Cargo.toml index ed27f473..50d1e17e 100644 --- a/crates/sv-consumer/Cargo.toml +++ b/crates/sv-consumer/Cargo.toml @@ -19,12 +19,14 @@ path = "src/main.rs" anyhow = { workspace = true } async-nats = { workspace = true } clap = { workspace = true } +dotenvy = { workspace = true } fuel-core = { workspace = true, default-features = false, features = ["p2p", "relayer", "rocksdb"] } fuel-streams-core = { workspace = true, features = ["test-helpers"] } fuel-streams-executors = { workspace = true, features = ["test-helpers"] } futures = { workspace = true } +num_cpus = { workspace = true } serde_json = { workspace = true } -sv-emitter = { workspace = true } +sv-publisher = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } tokio-util = "0.7.13" diff --git a/crates/sv-consumer/src/cli.rs b/crates/sv-consumer/src/cli.rs index 2f893567..6b51f6bf 100644 --- a/crates/sv-consumer/src/cli.rs +++ b/crates/sv-consumer/src/cli.rs @@ -5,17 +5,17 @@ pub struct Cli { /// Fuel Network to connect to. #[arg( long, - value_name = "NATS_CORE_URL", - env = "NATS_CORE_URL", + value_name = "NATS_URL", + env = "NATS_URL", default_value = "localhost:4222", - help = "NATS Core URL to connect to." + help = "NATS URL to connect to." )] - pub nats_core_url: String, + pub nats_url: String, #[arg( long, value_name = "NATS_PUBLISHER_URL", env = "NATS_PUBLISHER_URL", - default_value = "localhost:4222", + default_value = "localhost:4333", help = "NATS Publisher URL to connect to." )] pub nats_publisher_url: String, diff --git a/crates/sv-consumer/src/lib.rs b/crates/sv-consumer/src/lib.rs index ddf67e02..1cddb9ed 100644 --- a/crates/sv-consumer/src/lib.rs +++ b/crates/sv-consumer/src/lib.rs @@ -14,7 +14,7 @@ pub enum Client { impl Client { pub fn url(&self, cli: &cli::Cli) -> String { match self { - Client::Core => cli.nats_core_url.clone(), + Client::Core => cli.nats_url.clone(), Client::Publisher => cli.nats_publisher_url.clone(), } } @@ -23,9 +23,11 @@ impl Client { cli: &cli::Cli, ) -> Result, NatsError> { let url = self.url(cli); - let opts = NatsClientOpts::admin_opts(None) - .with_custom_url(url) - .with_domain("CORE".to_string()); + let opts = NatsClientOpts::admin_opts() + .with_url(url) + .with_domain("CORE".to_string()) + .with_user("admin".to_string()) + .with_password("admin".to_string()); Ok(Arc::new(NatsClient::connect(&opts).await?)) } } diff --git a/crates/sv-consumer/src/main.rs b/crates/sv-consumer/src/main.rs index d764048e..3e4fb6b2 100644 --- a/crates/sv-consumer/src/main.rs +++ b/crates/sv-consumer/src/main.rs @@ -1,4 +1,8 @@ -use std::{sync::Arc, time::Duration}; +use std::{ + env, + sync::{Arc, LazyLock}, + time::Duration, +}; use async_nats::jetstream::{ consumer::{ @@ -11,10 +15,11 @@ use async_nats::jetstream::{ use clap::Parser; use fuel_streams_core::prelude::*; use fuel_streams_executors::*; -use futures::StreamExt; +use futures::{future::try_join_all, stream::FuturesUnordered, StreamExt}; use sv_consumer::{cli::Cli, Client}; -use sv_emitter::shutdown::ShutdownController; +use sv_publisher::shutdown::ShutdownController; use tokio_util::sync::CancellationToken; +use tracing::level_filters::LevelFilter; use tracing_subscriber::fmt::time; #[derive(thiserror::Error, Debug)] @@ -37,18 +42,31 @@ pub enum ConsumerError { #[error("Failed to deserialize block payload from message: {0}")] Deserialization(#[from] serde_json::Error), - #[error("Failed to publish block to stream: {0}")] - Publish(#[from] ExecutorError), - #[error("Failed to decode UTF-8: {0}")] Utf8(#[from] std::str::Utf8Error), + + #[error("Failed to execute executor tasks: {0}")] + Executor(#[from] ExecutorError), + + #[error("Failed to join tasks: {0}")] + JoinTasks(#[from] tokio::task::JoinError), + + #[error("Failed to acquire semaphore: {0}")] + Semaphore(#[from] tokio::sync::AcquireError), + + #[error("Failed to setup S3 client: {0}")] + S3(#[from] S3ClientError), } #[tokio::main] async fn main() -> anyhow::Result<()> { // Initialize tracing subscriber tracing_subscriber::fmt() - .with_env_filter("sv_consumer=trace,fuel_streams_executors=trace") + .with_env_filter( + tracing_subscriber::EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy(), + ) .with_timer(time::LocalTime::rfc_3339()) .with_target(false) .with_thread_ids(false) @@ -57,6 +75,10 @@ async fn main() -> anyhow::Result<()> { .with_level(true) .init(); + if let Err(err) = dotenvy::dotenv() { + tracing::warn!("File .env not found: {:?}", err); + } + let cli = Cli::parse(); let shutdown = Arc::new(ShutdownController::new()); shutdown.clone().spawn_signal_handler(); @@ -79,6 +101,12 @@ async fn main() -> anyhow::Result<()> { Ok(()) } +async fn setup_s3() -> Result, ConsumerError> { + let s3_client_opts = S3ClientOpts::admin_opts(); + let s3_client = S3Client::new(&s3_client_opts).await?; + Ok(Arc::new(s3_client)) +} + async fn setup_nats( cli: &Cli, ) -> Result< @@ -111,24 +139,82 @@ async fn setup_nats( Ok((core_client, publisher_client, consumer)) } +pub static CONSUMER_MAX_THREADS: LazyLock = LazyLock::new(|| { + let available_cpus = num_cpus::get(); + env::var("CONSUMER_MAX_THREADS") + .ok() + .and_then(|val| val.parse().ok()) + .unwrap_or(available_cpus) +}); + async fn process_messages( cli: &Cli, token: &CancellationToken, ) -> Result<(), ConsumerError> { let (core_client, publisher_client, consumer) = setup_nats(cli).await?; + let s3_client = setup_s3().await?; let (_, publisher_stream) = - FuelStreams::setup_all(&core_client, &publisher_client).await; + FuelStreams::setup_all(&core_client, &publisher_client, &s3_client) + .await; + let fuel_streams: Arc = publisher_stream.arc(); + let semaphore = Arc::new(tokio::sync::Semaphore::new(64)); while !token.is_cancelled() { - let messages = consumer.fetch().max_messages(100).messages().await?; - tokio::pin!(messages); + let mut messages = + consumer.fetch().max_messages(100).messages().await?.fuse(); + let mut futs = FuturesUnordered::new(); while let Some(msg) = messages.next().await { let msg = msg?; - let msg_str = std::str::from_utf8(&msg.payload)?; - let payload = Arc::new(BlockPayload::decode(msg_str)?); - Executor::::process_all(payload, &fuel_streams).await?; - msg.ack().await?; + let fuel_streams = fuel_streams.clone(); + let semaphore = semaphore.clone(); + let future = async move { + let msg_str = std::str::from_utf8(&msg.payload)?; + let payload = Arc::new(BlockPayload::decode(msg_str)?); + let start_time = std::time::Instant::now(); + let futures = Executor::::process_all( + payload.clone(), + &fuel_streams, + &semaphore, + ); + let results = try_join_all(futures).await?; + let end_time = std::time::Instant::now(); + msg.ack().await?; + Ok::<_, ConsumerError>((results, start_time, end_time, payload)) + }; + futs.push(future); + } + while let Some(result) = futs.next().await { + let (results, start_time, end_time, payload) = result?; + log_task(results, start_time, end_time, payload); } } Ok(()) } + +fn log_task( + res: Vec>, + start_time: std::time::Instant, + end_time: std::time::Instant, + payload: Arc, +) { + let height = payload.metadata().clone().block_height; + let has_error = res.iter().any(|r| r.is_err()); + let errors = res + .iter() + .filter_map(|r| r.as_ref().err()) + .collect::>(); + + let elapsed = end_time.duration_since(start_time); + if has_error { + tracing::error!( + "Block {height} published with errors in {:?}", + elapsed + ); + tracing::debug!("Errors: {:?}", errors); + } else { + tracing::info!( + "Block {height} published successfully in {:?}", + elapsed + ); + } +} diff --git a/crates/sv-emitter/src/main.rs b/crates/sv-emitter/src/main.rs index b7bd5bdc..c928015c 100644 --- a/crates/sv-emitter/src/main.rs +++ b/crates/sv-emitter/src/main.rs @@ -73,8 +73,8 @@ async fn main() -> anyhow::Result<()> { } async fn setup_nats(nats_url: &str) -> anyhow::Result { - let opts = NatsClientOpts::admin_opts(None) - .with_custom_url(nats_url.to_string()) + let opts = NatsClientOpts::admin_opts() + .with_url(nats_url.to_string()) .with_domain("CORE".to_string()); let nats_client = NatsClient::connect(&opts).await?; let stream_name = nats_client.namespace.stream_name("block_importer"); @@ -95,7 +95,10 @@ async fn setup_nats(nats_url: &str) -> anyhow::Result { async fn find_last_published_height( nats_client: &NatsClient, ) -> anyhow::Result { - let block_stream = Stream::::get_or_init(nats_client).await; + let s3_client_opts = S3ClientOpts::admin_opts(); + let s3_client = Arc::new(S3Client::new(&s3_client_opts).await?); + let block_stream = + Stream::::get_or_init(nats_client, &s3_client).await; let last_publish_height = block_stream .get_last_published(BlocksSubject::WILDCARD) .await?; diff --git a/crates/sv-publisher/Cargo.toml b/crates/sv-publisher/Cargo.toml new file mode 100644 index 00000000..fdf306e6 --- /dev/null +++ b/crates/sv-publisher/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "sv-publisher" +description = "Service that emitts new blocks using fuel-core block subscription" +authors = { workspace = true } +keywords = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } +rust-version = { workspace = true } +publish = false + +[[bin]] +name = "sv-publisher" +path = "src/main.rs" + +[dependencies] +anyhow = { workspace = true } +async-nats = { workspace = true } +clap = { workspace = true } +fuel-core = { workspace = true, default-features = false, features = ["p2p", "relayer", "rocksdb"] } +fuel-core-bin = { workspace = true, default-features = false, features = [ + "p2p", + "relayer", + "rocksdb", +] } +fuel-core-types = { workspace = true, default-features = false, features = ["std", "serde"] } +fuel-streams-core = { workspace = true, features = ["test-helpers"] } +fuel-streams-executors = { workspace = true, features = ["test-helpers"] } +futures = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tokio-util = "0.7.13" +tracing = { workspace = true } + +[features] +default = [] +test-helpers = [] + +[target.x86_64-unknown-linux-gnu.dependencies] +openssl = { version = "0.10.68", features = ["vendored"] } + +[target.x86_64-unknown-linux-musl.dependencies] +openssl = { version = "0.10.68", features = ["vendored"] } + +[target.aarch64-unknown-linux-gnu.dependencies] +openssl = { version = "0.10.68", features = ["vendored"] } + +[target.aarch64-unknown-linux-musl.dependencies] +openssl = { version = "0.10.68", features = ["vendored"] } diff --git a/crates/fuel-streams-publisher/src/cli.rs b/crates/sv-publisher/src/cli.rs similarity index 63% rename from crates/fuel-streams-publisher/src/cli.rs rename to crates/sv-publisher/src/cli.rs index 92d5765c..4573fedd 100644 --- a/crates/fuel-streams-publisher/src/cli.rs +++ b/crates/sv-publisher/src/cli.rs @@ -9,6 +9,9 @@ use clap::Parser; /// - `fuel_core_config`: Configuration for the Fuel Core service, parsed using a flattened command. #[derive(Clone, Parser)] pub struct Cli { + /// Flattened command structure for Fuel Core configuration. + #[command(flatten)] + pub fuel_core_config: fuel_core_bin::cli::run::Command, /// Fuel Network to connect to. #[arg( long, @@ -18,24 +21,4 @@ pub struct Cli { help = "NATS URL to connect to." )] pub nats_url: String, - /// Flattened command structure for Fuel Core configuration. - #[command(flatten)] - pub fuel_core_config: fuel_core_bin::cli::run::Command, - /// Http server address - #[arg( - long, - value_name = "TPORT", - env = "TELEMETRY_PORT", - default_value = "8080", - help = "Port for the Actix Web server to bind telemetry to." - )] - pub telemetry_port: u16, - #[arg( - long, - value_name = "HISTORICAL", - env = "HISTORICAL", - default_value = "false", - help = "Whether to publish historical data to NATS" - )] - pub historical: bool, } diff --git a/crates/sv-publisher/src/lib.rs b/crates/sv-publisher/src/lib.rs new file mode 100644 index 00000000..5bf4a4b0 --- /dev/null +++ b/crates/sv-publisher/src/lib.rs @@ -0,0 +1,2 @@ +pub mod cli; +pub mod shutdown; diff --git a/crates/sv-publisher/src/main.rs b/crates/sv-publisher/src/main.rs new file mode 100644 index 00000000..38b1d491 --- /dev/null +++ b/crates/sv-publisher/src/main.rs @@ -0,0 +1,213 @@ +use std::{sync::Arc, time::Duration}; + +use async_nats::jetstream::{ + context::PublishErrorKind, + stream::RetentionPolicy, + Context, +}; +use clap::Parser; +use fuel_core_types::blockchain::SealedBlock; +use fuel_streams_core::prelude::*; +use fuel_streams_executors::*; +use futures::StreamExt; +use sv_publisher::{cli::Cli, shutdown::ShutdownController}; +use thiserror::Error; +use tokio_util::sync::CancellationToken; + +#[derive(Error, Debug)] +pub enum LiveBlockProcessingError { + #[error("Failed to publish block: {0}")] + PublishError(#[from] PublishError), + + #[error("Processing was cancelled")] + Cancelled, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + let config = cli.fuel_core_config; + let fuel_core: Arc = FuelCore::new(config).await?; + fuel_core.start().await?; + + let s3_client = setup_s3().await?; + let nats_client = setup_nats(&cli.nats_url).await?; + let last_block_height = Arc::new(fuel_core.get_latest_block_height()?); + let last_published = + Arc::new(find_last_published_height(&nats_client, &s3_client).await?); + + let shutdown = Arc::new(ShutdownController::new()); + shutdown.clone().spawn_signal_handler(); + + tracing::info!("Last published height: {}", last_published); + tracing::info!("Last block height: {}", last_block_height); + tokio::select! { + result = async { + let historical = process_historical_blocks( + &nats_client, + fuel_core.clone(), + last_block_height, + last_published, + shutdown.token().clone(), + ); + + let live = process_live_blocks( + &nats_client.jetstream, + fuel_core.clone(), + shutdown.token().clone(), + ); + + tokio::join!(historical, live) + } => { + result.0?; + result.1?; + } + _ = shutdown.wait_for_shutdown() => { + tracing::info!("Shutdown signal received, waiting for processing to complete..."); + fuel_core.stop().await + } + } + + tracing::info!("Shutdown complete"); + Ok(()) +} + +async fn setup_s3() -> anyhow::Result> { + let s3_client_opts = S3ClientOpts::admin_opts(); + let s3_client = S3Client::new(&s3_client_opts).await?; + Ok(Arc::new(s3_client)) +} + +async fn setup_nats(nats_url: &str) -> anyhow::Result { + let opts = NatsClientOpts::admin_opts() + .with_url(nats_url.to_string()) + .with_domain("CORE".to_string()); + let nats_client = NatsClient::connect(&opts).await?; + let stream_name = nats_client.namespace.stream_name("block_importer"); + nats_client + .jetstream + .get_or_create_stream(async_nats::jetstream::stream::Config { + name: stream_name, + subjects: vec!["block_submitted.>".to_string()], + retention: RetentionPolicy::WorkQueue, + duplicate_window: Duration::from_secs(1), + ..Default::default() + }) + .await?; + + Ok(nats_client) +} + +async fn find_last_published_height( + nats_client: &NatsClient, + s3_client: &Arc, +) -> anyhow::Result { + let block_stream = + Stream::::get_or_init(nats_client, s3_client).await; + let last_publish_height = block_stream + .get_last_published(BlocksSubject::WILDCARD) + .await?; + match last_publish_height { + Some(block) => Ok(block.height), + None => Ok(0), + } +} + +fn get_historical_block_range( + last_published_height: Arc, + last_block_height: Arc, +) -> Option> { + let last_published_height = *last_published_height; + let last_block_height = *last_block_height; + let start_height = last_published_height + 1; + let end_height = last_block_height; + if start_height > end_height { + tracing::info!("No historical blocks to process"); + return None; + } + let block_count = end_height - start_height + 1; + let heights: Vec = (start_height..=end_height).collect(); + tracing::info!( + "Processing {block_count} historical blocks from height {start_height} to {end_height}" + ); + Some(heights) +} + +fn process_historical_blocks( + nats_client: &NatsClient, + fuel_core: Arc, + last_block_height: Arc, + last_published_height: Arc, + token: CancellationToken, +) -> tokio::task::JoinHandle<()> { + let jetstream = nats_client.jetstream.clone(); + tokio::spawn(async move { + let Some(heights) = get_historical_block_range( + last_published_height, + last_block_height, + ) else { + return; + }; + futures::stream::iter(heights) + .map(|height| { + let jetstream = jetstream.clone(); + let fuel_core = fuel_core.clone(); + let sealed_block = fuel_core.get_sealed_block_by_height(height); + let sealed_block = Arc::new(sealed_block); + async move { + publish_block(&jetstream, &fuel_core, &sealed_block).await + } + }) + .buffer_unordered(100) + .take_until(token.cancelled()) + .collect::>() + .await; + }) +} + +async fn process_live_blocks( + jetstream: &Context, + fuel_core: Arc, + token: CancellationToken, +) -> Result<(), LiveBlockProcessingError> { + let mut subscription = fuel_core.blocks_subscription(); + while let Ok(data) = subscription.recv().await { + if token.is_cancelled() { + break; + } + let sealed_block = Arc::new(data.sealed_block.clone()); + publish_block(jetstream, &fuel_core, &sealed_block).await?; + } + Ok(()) +} + +#[derive(Error, Debug)] +pub enum PublishError { + #[error("Failed to publish block to NATS server: {0}")] + NatsPublish(#[from] async_nats::error::Error), + + #[error("Failed to create block payload due to: {0}")] + BlockPayload(#[from] ExecutorError), + + #[error("Failed to access offchain database: {0}")] + OffchainDatabase(String), +} + +async fn publish_block( + jetstream: &Context, + fuel_core: &Arc, + sealed_block: &Arc, +) -> Result<(), PublishError> { + let metadata = Metadata::new(fuel_core, sealed_block); + let fuel_core = Arc::clone(fuel_core); + let payload = BlockPayload::new(fuel_core, sealed_block, &metadata)?; + jetstream + .send_publish(payload.subject(), payload.to_owned().try_into()?) + .await + .map_err(PublishError::NatsPublish)? + .await + .map_err(PublishError::NatsPublish)?; + + tracing::info!("New block submitted: {}", payload.block_height()); + Ok(()) +} diff --git a/crates/sv-publisher/src/shutdown.rs b/crates/sv-publisher/src/shutdown.rs new file mode 100644 index 00000000..6d66e7b1 --- /dev/null +++ b/crates/sv-publisher/src/shutdown.rs @@ -0,0 +1,104 @@ +use std::sync::Arc; + +use tokio_util::sync::CancellationToken; + +#[derive(Clone)] +pub struct ShutdownController { + token: CancellationToken, +} + +impl Default for ShutdownController { + fn default() -> Self { + Self::new() + } +} + +impl ShutdownController { + pub fn new() -> Self { + Self { + token: CancellationToken::new(), + } + } + + pub fn token(&self) -> &CancellationToken { + &self.token + } + + pub fn spawn_signal_handler(self: Arc) -> Arc { + tokio::spawn({ + let shutdown = self.clone(); + async move { + tokio::signal::ctrl_c() + .await + .expect("Failed to listen for ctrl+c"); + tracing::info!("Received shutdown signal"); + shutdown.initiate_shutdown(); + } + }); + self + } + + pub fn initiate_shutdown(&self) { + tracing::info!("Initiating graceful shutdown..."); + self.token.cancel(); + } + + pub fn is_shutdown_initiated(&self) -> bool { + self.token.is_cancelled() + } + + pub async fn wait_for_shutdown(&self) { + self.token.cancelled().await; + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use super::*; + + #[tokio::test] + async fn test_manual_shutdown() { + let controller = ShutdownController::new(); + assert!( + !controller.is_shutdown_initiated(), + "Controller should not be shutdown initially" + ); + + controller.initiate_shutdown(); + assert!( + controller.is_shutdown_initiated(), + "Controller should be shutdown after initiation" + ); + } + + #[tokio::test] + async fn test_wait_for_shutdown_timeout() { + let controller = ShutdownController::new(); + + let timeout = Duration::from_millis(50); + let result = + tokio::time::timeout(timeout, controller.wait_for_shutdown()).await; + + assert!( + result.is_err(), + "wait_for_shutdown should not complete without initiation" + ); + } + + #[tokio::test] + async fn test_clone_behavior() { + let controller = ShutdownController::new(); + let cloned = controller.clone(); + + // Initiate shutdown from clone + cloned.initiate_shutdown(); + + assert!( + controller.is_shutdown_initiated(), + "Original should be shutdown" + ); + assert!(cloned.is_shutdown_initiated(), "Clone should be shutdown"); + } +} diff --git a/crates/fuel-streams-publisher/Cargo.toml b/crates/sv-webserver/Cargo.toml similarity index 69% rename from crates/fuel-streams-publisher/Cargo.toml rename to crates/sv-webserver/Cargo.toml index 6466a81e..621f504c 100644 --- a/crates/fuel-streams-publisher/Cargo.toml +++ b/crates/sv-webserver/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "fuel-streams-publisher" -description = "Fuel library for publishing data streams from events that happen in Fuel chain(s)" +name = "sv-webserver" +description = "Fuel library for streaming data from nats and storage" authors = { workspace = true } keywords = { workspace = true } edition = { workspace = true } @@ -9,36 +9,33 @@ license = { workspace = true } repository = { workspace = true } version = { workspace = true } rust-version = { workspace = true } -publish = false + +[[bin]] +name = "sv-webserver" +path = "src/main.rs" [dependencies] actix-cors = { workspace = true } actix-server = { workspace = true } +actix-service = "2.0.2" actix-web = { workspace = true } +actix-ws = "0.3.0" anyhow = { workspace = true } async-nats = { workspace = true } -async-trait = { workspace = true } +bytestring = "1.4.0" chrono = { workspace = true } clap = { workspace = true } derive_more = { version = "1.0", features = ["full"] } displaydoc = { workspace = true } dotenvy = { workspace = true } elasticsearch = "8.15.0-alpha.1" -fuel-core = { workspace = true, default-features = false, features = [ - "p2p", - "relayer", - "rocksdb", - "test-helpers", -] } -fuel-core-bin = { workspace = true, default-features = false, features = [ - "p2p", - "relayer", - "rocksdb", -] } -fuel-core-services = { workspace = true, default-features = false, features = ["test-helpers"] } fuel-streams-core = { workspace = true, features = ["test-helpers"] } -fuel-streams-executors = { workspace = true, features = ["test-helpers"] } +fuel-streams-nats = { workspace = true, features = ["test-helpers"] } +fuel-streams-storage = { workspace = true, features = ["test-helpers"] } futures = { workspace = true } +futures-util = { workspace = true } +jsonwebtoken = "9.3.0" +num_cpus = { workspace = true } parking_lot = { version = "0.12", features = ["serde"] } prometheus = { version = "0.13", features = ["process"] } rand = { workspace = true } @@ -48,16 +45,17 @@ serde_json = { workspace = true } serde_prometheus = { version = "0.2" } sysinfo = { version = "0.29" } thiserror = "2.0" +time = { version = "0.3", features = ["serde"] } tokio = { workspace = true } -tokio-stream = { workspace = true } tracing = { workspace = true } tracing-actix-web = { workspace = true } +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } url = "2.5" +urlencoding = "2.1" +uuid = { version = "1.11.0", features = ["serde", "v4"] } +validator = { version = "0.19.0", features = ["derive"] } [dev-dependencies] -assert_matches = { workspace = true } -mockall = { workspace = true } -mockall_double = { workspace = true } [features] default = [] diff --git a/crates/fuel-streams-publisher/README.md b/crates/sv-webserver/README.md similarity index 65% rename from crates/fuel-streams-publisher/README.md rename to crates/sv-webserver/README.md index 9d7eb196..cd717d7c 100644 --- a/crates/fuel-streams-publisher/README.md +++ b/crates/sv-webserver/README.md @@ -3,9 +3,9 @@ Logo -

Fuel Streams Publisher

+

Fuel Streams Websockets

- A binary that subscribes to events from a Fuel client or node and publishes streams consumable via the fuel-streams SDK + A binary webserver that consumes events published by fuel-streams and streams them further via websockets upon subscription

@@ -16,7 +16,7 @@

- 📚 Documentation + 📚 Documentation   🐛 Report Bug   @@ -26,7 +26,7 @@ ## 📝 About The Project -The Fuel Streams Publisher is a binary that subscribes to events emitted from a Fuel client or node and publishes streams that can be consumed via the `fuel-streams` SDK. +A binary that consumes events published by fuel-streams and streams them further via websockets. ## ⚡️ Getting Started @@ -35,32 +35,6 @@ The Fuel Streams Publisher is a binary that subscribes to events emitted from a - [Rust toolchain](https://www.rust-lang.org/tools/install) - [Docker](https://www.docker.com/get-started/) (optional) -### Development - -1. Generate the `KEYPAIR` environment variable: - - ```sh - fuel-core-keygen new --key-type peering -p - ``` - -2. Generate an `INFURA_API_KEY` from [Infura](https://app.infura.io/) - -3. Copy `.env.sample` to `.env` and update the `KEYPAIR` and `INFURA_API_KEY` with the values generated above - -4. Run the binary: - - - From the monorepo's root: - - ```sh - ./scripts/start-publisher.sh - ``` - - - Or using `make` and `docker`: - - ```sh - make start/publisher - ``` - ## 🤝 Contributing Contributions are welcome! Please feel free to submit a Pull Request. diff --git a/crates/sv-webserver/src/cli.rs b/crates/sv-webserver/src/cli.rs new file mode 100644 index 00000000..37cad8bd --- /dev/null +++ b/crates/sv-webserver/src/cli.rs @@ -0,0 +1,54 @@ +use clap::Parser; + +/// CLI structure for parsing command-line arguments. +#[derive(Clone, Parser)] +pub struct Cli { + /// API port number + #[arg( + long, + value_name = "PORT", + env = "PORT", + default_value = "9003", + help = "Port number for the API server" + )] + pub port: u16, + + /// NATS URL + #[arg( + long, + value_name = "NATS_URL", + env = "NATS_URL", + default_value = "nats://localhost:4222", + help = "NATS URL" + )] + pub nats_url: String, + + /// Enable S3 + #[arg( + long, + value_name = "AWS_S3_ENABLED", + env = "AWS_S3_ENABLED", + default_value = "true", + help = "Enable S3 integration" + )] + pub s3_enabled: bool, + + /// JWT secret + #[arg( + long, + value_name = "JWT_AUTH_SECRET", + env = "JWT_AUTH_SECRET", + default_value = "secret", + help = "Secret key for JWT authentication" + )] + pub jwt_secret: String, + + /// Use metrics + #[arg( + long, + env = "USE_METRICS", + default_value = "false", + help = "Enable metrics" + )] + pub use_metrics: bool, +} diff --git a/crates/sv-webserver/src/config.rs b/crates/sv-webserver/src/config.rs new file mode 100644 index 00000000..eeaada1b --- /dev/null +++ b/crates/sv-webserver/src/config.rs @@ -0,0 +1,71 @@ +use std::path::PathBuf; + +use clap::Parser; +use displaydoc::Display as DisplayDoc; +use thiserror::Error; + +#[derive(Debug, DisplayDoc, Error)] +pub enum Error { + /// Undecodable config element: {0} + UndecodableConfigElement(&'static str), +} + +#[derive(Debug, Default, Clone)] +pub struct S3Config { + pub enabled: bool, +} + +#[derive(Clone, Debug)] +pub struct TlsConfig { + pub private_key: PathBuf, + pub certificate: PathBuf, +} + +#[derive(Clone, Debug)] +pub struct ApiConfig { + pub port: u16, + pub tls: Option, +} + +#[derive(Clone, Debug)] +pub struct AuthConfig { + pub jwt_secret: String, +} + +#[derive(Clone, Debug)] +pub struct NatsConfig { + pub url: String, +} + +#[derive(Clone, Debug)] +pub struct Config { + pub api: ApiConfig, + pub auth: AuthConfig, + pub s3: S3Config, + pub nats: NatsConfig, +} + +impl Config { + pub fn load() -> Result { + let cli = crate::cli::Cli::parse(); + Self::from_cli(&cli) + } + + fn from_cli(cli: &crate::cli::Cli) -> Result { + Ok(Config { + api: ApiConfig { + port: cli.port, + tls: None, + }, + auth: AuthConfig { + jwt_secret: cli.jwt_secret.clone(), + }, + nats: NatsConfig { + url: cli.nats_url.clone(), + }, + s3: S3Config { + enabled: cli.s3_enabled, + }, + }) + } +} diff --git a/crates/sv-webserver/src/lib.rs b/crates/sv-webserver/src/lib.rs new file mode 100644 index 00000000..ed3faedc --- /dev/null +++ b/crates/sv-webserver/src/lib.rs @@ -0,0 +1,16 @@ +pub mod cli; +pub mod config; +pub mod server; +pub mod telemetry; + +use std::sync::LazyLock; + +pub static STREAMER_MAX_WORKERS: LazyLock = LazyLock::new(|| { + let available_cpus = num_cpus::get(); + let default_threads = 2 * available_cpus; + + dotenvy::var("STREAMER_MAX_WORKERS") + .ok() + .and_then(|val| val.parse().ok()) + .unwrap_or(default_threads) +}); diff --git a/crates/sv-webserver/src/main.rs b/crates/sv-webserver/src/main.rs new file mode 100644 index 00000000..253f6fb3 --- /dev/null +++ b/crates/sv-webserver/src/main.rs @@ -0,0 +1,46 @@ +use sv_webserver::{ + config::Config, + server::{api::create_api, context::Context, state::ServerState}, +}; +use tracing::level_filters::LevelFilter; +use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter}; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // init tracing + tracing_subscriber::fmt() + .with_env_filter( + EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy(), + ) + .with_span_events(FmtSpan::CLOSE) + .init(); + + if let Err(err) = dotenvy::dotenv() { + tracing::warn!("File .env not found: {:?}", err); + } + + let config = Config::load()?; + let context = Context::new(&config).await?; + let state = ServerState::new(context).await; + let server = create_api(&config, state)?; + let server_handle = server.handle(); + + // spawn the server in the background + let jh = tokio::spawn(async move { + tracing::info!("Starting actix server ..."); + if let Err(err) = server.await { + tracing::error!("Actix Web server error: {:?}", err); + } + }); + + let _ = tokio::join!(jh); + + // Await the Actix server shutdown + tracing::info!("Stopping actix server ..."); + server_handle.stop(true).await; + tracing::info!("Actix server stopped. Goodbye!"); + + Ok(()) +} diff --git a/crates/sv-webserver/src/server/api.rs b/crates/sv-webserver/src/server/api.rs new file mode 100644 index 00000000..4e06e39d --- /dev/null +++ b/crates/sv-webserver/src/server/api.rs @@ -0,0 +1,88 @@ +use std::net::{Ipv4Addr, SocketAddrV4}; + +use actix_cors::Cors; +use actix_server::Server; +use actix_web::{ + http::{self, Method}, + middleware::{Compress, Logger as ActixLogger}, + web, + App, + HttpServer, +}; +use tracing_actix_web::TracingLogger; + +use super::{ + http::handlers::{get_health, get_metrics, request_jwt}, + middlewares::auth::JwtAuth, + state::ServerState, + ws::socket::get_ws, +}; +use crate::{config::Config, STREAMER_MAX_WORKERS}; + +const API_VERSION: &str = "v1"; + +fn with_prefixed_route(route: &str) -> String { + format!("/api/{}/{}", API_VERSION, route) +} + +pub fn create_api( + config: &Config, + state: ServerState, +) -> anyhow::Result { + let server_addr = std::net::SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::UNSPECIFIED, + config.api.port, + )); + let jwt_secret = config.auth.jwt_secret.clone(); + let server = HttpServer::new(move || { + let jwt_secret = jwt_secret.clone(); + // create cors + let cors = Cors::default() + .allow_any_origin() + .allowed_methods(vec![ + Method::GET, + Method::POST, + Method::PUT, + Method::OPTIONS, + Method::DELETE, + Method::PATCH, + Method::TRACE, + ]) + .allowed_headers(vec![ + http::header::AUTHORIZATION, + http::header::ACCEPT, + ]) + .allowed_header(http::header::CONTENT_TYPE) + .max_age(3600); + + App::new() + .app_data(web::Data::new(state.clone())) + .wrap(ActixLogger::default()) + .wrap(TracingLogger::default()) + .wrap(Compress::default()) + .wrap(cors) + .service( + web::resource(with_prefixed_route("health")) + .route(web::get().to(get_health)), + ) + .service( + web::resource(with_prefixed_route("metrics")) + .route(web::get().to(get_metrics)), + ) + .service( + web::resource(with_prefixed_route("jwt")) + .route(web::get().to(request_jwt)), + ) + .service( + web::resource(with_prefixed_route("ws")) + .wrap(JwtAuth::new(jwt_secret)) + .route(web::get().to(get_ws)), + ) + }) + .bind(server_addr)? + .workers(*STREAMER_MAX_WORKERS) + .shutdown_timeout(20) + .run(); + + Ok(server) +} diff --git a/crates/sv-webserver/src/server/auth.rs b/crates/sv-webserver/src/server/auth.rs new file mode 100755 index 00000000..fc11fe2c --- /dev/null +++ b/crates/sv-webserver/src/server/auth.rs @@ -0,0 +1,357 @@ +use std::{collections::HashMap, convert::TryFrom, fmt}; + +use actix_web::{ + http::header::{HeaderMap, HeaderValue, AUTHORIZATION}, + HttpResponse, + ResponseError, +}; +use chrono::Utc; +use displaydoc::Display as DisplayDoc; +use jsonwebtoken::{ + decode, + encode, + Algorithm, + DecodingKey, + EncodingKey, + Header, + Validation, +}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use uuid::Uuid; + +const BEARER: &str = "Bearer"; + +#[derive( + Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, +)] +pub enum UserType { + ADMIN, + CLIENT, +} + +/// User-related errors +#[derive(Clone, Debug, DisplayDoc, Error, PartialEq)] +pub enum UserError { + /// User not found + UserNotFound, + /// Unknown User Role: `{0}` + UnknownUserRole(String), + /// Unknown User Status: `{0}` + UnknownUserStatus(String), + /// Unallowed User Role: `{0}` + UnallowedUserRole(String), + /// Missing password + MissingPassword, + /// Missing username + MissingUsername, + /// Wrong password + WrongPassword, + /// User is not verified + UnverifiedUser, +} + +impl ResponseError for UserError { + fn error_response(&self) -> HttpResponse { + match self { + UserError::UserNotFound => { + HttpResponse::NotFound().body(self.to_string()) + } + UserError::UnknownUserRole(_) => { + HttpResponse::Unauthorized().body(self.to_string()) + } + UserError::UnknownUserStatus(_) => { + HttpResponse::NotFound().body(self.to_string()) + } + UserError::UnallowedUserRole(_) => { + HttpResponse::Unauthorized().body(self.to_string()) + } + UserError::MissingPassword => { + HttpResponse::Unauthorized().body(self.to_string()) + } + UserError::MissingUsername => { + HttpResponse::Unauthorized().body(self.to_string()) + } + UserError::WrongPassword => { + HttpResponse::Unauthorized().body(self.to_string()) + } + UserError::UnverifiedUser => { + HttpResponse::Unauthorized().body(self.to_string()) + } + } + } +} + +/// Auth errors +#[derive(Clone, Debug, DisplayDoc, Error, PartialEq)] +pub enum AuthError { + /// Wrong Credentials + WrongCredentialsError, + /// JWT Token not valid + JWTTokenError, + /// JWT Token Creation Error + JWTTokenCreationError, + /// No Auth Header + NoAuthHeaderError, + /// Invalid Auth Header + InvalidAuthHeaderError, + /// No Permission + NoPermissionError, + /// Expired Token + ExpiredToken, + /// Bad Encoded User Role: `{0}` + BadEncodedUserRole(String), + /// Unparsable UUID error: `{0}` + UnparsableUuid(String), +} + +impl ResponseError for AuthError { + fn error_response(&self) -> HttpResponse { + match self { + AuthError::WrongCredentialsError => { + HttpResponse::Unauthorized().body(self.to_string()) + } + AuthError::JWTTokenError => { + HttpResponse::Unauthorized().body(self.to_string()) + } + AuthError::JWTTokenCreationError => { + HttpResponse::InternalServerError().body(self.to_string()) + } + AuthError::NoAuthHeaderError => { + HttpResponse::Unauthorized().body(self.to_string()) + } + AuthError::InvalidAuthHeaderError => { + HttpResponse::Unauthorized().body(self.to_string()) + } + AuthError::NoPermissionError => { + HttpResponse::Unauthorized().body(self.to_string()) + } + AuthError::ExpiredToken => { + HttpResponse::Unauthorized().body(self.to_string()) + } + AuthError::BadEncodedUserRole(_) => { + HttpResponse::Unauthorized().body(self.to_string()) + } + AuthError::UnparsableUuid(_) => { + HttpResponse::Unauthorized().body(self.to_string()) + } + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +struct Claims { + sub: String, + role: String, + exp: usize, +} + +/// A user role +#[repr(i16)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum UserRole { + Admin = 0, + Client = 1, +} + +impl From for i16 { + fn from(role: UserRole) -> i16 { + role as i16 + } +} + +impl TryFrom for UserRole { + type Error = UserError; + + fn try_from(n: i16) -> Result { + match n { + 0 => Ok(UserRole::Admin), + 1 => Ok(UserRole::Client), + _ => Err(UserError::UnknownUserRole(n.to_string())), + } + } +} + +/// Maps a string to a Role +impl TryFrom<&str> for UserRole { + type Error = UserError; + + fn try_from(role: &str) -> Result { + match role.to_lowercase().as_str() { + "admin" => Ok(UserRole::Admin), + "client" => Ok(UserRole::Client), + _ => Err(UserError::UnknownUserRole(role.to_string())), + } + } +} + +impl fmt::Display for UserRole { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UserRole::Admin => write!(f, "admin"), + UserRole::Client => write!(f, "client"), + } + } +} + +impl From for UserRole { + fn from(value: UserType) -> Self { + match value { + UserType::ADMIN => UserRole::Admin, + UserType::CLIENT => UserRole::Client, + } + } +} + +impl From for UserType { + fn from(value: UserRole) -> Self { + match value { + UserRole::Admin => UserType::ADMIN, + UserRole::Client => UserType::CLIENT, + } + } +} + +/// A user status +#[repr(i16)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum UserStatus { + Unverified = 0, + Verified = 1, +} + +impl From for i16 { + fn from(user_status: UserStatus) -> i16 { + user_status as i16 + } +} + +impl TryFrom for UserStatus { + type Error = UserError; + + fn try_from(n: i16) -> Result { + match n { + 0 => Ok(UserStatus::Unverified), + 1 => Ok(UserStatus::Verified), + _ => Err(UserError::UnknownUserStatus(n.to_string())), + } + } +} + +/// Maps a string to a status +impl TryFrom<&str> for UserStatus { + type Error = UserError; + + fn try_from(user_status: &str) -> Result { + match user_status.to_lowercase().as_str() { + "unverified" => Ok(UserStatus::Unverified), + "verified" => Ok(UserStatus::Verified), + _ => Err(UserError::UnknownUserStatus(user_status.to_string())), + } + } +} + +impl fmt::Display for UserStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UserStatus::Unverified => write!(f, "unverified"), + UserStatus::Verified => write!(f, "verified"), + } + } +} + +pub fn create_jwt( + uid: &str, + role: &UserRole, + jwt_secret: &[u8], +) -> Result { + let expiration = Utc::now() + .checked_add_signed(chrono::Duration::minutes(60)) + .expect("valid timestamp") + .timestamp(); + + let claims = Claims { + sub: uid.to_owned(), + role: role.to_string(), + exp: expiration as usize, + }; + let header = Header::new(Algorithm::HS512); + encode(&header, &claims, &EncodingKey::from_secret(jwt_secret)) + .map_err(|_| AuthError::JWTTokenCreationError) +} + +fn jwt_from_header(headers: &HeaderMap) -> Result { + let header = match headers.get(AUTHORIZATION) { + Some(v) => v, + None => return Err(AuthError::NoAuthHeaderError), + }; + let auth_header = match std::str::from_utf8(header.as_bytes()) { + Ok(v) => v, + Err(_) => return Err(AuthError::NoAuthHeaderError), + }; + if !auth_header.starts_with(BEARER) { + return Err(AuthError::InvalidAuthHeaderError); + } + let decoded_jwt = + urlencoding::decode(auth_header.trim_start_matches(BEARER)) + .unwrap() + .trim() + .to_string(); + Ok(decoded_jwt) +} + +fn authorize( + (jwt_secret, headers): (String, actix_web::http::header::HeaderMap), +) -> Result<(Uuid, String), AuthError> { + match jwt_from_header(&headers) { + Ok(jwt) => { + let decoded = decode::( + &jwt, + &DecodingKey::from_secret(jwt_secret.as_bytes()), + &Validation::new(Algorithm::HS512), + ) + .map_err(|_| AuthError::JWTTokenError)?; + + // check user id + let user_id = + Uuid::parse_str(&decoded.claims.sub).map_err(|_| { + AuthError::UnparsableUuid(decoded.claims.sub.to_string()) + })?; + // check token expiration + let now = Utc::now().timestamp(); + + if (decoded.claims.exp as i64).lt(&now) { + return Err(AuthError::ExpiredToken); + } + + // TODO: check for user in the db by user_id + + // get the user's role + let _token_role = UserRole::try_from(decoded.claims.role.as_str()) + .map_err(|_| { + AuthError::BadEncodedUserRole(decoded.claims.role) + })?; + + // TODO: verify db user's role vs token_role + Ok((user_id, jwt)) + } + Err(e) => Err(e), + } +} + +pub fn authorize_request( + (jwt_secret, mut headers, query_map): ( + String, + actix_web::http::header::HeaderMap, + HashMap, + ), +) -> Result<(Uuid, String), AuthError> { + // move all query values to the headers + for (key, value) in query_map.iter() { + if AUTHORIZATION.as_str().eq_ignore_ascii_case(key) { + headers + .insert(AUTHORIZATION, HeaderValue::from_str(value).unwrap()); + } + } + authorize((jwt_secret, headers)) +} diff --git a/crates/sv-webserver/src/server/context.rs b/crates/sv-webserver/src/server/context.rs new file mode 100644 index 00000000..c0af7476 --- /dev/null +++ b/crates/sv-webserver/src/server/context.rs @@ -0,0 +1,69 @@ +use std::{sync::Arc, time::Duration}; + +use fuel_streams_core::prelude::*; +use fuel_streams_storage::S3Client; + +use crate::{config::Config, telemetry::Telemetry}; + +pub const GRACEFUL_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(90); + +#[allow(dead_code)] +#[derive(Clone)] +pub struct Context { + pub nats_client: NatsClient, + pub fuel_streams: Arc, + pub telemetry: Arc, + pub s3_client: Option>, + pub jwt_secret: String, +} + +impl Context { + pub async fn new(config: &Config) -> anyhow::Result { + let nats_client_opts = NatsClientOpts::admin_opts() + .with_url(config.nats.url.clone()) + .with_domain("CORE"); + let nats_client = NatsClient::connect(&nats_client_opts).await?; + let s3_client_opts = S3ClientOpts::admin_opts(); + let s3_client = Arc::new(S3Client::new(&s3_client_opts).await?); + let fuel_streams = + Arc::new(FuelStreams::new(&nats_client, &s3_client).await); + let telemetry = Telemetry::new(None).await?; + telemetry.start().await?; + + Ok(Context { + fuel_streams, + nats_client, + // client, + telemetry, + s3_client: if config.s3.enabled { + Some(s3_client) + } else { + None + }, + jwt_secret: config.auth.jwt_secret.clone(), + }) + } + + #[allow(dead_code)] + async fn shutdown_services_with_timeout(&self) -> anyhow::Result<()> { + tokio::time::timeout(GRACEFUL_SHUTDOWN_TIMEOUT, async { + Context::flush_await_all_streams(&self.nats_client).await; + }) + .await?; + + Ok(()) + } + + #[allow(dead_code)] + async fn flush_await_all_streams(nats_client: &NatsClient) { + tracing::info!("Flushing in-flight messages to nats ..."); + match nats_client.nats_client.flush().await { + Ok(_) => { + tracing::info!("Flushed all streams successfully!"); + } + Err(e) => { + tracing::error!("Failed to flush all streams: {:?}", e); + } + } + } +} diff --git a/crates/sv-webserver/src/server/http/handlers.rs b/crates/sv-webserver/src/server/http/handlers.rs new file mode 100644 index 00000000..45fd8aa8 --- /dev/null +++ b/crates/sv-webserver/src/server/http/handlers.rs @@ -0,0 +1,73 @@ +use std::{collections::HashMap, sync::LazyLock}; + +use actix_web::{web, HttpResponse, Result}; +use uuid::Uuid; + +use super::models::{LoginRequest, LoginResponse}; +use crate::server::{ + auth::{create_jwt, AuthError, UserError, UserRole}, + state::ServerState, +}; + +pub static AUTH_DATA: LazyLock> = + LazyLock::new(|| { + HashMap::from_iter(vec![ + ( + "client".to_string(), + ("client".to_string(), UserRole::Client, Uuid::new_v4()), + ), + ( + "admin".to_string(), + ("admin".to_string(), UserRole::Admin, Uuid::new_v4()), + ), + ]) + }); + +pub async fn get_metrics( + state: web::Data, +) -> Result { + Ok(HttpResponse::Ok() + .content_type( + "application/openmetrics-text; version=1.0.0; charset=utf-8", + ) + .body(state.context.telemetry.get_metrics().await)) +} + +pub async fn get_health(state: web::Data) -> Result { + if !state.is_healthy() { + return Ok( + HttpResponse::ServiceUnavailable().body("Service Unavailable") + ); + } + Ok(HttpResponse::Ok().json(state.get_health().await)) +} + +// request jwt +pub async fn request_jwt( + state: web::Data, + req_body: web::Json, +) -> actix_web::Result { + // get user by username + let (password, user_role, uuid) = (*AUTH_DATA) + .get(&req_body.username) + .ok_or(UserError::UserNotFound)?; + + // compare pwd with expected one + if !password.eq_ignore_ascii_case(&req_body.password) { + return Err(AuthError::WrongCredentialsError.into()); + } + + // if all good, generate a jwt with the user role encoded + let jwt_token = create_jwt( + &uuid.to_string(), + user_role, + state.context.jwt_secret.as_bytes(), + ) + .map_err(|_| AuthError::JWTTokenCreationError)?; + + Ok(HttpResponse::Ok().json(&LoginResponse { + id: uuid.to_owned(), + username: req_body.username.clone(), + jwt_token, + })) +} diff --git a/crates/sv-webserver/src/server/http/mod.rs b/crates/sv-webserver/src/server/http/mod.rs new file mode 100644 index 00000000..759a498a --- /dev/null +++ b/crates/sv-webserver/src/server/http/mod.rs @@ -0,0 +1,2 @@ +pub mod handlers; +pub mod models; diff --git a/crates/sv-webserver/src/server/http/models.rs b/crates/sv-webserver/src/server/http/models.rs new file mode 100644 index 00000000..11d8ebb8 --- /dev/null +++ b/crates/sv-webserver/src/server/http/models.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; +use validator::Validate; + +#[derive(Debug, Serialize, Deserialize, Validate)] +#[serde(rename_all = "camelCase")] +pub struct LoginRequest { + pub username: String, + pub password: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LoginResponse { + pub id: uuid::Uuid, + pub username: String, + pub jwt_token: String, +} diff --git a/crates/sv-webserver/src/server/middlewares/auth.rs b/crates/sv-webserver/src/server/middlewares/auth.rs new file mode 100644 index 00000000..e57de0cd --- /dev/null +++ b/crates/sv-webserver/src/server/middlewares/auth.rs @@ -0,0 +1,109 @@ +use std::{ + collections::HashMap, + task::{Context, Poll}, +}; + +use actix_service::Transform; +use actix_web::{ + dev::{ServiceRequest, ServiceResponse}, + Error, + HttpMessage, + Result, +}; +use futures_util::future::{ready, LocalBoxFuture, Ready}; + +use crate::server::auth::authorize_request; + +pub struct JwtAuth { + jwt_secret: String, +} + +impl JwtAuth { + pub fn new(jwt_secret: String) -> Self { + JwtAuth { jwt_secret } + } +} + +impl Transform for JwtAuth +where + S: actix_service::Service< + ServiceRequest, + Response = ServiceResponse, + Error = Error, + > + 'static, + S::Future: 'static, +{ + type Response = ServiceResponse; + type Error = Error; + type InitError = (); + type Transform = JwtAuthMiddleware; + type Future = Ready>; + + fn new_transform(&self, service: S) -> Self::Future { + ready(Ok(JwtAuthMiddleware { + service, + jwt_secret: self.jwt_secret.clone(), + })) + } +} + +pub struct JwtAuthMiddleware { + service: S, + jwt_secret: String, +} + +impl actix_service::Service for JwtAuthMiddleware +where + S: actix_service::Service< + ServiceRequest, + Response = ServiceResponse, + Error = Error, + > + 'static, + S::Future: 'static, +{ + type Response = ServiceResponse; + type Error = Error; + type Future = LocalBoxFuture<'static, Result>; + + fn poll_ready( + &self, + cx: &mut Context<'_>, + ) -> Poll> { + self.service.poll_ready(cx) + } + + fn call(&self, req: ServiceRequest) -> Self::Future { + let jwt_secret = self.jwt_secret.clone(); + let headers = req.headers().clone(); + let query_map: HashMap = req + .query_string() + .split('&') + .filter_map(|pair| { + let mut parts = pair.split('='); + if let (Some(key), Some(value)) = (parts.next(), parts.next()) { + Some(( + key.to_string(), + urlencoding::decode(value).unwrap().into_owned(), + )) + } else { + None + } + }) + .collect(); + + // Validate the JWT + match authorize_request((jwt_secret, headers, query_map)) { + Ok((user_id, _jwt)) => { + req.extensions_mut().insert(user_id); + Box::pin(self.service.call(req)) + } + Err(e) => { + let err = e.to_string(); + // If JWT is invalid or missing, reject the request + Box::pin(async { + Err(actix_web::error::ErrorUnauthorized(err)) + }) + } + } + } +} diff --git a/crates/sv-webserver/src/server/middlewares/mod.rs b/crates/sv-webserver/src/server/middlewares/mod.rs new file mode 100644 index 00000000..0e4a05d5 --- /dev/null +++ b/crates/sv-webserver/src/server/middlewares/mod.rs @@ -0,0 +1 @@ +pub mod auth; diff --git a/crates/sv-webserver/src/server/mod.rs b/crates/sv-webserver/src/server/mod.rs new file mode 100644 index 00000000..1ecd4c65 --- /dev/null +++ b/crates/sv-webserver/src/server/mod.rs @@ -0,0 +1,7 @@ +pub mod api; +pub mod auth; +pub mod context; +pub mod http; +pub mod middlewares; +pub mod state; +pub mod ws; diff --git a/crates/fuel-streams-publisher/src/server/state.rs b/crates/sv-webserver/src/server/state.rs similarity index 90% rename from crates/fuel-streams-publisher/src/server/state.rs rename to crates/sv-webserver/src/server/state.rs index c15660b9..19e379d2 100644 --- a/crates/fuel-streams-publisher/src/server/state.rs +++ b/crates/sv-webserver/src/server/state.rs @@ -4,10 +4,11 @@ use std::{ }; use async_nats::jetstream::stream::State; +use fuel_streams_core::prelude::FuelStreamsExt; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; -use crate::Publisher; +use super::context::Context; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct StreamInfo { @@ -60,29 +61,32 @@ pub struct HealthResponse { #[derive(Clone)] pub struct ServerState { - pub publisher: Publisher, + pub context: Context, pub start_time: Instant, pub connection_count: Arc>, } impl ServerState { - pub async fn new(publisher: Publisher) -> Self { + pub async fn new(context: Context) -> Self { Self { - publisher, start_time: Instant::now(), connection_count: Arc::new(RwLock::new(0)), + context, } } } impl ServerState { pub fn is_healthy(&self) -> bool { - self.publisher.is_healthy() + if !self.context.nats_client.is_connected() { + return false; + } + true } pub async fn get_health(&self) -> HealthResponse { let streams_info = self - .publisher + .context .fuel_streams .get_consumers_and_state() .await diff --git a/crates/sv-webserver/src/server/ws/errors.rs b/crates/sv-webserver/src/server/ws/errors.rs new file mode 100644 index 00000000..db76ca56 --- /dev/null +++ b/crates/sv-webserver/src/server/ws/errors.rs @@ -0,0 +1,20 @@ +use displaydoc::Display as DisplayDoc; +use fuel_streams_core::StreamError; +use thiserror::Error; + +/// Ws Subscription-related errors +#[derive(Debug, DisplayDoc, Error)] +pub enum WsSubscriptionError { + /// Unparsable subscription payload: `{0}` + UnparsablePayload(serde_json::Error), + /// Unknown subject name: `{0}` + UnknownSubjectName(String), + /// Unsupported wildcard pattern: `{0}` + UnsupportedWildcardPattern(String), + /// Unserializable message payload: `{0}` + UnserializableMessagePayload(serde_json::Error), + /// Stream Error: `{0}` + Stream(#[from] StreamError), + /// Closed by client with reason: `{0}` + ClosedWithReason(String), +} diff --git a/crates/sv-webserver/src/server/ws/mod.rs b/crates/sv-webserver/src/server/ws/mod.rs new file mode 100644 index 00000000..bb1b9404 --- /dev/null +++ b/crates/sv-webserver/src/server/ws/mod.rs @@ -0,0 +1,4 @@ +pub mod errors; +pub mod models; +pub mod socket; +pub mod state; diff --git a/crates/sv-webserver/src/server/ws/models.rs b/crates/sv-webserver/src/server/ws/models.rs new file mode 100644 index 00000000..3a296acf --- /dev/null +++ b/crates/sv-webserver/src/server/ws/models.rs @@ -0,0 +1,106 @@ +use fuel_streams_nats::NatsDeliverPolicy; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +#[serde(rename_all = "camelCase")] +pub enum DeliverPolicy { + All, + Last, + New, + ByStartSequence { + #[serde(rename = "optStartSeq")] + start_sequence: u64, + }, + ByStartTime { + #[serde(rename = "optStartTime")] + start_time: time::OffsetDateTime, + }, + LastPerSubject, +} + +impl From for NatsDeliverPolicy { + fn from(policy: DeliverPolicy) -> Self { + match policy { + DeliverPolicy::All => NatsDeliverPolicy::All, + DeliverPolicy::Last => NatsDeliverPolicy::Last, + DeliverPolicy::New => NatsDeliverPolicy::New, + DeliverPolicy::ByStartSequence { start_sequence } => { + NatsDeliverPolicy::ByStartSequence { start_sequence } + } + DeliverPolicy::ByStartTime { start_time } => { + NatsDeliverPolicy::ByStartTime { start_time } + } + DeliverPolicy::LastPerSubject => NatsDeliverPolicy::LastPerSubject, + } + } +} + +impl From for DeliverPolicy { + fn from(policy: NatsDeliverPolicy) -> Self { + match policy { + NatsDeliverPolicy::All => DeliverPolicy::All, + NatsDeliverPolicy::Last => DeliverPolicy::Last, + NatsDeliverPolicy::New => DeliverPolicy::New, + NatsDeliverPolicy::ByStartSequence { start_sequence } => { + DeliverPolicy::ByStartSequence { start_sequence } + } + NatsDeliverPolicy::ByStartTime { start_time } => { + DeliverPolicy::ByStartTime { start_time } + } + NatsDeliverPolicy::LastPerSubject => DeliverPolicy::LastPerSubject, + } + } +} + +#[derive(Eq, PartialEq, Debug, Deserialize, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct SubscriptionPayload { + pub wildcard: String, + pub deliver_policy: DeliverPolicy, +} + +#[derive(Eq, PartialEq, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum ClientMessage { + Subscribe(SubscriptionPayload), + Unsubscribe(SubscriptionPayload), +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum ServerMessage { + Subscribed(SubscriptionPayload), + Unsubscribed(SubscriptionPayload), + Response(serde_json::Value), + Error(String), +} + +#[cfg(test)] +mod tests { + use super::{ClientMessage, DeliverPolicy, SubscriptionPayload}; + + #[test] + fn test_sub_ser() { + let stream_topic_wildcard = "blocks.*.*".to_owned(); + let msg = ClientMessage::Subscribe(SubscriptionPayload { + wildcard: stream_topic_wildcard.clone(), + deliver_policy: DeliverPolicy::All, + }); + let ser_str_value = serde_json::to_string(&msg).unwrap(); + println!("Ser value {:?}", ser_str_value); + let expected_value = serde_json::json!({ + "subscribe": { + "wildcard": stream_topic_wildcard, + "deliverPolicy": "all" + } + }); + let deser_msg_val = + serde_json::from_value::(expected_value).unwrap(); + assert!(msg.eq(&deser_msg_val)); + + let deser_msg_str = + serde_json::from_str::(&ser_str_value).unwrap(); + assert!(msg.eq(&deser_msg_str)); + } +} diff --git a/crates/sv-webserver/src/server/ws/socket.rs b/crates/sv-webserver/src/server/ws/socket.rs new file mode 100644 index 00000000..b07ad6b4 --- /dev/null +++ b/crates/sv-webserver/src/server/ws/socket.rs @@ -0,0 +1,378 @@ +use std::sync::{atomic::AtomicUsize, Arc}; + +use actix_web::{ + web::{self, Bytes}, + HttpMessage, + HttpRequest, + Responder, +}; +use actix_ws::{Message, Session}; +use fuel_streams_core::prelude::*; +use fuel_streams_nats::NatsDeliverPolicy; +use futures::StreamExt; +use uuid::Uuid; + +use super::{errors::WsSubscriptionError, models::ClientMessage}; +use crate::{ + server::{ + state::ServerState, + ws::models::{ServerMessage, SubscriptionPayload}, + }, + telemetry::Telemetry, +}; + +static _NEXT_USER_ID: AtomicUsize = AtomicUsize::new(1); + +pub async fn get_ws( + req: HttpRequest, + body: web::Payload, + state: web::Data, +) -> actix_web::Result { + // extract user id + let user_id = match req.extensions().get::() { + Some(user_id) => { + tracing::info!( + "Authenticated WebSocket connection for user: {:?}", + user_id.to_string() + ); + user_id.to_owned() + } + None => { + tracing::info!("Unauthenticated WebSocket connection"); + return Err(actix_web::error::ErrorUnauthorized( + "Missing or invalid JWT", + )); + } + }; + + // split the request into response, session, and message stream + let (response, session, mut msg_stream) = actix_ws::handle(&req, body)?; + + // record the new subscription + state.context.telemetry.increment_subscriptions_count(); + + // spawm an actor handling the ws connection + let streams = state.context.fuel_streams.clone(); + let telemetry = state.context.telemetry.clone(); + actix_web::rt::spawn(async move { + tracing::info!("Ws opened for user id {:?}", user_id.to_string()); + while let Some(Ok(msg)) = msg_stream.recv().await { + let mut session = session.clone(); + match msg { + Message::Ping(bytes) => { + tracing::info!("Received ping, {:?}", bytes); + if session.pong(&bytes).await.is_err() { + tracing::error!("Error sending pong, {:?}", bytes); + } + } + Message::Pong(bytes) => { + tracing::info!("Received pong, {:?}", bytes); + } + Message::Text(string) => { + tracing::info!("Received text, {string}"); + let bytes = Bytes::from(string.as_bytes().to_vec()); + let _ = handle_binary_message( + bytes, + user_id, + session, + Arc::clone(&telemetry), + Arc::clone(&streams), + ) + .await; + } + Message::Binary(bytes) => { + let _ = handle_binary_message( + bytes, + user_id, + session, + Arc::clone(&telemetry), + Arc::clone(&streams), + ) + .await; + } + Message::Close(reason) => { + tracing::info!( + "Got close event, terminating session with reason {:?}", + reason + ); + let reason_str = + reason.and_then(|r| r.description).unwrap_or_default(); + close_socket_with_error( + WsSubscriptionError::ClosedWithReason( + reason_str.to_string(), + ), + user_id, + session, + None, + telemetry, + ) + .await; + return; + } + _ => { + tracing::error!("Received unknown message type"); + close_socket_with_error( + WsSubscriptionError::ClosedWithReason( + "Unknown message type".to_string(), + ), + user_id, + session, + None, + telemetry, + ) + .await; + return; + } + }; + } + }); + + Ok(response) +} + +async fn handle_binary_message( + msg: Bytes, + user_id: uuid::Uuid, + mut session: Session, + telemetry: Arc, + streams: Arc, +) -> Result<(), WsSubscriptionError> { + tracing::info!("Received binary {:?}", msg); + let client_message = match parse_client_message(msg) { + Ok(msg) => msg, + Err(e) => { + close_socket_with_error(e, user_id, session, None, telemetry).await; + return Ok(()); + } + }; + + tracing::info!("Message parsed: {:?}", client_message); + // handle the client message + match client_message { + ClientMessage::Subscribe(payload) => { + tracing::info!("Received subscribe message: {:?}", payload); + let subject_wildcard = payload.wildcard; + let deliver_policy = payload.deliver_policy; + + // verify the subject name + let sub_subject = + match verify_and_extract_subject_name(&subject_wildcard) { + Ok(res) => res, + Err(e) => { + close_socket_with_error( + e, + user_id, + session, + Some(subject_wildcard.clone()), + telemetry, + ) + .await; + return Ok(()); + } + }; + + // start the streamer async + let mut stream_session = session.clone(); + + // reply to socket with subscription + send_message_to_socket( + &mut session, + ServerMessage::Subscribed(SubscriptionPayload { + wildcard: subject_wildcard.clone(), + deliver_policy, + }), + ) + .await; + + // receive streaming in a background thread + let streams = streams.clone(); + let telemetry = telemetry.clone(); + actix_web::rt::spawn(async move { + // update metrics + telemetry.update_user_subscription_metrics( + user_id, + &subject_wildcard, + ); + + // subscribe to the stream + let config = SubscriptionConfig { + deliver_policy: NatsDeliverPolicy::All, + filter_subjects: vec![subject_wildcard.clone()], + }; + let mut sub = + match streams.subscribe(&sub_subject, Some(config)).await { + Ok(sub) => sub, + Err(e) => { + close_socket_with_error( + WsSubscriptionError::Stream(e), + user_id, + session, + Some(subject_wildcard.clone()), + telemetry, + ) + .await; + return; + } + }; + + // consume and forward to the ws + while let Some(s3_serialized_payload) = sub.next().await { + // decode and serialize back to ws payload + let serialized_ws_payload = match decode( + &subject_wildcard, + s3_serialized_payload, + ) + .await + { + Ok(res) => res, + Err(e) => { + telemetry.update_error_metrics( + &subject_wildcard, + &e.to_string(), + ); + tracing::error!("Error serializing received stream message: {:?}", e); + continue; + } + }; + + // send the payload over the stream + let _ = stream_session.binary(serialized_ws_payload).await; + } + }); + Ok(()) + } + ClientMessage::Unsubscribe(payload) => { + tracing::info!("Received unsubscribe message: {:?}", payload); + let subject_wildcard = payload.wildcard; + + let deliver_policy = payload.deliver_policy; + + if let Err(e) = verify_and_extract_subject_name(&subject_wildcard) { + close_socket_with_error( + e, + user_id, + session, + Some(subject_wildcard.clone()), + telemetry, + ) + .await; + return Ok(()); + } + + // TODO: implement session management for the same user_id + // send a message to the client to confirm unsubscribing + send_message_to_socket( + &mut session, + ServerMessage::Unsubscribed(SubscriptionPayload { + wildcard: subject_wildcard, + deliver_policy, + }), + ) + .await; + Ok(()) + } + } +} + +fn parse_client_message( + msg: Bytes, +) -> Result { + let msg = serde_json::from_slice::(&msg) + .map_err(WsSubscriptionError::UnparsablePayload)?; + Ok(msg) +} + +pub fn verify_and_extract_subject_name( + subject_wildcard: &str, +) -> Result { + let mut subject_parts = subject_wildcard.split('.'); + // TODO: more advanced checks here with Regex + if subject_parts.clone().count() == 1 { + return Err(WsSubscriptionError::UnsupportedWildcardPattern( + subject_wildcard.to_string(), + )); + } + let subject_name = subject_parts.next().unwrap_or_default(); + if !FuelStreamsUtils::is_within_subject_names(subject_name) { + return Err(WsSubscriptionError::UnknownSubjectName( + subject_wildcard.to_string(), + )); + } + Ok(subject_name.to_string()) +} + +async fn close_socket_with_error( + e: WsSubscriptionError, + user_id: uuid::Uuid, + mut session: Session, + subject_wildcard: Option, + telemetry: Arc, +) { + tracing::error!("ws subscription error: {:?}", e.to_string()); + if let Some(subject_wildcard) = subject_wildcard { + telemetry.update_error_metrics(&subject_wildcard, &e.to_string()); + telemetry.update_unsubscribed(user_id, &subject_wildcard); + } + telemetry.decrement_subscriptions_count(); + send_message_to_socket(&mut session, ServerMessage::Error(e.to_string())) + .await; + let _ = session.close(None).await; +} + +async fn send_message_to_socket(session: &mut Session, message: ServerMessage) { + let data = serde_json::to_vec(&message).ok().unwrap_or_default(); + let _ = session.binary(data).await; +} + +async fn decode( + subject_wildcard: &str, + s3_payload: Vec, +) -> Result, WsSubscriptionError> { + let subject = verify_and_extract_subject_name(subject_wildcard)?; + let entity = match subject.as_str() { + Transaction::NAME => { + let entity = Transaction::decode_or_panic(s3_payload); + serde_json::to_value(entity) + .map_err(WsSubscriptionError::UnparsablePayload)? + } + Block::NAME => { + let entity = Block::decode_or_panic(s3_payload); + serde_json::to_value(entity) + .map_err(WsSubscriptionError::UnparsablePayload)? + } + Input::NAME => { + let entity = Input::decode_or_panic(s3_payload); + serde_json::to_value(entity) + .map_err(WsSubscriptionError::UnparsablePayload)? + } + Output::NAME => { + let entity = Output::decode_or_panic(s3_payload); + serde_json::to_value(entity) + .map_err(WsSubscriptionError::UnparsablePayload)? + } + Receipt::NAME => { + let entity = Receipt::decode_or_panic(s3_payload); + serde_json::to_value(entity) + .map_err(WsSubscriptionError::UnparsablePayload)? + } + Utxo::NAME => { + let entity = Utxo::decode_or_panic(s3_payload); + serde_json::to_value(entity) + .map_err(WsSubscriptionError::UnparsablePayload)? + } + Log::NAME => { + let entity = Log::decode_or_panic(s3_payload); + serde_json::to_value(entity) + .map_err(WsSubscriptionError::UnparsablePayload)? + } + _ => { + return Err(WsSubscriptionError::UnknownSubjectName( + subject.to_string(), + )) + } + }; + + // Wrap the entity in ServerMessage::Response and serialize once + serde_json::to_vec(&ServerMessage::Response(entity)) + .map_err(WsSubscriptionError::UnserializableMessagePayload) +} diff --git a/crates/sv-webserver/src/server/ws/state.rs b/crates/sv-webserver/src/server/ws/state.rs new file mode 100644 index 00000000..4ee4ced1 --- /dev/null +++ b/crates/sv-webserver/src/server/ws/state.rs @@ -0,0 +1,55 @@ +use std::sync::Arc; + +use actix_ws::Session; +use bytestring::ByteString; +use futures_util::{stream::FuturesUnordered, StreamExt as _}; +use tokio::sync::Mutex; + +#[allow(dead_code)] +#[derive(Clone)] +struct WsClient { + inner: Arc>, +} + +#[allow(dead_code)] +struct WsClientInner { + sessions: Vec, +} + +#[allow(dead_code)] +impl WsClient { + fn new() -> Self { + WsClient { + inner: Arc::new(Mutex::new(WsClientInner { + sessions: Vec::new(), + })), + } + } + + async fn insert(&self, session: Session) { + self.inner.lock().await.sessions.push(session); + } + + async fn broadcast(&self, msg: impl Into) { + let msg = msg.into(); + + let mut inner = self.inner.lock().await; + let mut unordered = FuturesUnordered::new(); + + for mut session in inner.sessions.drain(..) { + let msg = msg.clone(); + + unordered.push(async move { + let res = session.text(msg).await; + res.map(|_| session) + .map_err(|_| tracing::debug!("Dropping session")) + }); + } + + while let Some(res) = unordered.next().await { + if let Ok(session) = res { + inner.sessions.push(session); + } + } + } +} diff --git a/crates/fuel-streams-publisher/src/telemetry/elastic_search.rs b/crates/sv-webserver/src/telemetry/elastic_search.rs similarity index 100% rename from crates/fuel-streams-publisher/src/telemetry/elastic_search.rs rename to crates/sv-webserver/src/telemetry/elastic_search.rs diff --git a/crates/sv-webserver/src/telemetry/metrics.rs b/crates/sv-webserver/src/telemetry/metrics.rs new file mode 100644 index 00000000..5a65f1a0 --- /dev/null +++ b/crates/sv-webserver/src/telemetry/metrics.rs @@ -0,0 +1,190 @@ +use prometheus::{ + register_int_counter_vec, + register_int_gauge_vec, + IntCounterVec, + IntGaugeVec, + Registry, +}; +use rand::{distributions::Alphanumeric, Rng}; + +#[derive(Clone, Debug)] +pub struct Metrics { + pub registry: Registry, + pub total_ws_subs: IntGaugeVec, + pub user_subscribed_messages: IntGaugeVec, + pub subs_messages_throughput: IntCounterVec, + pub subs_messages_error_rates: IntCounterVec, +} + +impl Default for Metrics { + fn default() -> Self { + Metrics::new(None).expect("Failed to create default Metrics") + } +} + +impl Metrics { + pub fn generate_random_prefix() -> String { + rand::thread_rng() + .sample_iter(&Alphanumeric) + .filter(|c| c.is_ascii_alphabetic()) + .take(6) + .map(char::from) + .collect() + } + + pub fn new_with_random_prefix() -> anyhow::Result { + Metrics::new(Some(Metrics::generate_random_prefix())) + } + + pub fn new(prefix: Option) -> anyhow::Result { + let metric_prefix = prefix + .clone() + .map(|p| format!("{}_", p)) + .unwrap_or_default(); + + let total_ws_subs = register_int_gauge_vec!( + format!("{}ws_streamer_metrics_total_subscriptions", metric_prefix), + "A metric counting the number of active ws subscriptions", + &[], + ) + .expect("metric must be created"); + + let user_subscribed_messages = register_int_gauge_vec!( + format!( + "{}ws_streamer_metrics_user_subscribed_messages", + metric_prefix + ), + "A metric counting the number of published messages", + &["user_id", "subject_wildcard"], + ) + .expect("metric must be created"); + + let subs_messages_throughput = register_int_counter_vec!( + format!("{}ws_streamer_metrics_subs_messages_throughput", metric_prefix), + "A metric counting the number of subscription messages per subject wildcard", + &["subject_wildcard"], + ) + .expect("metric must be created"); + + let subs_messages_error_rates = + register_int_counter_vec!( + format!("{}ws_streamer_metrics_subs_messages_error_rates", metric_prefix), + "A metric counting errors or failures during subscription message processing", + &["subject_wildcard", "error_type"], + ) + .expect("metric must be created"); + + let registry = + Registry::new_custom(prefix, None).expect("registry to be created"); + registry.register(Box::new(total_ws_subs.clone()))?; + registry.register(Box::new(user_subscribed_messages.clone()))?; + registry.register(Box::new(subs_messages_throughput.clone()))?; + registry.register(Box::new(subs_messages_error_rates.clone()))?; + + Ok(Self { + registry, + total_ws_subs, + user_subscribed_messages, + subs_messages_throughput, + subs_messages_error_rates, + }) + } +} + +#[cfg(test)] +mod tests { + use prometheus::{gather, Encoder, TextEncoder}; + + use super::*; + + impl Metrics { + pub fn random() -> Self { + Metrics::new_with_random_prefix() + .expect("Failed to create random Metrics") + } + } + + #[test] + fn test_user_subscribed_messages_metric() { + let metrics = Metrics::random(); + + metrics + .user_subscribed_messages + .with_label_values(&["user_id_1", "subject_wildcard_1"]) + .set(5); + + let metric_families = gather(); + let mut buffer = Vec::new(); + let encoder = TextEncoder::new(); + encoder.encode(&metric_families, &mut buffer).unwrap(); + + let output = String::from_utf8(buffer.clone()).unwrap(); + + assert!(output.contains("ws_streamer_metrics_user_subscribed_messages")); + assert!(output.contains("user_id_1")); + assert!(output.contains("subject_wildcard_1")); + assert!(output.contains("5")); + } + + #[test] + fn test_subs_messages_total_metric() { + let metrics = Metrics::random(); + + metrics.total_ws_subs.with_label_values(&[]).set(10); + + let metric_families = gather(); + let mut buffer = Vec::new(); + let encoder = TextEncoder::new(); + encoder.encode(&metric_families, &mut buffer).unwrap(); + + let output = String::from_utf8(buffer.clone()).unwrap(); + + assert!(output.contains("ws_streamer_metrics_total_subscriptions")); + assert!(output.contains("10")); + } + + #[test] + fn test_subs_messages_throughput_metric() { + let metrics = Metrics::random(); + + metrics + .subs_messages_throughput + .with_label_values(&["wildcard_1"]) + .inc_by(10); + + let metric_families = gather(); + let mut buffer = Vec::new(); + let encoder = TextEncoder::new(); + encoder.encode(&metric_families, &mut buffer).unwrap(); + + let output = String::from_utf8(buffer.clone()).unwrap(); + + assert!(output.contains("ws_streamer_metrics_subs_messages_throughput")); + assert!(output.contains("wildcard_1")); + assert!(output.contains("10")); + } + + #[test] + fn test_subs_messages_error_rates_metric() { + let metrics = Metrics::random(); + + metrics + .subs_messages_error_rates + .with_label_values(&["wildcard_1", "timeout"]) + .inc_by(1); + + let metric_families = gather(); + let mut buffer = Vec::new(); + let encoder = TextEncoder::new(); + encoder.encode(&metric_families, &mut buffer).unwrap(); + + let output = String::from_utf8(buffer.clone()).unwrap(); + + assert!( + output.contains("ws_streamer_metrics_subs_messages_error_rates") + ); + assert!(output.contains("wildcard_1")); + assert!(output.contains("timeout")); + assert!(output.contains("1")); + } +} diff --git a/crates/fuel-streams-publisher/src/telemetry/mod.rs b/crates/sv-webserver/src/telemetry/mod.rs similarity index 66% rename from crates/fuel-streams-publisher/src/telemetry/mod.rs rename to crates/sv-webserver/src/telemetry/mod.rs index 13e1ebe1..db2a82db 100644 --- a/crates/fuel-streams-publisher/src/telemetry/mod.rs +++ b/crates/sv-webserver/src/telemetry/mod.rs @@ -1,5 +1,5 @@ mod elastic_search; -mod publisher; +pub mod metrics; mod runtime; #[allow(clippy::needless_borrows_for_generic_args)] mod system; @@ -13,10 +13,9 @@ use elastic_search::{ ElasticSearch, LogEntry, }; -use fuel_streams_core::prelude::*; +use metrics::Metrics; // TODO: Consider using tokio's Rwlock instead use parking_lot::RwLock; -use publisher::PublisherMetrics; use runtime::Runtime; use system::{System, SystemMetricsWrapper}; @@ -24,20 +23,20 @@ use system::{System, SystemMetricsWrapper}; pub struct Telemetry { runtime: Arc, system: Arc>, - publisher_metrics: Option>, + metrics: Option>, elastic_search: Option>, } impl Telemetry { const DEDICATED_THREADS: usize = 2; - pub async fn new() -> anyhow::Result> { + pub async fn new(prefix: Option) -> anyhow::Result> { let runtime = Runtime::new(Self::DEDICATED_THREADS, Duration::from_secs(20)); let system = Arc::new(RwLock::new(System::new().await)); - let publisher_metrics = if should_use_publisher_metrics() { - Some(Arc::new(PublisherMetrics::default())) + let metrics = if should_use_metrics() { + Some(Arc::new(Metrics::new(prefix)?)) } else { None }; @@ -51,7 +50,7 @@ impl Telemetry { Ok(Arc::new(Self { runtime: Arc::new(runtime), system, - publisher_metrics, + metrics, elastic_search, })) } @@ -98,116 +97,88 @@ impl Telemetry { } } - pub fn update_publisher_success_metrics( + pub fn update_user_subscription_metrics( &self, - subject: &str, - published_data_size: usize, - chain_id: &FuelCoreChainId, - block_producer: &Address, + user_id: uuid::Uuid, + subject_wildcard: &str, ) { self.maybe_use_metrics(|metrics| { - // Update message size histogram + // Increment total user subscribed messages metrics - .message_size_histogram + .user_subscribed_messages .with_label_values(&[ - &chain_id.to_string(), - &block_producer.to_string(), - subject, - ]) - .observe(published_data_size as f64); - - // Increment total published messages - metrics - .total_published_messages - .with_label_values(&[ - &chain_id.to_string(), - &block_producer.to_string(), + user_id.to_string().as_str(), + subject_wildcard, ]) .inc(); - // Increment throughput for the published messages + // Increment throughput for the subscribed messages metrics - .published_messages_throughput - .with_label_values(&[ - &chain_id.to_string(), - &block_producer.to_string(), - subject, - ]) + .subs_messages_throughput + .with_label_values(&[subject_wildcard]) .inc(); }); } - pub fn update_publisher_error_metrics( + pub fn update_error_metrics( &self, - subject: &str, - chain_id: &FuelCoreChainId, - block_producer: &Address, - error: &str, + subject_wildcard: &str, + error_type: &str, ) { self.maybe_use_metrics(|metrics| { metrics - .error_rates - .with_label_values(&[ - &chain_id.to_string(), - &block_producer.to_string(), - subject, - error, - ]) + .subs_messages_error_rates + .with_label_values(&[subject_wildcard, error_type]) .inc(); }); } - pub fn record_streams_count( - &self, - chain_id: &FuelCoreChainId, - count: usize, - ) { + pub fn increment_subscriptions_count(&self) { self.maybe_use_metrics(|metrics| { - metrics - .total_subs - .with_label_values(&[&chain_id.to_string()]) - .set(count as i64); + metrics.total_ws_subs.with_label_values(&[]).inc(); }); } - pub fn record_failed_publishing( + pub fn decrement_subscriptions_count(&self) { + self.maybe_use_metrics(|metrics| { + metrics.total_ws_subs.with_label_values(&[]).inc(); + }); + } + + pub fn update_unsubscribed( &self, - chain_id: &FuelCoreChainId, - block_producer: &Address, + user_id: uuid::Uuid, + subject_wildcard: &str, ) { self.maybe_use_metrics(|metrics| { metrics - .total_failed_messages - .with_label_values(&[ - &chain_id.to_string(), - &block_producer.to_string(), - ]) - .inc(); + .user_subscribed_messages + .with_label_values(&[&user_id.to_string(), subject_wildcard]) + .dec(); }); } pub fn maybe_use_metrics(&self, f: F) where - F: Fn(&PublisherMetrics), + F: Fn(&Metrics), { - if let Some(metrics) = &self.publisher_metrics { + if let Some(metrics) = &self.metrics { f(metrics); } } - // TODO: Break into smaller functions pub async fn get_metrics(&self) -> String { use prometheus::Encoder; let encoder = prometheus::TextEncoder::new(); - if self.publisher_metrics.is_none() { + if self.metrics.is_none() { return "".to_string(); } // fetch all measured metrics let mut buffer = Vec::new(); if let Err(e) = encoder.encode( - &self.publisher_metrics.as_ref().unwrap().registry.gather(), + &self.metrics.as_ref().unwrap().registry.gather(), &mut buffer, ) { tracing::error!("could not encode custom metrics: {}", e); @@ -272,6 +243,6 @@ impl Telemetry { } } -pub fn should_use_publisher_metrics() -> bool { - dotenvy::var("USE_PUBLISHER_METRICS").is_ok_and(|val| val == "true") +pub fn should_use_metrics() -> bool { + dotenvy::var("USE_METRICS").is_ok_and(|val| val == "true") } diff --git a/crates/fuel-streams-publisher/src/telemetry/runtime.rs b/crates/sv-webserver/src/telemetry/runtime.rs similarity index 100% rename from crates/fuel-streams-publisher/src/telemetry/runtime.rs rename to crates/sv-webserver/src/telemetry/runtime.rs diff --git a/crates/fuel-streams-publisher/src/telemetry/system.rs b/crates/sv-webserver/src/telemetry/system.rs similarity index 100% rename from crates/fuel-streams-publisher/src/telemetry/system.rs rename to crates/sv-webserver/src/telemetry/system.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 846fd568..e6465fd8 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -6,7 +6,6 @@ edition = "2021" [dependencies] anyhow = { workspace = true } -fuel-core-types = { workspace = true } fuel-streams = { workspace = true, features = ["test-helpers"] } futures = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } @@ -38,7 +37,3 @@ path = "utxos.rs" [[example]] name = "logs" path = "logs.rs" - -[[example]] -name = "multiple-streams" -path = "multiple-streams.rs" diff --git a/examples/blocks.rs b/examples/blocks.rs index e92a72e7..83a2a22f 100644 --- a/examples/blocks.rs +++ b/examples/blocks.rs @@ -20,32 +20,20 @@ use futures::StreamExt; #[tokio::main] async fn main() -> anyhow::Result<()> { // Initialize a client connection to the Fuel streaming service - let client = Client::connect(FuelNetwork::Testnet).await?; + let mut client = Client::new(FuelNetwork::Local).await?; + let mut connection = client.connect().await?; - // Create a new stream for blocks - let stream = fuel_streams::Stream::::new(&client).await; - - // Configure the stream to start from the last published block - let config = StreamConfig { - deliver_policy: DeliverPolicy::Last, - }; + println!("Listening for blocks..."); + let subject = BlocksSubject::new(); // Subscribe to the block stream with the specified configuration - let mut sub = stream.subscribe_with_config(config).await?; - - println!("Listening for blocks..."); + let mut stream = connection + .subscribe::(subject, DeliverPolicy::Last) + .await?; // Process incoming blocks - while let Some(bytes) = sub.next().await { - let message = bytes?; - let decoded_msg = Block::decode_raw(message.payload.to_vec()).await; - let tx_subject = decoded_msg.subject; - let tx_published_at = decoded_msg.timestamp; - - println!( - "Received block:\n Subject: {}\n Published at: {}\n Block: {:?}\n", - tx_subject, tx_published_at, decoded_msg.payload - ); + while let Some(block) = stream.next().await { + println!("Received block: {:?}", block); } Ok(()) diff --git a/examples/inputs.rs b/examples/inputs.rs index 10fcbac9..5e874cdf 100644 --- a/examples/inputs.rs +++ b/examples/inputs.rs @@ -20,32 +20,20 @@ use futures::StreamExt; #[tokio::main] async fn main() -> anyhow::Result<()> { // Initialize a client connection to the Fuel streaming service - let client = Client::connect(FuelNetwork::Testnet).await?; + let mut client = Client::new(FuelNetwork::Testnet).await?; + let mut connection = client.connect().await?; - // Create a new stream for inputs - let stream = fuel_streams::Stream::::new(&client).await; - - // Configure the stream to start from the last published input - let config = StreamConfig { - deliver_policy: DeliverPolicy::Last, - }; + println!("Listening for inputs..."); + let subject = InputsCoinSubject::new(); // Subscribe to the input stream with the specified configuration - let mut sub = stream.subscribe_with_config(config).await?; - - println!("Listening for inputs..."); + let mut stream = connection + .subscribe::(subject, DeliverPolicy::Last) + .await?; // Process incoming inputs - while let Some(bytes) = sub.next().await { - let message = bytes?; - let decoded_msg = Input::decode_raw(message.payload.to_vec()).await; - let tx_subject = decoded_msg.subject; - let tx_published_at = decoded_msg.timestamp; - - println!( - "Received input:\n Subject: {}\n Published at: {}\n Input: {:?}\n", - tx_subject, tx_published_at, decoded_msg.payload - ); + while let Some(input) = stream.next().await { + println!("Received input: {:?}", input); } Ok(()) diff --git a/examples/logs.rs b/examples/logs.rs index 5fc53cdd..d703be4e 100644 --- a/examples/logs.rs +++ b/examples/logs.rs @@ -20,32 +20,20 @@ use futures::StreamExt; #[tokio::main] async fn main() -> anyhow::Result<()> { // Initialize a client connection to the Fuel streaming service - let client = Client::connect(FuelNetwork::Testnet).await?; + let mut client = Client::new(FuelNetwork::Testnet).await?; + let mut connection = client.connect().await?; - // Create a new stream for logs - let stream = fuel_streams::Stream::::new(&client).await; - - // Configure the stream to start from the last published log - let config = StreamConfig { - deliver_policy: DeliverPolicy::Last, - }; + println!("Listening for logs..."); + let subject = LogsSubject::new(); // Subscribe to the log stream with the specified configuration - let mut sub = stream.subscribe_with_config(config).await?; - - println!("Listening for logs..."); + let mut stream = connection + .subscribe::(subject, DeliverPolicy::Last) + .await?; // Process incoming logs - while let Some(bytes) = sub.next().await { - let message = bytes?; - let decoded_msg = Log::decode_raw(message.payload.to_vec()).await; - let log_subject = decoded_msg.subject; - let log_published_at = decoded_msg.timestamp; - - println!( - "Received log:\n Subject: {}\n Published at: {}\n Log: {:?}\n", - log_subject, log_published_at, decoded_msg.payload - ); + while let Some(log) = stream.next().await { + println!("Received log: {:?}", log); } Ok(()) diff --git a/examples/multiple-streams.rs b/examples/multiple-streams.rs deleted file mode 100644 index ebc0e5c0..00000000 --- a/examples/multiple-streams.rs +++ /dev/null @@ -1,360 +0,0 @@ -// Copyright 2024 Fuel Labs -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use fuel_core_types::fuel_tx::ContractId; -use fuel_streams::{ - client::Client, - subjects::*, - types::*, - Filter, - StreamConfig, - StreamEncoder, -}; -use futures::{future::try_join_all, StreamExt}; - -// This example demonstrates how to use the fuel-streams library to subscribe to multiple streams. -#[tokio::main] -async fn main() -> Result<(), anyhow::Error> { - // initialize a client - let client = Client::connect(FuelNetwork::Testnet).await?; - - let mut handles = vec![]; - - // stream blocks - let stream_client = client.clone(); - handles.push(tokio::spawn(async move { - stream_blocks(&stream_client, None).await.unwrap(); - })); - - // stream blocks with filter - let stream_client = client.clone(); - handles.push(tokio::spawn(async move { - let filter = Filter::::build() - .with_producer(Some(Address::zeroed())) - .with_height(Some(5.into())); - stream_blocks(&stream_client, Some(filter)).await.unwrap(); - })); - - // stream transactions - let txs_client = client.clone(); - handles.push(tokio::spawn(async move { - stream_transactions(&txs_client, None).await.unwrap(); - })); - - // stream transactions with filter - let txs_client = client.clone(); - handles.push(tokio::spawn(async move { - let filter = Filter::::build() - .with_block_height(Some(5.into())) - .with_kind(Some(TransactionKind::Mint)); - stream_transactions(&txs_client, Some(filter)) - .await - .unwrap(); - })); - - // stream contract receipts - handles.push(tokio::spawn({ - let contract_client = client.clone(); - // Replace with an actual contract ID - let contract_id = ContractId::from([0u8; 32]); - async move { - stream_contract(&contract_client, contract_id) - .await - .unwrap(); - } - })); - - // stream transactions by contract ID - handles.push(tokio::spawn({ - let txs_client = client.clone(); - // Replace with an actual contract ID - let contract_id = ContractId::from([0u8; 32]); - async move { - stream_transactions_by_contract(&txs_client, contract_id) - .await - .unwrap(); - } - })); - - // stream inputs by contract ID - handles.push(tokio::spawn({ - let inputs_client = client.clone(); - // Replace with an actual contract ID - let contract_id = ContractId::from([0u8; 32]); - async move { - stream_inputs_by_contract(&inputs_client, contract_id) - .await - .unwrap(); - } - })); - - // stream receipts by contract ID - handles.push(tokio::spawn({ - let receipts_client = client.clone(); - // Replace with an actual contract ID - let contract_id = ContractId::from([0u8; 32]); - async move { - stream_receipts_by_contract(&receipts_client, contract_id) - .await - .unwrap(); - } - })); - - // await all handles - try_join_all(handles).await?; - - Ok(()) -} - -async fn stream_blocks( - client: &Client, - filter: Option, -) -> anyhow::Result<()> { - let mut block_stream = fuel_streams::Stream::::new(client).await; - - let mut sub = match filter { - Some(filter) => block_stream.with_filter(filter).subscribe().await?, - None => block_stream.subscribe().await?, - }; - while let Some(bytes) = sub.next().await { - let decoded_msg = Block::decode_raw(bytes.unwrap()).await; - let block_height = decoded_msg.payload.height; - let block_subject = decoded_msg.subject; - let block_published_at = decoded_msg.timestamp; - println!( - "Received block: height={}, subject={}, published_at={}", - block_height, block_subject, block_published_at - ) - } - - Ok(()) -} - -async fn stream_transactions( - client: &Client, - filter: Option, -) -> anyhow::Result<()> { - let mut txs_stream = fuel_streams::Stream::::new(client).await; - - // here we apply a config to the streaming to start getting only from the last published transaction onwards - let config = StreamConfig { - deliver_policy: DeliverPolicy::Last, - }; - - let mut sub = match filter { - Some(filter) => { - txs_stream - .with_filter(filter) - .subscribe_with_config(config) - .await? - } - None => txs_stream.subscribe_with_config(config).await?, - }; - - while let Some(bytes) = sub.next().await { - let message = bytes?; - let decoded_msg = - Transaction::decode_raw(message.payload.to_vec()).await; - let tx = decoded_msg.payload; - let tx_subject = decoded_msg.subject; - let tx_published_at = decoded_msg.timestamp; - println!( - "Received transaction: data={:?}, subject={}, published_at={}", - tx, tx_subject, tx_published_at - ) - } - Ok(()) -} - -/// Streams transactions associated with a specific contract ID. -/// -/// This function creates a filtered stream of transactions related to the given contract ID -/// and processes each received transaction by printing its details. -/// -/// # Arguments -/// -/// * `client` - A reference to the NATS client used for streaming. -/// * `contract_id` - The `ContractId` to filter transactions by. -/// -/// # Returns -/// -/// Returns `Ok(())` if the stream processes successfully, or an error if there are any issues. -async fn stream_transactions_by_contract( - client: &Client, - contract_id: ContractId, -) -> anyhow::Result<()> { - let mut txs_stream = fuel_streams::Stream::::new(client).await; - - // Build a filter for transactions by contract ID - let filter = Filter::::build() - .with_id_kind(Some(IdentifierKind::ContractID)) - .with_id_value(Some((*contract_id).into())); - - // Filtered stream - let mut sub = txs_stream.with_filter(filter).subscribe().await?; - - while let Some(bytes) = sub.next().await { - let decoded_msg = Transaction::decode_raw(bytes.unwrap()).await; - let tx = decoded_msg.payload; - let tx_subject = decoded_msg.subject; - let tx_published_at = decoded_msg.timestamp; - println!( - "Received transaction for contract: data={:?}, subject={}, published_at={}", - tx, tx_subject, tx_published_at - ); - } - Ok(()) -} - -/// Subscribes to receipts related to a specific contract, effectively listening to contract events. -/// -/// This function creates a stream that subscribes to various types of receipts (except `ScriptResult` -/// and `MessageOut`) that are associated with the specified contract ID. It's a way to monitor -/// contract-related events such as calls, returns, logs, transfers, mints, and burns. -/// -/// The function filters the receipts to ensure they match the given contract ID before processing them. -/// This approach allows for efficient monitoring of contract activities without the need to process -/// irrelevant receipts. -/// -/// # Arguments -/// -/// * `client` - A reference to the NATS client used for streaming. -/// * `contract_id` - The ID of the contract to monitor. -/// -/// # Returns -/// -/// Returns `Ok(())` if the streaming completes successfully, or an error if there are any issues. -async fn stream_contract( - client: &Client, - contract_id: ContractId, -) -> anyhow::Result<()> { - let mut receipt_stream = fuel_streams::Stream::::new(client).await; - - receipt_stream.with_filter( - ReceiptsBurnSubject::new().with_contract_id(Some(contract_id.into())), - ); - receipt_stream.with_filter( - ReceiptsCallSubject::new().with_from(Some(contract_id.into())), - ); - receipt_stream.with_filter( - ReceiptsReturnSubject::new().with_id(Some(contract_id.into())), - ); - receipt_stream.with_filter( - ReceiptsReturnDataSubject::new().with_id(Some(contract_id.into())), - ); - receipt_stream.with_filter( - ReceiptsPanicSubject::new().with_id(Some(contract_id.into())), - ); - receipt_stream.with_filter( - ReceiptsRevertSubject::new().with_id(Some(contract_id.into())), - ); - receipt_stream.with_filter( - ReceiptsLogSubject::new().with_id(Some(contract_id.into())), - ); - receipt_stream.with_filter( - ReceiptsLogDataSubject::new().with_id(Some(contract_id.into())), - ); - receipt_stream.with_filter( - ReceiptsTransferSubject::new().with_from(Some(contract_id.into())), - ); - receipt_stream.with_filter( - ReceiptsTransferOutSubject::new().with_from(Some(contract_id.into())), - ); - receipt_stream.with_filter( - ReceiptsMintSubject::new().with_contract_id(Some(contract_id.into())), - ); - - Ok(()) -} - -/// Streams inputs related to a specific contract ID. -/// -/// This function creates a filtered stream of inputs associated with the given contract ID -/// and processes each received input by printing its details. -/// -/// # Arguments -/// -/// * `client` - A reference to the NATS client used for streaming. -/// * `contract_id` - The `ContractId` to filter inputs by. -/// -/// # Returns -/// -/// Returns `Ok(())` if the stream processes successfully, or an error if there are any issues. -async fn stream_inputs_by_contract( - client: &Client, - contract_id: ContractId, -) -> anyhow::Result<()> { - let mut inputs_stream = fuel_streams::Stream::::new(client).await; - - inputs_stream.with_filter( - InputsByIdSubject::new() - .with_id_kind(Some(IdentifierKind::ContractID)) - .with_id_value(Some((*contract_id).into())), - ); - - let mut sub = inputs_stream.subscribe().await?; - - while let Some(bytes) = sub.next().await { - let decoded_msg = Input::decode_raw(bytes.unwrap().to_vec()).await; - let input = decoded_msg.payload; - let input_subject = decoded_msg.subject; - let input_published_at = decoded_msg.timestamp; - println!( - "Received input for contract: data={:?}, subject={}, published_at={}", - input, input_subject, input_published_at - ); - } - - Ok(()) -} - -/// Streams receipts associated with a specific contract ID. -/// -/// This function creates a filtered stream of receipts related to the given contract ID -/// and processes each received receipt by printing its details. -/// -/// # Arguments -/// -/// * `client` - A reference to the NATS client used for streaming. -/// * `contract_id` - The `ContractId` to filter receipts by. -/// -/// # Returns -/// -/// Returns `Ok(())` if the stream processes successfully, or an error if there are any issues. -async fn stream_receipts_by_contract( - client: &Client, - contract_id: ContractId, -) -> anyhow::Result<()> { - let mut receipt_stream = fuel_streams::Stream::::new(client).await; - - receipt_stream.with_filter( - ReceiptsByIdSubject::new() - .with_id_kind(Some(IdentifierKind::ContractID)) - .with_id_value(Some((*contract_id).into())), - ); - - let mut sub = receipt_stream.subscribe().await?; - - while let Some(bytes) = sub.next().await { - let decoded_msg = Receipt::decode_raw(bytes.unwrap().to_vec()).await; - let receipt = decoded_msg.payload; - let receipt_subject = decoded_msg.subject; - let receipt_published_at = decoded_msg.timestamp; - println!( - "Received receipt for contract: data={:?}, subject={}, published_at={}", - receipt, receipt_subject, receipt_published_at - ); - } - - Ok(()) -} diff --git a/examples/outputs.rs b/examples/outputs.rs index 83f2be46..6e069d52 100644 --- a/examples/outputs.rs +++ b/examples/outputs.rs @@ -20,32 +20,20 @@ use futures::StreamExt; #[tokio::main] async fn main() -> anyhow::Result<()> { // Initialize a client connection to the Fuel streaming service - let client = Client::connect(FuelNetwork::Testnet).await?; + let mut client = Client::new(FuelNetwork::Testnet).await?; + let mut connection = client.connect().await?; - // Create a new stream for outputs - let stream = fuel_streams::Stream::::new(&client).await; - - // Configure the stream to start from the last published output - let config = StreamConfig { - deliver_policy: DeliverPolicy::Last, - }; + println!("Listening for outputs..."); + let subject = OutputsCoinSubject::new(); // Subscribe to the output stream with the specified configuration - let mut sub = stream.subscribe_with_config(config).await?; - - println!("Listening for outputs..."); + let mut stream = connection + .subscribe::(subject, DeliverPolicy::Last) + .await?; // Process incoming outputs - while let Some(bytes) = sub.next().await { - let message = bytes?; - let decoded_msg = Output::decode_raw(message.payload.to_vec()).await; - let tx_subject = decoded_msg.subject; - let tx_published_at = decoded_msg.timestamp; - - println!( - "Received output:\n Subject: {}\n Published at: {}\n Output: {:?}\n", - tx_subject, tx_published_at, decoded_msg.payload - ); + while let Some(output) = stream.next().await { + println!("Received output: {:?}", output); } Ok(()) diff --git a/examples/receipts.rs b/examples/receipts.rs index 8160e436..ae0f38dc 100644 --- a/examples/receipts.rs +++ b/examples/receipts.rs @@ -11,107 +11,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -use anyhow::Result; -use fuel_streams::{prelude::*, receipts::*}; +use fuel_streams::prelude::*; use futures::StreamExt; /// The contract ID to stream the receipts for. For this example, we're using the contract ID of the https://thundernft.market/ const CONTRACT_ID: &str = "0x243ef4c2301f44eecbeaf1c39fee9379664b59a2e5b75317e8c7e7f26a25ed4d"; -/// Subscribes to receipts related to a specific contract, effectively listening to contract events. -/// -/// This function creates a stream that subscribes to various types of receipts -/// -/// The function filters the receipts to ensure they match the given contract ID before processing them. -/// This approach allows for efficient monitoring of contract activities without the need to process -/// irrelevant receipts. -/// -/// # Arguments -/// -/// * `client` - A reference to the NATS client used for streaming. -/// * `contract_id` - The ID of the contract to monitor. -/// -/// # Returns -/// -/// Returns `Ok(())` if the streaming completes successfully, or an error if there are any issues. - -// This example demonstrates how to use the fuel-streams library to stream -// receipts from a Fuel network. It connects to a streaming service, -// subscribes to a receipt for a given contract, and prints incoming receipts. #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> anyhow::Result<()> { // Initialize a client connection to the Fuel streaming service - let client = Client::connect(FuelNetwork::Testnet).await?; - - let contract_id: ContractId = CONTRACT_ID.into(); - - // Create a new stream for receipts - let mut receipt_stream = - fuel_streams::Stream::::new(&client).await; + let mut client = Client::new(FuelNetwork::Testnet).await?; + let mut connection = client.connect().await?; - // Use multiple filters to subscribe to different types of receipts (all receipts except - // `ScriptResult` and `MessageOut`) that are associated with the specified contract ID. - // It's a way to monitor all contract-related events such as calls, returns, logs, transfers, - // mints, and burns. - receipt_stream.with_filter( - ReceiptsBurnSubject::default() - .with_contract_id(Some(contract_id.clone())), - ); - receipt_stream.with_filter( - ReceiptsCallSubject::default().with_from(Some(contract_id.clone())), - ); - receipt_stream.with_filter( - ReceiptsReturnSubject::default().with_id(Some(contract_id.clone())), - ); - receipt_stream.with_filter( - ReceiptsReturnDataSubject::default().with_id(Some(contract_id.clone())), - ); - receipt_stream.with_filter( - ReceiptsPanicSubject::default().with_id(Some(contract_id.clone())), - ); - receipt_stream.with_filter( - ReceiptsRevertSubject::default().with_id(Some(contract_id.clone())), - ); - receipt_stream.with_filter( - ReceiptsLogSubject::default().with_id(Some(contract_id.clone())), - ); - receipt_stream.with_filter( - ReceiptsLogDataSubject::default().with_id(Some(contract_id.clone())), - ); - receipt_stream.with_filter( - ReceiptsTransferSubject::default().with_from(Some(contract_id.clone())), - ); - receipt_stream.with_filter( - ReceiptsTransferOutSubject::default() - .with_from(Some(contract_id.clone())), - ); - receipt_stream.with_filter( - ReceiptsMintSubject::default() - .with_contract_id(Some(contract_id.clone())), - ); - - // Configure the stream to start from the first published receipt - let config = StreamConfig { - deliver_policy: DeliverPolicy::All, - }; + println!("Listening for receipts..."); - // Subscribe to the receipt stream - let mut sub = receipt_stream.subscribe_with_config(config).await?; + // Create a subject for all receipt types related to the contract + let subject = ReceiptsByIdSubject::new() + .with_id_kind(Some(IdentifierKind::ContractID)) + .with_id_value(Some(CONTRACT_ID.into())); - println!("Listening for receipts..."); + // Subscribe to the receipt stream with the specified configuration + let mut stream = connection + .subscribe::(subject, DeliverPolicy::All) + .await?; // Process incoming receipts - while let Some(bytes) = sub.next().await { - let message = bytes.unwrap(); - let decoded_msg = Receipt::decode_raw(message.payload.to_vec()).await; - let receipt = decoded_msg.payload; - let receipt_subject = decoded_msg.subject; - let receipt_published_at = decoded_msg.timestamp; - println!( - "Received receipt:\n Subject: {}\n Published at: {}\n Data: {:?}\n", - receipt_subject, receipt_published_at, receipt - ); + while let Some(receipt) = stream.next().await { + println!("Received receipt: {:?}", receipt); } Ok(()) diff --git a/examples/transactions.rs b/examples/transactions.rs index 5294ec44..0b8b8585 100644 --- a/examples/transactions.rs +++ b/examples/transactions.rs @@ -20,34 +20,23 @@ use futures::StreamExt; #[tokio::main] async fn main() -> anyhow::Result<()> { // Initialize a client connection to the Fuel streaming service - let client = Client::connect(FuelNetwork::Testnet).await?; + let mut client = Client::new(FuelNetwork::Testnet).await?; + let mut connection = client.connect().await?; - // Create a new stream for transactions - let stream = fuel_streams::Stream::::new(&client).await; + println!("Listening for transactions..."); - // Configure the stream to start from the last published transaction - let config = StreamConfig { - deliver_policy: DeliverPolicy::Last, - }; + // Create a subject for all transactions + let subject = + TransactionsSubject::new().with_kind(Some(TransactionKind::Script)); // Example: filter for script transactions // Subscribe to the transaction stream with the specified configuration - let mut sub = stream.subscribe_with_config(config).await?; - - println!("Listening for transactions..."); + let mut stream = connection + .subscribe::(subject, DeliverPolicy::Last) + .await?; // Process incoming transactions - while let Some(bytes) = sub.next().await { - let message = bytes?; - let decoded_msg = - Transaction::decode_raw(message.payload.to_vec()).await; - let tx = decoded_msg.payload; - let tx_subject = decoded_msg.subject; - let tx_published_at = decoded_msg.timestamp; - - println!( - "Received transaction:\n Subject: {}\n Published at: {}\n Data: {:?}\n", - tx_subject, tx_published_at, tx - ); + while let Some(transaction) = stream.next().await { + println!("Received transaction: {:?}", transaction); } Ok(()) diff --git a/examples/utxos.rs b/examples/utxos.rs index fabcc31f..052436e5 100644 --- a/examples/utxos.rs +++ b/examples/utxos.rs @@ -20,32 +20,22 @@ use futures::StreamExt; #[tokio::main] async fn main() -> anyhow::Result<()> { // Initialize a client connection to the Fuel streaming service - let client = Client::connect(FuelNetwork::Testnet).await?; + let mut client = Client::new(FuelNetwork::Testnet).await?; + let mut connection = client.connect().await?; - // Create a new stream for UTXOs - let stream = fuel_streams::Stream::::new(&client).await; + println!("Listening for UTXOs..."); - // Configure the stream to start from the last published UTXO - let config = StreamConfig { - deliver_policy: DeliverPolicy::Last, - }; + // Create a subject for all UTXOs, optionally filter by type + let subject = UtxosSubject::new().with_utxo_type(Some(UtxoType::Message)); // Example: filter for message UTXOs // Subscribe to the UTXO stream with the specified configuration - let mut sub = stream.subscribe_with_config(config).await?; - - println!("Listening for UTXOs..."); + let mut stream = connection + .subscribe::(subject, DeliverPolicy::Last) + .await?; // Process incoming UTXOs - while let Some(bytes) = sub.next().await { - let message = bytes?; - let decoded_msg = Utxo::decode_raw(message.payload.to_vec()).await; - let utxo_subject = decoded_msg.subject; - let utxo_published_at = decoded_msg.timestamp; - - println!( - "Received UTXO:\n Subject: {}\n Published at: {}\n UTXO: {:?}\n", - utxo_subject, utxo_published_at, decoded_msg.payload - ); + while let Some(utxo) = stream.next().await { + println!("Received UTXO: {:?}", utxo); } Ok(()) diff --git a/knope.toml b/knope.toml index 0f603216..57f4fc54 100644 --- a/knope.toml +++ b/knope.toml @@ -1,3 +1,6 @@ +# ------------------------------------------------------------ +# Fuel-streams package +# ------------------------------------------------------------ [packages.fuel-streams] versioned_files = ["crates/fuel-streams/Cargo.toml"] changelog = "CHANGELOG.md" @@ -16,24 +19,6 @@ extra_changelog_sections = [ ], name = "📝 Notes" }, ] -[[packages.fuel-streams.assets]] -path = "artifacts/fuel-streams-publisher-Linux-aarch64-gnu.tar.gz" - -[[packages.fuel-streams.assets]] -path = "artifacts/fuel-streams-publisher-Linux-aarch64-musl.tar.gz" - -[[packages.fuel-streams.assets]] -path = "artifacts/fuel-streams-publisher-Linux-x86_64-gnu.tar.gz" - -[[packages.fuel-streams.assets]] -path = "artifacts/fuel-streams-publisher-Linux-x86_64-musl.tar.gz" - -[[packages.fuel-streams.assets]] -path = "artifacts/fuel-streams-publisher-macOS-aarch64.tar.gz" - -[[packages.fuel-streams.assets]] -path = "artifacts/fuel-streams-publisher-macOS-x86_64.tar.gz" - # ------------------------------------------------------------ # Workflow to get the current version # ------------------------------------------------------------ diff --git a/scripts/run_publisher.sh b/scripts/run_publisher.sh index b6240036..3ffa1868 100755 --- a/scripts/run_publisher.sh +++ b/scripts/run_publisher.sh @@ -9,7 +9,7 @@ set -e usage() { echo "Usage: $0 [options]" echo "Options:" - echo " --network : Specify the network (mainnet|testnet)" + echo " --network : Specify the network (mainnet|testnet|local)" echo " Default: testnet" echo " --mode : Specify the run mode (dev|profiling)" echo " Default: profiling" @@ -33,7 +33,6 @@ NETWORK=${NETWORK:-"testnet"} MODE=${MODE:-"profiling"} PORT=${PORT:-"4004"} TELEMETRY_PORT=${TELEMETRY_PORT:-"8080"} -PACKAGE=${PACKAGE:-"fuel-streams-publisher"} while [[ "$#" -gt 0 ]]; do case $1 in @@ -117,17 +116,19 @@ COMMON_ARGS=( "--relayer-da-deploy-height=${RELAYER_DA_DEPLOY_HEIGHT}" "--relayer-log-page-size=${RELAYER_LOG_PAGE_SIZE}" "--sync-block-stream-buffer-size" "50" - "--nats-url" "nats://localhost:4222" "--max-database-cache-size" "17179869184" "--state-rewind-duration" "136y" "--request-timeout" "60" "--graphql-max-complexity" "1000000000" + # Application specific + "--nats-url" "nats://localhost:4222" + # "--telemetry-port" "${TELEMETRY_PORT}" ) # Execute based on mode if [ "$MODE" == "dev" ]; then - cargo run -p ${PACKAGE} -- "${COMMON_ARGS[@]}" ${EXTRA_ARGS} + cargo run -p sv-publisher -- "${COMMON_ARGS[@]}" ${EXTRA_ARGS} else - cargo build --profile profiling --package ${PACKAGE} - samply record ./target/profiling/${PACKAGE} "${COMMON_ARGS[@]}" ${EXTRA_ARGS} + cargo build --profile profiling --package sv-publisher + samply record ./target/profiling/sv-publisher "${COMMON_ARGS[@]}" ${EXTRA_ARGS} fi diff --git a/scripts/run_webserver.sh b/scripts/run_webserver.sh new file mode 100755 index 00000000..5b91c749 --- /dev/null +++ b/scripts/run_webserver.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +# Exit immediately if a command exits with a non-zero status +set -e + +# Load environment variables with defaults +PORT=${PORT:-9003} +NATS_URL=${NATS_URL:-nats://localhost:4222} +MODE=${MODE:-dev} +EXTRA_ARGS=${EXTRA_ARGS:-""} + +# ------------------------------ +# Function to Display Usage +# ------------------------------ +usage() { + echo "Usage: $0 [options]" + echo "Options:" + echo " --mode : Specify the run mode (dev|profiling)" + echo " --port : Port number for the API server (default: 9003)" + echo " --nats-url : NATS URL (default: nats://localhost:4222)" + echo " --extra-args : Optional additional arguments to append (in quotes)" + echo "" + echo "Examples:" + echo " $0 # Runs with all defaults" + echo " $0 --mode dev --port 8080 # Custom port" + echo " $0 --mode dev --extra-args '\"--use-metrics\"' # Enable metrics" + exit 1 +} + +while [[ "$#" -gt 0 ]]; do + case $1 in + --mode) + MODE="$2" + shift 2 + ;; + --port) + PORT="$2" + shift 2 + ;; + --nats-url) + NATS_URL="$2" + shift 2 + ;; + --extra-args) + EXTRA_ARGS="$2" + shift 2 + ;; + --help) + usage + ;; + *) + echo "Error: Unknown parameter passed: $1" >&2 + usage + ;; + esac +done + +# ------------------------------ +# Load Environment +# ------------------------------ +source ./scripts/set_env.sh NATS_URL=${NATS_URL} + +# Print the configuration being used +echo -e "\n==========================================" +echo "⚙️ Configuration" +echo -e "==========================================" + +# Runtime Configuration +echo "Runtime Settings:" +echo "→ Mode: ${MODE:-dev}" +echo "→ API Port: ${PORT:-9003}" +echo "→ NATS URL: ${NATS_URL:-"nats://localhost:4222"}" +if [ -n "$EXTRA_ARGS" ]; then + echo "→ Extra Arguments: $EXTRA_ARGS" +fi + +echo -e "==========================================\n" + +# Define common arguments +COMMON_ARGS=( + "--port" "${PORT:-9003}" + "--nats-url" "${NATS_URL:-"nats://localhost:4222"}" +) + +# Execute based on mode +if [ "${MODE:-dev}" == "dev" ]; then + cargo run -p sv-webserver -- "${COMMON_ARGS[@]}" ${EXTRA_ARGS} +else + cargo build --profile profiling --package sv-webserver + samply record ./target/profiling/sv-webserver "${COMMON_ARGS[@]}" ${EXTRA_ARGS} +fi diff --git a/scripts/set_env.sh b/scripts/set_env.sh index 9b67f661..36efa113 100755 --- a/scripts/set_env.sh +++ b/scripts/set_env.sh @@ -47,6 +47,7 @@ cleanup_env load_env # Set and export network-specific variables +export NETWORK=$NETWORK export RESERVED_NODES=$(eval echo "\$${NETWORK_UPPER}_RESERVED_NODES") export RELAYER_V2_LISTENING_CONTRACTS=$(eval echo "\$${NETWORK_UPPER}_RELAYER_V2_LISTENING_CONTRACTS") export RELAYER_DA_DEPLOY_HEIGHT=$(eval echo "\$${NETWORK_UPPER}_RELAYER_DA_DEPLOY_HEIGHT") @@ -54,9 +55,6 @@ export RELAYER=$(eval echo "\$${NETWORK_UPPER}_RELAYER") export SYNC_HEADER_BATCH_SIZE=$(eval echo "\$${NETWORK_UPPER}_SYNC_HEADER_BATCH_SIZE") export RELAYER_LOG_PAGE_SIZE=$(eval echo "\$${NETWORK_UPPER}_RELAYER_LOG_PAGE_SIZE") export CHAIN_CONFIG=$NETWORK -export NETWORK=$NETWORK -export USE_PUBLISHER_METRICS="$(echo "$USE_PUBLISHER_METRICS")" -export USE_ELASTIC_LOGGING="$(echo "$USE_ELASTIC_LOGGING")" # Append network-specific variables to .env file { @@ -71,6 +69,4 @@ export USE_ELASTIC_LOGGING="$(echo "$USE_ELASTIC_LOGGING")" echo "SYNC_HEADER_BATCH_SIZE=$SYNC_HEADER_BATCH_SIZE" echo "RELAYER_LOG_PAGE_SIZE=$RELAYER_LOG_PAGE_SIZE" echo "CHAIN_CONFIG=$CHAIN_CONFIG" - echo "USE_PUBLISHER_METRICS=$USE_PUBLISHER_METRICS" - echo "USE_ELASTIC_LOGGING=$USE_ELASTIC_LOGGING" } >> .env diff --git a/tarpaulin.toml b/tarpaulin.toml index 1bc0cf6d..ce0c18cd 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -63,10 +63,10 @@ engine = "Llvm" # ========================================== # ignore due to wasm incompatibility -# [cov_fuel_streams_publisher] +# [cov_sv_publisher] # name = "Fuel Streams Publisher Coverage Analysis" # packages = [ -# "fuel-streams-publisher" +# "sv-publisher" # ] # all-features = true # run-types = [ diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 943b0e62..bb1077fe 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -17,21 +17,10 @@ harness = true name = "integration_tests" path = "tests/lib.rs" -[[bin]] -name = "special_integration_tests" -path = "src/main.rs" - [dependencies] -anyhow = { workspace = true } -async-trait = { workspace = true } fuel-core = { workspace = true, features = ["test-helpers"] } -fuel-core-importer = { workspace = true, features = ["test-helpers"] } -fuel-core-types = { workspace = true, features = ["test-helpers"] } fuel-streams = { workspace = true, features = ["test-helpers"] } fuel-streams-core = { workspace = true, features = ["test-helpers"] } -fuel-streams-publisher = { workspace = true, features = ["test-helpers"] } -futures = { workspace = true } -rand = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros", "test-util"] } [dev-dependencies] diff --git a/tests/src/lib.rs b/tests/src/lib.rs index ac6267df..394fc05c 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,5 +1,6 @@ -use std::time::Duration; +use std::{sync::Arc, time::Duration}; +use fuel_streams::{client::Client, Connection, FuelNetwork}; use fuel_streams_core::{ nats::NatsClient, prelude::*, @@ -20,9 +21,13 @@ pub struct Streams { } impl Streams { - pub async fn new(client: &NatsClient) -> Self { - let blocks = Stream::::get_or_init(client).await; - let transactions = Stream::::get_or_init(client).await; + pub async fn new( + nats_client: &NatsClient, + s3_client: &Arc, + ) -> Self { + let blocks = Stream::::get_or_init(nats_client, s3_client).await; + let transactions = + Stream::::get_or_init(nats_client, s3_client).await; Self { transactions, blocks, @@ -30,12 +35,20 @@ impl Streams { } } -pub async fn server_setup() -> BoxedResult<(NatsClient, Streams)> { - let opts = NatsClientOpts::admin_opts(Some(FuelNetwork::Local)) - .with_rdn_namespace(); - let client = NatsClient::connect(&opts).await?; - let streams = Streams::new(&client).await; - Ok((client, streams)) +pub async fn server_setup() -> BoxedResult<(NatsClient, Streams, Connection)> { + let nats_client_opts = NatsClientOpts::admin_opts().with_rdn_namespace(); + let nats_client = NatsClient::connect(&nats_client_opts).await?; + + let s3_client_opts = S3ClientOpts::admin_opts().with_random_namespace(); + let s3_client = Arc::new(S3Client::new(&s3_client_opts).await?); + s3_client.create_bucket().await?; + + let streams = Streams::new(&nats_client, &s3_client).await; + + let mut client = Client::new(FuelNetwork::Local).await?; + let connection = client.connect().await?; + + Ok((nats_client, streams, connection)) } pub fn publish_items( @@ -49,8 +62,10 @@ pub fn publish_items( for item in items { tokio::time::sleep(Duration::from_millis(50)).await; let payload = item.1.clone(); - let subject = item.0; - stream.publish(&subject, &payload).await.unwrap(); + let subject = Arc::new(item.0); + let packet = payload.to_packet(subject); + + stream.publish(&packet).await.unwrap(); } } }) diff --git a/tests/src/main.rs b/tests/src/main.rs deleted file mode 100644 index 159bb8af..00000000 --- a/tests/src/main.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::{ - env, - fs, - ops::Range, - path::{Path, PathBuf}, - process::Command, - time::Duration, -}; - -use fuel_streams::prelude::*; -use fuel_streams_core::prelude::*; -use futures::StreamExt; -use rand::Rng; -use streams_tests::{publish_blocks, server_setup}; - -const INTERVAL: Range = 10..15; - -fn find_workspace_root() -> Option { - let mut current_dir = env::current_dir().ok()?; - - loop { - if current_dir.join("Cargo.toml").exists() { - // Check if this is a workspace root - let cargo_toml = current_dir.join("Cargo.toml"); - let content = fs::read_to_string(&cargo_toml).ok()?; - if content.contains("[workspace]") { - return Some(current_dir); - } - } - - if !current_dir.pop() { - break; - } - } - - None -} - -fn start_nats(makefile_path: &Path) { - let status = Command::new("make") - .arg("-f") - .arg(makefile_path.to_str().unwrap()) - .arg("cluster_up") - .status() - .expect("Failed to start NATS"); - - if status.success() { - println!("NATS started successfully."); - } else { - println!("Failed to start NATS."); - } -} - -fn stop_nats(makefile_path: &Path) { - let status = Command::new("make") - .arg("-f") - .arg(makefile_path.to_str().unwrap()) - .arg("cluster_up") - .status() - .expect("Failed to stop NATS"); - - if status.success() { - println!("NATS stopped successfully."); - } else { - println!("Failed to stop NATS."); - } -} - -#[tokio::main] -async fn main() -> BoxedResult<()> { - let workspace_root = - find_workspace_root().expect("Could not find the workspace root"); - let makefile_path = workspace_root.join("Makefile"); - env::set_current_dir(&workspace_root) - .expect("Failed to change directory to workspace root"); - - // ensure nats is connected and running - let client_opts = NatsClientOpts::admin_opts(Some(FuelNetwork::Local)) - .with_rdn_namespace() - .with_timeout(1); - let is_connected = Client::with_opts(&client_opts) - .await - .ok() - .map(|c| c.conn.is_connected()) - .unwrap_or_default(); - if !is_connected { - println!("Starting nats ..."); - start_nats(&makefile_path); - } - - // create a subscription - let (conn, _) = server_setup().await.unwrap(); - let client = Client::with_opts(&conn.opts).await.unwrap(); - let stream = fuel_streams::Stream::::new(&client).await; - let mut sub = stream.subscribe().await.unwrap().enumerate(); - - // publish all items in a separate thread - let (items, publish_join_handle) = - publish_blocks(stream.stream(), Some(Address::zeroed()), None).unwrap(); - - // await publishing to finish - publish_join_handle.await.unwrap(); - println!("All items pushed to nats !"); - - let mut rng = rand::thread_rng(); - let mut action_interval = - tokio::time::interval(Duration::from_secs(rng.gen_range(INTERVAL))); - - loop { - tokio::select! { - bytes = sub.next() => { - let (index, bytes) = bytes.unzip(); - if let Some(bytes) = bytes.flatten() { - println!("Valid subscription"); - let decoded_msg = Block::decode_raw(bytes).await; - let (subject, block) = items[index.unwrap()].to_owned(); - let height = decoded_msg.payload.height; - assert_eq!(decoded_msg.subject, subject.parse()); - assert_eq!(decoded_msg.payload, block); - assert_eq!(height, index.unwrap() as u32); - if index.unwrap() == 9 { - break; - } - } - } - _ = action_interval.tick() => { - let client_opts = NatsClientOpts::admin_opts(Some(FuelNetwork::Local)) - .with_rdn_namespace() - .with_timeout(1); - let is_nats_connected = Client::with_opts(&client_opts).await.ok().map(|c| c.conn.is_connected()).unwrap_or_default(); - if is_nats_connected { - stop_nats(&makefile_path); - } else { - start_nats(&makefile_path); - } - action_interval = tokio::time::interval(Duration::from_secs(rng.gen_range(INTERVAL))); - } - } - } - - Ok(()) -} diff --git a/tests/tests/client.rs b/tests/tests/client.rs index 06831bbd..04e9c880 100644 --- a/tests/tests/client.rs +++ b/tests/tests/client.rs @@ -1,315 +1,342 @@ -use std::{collections::HashSet, time::Duration}; - -use fuel_streams::prelude::*; -use fuel_streams_core::prelude::{types, *}; -use futures::{ - future::{try_join_all, BoxFuture}, - FutureExt, - StreamExt, - TryStreamExt, -}; -use rand::{distributions::Alphanumeric, Rng}; -use streams_tests::{publish_blocks, server_setup}; -use tokio::time::timeout; - -fn gen_random_string(size: usize) -> String { - rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(size) - .map(char::from) - .collect() -} - -#[tokio::test] -async fn conn_streams_has_required_streams() -> BoxedResult<()> { - let (client, streams) = server_setup().await.unwrap(); - let mut context_streams = client.jetstream.stream_names(); - - let mut names = HashSet::new(); - while let Some(name) = context_streams.try_next().await? { - names.insert(name); - } - streams.blocks.assert_has_stream(&names).await; - streams.transactions.assert_has_stream(&names).await; - - for name in names.iter() { - let empty = streams.blocks.is_empty(name).await; - assert!(empty, "stream must be empty after creation"); - } - Ok(()) -} - -#[tokio::test] -async fn fuel_streams_client_connection() -> BoxedResult<()> { - let opts = NatsClientOpts::admin_opts(Some(FuelNetwork::Local)); - let client = NatsClient::connect(&opts).await?; - assert!(client.is_connected()); - let client = Client::with_opts(&opts).await?; - assert!(client.conn.is_connected()); - Ok(()) -} - -#[tokio::test] -async fn multiple_client_connections() -> BoxedResult<()> { - let opts = NatsClientOpts::admin_opts(Some(FuelNetwork::Local)); - let tasks: Vec<_> = (0..100) - .map(|_| { - let opts = opts.clone(); - async move { - let client = Client::with_opts(&opts).await.unwrap(); - assert!(client.conn.is_connected()); - Ok::<(), NatsError>(()) - } - }) - .collect(); - - assert!(try_join_all(tasks).await.is_ok()); - Ok(()) -} - -#[tokio::test] -async fn public_user_cannot_create_streams() -> BoxedResult<()> { - let opts = NatsClientOpts::default_opts(Some(FuelNetwork::Local)) - .with_rdn_namespace() - .with_timeout(1); - let client = NatsClient::connect(&opts).await?; - let (random_stream_title, random_subject) = - (gen_random_string(6), gen_random_string(6)); - - assert!(client - .jetstream - .create_stream(types::NatsStreamConfig { - name: random_stream_title, - subjects: vec![random_subject], - ..Default::default() - }) - .await - .is_err()); - - Ok(()) -} - -#[tokio::test] -async fn public_user_cannot_create_stores() -> BoxedResult<()> { - let opts = NatsClientOpts::default_opts(Some(FuelNetwork::Local)) - .with_rdn_namespace() - .with_timeout(1); - - let random_bucket_title = gen_random_string(6); - - let client = NatsClient::connect(&opts).await?; - assert!(client - .jetstream - .create_key_value(types::KvStoreConfig { - bucket: random_bucket_title, - ..Default::default() - }) - .await - .is_err()); - - Ok(()) -} - -#[tokio::test] -async fn public_user_cannot_delete_stores() -> BoxedResult<()> { - let opts = NatsClientOpts::admin_opts(Some(FuelNetwork::Local)) - .with_rdn_namespace() - .with_timeout(1); - - let random_bucket_title = gen_random_string(6); - - let client = NatsClient::connect(&opts).await?; - client - .jetstream - .create_key_value(types::KvStoreConfig { - bucket: random_bucket_title.clone(), - ..Default::default() - }) - .await?; - - let opts = NatsClientOpts::default_opts(Some(FuelNetwork::Local)) - .with_rdn_namespace() - .with_timeout(1); - let client = NatsClient::connect(&opts).await?; - - assert!(client - .jetstream - .delete_key_value(&random_bucket_title) - .await - .is_err()); - - Ok(()) -} - -#[tokio::test] -async fn public_user_cannot_delete_stream() -> BoxedResult<()> { - let opts = NatsClientOpts::admin_opts(Some(FuelNetwork::Local)) - .with_rdn_namespace() - .with_timeout(1); - let client = NatsClient::connect(&opts).await?; - - let (random_stream_title, random_subject) = - (gen_random_string(6), gen_random_string(6)); - - client - .jetstream - .create_stream(types::NatsStreamConfig { - name: random_stream_title.clone(), - subjects: vec![random_subject], - ..Default::default() - }) - .await?; - - let public_opts = opts.clone().with_role(NatsUserRole::Default); - let public_client = NatsClient::connect(&public_opts).await?; - - assert!( - public_client - .jetstream - .delete_stream(&random_stream_title) - .await - .is_err(), - "Stream must be deleted at this point" - ); - - Ok(()) -} - -#[tokio::test] -async fn public_user_can_access_streams_after_created() { - let opts = NatsClientOpts::new(Some(FuelNetwork::Local)) - .with_rdn_namespace() - .with_timeout(1); - - let admin_opts = opts.clone().with_role(NatsUserRole::Admin); - assert!(NatsClient::connect(&admin_opts).await.is_ok()); - - let public_opts = opts.clone().with_role(NatsUserRole::Default); - assert!(NatsClient::connect(&public_opts).await.is_ok()); -} - -#[tokio::test] -async fn public_and_admin_user_can_access_streams_after_created( -) -> BoxedResult<()> { - let admin_opts = NatsClientOpts::admin_opts(Some(FuelNetwork::Local)); - let admin_tasks: Vec>> = (0..100) - .map(|_| { - let opts: NatsClientOpts = admin_opts.clone(); - async move { - let client = Client::with_opts(&opts).await.unwrap(); - assert!(client.conn.is_connected()); - Ok::<(), NatsError>(()) - } - .boxed() - }) - .collect(); - - let public_opts = NatsClientOpts::default_opts(Some(FuelNetwork::Local)); - let public_tasks: Vec>> = (0..100) - .map(|_| { - let opts: NatsClientOpts = public_opts.clone(); - async move { - let client = Client::with_opts(&opts).await.unwrap(); - assert!(client.conn.is_connected()); - Ok::<(), NatsError>(()) - } - .boxed() - }) - .collect(); - - // Combine both vectors into one - let mut all_tasks = - Vec::with_capacity(admin_tasks.len() + public_tasks.len()); - all_tasks.extend(admin_tasks); - all_tasks.extend(public_tasks); - - assert!(try_join_all(all_tasks).await.is_ok()); - Ok(()) -} - -#[tokio::test] -async fn admin_user_can_delete_stream() -> BoxedResult<()> { - let opts = NatsClientOpts::admin_opts(Some(FuelNetwork::Local)) - .with_rdn_namespace() - .with_timeout(1); - let client = NatsClient::connect(&opts).await?; - - let (random_stream_title, random_subject) = - (gen_random_string(6), gen_random_string(6)); - - client - .jetstream - .create_stream(types::NatsStreamConfig { - name: random_stream_title.clone(), - subjects: vec![random_subject], - ..Default::default() - }) - .await?; - - let status = client.jetstream.delete_stream(&random_stream_title).await?; - assert!(status.success, "Stream must be deleted at this point"); - - Ok(()) -} - -#[tokio::test] -async fn admin_user_can_delete_stores() -> BoxedResult<()> { - let opts = NatsClientOpts::admin_opts(Some(FuelNetwork::Local)) - .with_rdn_namespace() - .with_timeout(1); - - let random_bucket_title = gen_random_string(6); - - let client = NatsClient::connect(&opts).await?; - client - .jetstream - .create_key_value(types::KvStoreConfig { - bucket: random_bucket_title.clone(), - ..Default::default() - }) - .await?; - - assert!(client - .jetstream - .delete_key_value(&random_bucket_title) - .await - .is_ok()); - - Ok(()) -} - -#[tokio::test] -async fn ensure_deduplication_when_publishing() -> BoxedResult<()> { - let (conn, _) = server_setup().await.unwrap(); - let client = Client::with_opts(&conn.opts).await.unwrap(); - let stream = fuel_streams::Stream::::new(&client).await; - let producer = Some(Address::zeroed()); - let const_block_height = 1001; - let items = - publish_blocks(stream.stream(), producer, Some(const_block_height)) - .unwrap() - .0; - - let mut sub = stream.subscribe().await.unwrap().enumerate(); - let timeout_duration = Duration::from_secs(1); - - // ensure just one message was published - 'l: loop { - match timeout(timeout_duration, sub.next()).await { - Ok(Some((idx, entry))) => { - assert!(entry.is_some()); - let decoded_msg = Block::decode_raw(entry.unwrap()).await; - let (subject, _block) = items[idx].to_owned(); - let height = decoded_msg.payload.height; - assert_eq!(decoded_msg.subject, subject.parse()); - assert_eq!(height, const_block_height); - assert!(idx < 1); - } - _ => { - break 'l; - } - } - } - - Ok(()) -} +// use std::{collections::HashSet, sync::Arc, time::Duration}; + +// use fuel_streams::prelude::*; +// use fuel_streams_core::prelude::{types, *}; +// use futures::{ +// future::{try_join_all, BoxFuture}, +// FutureExt, +// StreamExt, +// TryStreamExt, +// }; +// use rand::{distributions::Alphanumeric, Rng}; +// use streams_tests::{publish_blocks, server_setup}; +// use tokio::time::timeout; + +// fn gen_random_string(size: usize) -> String { +// rand::thread_rng() +// .sample_iter(&Alphanumeric) +// .take(size) +// .map(char::from) +// .collect() +// } + +// #[tokio::test] +// async fn conn_streams_has_required_streams() -> BoxedResult<()> { +// let (client, streams, _) = server_setup().await.unwrap(); +// let mut context_streams = client.jetstream.stream_names(); + +// let mut names = HashSet::new(); +// while let Some(name) = context_streams.try_next().await? { +// names.insert(name); +// } +// streams.blocks.assert_has_stream(&names).await; +// streams.transactions.assert_has_stream(&names).await; + +// for name in names.iter() { +// let empty = streams.blocks.is_empty(name).await; +// assert!(empty, "stream must be empty after creation"); +// } +// Ok(()) +// } + +// #[tokio::test] +// async fn fuel_streams_client_connection() -> BoxedResult<()> { +// let nats_opts = NatsClientOpts::admin_opts(); +// let client = NatsClient::connect(&nats_opts).await?; +// assert!(client.is_connected()); +// let s3_opts = Arc::new(S3ClientOpts::admin_opts()); +// let client = Client::with_opts(&nats_opts, &s3_opts).await?; +// assert!(client.nats_conn.is_connected()); +// Ok(()) +// } + +// #[tokio::test] +// async fn multiple_client_connections() -> BoxedResult<()> { +// let nats_opts = NatsClientOpts::admin_opts(); +// let s3_opts = Arc::new(S3ClientOpts::admin_opts()); +// let tasks: Vec<_> = (0..100) +// .map(|_| { +// let nats_opts = nats_opts.clone(); +// let s3_opts = s3_opts.clone(); +// async move { +// let client = +// Client::with_opts(&nats_opts, &s3_opts).await.unwrap(); +// assert!(client.nats_conn.is_connected()); +// Ok::<(), NatsError>(()) +// } +// }) +// .collect(); + +// assert!(try_join_all(tasks).await.is_ok()); +// Ok(()) +// } + +// #[tokio::test] +// async fn public_user_cannot_create_streams() -> BoxedResult<()> { +// let network = FuelNetwork::Local; +// let opts = NatsClientOpts::public_opts() +// .with_url(network.to_nats_url()) +// .with_rdn_namespace() +// .with_timeout(1); +// let client = NatsClient::connect(&opts).await?; +// let (random_stream_title, random_subject) = +// (gen_random_string(6), gen_random_string(6)); + +// assert!(client +// .jetstream +// .create_stream(types::NatsStreamConfig { +// name: random_stream_title, +// subjects: vec![random_subject], +// ..Default::default() +// }) +// .await +// .is_err()); + +// Ok(()) +// } + +// #[tokio::test] +// async fn public_user_cannot_create_stores() -> BoxedResult<()> { +// let network = FuelNetwork::Local; +// let opts = NatsClientOpts::public_opts() +// .with_url(network.to_nats_url()) +// .with_rdn_namespace() +// .with_timeout(1); + +// let random_bucket_title = gen_random_string(6); + +// let client = NatsClient::connect(&opts).await?; +// assert!(client +// .jetstream +// .create_key_value(types::KvStoreConfig { +// bucket: random_bucket_title, +// ..Default::default() +// }) +// .await +// .is_err()); + +// Ok(()) +// } + +// #[tokio::test] +// async fn public_user_cannot_delete_stores() -> BoxedResult<()> { +// let network = FuelNetwork::Local; +// let opts = NatsClientOpts::admin_opts() +// .with_url(network.to_nats_url()) +// .with_rdn_namespace() +// .with_timeout(1); + +// let random_bucket_title = gen_random_string(6); + +// let client = NatsClient::connect(&opts).await?; +// client +// .jetstream +// .create_key_value(types::KvStoreConfig { +// bucket: random_bucket_title.clone(), +// ..Default::default() +// }) +// .await?; + +// let opts = NatsClientOpts::public_opts() +// .with_url(network.to_nats_url()) +// .with_rdn_namespace() +// .with_timeout(1); +// let client = NatsClient::connect(&opts).await?; + +// assert!(client +// .jetstream +// .delete_key_value(&random_bucket_title) +// .await +// .is_err()); + +// Ok(()) +// } + +// #[tokio::test] +// async fn public_user_cannot_delete_stream() -> BoxedResult<()> { +// let opts = NatsClientOpts::admin_opts() +// .with_rdn_namespace() +// .with_timeout(1); +// let client = NatsClient::connect(&opts).await?; + +// let (random_stream_title, random_subject) = +// (gen_random_string(6), gen_random_string(6)); + +// client +// .jetstream +// .create_stream(types::NatsStreamConfig { +// name: random_stream_title.clone(), +// subjects: vec![random_subject], +// ..Default::default() +// }) +// .await?; + +// let network = FuelNetwork::Local; +// let public_opts = +// NatsClientOpts::public_opts().with_url(network.to_nats_url()); +// let public_client = NatsClient::connect(&public_opts).await?; + +// assert!( +// public_client +// .jetstream +// .delete_stream(&random_stream_title) +// .await +// .is_err(), +// "Stream must be deleted at this point" +// ); + +// Ok(()) +// } + +// #[tokio::test] +// async fn public_user_can_access_streams_after_created() { +// let network = FuelNetwork::Local; +// let admin_opts = NatsClientOpts::admin_opts() +// .with_url(network.to_nats_url()) +// .with_rdn_namespace() +// .with_timeout(1); + +// let public_opts = NatsClientOpts::public_opts() +// .with_url(network.to_nats_url()) +// .with_rdn_namespace() +// .with_timeout(1); + +// assert!(NatsClient::connect(&admin_opts).await.is_ok()); +// assert!(NatsClient::connect(&public_opts).await.is_ok()); +// } + +// #[tokio::test] +// async fn public_and_admin_user_can_access_streams_after_created( +// ) -> BoxedResult<()> { +// let network = FuelNetwork::Local; +// let admin_opts = NatsClientOpts::admin_opts() +// .with_url(network.to_nats_url()) +// .with_rdn_namespace() +// .with_timeout(1); +// let s3_opts = Arc::new(S3ClientOpts::admin_opts()); +// let admin_tasks: Vec>> = (0..100) +// .map(|_| { +// let opts: NatsClientOpts = admin_opts.clone(); +// let s3_opts = s3_opts.clone(); +// async move { +// let client = Client::with_opts(&opts, &s3_opts).await.unwrap(); +// assert!(client.nats_conn.is_connected()); +// Ok::<(), NatsError>(()) +// } +// .boxed() +// }) +// .collect(); + +// let public_opts = NatsClientOpts::public_opts() +// .with_url(network.to_nats_url()) +// .with_rdn_namespace() +// .with_timeout(1); +// let s3_public_opts = +// Arc::new(S3ClientOpts::new(S3Env::Local, S3Role::Public)); +// let public_tasks: Vec>> = (0..100) +// .map(|_| { +// let opts: NatsClientOpts = public_opts.clone(); +// let s3_opts = s3_public_opts.clone(); +// async move { +// let client = Client::with_opts(&opts, &s3_opts).await.unwrap(); +// assert!(client.nats_conn.is_connected()); +// Ok::<(), NatsError>(()) +// } +// .boxed() +// }) +// .collect(); + +// // Combine both vectors into one +// let mut all_tasks = +// Vec::with_capacity(admin_tasks.len() + public_tasks.len()); +// all_tasks.extend(admin_tasks); +// all_tasks.extend(public_tasks); + +// assert!(try_join_all(all_tasks).await.is_ok()); +// Ok(()) +// } + +// #[tokio::test] +// async fn admin_user_can_delete_stream() -> BoxedResult<()> { +// let opts = NatsClientOpts::admin_opts() +// .with_rdn_namespace() +// .with_timeout(1); +// let client = NatsClient::connect(&opts).await?; + +// let (random_stream_title, random_subject) = +// (gen_random_string(6), gen_random_string(6)); + +// client +// .jetstream +// .create_stream(types::NatsStreamConfig { +// name: random_stream_title.clone(), +// subjects: vec![random_subject], +// ..Default::default() +// }) +// .await?; + +// let status = client.jetstream.delete_stream(&random_stream_title).await?; +// assert!(status.success, "Stream must be deleted at this point"); + +// Ok(()) +// } + +// #[tokio::test] +// async fn admin_user_can_delete_stores() -> BoxedResult<()> { +// let opts = NatsClientOpts::admin_opts() +// .with_rdn_namespace() +// .with_timeout(1); + +// let random_bucket_title = gen_random_string(6); + +// let client = NatsClient::connect(&opts).await?; +// client +// .jetstream +// .create_key_value(types::KvStoreConfig { +// bucket: random_bucket_title.clone(), +// ..Default::default() +// }) +// .await?; + +// assert!(client +// .jetstream +// .delete_key_value(&random_bucket_title) +// .await +// .is_ok()); + +// Ok(()) +// } + +// #[tokio::test] +// async fn ensure_deduplication_when_publishing() -> BoxedResult<()> { +// let (_, _, client) = server_setup().await.unwrap(); +// let stream = fuel_streams::Stream::::new(&client).await; +// let producer = Some(Address::zeroed()); +// let const_block_height = 1001; +// let items = +// publish_blocks(stream.stream(), producer, Some(const_block_height)) +// .unwrap() +// .0; + +// let mut sub = stream.subscribe_raw().await.unwrap().enumerate(); +// let timeout_duration = Duration::from_secs(1); + +// // ensure just one message was published +// 'l: loop { +// match timeout(timeout_duration, sub.next()).await { +// Ok(Some((idx, entry))) => { +// let decoded_msg = Block::decode_raw(entry).unwrap(); +// let (subject, _block) = items[idx].to_owned(); +// let height = decoded_msg.payload.height; +// assert_eq!(decoded_msg.subject, subject.parse()); +// assert_eq!(height, const_block_height); +// assert!(idx < 1); +// } +// _ => { +// break 'l; +// } +// } +// } + +// Ok(()) +// } diff --git a/tests/tests/publisher.rs b/tests/tests/publisher.rs index 81620ab8..19eb20a2 100644 --- a/tests/tests/publisher.rs +++ b/tests/tests/publisher.rs @@ -1,410 +1,416 @@ -use std::{collections::HashMap, sync::Arc}; - -use fuel_core::{ - combined_database::CombinedDatabase, - service::{Config, FuelService}, - ShutdownListener, -}; -use fuel_core_importer::ImporterResult; -use fuel_core_types::blockchain::SealedBlock; -use fuel_streams_core::prelude::*; -use fuel_streams_publisher::{ - publisher::shutdown::ShutdownController, - Publisher, -}; -use futures::StreamExt; -use tokio::sync::broadcast::{self, Receiver, Sender}; - -// TODO - Re-implement with `mockall` and `mock` macros -struct TestFuelCore { - fuel_service: FuelService, - chain_id: FuelCoreChainId, - base_asset_id: FuelCoreAssetId, - database: CombinedDatabase, - blocks_broadcaster: Sender, - receipts: Option>, -} - -impl TestFuelCore { - fn default( - blocks_broadcaster: Sender, - ) -> Self { - let mut shutdown = ShutdownListener::spawn(); - let service = FuelService::new( - Default::default(), - Config::local_node(), - &mut shutdown, - ) - .unwrap(); - Self { - fuel_service: service, - chain_id: FuelCoreChainId::default(), - base_asset_id: FuelCoreAssetId::zeroed(), - database: CombinedDatabase::default(), - blocks_broadcaster, - receipts: None, - } - } - fn with_receipts(mut self, receipts: Vec) -> Self { - self.receipts = Some(receipts); - self - } - fn arc(self) -> Arc { - Arc::new(self) - } -} - -#[async_trait::async_trait] -impl FuelCoreLike for TestFuelCore { - async fn start(&self) -> anyhow::Result<()> { - Ok(()) - } - fn is_started(&self) -> bool { - true - } - fn fuel_service(&self) -> &FuelService { - &self.fuel_service - } - async fn await_synced_at_least_once( - &self, - _historical: bool, - ) -> anyhow::Result<()> { - Ok(()) - } - async fn stop(&self) {} - - async fn await_offchain_db_sync( - &self, - _block_id: &FuelCoreBlockId, - ) -> anyhow::Result<()> { - Ok(()) - } - - fn base_asset_id(&self) -> &FuelCoreAssetId { - &self.base_asset_id - } - fn chain_id(&self) -> &FuelCoreChainId { - &self.chain_id - } - - fn database(&self) -> &CombinedDatabase { - &self.database - } - - fn blocks_subscription( - &self, - ) -> Receiver { - self.blocks_broadcaster.subscribe() - } - - fn get_receipts( - &self, - _tx_id: &FuelCoreBytes32, - ) -> anyhow::Result>> { - Ok(self.receipts.clone()) - } - - fn get_tx_status( - &self, - _tx_id: &FuelCoreBytes32, - ) -> anyhow::Result> { - Ok(Some(FuelCoreTransactionStatus::Success { - receipts: self.receipts.clone().unwrap_or_default(), - block_height: 0.into(), - result: None, - time: FuelCoreTai64::now(), - total_gas: 0, - total_fee: 0, - })) - } -} - -#[tokio::test(flavor = "multi_thread")] -async fn doesnt_publish_any_message_when_no_block_has_been_mined() { - let (blocks_broadcaster, _) = broadcast::channel::(1); - let publisher = new_publisher(blocks_broadcaster.clone()).await; - - let shutdown_controller = start_publisher(&publisher).await; - stop_publisher(shutdown_controller).await; - - assert!(publisher.get_fuel_streams().is_empty().await); -} - -#[tokio::test(flavor = "multi_thread")] -async fn publishes_a_block_message_when_a_single_block_has_been_mined() { - let (blocks_broadcaster, _) = broadcast::channel::(1); - let publisher = new_publisher(blocks_broadcaster.clone()).await; - - publish_block(&publisher, &blocks_broadcaster).await; - - assert!(publisher - .get_fuel_streams() - .blocks() - .get_last_published(BlocksSubject::WILDCARD) - .await - .is_ok_and(|result| result.is_some())); -} - -#[tokio::test(flavor = "multi_thread")] -async fn publishes_transaction_for_each_published_block() { - let (blocks_broadcaster, _) = broadcast::channel::(1); - let publisher = new_publisher(blocks_broadcaster.clone()).await; - - publish_block(&publisher, &blocks_broadcaster).await; - - assert!(publisher - .get_fuel_streams() - .transactions() - .get_last_published(TransactionsSubject::WILDCARD) - .await - .is_ok_and(|result| result.is_some())); -} - -#[tokio::test(flavor = "multi_thread")] -async fn publishes_receipts() { - let (blocks_broadcaster, _) = broadcast::channel::(1); - - let receipts = [ - FuelCoreReceipt::Call { - id: FuelCoreContractId::default(), - to: Default::default(), - amount: 0, - asset_id: Default::default(), - gas: 0, - param1: 0, - param2: 0, - pc: 0, - is: 0, - }, - FuelCoreReceipt::Return { - id: FuelCoreContractId::default(), - val: 0, - pc: 0, - is: 0, - }, - FuelCoreReceipt::ReturnData { - id: FuelCoreContractId::default(), - ptr: 0, - len: 0, - digest: FuelCoreBytes32::default(), - pc: 0, - is: 0, - data: None, - }, - FuelCoreReceipt::Revert { - id: FuelCoreContractId::default(), - ra: 0, - pc: 0, - is: 0, - }, - FuelCoreReceipt::Log { - id: FuelCoreContractId::default(), - ra: 0, - rb: 0, - rc: 0, - rd: 0, - pc: 0, - is: 0, - }, - FuelCoreReceipt::LogData { - id: FuelCoreContractId::default(), - ra: 0, - rb: 0, - ptr: 0, - len: 0, - digest: FuelCoreBytes32::default(), - pc: 0, - is: 0, - data: None, - }, - FuelCoreReceipt::Transfer { - id: FuelCoreContractId::default(), - to: FuelCoreContractId::default(), - amount: 0, - asset_id: FuelCoreAssetId::default(), - pc: 0, - is: 0, - }, - FuelCoreReceipt::TransferOut { - id: FuelCoreContractId::default(), - to: FuelCoreAddress::default(), - amount: 0, - asset_id: FuelCoreAssetId::default(), - pc: 0, - is: 0, - }, - FuelCoreReceipt::Mint { - sub_id: FuelCoreBytes32::default(), - contract_id: FuelCoreContractId::default(), - val: 0, - pc: 0, - is: 0, - }, - FuelCoreReceipt::Burn { - sub_id: FuelCoreBytes32::default(), - contract_id: FuelCoreContractId::default(), - val: 0, - pc: 0, - is: 0, - }, - ]; - - let fuel_core = TestFuelCore::default(blocks_broadcaster.clone()) - .with_receipts(receipts.to_vec()) - .arc(); - - let publisher = Publisher::default(&nats_client().await, fuel_core) - .await - .unwrap(); - - publish_block(&publisher, &blocks_broadcaster).await; - - let mut receipts_stream = publisher - .get_fuel_streams() - .receipts() - .catchup(10) - .await - .unwrap(); - - let expected_receipts: Vec = - receipts.iter().map(Into::into).collect(); - let mut found_receipts = Vec::new(); - - while let Some(Some(receipt)) = receipts_stream.next().await { - found_receipts.push(receipt); - } - - assert_eq!( - found_receipts.len(), - expected_receipts.len(), - "Number of receipts doesn't match" - ); - - // Create sets of receipt identifiers - let found_ids: std::collections::HashSet<_> = found_receipts - .into_iter() - .map(|r| match r { - Receipt::Call(r) => r.id, - Receipt::Return(r) => r.id, - Receipt::ReturnData(r) => r.id, - Receipt::Revert(r) => r.id, - Receipt::Log(r) => r.id, - Receipt::LogData(r) => r.id, - Receipt::Transfer(r) => r.id, - Receipt::TransferOut(r) => r.id, - Receipt::Mint(r) => r.contract_id, - Receipt::Burn(r) => r.contract_id, - Receipt::Panic(r) => r.id, - _ => unreachable!(), - }) - .collect(); - - let expected_ids: std::collections::HashSet<_> = expected_receipts - .into_iter() - .map(|r| match r { - Receipt::Call(r) => r.id, - Receipt::Return(r) => r.id, - Receipt::ReturnData(r) => r.id, - Receipt::Revert(r) => r.id, - Receipt::Log(r) => r.id, - Receipt::LogData(r) => r.id, - Receipt::Transfer(r) => r.id, - Receipt::TransferOut(r) => r.id, - Receipt::Mint(r) => r.contract_id, - Receipt::Burn(r) => r.contract_id, - Receipt::Panic(r) => r.id, - _ => unreachable!(), - }) - .collect(); - - assert_eq!( - found_ids, expected_ids, - "Published receipt IDs don't match expected IDs" - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn publishes_inputs() { - let (blocks_broadcaster, _) = broadcast::channel::(1); - let publisher = new_publisher(blocks_broadcaster.clone()).await; - - publish_block(&publisher, &blocks_broadcaster).await; - - assert!(publisher - .get_fuel_streams() - .inputs() - .get_last_published(InputsByIdSubject::WILDCARD) - .await - .is_ok_and(|result| result.is_some())); -} - -async fn new_publisher(broadcaster: Sender) -> Publisher { - let fuel_core = TestFuelCore::default(broadcaster).arc(); - Publisher::default(&nats_client().await, fuel_core) - .await - .unwrap() -} - -async fn publish_block( - publisher: &Publisher, - blocks_broadcaster: &Sender, -) { - let shutdown_controller = start_publisher(publisher).await; - send_block(blocks_broadcaster); - stop_publisher(shutdown_controller).await; -} - -async fn start_publisher(publisher: &Publisher) -> Arc { - let shutdown_controller = ShutdownController::new().arc(); - let shutdown_token = shutdown_controller.get_token(); - tokio::spawn({ - let publisher = publisher.clone(); - async move { - publisher.run(shutdown_token, true).await.unwrap(); - } - }); - wait_for_publisher_to_start().await; - shutdown_controller -} -async fn stop_publisher(shutdown_controller: Arc) { - wait_for_publisher_to_process_block().await; - - assert!(shutdown_controller.initiate_shutdown().is_ok()); -} - -async fn wait_for_publisher_to_start() { - tokio::time::sleep(std::time::Duration::from_secs(1)).await; -} -async fn wait_for_publisher_to_process_block() { - tokio::time::sleep(std::time::Duration::from_secs(1)).await; -} - -fn send_block(broadcaster: &Sender) { - let block = create_test_block(); - assert!(broadcaster.send(block).is_ok()); -} -fn create_test_block() -> ImporterResult { - let mut block_entity = FuelCoreBlock::default(); - let tx = FuelCoreTransaction::default_test_tx(); - - *block_entity.transactions_mut() = vec![tx]; - - ImporterResult { - shared_result: Arc::new(FuelCoreImportResult { - sealed_block: SealedBlock { - entity: block_entity, - ..Default::default() - }, - ..Default::default() - }), - changes: Arc::new(HashMap::new()), - } -} - -async fn nats_client() -> NatsClient { - let nats_client_opts = NatsClientOpts::admin_opts(Some(FuelNetwork::Local)) - .with_rdn_namespace(); - NatsClient::connect(&nats_client_opts) - .await - .expect("NATS connection failed") -} +// use std::{collections::HashMap, sync::Arc}; + +// use fuel_core::{ +// combined_database::CombinedDatabase, +// service::{Config, FuelService}, +// ShutdownListener, +// }; +// use fuel_core_importer::ImporterResult; +// use fuel_core_types::blockchain::SealedBlock; +// use fuel_streams_core::prelude::*; +// use tokio::sync::broadcast::{self, Receiver, Sender}; + +// // TODO - Re-implement with `mockall` and `mock` macros +// struct TestFuelCore { +// fuel_service: FuelService, +// chain_id: FuelCoreChainId, +// base_asset_id: FuelCoreAssetId, +// database: CombinedDatabase, +// blocks_broadcaster: Sender, +// receipts: Option>, +// } + +// impl TestFuelCore { +// fn default( +// blocks_broadcaster: Sender, +// ) -> Self { +// let mut shutdown = ShutdownListener::spawn(); +// let service = FuelService::new( +// Default::default(), +// Config::local_node(), +// &mut shutdown, +// ) +// .unwrap(); +// Self { +// fuel_service: service, +// chain_id: FuelCoreChainId::default(), +// base_asset_id: FuelCoreAssetId::zeroed(), +// database: CombinedDatabase::default(), +// blocks_broadcaster, +// receipts: None, +// } +// } +// fn with_receipts(mut self, receipts: Vec) -> Self { +// self.receipts = Some(receipts); +// self +// } +// fn arc(self) -> Arc { +// Arc::new(self) +// } +// } + +// #[async_trait::async_trait] +// impl FuelCoreLike for TestFuelCore { +// async fn start(&self) -> anyhow::Result<()> { +// Ok(()) +// } +// fn is_started(&self) -> bool { +// true +// } +// fn fuel_service(&self) -> &FuelService { +// &self.fuel_service +// } +// async fn await_synced_at_least_once( +// &self, +// _historical: bool, +// ) -> anyhow::Result<()> { +// Ok(()) +// } +// async fn stop(&self) {} + +// async fn await_offchain_db_sync( +// &self, +// _block_id: &FuelCoreBlockId, +// ) -> anyhow::Result<()> { +// Ok(()) +// } + +// fn base_asset_id(&self) -> &FuelCoreAssetId { +// &self.base_asset_id +// } +// fn chain_id(&self) -> &FuelCoreChainId { +// &self.chain_id +// } + +// fn database(&self) -> &CombinedDatabase { +// &self.database +// } + +// fn blocks_subscription( +// &self, +// ) -> Receiver { +// self.blocks_broadcaster.subscribe() +// } + +// fn get_receipts( +// &self, +// _tx_id: &FuelCoreBytes32, +// ) -> anyhow::Result>> { +// Ok(self.receipts.clone()) +// } + +// fn get_tx_status( +// &self, +// _tx_id: &FuelCoreBytes32, +// ) -> anyhow::Result> { +// Ok(Some(FuelCoreTransactionStatus::Success { +// receipts: self.receipts.clone().unwrap_or_default(), +// block_height: 0.into(), +// result: None, +// time: FuelCoreTai64::now(), +// total_gas: 0, +// total_fee: 0, +// })) +// } +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn doesnt_publish_any_message_when_no_block_has_been_mined() { +// let (blocks_broadcaster, _) = broadcast::channel::(1); +// let s3_client = Arc::new(S3Client::new_for_testing().await); +// let publisher = new_publisher(blocks_broadcaster.clone(), &s3_client).await; + +// let shutdown_controller = start_publisher(&publisher).await; +// stop_publisher(shutdown_controller).await; + +// assert!(publisher.get_fuel_streams().is_empty().await); +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn publishes_a_block_message_when_a_single_block_has_been_mined() { +// let (blocks_broadcaster, _) = broadcast::channel::(1); +// let s3_client = Arc::new(S3Client::new_for_testing().await); +// let publisher = new_publisher(blocks_broadcaster.clone(), &s3_client).await; + +// publish_block(&publisher, &blocks_broadcaster).await; + +// assert!(publisher +// .get_fuel_streams() +// .blocks() +// .get_last_published(BlocksSubject::WILDCARD) +// .await +// .is_ok_and(|result| result.is_some())); +// s3_client.cleanup_after_testing().await; +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn publishes_transaction_for_each_published_block() { +// let (blocks_broadcaster, _) = broadcast::channel::(1); +// let s3_client = Arc::new(S3Client::new_for_testing().await); +// let publisher = new_publisher(blocks_broadcaster.clone(), &s3_client).await; + +// publish_block(&publisher, &blocks_broadcaster).await; + +// assert!(publisher +// .get_fuel_streams() +// .transactions() +// .get_last_published(TransactionsSubject::WILDCARD) +// .await +// .is_ok_and(|result| result.is_some())); +// s3_client.cleanup_after_testing().await; +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn publishes_receipts() { +// let (blocks_broadcaster, _) = broadcast::channel::(1); + +// let receipts = [ +// FuelCoreReceipt::Call { +// id: FuelCoreContractId::default(), +// to: Default::default(), +// amount: 0, +// asset_id: Default::default(), +// gas: 0, +// param1: 0, +// param2: 0, +// pc: 0, +// is: 0, +// }, +// FuelCoreReceipt::Return { +// id: FuelCoreContractId::default(), +// val: 0, +// pc: 0, +// is: 0, +// }, +// FuelCoreReceipt::ReturnData { +// id: FuelCoreContractId::default(), +// ptr: 0, +// len: 0, +// digest: FuelCoreBytes32::default(), +// pc: 0, +// is: 0, +// data: None, +// }, +// FuelCoreReceipt::Revert { +// id: FuelCoreContractId::default(), +// ra: 0, +// pc: 0, +// is: 0, +// }, +// FuelCoreReceipt::Log { +// id: FuelCoreContractId::default(), +// ra: 0, +// rb: 0, +// rc: 0, +// rd: 0, +// pc: 0, +// is: 0, +// }, +// FuelCoreReceipt::LogData { +// id: FuelCoreContractId::default(), +// ra: 0, +// rb: 0, +// ptr: 0, +// len: 0, +// digest: FuelCoreBytes32::default(), +// pc: 0, +// is: 0, +// data: None, +// }, +// FuelCoreReceipt::Transfer { +// id: FuelCoreContractId::default(), +// to: FuelCoreContractId::default(), +// amount: 0, +// asset_id: FuelCoreAssetId::default(), +// pc: 0, +// is: 0, +// }, +// FuelCoreReceipt::TransferOut { +// id: FuelCoreContractId::default(), +// to: FuelCoreAddress::default(), +// amount: 0, +// asset_id: FuelCoreAssetId::default(), +// pc: 0, +// is: 0, +// }, +// FuelCoreReceipt::Mint { +// sub_id: FuelCoreBytes32::default(), +// contract_id: FuelCoreContractId::default(), +// val: 0, +// pc: 0, +// is: 0, +// }, +// FuelCoreReceipt::Burn { +// sub_id: FuelCoreBytes32::default(), +// contract_id: FuelCoreContractId::default(), +// val: 0, +// pc: 0, +// is: 0, +// }, +// ]; + +// let fuel_core = TestFuelCore::default(blocks_broadcaster.clone()) +// .with_receipts(receipts.to_vec()) +// .arc(); + +// let s3_client = Arc::new(S3Client::new_for_testing().await); +// let publisher = +// Publisher::new_for_testing(&nats_client().await, &s3_client, fuel_core) +// .await +// .unwrap(); + +// publish_block(&publisher, &blocks_broadcaster).await; + +// let mut receipts_stream = publisher +// .get_fuel_streams() +// .receipts() +// .catchup(10) +// .await +// .unwrap(); + +// let expected_receipts: Vec = +// receipts.iter().map(Into::into).collect(); +// let mut found_receipts = Vec::new(); + +// while let Some(Some(receipt)) = receipts_stream.next().await { +// found_receipts.push(receipt); +// } +// assert_eq!( +// found_receipts.len(), +// expected_receipts.len(), +// "Number of receipts doesn't match" +// ); + +// // Create sets of receipt identifiers +// let found_ids: std::collections::HashSet<_> = found_receipts +// .into_iter() +// .map(|r| match r { +// Receipt::Call(r) => r.id, +// Receipt::Return(r) => r.id, +// Receipt::ReturnData(r) => r.id, +// Receipt::Revert(r) => r.id, +// Receipt::Log(r) => r.id, +// Receipt::LogData(r) => r.id, +// Receipt::Transfer(r) => r.id, +// Receipt::TransferOut(r) => r.id, +// Receipt::Mint(r) => r.contract_id, +// Receipt::Burn(r) => r.contract_id, +// Receipt::Panic(r) => r.id, +// _ => unreachable!(), +// }) +// .collect(); + +// let expected_ids: std::collections::HashSet<_> = expected_receipts +// .into_iter() +// .map(|r| match r { +// Receipt::Call(r) => r.id, +// Receipt::Return(r) => r.id, +// Receipt::ReturnData(r) => r.id, +// Receipt::Revert(r) => r.id, +// Receipt::Log(r) => r.id, +// Receipt::LogData(r) => r.id, +// Receipt::Transfer(r) => r.id, +// Receipt::TransferOut(r) => r.id, +// Receipt::Mint(r) => r.contract_id, +// Receipt::Burn(r) => r.contract_id, +// Receipt::Panic(r) => r.id, +// _ => unreachable!(), +// }) +// .collect(); + +// assert_eq!( +// found_ids, expected_ids, +// "Published receipt IDs don't match expected IDs" +// ); + +// s3_client.cleanup_after_testing().await; +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn publishes_inputs() { +// let (blocks_broadcaster, _) = broadcast::channel::(1); +// let s3_client = Arc::new(S3Client::new_for_testing().await); +// let publisher = new_publisher(blocks_broadcaster.clone(), &s3_client).await; + +// publish_block(&publisher, &blocks_broadcaster).await; + +// assert!(publisher +// .get_fuel_streams() +// .inputs() +// .get_last_published(InputsByIdSubject::WILDCARD) +// .await +// .is_ok_and(|result| result.is_some())); +// s3_client.cleanup_after_testing().await; +// } + +// async fn new_publisher( +// broadcaster: Sender, +// s3_client: &Arc, +// ) -> Publisher { +// let fuel_core = TestFuelCore::default(broadcaster).arc(); +// Publisher::new_for_testing(&nats_client().await, s3_client, fuel_core) +// .await +// .unwrap() +// } + +// async fn publish_block( +// publisher: &Publisher, +// blocks_broadcaster: &Sender, +// ) { +// let shutdown_controller = start_publisher(publisher).await; +// send_block(blocks_broadcaster); +// stop_publisher(shutdown_controller).await; +// } + +// async fn start_publisher(publisher: &Publisher) -> ShutdownController { +// let (shutdown_controller, shutdown_token) = get_controller_and_token(); +// tokio::spawn({ +// let publisher = publisher.clone(); +// async move { +// publisher.run(shutdown_token, true).await.unwrap(); +// } +// }); +// wait_for_publisher_to_start().await; +// shutdown_controller +// } +// async fn stop_publisher(shutdown_controller: ShutdownController) { +// wait_for_publisher_to_process_block().await; + +// assert!(shutdown_controller.initiate_shutdown().is_ok()); +// } + +// async fn wait_for_publisher_to_start() { +// tokio::time::sleep(std::time::Duration::from_secs(1)).await; +// } +// async fn wait_for_publisher_to_process_block() { +// tokio::time::sleep(std::time::Duration::from_secs(1)).await; +// } + +// fn send_block(broadcaster: &Sender) { +// let block = create_test_block(); +// assert!(broadcaster.send(block).is_ok()); +// } +// fn create_test_block() -> ImporterResult { +// let mut block_entity = FuelCoreBlock::default(); +// let tx = FuelCoreTransaction::default_test_tx(); + +// *block_entity.transactions_mut() = vec![tx]; + +// ImporterResult { +// shared_result: Arc::new(FuelCoreImportResult { +// sealed_block: SealedBlock { +// entity: block_entity, +// ..Default::default() +// }, +// ..Default::default() +// }), +// changes: Arc::new(HashMap::new()), +// } +// } + +// async fn nats_client() -> NatsClient { +// let opts = NatsClientOpts::admin_opts().with_rdn_namespace(); +// NatsClient::connect(&opts) +// .await +// .expect("NATS connection failed") +// } diff --git a/tests/tests/stream.rs b/tests/tests/stream.rs index 73bdbf1e..2df55a23 100644 --- a/tests/tests/stream.rs +++ b/tests/tests/stream.rs @@ -1,253 +1,242 @@ -use fuel_streams::prelude::*; -use fuel_streams_core::prelude::*; -use futures::{future::try_join_all, StreamExt}; -use pretty_assertions::assert_eq; -use streams_tests::{publish_blocks, publish_transactions, server_setup}; - -#[tokio::test] -async fn blocks_streams_subscribe() { - let (conn, _) = server_setup().await.unwrap(); - let client = Client::with_opts(&conn.opts).await.unwrap(); - let stream = fuel_streams::Stream::::new(&client).await; - let producer = Some(Address::zeroed()); - let items = publish_blocks(stream.stream(), producer, None).unwrap().0; - - let mut sub = stream.subscribe().await.unwrap().enumerate(); - while let Some((i, bytes)) = sub.next().await { - let decoded_msg = Block::decode_raw(bytes.unwrap()).await; - let (subject, block) = items[i].to_owned(); - let height = decoded_msg.payload.height; - - assert_eq!(decoded_msg.subject, subject.parse()); - assert_eq!(decoded_msg.payload, block); - assert_eq!(height, i as u32); - if i == 9 { - break; - } - } -} - -#[tokio::test] -async fn blocks_streams_subscribe_with_filter() { - let (conn, _) = server_setup().await.unwrap(); - let client = Client::with_opts(&conn.opts).await.unwrap(); - let mut stream = fuel_streams::Stream::::new(&client).await; - let producer = Some(Address::zeroed()); - - // publishing 10 blocks - publish_blocks(stream.stream(), producer, None).unwrap(); - - // filtering by producer 0x000 and height 5 - let filter = Filter::::build() - .with_producer(Some(Address::zeroed())) - .with_height(Some(5.into())); - - // creating subscription - let mut sub = stream - .with_filter(filter) - .subscribe_with_config(StreamConfig::default()) - .await - .unwrap() - .take(10); - - // result should be just 1 single message with height 5 - while let Some(message) = sub.next().await { - let message = message.unwrap(); - let decoded_msg = - Block::decode_raw(message.payload.clone().into()).await; - let height = decoded_msg.payload.height; - assert_eq!(height, 5); - if height == 5 { - break; - } - } -} - -#[tokio::test] -async fn transactions_streams_subscribe() { - let (conn, _) = server_setup().await.unwrap(); - let client = Client::with_opts(&conn.opts).await.unwrap(); - let stream = fuel_streams::Stream::::new(&client).await; - - let mock_block = MockBlock::build(1); - let items = publish_transactions(stream.stream(), &mock_block, None) - .unwrap() - .0; - - let mut sub = stream.subscribe().await.unwrap().enumerate(); - while let Some((i, bytes)) = sub.next().await { - let decoded_msg = - Transaction::decode_raw(bytes.unwrap().to_vec()).await; - - let (_, transaction) = items[i].to_owned(); - assert_eq!(decoded_msg.payload, transaction); - if i == 9 { - break; - } - } -} - -#[tokio::test] -async fn transactions_streams_subscribe_with_filter() { - let (conn, _) = server_setup().await.unwrap(); - let client = Client::with_opts(&conn.opts).await.unwrap(); - let mut stream = fuel_streams::Stream::::new(&client).await; - - // publishing 10 transactions - let mock_block = MockBlock::build(5); - let items = publish_transactions(stream.stream(), &mock_block, None) - .unwrap() - .0; - - // filtering by transaction on block with height 5 - let filter = Filter::::build() - .with_block_height(Some(5.into())); - - // creating subscription - let mut sub = stream - .with_filter(filter) - .subscribe_with_config(StreamConfig::default()) - .await - .unwrap() - .take(10) - .enumerate(); - - // result should be 10 transactions messages - while let Some((i, message)) = sub.next().await { - let message = message.unwrap(); - let payload = message.payload.clone().into(); - let decoded_msg = Transaction::decode(payload).await; - - let (_, transaction) = items[i].to_owned(); - assert_eq!(decoded_msg, transaction); - if i == 9 { - break; - } - } -} - -#[tokio::test] -async fn multiple_subscribers_same_subject() { - let (conn, _) = server_setup().await.unwrap(); - let client = Client::with_opts(&conn.opts).await.unwrap(); - let stream = fuel_streams::Stream::::new(&client).await; - let producer = Some(Address::zeroed()); - let items = publish_blocks(stream.stream(), producer.clone(), None) - .unwrap() - .0; - - let clients_count = 100; - let done_signal = 99; - let mut handles = Vec::new(); - for _ in 0..clients_count { - let stream = stream.clone(); - let items = items.clone(); - handles.push(tokio::spawn(async move { - let mut sub = stream.subscribe().await.unwrap().enumerate(); - while let Some((i, bytes)) = sub.next().await { - let decoded_msg = Block::decode_raw(bytes.unwrap()).await; - let (subject, block) = items[i].to_owned(); - let height = decoded_msg.payload.height; - - assert_eq!(decoded_msg.subject, subject.parse()); - assert_eq!(decoded_msg.payload, block); - assert_eq!(height, i as u32); - if i == 9 { - return done_signal; - } - } - done_signal + 1 - })); - } - - let mut client_results = try_join_all(handles).await.unwrap(); - assert!( - client_results.len() == clients_count, - "must have all clients subscribed to one subject" - ); - client_results.dedup(); - assert!( - client_results.len() == 1, - "all clients must have the same result" - ); - assert!( - client_results.first().cloned().unwrap() == done_signal, - "all clients must have the same received the complete signal" - ); -} - -#[tokio::test] -async fn multiple_subscribers_different_subjects() { - let (conn, _) = server_setup().await.unwrap(); - let client = Client::with_opts(&conn.opts).await.unwrap(); - let producer = Some(Address::zeroed()); - let block_stream = fuel_streams::Stream::::new(&client).await; - let block_items = - publish_blocks(block_stream.stream(), producer.clone(), None) - .unwrap() - .0; - - let txs_stream = fuel_streams::Stream::::new(&client).await; - let mock_block = MockBlock::build(1); - let txs_items = - publish_transactions(txs_stream.stream(), &mock_block, None) - .unwrap() - .0; - - let clients_count = 100; - let done_signal = 99; - let mut handles = Vec::new(); - for _ in 0..clients_count { - // blocks stream - let stream = block_stream.clone(); - let items = block_items.clone(); - handles.push(tokio::spawn(async move { - let mut sub = stream.subscribe().await.unwrap().enumerate(); - while let Some((i, bytes)) = sub.next().await { - let decoded_msg = Block::decode_raw(bytes.unwrap()).await; - let (subject, block) = items[i].to_owned(); - let height = decoded_msg.payload.height; - - assert_eq!(decoded_msg.subject, subject.parse()); - assert_eq!(decoded_msg.payload, block); - assert_eq!(height, i as u32); - if i == 9 { - return done_signal; - } - } - done_signal + 1 - })); - - // txs stream - let stream = txs_stream.clone(); - let items = txs_items.clone(); - handles.push(tokio::spawn(async move { - let mut sub = stream.subscribe().await.unwrap().enumerate(); - while let Some((i, bytes)) = sub.next().await { - let decoded_msg = - Transaction::decode_raw(bytes.unwrap().to_vec()).await; - let (_, transaction) = items[i].to_owned(); - assert_eq!(decoded_msg.payload, transaction); - if i == 9 { - return done_signal; - } - } - done_signal + 1 - })); - } - - let mut client_results = try_join_all(handles).await.unwrap(); - assert!( - client_results.len() == 2 * clients_count, - "must have all clients subscribed to two subjects" - ); - client_results.dedup(); - assert!( - client_results.len() == 1, - "all clients must have the same result" - ); - assert!( - client_results.first().cloned().unwrap() == done_signal, - "all clients must have the same received the complete signal" - ); -} +// use fuel_streams::prelude::*; +// use fuel_streams_core::prelude::*; +// use futures::{future::try_join_all, StreamExt}; +// use pretty_assertions::assert_eq; +// use streams_tests::{publish_blocks, publish_transactions, server_setup}; + +// #[tokio::test] +// async fn blocks_streams_subscribe() { +// let (_, _, client) = server_setup().await.unwrap(); +// let stream = fuel_streams::Stream::::new(&client).await; +// let producer = Some(Address::zeroed()); +// let items = publish_blocks(stream.stream(), producer, None).unwrap().0; + +// let mut sub = stream.subscribe_raw().await.unwrap().enumerate(); + +// while let Some((i, bytes)) = sub.next().await { +// let decoded_msg = Block::decode_raw(bytes).unwrap(); +// let (subject, block) = items[i].to_owned(); +// let height = decoded_msg.payload.height; + +// assert_eq!(decoded_msg.subject, subject.parse()); +// assert_eq!(decoded_msg.payload, block); +// assert_eq!(height, i as u32); +// if i == 9 { +// break; +// } +// } +// } + +// #[tokio::test] +// async fn blocks_streams_subscribe_with_filter() { +// let (_, _, client) = server_setup().await.unwrap(); +// let mut stream = fuel_streams::Stream::::new(&client).await; +// let producer = Some(Address::zeroed()); + +// // publishing 10 blocks +// publish_blocks(stream.stream(), producer, None).unwrap(); + +// // filtering by producer 0x000 and height 5 +// let filter = Filter::::build() +// .with_producer(Some(Address::zeroed())) +// .with_height(Some(5.into())); + +// // creating subscription +// let mut sub = stream +// .with_filter(filter) +// .subscribe_raw_with_config(StreamConfig::default()) +// .await +// .unwrap() +// .take(10); + +// // result should be just 1 single message with height 5 +// while let Some(bytes) = sub.next().await { +// let decoded_msg = Block::decode_raw(bytes).unwrap(); +// let height = decoded_msg.payload.height; +// assert_eq!(height, 5); +// if height == 5 { +// break; +// } +// } +// } + +// #[tokio::test] +// async fn transactions_streams_subscribe() { +// let (_, _, client) = server_setup().await.unwrap(); +// let stream = fuel_streams::Stream::::new(&client).await; + +// let mock_block = MockBlock::build(1); +// let items = publish_transactions(stream.stream(), &mock_block, None) +// .unwrap() +// .0; + +// let mut sub = stream.subscribe_raw().await.unwrap().enumerate(); +// while let Some((i, bytes)) = sub.next().await { +// let decoded_msg = Transaction::decode_raw(bytes).unwrap(); + +// let (_, transaction) = items[i].to_owned(); +// assert_eq!(decoded_msg.payload, transaction); +// if i == 9 { +// break; +// } +// } +// } + +// #[tokio::test] +// async fn transactions_streams_subscribe_with_filter() { +// let (_, _, client) = server_setup().await.unwrap(); +// let mut stream = fuel_streams::Stream::::new(&client).await; + +// // publishing 10 transactions +// let mock_block = MockBlock::build(5); +// let items = publish_transactions(stream.stream(), &mock_block, None) +// .unwrap() +// .0; + +// // filtering by transaction on block with height 5 +// let filter = Filter::::build() +// .with_block_height(Some(5.into())); + +// // creating subscription +// let mut sub = stream +// .with_filter(filter) +// .subscribe_raw_with_config(StreamConfig::default()) +// .await +// .unwrap() +// .take(10) +// .enumerate(); + +// // result should be 10 transactions messages +// while let Some((i, bytes)) = sub.next().await { +// let decoded_msg = Transaction::decode(bytes).unwrap(); + +// let (_, transaction) = items[i].to_owned(); +// assert_eq!(decoded_msg, transaction); +// if i == 9 { +// break; +// } +// } +// } + +// #[tokio::test] +// async fn multiple_subscribers_same_subject() { +// let (_, _, client) = server_setup().await.unwrap(); +// let stream = fuel_streams::Stream::::new(&client).await; +// let producer = Some(Address::zeroed()); +// let items = publish_blocks(stream.stream(), producer.clone(), None) +// .unwrap() +// .0; + +// let clients_count = 100; +// let done_signal = 99; +// let mut handles = Vec::new(); +// for _ in 0..clients_count { +// let stream = stream.clone(); +// let items = items.clone(); +// handles.push(tokio::spawn(async move { +// let mut sub = stream.subscribe_raw().await.unwrap().enumerate(); +// while let Some((i, bytes)) = sub.next().await { +// let decoded_msg = Block::decode_raw(bytes).unwrap(); +// let (subject, block) = items[i].to_owned(); +// let height = decoded_msg.payload.height; + +// assert_eq!(decoded_msg.subject, subject.parse()); +// assert_eq!(decoded_msg.payload, block); +// assert_eq!(height, i as u32); +// if i == 9 { +// return done_signal; +// } +// } +// done_signal + 1 +// })); +// } + +// let mut client_results = try_join_all(handles).await.unwrap(); +// assert!( +// client_results.len() == clients_count, +// "must have all clients subscribed to one subject" +// ); +// client_results.dedup(); +// assert!( +// client_results.len() == 1, +// "all clients must have the same result" +// ); +// assert!( +// client_results.first().cloned().unwrap() == done_signal, +// "all clients must have the same received the complete signal" +// ); +// } + +// #[tokio::test] +// async fn multiple_subscribers_different_subjects() { +// let (_, _, client) = server_setup().await.unwrap(); +// let producer = Some(Address::zeroed()); +// let block_stream = fuel_streams::Stream::::new(&client).await; +// let block_items = +// publish_blocks(block_stream.stream(), producer.clone(), None) +// .unwrap() +// .0; + +// let txs_stream = fuel_streams::Stream::::new(&client).await; +// let mock_block = MockBlock::build(1); +// let txs_items = +// publish_transactions(txs_stream.stream(), &mock_block, None) +// .unwrap() +// .0; + +// let clients_count = 100; +// let done_signal = 99; +// let mut handles = Vec::new(); +// for _ in 0..clients_count { +// // blocks stream +// let stream = block_stream.clone(); +// let items = block_items.clone(); +// handles.push(tokio::spawn(async move { +// let mut sub = stream.subscribe_raw().await.unwrap().enumerate(); +// while let Some((i, bytes)) = sub.next().await { +// let decoded_msg = Block::decode_raw(bytes).unwrap(); +// let (subject, block) = items[i].to_owned(); +// let height = decoded_msg.payload.height; + +// assert_eq!(decoded_msg.subject, subject.parse()); +// assert_eq!(decoded_msg.payload, block); +// assert_eq!(height, i as u32); +// if i == 9 { +// return done_signal; +// } +// } +// done_signal + 1 +// })); + +// // txs stream +// let stream = txs_stream.clone(); +// let items = txs_items.clone(); +// handles.push(tokio::spawn(async move { +// let mut sub = stream.subscribe_raw().await.unwrap().enumerate(); +// while let Some((i, bytes)) = sub.next().await { +// let decoded_msg = Transaction::decode_raw(bytes).unwrap(); +// let (_, transaction) = items[i].to_owned(); +// assert_eq!(decoded_msg.payload, transaction); +// if i == 9 { +// return done_signal; +// } +// } +// done_signal + 1 +// })); +// } + +// let mut client_results = try_join_all(handles).await.unwrap(); +// assert!( +// client_results.len() == 2 * clients_count, +// "must have all clients subscribed to two subjects" +// ); +// client_results.dedup(); +// assert!( +// client_results.len() == 1, +// "all clients must have the same result" +// ); +// assert!( +// client_results.first().cloned().unwrap() == done_signal, +// "all clients must have the same received the complete signal" +// ); +// }