From 01706ee25fe4210abb9319530d83238f40a4bca4 Mon Sep 17 00:00:00 2001 From: Shashank Date: Tue, 30 Jun 2026 13:46:23 +0530 Subject: [PATCH 01/10] devnet wallet test --- .github/workflows/forest.yml | 11 +++++++ mise.toml | 15 ++++++++++ scripts/devnet/README.md | 17 ++++++++++- scripts/devnet/wallet_harness.sh | 50 ++++++++++++++++++++++++++++++++ src/dev/subcommands/tests_cmd.rs | 6 +++- 5 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 scripts/devnet/wallet_harness.sh diff --git a/.github/workflows/forest.yml b/.github/workflows/forest.yml index 10779abf9d25..8aa134835b8a 100644 --- a/.github/workflows/forest.yml +++ b/.github/workflows/forest.yml @@ -480,6 +480,17 @@ jobs: - name: Devnet check run: ./scripts/devnet/check.sh timeout-minutes: ${{ fromJSON(env.SCRIPT_TIMEOUT_MINUTES) }} + - name: Add forest binaries to PATH + run: | + chmod +x "$GITHUB_WORKSPACE"/forest* + echo "$GITHUB_WORKSPACE" >> "$GITHUB_PATH" + - name: Devnet wallet tests + run: | + source ./scripts/devnet/wallet_harness.sh + devnet_wallet_env_init + forest-dev tests devnet mpool + forest-dev tests devnet wallet + timeout-minutes: ${{ fromJSON(env.SCRIPT_TIMEOUT_MINUTES) }} - name: Dump docker logs if: always() uses: jwalton/gh-docker-logs@v2 diff --git a/mise.toml b/mise.toml index ce9cbb99d816..33ba3156f3e5 100644 --- a/mise.toml +++ b/mise.toml @@ -225,6 +225,21 @@ forest-dev tests calibnet mpool forest-dev tests calibnet wallet ''' +[tasks."test:wallet-devnet"] +description = "Run wallet integration tests against a local docker devnet." +shell = "bash -c" +run = ''' +set -euo pipefail +pushd scripts/devnet +./setup.sh +./check.sh +popd +source ./scripts/devnet/wallet_harness.sh +devnet_wallet_env_init +forest-dev tests devnet mpool +forest-dev tests devnet wallet +''' + [tasks."codecov:nextest"] description = "Run codecov with nextest" run = ''' diff --git a/scripts/devnet/README.md b/scripts/devnet/README.md index e6985a049500..34f6cfc78f6b 100644 --- a/scripts/devnet/README.md +++ b/scripts/devnet/README.md @@ -50,11 +50,26 @@ and setup credentials. Then run any command: ```shell export TOKEN=$(cat /forest_data/token.jwt) -export FULLNODE_API_INFO=$TOKEN:/dns/forest/tcp/1234/http +export FULLNODE_API_INFO=$TOKEN:/dns/forest/tcp/3456/http forest-cli net peers ``` +## Running the wallet integration tests + +The same wallet/mpool integration suite that runs against calibnet can be run +against the local devnet. This brings up the devnet, waits for it to sync, wires +up the host environment, and runs the tests: + +```shell +mise run test:wallet-devnet +``` + +Under the hood this sources `wallet_harness.sh`, which reads the admin token and +the funded genesis key from the running `forest` container, exports +`FULLNODE_API_INFO` (Forest RPC on port 3456) and `FOREST_TEST_PRELOADED_ADDRESS`, +then runs `forest-dev tests devnet mpool` and `forest-dev tests devnet wallet`. + ## Local devnet development If you prefer to have Forest running directly on the host, you can comment it diff --git a/scripts/devnet/wallet_harness.sh b/scripts/devnet/wallet_harness.sh new file mode 100644 index 000000000000..49aa502d63a0 --- /dev/null +++ b/scripts/devnet/wallet_harness.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# Helpers for running the wallet/mpool integration suite against the local +# docker devnet. Meant to be sourced (not executed) after the devnet is up +# (see `setup.sh`) and synced (see `check.sh`). +# +# The wallet tests are chain-agnostic and only need: +# - `FULLNODE_API_INFO` -> RPC endpoint + admin token +# - `FOREST_TEST_PRELOADED_ADDRESS`-> a funded sender address +# +# On the devnet, the funded sender is the genesis miner owner, whose key lives +# in the shared lotus volume at +# `${LOTUS_DATA_DIR}/genesis-sectors/pre-seal-${MINER_ACTOR_ADDRESS}.key`. + +# Path to the directory containing this script. +WALLET_HARNESS_PARENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +source "${WALLET_HARNESS_PARENT_PATH}/.env" + +# Allow overriding the binaries, mirroring `scripts/tests/harness.sh`. +export FOREST_CLI_PATH="${FOREST_CLI_PATH:-forest-cli}" +export FOREST_WALLET_PATH="${FOREST_WALLET_PATH:-forest-wallet}" + +# Wire up the host environment so `forest-cli`/`forest-wallet`/`forest-dev` +# talk to the dockerized Forest node and have access to the funded genesis key. +function devnet_wallet_env_init { + set -euo pipefail + + # Admin token is written by the Forest container on startup. + local token + token=$(docker exec forest cat "${FOREST_DATA_DIR}/token.jwt") + export FULLNODE_API_INFO="${token}:/ip4/127.0.0.1/tcp/${FOREST_RPC_PORT}/http" + + # Extract the funded genesis key from the running container (it mounts the + # shared lotus volume) onto the host so we can import it locally. + local key_path="${TMPDIR:-/tmp}/devnet_preloaded_wallet.key" + docker cp "forest:${LOTUS_DATA_DIR}/genesis-sectors/pre-seal-${MINER_ACTOR_ADDRESS}.key" "${key_path}" + + # Import into both keystores (local file + node-managed remote) so the + # `Backend::Local` and `Backend::Remote` test variants both work. + FOREST_TEST_PRELOADED_ADDRESS="$(${FOREST_WALLET_PATH} import "${key_path}")" + export FOREST_TEST_PRELOADED_ADDRESS + ${FOREST_WALLET_PATH} --remote-wallet import "${key_path}" || true + + echo "Devnet wallet env initialised:" + echo " FULLNODE_API_INFO=:/ip4/127.0.0.1/tcp/${FOREST_RPC_PORT}/http" + echo " FOREST_TEST_PRELOADED_ADDRESS=${FOREST_TEST_PRELOADED_ADDRESS}" + + # Sanity checks: the node is reachable and the preloaded address is funded. + ${FOREST_CLI_PATH} chain head + ${FOREST_WALLET_PATH} --remote-wallet balance "${FOREST_TEST_PRELOADED_ADDRESS}" --exact-balance +} diff --git a/src/dev/subcommands/tests_cmd.rs b/src/dev/subcommands/tests_cmd.rs index 8314764cf8c8..342f42ef2a53 100644 --- a/src/dev/subcommands/tests_cmd.rs +++ b/src/dev/subcommands/tests_cmd.rs @@ -8,12 +8,16 @@ mod calibnet; pub enum TestsCommand { #[command(subcommand)] Calibnet(calibnet::CalibnetTestsCommand), + /// Run the wallet/mpool integration suite against a local devnet. The tests + /// themselves are chain-agnostic, so the calibnet suite is reused. + #[command(subcommand)] + Devnet(calibnet::CalibnetTestsCommand), } impl TestsCommand { pub async fn run(self) -> anyhow::Result<()> { match self { - Self::Calibnet(cmd) => cmd.run().await, + Self::Calibnet(cmd) | Self::Devnet(cmd) => cmd.run().await, } } } From efc8a6a7b93d537ddb04c22bbfaae2555b362a5f Mon Sep 17 00:00:00 2001 From: Shashank Date: Wed, 1 Jul 2026 14:14:00 +0530 Subject: [PATCH 02/10] run only wallet test --- .github/workflows/forest.yml | 3 +- mise.toml | 3 +- scripts/devnet/wallet_harness.sh | 66 +++++++++++-------- .../subcommands/tests_cmd/calibnet/helpers.rs | 2 + .../subcommands/tests_cmd/calibnet/wallet.rs | 11 ++-- 5 files changed, 52 insertions(+), 33 deletions(-) diff --git a/.github/workflows/forest.yml b/.github/workflows/forest.yml index 8aa134835b8a..c2893900d1cf 100644 --- a/.github/workflows/forest.yml +++ b/.github/workflows/forest.yml @@ -488,7 +488,8 @@ jobs: run: | source ./scripts/devnet/wallet_harness.sh devnet_wallet_env_init - forest-dev tests devnet mpool + # mpool test temporarily disabled on devnet; only the wallet suite runs for now. + # forest-dev tests devnet mpool forest-dev tests devnet wallet timeout-minutes: ${{ fromJSON(env.SCRIPT_TIMEOUT_MINUTES) }} - name: Dump docker logs diff --git a/mise.toml b/mise.toml index 33ba3156f3e5..5d84984a61f7 100644 --- a/mise.toml +++ b/mise.toml @@ -236,7 +236,8 @@ pushd scripts/devnet popd source ./scripts/devnet/wallet_harness.sh devnet_wallet_env_init -forest-dev tests devnet mpool +# mpool test temporarily disabled on devnet; only the wallet suite runs for now. +# forest-dev tests devnet mpool forest-dev tests devnet wallet ''' diff --git a/scripts/devnet/wallet_harness.sh b/scripts/devnet/wallet_harness.sh index 49aa502d63a0..f354a2238f29 100644 --- a/scripts/devnet/wallet_harness.sh +++ b/scripts/devnet/wallet_harness.sh @@ -1,50 +1,62 @@ #!/bin/bash -# Helpers for running the wallet/mpool integration suite against the local -# docker devnet. Meant to be sourced (not executed) after the devnet is up -# (see `setup.sh`) and synced (see `check.sh`). +# Sourced (not executed) helpers for the wallet/mpool suite on the docker +# devnet. Run after the devnet is up (`setup.sh`) and synced (`check.sh`). # -# The wallet tests are chain-agnostic and only need: -# - `FULLNODE_API_INFO` -> RPC endpoint + admin token -# - `FOREST_TEST_PRELOADED_ADDRESS`-> a funded sender address -# -# On the devnet, the funded sender is the genesis miner owner, whose key lives -# in the shared lotus volume at -# `${LOTUS_DATA_DIR}/genesis-sectors/pre-seal-${MINER_ACTOR_ADDRESS}.key`. +# The genesis key is the Lotus miner's default wallet, so using it as the test +# sender causes nonce contention. We fund a dedicated wallet instead. -# Path to the directory containing this script. WALLET_HARNESS_PARENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) source "${WALLET_HARNESS_PARENT_PATH}/.env" -# Allow overriding the binaries, mirroring `scripts/tests/harness.sh`. export FOREST_CLI_PATH="${FOREST_CLI_PATH:-forest-cli}" export FOREST_WALLET_PATH="${FOREST_WALLET_PATH:-forest-wallet}" +export DEVNET_TEST_FUND_AMT="${DEVNET_TEST_FUND_AMT:-100 FIL}" -# Wire up the host environment so `forest-cli`/`forest-wallet`/`forest-dev` -# talk to the dockerized Forest node and have access to the funded genesis key. function devnet_wallet_env_init { set -euo pipefail - # Admin token is written by the Forest container on startup. local token token=$(docker exec forest cat "${FOREST_DATA_DIR}/token.jwt") export FULLNODE_API_INFO="${token}:/ip4/127.0.0.1/tcp/${FOREST_RPC_PORT}/http" - # Extract the funded genesis key from the running container (it mounts the - # shared lotus volume) onto the host so we can import it locally. - local key_path="${TMPDIR:-/tmp}/devnet_preloaded_wallet.key" - docker cp "forest:${LOTUS_DATA_DIR}/genesis-sectors/pre-seal-${MINER_ACTOR_ADDRESS}.key" "${key_path}" - - # Import into both keystores (local file + node-managed remote) so the - # `Backend::Local` and `Backend::Remote` test variants both work. - FOREST_TEST_PRELOADED_ADDRESS="$(${FOREST_WALLET_PATH} import "${key_path}")" - export FOREST_TEST_PRELOADED_ADDRESS - ${FOREST_WALLET_PATH} --remote-wallet import "${key_path}" || true + # Derive the genesis address via a throwaway keystore (`XDG_DATA_HOME`) so it + # never lands in the real keystores. + local genesis_key_path="${TMPDIR:-/tmp}/devnet_genesis_wallet.key" + docker cp "forest:${LOTUS_DATA_DIR}/genesis-sectors/pre-seal-${MINER_ACTOR_ADDRESS}.key" "${genesis_key_path}" + local genesis_addr + genesis_addr="$(XDG_DATA_HOME="$(mktemp -d)" ${FOREST_WALLET_PATH} import "${genesis_key_path}")" + + # Fresh sender the miner never touches; mirror to the remote keystore so both + # `Backend::Local` and `Backend::Remote` variants work. + local test_addr test_key_path + test_addr="$(${FOREST_WALLET_PATH} new)" + test_key_path="${TMPDIR:-/tmp}/devnet_test_wallet.key" + ${FOREST_WALLET_PATH} export "${test_addr}" > "${test_key_path}" + ${FOREST_WALLET_PATH} --remote-wallet import "${test_key_path}" + export FOREST_TEST_PRELOADED_ADDRESS="${test_addr}" + + # Fund it from the genesis key (the node holds it, so it can sign). + ${FOREST_WALLET_PATH} --remote-wallet send --from "${genesis_addr}" "${test_addr}" "${DEVNET_TEST_FUND_AMT}" echo "Devnet wallet env initialised:" echo " FULLNODE_API_INFO=:/ip4/127.0.0.1/tcp/${FOREST_RPC_PORT}/http" echo " FOREST_TEST_PRELOADED_ADDRESS=${FOREST_TEST_PRELOADED_ADDRESS}" + echo " Funding ${test_addr} with ${DEVNET_TEST_FUND_AMT} from ${genesis_addr}..." + + # Wait for the funding transfer to mine. + local attempt balance + for attempt in $(seq 1 120); do + balance="$(${FOREST_WALLET_PATH} --remote-wallet balance "${test_addr}" --exact-balance)" + if [[ "${balance}" != "0 FIL" ]]; then + break + fi + sleep 5 + done + if [[ "${balance}" == "0 FIL" ]]; then + echo "ERROR: dedicated test wallet ${test_addr} was not funded in time" >&2 + return 1 + fi - # Sanity checks: the node is reachable and the preloaded address is funded. ${FOREST_CLI_PATH} chain head - ${FOREST_WALLET_PATH} --remote-wallet balance "${FOREST_TEST_PRELOADED_ADDRESS}" --exact-balance + echo " balance=${balance}" } diff --git a/src/dev/subcommands/tests_cmd/calibnet/helpers.rs b/src/dev/subcommands/tests_cmd/calibnet/helpers.rs index 9c61ced2d922..fc8da040c49d 100644 --- a/src/dev/subcommands/tests_cmd/calibnet/helpers.rs +++ b/src/dev/subcommands/tests_cmd/calibnet/helpers.rs @@ -297,6 +297,8 @@ pub async fn rpc_call(method: &str, params: Value) -> anyhow::Result { /// Extract a CID string from either a Lotus `{ "/": "bafy..." }` map or a /// plain string. +// Only used by `market_add_balance_message_on_chain`, which is temporarily disabled. +#[allow(dead_code)] pub fn cid_from_lotus_json_result(result: &Value) -> anyhow::Result { if let Some(s) = result.as_str() { return Ok(s.to_string()); diff --git a/src/dev/subcommands/tests_cmd/calibnet/wallet.rs b/src/dev/subcommands/tests_cmd/calibnet/wallet.rs index 1a954fe5eaaa..ef578063a971 100644 --- a/src/dev/subcommands/tests_cmd/calibnet/wallet.rs +++ b/src/dev/subcommands/tests_cmd/calibnet/wallet.rs @@ -28,10 +28,11 @@ fn tests() -> Vec { block_on(export_import_roundtrip(Backend::Remote)); Ok(()) }), - Trial::test("market_add_balance_message_on_chain", || { - block_on(market_add_balance_message_on_chain()); - Ok(()) - }), + // TODO: temporarily disabled on devnet; re-enable once investigated. + // Trial::test("market_add_balance_message_on_chain", || { + // block_on(market_add_balance_message_on_chain()); + // Ok(()) + // }), Trial::test("send_to_filecoin_address_local", || { block_on(send_to_filecoin_address(Backend::Local)); Ok(()) @@ -91,6 +92,8 @@ async fn export_import_roundtrip(backend: Backend) { ); } +// Temporarily unused: the test trial in `tests()` is disabled for now. +#[allow(dead_code)] async fn market_add_balance_message_on_chain() { const ATTO_FIL: &str = "23"; let result = rpc_call( From 50561b108f72158532061be03e9c00860e26b34e Mon Sep 17 00:00:00 2001 From: Shashank Date: Thu, 2 Jul 2026 03:06:06 +0530 Subject: [PATCH 03/10] increase timeout --- src/dev/subcommands/tests_cmd/calibnet/helpers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dev/subcommands/tests_cmd/calibnet/helpers.rs b/src/dev/subcommands/tests_cmd/calibnet/helpers.rs index e2e0d982fb6a..22faa8bf3251 100644 --- a/src/dev/subcommands/tests_cmd/calibnet/helpers.rs +++ b/src/dev/subcommands/tests_cmd/calibnet/helpers.rs @@ -136,7 +136,7 @@ fn send_from_and_maybe_wait( ) -> anyhow::Result { let mut args = vec!["send", to, amount, "--from", from]; if wait { - args.extend(["--wait-confidence", "0", "--wait-timeout", "1m"]); + args.extend(["--wait-confidence", "0", "--wait-timeout", "10m"]); } let mut attempt = 1; loop { From a12580cb71e567edeb9448a96753e816c5ed359d Mon Sep 17 00:00:00 2001 From: Shashank Date: Thu, 2 Jul 2026 08:35:48 +0530 Subject: [PATCH 04/10] fix search msg --- src/dev/subcommands/tests_cmd/calibnet/helpers.rs | 2 -- src/dev/subcommands/tests_cmd/calibnet/wallet.rs | 11 ++++------- src/state_manager/message_search.rs | 9 +++++---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/dev/subcommands/tests_cmd/calibnet/helpers.rs b/src/dev/subcommands/tests_cmd/calibnet/helpers.rs index 22faa8bf3251..18e03eab7cdb 100644 --- a/src/dev/subcommands/tests_cmd/calibnet/helpers.rs +++ b/src/dev/subcommands/tests_cmd/calibnet/helpers.rs @@ -312,8 +312,6 @@ pub async fn rpc_call(method: &str, params: Value) -> anyhow::Result { /// Extract a CID string from either a Lotus `{ "/": "bafy..." }` map or a /// plain string. -// Only used by `market_add_balance_message_on_chain`, which is temporarily disabled. -#[allow(dead_code)] pub fn cid_from_lotus_json_result(result: &Value) -> anyhow::Result { if let Some(s) = result.as_str() { return Ok(s.to_string()); diff --git a/src/dev/subcommands/tests_cmd/calibnet/wallet.rs b/src/dev/subcommands/tests_cmd/calibnet/wallet.rs index ef578063a971..1a954fe5eaaa 100644 --- a/src/dev/subcommands/tests_cmd/calibnet/wallet.rs +++ b/src/dev/subcommands/tests_cmd/calibnet/wallet.rs @@ -28,11 +28,10 @@ fn tests() -> Vec { block_on(export_import_roundtrip(Backend::Remote)); Ok(()) }), - // TODO: temporarily disabled on devnet; re-enable once investigated. - // Trial::test("market_add_balance_message_on_chain", || { - // block_on(market_add_balance_message_on_chain()); - // Ok(()) - // }), + Trial::test("market_add_balance_message_on_chain", || { + block_on(market_add_balance_message_on_chain()); + Ok(()) + }), Trial::test("send_to_filecoin_address_local", || { block_on(send_to_filecoin_address(Backend::Local)); Ok(()) @@ -92,8 +91,6 @@ async fn export_import_roundtrip(backend: Backend) { ); } -// Temporarily unused: the test trial in `tests()` is disabled for now. -#[allow(dead_code)] async fn market_add_balance_message_on_chain() { const ATTO_FIL: &str = "23"; let result = rpc_call( diff --git a/src/state_manager/message_search.rs b/src/state_manager/message_search.rs index 4c67bf517035..91eba63269e7 100644 --- a/src/state_manager/message_search.rs +++ b/src/state_manager/message_search.rs @@ -104,10 +104,11 @@ impl StateManager { || (current_actor_state.sequence > message_sequence && parent_actor_state.as_ref().unwrap().sequence <= message_sequence) { - let receipt = self - .tipset_executed_message(¤t, message, allow_replaced)? - .context("Failed to get receipt with tipset_executed_message")?; - return Ok(Some((current, receipt))); + if let Some(receipt) = + self.tipset_executed_message(¤t, message, allow_replaced)? + { + return Ok(Some((current, receipt))); + } } if let Some(parent_actor_state) = parent_actor_state { From f594e268b921128186e4b5ef53d5905b9d79f469 Mon Sep 17 00:00:00 2001 From: Shashank Date: Thu, 2 Jul 2026 09:04:42 +0530 Subject: [PATCH 05/10] lint fix --- src/state_manager/message_search.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/state_manager/message_search.rs b/src/state_manager/message_search.rs index 91eba63269e7..f3ae7533754b 100644 --- a/src/state_manager/message_search.rs +++ b/src/state_manager/message_search.rs @@ -100,15 +100,13 @@ impl StateManager { .get_actor(&message_from_id, *parent_tipset.parent_state()) .map_err(|e| Error::State(e.to_string()))?; - if parent_actor_state.is_none() + if (parent_actor_state.is_none() || (current_actor_state.sequence > message_sequence - && parent_actor_state.as_ref().unwrap().sequence <= message_sequence) - { - if let Some(receipt) = + && parent_actor_state.as_ref().unwrap().sequence <= message_sequence)) + && let Some(receipt) = self.tipset_executed_message(¤t, message, allow_replaced)? - { - return Ok(Some((current, receipt))); - } + { + return Ok(Some((current, receipt))); } if let Some(parent_actor_state) = parent_actor_state { From 076821eb6bb0f7ad1c6500d3ca53773c5dc94ebd Mon Sep 17 00:00:00 2001 From: Shashank Date: Thu, 2 Jul 2026 09:05:03 +0530 Subject: [PATCH 06/10] mpool test devnet --- .github/workflows/forest.yml | 3 +-- mise.toml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/forest.yml b/.github/workflows/forest.yml index c2893900d1cf..8aa134835b8a 100644 --- a/.github/workflows/forest.yml +++ b/.github/workflows/forest.yml @@ -488,8 +488,7 @@ jobs: run: | source ./scripts/devnet/wallet_harness.sh devnet_wallet_env_init - # mpool test temporarily disabled on devnet; only the wallet suite runs for now. - # forest-dev tests devnet mpool + forest-dev tests devnet mpool forest-dev tests devnet wallet timeout-minutes: ${{ fromJSON(env.SCRIPT_TIMEOUT_MINUTES) }} - name: Dump docker logs diff --git a/mise.toml b/mise.toml index 5d84984a61f7..33ba3156f3e5 100644 --- a/mise.toml +++ b/mise.toml @@ -236,8 +236,7 @@ pushd scripts/devnet popd source ./scripts/devnet/wallet_harness.sh devnet_wallet_env_init -# mpool test temporarily disabled on devnet; only the wallet suite runs for now. -# forest-dev tests devnet mpool +forest-dev tests devnet mpool forest-dev tests devnet wallet ''' From 93ecf3dfca6c5e632c7ebc5bc5fbffd8a053843d Mon Sep 17 00:00:00 2001 From: Shashank Date: Thu, 2 Jul 2026 11:12:51 +0530 Subject: [PATCH 07/10] cleanup --- .github/workflows/forest.yml | 8 +++++++- src/dev/subcommands/tests_cmd.rs | 8 ++++---- .../subcommands/tests_cmd/{calibnet.rs => shared.rs} | 10 +++++----- .../tests_cmd/{calibnet => shared}/helpers.rs | 2 +- .../tests_cmd/{calibnet => shared}/mpool.rs | 12 ++++++------ .../tests_cmd/{calibnet => shared}/wallet.rs | 6 +++--- 6 files changed, 26 insertions(+), 20 deletions(-) rename src/dev/subcommands/tests_cmd/{calibnet.rs => shared.rs} (65%) rename src/dev/subcommands/tests_cmd/{calibnet => shared}/helpers.rs (99%) rename src/dev/subcommands/tests_cmd/{calibnet => shared}/mpool.rs (85%) rename src/dev/subcommands/tests_cmd/{calibnet => shared}/wallet.rs (98%) diff --git a/.github/workflows/forest.yml b/.github/workflows/forest.yml index 8aa134835b8a..f3e51098b614 100644 --- a/.github/workflows/forest.yml +++ b/.github/workflows/forest.yml @@ -225,7 +225,14 @@ jobs: - name: Migration Regression Tests run: ./scripts/tests/calibnet_migration_regression_tests.sh timeout-minutes: ${{ fromJSON(env.SCRIPT_TIMEOUT_MINUTES) }} + calibnet-wallet-check-no-ops: + name: Wallet tests + runs-on: ubuntu-slim + if: ${{ !contains(github.event.pull_request.labels.*.name, 'Wallet') }} + steps: + - run: echo "No-op job to trigger the required wallet tests." calibnet-wallet-check: + if: ${{ contains(github.event.pull_request.labels.*.name, 'Wallet') }} needs: - build-ubuntu name: Wallet tests @@ -585,7 +592,6 @@ jobs: - calibnet-stateless-mode-check - calibnet-stateless-rpc-check - state-migrations-check - - calibnet-wallet-check - calibnet-no-discovery-checks - calibnet-kademlia-checks - calibnet-eth-mapping-check diff --git a/src/dev/subcommands/tests_cmd.rs b/src/dev/subcommands/tests_cmd.rs index 342f42ef2a53..a42a93ce5513 100644 --- a/src/dev/subcommands/tests_cmd.rs +++ b/src/dev/subcommands/tests_cmd.rs @@ -1,17 +1,17 @@ // Copyright 2019-2026 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -mod calibnet; +mod shared; /// Integration tests #[derive(Debug, clap::Subcommand)] pub enum TestsCommand { #[command(subcommand)] - Calibnet(calibnet::CalibnetTestsCommand), + Calibnet(shared::TestCommand), /// Run the wallet/mpool integration suite against a local devnet. The tests - /// themselves are chain-agnostic, so the calibnet suite is reused. + /// themselves are chain-agnostic, so the same suite is reused. #[command(subcommand)] - Devnet(calibnet::CalibnetTestsCommand), + Devnet(shared::TestCommand), } impl TestsCommand { diff --git a/src/dev/subcommands/tests_cmd/calibnet.rs b/src/dev/subcommands/tests_cmd/shared.rs similarity index 65% rename from src/dev/subcommands/tests_cmd/calibnet.rs rename to src/dev/subcommands/tests_cmd/shared.rs index 83dd5a7c8b43..cce34f702813 100644 --- a/src/dev/subcommands/tests_cmd/calibnet.rs +++ b/src/dev/subcommands/tests_cmd/shared.rs @@ -5,14 +5,14 @@ mod helpers; mod mpool; mod wallet; -/// Calibnet integration tests +/// Shared integration tests (used by both calibnet and devnet) #[derive(Debug, clap::Subcommand)] -pub enum CalibnetTestsCommand { - Wallet(wallet::CalibnetWalletTestCommand), - Mpool(mpool::CalibnetMpoolTestCommand), +pub enum TestCommand { + Wallet(wallet::WalletTestCommand), + Mpool(mpool::MpoolTestCommand), } -impl CalibnetTestsCommand { +impl TestCommand { pub async fn run(self) -> anyhow::Result<()> { match self { Self::Wallet(cmd) => cmd.run().await, diff --git a/src/dev/subcommands/tests_cmd/calibnet/helpers.rs b/src/dev/subcommands/tests_cmd/shared/helpers.rs similarity index 99% rename from src/dev/subcommands/tests_cmd/calibnet/helpers.rs rename to src/dev/subcommands/tests_cmd/shared/helpers.rs index 18e03eab7cdb..17530d297640 100644 --- a/src/dev/subcommands/tests_cmd/calibnet/helpers.rs +++ b/src/dev/subcommands/tests_cmd/shared/helpers.rs @@ -27,7 +27,7 @@ pub const FIL_AMT: &str = "500 atto FIL"; /// Sentinel `forest-wallet balance --exact-balance` returns for an unfunded address. pub const FIL_ZERO: &str = "0 FIL"; /// Amount to seed a freshly-created delegated wallet. -pub const DELEGATE_FUND_AMT: &str = "3 micro FIL"; +pub const DELEGATE_FUND_AMT: &str = "30 micro FIL"; /// Maximum time to wait for a polled condition before failing the test. pub const POLL_TIMEOUT: Duration = Duration::from_secs(600); diff --git a/src/dev/subcommands/tests_cmd/calibnet/mpool.rs b/src/dev/subcommands/tests_cmd/shared/mpool.rs similarity index 85% rename from src/dev/subcommands/tests_cmd/calibnet/mpool.rs rename to src/dev/subcommands/tests_cmd/shared/mpool.rs index 9eedaf88e598..a080f1e345cf 100644 --- a/src/dev/subcommands/tests_cmd/calibnet/mpool.rs +++ b/src/dev/subcommands/tests_cmd/shared/mpool.rs @@ -1,20 +1,20 @@ // Copyright 2019-2026 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -//! Calibnet mpool CLI integration tests (shared preloaded address). +//! Mpool CLI integration tests (shared preloaded address). //! -//! Run via [`calibnet_wallet_mpool`] before [`calibnet_wallet`]; see `mise test:wallet`. -//! Each test assumes the same environment as [`calibnet_wallet`]. +//! Run the mpool suite before the wallet suite; see `mise test:wallet`. +//! Each test assumes the same environment as the wallet suite. use super::helpers::*; use libtest_mimic::{Arguments, Failed, Trial}; use std::time::Duration; -/// Calibnet mpool integration tests +/// Mpool integration tests #[derive(Debug, clap::Args)] -pub struct CalibnetMpoolTestCommand {} +pub struct MpoolTestCommand {} -impl CalibnetMpoolTestCommand { +impl MpoolTestCommand { pub async fn run(self) -> anyhow::Result<()> { let args = Arguments { test_threads: Some(8), diff --git a/src/dev/subcommands/tests_cmd/calibnet/wallet.rs b/src/dev/subcommands/tests_cmd/shared/wallet.rs similarity index 98% rename from src/dev/subcommands/tests_cmd/calibnet/wallet.rs rename to src/dev/subcommands/tests_cmd/shared/wallet.rs index 1a954fe5eaaa..989c662b58dc 100644 --- a/src/dev/subcommands/tests_cmd/calibnet/wallet.rs +++ b/src/dev/subcommands/tests_cmd/shared/wallet.rs @@ -4,11 +4,11 @@ use super::helpers::*; use libtest_mimic::{Arguments, Trial}; -/// Calibnet wallet integration tests +/// Wallet integration tests #[derive(Debug, clap::Args)] -pub struct CalibnetWalletTestCommand {} +pub struct WalletTestCommand {} -impl CalibnetWalletTestCommand { +impl WalletTestCommand { pub async fn run(self) -> anyhow::Result<()> { let args = Arguments { test_threads: Some(8), From 1fb6593c35c52424dde0bcb27380f48d71f170e4 Mon Sep 17 00:00:00 2001 From: Shashank Date: Thu, 2 Jul 2026 11:32:15 +0530 Subject: [PATCH 08/10] add mpool nonce fix test --- src/dev/subcommands/tests_cmd/shared/mpool.rs | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/src/dev/subcommands/tests_cmd/shared/mpool.rs b/src/dev/subcommands/tests_cmd/shared/mpool.rs index a080f1e345cf..09e6e08d509d 100644 --- a/src/dev/subcommands/tests_cmd/shared/mpool.rs +++ b/src/dev/subcommands/tests_cmd/shared/mpool.rs @@ -7,8 +7,7 @@ //! Each test assumes the same environment as the wallet suite. use super::helpers::*; -use libtest_mimic::{Arguments, Failed, Trial}; -use std::time::Duration; +use libtest_mimic::{Arguments, Trial}; /// Mpool integration tests #[derive(Debug, clap::Args)] @@ -17,7 +16,7 @@ pub struct MpoolTestCommand {} impl MpoolTestCommand { pub async fn run(self) -> anyhow::Result<()> { let args = Arguments { - test_threads: Some(8), + test_threads: Some(1), ..Default::default() }; libtest_mimic::run(&args, tests()).exit(); @@ -25,33 +24,45 @@ impl MpoolTestCommand { } fn tests() -> Vec { - vec![Trial::test( - "mpool_replace_auto_unblocks_pending", - mpool_replace_auto_unblocks_pending, - )] + vec![ + Trial::test("mpool_nonce_fix_auto_unblocks_pending", || { + block_on(mpool_nonce_fix_auto_unblocks_pending()); + Ok(()) + }), + Trial::test("mpool_replace_auto_unblocks_pending", || { + block_on(mpool_replace_auto_unblocks_pending()); + Ok(()) + }), + ] } -fn mpool_replace_auto_unblocks_pending() -> Result<(), Failed> { - // Retry for 3 times in case race condition happens. - // Chance of having race condition in nonce is low as messages are broadcasted in the network pretty fast - for i in (0..3).rev() { - if i <= 0 { - block_on(mpool_replace_auto_unblocks_pending_async()); - break; - } else if std::panic::catch_unwind(|| block_on(mpool_replace_auto_unblocks_pending_async())) - .is_err() - { - // Retry after 5s on error - std::thread::sleep(Duration::from_secs(5)); - } else { - // Succeeded - break; - } - } - Ok(()) +async fn mpool_nonce_fix_auto_unblocks_pending() { + let addr = FOREST_TEST_PRELOADED_ADDRESS.as_str(); + let nonce = mpool_nonce(addr).unwrap(); + // Skip one nonce so `--auto` has a gap to fill. + let next_nonce = nonce + 1; + forest_cli(&[ + "mpool", + "nonce-fix", + "--addr", + addr, + "--start", + &next_nonce.to_string(), + "--end", + &(next_nonce + 1).to_string(), + ]) + .unwrap(); + poll_until_pending_nonce(addr, next_nonce).await.unwrap(); + + forest_cli(&["mpool", "nonce-fix", "--addr", addr, "--auto"]).unwrap(); + + assert!( + poll_until_pending_nonce(addr, nonce).await.is_ok(), + "nonce-fix --auto should fill nonce gap at {nonce} for {addr}." + ); } -async fn mpool_replace_auto_unblocks_pending_async() { +async fn mpool_replace_auto_unblocks_pending() { let addr = FOREST_TEST_PRELOADED_ADDRESS.as_str(); let nonce = mpool_nonce(addr).unwrap(); From 126bb594405a46fd2d91ef034b5dbe898337e394 Mon Sep 17 00:00:00 2001 From: Shashank Date: Thu, 2 Jul 2026 13:42:01 +0530 Subject: [PATCH 09/10] address review comments --- .github/workflows/forest.yml | 1 + scripts/devnet/wallet_harness.sh | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/forest.yml b/.github/workflows/forest.yml index f3e51098b614..5f81a6b87cbf 100644 --- a/.github/workflows/forest.yml +++ b/.github/workflows/forest.yml @@ -493,6 +493,7 @@ jobs: echo "$GITHUB_WORKSPACE" >> "$GITHUB_PATH" - name: Devnet wallet tests run: | + set -euo pipefail source ./scripts/devnet/wallet_harness.sh devnet_wallet_env_init forest-dev tests devnet mpool diff --git a/scripts/devnet/wallet_harness.sh b/scripts/devnet/wallet_harness.sh index f354a2238f29..8b61aff94ac9 100644 --- a/scripts/devnet/wallet_harness.sh +++ b/scripts/devnet/wallet_harness.sh @@ -13,8 +13,6 @@ export FOREST_WALLET_PATH="${FOREST_WALLET_PATH:-forest-wallet}" export DEVNET_TEST_FUND_AMT="${DEVNET_TEST_FUND_AMT:-100 FIL}" function devnet_wallet_env_init { - set -euo pipefail - local token token=$(docker exec forest cat "${FOREST_DATA_DIR}/token.jwt") export FULLNODE_API_INFO="${token}:/ip4/127.0.0.1/tcp/${FOREST_RPC_PORT}/http" @@ -46,13 +44,13 @@ function devnet_wallet_env_init { # Wait for the funding transfer to mine. local attempt balance for attempt in $(seq 1 120); do - balance="$(${FOREST_WALLET_PATH} --remote-wallet balance "${test_addr}" --exact-balance)" - if [[ "${balance}" != "0 FIL" ]]; then + balance="$(${FOREST_WALLET_PATH} --remote-wallet balance "${test_addr}" --exact-balance)" || balance="" + if [[ -n "${balance}" && "${balance}" != "0 FIL" ]]; then break fi sleep 5 done - if [[ "${balance}" == "0 FIL" ]]; then + if [[ -z "${balance}" || "${balance}" == "0 FIL" ]]; then echo "ERROR: dedicated test wallet ${test_addr} was not funded in time" >&2 return 1 fi From 52a230a422034077a5ad06e0a23c42acb042d411 Mon Sep 17 00:00:00 2001 From: Shashank Date: Thu, 2 Jul 2026 14:30:10 +0530 Subject: [PATCH 10/10] cleanup --- scripts/devnet/wallet_harness.sh | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/scripts/devnet/wallet_harness.sh b/scripts/devnet/wallet_harness.sh index 8b61aff94ac9..14b7fc84701b 100644 --- a/scripts/devnet/wallet_harness.sh +++ b/scripts/devnet/wallet_harness.sh @@ -17,9 +17,11 @@ function devnet_wallet_env_init { token=$(docker exec forest cat "${FOREST_DATA_DIR}/token.jwt") export FULLNODE_API_INFO="${token}:/ip4/127.0.0.1/tcp/${FOREST_RPC_PORT}/http" + local tmp="${TMPDIR:-/tmp}" + # Derive the genesis address via a throwaway keystore (`XDG_DATA_HOME`) so it # never lands in the real keystores. - local genesis_key_path="${TMPDIR:-/tmp}/devnet_genesis_wallet.key" + local genesis_key_path="${tmp}/devnet_genesis_wallet.key" docker cp "forest:${LOTUS_DATA_DIR}/genesis-sectors/pre-seal-${MINER_ACTOR_ADDRESS}.key" "${genesis_key_path}" local genesis_addr genesis_addr="$(XDG_DATA_HOME="$(mktemp -d)" ${FOREST_WALLET_PATH} import "${genesis_key_path}")" @@ -28,28 +30,22 @@ function devnet_wallet_env_init { # `Backend::Local` and `Backend::Remote` variants work. local test_addr test_key_path test_addr="$(${FOREST_WALLET_PATH} new)" - test_key_path="${TMPDIR:-/tmp}/devnet_test_wallet.key" + test_key_path="${tmp}/devnet_test_wallet.key" ${FOREST_WALLET_PATH} export "${test_addr}" > "${test_key_path}" ${FOREST_WALLET_PATH} --remote-wallet import "${test_key_path}" export FOREST_TEST_PRELOADED_ADDRESS="${test_addr}" - # Fund it from the genesis key (the node holds it, so it can sign). - ${FOREST_WALLET_PATH} --remote-wallet send --from "${genesis_addr}" "${test_addr}" "${DEVNET_TEST_FUND_AMT}" - echo "Devnet wallet env initialised:" echo " FULLNODE_API_INFO=:/ip4/127.0.0.1/tcp/${FOREST_RPC_PORT}/http" echo " FOREST_TEST_PRELOADED_ADDRESS=${FOREST_TEST_PRELOADED_ADDRESS}" echo " Funding ${test_addr} with ${DEVNET_TEST_FUND_AMT} from ${genesis_addr}..." - # Wait for the funding transfer to mine. - local attempt balance - for attempt in $(seq 1 120); do - balance="$(${FOREST_WALLET_PATH} --remote-wallet balance "${test_addr}" --exact-balance)" || balance="" - if [[ -n "${balance}" && "${balance}" != "0 FIL" ]]; then - break - fi - sleep 5 - done + # Fund it from the genesis key and wait for the transfer. + ${FOREST_WALLET_PATH} --remote-wallet send --wait-confidence 0 --wait-timeout 10m \ + --from "${genesis_addr}" "${test_addr}" "${DEVNET_TEST_FUND_AMT}" + + local balance + balance="$(${FOREST_WALLET_PATH} --remote-wallet balance "${test_addr}" --exact-balance)" || balance="" if [[ -z "${balance}" || "${balance}" == "0 FIL" ]]; then echo "ERROR: dedicated test wallet ${test_addr} was not funded in time" >&2 return 1