Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
01706ee
devnet wallet test
sudo-shashank Jun 30, 2026
6015fc7
Merge branch 'main' into shashank/devnet-wallet-test
sudo-shashank Jul 1, 2026
efc8a6a
run only wallet test
sudo-shashank Jul 1, 2026
e9ad62c
Merge branch 'main' into shashank/devnet-wallet-test
sudo-shashank Jul 1, 2026
3442082
Merge branch 'main' into shashank/devnet-wallet-test
sudo-shashank Jul 1, 2026
3a69b4b
Merge branch 'main' into shashank/devnet-wallet-test
sudo-shashank Jul 1, 2026
50561b1
increase timeout
sudo-shashank Jul 1, 2026
a12580c
fix search msg
sudo-shashank Jul 2, 2026
f594e26
lint fix
sudo-shashank Jul 2, 2026
076821e
mpool test devnet
sudo-shashank Jul 2, 2026
93ecf3d
cleanup
sudo-shashank Jul 2, 2026
1fb6593
add mpool nonce fix test
sudo-shashank Jul 2, 2026
316ef45
Merge branch 'main' into shashank/devnet-wallet-test
sudo-shashank Jul 2, 2026
126bb59
address review comments
sudo-shashank Jul 2, 2026
3c1d377
Merge branch 'main' into shashank/devnet-wallet-test
sudo-shashank Jul 2, 2026
d52ca23
Merge branch 'main' into shashank/devnet-wallet-test
sudo-shashank Jul 2, 2026
52a230a
cleanup
sudo-shashank Jul 2, 2026
503a432
Merge branch 'main' into shashank/devnet-wallet-test
sudo-shashank Jul 2, 2026
9e5f5f0
Merge branch 'main' into shashank/devnet-wallet-test
sudo-shashank Jul 3, 2026
be296e2
keep TestsCommand simple
sudo-shashank Jul 3, 2026
57d49d3
Add test
sudo-shashank Jul 3, 2026
272a42d
Merge branch 'main' into shashank/devnet-wallet-test
sudo-shashank Jul 3, 2026
eb68e3c
fmt
sudo-shashank Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .github/workflows/forest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -480,6 +487,18 @@ 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: |
set -euo pipefail
source ./scripts/devnet/wallet_harness.sh
devnet_wallet_env_init
forest-dev tests mpool
forest-dev tests wallet
timeout-minutes: ${{ fromJSON(env.SCRIPT_TIMEOUT_MINUTES) }}
- name: Dump docker logs
if: always()
uses: jwalton/gh-docker-logs@v2
Expand Down Expand Up @@ -574,7 +593,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
Expand Down
19 changes: 17 additions & 2 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,23 @@ run = '''
set -euo pipefail
source ./scripts/tests/harness.sh
forest_wallet_init "${usage_preloaded_key?}"
forest-dev tests calibnet mpool
forest-dev tests calibnet wallet
forest-dev tests mpool
forest-dev tests 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 mpool
forest-dev tests wallet
'''

[tasks."codecov:nextest"]
Expand Down
17 changes: 16 additions & 1 deletion scripts/devnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 mpool` and `forest-dev tests wallet`.

## Local devnet development

If you prefer to have Forest running directly on the host, you can comment it
Expand Down
56 changes: 56 additions & 0 deletions scripts/devnet/wallet_harness.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/bash
# 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 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.

WALLET_HARNESS_PARENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
source "${WALLET_HARNESS_PARENT_PATH}/.env"

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}"

function devnet_wallet_env_init {
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"

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="${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="${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}"

echo "Devnet wallet env initialised:"
echo " FULLNODE_API_INFO=<token>:/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}..."

# 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
fi

${FOREST_CLI_PATH} chain head
echo " balance=${balance}"
}
11 changes: 7 additions & 4 deletions src/dev/subcommands/tests_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
// Copyright 2019-2026 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

mod calibnet;
mod helpers;
mod mpool;
mod wallet;

/// Integration tests
#[derive(Debug, clap::Subcommand)]
pub enum TestsCommand {
#[command(subcommand)]
Calibnet(calibnet::CalibnetTestsCommand),
Wallet(wallet::WalletTestCommand),
Mpool(mpool::MpoolTestCommand),
}

impl TestsCommand {
pub async fn run(self) -> anyhow::Result<()> {
match self {
Self::Calibnet(cmd) => cmd.run().await,
Self::Wallet(cmd) => cmd.run().await,
Self::Mpool(cmd) => cmd.run().await,
}
}
}
22 changes: 0 additions & 22 deletions src/dev/subcommands/tests_cmd/calibnet.rs

This file was deleted.

76 changes: 0 additions & 76 deletions src/dev/subcommands/tests_cmd/calibnet/mpool.rs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -136,7 +136,7 @@ fn send_from_and_maybe_wait(
) -> anyhow::Result<String> {
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 {
Expand Down
87 changes: 87 additions & 0 deletions src/dev/subcommands/tests_cmd/mpool.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2019-2026 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

//! Mpool CLI integration tests (shared preloaded address).
//!
//! 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, Trial};

/// Mpool integration tests
#[derive(Debug, clap::Args)]
pub struct MpoolTestCommand {}

impl MpoolTestCommand {
pub async fn run(self) -> anyhow::Result<()> {
let args = Arguments {
test_threads: Some(1),
..Default::default()
};
libtest_mimic::run(&args, tests()).exit();
}
}

fn tests() -> Vec<Trial> {
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(())
}),
]
}

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() {
let addr = FOREST_TEST_PRELOADED_ADDRESS.as_str();
let nonce = mpool_nonce(addr).unwrap();

let cid = send_from_no_wait(addr, addr, FIL_AMT, Backend::Local).unwrap();
poll_until_pending_nonce(addr, nonce).await.unwrap();

forest_cli(&[
"mpool",
"replace",
"--from",
addr,
"--nonce",
&nonce.to_string(),
"--auto",
])
.unwrap();

assert!(
poll_until_state_search_msg(&cid).await.is_ok(),
"mpool replace --auto should replace message {cid} from {addr} at nonce {nonce}."
);
}
Loading
Loading