From 37d8d93aafa46961f546bf5698a5d9477df6f983 Mon Sep 17 00:00:00 2001 From: Hubert Bugaj Date: Fri, 19 Jun 2026 12:58:04 +0200 Subject: [PATCH] feat: `FOREST_PATH` env variable --- CHANGELOG.md | 2 + docs/docs/users/reference/env_variables.md | 125 +++++++++--------- scripts/tests/butterflynet_check.sh | 2 +- scripts/tests/calibnet_kademlia_check.sh | 2 +- .../calibnet_migration_regression_tests.sh | 4 +- scripts/tests/calibnet_no_discovery_check.sh | 4 +- .../tests/calibnet_stateless_mode_check.sh | 2 +- scripts/tests/calibnet_stateless_rpc_check.sh | 2 +- scripts/tests/harness.sh | 14 +- src/cli_shared/cli/client.rs | 5 +- src/cli_shared/mod.rs | 75 +++++++++++ src/daemon/mod.rs | 1 + src/dev/subcommands/mod.rs | 2 +- src/rpc/client.rs | 30 ++++- src/tool/offline_server/server.rs | 4 +- .../subcommands/api_cmd/api_compare_tests.rs | 4 +- src/tool/subcommands/api_cmd/test_snapshot.rs | 3 +- src/tool/subcommands/snapshot_cmd.rs | 2 +- tests/config.rs | 21 +++ 19 files changed, 218 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89a0b17906d8..41914930a43d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ ### Added +- [#6008](https://github.com/ChainSafe/forest/issues/6008): Added the `FOREST_PATH` environment variable to override the Forest data directory (taking precedence over the configuration file and the default), mirroring Lotus' `LOTUS_PATH`. It is honored by all `forest*` binaries, so `forest-cli`/`forest-tool` read the JWT admin token from the same directory. The resolved data directory is now logged on daemon startup. + - [#7168](https://github.com/ChainSafe/forest/pull/7168): Added the `FOREST_RPC_METRICS_DISABLED` environment variable to disable JSON-RPC per-method metrics while leaving other metrics intact. - [#7195](https://github.com/ChainSafe/forest/pull/7195): Added the `rpc_in_flight_requests` metric reporting the number of JSON-RPC requests currently being processed. diff --git a/docs/docs/users/reference/env_variables.md b/docs/docs/users/reference/env_variables.md index f66f218b4115..8af5e8806c07 100644 --- a/docs/docs/users/reference/env_variables.md +++ b/docs/docs/users/reference/env_variables.md @@ -9,68 +9,69 @@ Besides CLI options and the configuration values in the configuration file, there are some environment variables that control the behavior of a `forest` process. -| Environment variable | Value | Default | Example | Description | -| ---------------------------------------------------------------- | -------------------------------- | ---------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `FOREST_KEYSTORE_PHRASE` | any text | empty | `asfvdda` | The passphrase for the encrypted keystore | -| `FOREST_CAR_LOADER_FILE_IO` | 1 or true | false | true | Load CAR files with `RandomAccessFile` instead of `Mmap` | -| `FOREST_DB_DEV_MODE` | [see here](#forest_db_dev_mode) | current | current | The database to use in development mode | -| `FOREST_ACTOR_BUNDLE_PATH` | file path | empty | `/path/to/file.car.zst` | Path to the local actor bundle, download from remote servers when not set | -| `FIL_PROOFS_PARAMETER_CACHE` | directory path | empty | `/var/tmp/filecoin-proof-parameters` | Path to folder that caches fil proof parameter files | -| `FOREST_PROOFS_ONLY_IPFS_GATEWAY` | 1 or true | false | 1 | Use only IPFS gateway for proofs parameters download | -| `FOREST_FORCE_TRUST_PARAMS` | 1 or true | false | 1 | Trust the parameters downloaded from the Cloudflare/IPFS | -| `IPFS_GATEWAY` | URL | `https://proofs.filecoin.io/ipfs/` | `https://proofs.filecoin.io/ipfs/` | The IPFS gateway to use for downloading proofs parameters | -| `FOREST_RPC_DEFAULT_TIMEOUT` | Duration (in seconds) | 60 | 10 | The default timeout for RPC calls | -| `FOREST_RPC_MAX_CONNECTIONS` | positive integer | 1000 | 42 | Maximum number of allowed connections for the RPC server | -| `FOREST_RPC_COMPRESS_MIN_BODY_SIZE` | integer in `[-1, 65535]` (bytes) | -1 (disabled) | 2048 (or `-1` to disable) | Disabled by default. When set to a non-negative value, gzip-compresses responses whose body is at least that many bytes; smaller responses are sent uncompressed. Values above 65535 are clamped to 65535. Set to a negative value (e.g. `-1`) to disable compression entirely | -| `FOREST_MAX_CONCURRENT_REQUEST_RESPONSE_STREAMS_PER_PEER` | positive integer | 10 | 10 | the maximum concurrent streams per peer for request-response-based p2p protocols | -| `FOREST_BLOCK_DELAY_SECS` | positive integer | Depends on the network | 30 | Duration of each tipset epoch | -| `FOREST_PROPAGATION_DELAY_SECS` | positive integer | Depends on the network | 20 | How long to wait for a block to propagate through the network | -| `FOREST_PLEDGE_RULE_RAMP` | positive integer | Depends on the network | 200 | Pledge rule ramp duration in epochs (FIP 0081) | -| `FOREST_MAX_FILTERS` | integer | 100 | 100 | The maximum number of filters | -| `FOREST_MAX_FILTER_RESULTS` | positive integer | 10,000 | 10000 | The maximum number of filter results | -| `FOREST_MAX_FILTER_HEIGHT_RANGE` | positive integer | 2880 | 2880 | The maximum filter height range allowed, a conservative limit of one day | -| `FOREST_STATE_MIGRATION_THREADS` | integer | Depends on the machine. | 3 | The number of threads for state migration thread-pool. Advanced users only. | -| `FOREST_CONFIG_PATH` | string | /$FOREST_HOME/com.ChainSafe.Forest/config.toml | `/path/to/config.toml` | Forest configuration path. Alternatively supplied via `--config` cli parameter. | -| `FOREST_TEST_RNG_FIXED_SEED` | non-negative integer | empty | 0 | Override RNG with a reproducible one seeded by the value. This should never be used out of test context for security. | -| `RUST_LOG` | string | empty | `debug,forest_libp2p::service=info` | Allows for log level customization. | -| `FOREST_IGNORE_DRAND` | 1 or true | empty | 1 | Ignore Drand validation. | -| `FOREST_LIBP2P_METRICS_ENABLED` | 1 or true | empty | 1 | Include `libp2p` metrics in Forest's Prometheus output. | -| `FOREST_F3_SIDECAR_RPC_ENDPOINT` | string | 127.0.0.1:23456 | `127.0.0.1:23456` | An RPC endpoint of F3 sidecar. | -| `FOREST_F3_SIDECAR_FFI_ENABLED` | 1 or true | hard-coded per chain | 1 | Whether or not to start the F3 sidecar via FFI | -| `FOREST_F3_CONSENSUS_ENABLED` | 1 or true | hard-coded per chain | 1 | Whether or not to apply the F3 consensus to the node | -| `FOREST_F3_FINALITY` | integer | inherited from chain configuration | 900 | Set the chain finality epochs in F3 manifest | -| `FOREST_F3_PERMANENT_PARTICIPATING_MINER_ADDRESSES` | comma delimited strings | empty | `t0100,t0101` | Set the miner addresses that participate in F3 permanently | -| `FOREST_F3_INITIAL_POWER_TABLE` | string | empty | `bafyreicmaj5hhoy5mgqvamfhgexxyergw7hdeshizghodwkjg6qmpoco7i` | Set the F3 initial power table CID | -| `FOREST_F3_ROOT` | string | [FOREST_DATA_ROOT]/f3 | `/var/tmp/f3` | Set the data directory for F3 | -| `FOREST_F3_BOOTSTRAP_EPOCH` | integer | -1 | 100 | Set the bootstrap epoch for F3 | -| `FOREST_DRAND_MAINNET_CONFIG` | string | empty | refer to Drand config format section | Override `DRAND_MAINNET` config | -| `FOREST_DRAND_QUICKNET_CONFIG` | string | empty | refer to Drand config format section | Override `DRAND_QUICKNET` config | -| `FOREST_DRAND_INCENTINET_CONFIG` | string | empty | refer to Drand config format section | Override `DRAND_INCENTINET` config | -| `FOREST_TRACE_FILTER_MAX_RESULT` | positive integer | 500 | 1000 | Sets the maximum results returned per request by `trace_filter` | -| `FOREST_CHAIN_INDEXER_ENABLED` | 1 or true | false | 1 | Whether or not to index the chain to support the Ethereum RPC API | -| `FOREST_MESSAGES_IN_TIPSET_CACHE_SIZE` | positive integer | 8192 | 42 | The size of an internal cache of tipsets to messages | -| `FOREST_STATE_MIGRATION_DB_WRITE_BUFFER` | non-negative integer | 10000 | 100000 | The size of db write buffer for state migration (`~10MB` RAM per `10k` buffer) | -| `FOREST_SNAPSHOT_GC_INTERVAL_EPOCHS` | non-negative integer | 20160 | 8000 | The interval in epochs for scheduling snapshot GC | -| `FOREST_SNAPSHOT_GC_CHECK_INTERVAL_SECONDS` | non-negative integer | 300 | 60 | The interval in seconds for checking if snapshot GC should run | -| `FOREST_SNAPSHOT_GC_KEEP_STATE_TREE_EPOCHS` | non-negative integer | 2000 | 20160 | The number of most recent epochs of state trees to keep after GC | -| `FOREST_DISABLE_BAD_BLOCK_CACHE` | 1 or true | empty | 1 | Whether or not to disable bad block cache | -| `FOREST_ZSTD_FRAME_CACHE_DEFAULT_MAX_SIZE` | positive integer | 536870912 | 536870912 | The default zstd frame cache max size in bytes | -| `FOREST_JWT_DISABLE_EXP_VALIDATION` | 1 or true | empty | 1 | Whether or not to disable JWT expiration validation | -| `FOREST_ETH_BLOCK_CACHE_SIZE` | positive integer | 500 | 1 | The size of Eth block cache | -| `FOREST_RPC_BACKFILL_FULL_TIPSET_FROM_NETWORK` | 1 or true | false | 1 | Whether or not to backfill full tipsets from the p2p network | -| `FOREST_STRICT_JSON` | 1 or true | false | 1 | Enable strict JSON validation to detect duplicate keys and reject unknown fields in RPC requests and responses | -| `FOREST_AUTO_DOWNLOAD_SNAPSHOT_PATH` | URL or file path | empty | `/var/tmp/forest_snapshot_calibnet.forest.car.zst` | Override snapshot path for `--auto-download-snapshot` | -| `FOREST_DOWNLOAD_CONNECTIONS` | positive integer | 5 | 10 | Number of parallel HTTP connections for downloading snapshots | -| `FOREST_ETH_V1_DISABLE_F3_FINALITY_RESOLUTION` | 1 or true | empty | 1 | Whether or not to disable F3 finality resolution in Eth `v1` RPC methods | -| `FOREST_GENESIS_NETWORK_VERSION` | non-negative integer | empty | 25 | Override the genesis network version (devnet only) | -| `FOREST_TIPSET_CACHE_DISABLED` | 1 or true | empty | 1 | Disable the tipset cache. Used internally by development and tool subcommands | -| `FOREST_TIPSET_LOOKUP_TABLE_DISABLED` | 1 or true | empty | 1 | Disable the tipset lookup table. Used internally by development and tool subcommands | -| `FOREST_MAX_CONCURRENT_CHAIN_EXCHANGE_REQUESTS` | positive integer | 3 | 3 | Maximum number of **outbound** chain exchange requests sent by chain sync to the network | -| `FOREST_MAX_CONCURRENT_INBOUND_CHAIN_EXCHANGE_REQUESTS` | positive integer | 32 | 32 | Maximum number of inbound chain exchange requests Forest will service concurrently. Excess requests are rejected with a `GoAway` response | -| `FOREST_MAX_CONCURRENT_INBOUND_CHAIN_EXCHANGE_REQUESTS_PER_PEER` | positive integer | 4 | 4 | Per-peer cap on concurrent inbound chain exchange requests. Excess requests from a single peer are rejected with a `GoAway` response | -| `FOREST_MAX_OUTBOUND_CHAIN_EXCHANGE_RESPONSE_BYTES` | positive integer (bytes) | 10485760 (10 MiB) | 10485760 | Cap on the encoded byte size of a chain exchange response Forest serves to peers. Building stops as soon as the running encoded size would exceed this cap and the response is returned with `PartialResponse` status | -| `FOREST_ETH_RPC_COMPUTE_STATE_ON_INDEX_MISS` | 1 or true | false | 1 | Allows Ethereum RPC methods to compute state trees on index miss | -| `FOREST_RPC_METRICS_DISABLED` | 1 or true | false | 1 | Disable per-method JSON-RPC metrics only, leaving the metrics endpoint and all other metrics (cache, sync, database, ...) intact. To turn off metrics entirely, disable the endpoint instead with `--no-metrics` | +| Environment variable | Value | Default | Example | Description | +| ---------------------------------------------------------------- | -------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `FOREST_KEYSTORE_PHRASE` | any text | empty | `asfvdda` | The passphrase for the encrypted keystore | +| `FOREST_CAR_LOADER_FILE_IO` | 1 or true | false | true | Load CAR files with `RandomAccessFile` instead of `Mmap` | +| `FOREST_DB_DEV_MODE` | [see here](#forest_db_dev_mode) | current | current | The database to use in development mode | +| `FOREST_ACTOR_BUNDLE_PATH` | file path | empty | `/path/to/file.car.zst` | Path to the local actor bundle, download from remote servers when not set | +| `FIL_PROOFS_PARAMETER_CACHE` | directory path | empty | `/var/tmp/filecoin-proof-parameters` | Path to folder that caches fil proof parameter files | +| `FOREST_PROOFS_ONLY_IPFS_GATEWAY` | 1 or true | false | 1 | Use only IPFS gateway for proofs parameters download | +| `FOREST_FORCE_TRUST_PARAMS` | 1 or true | false | 1 | Trust the parameters downloaded from the Cloudflare/IPFS | +| `IPFS_GATEWAY` | URL | `https://proofs.filecoin.io/ipfs/` | `https://proofs.filecoin.io/ipfs/` | The IPFS gateway to use for downloading proofs parameters | +| `FOREST_RPC_DEFAULT_TIMEOUT` | Duration (in seconds) | 60 | 10 | The default timeout for RPC calls | +| `FOREST_RPC_MAX_CONNECTIONS` | positive integer | 1000 | 42 | Maximum number of allowed connections for the RPC server | +| `FOREST_RPC_COMPRESS_MIN_BODY_SIZE` | integer in `[-1, 65535]` (bytes) | -1 (disabled) | 2048 (or `-1` to disable) | Disabled by default. When set to a non-negative value, gzip-compresses responses whose body is at least that many bytes; smaller responses are sent uncompressed. Values above 65535 are clamped to 65535. Set to a negative value (e.g. `-1`) to disable compression entirely | +| `FOREST_MAX_CONCURRENT_REQUEST_RESPONSE_STREAMS_PER_PEER` | positive integer | 10 | 10 | the maximum concurrent streams per peer for request-response-based p2p protocols | +| `FOREST_BLOCK_DELAY_SECS` | positive integer | Depends on the network | 30 | Duration of each tipset epoch | +| `FOREST_PROPAGATION_DELAY_SECS` | positive integer | Depends on the network | 20 | How long to wait for a block to propagate through the network | +| `FOREST_PLEDGE_RULE_RAMP` | positive integer | Depends on the network | 200 | Pledge rule ramp duration in epochs (FIP 0081) | +| `FOREST_MAX_FILTERS` | integer | 100 | 100 | The maximum number of filters | +| `FOREST_MAX_FILTER_RESULTS` | positive integer | 10,000 | 10000 | The maximum number of filter results | +| `FOREST_MAX_FILTER_HEIGHT_RANGE` | positive integer | 2880 | 2880 | The maximum filter height range allowed, a conservative limit of one day | +| `FOREST_STATE_MIGRATION_THREADS` | integer | Depends on the machine. | 3 | The number of threads for state migration thread-pool. Advanced users only. | +| `FOREST_CONFIG_PATH` | string | /$FOREST_HOME/com.ChainSafe.Forest/config.toml | `/path/to/config.toml` | Forest configuration path. Alternatively supplied via `--config` cli parameter. | +| `FOREST_PATH` | directory path | platform data directory (e.g. `$HOME/.local/share/forest`) | `/var/lib/forest` | Override the Forest data directory. Takes precedence over the `data_dir` set in the configuration file and the default. Honored by all `forest*` binaries, so `forest-cli`/`forest-tool` also read the JWT admin token from this directory. Equivalent to Lotus' `LOTUS_PATH`. | +| `FOREST_TEST_RNG_FIXED_SEED` | non-negative integer | empty | 0 | Override RNG with a reproducible one seeded by the value. This should never be used out of test context for security. | +| `RUST_LOG` | string | empty | `debug,forest_libp2p::service=info` | Allows for log level customization. | +| `FOREST_IGNORE_DRAND` | 1 or true | empty | 1 | Ignore Drand validation. | +| `FOREST_LIBP2P_METRICS_ENABLED` | 1 or true | empty | 1 | Include `libp2p` metrics in Forest's Prometheus output. | +| `FOREST_F3_SIDECAR_RPC_ENDPOINT` | string | 127.0.0.1:23456 | `127.0.0.1:23456` | An RPC endpoint of F3 sidecar. | +| `FOREST_F3_SIDECAR_FFI_ENABLED` | 1 or true | hard-coded per chain | 1 | Whether or not to start the F3 sidecar via FFI | +| `FOREST_F3_CONSENSUS_ENABLED` | 1 or true | hard-coded per chain | 1 | Whether or not to apply the F3 consensus to the node | +| `FOREST_F3_FINALITY` | integer | inherited from chain configuration | 900 | Set the chain finality epochs in F3 manifest | +| `FOREST_F3_PERMANENT_PARTICIPATING_MINER_ADDRESSES` | comma delimited strings | empty | `t0100,t0101` | Set the miner addresses that participate in F3 permanently | +| `FOREST_F3_INITIAL_POWER_TABLE` | string | empty | `bafyreicmaj5hhoy5mgqvamfhgexxyergw7hdeshizghodwkjg6qmpoco7i` | Set the F3 initial power table CID | +| `FOREST_F3_ROOT` | string | [FOREST_DATA_ROOT]/f3 | `/var/tmp/f3` | Set the data directory for F3 | +| `FOREST_F3_BOOTSTRAP_EPOCH` | integer | -1 | 100 | Set the bootstrap epoch for F3 | +| `FOREST_DRAND_MAINNET_CONFIG` | string | empty | refer to Drand config format section | Override `DRAND_MAINNET` config | +| `FOREST_DRAND_QUICKNET_CONFIG` | string | empty | refer to Drand config format section | Override `DRAND_QUICKNET` config | +| `FOREST_DRAND_INCENTINET_CONFIG` | string | empty | refer to Drand config format section | Override `DRAND_INCENTINET` config | +| `FOREST_TRACE_FILTER_MAX_RESULT` | positive integer | 500 | 1000 | Sets the maximum results returned per request by `trace_filter` | +| `FOREST_CHAIN_INDEXER_ENABLED` | 1 or true | false | 1 | Whether or not to index the chain to support the Ethereum RPC API | +| `FOREST_MESSAGES_IN_TIPSET_CACHE_SIZE` | positive integer | 8192 | 42 | The size of an internal cache of tipsets to messages | +| `FOREST_STATE_MIGRATION_DB_WRITE_BUFFER` | non-negative integer | 10000 | 100000 | The size of db write buffer for state migration (`~10MB` RAM per `10k` buffer) | +| `FOREST_SNAPSHOT_GC_INTERVAL_EPOCHS` | non-negative integer | 20160 | 8000 | The interval in epochs for scheduling snapshot GC | +| `FOREST_SNAPSHOT_GC_CHECK_INTERVAL_SECONDS` | non-negative integer | 300 | 60 | The interval in seconds for checking if snapshot GC should run | +| `FOREST_SNAPSHOT_GC_KEEP_STATE_TREE_EPOCHS` | non-negative integer | 2000 | 20160 | The number of most recent epochs of state trees to keep after GC | +| `FOREST_DISABLE_BAD_BLOCK_CACHE` | 1 or true | empty | 1 | Whether or not to disable bad block cache | +| `FOREST_ZSTD_FRAME_CACHE_DEFAULT_MAX_SIZE` | positive integer | 536870912 | 536870912 | The default zstd frame cache max size in bytes | +| `FOREST_JWT_DISABLE_EXP_VALIDATION` | 1 or true | empty | 1 | Whether or not to disable JWT expiration validation | +| `FOREST_ETH_BLOCK_CACHE_SIZE` | positive integer | 500 | 1 | The size of Eth block cache | +| `FOREST_RPC_BACKFILL_FULL_TIPSET_FROM_NETWORK` | 1 or true | false | 1 | Whether or not to backfill full tipsets from the p2p network | +| `FOREST_STRICT_JSON` | 1 or true | false | 1 | Enable strict JSON validation to detect duplicate keys and reject unknown fields in RPC requests and responses | +| `FOREST_AUTO_DOWNLOAD_SNAPSHOT_PATH` | URL or file path | empty | `/var/tmp/forest_snapshot_calibnet.forest.car.zst` | Override snapshot path for `--auto-download-snapshot` | +| `FOREST_DOWNLOAD_CONNECTIONS` | positive integer | 5 | 10 | Number of parallel HTTP connections for downloading snapshots | +| `FOREST_ETH_V1_DISABLE_F3_FINALITY_RESOLUTION` | 1 or true | empty | 1 | Whether or not to disable F3 finality resolution in Eth `v1` RPC methods | +| `FOREST_GENESIS_NETWORK_VERSION` | non-negative integer | empty | 25 | Override the genesis network version (devnet only) | +| `FOREST_TIPSET_CACHE_DISABLED` | 1 or true | empty | 1 | Disable the tipset cache. Used internally by development and tool subcommands | +| `FOREST_TIPSET_LOOKUP_TABLE_DISABLED` | 1 or true | empty | 1 | Disable the tipset lookup table. Used internally by development and tool subcommands | +| `FOREST_MAX_CONCURRENT_CHAIN_EXCHANGE_REQUESTS` | positive integer | 3 | 3 | Maximum number of **outbound** chain exchange requests sent by chain sync to the network | +| `FOREST_MAX_CONCURRENT_INBOUND_CHAIN_EXCHANGE_REQUESTS` | positive integer | 32 | 32 | Maximum number of inbound chain exchange requests Forest will service concurrently. Excess requests are rejected with a `GoAway` response | +| `FOREST_MAX_CONCURRENT_INBOUND_CHAIN_EXCHANGE_REQUESTS_PER_PEER` | positive integer | 4 | 4 | Per-peer cap on concurrent inbound chain exchange requests. Excess requests from a single peer are rejected with a `GoAway` response | +| `FOREST_MAX_OUTBOUND_CHAIN_EXCHANGE_RESPONSE_BYTES` | positive integer (bytes) | 10485760 (10 MiB) | 10485760 | Cap on the encoded byte size of a chain exchange response Forest serves to peers. Building stops as soon as the running encoded size would exceed this cap and the response is returned with `PartialResponse` status | +| `FOREST_ETH_RPC_COMPUTE_STATE_ON_INDEX_MISS` | 1 or true | false | 1 | Allows Ethereum RPC methods to compute state trees on index miss | +| `FOREST_RPC_METRICS_DISABLED` | 1 or true | false | 1 | Disable per-method JSON-RPC metrics only, leaving the metrics endpoint and all other metrics (cache, sync, database, ...) intact. To turn off metrics entirely, disable the endpoint instead with `--no-metrics` | ### `FOREST_F3_SIDECAR_FFI_BUILD_OPT_OUT` diff --git a/scripts/tests/butterflynet_check.sh b/scripts/tests/butterflynet_check.sh index aac7297b95a5..1b7780251d89 100755 --- a/scripts/tests/butterflynet_check.sh +++ b/scripts/tests/butterflynet_check.sh @@ -11,7 +11,7 @@ function shutdown { trap shutdown EXIT -$FOREST_PATH --chain butterflynet --encrypt-keystore false & +$FOREST_DAEMON_PATH --chain butterflynet --encrypt-keystore false & FOREST_NODE_PID=$! forest_wait_api diff --git a/scripts/tests/calibnet_kademlia_check.sh b/scripts/tests/calibnet_kademlia_check.sh index 91f6aa68cc94..c4af4a89be71 100755 --- a/scripts/tests/calibnet_kademlia_check.sh +++ b/scripts/tests/calibnet_kademlia_check.sh @@ -24,7 +24,7 @@ cat <<- EOF > $CONFIG_PATH kademlia = true EOF -$FOREST_PATH --chain calibnet --encrypt-keystore false --auto-download-snapshot --config "$CONFIG_PATH" --save-token ./admin_token --rpc-address 127.0.0.1:12345 --metrics-address 127.0.0.1:6117 --healthcheck-address 127.0.0.1:2347 & +$FOREST_DAEMON_PATH --chain calibnet --encrypt-keystore false --auto-download-snapshot --config "$CONFIG_PATH" --save-token ./admin_token --rpc-address 127.0.0.1:12345 --metrics-address 127.0.0.1:6117 --healthcheck-address 127.0.0.1:2347 & FOREST_NODE_PID=$! # Verify that more peers are connected via kademlia until (( $(curl http://127.0.0.1:6117/metrics | grep full_peers | tail -n 1 | cut --delimiter=" " --fields=2) > 1 )); do diff --git a/scripts/tests/calibnet_migration_regression_tests.sh b/scripts/tests/calibnet_migration_regression_tests.sh index 6f95a2c69e6d..a73e1df3f124 100755 --- a/scripts/tests/calibnet_migration_regression_tests.sh +++ b/scripts/tests/calibnet_migration_regression_tests.sh @@ -6,8 +6,8 @@ set -e # migration point and then we validate the last 200 tipsets. This triggers the # migration logic without connecting to the real Filecoin network. -FOREST_PATH="forest" -MIGRATION_TEST="/usr/bin/time -v $FOREST_PATH --chain calibnet --encrypt-keystore false --halt-after-import --height=-200 --no-gc --import-snapshot" +FOREST_DAEMON_PATH="forest" +MIGRATION_TEST="/usr/bin/time -v $FOREST_DAEMON_PATH --chain calibnet --encrypt-keystore false --halt-after-import --height=-200 --no-gc --import-snapshot" # NV17 - Shark, uncomment when we support the nv17 migration echo NV17 - Shark diff --git a/scripts/tests/calibnet_no_discovery_check.sh b/scripts/tests/calibnet_no_discovery_check.sh index 8d4c14f13ffb..b99ec66810c2 100755 --- a/scripts/tests/calibnet_no_discovery_check.sh +++ b/scripts/tests/calibnet_no_discovery_check.sh @@ -11,8 +11,8 @@ function shutdown { trap shutdown EXIT -$FOREST_PATH --chain calibnet --encrypt-keystore false --mdns false --kademlia false --auto-download-snapshot --exit-after-init -$FOREST_PATH --chain calibnet --encrypt-keystore false --mdns false --kademlia false --auto-download-snapshot --log-dir "$LOG_DIRECTORY" & +$FOREST_DAEMON_PATH --chain calibnet --encrypt-keystore false --mdns false --kademlia false --auto-download-snapshot --exit-after-init +$FOREST_DAEMON_PATH --chain calibnet --encrypt-keystore false --mdns false --kademlia false --auto-download-snapshot --log-dir "$LOG_DIRECTORY" & FOREST_NODE_PID=$! forest_wait_api diff --git a/scripts/tests/calibnet_stateless_mode_check.sh b/scripts/tests/calibnet_stateless_mode_check.sh index 232af0dfb79d..49c02bc5f916 100755 --- a/scripts/tests/calibnet_stateless_mode_check.sh +++ b/scripts/tests/calibnet_stateless_mode_check.sh @@ -25,7 +25,7 @@ cat <<- EOF > $CONFIG_PATH EOF # Disable discovery to not connect to more nodes -$FOREST_PATH --chain calibnet --encrypt-keystore false --auto-download-snapshot --config "$CONFIG_PATH" --rpc false --metrics-address 127.0.0.1:6117 --healthcheck-address 127.0.0.1:2347 & +$FOREST_DAEMON_PATH --chain calibnet --encrypt-keystore false --auto-download-snapshot --config "$CONFIG_PATH" --rpc false --metrics-address 127.0.0.1:6117 --healthcheck-address 127.0.0.1:2347 & FOREST_NODE_PID=$! # Verify that the stateless node can respond to chain exchange requests until curl http://127.0.0.1:6117/metrics | grep "chain_exchange_response_in"; do diff --git a/scripts/tests/calibnet_stateless_rpc_check.sh b/scripts/tests/calibnet_stateless_rpc_check.sh index 8b6aaadb7fd8..7aff2d9f6a72 100755 --- a/scripts/tests/calibnet_stateless_rpc_check.sh +++ b/scripts/tests/calibnet_stateless_rpc_check.sh @@ -10,7 +10,7 @@ function forest_run_node_stateless_detached_with_filter_list { pkill -9 forest || true local filter_list=$1 - $FOREST_PATH --chain calibnet --encrypt-keystore false --log-dir "$LOG_DIRECTORY" --stateless --rpc-filter-list "$filter_list" & + $FOREST_DAEMON_PATH --chain calibnet --encrypt-keystore false --log-dir "$LOG_DIRECTORY" --stateless --rpc-filter-list "$filter_list" & forest_wait_api } diff --git a/scripts/tests/harness.sh b/scripts/tests/harness.sh index f7ccd98a5783..b9639b6310a5 100644 --- a/scripts/tests/harness.sh +++ b/scripts/tests/harness.sh @@ -5,7 +5,11 @@ export FOREST_CHAIN_INDEXER_ENABLED="1" -export FOREST_PATH="forest" +# Path to the `forest` daemon binary. Note this is intentionally NOT named +# `FOREST_PATH`: that environment variable is honored by Forest itself to +# override the data directory, so exporting it here would clobber the data dir +# of every daemon launched by these tests. +export FOREST_DAEMON_PATH="forest" export FOREST_CLI_PATH="forest-cli" export FOREST_WALLET_PATH="forest-wallet" export FOREST_TOOL_PATH="forest-tool" @@ -18,12 +22,12 @@ export LOG_DIRECTORY function forest_import_non_calibnet_snapshot { echo "Importing a non calibnet snapshot" - $FOREST_PATH --chain calibnet --encrypt-keystore false --halt-after-import --import-snapshot ./test-snapshots/chain4.car + $FOREST_DAEMON_PATH --chain calibnet --encrypt-keystore false --halt-after-import --import-snapshot ./test-snapshots/chain4.car } function forest_download_and_import_snapshot { echo "Downloading and importing snapshot" - $FOREST_PATH --chain calibnet --encrypt-keystore false --halt-after-import --height=-200 --auto-download-snapshot + $FOREST_DAEMON_PATH --chain calibnet --encrypt-keystore false --halt-after-import --height=-200 --auto-download-snapshot } function get_epoch_from_car_db { @@ -71,7 +75,7 @@ function forest_query_format { function forest_run_node_detached { echo "Running forest" - /usr/bin/time -v $FOREST_PATH --chain calibnet --encrypt-keystore false --log-dir "$LOG_DIRECTORY" & + /usr/bin/time -v $FOREST_DAEMON_PATH --chain calibnet --encrypt-keystore false --log-dir "$LOG_DIRECTORY" & } function forest_run_node_stateless_detached { @@ -86,7 +90,7 @@ function forest_run_node_stateless_detached { listening_multiaddrs = ["/ip4/127.0.0.1/tcp/0", "/ip4/127.0.0.1/udp/0/quic-v1"] EOF - $FOREST_PATH --chain calibnet --encrypt-keystore false --config "$CONFIG_PATH" --log-dir "$LOG_DIRECTORY" --save-token ./stateless_admin_token --stateless & + $FOREST_DAEMON_PATH --chain calibnet --encrypt-keystore false --config "$CONFIG_PATH" --log-dir "$LOG_DIRECTORY" --save-token ./stateless_admin_token --stateless & } function forest_wait_api { diff --git a/src/cli_shared/cli/client.rs b/src/cli_shared/cli/client.rs index 59fd0c972d74..7693ef5a2be6 100644 --- a/src/cli_shared/cli/client.rs +++ b/src/cli_shared/cli/client.rs @@ -99,8 +99,11 @@ impl Default for Client { } impl Client { + /// File name of the RPC admin token, stored within the data directory. + pub const RPC_TOKEN_FILENAME: &'static str = "token"; + pub fn default_rpc_token_path(&self) -> PathBuf { - self.data_dir.join("token") + self.data_dir.join(Self::RPC_TOKEN_FILENAME) } pub fn rpc_v1_endpoint(&self) -> Result { diff --git a/src/cli_shared/mod.rs b/src/cli_shared/mod.rs index 170c032f1b6d..cf91be5260f4 100644 --- a/src/cli_shared/mod.rs +++ b/src/cli_shared/mod.rs @@ -16,6 +16,11 @@ cfg_if::cfg_if! { } } +/// Environment variable that overrides the Forest data directory, taking +/// precedence over both the configuration file and the built-in default. Named +/// after Lotus' `LOTUS_PATH` to ease switching between implementations. +pub const FOREST_DATA_DIR_ENV: &str = "FOREST_PATH"; + /// Gets chain data directory pub fn chain_path(config: &Config) -> PathBuf { PathBuf::from(&config.client.data_dir).join(config.chain().to_string()) @@ -37,9 +42,39 @@ pub fn read_config( if let Some(chain) = chain_opt { config.chain = chain; } + // The `FOREST_PATH` environment variable takes precedence over the data + // directory set in the configuration file (or the default one). + if let Some(data_dir) = data_dir_from_env() { + config.client.data_dir = data_dir; + } Ok((path, config)) } +/// Returns the data directory set via the [`FOREST_DATA_DIR_ENV`] environment +/// variable, if it is present and non-empty. +fn data_dir_from_env() -> Option { + match std::env::var(FOREST_DATA_DIR_ENV) { + Ok(s) if !s.trim().is_empty() => Some(PathBuf::from(s)), + _ => None, + } +} + +/// Returns the effective Forest data directory: the [`FOREST_DATA_DIR_ENV`] +/// environment variable if set, otherwise the built-in default. Unlike +/// [`read_config`], this does not consult a configuration file and is meant for +/// contexts (e.g. the RPC client) that need the data directory without loading +/// the full configuration. +pub fn default_data_dir() -> PathBuf { + data_dir_from_env().unwrap_or_else(|| crate::cli_shared::cli::Client::default().data_dir) +} + +/// Returns the path to the RPC admin token within the effective data directory +/// (see [`default_data_dir`]). This is where a daemon started with the same +/// environment saves the token, so clients can read it back from here. +pub fn default_token_path() -> PathBuf { + default_data_dir().join(crate::cli_shared::cli::Client::RPC_TOKEN_FILENAME) +} + #[cfg(test)] mod tests { use super::*; @@ -68,7 +103,47 @@ mod tests { assert_eq!(config.chain(), &NetworkChain::Butterflynet); } + /// Runs `f` with [`FOREST_DATA_DIR_ENV`] set to `value`, restoring the + /// environment afterwards. + fn with_data_dir_env(value: &str, f: impl FnOnce() -> T) -> T { + unsafe { std::env::set_var(FOREST_DATA_DIR_ENV, value) }; + let result = f(); + unsafe { std::env::remove_var(FOREST_DATA_DIR_ENV) }; + result + } + + #[test] + #[serial_test::serial] + fn read_config_data_dir_env_override() { + let data_dir = "/tmp/forest-path-env-override-test"; + let (_, config) = with_data_dir_env(data_dir, || read_config(None, None).unwrap()); + + // The env variable takes precedence over the default data directory. + assert_eq!(config.client.data_dir, std::path::Path::new(data_dir)); + } + + #[test] + #[serial_test::serial] + fn default_data_dir_honors_env_override() { + let data_dir = "/tmp/forest-path-default-data-dir-test"; + let resolved = with_data_dir_env(data_dir, default_data_dir); + assert_eq!(resolved, std::path::Path::new(data_dir)); + + // Without the env variable, it falls back to the default data directory. + assert_eq!(default_data_dir(), Config::default().client.data_dir); + } + + #[test] + #[serial_test::serial] + fn read_config_data_dir_env_empty_is_ignored() { + let (_, config) = with_data_dir_env("", || read_config(None, None).unwrap()); + + // An empty env variable falls back to the default data directory. + assert_eq!(config.client.data_dir, Config::default().client.data_dir); + } + #[test] + #[serial_test::serial] fn read_config_with_path() { let default_config = Config::default(); let temp_dir = tempfile::tempdir().expect("couldn't create temp dir"); diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index f19b0a532292..f1b87f1c91b4 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -115,6 +115,7 @@ fn startup_init(config: &Config) -> anyhow::Result<()> { "Starting Forest daemon, version {}", FOREST_VERSION_STRING.as_str() ); + info!("Using data directory: {}", config.client.data_dir.display()); Ok(()) } diff --git a/src/dev/subcommands/mod.rs b/src/dev/subcommands/mod.rs index caf49d19477b..b2d501b77ad8 100644 --- a/src/dev/subcommands/mod.rs +++ b/src/dev/subcommands/mod.rs @@ -71,7 +71,7 @@ impl Subcommand { async fn fetch_test_snapshots(actor_bundle: Option) -> anyhow::Result<()> { // Prepare proof parameter files crate::utils::proofs_api::maybe_set_proofs_parameter_cache_dir_env( - &crate::Config::default().client.data_dir, + &crate::cli_shared::default_data_dir(), ); ensure_proof_params_downloaded().await?; diff --git a/src/rpc/client.rs b/src/rpc/client.rs index 287fd4fd16bf..107e6738033b 100644 --- a/src/rpc/client.rs +++ b/src/rpc/client.rs @@ -58,8 +58,9 @@ impl Client { } // Set default token if not provided if token.is_none() && base_url.password().is_none() { - let client_config = crate::cli_shared::cli::Client::default(); - let default_token_path = client_config.default_rpc_token_path(); + // Honor the `FOREST_PATH` data directory override so the token saved + // by a daemon started with `FOREST_PATH` set is found here as well. + let default_token_path = crate::cli_shared::default_token_path(); if default_token_path.is_file() { if let Ok(token) = std::fs::read_to_string(&default_token_path) { if base_url.set_password(Some(token.trim())).is_ok() { @@ -325,3 +326,28 @@ impl jsonrpsee::core::client::SubscriptionClientT for UrlClient { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::cli_shared::FOREST_DATA_DIR_ENV; + + // The RPC client should pick up the admin token from the data directory + // pointed to by `FOREST_PATH`, mirroring where a daemon started with the + // same variable saves it. + #[test] + #[serial_test::serial] + fn default_token_is_loaded_from_forest_path_data_dir() { + let tmp_dir = tempfile::tempdir().unwrap(); + std::fs::write(tmp_dir.path().join("token"), "secret-token").unwrap(); + + unsafe { + env::remove_var("FULLNODE_API_INFO"); + env::set_var(FOREST_DATA_DIR_ENV, tmp_dir.path()); + } + let client = Client::default_or_from_env(None).unwrap(); + unsafe { env::remove_var(FOREST_DATA_DIR_ENV) }; + + assert_eq!(client.token.as_deref(), Some("secret-token")); + } +} diff --git a/src/tool/offline_server/server.rs b/src/tool/offline_server/server.rs index 11c5c9262e74..9ff2c3760555 100644 --- a/src/tool/offline_server/server.rs +++ b/src/tool/offline_server/server.rs @@ -1,6 +1,7 @@ // Copyright 2019-2026 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT +use crate::JWT_IDENTIFIER; use crate::auth::generate_priv_key; use crate::chain::ChainStore; use crate::chain_sync::SyncStatusReport; @@ -21,7 +22,6 @@ use crate::shim::address::{CurrentNetwork, Network}; use crate::state_manager::StateManager; use crate::utils::net::{DownloadFileOption, download_to}; use crate::utils::proofs_api::{self, ensure_proof_params_downloaded}; -use crate::{Config, JWT_IDENTIFIER}; use jsonrpsee::server::stop_channel; use parking_lot::RwLock; use std::{ @@ -132,7 +132,7 @@ pub async fn start_offline_server( // Set proof parameter data dir and make sure the proofs are available. Otherwise, // validation might fail due to missing proof parameters. - proofs_api::maybe_set_proofs_parameter_cache_dir_env(&Config::default().client.data_dir); + proofs_api::maybe_set_proofs_parameter_cache_dir_env(&crate::cli_shared::default_data_dir()); ensure_proof_params_downloaded().await?; let db = { diff --git a/src/tool/subcommands/api_cmd/api_compare_tests.rs b/src/tool/subcommands/api_cmd/api_compare_tests.rs index ae0de69aeb54..73aef6ba094f 100644 --- a/src/tool/subcommands/api_cmd/api_compare_tests.rs +++ b/src/tool/subcommands/api_cmd/api_compare_tests.rs @@ -9,6 +9,7 @@ use crate::eth::EthChainId as EthChainIdType; use crate::lotus_json::HasLotusJson; use crate::message::{MessageRead as _, SignedMessage}; use crate::prelude::*; +use crate::rpc; use crate::rpc::auth::AuthNewParams; use crate::rpc::beacon::BeaconGetEntry; use crate::rpc::eth::{ @@ -39,7 +40,6 @@ use crate::tool::subcommands::api_cmd::NetworkChain; use crate::tool::subcommands::api_cmd::report::ReportBuilder; use crate::tool::subcommands::api_cmd::state_decode_params_tests::create_all_state_decode_params_tests; use crate::utils::proofs_api::{self, ensure_proof_params_downloaded}; -use crate::{Config, rpc}; use ahash::HashMap; use bls_signatures::Serialize as _; use chrono::Utc; @@ -2633,7 +2633,7 @@ async fn revalidate_chain(db: Arc, n_ts_to_validate: usize) -> anyhow:: // Set proof parameter data dir and make sure the proofs are available. Otherwise, // validation might fail due to missing proof parameters. - proofs_api::maybe_set_proofs_parameter_cache_dir_env(&Config::default().client.data_dir); + proofs_api::maybe_set_proofs_parameter_cache_dir_env(&crate::cli_shared::default_data_dir()); ensure_proof_params_downloaded().await?; state_manager.validate_tipsets_blocking( head_ts diff --git a/src/tool/subcommands/api_cmd/test_snapshot.rs b/src/tool/subcommands/api_cmd/test_snapshot.rs index 4963fce3f8f1..979f0641267e 100644 --- a/src/tool/subcommands/api_cmd/test_snapshot.rs +++ b/src/tool/subcommands/api_cmd/test_snapshot.rs @@ -202,7 +202,6 @@ pub(super) async fn drain_mpool_services(mut services: JoinSet