Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions src/rpc/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ impl ServerError {
Some("This method is not supported by the current version of the Forest node".into()),
)
}

pub fn inner(&self) -> &ErrorObjectOwned {
&self.inner
}
}

impl<E: std::error::Error + RpcErrorData + 'static> From<E> for ServerError {
Expand Down
5 changes: 5 additions & 0 deletions src/tool/subcommands/api_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ pub enum ApiCommands {
/// only when the response from Forest is fixed and matches the response from Lotus.
#[arg(long)]
use_response_from: Option<NodeType>,
/// Allow generating snapshot even if the test fails.
#[arg(long, default_value_t = false)]
allow_failure: bool,
},
/// Dumps RPC test cases for a specified API path.
///
Expand Down Expand Up @@ -272,6 +275,7 @@ impl ApiCommands {
chain,
out_dir,
use_response_from,
allow_failure,
} => {
unsafe { std::env::set_var("FOREST_TIPSET_CACHE_DISABLED", "1") };
if !out_dir.is_dir() {
Expand All @@ -296,6 +300,7 @@ impl ApiCommands {
tracking_db.clone(),
&chain,
allow_response_mismatch,
allow_failure,
)
.await
{
Expand Down
50 changes: 45 additions & 5 deletions src/tool/subcommands/api_cmd/api_compare_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ pub(super) enum PolicyOnRejected {
Fail,
Pass,
PassWithIdenticalError,
PassWithIdenticalErrorCaseInsensitive,
/// If Forest reason is a subset of Lotus reason, the test passes.
/// We don't always bubble up errors and format the error chain like Lotus.
PassWithQuasiIdenticalError,
Expand Down Expand Up @@ -1138,6 +1139,27 @@ fn state_tests_with_tipset<DB: Blockstore>(
}

fn wallet_tests(worker_address: Option<Address>) -> Vec<RpcTest> {
let prefunded_wallets = [
// the following addresses should have 666 attoFIL each
Address::from_str("t0168923").unwrap(), // this is the ID address of the `t1w2zb5a723izlm4q3khclsjcnapfzxcfhvqyfoly` address
Address::from_str("t1w2zb5a723izlm4q3khclsjcnapfzxcfhvqyfoly").unwrap(),
Address::from_str("t2nfplhzpyeck5dcc4fokj5ar6nbs3mhbdmq6xu3q").unwrap(),
Address::from_str("t3wmbvnabsj6x2uki33phgtqqemmunnttowpx3chklrchy76pv52g5ajnaqdypxoomq5ubfk65twl5ofvkhshq").unwrap(),
Address::from_str("t410fx2cumi6pgaz64varl77xbuub54bgs3k5xsvn3ki").unwrap(),
// This address should have 0 FIL
Address::from_str("t1qb2x5qctp34rxd7ucl327h5ru6aazj2heno7x5y").unwrap(),
];

let mut tests = vec![];
for wallet in prefunded_wallets {
tests.push(RpcTest::identity(
WalletBalance::request((wallet,)).unwrap(),
));
tests.push(RpcTest::identity(
WalletValidateAddress::request((wallet.to_string(),)).unwrap(),
));
}

let known_wallet = *KNOWN_CALIBNET_ADDRESS;
// "Hello world!" signed with the above address:
let signature = "44364ca78d85e53dda5ac6f719a4f2de3261c17f58558ab7730f80c478e6d43775244e7d6855afad82e4a1fd6449490acfa88e3fcfe7c1fe96ed549c100900b400";
Expand All @@ -1149,11 +1171,26 @@ fn wallet_tests(worker_address: Option<Address>) -> Vec<RpcTest> {
_ => panic!("Invalid signature (must be bls or secp256k1)"),
};

let mut tests = vec![
RpcTest::identity(WalletBalance::request((known_wallet,)).unwrap()),
RpcTest::identity(WalletValidateAddress::request((known_wallet.to_string(),)).unwrap()),
RpcTest::identity(WalletVerify::request((known_wallet, text, signature)).unwrap()),
];
tests.push(RpcTest::identity(
WalletBalance::request((known_wallet,)).unwrap(),
));
tests.push(RpcTest::identity(
WalletValidateAddress::request((known_wallet.to_string(),)).unwrap(),
));
tests.push(
RpcTest::identity(
// Both Forest and Lotus should fail miserably at invocking Cthulhu's name
WalletValidateAddress::request((
"Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn".to_string(),
))
.unwrap(),
)
// Forest returns `Unknown address network`, Lotus `unknown address network`.
.policy_on_rejected(PolicyOnRejected::PassWithIdenticalErrorCaseInsensitive),
);
tests.push(RpcTest::identity(
WalletVerify::request((known_wallet, text, signature)).unwrap(),
));

// If a worker address is provided, we can test wallet methods requiring
// a shared key.
Expand Down Expand Up @@ -3046,6 +3083,9 @@ fn evaluate_test_success(
match test.policy_on_rejected {
PolicyOnRejected::Pass => true,
PolicyOnRejected::PassWithIdenticalError => reason_forest == reason_lotus,
PolicyOnRejected::PassWithIdenticalErrorCaseInsensitive => {
reason_forest.to_lowercase() == reason_lotus.to_lowercase()
}
PolicyOnRejected::PassWithQuasiIdenticalError => {
reason_lotus.contains(reason_forest) || reason_forest.contains(reason_lotus)
}
Expand Down
22 changes: 16 additions & 6 deletions src/tool/subcommands/api_cmd/generate_test_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub async fn run_test_with_dump(
db: Arc<ReadOpsTrackingStore<ManyCar<ParityDb>>>,
chain: &NetworkChain,
allow_response_mismatch: bool,
allow_failure: bool,
) -> anyhow::Result<()> {
if chain.is_testnet() {
CurrentNetwork::set_global(Network::Testnet);
Expand All @@ -45,12 +46,21 @@ pub async fn run_test_with_dump(
($ty:ty) => {
if test_dump.request.method_name.as_ref() == <$ty>::NAME {
let params = <$ty>::parse_params(params_raw.clone(), ParamStructure::Either)?;
let result = <$ty>::handle(ctx.clone(), params).await?;
anyhow::ensure!(
allow_response_mismatch
|| test_dump.forest_response == Ok(result.into_lotus_json_value()?),
"Response mismatch between Forest and Lotus"
);
match <$ty>::handle(ctx.clone(), params).await {
Ok(result) => {
anyhow::ensure!(
allow_response_mismatch
|| test_dump.forest_response == Ok(result.into_lotus_json_value()?),
"Response mismatch between Forest and Lotus"
);
}
Err(_) if allow_failure => {
// If we allow failure, we do not check the error
}
Err(e) => {
bail!("Error running test {}: {}", <$ty>::NAME, e);
}
}
run = true;
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/tool/subcommands/api_cmd/test_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub async fn run_test_from_snapshot(path: &Path) -> anyhow::Result<()> {
let result = <$ty>::handle(ctx.clone(), params)
.await
.map(|r| r.into_lotus_json())
.map_err(|e| e.to_string());
.map_err(|e| e.inner().to_string());
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is needed, otherwise the error is wrapped and the comparison fails:

❯ forest-tool api test rpc-snaps/filecoin_walletvalidateaddress_1755692429792245.rpcsnap.json
2025-08-20T13:18:54.548741Z  INFO forest::genesis: Initialized genesis: bafy2bzacecyaggy24wol5ruvs6qm73gjibs2l2iyhcqmvi7r7a4ph7zx3yqd4

thread 'main' panicked at /home/rumcajs/prj/forest3/src/tool/subcommands/api_cmd/test_snapshot.rs:120:5:
assertion `left == right` failed
  left: Err("JSON-RPC error:\n\tcode: -32603\n\tmessage: Unknown address network\n")
 right: Err("ErrorObject { code: InternalError, message: \"Unknown address network\", data: None }")
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
fish: Job 1, 'forest-tool api test rpc-snaps/…' terminated by signal SIGABRT (Abort)

let expected = match expected_response.clone() {
Ok(v) => serde_json::from_value(v).map_err(|e| e.to_string()),
Err(e) => Err(e),
Expand Down
15 changes: 15 additions & 0 deletions src/tool/subcommands/api_cmd/test_snapshots.txt
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,18 @@ filecoin_stateverifiedregistryrootkey_1737546933391747.rpcsnap.json.zst
filecoin_stateverifierstatus_1737546933391932.rpcsnap.json.zst
filecoin_statevmcirculatingsupplyinternal_1737546933391364.rpcsnap.json.zst
filecoin_statewaitmsg_1741784839566802.rpcsnap.json.zst
filecoin_walletbalance_1755692429792122.rpcsnap.json.zst
filecoin_walletbalance_1755692429792131.rpcsnap.json.zst
filecoin_walletbalance_1755692429792140.rpcsnap.json.zst
filecoin_walletbalance_1755692429792149.rpcsnap.json.zst
filecoin_walletbalance_1755692429792157.rpcsnap.json.zst
filecoin_walletbalance_1755692429792168.rpcsnap.json.zst
filecoin_walletvalidateaddress_1755692429792177.rpcsnap.json.zst
filecoin_walletvalidateaddress_1755692429792185.rpcsnap.json.zst
filecoin_walletvalidateaddress_1755692429792193.rpcsnap.json.zst
filecoin_walletvalidateaddress_1755692429792202.rpcsnap.json.zst
filecoin_walletvalidateaddress_1755692429792220.rpcsnap.json.zst
filecoin_walletvalidateaddress_1755692429792228.rpcsnap.json.zst
filecoin_walletvalidateaddress_1755692429792237.rpcsnap.json.zst
filecoin_walletvalidateaddress_1755692429792245.rpcsnap.json.zst
filecoin_walletverify_1755692429792254.rpcsnap.json.zst
3 changes: 0 additions & 3 deletions src/tool/subcommands/api_cmd/test_snapshots_ignored.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ Filecoin.SyncCheckBad
Filecoin.SyncMarkBad
Filecoin.SyncSubmitBlock
Filecoin.Version
Filecoin.WalletBalance
Filecoin.WalletDefaultAddress
Filecoin.WalletDelete
Filecoin.WalletExport
Expand All @@ -78,8 +77,6 @@ Filecoin.WalletNew
Filecoin.WalletSetDefault
Filecoin.WalletSign
Filecoin.WalletSignMessage
Filecoin.WalletValidateAddress
Filecoin.WalletVerify
Filecoin.Web3ClientVersion
Forest.ChainExport
Forest.ChainGetMinBaseFee
Expand Down
Loading