diff --git a/Cargo.lock b/Cargo.lock index 115a1dd3d..ccbb1fa76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3414,6 +3414,7 @@ dependencies = [ "fuel-indexer-lib", "fuel-indexer-schema", "fuel-indexer-types", + "futures", "getrandom", "hex", "serde", @@ -4081,6 +4082,50 @@ dependencies = [ "thiserror", ] +[[package]] +name = "greetings-data" +version = "0.0.0" +dependencies = [ + "clap 3.2.25", + "fuel-indexer-lib", + "fuel-indexer-tests", + "fuel-tx 0.35.3", + "fuel-types 0.35.3", + "fuels", + "rand", + "thiserror", + "tokio", +] + +[[package]] +name = "greetings-fuel-client" +version = "0.0.0" +dependencies = [ + "clap 3.2.25", + "fuel-indexer-tests", + "tokio", +] + +[[package]] +name = "greetings_indexer" +version = "0.0.0" +dependencies = [ + "fuel-indexer-utils", + "fuels", + "serde", +] + +[[package]] +name = "greetings_native_indexer" +version = "0.0.0" +dependencies = [ + "async-trait", + "fuel-indexer", + "fuel-indexer-utils", + "fuels", + "serde", +] + [[package]] name = "group" version = "0.12.1" @@ -4245,44 +4290,9 @@ dependencies = [ ] [[package]] -name = "hello-world-data" -version = "0.0.0" -dependencies = [ - "clap 3.2.25", - "fuel-indexer-lib", - "fuel-indexer-tests", - "fuel-tx 0.35.3", - "fuel-types 0.35.3", - "fuels", - "rand", - "thiserror", - "tokio", -] - -[[package]] -name = "hello-world-node" +name = "hello_world" version = "0.0.0" dependencies = [ - "clap 3.2.25", - "fuel-indexer-tests", - "tokio", -] - -[[package]] -name = "hello_indexer" -version = "0.0.0" -dependencies = [ - "fuel-indexer-utils", - "fuels", - "serde", -] - -[[package]] -name = "hello_indexer_native" -version = "0.0.0" -dependencies = [ - "async-trait", - "fuel-indexer", "fuel-indexer-utils", "fuels", "serde", diff --git a/Cargo.toml b/Cargo.toml index 7950a8c3d..0c3e3c280 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,10 +3,11 @@ resolver = "2" members = [ "examples/fuel-explorer/fuel-explorer", - "examples/hello-world-native/hello-indexer-native", - "examples/hello-world/hello-indexer", - "examples/hello-world/hello-world-data", - "examples/hello-world/hello-world-node", + "examples/greetings-native/greetings-native-indexer", + "examples/greetings/greetings-data", + "examples/greetings/greetings-fuel-client", + "examples/greetings/greetings-indexer", + "examples/hello-world/hello-world", "packages/fuel-indexer-api-server", "packages/fuel-indexer-benchmarks", "packages/fuel-indexer-database", diff --git a/ci/Dockerfile.fuel-node b/ci/Dockerfile.fuel-node index 14be4c167..c45ec95ad 100644 --- a/ci/Dockerfile.fuel-node +++ b/ci/Dockerfile.fuel-node @@ -52,4 +52,4 @@ COPY --from=builder /build/target/release/fuel-node . COPY --from=builder /build/target/release/fuel-node.d . COPY --from=builder /build/packages/fuel-indexer-tests/test-chain-config.json . COPY --from=builder /build/packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test.bin . -COPY --from=builder /build/examples/hello-world/contracts/greeting/out/debug/greeting.bin . +COPY --from=builder /build/examples/greetings/contracts/greeting/out/debug/greeting.bin . diff --git a/docs/README.md b/docs/README.md index 882129166..3a5ac7549 100644 --- a/docs/README.md +++ b/docs/README.md @@ -68,19 +68,20 @@ OPTIONS: -V, --version Print version information SUBCOMMANDS: - auth Authenticate against an indexer service - build Build an indexer - check Check for Fuel indexer components - deploy Deploy an indexer to an indexer service - help Print this message or the help of the given subcommand(s) - kill Kill the indexer process. Note that this command will kill any process listening - on the default indexer port or the port specified by the `--port` flag - new Create a new indexer project in a new directory - postgres Fuel Postgres Orchestrator - remove Stop and remove a running indexer - start Standalone binary for the Fuel indexer service - status Check the status of a registered indexer - + auth Authenticate against an indexer service + build Build an indexer + check Check for Fuel indexer components + deploy Deploy an indexer to an indexer service + help Print this message or the help of the given subcommand(s) + kill Kill the indexer process. Note that this command will kill any process + listening on the default indexer port or the port specified by the `--port` + flag + new Create a new indexer project in a new directory + postgres Fuel Postgres Orchestrator + remove Stop and remove a running indexer + run-native Run a native indexer + start Standalone binary for the Fuel indexer service + status Check the status of a registered indexer ``` #### [➡️ For more details on how to build Fuel indexers, read the docs! ➡️](https://docs.fuel.network/docs/indexer/) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 970c27264..26cd29c76 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -44,6 +44,7 @@ - [remove](./forc-index/remove.md) - [start](./forc-index/start.md) - [status](./forc-index/status.md) + - [run-native](./forc-index/run-native.md) - [forc postgres](./forc-postgres/index.md) - [create](./forc-postgres/create.md) - [drop](./forc-postgres/drop.md) diff --git a/docs/src/forc-index/index.md b/docs/src/forc-index/index.md index 45bc1cf14..a541b3826 100644 --- a/docs/src/forc-index/index.md +++ b/docs/src/forc-index/index.md @@ -2,29 +2,6 @@ `forc index` is the recommended method for end users to interact with the Fuel indexer. After you have installed `fuelup`, you can run the `forc index help` command in your terminal to view the available commands. -```text -forc index help -``` - -```text -USAGE: - forc-index - -OPTIONS: - -h, --help Print help information - -V, --version Print version information - -SUBCOMMANDS: - auth Authenticate against an indexer service - build Build an indexer - check Check for Fuel indexer components - deploy Deploy an indexer to an indexer service - help Print this message or the help of the given subcommand(s) - kill Kill the indexer process. Note that this command will kill any process listening - on the default indexer port or the port specified by the `--port` flag - new Create a new indexer project in a new directory - postgres Fuel Postgres Orchestrator - remove Stop and remove a running indexer - start Standalone binary for the Fuel indexer service - status Check the status of a registered indexer +```text,ignore +{{#include ./../../README.md 59:84}} ``` diff --git a/docs/src/forc-index/run-native.md b/docs/src/forc-index/run-native.md new file mode 100644 index 000000000..8a303c86b --- /dev/null +++ b/docs/src/forc-index/run-native.md @@ -0,0 +1,45 @@ +# `forc index run-native` + +Run a native indexer. + +```bash +forc index run-native --path . -- --run-migrations --postgres-host localhost +``` + +```text +Run a native indexer + +USAGE: + forc-index run-native [OPTIONS] [-- ...] + +ARGS: + ... + Extra passed to `fuel-indexer run` + + Example usage: `forc-index run-native --path . -- --run-migrations --stop-idle-indexers` + +OPTIONS: + --bin + Path to native indexer binary (if not using default location). + + -d, --debug + Build artifacts with the debug profile. + + -h, --help + Print help information + + --locked + Ensure that the Cargo.lock file is up-to-date. + + -m, --manifest + Manifest file name of indexer being built. + + -p, --path + Path to the indexer project. + + --skip-build + Do not build before deploying. + + -v, --verbose + Enable verbose output. +``` diff --git a/examples/fuel-explorer/README.md b/examples/fuel-explorer/README.md index 20a7f8ff8..ad22402df 100644 --- a/examples/fuel-explorer/README.md +++ b/examples/fuel-explorer/README.md @@ -19,7 +19,7 @@ docker compose up --build ### Deploy the indexer ```bash -forc index deploy --path hello-indexer --url http://0.0.0.0:29987 +forc index deploy --path greetings --url http://0.0.0.0:29987 ``` ### Interact diff --git a/examples/greetings-native/Dockerfile b/examples/greetings-native/Dockerfile new file mode 100644 index 000000000..59dc94e9c --- /dev/null +++ b/examples/greetings-native/Dockerfile @@ -0,0 +1,81 @@ +# Stage 1: Build +FROM --platform=$BUILDPLATFORM tonistiigi/xx AS xx +FROM --platform=$BUILDPLATFORM rust:1.72.1 AS chef + +ARG TARGETPLATFORM +RUN cargo install cargo-chef +WORKDIR /build/ + +COPY --from=xx / / + +# hadolint ignore=DL3008 +RUN apt-get update -y && \ + apt-get install -y --no-install-recommends \ + build-essential \ + clang \ + cmake \ + gcc \ + git \ + libclang-dev \ + lld \ + llvm \ + pkg-config + +RUN xx-apt-get update && \ + xx-apt-get install -y binutils g++ libc6-dev && \ + apt-get clean + +FROM chef AS planner +ENV CARGO_NET_GIT_FETCH_WITH_CLI=true +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +# Stage 2: build binaries +FROM chef AS builder +ENV CARGO_NET_GIT_FETCH_WITH_CLI=true +COPY --from=planner /build/recipe.json recipe.json + +ENV SQLX_OFFLINE=true + +RUN xx-cargo chef cook --release -p greetings_native_indexer --recipe-path recipe.json +COPY . . +RUN xx-cargo build --release -p greetings_native_indexer \ + && xx-verify ./target/$(xx-cargo --print-target-triple)/release/greetings_native_indexer \ + && mv ./target/$(xx-cargo --print-target-triple)/release/greetings_native_indexer ./target/release/greetings_native_indexer \ + && mv ./target/$(xx-cargo --print-target-triple)/release/greetings_native_indexer.d ./target/release/greetings_native_indexer.d + +# Stage 3: Run +FROM ubuntu:22.04 AS run + +WORKDIR /root/ + +RUN DEBIAN_FRONTEND=noninteractive apt-get update -y \ + && apt-get install -y --no-install-recommends \ + tzdata \ + ca-certificates \ + libpq-dev \ + wget \ + # Clean up + && apt-get autoremove -y \ + && apt-get clean -y + +COPY --from=builder /build/target/release/greetings_native_indexer . +COPY --from=builder /build/target/release/greetings_native_indexer.d . +COPY --from=builder /build/examples/greetings-native/greetings-native-indexer . +COPY --from=builder /build/examples/greetings/contracts/greeting/out/debug/greeting-abi.json . + +# Since unlike WASM indexers, native indexers can't be deployed, we need to +# write the specific asset paths to the manifest, as well as the manifest itself. +# +# We only have to do this because the example is contained within the project worksapce (and it's +# manifest asset paths are relative to that workspace root). You wouldn't need to do this otherwise. +RUN echo "namespace: fuellabs\n\ +fuel_client: ~\n\ +graphql_schema: schema/greetings_native_indexer.schema.graphql\n\ +abi: greetings-abi.son\n\ +start_block: ~\n\ +end_block: ~\n\ +contract_id: fuel1q6sj2srt0u40jdqg2lvvnspyuhse9rs2s2fv9nmv0hqjcrdc7sqsfpwv9x\n\ +identifier: greetings_native\n\ +module: native\n\ +resumable: true" > greetings_native_indexer.manifest.yaml \ No newline at end of file diff --git a/examples/greetings-native/README.md b/examples/greetings-native/README.md new file mode 100644 index 000000000..13ec524d4 --- /dev/null +++ b/examples/greetings-native/README.md @@ -0,0 +1,60 @@ +# greetings-native + +A simple program that demonstrates the full Fuel indexer experience. + +## Usage + +> NOTE: Commands are run from `fuel-indexer/examples/greetings-native` + +### Spin up containers + +Build image locally + +```bash +docker compose up +``` + +Spin up containers for the Postgres database server and the indexer service. + +> IMPORTANT: Ensure that any local Postgres instance on port 5432 is stopped. + +```bash +docker compose up +``` + +### Deploy the indexer + +> Note that since this example uses native execution (as opposed to WASM execution), there is no need to +> "deploy" the indexer. You'll notice that your indexer is already running inside your Docker container. + +### Interact + +Trigger some test data by simulating a contract call. + +```bash +cargo run -p greetings-data --bin greetings-data -- --host 0.0.0.0:4000 +``` + +### Validate + +Ensure that test data was indexed via a GraphQL query: + 1. Open this GraphQL playground link http://192.168.1.34:29987/api/playground/fuellabs/greetings-native_indexer + 2. Submit the following query + +```graphql +query { + transaction { + hash + block { + hash + height + } + } +} +``` + +> IMPORTANT: Since this example uses a dockerized indexer service, with the GraphQL +> web server being bound at interface `0.0.0.0` your LAN IP might differ from the +> `192.168.1.34` mentioned above. +> +> On *nix platforms you can typically find your LAN IP via `ifconfig | grep inet` diff --git a/examples/greetings-native/docker-compose.yaml b/examples/greetings-native/docker-compose.yaml new file mode 100644 index 000000000..c799f920d --- /dev/null +++ b/examples/greetings-native/docker-compose.yaml @@ -0,0 +1,48 @@ +version: "3.9" +services: + postgres: + image: postgres:latest + ports: + - "5432:5432" + volumes: + - .:/usr/local/postgres + environment: + - POSTGRES_PASSWORD=postgres + - PGUSER=postgres + healthcheck: + test: + [ + "CMD-SHELL", + "pg_isready", + "-U", + "postgres", + "-d", + "postgres" + ] + interval: 30s + timeout: 60s + retries: 5 + start_period: 80s + fuel-node: + build: + context: ./../../ + dockerfile: ./ci/Dockerfile.fuel-node + image: fuel-indexer/local:fuel-node + command: bash -c "./fuel-node --host 0.0.0.0:4000 --contract-bin greeting.bin --chain-config test-chain-config.json" + ports: + - "4000:4000" + volumes: + - .:/usr/local/fuel-node + depends_on: + - postgres + greetings-native: + build: + context: ./../../ + dockerfile: ./examples/greetings-native/Dockerfile + command: bash -c "sleep 2 && ./greetings_native_indexer --fuel-node-host fuel-node --postgres-host postgres --postgres-password postgres --web-api-host 0.0.0.0 --run-migrations --manifest ./greetings_native_indexer.manifest.yaml" + ports: + - "29987:29987" + volumes: + - .:/usr/local/greetings-native + depends_on: + - fuel-node \ No newline at end of file diff --git a/examples/hello-world-native/hello-indexer-native/Cargo.toml b/examples/greetings-native/greetings-native-indexer/Cargo.toml similarity index 90% rename from examples/hello-world-native/hello-indexer-native/Cargo.toml rename to examples/greetings-native/greetings-native-indexer/Cargo.toml index f64553625..fe6d33ea0 100644 --- a/examples/hello-world-native/hello-indexer-native/Cargo.toml +++ b/examples/greetings-native/greetings-native-indexer/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "hello_indexer_native" +name = "greetings_native_indexer" version = "0.0.0" edition = "2021" publish = false diff --git a/examples/hello-world-native/hello-indexer-native/hello_indexer_native.manifest.yaml b/examples/greetings-native/greetings-native-indexer/greetings_native_indexer.manifest.yaml similarity index 83% rename from examples/hello-world-native/hello-indexer-native/hello_indexer_native.manifest.yaml rename to examples/greetings-native/greetings-native-indexer/greetings_native_indexer.manifest.yaml index e3e2729d8..664e3601e 100644 --- a/examples/hello-world-native/hello-indexer-native/hello_indexer_native.manifest.yaml +++ b/examples/greetings-native/greetings-native-indexer/greetings_native_indexer.manifest.yaml @@ -3,11 +3,11 @@ namespace: fuellabs # The identifier field is used to identify the given index. -identifier: hello_indexer_native +identifier: greetings_native # The abi option is used to provide a link to the Sway JSON ABI that is generated when you # build your project. -abi: examples/hello-world/contracts/greeting/out/debug/greeting-abi.json +abi: examples/greetings/contracts/greeting/out/debug/greeting-abi.json # The particular start block after which you'd like your indexer to start indexing events. start_block: ~ @@ -22,11 +22,11 @@ end_block: ~ fuel_client: ~ # The contract_id specifies which particular contract you would like your index to subscribe to. -contract_id: fuel18hchrf7f4hnpkl84sqf8k0sk8gcauzeemzwgweea8dgr7eachv4s86r9t9 +contract_id: fuel1q6sj2srt0u40jdqg2lvvnspyuhse9rs2s2fv9nmv0hqjcrdc7sqsfpwv9x # The graphql_schema field contains the file path that points to the GraphQL schema for the # given index. -graphql_schema: examples/hello-world-native/hello-indexer-native/schema/hello_indexer_native.schema.graphql +graphql_schema: examples/greetings-native/greetings-native-indexer/schema/greetings_native_indexer.schema.graphql # The module field contains a file path that points to code that will be run as an executor inside # of the indexer. diff --git a/examples/hello-world-native/hello-indexer-native/schema/hello_indexer_native.schema.graphql b/examples/greetings-native/greetings-native-indexer/schema/greetings_native_indexer.schema.graphql similarity index 100% rename from examples/hello-world-native/hello-indexer-native/schema/hello_indexer_native.schema.graphql rename to examples/greetings-native/greetings-native-indexer/schema/greetings_native_indexer.schema.graphql diff --git a/examples/greetings-native/greetings-native-indexer/src/main.rs b/examples/greetings-native/greetings-native-indexer/src/main.rs new file mode 100644 index 000000000..b5a6c127e --- /dev/null +++ b/examples/greetings-native/greetings-native-indexer/src/main.rs @@ -0,0 +1,25 @@ +extern crate alloc; +use fuel_indexer_utils::prelude::*; + +#[indexer( + manifest = "examples/greetings-native/greetings-native-indexer/greetings_native_indexer.manifest.yaml" +)] +mod greetings_native_indexer { + + async fn greetings_indexer_handler(event: Greeting, block_data: BlockData) { + info!("Handling new Greeting event."); + let height = std::cmp::min(0, block_data.header.height - 1); + let name = event.person.name.to_right_trimmed_str().to_string(); + let greeting = event.greeting.to_right_trimmed_str().to_string(); + let message = format!("{greeting} 👋, my name is {name}"); + + let greeter = Greeter::new(name, height).get_or_create().await; + + let salutation = Salutation::new(message, greeter.id.clone(), height) + .get_or_create() + .await; + + greeter.save().await; + salutation.save().await; + } +} diff --git a/examples/greetings/README.md b/examples/greetings/README.md new file mode 100644 index 000000000..561291c70 --- /dev/null +++ b/examples/greetings/README.md @@ -0,0 +1,62 @@ +# greetings + +A simple program that demonstrates the full Fuel indexer experience. + +## Usage + +> NOTE: Commands are run from `fuel-indexer/examples/greetings` + +### Spin up containers + +Pull the latest image + +```bash +docker pull ghcr.io/fuellabs/fuel-indexer:latest +``` + +Spin up containers for the Postgres database server and the indexer service. + +> IMPORTANT: Ensure that any local Postgres instance on port 5432 is stopped. + +```bash +docker compose up +``` + +### Deploy the indexer + +```bash +forc index deploy --path greetings --url http://0.0.0.0:29987 +``` + +### Interact + +Trigger some test data by simulating a contract call. + +```bash +cargo run -p greetings-data --bin greetings-data -- --host 0.0.0.0:4000 +``` + +### Validate + +Ensure that test data was indexed via a GraphQL query: + 1. Open this GraphQL playground link http://192.168.1.34:29987/api/playground/fuellabs/greetings_indexer + 2. Submit the following query + +```graphql +query { + salutation { + id + message_hash + message + greeter + first_seen + last_seen + } +} +``` + +> IMPORTANT: Since this example uses a dockerized indexer service, with the GraphQL +> web server being bound at interface `0.0.0.0` your LAN IP might differ from the +> `192.168.1.34` mentioned above. +> +> On *nix platforms you can typically find your LAN IP via `ifconfig | grep inet` diff --git a/examples/hello-world/contracts/greeting/.gitignore b/examples/greetings/contracts/greeting/.gitignore similarity index 100% rename from examples/hello-world/contracts/greeting/.gitignore rename to examples/greetings/contracts/greeting/.gitignore diff --git a/examples/hello-world/contracts/greeting/Forc.lock b/examples/greetings/contracts/greeting/Forc.lock similarity index 100% rename from examples/hello-world/contracts/greeting/Forc.lock rename to examples/greetings/contracts/greeting/Forc.lock diff --git a/examples/hello-world/contracts/greeting/Forc.toml b/examples/greetings/contracts/greeting/Forc.toml similarity index 100% rename from examples/hello-world/contracts/greeting/Forc.toml rename to examples/greetings/contracts/greeting/Forc.toml diff --git a/examples/hello-world/contracts/greeting/out/debug/greeting-abi.json b/examples/greetings/contracts/greeting/out/debug/greeting-abi.json similarity index 100% rename from examples/hello-world/contracts/greeting/out/debug/greeting-abi.json rename to examples/greetings/contracts/greeting/out/debug/greeting-abi.json diff --git a/examples/hello-world/contracts/greeting/out/debug/greeting-storage_slots.json b/examples/greetings/contracts/greeting/out/debug/greeting-storage_slots.json similarity index 100% rename from examples/hello-world/contracts/greeting/out/debug/greeting-storage_slots.json rename to examples/greetings/contracts/greeting/out/debug/greeting-storage_slots.json diff --git a/examples/hello-world/contracts/greeting/out/debug/greeting.bin b/examples/greetings/contracts/greeting/out/debug/greeting.bin similarity index 100% rename from examples/hello-world/contracts/greeting/out/debug/greeting.bin rename to examples/greetings/contracts/greeting/out/debug/greeting.bin diff --git a/examples/hello-world/contracts/greeting/src/main.sw b/examples/greetings/contracts/greeting/src/main.sw similarity index 100% rename from examples/hello-world/contracts/greeting/src/main.sw rename to examples/greetings/contracts/greeting/src/main.sw diff --git a/examples/greetings/docker-compose.yaml b/examples/greetings/docker-compose.yaml new file mode 100644 index 000000000..a928c365c --- /dev/null +++ b/examples/greetings/docker-compose.yaml @@ -0,0 +1,46 @@ +version: "3.9" +services: + postgres: + image: postgres:latest + ports: + - "5432:5432" + volumes: + - .:/usr/local/postgres + environment: + - POSTGRES_PASSWORD=postgres + - PGUSER=postgres + healthcheck: + test: + [ + "CMD-SHELL", + "pg_isready", + "-U", + "postgres", + "-d", + "postgres" + ] + interval: 30s + timeout: 60s + retries: 5 + start_period: 80s + fuel-node: + build: + context: ./../../ + dockerfile: ./ci/Dockerfile.fuel-node + image: fuel-indexer/local:fuel-node + command: bash -c "./fuel-node --host 0.0.0.0:4000 --contract-bin greeting.bin --chain-config test-chain-config.json" + ports: + - "4000:4000" + volumes: + - .:/usr/local/fuel-node + depends_on: + - postgres + fuel-indexer: + image: ghcr.io/fuellabs/fuel-indexer:latest + command: bash -c "sleep 2 && ./fuel-indexer run --fuel-node-host fuel-node --postgres-host postgres --postgres-password postgres --web-api-host 0.0.0.0 --run-migrations" + ports: + - "29987:29987" + volumes: + - .:/usr/local/fuel-indexer + depends_on: + - fuel-node diff --git a/examples/hello-world/hello-world-data/Cargo.toml b/examples/greetings/greetings-data/Cargo.toml similarity index 94% rename from examples/hello-world/hello-world-data/Cargo.toml rename to examples/greetings/greetings-data/Cargo.toml index 6a5e266ae..5340de0c4 100644 --- a/examples/hello-world/hello-world-data/Cargo.toml +++ b/examples/greetings/greetings-data/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "hello-world-data" +name = "greetings-data" version = "0.0.0" edition = "2021" publish = false diff --git a/examples/hello-world/hello-world-data/README.md b/examples/greetings/greetings-data/README.md similarity index 55% rename from examples/hello-world/hello-world-data/README.md rename to examples/greetings/greetings-data/README.md index 61012b6a0..8dd9c11a1 100644 --- a/examples/hello-world/hello-world-data/README.md +++ b/examples/greetings/greetings-data/README.md @@ -1,19 +1,19 @@ -# hello-world-data +# greetings-data -This program is used to generate test data for the `hello-world` example. +This program is used to generate test data for the `greetings` example. ## Usage ```bash -cargo run --bin hello-world-data -- --help +cargo run --bin greetings-data -- --help ``` ```text -Test program used to generate data for the hello-world example. -hello-world-data +Test program used to generate data for the greetings example. +greetings-data USAGE: - hello-world-data [OPTIONS] + greetings-data [OPTIONS] OPTIONS: --chain-config Test wallet filepath @@ -25,5 +25,5 @@ OPTIONS: Generate a test data point. ```bash -cargo run --bin hello-world-data +cargo run --bin greetings-data ``` diff --git a/examples/hello-world/hello-world-data/src/main.rs b/examples/greetings/greetings-data/src/main.rs similarity index 90% rename from examples/hello-world/hello-world-data/src/main.rs rename to examples/greetings/greetings-data/src/main.rs index 4c201cc8b..644d05c57 100644 --- a/examples/hello-world/hello-world-data/src/main.rs +++ b/examples/greetings/greetings-data/src/main.rs @@ -11,19 +11,21 @@ use std::path::{Path, PathBuf}; abigen!(Contract( name = "Greet", - abi = "examples/hello-world/contracts/greeting/out/debug/greeting-abi.json" + abi = "examples/greetings/contracts/greeting/out/debug/greeting-abi.json" )); #[derive(Debug, Parser, Clone)] #[clap( - name = "hello-world-data", - about = "Test program used to generate data for the hello-world example." + name = "greetings-data", + about = "Test program used to generate data for the greetings example." )] pub struct Args { #[clap(long, help = "Test wallet filepath")] pub chain_config: Option, + #[clap(long, help = "Contract bin filepath")] pub contract_bin: Option, + #[clap(long, help = "Host at which to bind.")] pub host: Option, } @@ -74,14 +76,15 @@ async fn main() -> Result<(), Box> { let host = opts .host .unwrap_or_else(|| defaults::FUEL_NODE_ADDR.to_string()); - println!("Using Fuel node at {host}",); + + println!("Using Fuel node at {host}"); let wallet_path_str = chain_config.as_os_str().to_str().unwrap(); let mut wallet = WalletUnlocked::load_keystore(wallet_path_str, defaults::WALLET_PASSWORD, None) .unwrap(); - let provider = Provider::connect(defaults::FUEL_NODE_ADDR).await.unwrap(); + let provider = Provider::connect(host).await.unwrap(); wallet.set_provider(provider.clone()); @@ -93,6 +96,7 @@ async fn main() -> Result<(), Box> { let id = loaded_contract.contract_id(); let contract_id = Bech32ContractId::from(id); + println!("Using contract id: {}", contract_id.to_string()); let contract = Greet::new(contract_id, wallet.clone()); diff --git a/examples/hello-world/hello-world-node/Cargo.toml b/examples/greetings/greetings-fuel-client/Cargo.toml similarity index 90% rename from examples/hello-world/hello-world-node/Cargo.toml rename to examples/greetings/greetings-fuel-client/Cargo.toml index aae3a630c..d9fa6fa5f 100644 --- a/examples/hello-world/hello-world-node/Cargo.toml +++ b/examples/greetings/greetings-fuel-client/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "hello-world-node" +name = "greetings-fuel-client" version = "0.0.0" edition = "2021" publish = false diff --git a/examples/hello-world/hello-world-node/src/main.rs b/examples/greetings/greetings-fuel-client/src/main.rs similarity index 96% rename from examples/hello-world/hello-world-node/src/main.rs rename to examples/greetings/greetings-fuel-client/src/main.rs index 927f705a5..c7c37128d 100644 --- a/examples/hello-world/hello-world-node/src/main.rs +++ b/examples/greetings/greetings-fuel-client/src/main.rs @@ -7,8 +7,8 @@ const NODE_TTL_SECS: u64 = 60 * 30; #[derive(Debug, Parser, Clone)] #[clap( - name = "hello-world-node", - about = "Test program used to start up a test Fuel node for the hello-world example." + name = "greetings-fuel-client", + about = "Test program used to start up a test Fuel node for the greetings example." )] pub struct Args { #[clap(long, help = "Test wallet filepath")] diff --git a/examples/hello-world/hello-indexer/.cargo/config b/examples/greetings/greetings-indexer/.cargo/config similarity index 100% rename from examples/hello-world/hello-indexer/.cargo/config rename to examples/greetings/greetings-indexer/.cargo/config diff --git a/examples/greetings/greetings-indexer/Cargo.toml b/examples/greetings/greetings-indexer/Cargo.toml new file mode 100644 index 000000000..43f240308 --- /dev/null +++ b/examples/greetings/greetings-indexer/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "greetings_indexer" +version = "0.0.0" +edition = "2021" +publish = false +rust-version = "1.72.1" + +[lib] +crate-type = ['cdylib'] + +[dependencies] +fuel-indexer-utils = { workspace = true } +fuels = { workspace = true } +serde = { workspace = true } diff --git a/examples/greetings/greetings-indexer/greetings_indexer.manifest.yaml b/examples/greetings/greetings-indexer/greetings_indexer.manifest.yaml new file mode 100644 index 000000000..2c49dbbc7 --- /dev/null +++ b/examples/greetings/greetings-indexer/greetings_indexer.manifest.yaml @@ -0,0 +1,39 @@ +# A namespace is a logical grouping of declared names. Think of the namespace +# as an organization identifier +namespace: fuellabs + +# The identifier field is used to identify the given index. +identifier: greetings + +# The abi option is used to provide a link to the Sway JSON ABI that is generated when you +# build your project. +abi: examples/greetings/contracts/greeting/out/debug/greeting-abi.json + +# The particular start block after which you'd like your indexer to start indexing events. +start_block: ~ + +# The particular end block after which you'd like your indexer to stop indexing events. +end_block: ~ + +# The `fuel_client` denotes the address (host, port combination) of the running Fuel client +# that you would like your indexer to index events from. In order to use this per-indexer +# `fuel_client` option, the indexer service at which your indexer is deployed will have to run +# with the `--indexer_net_config` option. +fuel_client: ~ + +# The contract_id specifies which particular contract you would like your index to subscribe to. +contract_id: fuel1q6sj2srt0u40jdqg2lvvnspyuhse9rs2s2fv9nmv0hqjcrdc7sqsfpwv9x + +# The graphql_schema field contains the file path that points to the GraphQL schema for the +# given index. +graphql_schema: examples/greetings/greetings-indexer/schema/greetings_indexer.schema.graphql + +# The module field contains a file path that points to code that will be run as an executor inside +# of the indexer. +# Important: At this time, wasm is the preferred method of execution. +module: + wasm: target/wasm32-unknown-unknown/release/greetings_indexer.wasm + +# The resumable field contains a boolean that specifies whether or not the indexer should, synchronise +# with the latest block if it has fallen out of sync. +resumable: true \ No newline at end of file diff --git a/examples/hello-world/hello-indexer/schema/hello_indexer.schema.graphql b/examples/greetings/greetings-indexer/schema/greetings_indexer.schema.graphql similarity index 100% rename from examples/hello-world/hello-indexer/schema/hello_indexer.schema.graphql rename to examples/greetings/greetings-indexer/schema/greetings_indexer.schema.graphql diff --git a/examples/greetings/greetings-indexer/src/lib.rs b/examples/greetings/greetings-indexer/src/lib.rs new file mode 100644 index 000000000..ed97c4ef3 --- /dev/null +++ b/examples/greetings/greetings-indexer/src/lib.rs @@ -0,0 +1,24 @@ +extern crate alloc; +use fuel_indexer_utils::prelude::*; + +#[indexer( + manifest = "examples/greetings/greetings-indexer/greetings_indexer.manifest.yaml" +)] +mod greetings_indexer { + + fn greetings_indexer_handler(event: Greeting, block_data: BlockData) { + info!("Handling new Greeting event."); + let height = std::cmp::min(0, block_data.header.height - 1); + let name = event.person.name.to_right_trimmed_str().to_string(); + let greeting = event.greeting.to_right_trimmed_str().to_string(); + let message = format!("{greeting} 👋, my name is {name}"); + + let greeter = Greeter::new(name, height).get_or_create(); + + let salutation = + Salutation::new(message, greeter.id.clone(), height).get_or_create(); + + greeter.save(); + salutation.save(); + } +} diff --git a/examples/hello-world-native/README.md b/examples/hello-world-native/README.md deleted file mode 100644 index e5ddecd01..000000000 --- a/examples/hello-world-native/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# hello-world-native - -## Usage - -> NOTE: Commands are run from `fuel-indexer/examples/hello-world-native` - -### Start a Fuel node - -```bash -cargo run -p hello-world-node --bin hello-world-node -``` - -### Start the indexer service - -> IMPORTANT: Remember that unlike WebAssembly (WASM) execution, native execution builds a binary -> that can be invoked using either `cargo` or executed directly. - -Build binary. - -```bash -cargo build -p hello_indexer_native --locked --release -``` - -Run binary. - -```bash -./target/release/hello_indexer_native --manifest examples/hello-world-native/hello-indexer-native/hello_indexer_native.manifest.yaml --run-migrations -``` - -### Interact - -Trigger some test data by simulating a contract call. - -```bash -cargo run -p hello-world-data --bin hello-world-data -- --host 0.0.0.0:4000 -``` - -### Validate - -Ensure that test data was indexed via a GraphQL query: - 1. Open this GraphQL playground link http://192.168.1.34:29987/api/playground/fuellabs/hello_indexer_native - 2. Submit the following query - -```graphql -query { - transaction { - id - time - label - } -} -``` - -> IMPORTANT: Since this example uses a dockerized indexer service, with the GraphQL -> web server being bound at interface `0.0.0.0` your LAN IP might differ from the -> `192.168.1.34` mentioned above. -> -> On *nix platforms you can typically find your LAN IP via `ifconfig | grep inet` \ No newline at end of file diff --git a/examples/hello-world-native/hello-indexer-native/src/main.rs b/examples/hello-world-native/hello-indexer-native/src/main.rs deleted file mode 100644 index 9b1d4d9d7..000000000 --- a/examples/hello-world-native/hello-indexer-native/src/main.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! A "Hello World" type of program for the Fuel Indexer service that uses native execution. -//! -//! Build this example's binary using the following command. -//! -//! Start a local test Fuel node -//! -//! ```bash -//! cargo run -p hello-world-node --bin hello-world-node -//! ``` -//! -//! With your database backend set up, now start your fuel-indexer binary using the -//! assets from this example: -//! -//! ```bash -//! cargo run -p hello_indexer_native --bin hello_indexer_native -- --manifest examples/hello-world-native/hello-indexer-native/hello_indexer_native.manifest.yaml -//! ``` -//! -//! Now trigger an event. -//! -//! ```bash -//! cargo run -p hello-world-data --bin hello-world-data -//! ``` -extern crate alloc; -use fuel_indexer_utils::prelude::*; - -#[indexer( - manifest = "examples/hello-world-native/hello-indexer-native/hello_indexer_native.manifest.yaml" -)] -mod hello_world_native { - - async fn index_logged_greeting(event: Greeting, block_data: BlockData) { - let height = std::cmp::min(0, block_data.header.height - 1); - let name = event.person.name.to_right_trimmed_str().to_string(); - let greeting = event.greeting.to_right_trimmed_str().to_string(); - let message = format!("{greeting} 👋, my name is {name}"); - - let greeter = Greeter::new(name, height).get_or_create().await; - - let salutation = Salutation::new(message, greeter.id.clone(), height) - .get_or_create() - .await; - - greeter.save().await; - salutation.save().await; - } -} diff --git a/examples/hello-world/README.md b/examples/hello-world/README.md index 3bc0af5f0..9fa1fe03c 100644 --- a/examples/hello-world/README.md +++ b/examples/hello-world/README.md @@ -1,6 +1,6 @@ -# hello world +# hello-world -A "Hello World" type of program for the Fuel Indexer service. +A simple program that demonstrates the full Fuel indexer experience. ## Usage @@ -8,6 +8,12 @@ A "Hello World" type of program for the Fuel Indexer service. ### Spin up containers +Pull the latest image + +```bash +docker pull ghcr.io/fuellabs/fuel-indexer:latest +``` + Spin up containers for the Postgres database server and the indexer service. > IMPORTANT: Ensure that any local Postgres instance on port 5432 is stopped. @@ -19,32 +25,23 @@ docker compose up ### Deploy the indexer ```bash -forc index deploy --path hello-indexer --url http://0.0.0.0:29987 -``` - -### Interact - -Trigger some test data by simulating a contract call. - -```bash -cargo run -p hello-world-data --bin hello-world-data -- --host 0.0.0.0:4000 +forc index deploy --path hello-world --url http://0.0.0.0:29987 ``` ### Validate Ensure that test data was indexed via a GraphQL query: - 1. Open this GraphQL playground link http://192.168.1.34:29987/api/playground/fuellabs/hello_indexer + 1. Open this GraphQL playground link http://192.168.1.34:29987/api/playground/fuellabs/hello-world_indexer 2. Submit the following query ```graphql query { - salutation { - id - message_hash - message - greeter - first_seen - last_seen + transaction { + hash + block { + hash + height + } } } ``` diff --git a/examples/hello-world/docker-compose.yaml b/examples/hello-world/docker-compose.yaml index a928c365c..bb5dbdc08 100644 --- a/examples/hello-world/docker-compose.yaml +++ b/examples/hello-world/docker-compose.yaml @@ -10,37 +10,17 @@ services: - POSTGRES_PASSWORD=postgres - PGUSER=postgres healthcheck: - test: - [ - "CMD-SHELL", - "pg_isready", - "-U", - "postgres", - "-d", - "postgres" - ] + test: ["CMD-SHELL", "pg_isready", "-U", "postgres", "-d", "postgres"] interval: 30s timeout: 60s retries: 5 start_period: 80s - fuel-node: - build: - context: ./../../ - dockerfile: ./ci/Dockerfile.fuel-node - image: fuel-indexer/local:fuel-node - command: bash -c "./fuel-node --host 0.0.0.0:4000 --contract-bin greeting.bin --chain-config test-chain-config.json" - ports: - - "4000:4000" - volumes: - - .:/usr/local/fuel-node - depends_on: - - postgres fuel-indexer: image: ghcr.io/fuellabs/fuel-indexer:latest - command: bash -c "sleep 2 && ./fuel-indexer run --fuel-node-host fuel-node --postgres-host postgres --postgres-password postgres --web-api-host 0.0.0.0 --run-migrations" + command: bash -c "sleep 2 && ./fuel-indexer run --fuel-node-host beta-4.fuel.network --fuel-node-port 80 --postgres-host postgres --postgres-password postgres --web-api-host 0.0.0.0 --run-migrations" ports: - "29987:29987" volumes: - .:/usr/local/fuel-indexer depends_on: - - fuel-node + - postgres \ No newline at end of file diff --git a/examples/hello-world/hello-indexer/src/lib.rs b/examples/hello-world/hello-indexer/src/lib.rs deleted file mode 100644 index f6a77c2c6..000000000 --- a/examples/hello-world/hello-indexer/src/lib.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! A "Hello World" type of program for the Fuel Indexer service. -//! -//! Build this example's WASM module using the following command. Note that a -//! wasm32-unknown-unknown target will be required. -//! -//! ```bash -//! cargo build -p hello-indexer --release --target wasm32-unknown-unknown -//! ``` -//! -//! Start a local test Fuel node. -//! -//! ```bash -//! cargo run -p hello-world-node --bin hello-world-node -//! ``` -//! -//! With your database backend set up, now start your fuel-indexer binary using the -//! assets from this example: -//! -//! ```bash -//! cargo run --bin fuel-indexer -- run --manifest examples/hello-world/hello-indexer/hello_indexer.manifest.yaml --run-migrations -//! ``` -//! -//! Now trigger an event. -//! -//! ```bash -//! cargo run -p hello-world-data --bin hello-world-data -//! ``` - -extern crate alloc; -use fuel_indexer_utils::prelude::*; - -#[indexer(manifest = "examples/hello-world/hello-indexer/hello_indexer.manifest.yaml")] -mod hello_world_indexer { - - fn index_logged_greeting(event: Greeting, block_data: BlockData) { - let height = std::cmp::min(0, block_data.header.height - 1); - let name = event.person.name.to_right_trimmed_str().to_string(); - let greeting = event.greeting.to_right_trimmed_str().to_string(); - let message = format!("{greeting} 👋, my name is {name}"); - - let greeter = Greeter::new(name, height).get_or_create(); - - let salutation = - Salutation::new(message, greeter.id.clone(), height).get_or_create(); - - greeter.save(); - salutation.save(); - } -} diff --git a/examples/hello-world/hello-world/.cargo/config b/examples/hello-world/hello-world/.cargo/config new file mode 100644 index 000000000..f4e8c002f --- /dev/null +++ b/examples/hello-world/hello-world/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/examples/hello-world/hello-indexer/Cargo.toml b/examples/hello-world/hello-world/Cargo.toml similarity index 90% rename from examples/hello-world/hello-indexer/Cargo.toml rename to examples/hello-world/hello-world/Cargo.toml index 8df963683..252c53ecc 100644 --- a/examples/hello-world/hello-indexer/Cargo.toml +++ b/examples/hello-world/hello-world/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "hello_indexer" +name = "hello_world" version = "0.0.0" edition = "2021" publish = false diff --git a/examples/hello-world/hello-indexer/hello_indexer.manifest.yaml b/examples/hello-world/hello-world/hello_world.manifest.yaml similarity index 80% rename from examples/hello-world/hello-indexer/hello_indexer.manifest.yaml rename to examples/hello-world/hello-world/hello_world.manifest.yaml index 951a93ed6..bebda374e 100644 --- a/examples/hello-world/hello-indexer/hello_indexer.manifest.yaml +++ b/examples/hello-world/hello-world/hello_world.manifest.yaml @@ -3,11 +3,11 @@ namespace: fuellabs # The identifier field is used to identify the given index. -identifier: hello_indexer +identifier: hello_world # The abi option is used to provide a link to the Sway JSON ABI that is generated when you # build your project. -abi: examples/hello-world/contracts/greeting/out/debug/greeting-abi.json +abi: ~ # The particular start block after which you'd like your indexer to start indexing events. start_block: ~ @@ -22,18 +22,18 @@ end_block: ~ fuel_client: ~ # The contract_id specifies which particular contract you would like your index to subscribe to. -contract_id: fuel18hchrf7f4hnpkl84sqf8k0sk8gcauzeemzwgweea8dgr7eachv4s86r9t9 +contract_id: ~ # The graphql_schema field contains the file path that points to the GraphQL schema for the # given index. -graphql_schema: examples/hello-world/hello-indexer/schema/hello_indexer.schema.graphql +graphql_schema: examples/hello-world/hello-world/schema/hello_world.schema.graphql # The module field contains a file path that points to code that will be run as an executor inside # of the indexer. # Important: At this time, wasm is the preferred method of execution. module: - wasm: target/wasm32-unknown-unknown/release/hello_indexer.wasm + wasm: target/wasm32-unknown-unknown/release/hello_world.wasm # The resumable field contains a boolean that specifies whether or not the indexer should, synchronise # with the latest block if it has fallen out of sync. -resumable: true +resumable: true \ No newline at end of file diff --git a/examples/hello-world/hello-world/schema/hello_world.schema.graphql b/examples/hello-world/hello-world/schema/hello_world.schema.graphql new file mode 100644 index 000000000..18fdda7b3 --- /dev/null +++ b/examples/hello-world/hello-world/schema/hello_world.schema.graphql @@ -0,0 +1,12 @@ +type Block @entity { + id: ID! + height: U64! + hash: Bytes32! @unique +} + +type Transaction @entity { + id: ID! + block: Block! @join(on:hash) + hash: Bytes32! @unique +} + diff --git a/examples/hello-world/hello-world/src/lib.rs b/examples/hello-world/hello-world/src/lib.rs new file mode 100644 index 000000000..70f6eec3a --- /dev/null +++ b/examples/hello-world/hello-world/src/lib.rs @@ -0,0 +1,19 @@ +extern crate alloc; +use fuel_indexer_utils::prelude::*; + +#[indexer(manifest = "examples/hello-world/hello-world/hello_world.manifest.yaml")] +pub mod hello_world_index_mod { + + fn hello_world_handler(block_data: BlockData) { + let block = Block::new(block_data.header.height.into(), block_data.id); + block.save(); + + for transaction in block_data.transactions.iter() { + let tx = Transaction::new( + block_data.id, + Bytes32::from(<[u8; 32]>::from(transaction.id)), + ); + tx.save(); + } + } +} diff --git a/packages/fuel-indexer-benchmarks/src/bin/qa.rs b/packages/fuel-indexer-benchmarks/src/bin/qa.rs index a843838d1..7b0f3a7e7 100644 --- a/packages/fuel-indexer-benchmarks/src/bin/qa.rs +++ b/packages/fuel-indexer-benchmarks/src/bin/qa.rs @@ -1,7 +1,9 @@ use chrono::Utc; use clap::Parser; use duct::cmd; -use fuel_indexer_lib::{config::IndexerConfig, manifest::Manifest, utils::init_logging}; +use fuel_indexer_lib::{ + config::IndexerConfig, defaults, manifest::Manifest, utils::init_logging, +}; use reqwest::{ header::{HeaderMap, CONTENT_TYPE}, Client, @@ -29,8 +31,30 @@ struct Args { help = "Number of blocks to index during run." )] pub blocks: String, + #[clap(short, long, help = "Network at which to connect.")] pub network: String, + + #[clap(long, help = "Database type.", default_value = defaults::DATABASE, value_parser(["postgres"]))] + pub database: String, + + #[clap(long, help = "Postgres username.")] + pub postgres_user: Option, + + #[clap(long, help = "Postgres database.")] + pub postgres_database: Option, + + #[clap(long, help = "Postgres password.")] + pub postgres_password: Option, + + #[clap(long, help = "Postgres host.")] + pub postgres_host: Option, + + #[clap(long, help = "Postgres port.")] + pub postgres_port: Option, + + #[clap(long, help = "Number of additional indexers to run.")] + pub num_additional_indexers: Option, } #[derive(Debug)] @@ -60,7 +84,8 @@ impl StatManager { self.runs.push(run); } - pub fn report(self) { + pub fn report(self, num_additional_indexers: Option) { + let additional_indexers = num_additional_indexers.unwrap_or(0); let Self { runs, system } = self; let reports = runs @@ -118,6 +143,7 @@ system: {system} date: {date} host: {host} branch: {branch} +additional indexers: {additional_indexers} runtime: {runtime:.1} minutes missing blocks: {missing_blocks} avg memory: {avg_memory:.1}kB @@ -486,16 +512,25 @@ async fn main() { let num_runs = opts.runs.parse::().unwrap(); let blocks_per_run = opts.blocks.parse::().unwrap(); + let postgres_host = opts + .postgres_host + .unwrap_or(defaults::POSTGRES_HOST.to_string()); + let postgres_port = opts + .postgres_port + .unwrap_or(defaults::POSTGRES_PORT.to_string()); + let postgres_user = opts + .postgres_user + .unwrap_or(defaults::POSTGRES_USER.to_string()); + let postgres_db = opts + .postgres_database + .unwrap_or(defaults::POSTGRES_DATABASE.to_string()); init_logging(&config).await.unwrap(); let root = std::env::current_dir().unwrap(); - let explorer_root = canonicalize( - root.join("examples") - .join("fuel-explorer") - .join("fuel-explorer"), - ) - .unwrap(); + let examples_root = root.join("examples"); + let explorer_root = + canonicalize(examples_root.join("fuel-explorer").join("fuel-explorer")).unwrap(); let mani_path = explorer_root.join("fuel_explorer.manifest.yaml"); @@ -508,6 +543,14 @@ async fn main() { .arg("80") .arg("--replace-indexer") .arg("--allow-non-sequential-blocks") + .arg("--postgres-host") + .arg(&postgres_host) + .arg("--postgres-port") + .arg(&postgres_port) + .arg("--postgres-user") + .arg(&postgres_user) + .arg("--postgres-database") + .arg(&postgres_db) .spawn() .unwrap(); @@ -520,6 +563,37 @@ async fn main() { let manifest = Manifest::from_file(&mani_path).unwrap(); let mut stats = StatManager::new(); + // Deploy some number of additional hello-world indexers. + // + // Since the Fuel indexer is intended to support multiple indexers, we need to analyze + // performance when multiple indexers are running at the same time - if the user so specifies. + if let Some(num_additional_indexers) = opts.num_additional_indexers { + let hello_world_root = examples_root.join("hello-world").join("hello-world"); + let mani_path = hello_world_root.join("hello_world.manifest.yaml"); + let mut manifest = Manifest::from_file(&mani_path).unwrap(); + + for i in 0..num_additional_indexers { + tracing::info!("Deploying additional indexer #{}", i + 1); + manifest.set_identifier(format!("hello_world_{}", i)); + let _ = manifest.write(&mani_path).unwrap(); + + sleep(Duration::from_secs(1)).await; + + let mut proc = Command::new("forc-index") + .arg("deploy") + .arg("--path") + .arg(&hello_world_root.to_str().unwrap()) + .spawn() + .unwrap(); + + let _ = proc.wait().unwrap(); + + sleep(Duration::from_secs(1)).await; + } + + tracing::info!("✅ All additional indexers deployed."); + } + for (i, start_block) in start_blocks.iter().enumerate() { let start = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -576,7 +650,7 @@ async fn main() { stats.add_run(run_stats); } - stats.report(); + stats.report(opts.num_additional_indexers); let _ = Command::new("forc-index").arg("kill").spawn().unwrap(); } diff --git a/packages/fuel-indexer-lib/src/manifest.rs b/packages/fuel-indexer-lib/src/manifest.rs index 933f992e4..20e8dcbd4 100644 --- a/packages/fuel-indexer-lib/src/manifest.rs +++ b/packages/fuel-indexer-lib/src/manifest.rs @@ -211,6 +211,14 @@ impl Manifest { &self.namespace } + pub fn set_namespace(&mut self, namespace: String) { + self.namespace = namespace; + } + + pub fn set_identifier(&mut self, identifier: String) { + self.identifier = identifier; + } + pub fn identifier(&self) -> &str { &self.identifier } diff --git a/packages/fuel-indexer-macros/src/native.rs b/packages/fuel-indexer-macros/src/native.rs index 463df0b92..c9efedd92 100644 --- a/packages/fuel-indexer-macros/src/native.rs +++ b/packages/fuel-indexer-macros/src/native.rs @@ -43,12 +43,50 @@ fn native_prelude() -> proc_macro2::TokenStream { core::{codec::ABIDecoder, Configurables, traits::{Parameterize, Tokenizable}}, types::{StringToken}, }; + } } /// Generate the `main` function for the native execution module. +/// +/// This should be an exact reference to `fuel_indexer::main` with a few exceptions: +/// - No references to an embedded database +/// - `--manifest` is a required option. +/// - Handlers are registered via `register_native_indexer` instead of `register_indexer_from_manifest`. pub fn native_main() -> proc_macro2::TokenStream { quote! { + // Returns a future which completes when a shutdown signal has been received. + fn shutdown_signal_handler() -> std::io::Result> { + let mut sighup: Signal = signal(SignalKind::hangup())?; + let mut sigterm: Signal = signal(SignalKind::terminate())?; + let mut sigint: Signal = signal(SignalKind::interrupt())?; + + let future = async move { + #[cfg(unix)] + { + tokio::select! { + _ = sighup.recv() => { + info!("Received SIGHUP. Stopping services."); + } + _ = sigterm.recv() => { + info!("Received SIGTERM. Stopping services."); + } + _ = sigint.recv() => { + info!("Received SIGINT. Stopping services."); + } + } + } + + #[cfg(not(unix))] + { + signal::ctrl_c().await?; + info!("Received CTRL+C. Stopping services."); + } + }; + + Ok(future) + } + #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -56,10 +94,13 @@ pub fn native_main() -> proc_macro2::TokenStream { let IndexerArgs { manifest, .. } = args.clone(); + let mut subsystems: tokio::task::JoinSet<()> = tokio::task::JoinSet::new(); + + subsystems.spawn(shutdown_signal_handler()?); let config = args .config - .as_ref() + .clone() .map(IndexerConfig::from_file) .unwrap_or(Ok(IndexerConfig::from(args)))?; @@ -67,7 +108,8 @@ pub fn native_main() -> proc_macro2::TokenStream { info!("Configuration: {:?}", config); - let (tx, rx) = channel::(SERVICE_REQUEST_CHANNEL_SIZE); + #[allow(unused)] + let (tx, rx) = channel::(defaults::SERVICE_REQUEST_CHANNEL_SIZE); let pool = IndexerConnectionPool::connect(&config.database.to_string()).await?; @@ -89,10 +131,55 @@ pub fn native_main() -> proc_macro2::TokenStream { let manifest = Manifest::from_file(&p)?; service.register_native_indexer(manifest, handle_events).await?; - let service_handle = tokio::spawn(service.run()); - let web_handle = tokio::spawn(WebApi::build_and_run(config.clone(), pool, tx)); - - let _ = tokio::join!(service_handle, web_handle); + subsystems.spawn(async { + let result = service.run().await; + if let Err(e) = result { + tracing::error!("Indexer Service failed: {e}"); + } + }); + + // Fuel indexer API web server always on due to feature-flagging + // + // TODO: https://github.com/FuelLabs/fuel-indexer/issues/1393 + // + // #[cfg(feature = "api-server")] + subsystems.spawn({ + let config = config.clone(); + async { + if let Err(e) = WebApi::build_and_run(config, pool, tx).await { + tracing::error!("Api Server failed: {e}"); + } + } + }); + + // Fuel core components removed due to feature-flagging + // + // TODO: https://github.com/FuelLabs/fuel-indexer/issues/1393 + // + // #[cfg(feature = "fuel-core-lib")] + // { + // use fuel_core::service::{Config, FuelService}; + // use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + + // if config.local_fuel_node { + // let config = Config { + // addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 4000), + // ..Config::local_node() + // }; + // subsystems.spawn(async move { + // if let Err(e) = FuelService::new_node(config).await { + // tracing::error!("Fuel Node failed: {e}"); + // }; + // }); + // } + // }; + + // Each subsystem runs its own loop, and we require all subsystems for the + // Indexer service to operate correctly. If any of the subsystems stops + // running, the entire Indexer Service exits. + if subsystems.join_next().await.is_some() { + subsystems.shutdown().await; + } Ok(()) } diff --git a/packages/fuel-indexer-plugin/Cargo.toml b/packages/fuel-indexer-plugin/Cargo.toml index 57d7e129c..35d62d5f8 100644 --- a/packages/fuel-indexer-plugin/Cargo.toml +++ b/packages/fuel-indexer-plugin/Cargo.toml @@ -22,6 +22,7 @@ fuel-indexer-database = { workspace = true, optional = true } fuel-indexer-lib = { workspace = true } fuel-indexer-schema = { workspace = true, default-features = false } fuel-indexer-types = { workspace = true } +futures = { version = "0.3", optional = true } getrandom = { version = "0.2", features = ["js"] } hex = "0.4" serde = { workspace = true } @@ -39,6 +40,7 @@ native-execution = [ "fuel-indexer-api-server", "fuel-indexer-database", "fuel-indexer", + "futures", "tokio", "tracing-subscriber", "tracing", diff --git a/packages/fuel-indexer-plugin/src/native.rs b/packages/fuel-indexer-plugin/src/native.rs index 3b15cd2ca..b275a0541 100644 --- a/packages/fuel-indexer-plugin/src/native.rs +++ b/packages/fuel-indexer-plugin/src/native.rs @@ -13,19 +13,22 @@ pub use fuel_indexer_api_server::api::WebApi; pub use fuel_indexer_database::{queries, IndexerConnectionPool}; pub use fuel_indexer_lib::{ config::{cli::Parser, IndexerArgs, IndexerConfig}, - defaults::SERVICE_REQUEST_CHANNEL_SIZE, + defaults, graphql::MAX_FOREIGN_KEY_LIST_FIELDS, manifest::Manifest, utils::{init_logging, ServiceRequest}, }; use fuel_indexer_types::scalar::UID; +pub use futures; pub use sha2::{Digest, Sha256}; pub use std::{ collections::{HashMap, HashSet}, str::FromStr, }; pub use tokio; +pub use tokio::signal::unix::{signal, Signal, SignalKind}; pub use tokio::sync::mpsc::channel; +pub use tracing; pub use tracing_subscriber; pub use tracing_subscriber::filter::EnvFilter; diff --git a/packages/fuel-indexer-tests/scripts/test-examples.bash b/packages/fuel-indexer-tests/scripts/test-examples.bash index cd6b7d5ee..1e60efb18 100644 --- a/packages/fuel-indexer-tests/scripts/test-examples.bash +++ b/packages/fuel-indexer-tests/scripts/test-examples.bash @@ -6,61 +6,85 @@ # to write and maintain copious amounts of integration/unit tests for an API that # is still in flux. + +check_service_status() { + db_status=$(curl -s http://localhost:29987/api/health | jq '.database_status') + sleep 1 + client_status=$(curl -s http://localhost:29987/api/health | jq '.client_status') + + if [[ "$db_status" == "$client_status" ]] && [[ "$db_status" == '"OK"' ]]; then + echo "Database and client are up and running." + else + echo "Database and/or client are not up and running." + exit 1 + fi +} + +export PGPASSWORD=my-secret + set -ex +forc_index_kill() { + # Shutdown the service + forc-index kill + sleep 2 +} + # ******************************** -# examples/hello-indexer +# examples/greetings # ******************************** -# Go to example -cd examples/hello-world/ +cd examples/greetings/ -# Start the local Fuel node in the background -cargo build -p hello-world-node -cargo run -p hello-world-node --bin hello-world-node & -sleep 2 +cargo build -p greetings-fuel-client +cargo run -p greetings-fuel-client --bin greetings-fuel-client & +sleep 1 -# Start service in the background forc-index start --postgres-password my-secret --run-migrations & -sleep 2 - -# Deploy the example indexer -forc-index deploy --path ./hello-indexer -sleep 5 - -# Ensure service is up and running via health check -db_status=$(curl http://localhost:29987/api/health | json_pp | jq '.database_status') sleep 1 -client_status=$(curl http://localhost:29987/api/health | json_pp | jq '.client_status') -if [[ "$db_status" == "$client_status" ]] && [[ "$db_status" == '"OK"' ]]; then - echo "Database and client are up and running." -else - echo "Database and/or client are not up and running." - exit 1 -fi +forc-index deploy --path ./greetings-indexer +sleep 3 +# check_service_status # Trigger an indexable event -cargo run -p hello-world-data --bin hello-world-data -sleep 2 +cargo run -p greetings-data --bin greetings-data +sleep 1 # Check data is indexed -export PGPASSWORD=my-secret -echo "SELECT COUNT(*) FROM fuel_examples.salutation;" | psql -h localhost -p 5432 -U postgres +echo "SELECT COUNT(*) FROM fuellabs_greetings.salutation;" | psql -h localhost -p 5432 -U postgres sleep 2 -# Shutdown the service -forc-index kill -sleep 2 +forc_index_kill # ******************************** -# examples/hello-indexer-native +# examples/greetings-native # ******************************** -# TODO: https://github.com/FuelLabs/fuel-indexer/issues/1069 +# cd ../greetings-native + +# forc-index build --path ./greetings-native-indexer --native + +# # Update manifest with paths relative to the example due to cargo workspace +# sed -i \ +# "s|^graphql_schema: examples/greetings-native/greetings-native-indexer/schema/greetings_native_indexer\.schema\.graphql|graphql_schema: $PWD/greetings-native-indexer/schema/greetings_native_indexer.schema.graphql|" \ +# ./greetings-native-indexer/greetings_native_indexer.manifest.yaml + +# Start indexer in the background +# forc-index run-native --path ./greetings-native-indexer -- --run-migrations --postgres-password my-secret +# sleep 60 -cargo build -p hello_indexer_native --locked --release +# check_service_status + +# cargo run -p greetings-data --bin greetings-data +# sleep 5 + +# # Check data is indexed +# echo "SELECT COUNT(*) FROM fuellabs_greetings_native.salutation;" | psql -h localhost -p 5432 -U postgres +# sleep 2 + +# forc_index_kill # ******************************** # examples/fuel-explorer @@ -73,30 +97,38 @@ cd ../fuel-explorer forc-index start --postgres-password my-secret --run-migrations & sleep 2 -# Deploy the example indexer forc-index deploy --path ./fuel-explorer sleep 5 -# Ensure service is up and running via -db_status=$(curl http://localhost:29987/api/health | json_pp | jq '.database_status') -sleep 1 -client_status=$(curl http://localhost:29987/api/health | json_pp | jq '.client_status') +check_service_status -if [[ "$db_status" == "$client_status" ]] && [[ "$db_status" == '"OK"' ]]; then - echo "Database and client are up and running." -else - echo "Database and/or client are not up and running." -exit 1 -fi +echo "SELECT COUNT(*) FROM fuellabs_explorer.block;" | psql -h localhost -p 5432 -U postgres +sleep 2 -# Check data is indexed -export PGPASSWORD=my-secret -echo "SELECT COUNT(*) FROM fuel_explorer.block;" | psql -h localhost -p 5432 -U postgres +forc_index_kill + +# ******************************** +# examples/hello-world +# ******************************** + +# Go to example +cd ../hello-world + +# Start service in the background +forc-index start --postgres-password my-secret --run-migrations & sleep 2 -# Shutdown the service -forc-index kill +# Deploy the example indexer +forc-index deploy --path ./hello-world +sleep 5 + +check_service_status + +# Check data is indexed +echo "SELECT COUNT(*) FROM fuellabs_hello_world.block;" | psql -h localhost -p 5432 -U postgres sleep 2 -# Finally, kill the hello-world-node +forc_index_kill + +# Finally, kill the greetings-fuel-client kill -9 $(lsof -ti:4000) diff --git a/packages/fuel-indexer-tests/tests/commands.rs b/packages/fuel-indexer-tests/tests/commands.rs index 49e1607bb..c19b3e684 100644 --- a/packages/fuel-indexer-tests/tests/commands.rs +++ b/packages/fuel-indexer-tests/tests/commands.rs @@ -1,6 +1,10 @@ use duct::cmd; use fuel_indexer_lib::config::IndexerConfig; +const FUEL_INDEXER: &str = "./../../target/release/fuel-indexer"; +const FUEL_INDEXER_API_SERVER: &str = "./../../target/release/fuel-indexer-api-server"; +const FORC_INDEX: &str = "./../../target/release/forc-index"; + #[test] fn test_default_indexer_config() { let output = serde_yaml::to_string(&IndexerConfig::default()).unwrap(); @@ -9,7 +13,7 @@ fn test_default_indexer_config() { #[test] fn test_fuel_indexer_help_output() { - let output = cmd!("fuel-indexer", "--help") + let output = cmd!(FUEL_INDEXER, "--help") .pipe(cmd!("tail", "-n", "+2")) .read() .unwrap(); @@ -18,7 +22,7 @@ fn test_fuel_indexer_help_output() { #[test] fn test_fuel_indexer_api_server_help_output() { - let output = cmd!("fuel-indexer-api-server", "--help") + let output = cmd!(FUEL_INDEXER_API_SERVER, "--help") .pipe(cmd!("tail", "-n", "+2")) .read() .unwrap(); @@ -27,7 +31,7 @@ fn test_fuel_indexer_api_server_help_output() { #[test] fn test_fuel_indexer_run_help_output() { - let output = cmd!("fuel-indexer", "run", "--help") + let output = cmd!(FUEL_INDEXER, "run", "--help") .pipe(cmd!("tail", "-n", "+2")) .read() .unwrap(); @@ -36,7 +40,7 @@ fn test_fuel_indexer_run_help_output() { #[test] fn test_fuel_indexer_api_server_run_help_output() { - let output = cmd!("fuel-indexer-api-server", "run", "--help") + let output = cmd!(FUEL_INDEXER_API_SERVER, "run", "--help") .pipe(cmd!("tail", "-n", "+2")) .read() .unwrap(); @@ -45,7 +49,7 @@ fn test_fuel_indexer_api_server_run_help_output() { #[test] fn test_forc_index_help_output() { - let output = cmd!("forc-index", "--help") + let output = cmd!(FORC_INDEX, "--help") .pipe(cmd!("tail", "-n", "+2")) .read() .unwrap(); @@ -54,7 +58,7 @@ fn test_forc_index_help_output() { #[test] fn test_forc_index_new_help_output() { - let output = cmd!("forc-index", "new", "--help") + let output = cmd!(FORC_INDEX, "new", "--help") .pipe(cmd!("tail", "-n", "+2")) .read() .unwrap(); @@ -63,7 +67,7 @@ fn test_forc_index_new_help_output() { #[test] fn test_forc_index_build_help_output() { - let output = cmd!("forc-index", "build", "--help") + let output = cmd!(FORC_INDEX, "build", "--help") .pipe(cmd!("tail", "-n", "+2")) .read() .unwrap(); @@ -72,7 +76,7 @@ fn test_forc_index_build_help_output() { #[test] fn test_forc_index_deploy_help_output() { - let output = cmd!("forc-index", "deploy", "--help") + let output = cmd!(FORC_INDEX, "deploy", "--help") .pipe(cmd!("tail", "-n", "+2")) .read() .unwrap(); @@ -81,7 +85,7 @@ fn test_forc_index_deploy_help_output() { #[test] fn test_forc_index_remove_help_output() { - let output = cmd!("forc-index", "remove", "--help") + let output = cmd!(FORC_INDEX, "remove", "--help") .pipe(cmd!("tail", "-n", "+2")) .read() .unwrap(); @@ -90,7 +94,7 @@ fn test_forc_index_remove_help_output() { #[test] fn test_forc_index_status_help_output() { - let output = cmd!("forc-index", "status", "--help") + let output = cmd!(FORC_INDEX, "status", "--help") .pipe(cmd!("tail", "-n", "+2")) .read() .unwrap(); @@ -99,7 +103,7 @@ fn test_forc_index_status_help_output() { #[test] fn test_forc_index_auth_help_output() { - let output = cmd!("forc-index", "auth", "--help") + let output = cmd!(FORC_INDEX, "auth", "--help") .pipe(cmd!("tail", "-n", "+2")) .read() .unwrap(); @@ -108,7 +112,7 @@ fn test_forc_index_auth_help_output() { #[test] fn test_forc_index_kill_help_output() { - let output = cmd!("forc-index", "kill", "--help") + let output = cmd!(FORC_INDEX, "kill", "--help") .pipe(cmd!("tail", "-n", "+2")) .read() .unwrap(); @@ -117,7 +121,16 @@ fn test_forc_index_kill_help_output() { #[test] fn test_forc_index_start_help_output() { - let output = cmd!("forc-index", "start", "--help") + let output = cmd!(FORC_INDEX, "start", "--help") + .pipe(cmd!("tail", "-n", "+2")) + .read() + .unwrap(); + insta::assert_snapshot!(output); +} + +#[test] +fn test_forc_index_run_native_help_output() { + let output = cmd!(FORC_INDEX, "run-native", "--help") .pipe(cmd!("tail", "-n", "+2")) .read() .unwrap(); diff --git a/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_help_output.snap b/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_help_output.snap index ee6f118d8..bccc48fe5 100644 --- a/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_help_output.snap +++ b/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_help_output.snap @@ -12,15 +12,17 @@ OPTIONS: -V, --version Print version information SUBCOMMANDS: - auth Authenticate against an indexer service - build Build an indexer - check Check for Fuel indexer components - deploy Deploy an indexer to an indexer service - help Print this message or the help of the given subcommand(s) - kill Kill the indexer process. Note that this command will kill any process listening - on the default indexer port or the port specified by the `--port` flag - new Create a new indexer project in a new directory - postgres Fuel Postgres Orchestrator - remove Stop and remove a running indexer - start Standalone binary for the Fuel indexer service - status Check the status of a registered indexer + auth Authenticate against an indexer service + build Build an indexer + check Check for Fuel indexer components + deploy Deploy an indexer to an indexer service + help Print this message or the help of the given subcommand(s) + kill Kill the indexer process. Note that this command will kill any process + listening on the default indexer port or the port specified by the `--port` + flag + new Create a new indexer project in a new directory + postgres Fuel Postgres Orchestrator + remove Stop and remove a running indexer + run-native Run a native indexer + start Standalone binary for the Fuel indexer service + status Check the status of a registered indexer diff --git a/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_run_native_help_output.snap b/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_run_native_help_output.snap new file mode 100644 index 000000000..82e7a94b1 --- /dev/null +++ b/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_run_native_help_output.snap @@ -0,0 +1,39 @@ +--- +source: packages/fuel-indexer-tests/tests/commands.rs +expression: output +--- +Run a native indexer + +USAGE: + forc-index run-native [OPTIONS] [-- ...] + +ARGS: + ... + Extra passed to `fuel-indexer run` + + Example usage: `forc-index run-native --path . -- --run-migrations --stop-idle-indexers` + +OPTIONS: + --bin + Path to native indexer binary (if not using default location). + + -d, --debug + Build artifacts with the debug profile. + + -h, --help + Print help information + + --locked + Ensure that the Cargo.lock file is up-to-date. + + -m, --manifest + Manifest file name of indexer being built. + + -p, --path + Path to the indexer project. + + --skip-build + Do not build before deploying. + + -v, --verbose + Enable verbose output. diff --git a/plugins/forc-index/src/cli.rs b/plugins/forc-index/src/cli.rs index 955c24499..f9ca11039 100644 --- a/plugins/forc-index/src/cli.rs +++ b/plugins/forc-index/src/cli.rs @@ -3,8 +3,8 @@ pub(crate) use crate::commands::{ auth::Command as AuthCommand, build::Command as BuildCommand, check::Command as CheckCommand, deploy::Command as DeployCommand, kill::Command as KillCommand, new::Command as NewCommand, - remove::Command as RemoveCommand, start::Command as StartCommand, - status::Command as StatusCommand, + remove::Command as RemoveCommand, run_native::Command as RunNativeCommand, + start::Command as StartCommand, status::Command as StatusCommand, }; use clap::{Parser, Subcommand}; use forc_postgres::{ @@ -23,15 +23,16 @@ pub struct Opt { #[derive(Subcommand, Debug)] pub enum ForcIndex { - New(NewCommand), - Deploy(DeployCommand), - Start(Box), - Check(CheckCommand), - Remove(RemoveCommand), - Build(BuildCommand), Auth(AuthCommand), - Postgres(ForcPostgresOpt), + Build(BuildCommand), + Check(CheckCommand), + Deploy(DeployCommand), Kill(KillCommand), + New(NewCommand), + Postgres(ForcPostgresOpt), + Remove(RemoveCommand), + RunNative(RunNativeCommand), + Start(Box), Status(StatusCommand), } @@ -58,5 +59,6 @@ pub async fn run_cli() -> Result<(), anyhow::Error> { }, ForcIndex::Kill(command) => crate::commands::kill::exec(command), ForcIndex::Status(command) => crate::commands::status::exec(command).await, + ForcIndex::RunNative(command) => crate::commands::run_native::exec(command).await, } } diff --git a/plugins/forc-index/src/commands/mod.rs b/plugins/forc-index/src/commands/mod.rs index 091d8a1a4..8d20177c0 100644 --- a/plugins/forc-index/src/commands/mod.rs +++ b/plugins/forc-index/src/commands/mod.rs @@ -5,5 +5,6 @@ pub mod deploy; pub mod kill; pub mod new; pub mod remove; +pub mod run_native; pub mod start; pub mod status; diff --git a/plugins/forc-index/src/commands/run_native.rs b/plugins/forc-index/src/commands/run_native.rs new file mode 100644 index 000000000..52eedf51f --- /dev/null +++ b/plugins/forc-index/src/commands/run_native.rs @@ -0,0 +1,54 @@ +use crate::ops::forc_index_run_native; +use anyhow::Result; +use clap::Parser; +use std::path::PathBuf; + +/// Run a native indexer. +#[derive(Debug, Parser)] +pub struct Command { + /// Manifest file name of indexer being built. + #[clap(short, long, help = "Manifest file name of indexer being built.")] + pub manifest: Option, + + /// Path of indexer project. + #[clap(short, long, help = "Path to the indexer project.")] + pub path: Option, + + /// Build optimized artifacts with the debug profile. + #[clap(short, long, help = "Build artifacts with the debug profile.")] + pub debug: bool, + + /// Ensure that the Cargo.lock file is up-to-date. + #[clap(long, help = "Ensure that the Cargo.lock file is up-to-date.")] + pub locked: bool, + + /// Do not build before deploying. + #[clap(long, help = "Do not build before deploying.")] + pub skip_build: bool, + + /// Enable verbose output. + #[clap(short, long, help = "Enable verbose output.")] + pub verbose: bool, + + /// Path to native indexer binary (if not using default location). + #[clap( + long, + help = "Path to native indexer binary (if not using default location)." + )] + pub bin: Option, + + /// Extra passed to `fuel-indexer run` + /// + /// Example usage: `forc-index run-native --path . -- --run-migrations --stop-idle-indexers` + #[clap( + multiple = true, + last = true, + help = "Extra passed to `fuel-indexer run`" + )] + pub args: Vec, +} + +pub async fn exec(command: Command) -> Result<()> { + forc_index_run_native::init(command).await?; + Ok(()) +} diff --git a/plugins/forc-index/src/ops/forc_index_build.rs b/plugins/forc-index/src/ops/forc_index_build.rs index 23fbe8359..a83800f63 100644 --- a/plugins/forc-index/src/ops/forc_index_build.rs +++ b/plugins/forc-index/src/ops/forc_index_build.rs @@ -28,10 +28,10 @@ pub fn init(command: BuildCommand) -> anyhow::Result<()> { let release = !debug; - let (root_dir, manifest, _index_name) = + let (root_dir, manifest, _indexer_name) = project_dir_info(path.as_ref(), manifest.as_ref())?; - // Must be in the directory of the index being built + // Must be in the directory of the indexer being built let cargo_manifest_path = root_dir.join(defaults::CARGO_MANIFEST_FILE_NAME); if !cargo_manifest_path.exists() { let cargo_manifest_dir = { diff --git a/plugins/forc-index/src/ops/forc_index_deploy.rs b/plugins/forc-index/src/ops/forc_index_deploy.rs index c59074aa3..cfe7d4545 100644 --- a/plugins/forc-index/src/ops/forc_index_deploy.rs +++ b/plugins/forc-index/src/ops/forc_index_deploy.rs @@ -56,7 +56,7 @@ pub async fn init(command: DeployCommand) -> anyhow::Result<()> { .await?; } - let (_root_dir, manifest_path, _index_name) = + let (_root_dir, manifest_path, _indexer_name) = project_dir_info(path.as_ref(), manifest.as_ref())?; let manifest = Manifest::from_file(&manifest_path)?; diff --git a/plugins/forc-index/src/ops/forc_index_new.rs b/plugins/forc-index/src/ops/forc_index_new.rs index 65376f6bf..98ea8dd56 100644 --- a/plugins/forc-index/src/ops/forc_index_new.rs +++ b/plugins/forc-index/src/ops/forc_index_new.rs @@ -44,7 +44,9 @@ fn print_welcome_message() { `forc index start` Start a local indexer service. `forc index status` - Check the status of an indexer."#; + Check the status of an indexer. +`forc index run-native` + Run a native indexer."#; let ascii_tag = r#" ███████ ██  ██ ███████ ██  ██ ███  ██ ██████  ███████ ██  ██ ███████ ██████  diff --git a/plugins/forc-index/src/ops/forc_index_remove.rs b/plugins/forc-index/src/ops/forc_index_remove.rs index 967645eaa..59329d4ad 100644 --- a/plugins/forc-index/src/ops/forc_index_remove.rs +++ b/plugins/forc-index/src/ops/forc_index_remove.rs @@ -17,7 +17,7 @@ pub async fn init(command: RemoveCommand) -> anyhow::Result<()> { .. } = command; - let (_root_dir, manifest_path, _index_name) = + let (_root_dir, manifest_path, _indexer_name) = project_dir_info(path.as_ref(), manifest.as_ref())?; let manifest: Manifest = Manifest::from_file(manifest_path.as_path())?; diff --git a/plugins/forc-index/src/ops/forc_index_run_native.rs b/plugins/forc-index/src/ops/forc_index_run_native.rs new file mode 100644 index 000000000..51245374c --- /dev/null +++ b/plugins/forc-index/src/ops/forc_index_run_native.rs @@ -0,0 +1,111 @@ +use crate::{ + cli::{BuildCommand, RunNativeCommand}, + commands::build, + defaults, + utils::*, +}; +use fuel_indexer_lib::manifest::Manifest; +use std::{ + fs::File, + io::Read, + path::{Path, PathBuf}, + process::Command, +}; +use tracing::info; + +pub async fn init(command: RunNativeCommand) -> anyhow::Result<()> { + let RunNativeCommand { + path, + debug, + locked, + manifest: mani_path, + verbose, + skip_build, + bin, + args, + .. + } = command; + + if !skip_build { + build::exec(BuildCommand { + manifest: mani_path.clone(), + path: path.clone(), + debug, + verbose, + locked, + native: true, + })?; + } + + let release = !debug; + + let (root_dir, manifest, indexer_name) = + project_dir_info(path.as_ref(), mani_path.as_ref())?; + + // Must be in the directory of the indexer being built + let cargo_manifest_path = root_dir.join(defaults::CARGO_MANIFEST_FILE_NAME); + if !cargo_manifest_path.exists() { + let cargo_manifest_dir = { + let mut path = cargo_manifest_path; + path.pop(); + path + }; + anyhow::bail!( + "could not find `Cargo.toml` in `{}`", + cargo_manifest_dir.display() + ); + } + + let current_dir = std::env::current_dir()?; + + let path = path.unwrap_or(current_dir); + + let mut file = File::open(&cargo_manifest_path)?; + let mut content = String::new(); + file.read_to_string(&mut content)?; + + let indexer_manifest_path = root_dir.join(manifest); + let manifest = Manifest::from_file(&indexer_manifest_path)?; + + let workspace_root: PathBuf = cargo_workspace_root_dir(path.as_path()).unwrap(); + + let manifest_schema_file = Path::new(&workspace_root).join(manifest.graphql_schema()); + + let binpath = bin + .unwrap_or_else(|| { + let dir = if release { "release" } else { "debug" }; + let name = dasherize_to_underscore(&indexer_name); + workspace_root.join("target").join(dir).join(name) + }) + .display() + .to_string(); + + // Rebuild the WASM module even if only the schema has changed. + ensure_rebuild_if_schema_or_manifest_changed( + root_dir.as_path(), + Path::new(manifest_schema_file.as_path()), + indexer_manifest_path.as_path(), + manifest.execution_source(), + )?; + + let mut cmd = Command::new(binpath); + cmd.arg("--manifest").arg(&indexer_manifest_path); + + for arg in args { + cmd.arg(arg); + } + + if verbose { + info!("{cmd:?}") + } + + match cmd.spawn() { + Ok(child) => { + let pid = child.id(); + info!("✅ Successfully started the indexer service at PID {pid}"); + } + Err(e) => panic!("❌ Failed to spawn fuel-indexer child process: {e:?}."), + } + + Ok(()) +} diff --git a/plugins/forc-index/src/ops/mod.rs b/plugins/forc-index/src/ops/mod.rs index 512edfa0f..9807f456f 100644 --- a/plugins/forc-index/src/ops/mod.rs +++ b/plugins/forc-index/src/ops/mod.rs @@ -5,5 +5,6 @@ pub mod forc_index_deploy; pub mod forc_index_kill; pub mod forc_index_new; pub mod forc_index_remove; +pub mod forc_index_run_native; pub mod forc_index_start; pub mod forc_index_status; diff --git a/plugins/forc-index/src/utils.rs b/plugins/forc-index/src/utils.rs index 5dc57c46b..3a0652907 100644 --- a/plugins/forc-index/src/utils.rs +++ b/plugins/forc-index/src/utils.rs @@ -157,7 +157,13 @@ pub fn ensure_rebuild_if_schema_or_manifest_changed( exec_source: ExecutionSource, ) -> std::io::Result<()> { let schema_mtime = { - let metadata = std::fs::metadata(schema).unwrap(); + let metadata = std::fs::metadata(schema).unwrap_or_else(|e| { + panic!( + "Failed to get metadata for schema file `{}`: {}", + schema.display(), + e + ) + }); filetime::FileTime::from_last_modification_time(&metadata) }; diff --git a/scripts/utils/build_modules.bash b/scripts/utils/build_modules.bash index e77d52f2f..41b246232 100644 --- a/scripts/utils/build_modules.bash +++ b/scripts/utils/build_modules.bash @@ -23,11 +23,19 @@ bash scripts/stripper.bash fuel_explorer.wasm cp fuel_explorer.wasm target/wasm32-unknown-unknown/release/ rm -fv fuel_explorer.wasm -cargo build -p hello_indexer --release --target wasm32-unknown-unknown -bash scripts/stripper.bash hello_indexer.wasm -cp hello_indexer.wasm target/wasm32-unknown-unknown/release/ -rm -fv hello_indexer.wasm +cargo build -p hello_world --release --target wasm32-unknown-unknown +bash scripts/stripper.bash hello_world.wasm +cp hello_world.wasm target/wasm32-unknown-unknown/release/ +rm -fv hello_world.wasm + +cargo build -p greetings_indexer --release --target wasm32-unknown-unknown +bash scripts/stripper.bash greetings_indexer.wasm +cp greetings_indexer.wasm target/wasm32-unknown-unknown/release/ +rm -fv greetings_indexer.wasm + + +cargo build -p greetings_native_indexer --locked --release + -cargo build -p hello_indexer_native --locked --release set +ex