1use super::{CreateTestsArgs, ReportMode, RunIgnored, TestCriteriaOverride};
5use crate::blocks::{ElectionProof, Ticket, Tipset};
6use crate::chain::ChainStore;
7use crate::db::car::ManyCar;
8use crate::eth::EthChainId as EthChainIdType;
9use crate::lotus_json::HasLotusJson;
10use crate::message::{MessageRead as _, SignedMessage};
11use crate::rpc::auth::AuthNewParams;
12use crate::rpc::beacon::BeaconGetEntry;
13use crate::rpc::eth::{
14 BlockNumberOrHash, EthInt64, Predefined, new_eth_tx_from_signed_message, trace::types::*,
15 types::*,
16};
17use crate::rpc::gas::{GasEstimateGasLimit, GasEstimateMessageGas};
18use crate::rpc::miner::BlockTemplate;
19use crate::rpc::misc::ActorEventFilter;
20use crate::rpc::state::StateGetAllClaims;
21use crate::rpc::types::*;
22use crate::rpc::{ApiPaths, FilterList};
23use crate::rpc::{Permission, prelude::*};
24use crate::shim::actors::MarketActorStateLoad as _;
25use crate::shim::actors::market;
26use crate::shim::executor::Receipt;
27use crate::shim::sector::SectorSize;
28use crate::shim::{
29 address::{Address, Protocol},
30 crypto::Signature,
31 econ::TokenAmount,
32 message::{METHOD_SEND, Message},
33 state_tree::StateTree,
34};
35use crate::state_manager::StateManager;
36use crate::tool::offline_server::server::handle_chain_config;
37use crate::tool::subcommands::api_cmd::NetworkChain;
38use crate::tool::subcommands::api_cmd::report::ReportBuilder;
39use crate::tool::subcommands::api_cmd::state_decode_params_tests::create_all_state_decode_params_tests;
40use crate::utils::proofs_api::{self, ensure_proof_params_downloaded};
41use crate::{Config, rpc};
42use ahash::HashMap;
43use bls_signatures::Serialize as _;
44use chrono::Utc;
45use cid::Cid;
46use fil_actors_shared::fvm_ipld_bitfield::BitField;
47use fil_actors_shared::v10::runtime::DomainSeparationTag;
48use fvm_ipld_blockstore::Blockstore;
49use ipld_core::ipld::Ipld;
50use itertools::Itertools as _;
51use jsonrpsee::types::ErrorCode;
52use libp2p::PeerId;
53use num_traits::Signed;
54use serde::de::DeserializeOwned;
55use serde::{Deserialize, Serialize};
56use serde_json::Value;
57use similar::{ChangeTag, TextDiff};
58use std::borrow::Cow;
59use std::path::Path;
60use std::time::Instant;
61use std::{
62 path::PathBuf,
63 str::FromStr,
64 sync::{Arc, LazyLock},
65 time::Duration,
66};
67use tokio::sync::Semaphore;
68use tokio::task::JoinSet;
69use tracing::debug;
70
71const COLLECTION_SAMPLE_SIZE: usize = 5;
72const SAFE_EPOCH_DELAY_FOR_TESTING: i64 = 20; const MESSAGE_LOOKBACK_LIMIT: i64 = 2000;
74
75static KNOWN_CALIBNET_ADDRESS: LazyLock<Address> = LazyLock::new(|| {
78 crate::shim::address::Network::Testnet
79 .parse_address("t1c4dkec3qhrnrsa4mccy7qntkyq2hhsma4sq7lui")
80 .unwrap()
81 .into()
82});
83
84static KNOWN_EMPTY_CALIBNET_ADDRESS: LazyLock<Address> = LazyLock::new(|| {
86 crate::shim::address::Network::Testnet
87 .parse_address("t1qb2x5qctp34rxd7ucl327h5ru6aazj2heno7x5y")
88 .unwrap()
89 .into()
90});
91
92static KNOWN_CALIBNET_F0_ADDRESS: LazyLock<Address> = LazyLock::new(|| {
94 crate::shim::address::Network::Testnet
95 .parse_address("t0168923")
96 .unwrap()
97 .into()
98});
99
100static KNOWN_CALIBNET_F1_ADDRESS: LazyLock<Address> = LazyLock::new(|| {
101 crate::shim::address::Network::Testnet
102 .parse_address("t1w2zb5a723izlm4q3khclsjcnapfzxcfhvqyfoly")
103 .unwrap()
104 .into()
105});
106
107static KNOWN_CALIBNET_F2_ADDRESS: LazyLock<Address> = LazyLock::new(|| {
108 crate::shim::address::Network::Testnet
109 .parse_address("t2nfplhzpyeck5dcc4fokj5ar6nbs3mhbdmq6xu3q")
110 .unwrap()
111 .into()
112});
113
114static KNOWN_CALIBNET_F3_ADDRESS: LazyLock<Address> = LazyLock::new(|| {
115 crate::shim::address::Network::Testnet
116 .parse_address("t3wmbvnabsj6x2uki33phgtqqemmunnttowpx3chklrchy76pv52g5ajnaqdypxoomq5ubfk65twl5ofvkhshq")
117 .unwrap()
118 .into()
119});
120
121static KNOWN_CALIBNET_F4_ADDRESS: LazyLock<Address> = LazyLock::new(|| {
122 crate::shim::address::Network::Testnet
123 .parse_address("t410fx2cumi6pgaz64varl77xbuub54bgs3k5xsvn3ki")
124 .unwrap()
125 .into()
126});
127
128fn generate_eth_random_address() -> anyhow::Result<EthAddress> {
129 k256::ecdsa::SigningKey::random(&mut crate::utils::rand::forest_os_rng()).try_into()
130}
131
132const TICKET_QUALITY_GREEDY: f64 = 0.9;
133const TICKET_QUALITY_OPTIMAL: f64 = 0.8;
134const ZERO_ADDRESS: &str = "0x0000000000000000000000000000000000000000";
135const MINER_ADDRESS: Address = Address::new_id(78216); const ACCOUNT_ADDRESS: Address = Address::new_id(1234); const EVM_ADDRESS: &str = "t410fbqoynu2oi2lxam43knqt6ordiowm2ywlml27z4i";
139
140#[derive(
142 Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize, strum::Display,
143)]
144#[serde(rename_all = "snake_case")]
145pub enum TestSummary {
146 MissingMethod,
148 Rejected(String),
150 NotJsonRPC,
152 InfraError,
154 BadJson,
156 CustomCheckFailed,
158 Timeout,
160 Valid,
162}
163
164impl TestSummary {
165 fn from_err(err: &rpc::ClientError) -> Self {
166 match err {
167 rpc::ClientError::Call(it) => match it.code().into() {
168 ErrorCode::MethodNotFound => Self::MissingMethod,
169 _ => {
170 let message = normalized_error_message(it.message());
173 Self::Rejected(message.to_string())
174 }
175 },
176 rpc::ClientError::ParseError(_) => Self::NotJsonRPC,
177 rpc::ClientError::RequestTimeout => Self::Timeout,
178 rpc::ClientError::Transport(_)
179 | rpc::ClientError::RestartNeeded(_)
180 | rpc::ClientError::InvalidSubscriptionId
181 | rpc::ClientError::InvalidRequestId(_)
182 | rpc::ClientError::Custom(_)
183 | rpc::ClientError::HttpNotImplemented
184 | rpc::ClientError::EmptyBatchRequest(_)
185 | rpc::ClientError::RegisterMethod(_) => Self::InfraError,
186 _ => unimplemented!(),
187 }
188 }
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct TestDump {
194 pub request: rpc::Request,
195 pub path: rpc::ApiPaths,
196 pub forest_response: Result<Value, String>,
197 pub lotus_response: Result<Value, String>,
198}
199
200impl std::fmt::Display for TestDump {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 writeln!(f, "Request path: {}", self.path.path())?;
203 writeln!(f, "Request dump: {:?}", self.request)?;
204 writeln!(f, "Request params JSON: {}", self.request.params)?;
205 let (forest_response, lotus_response) = (
206 self.forest_response
207 .as_ref()
208 .ok()
209 .and_then(|v| serde_json::to_string_pretty(v).ok()),
210 self.lotus_response
211 .as_ref()
212 .ok()
213 .and_then(|v| serde_json::to_string_pretty(v).ok()),
214 );
215 if let Some(forest_response) = &forest_response
216 && let Some(lotus_response) = &lotus_response
217 {
218 let diff = TextDiff::from_lines(forest_response, lotus_response);
219 let mut print_diff = Vec::new();
220 for change in diff.iter_all_changes() {
221 let sign = match change.tag() {
222 ChangeTag::Delete => "-",
223 ChangeTag::Insert => "+",
224 ChangeTag::Equal => " ",
225 };
226 print_diff.push(format!("{sign}{change}"));
227 }
228 writeln!(f, "Forest response: {forest_response}")?;
229 writeln!(f, "Lotus response: {lotus_response}")?;
230 writeln!(f, "Diff: {}", print_diff.join("\n"))?;
231 } else {
232 if let Some(forest_response) = &forest_response {
233 writeln!(f, "Forest response: {forest_response}")?;
234 }
235 if let Some(lotus_response) = &lotus_response {
236 writeln!(f, "Lotus response: {lotus_response}")?;
237 }
238 };
239 Ok(())
240 }
241}
242
243pub struct TestResult {
245 pub forest_status: TestSummary,
247 pub lotus_status: TestSummary,
249 pub test_dump: Option<TestDump>,
251 pub duration: Duration,
253}
254
255pub(super) enum PolicyOnRejected {
256 Fail,
257 Pass,
258 PassWithIdenticalError,
259 PassWithIdenticalErrorCaseInsensitive,
260 PassWithQuasiIdenticalError,
263}
264
265pub(super) enum SortPolicy {
266 All,
268}
269
270pub(super) struct RpcTest {
271 pub request: rpc::Request,
272 pub check_syntax: Box<dyn Fn(serde_json::Value) -> bool + Send + Sync>,
273 pub check_semantics: Box<dyn Fn(serde_json::Value, serde_json::Value) -> bool + Send + Sync>,
274 pub ignore: Option<&'static str>,
275 pub policy_on_rejected: PolicyOnRejected,
276 pub sort_policy: Option<SortPolicy>,
277}
278
279fn sort_json(value: &mut Value) {
280 match value {
281 Value::Array(arr) => {
282 for v in arr.iter_mut() {
283 sort_json(v);
284 }
285 arr.sort_by_key(|a| a.to_string());
286 }
287 Value::Object(obj) => {
288 let mut sorted_map: serde_json::Map<String, Value> = serde_json::Map::new();
289 let mut keys: Vec<String> = obj.keys().cloned().collect();
290 keys.sort();
291 for k in keys {
292 let mut v = obj.remove(&k).unwrap();
293 sort_json(&mut v);
294 sorted_map.insert(k, v);
295 }
296 *obj = sorted_map;
297 }
298 _ => (),
299 }
300}
301
302impl RpcTest {
306 fn basic<T>(request: rpc::Request<T>) -> Self
309 where
310 T: HasLotusJson,
311 {
312 Self::basic_raw(request.map_ty::<T::LotusJson>())
313 }
314 fn basic_raw<T: DeserializeOwned>(request: rpc::Request<T>) -> Self {
316 Self {
317 request: request.map_ty(),
318 check_syntax: Box::new(|it| {
319 match crate::rpc::json_validator::from_value_rejecting_unknown_fields::<T>(it) {
320 Ok(_) => true,
321 Err(e) => {
322 debug!(?e);
323 false
324 }
325 }
326 }),
327 check_semantics: Box::new(|_, _| true),
328 ignore: None,
329 policy_on_rejected: PolicyOnRejected::Fail,
330 sort_policy: None,
331 }
332 }
333 fn validate<T: HasLotusJson>(
336 request: rpc::Request<T>,
337 validate: impl Fn(T, T) -> bool + Send + Sync + 'static,
338 ) -> Self {
339 Self::validate_raw(request.map_ty::<T::LotusJson>(), move |l, r| {
340 validate(T::from_lotus_json(l), T::from_lotus_json(r))
341 })
342 }
343 fn validate_raw<T: DeserializeOwned>(
345 request: rpc::Request<T>,
346 validate: impl Fn(T, T) -> bool + Send + Sync + 'static,
347 ) -> Self {
348 Self {
349 request: request.map_ty(),
350 check_syntax: Box::new(|value| {
351 match crate::rpc::json_validator::from_value_rejecting_unknown_fields::<T>(value) {
352 Ok(_) => true,
353 Err(e) => {
354 debug!("{e}");
355 false
356 }
357 }
358 }),
359 check_semantics: Box::new(move |forest_json, lotus_json| {
360 match (
361 crate::rpc::json_validator::from_value_rejecting_unknown_fields::<T>(
362 forest_json,
363 ),
364 crate::rpc::json_validator::from_value_rejecting_unknown_fields::<T>(
365 lotus_json,
366 ),
367 ) {
368 (Ok(forest), Ok(lotus)) => validate(forest, lotus),
369 (forest, lotus) => {
370 if let Err(e) = forest {
371 debug!("[forest] invalid json: {e}");
372 }
373 if let Err(e) = lotus {
374 debug!("[lotus] invalid json: {e}");
375 }
376 false
377 }
378 }
379 }),
380 ignore: None,
381 policy_on_rejected: PolicyOnRejected::Fail,
382 sort_policy: None,
383 }
384 }
385 pub(crate) fn identity<T: PartialEq + HasLotusJson>(request: rpc::Request<T>) -> RpcTest {
388 Self::validate(request, |forest, lotus| forest == lotus)
389 }
390
391 fn ignore(mut self, msg: &'static str) -> Self {
392 self.ignore = Some(msg);
393 self
394 }
395
396 fn policy_on_rejected(mut self, policy: PolicyOnRejected) -> Self {
397 self.policy_on_rejected = policy;
398 self
399 }
400
401 fn sort_policy(mut self, policy: SortPolicy) -> Self {
402 self.sort_policy = Some(policy);
403 self
404 }
405
406 async fn run(&self, forest: &rpc::Client, lotus: &rpc::Client) -> TestResult {
407 let start = Instant::now();
408 let forest_resp = forest.call(self.request.clone()).await;
409 let forest_response = forest_resp.as_ref().map_err(|e| e.to_string()).cloned();
410 let lotus_resp = lotus.call(self.request.clone()).await;
411 let lotus_response = lotus_resp.as_ref().map_err(|e| e.to_string()).cloned();
412
413 let (forest_status, lotus_status) = match (forest_resp, lotus_resp) {
414 (Ok(forest), Ok(lotus))
415 if (self.check_syntax)(forest.clone()) && (self.check_syntax)(lotus.clone()) =>
416 {
417 let (forest, lotus) = if self.sort_policy.is_some() {
418 let mut sorted_forest = forest.clone();
419 sort_json(&mut sorted_forest);
420 let mut sorted_lotus = lotus.clone();
421 sort_json(&mut sorted_lotus);
422 (sorted_forest, sorted_lotus)
423 } else {
424 (forest, lotus)
425 };
426 let forest_status = if (self.check_semantics)(forest, lotus) {
427 TestSummary::Valid
428 } else {
429 TestSummary::CustomCheckFailed
430 };
431 (forest_status, TestSummary::Valid)
432 }
433 (forest_resp, lotus_resp) => {
434 let forest_status = forest_resp.map_or_else(
435 |e| TestSummary::from_err(&e),
436 |value| {
437 if (self.check_syntax)(value) {
438 TestSummary::Valid
439 } else {
440 TestSummary::BadJson
441 }
442 },
443 );
444 let lotus_status = lotus_resp.map_or_else(
445 |e| TestSummary::from_err(&e),
446 |value| {
447 if (self.check_syntax)(value) {
448 TestSummary::Valid
449 } else {
450 TestSummary::BadJson
451 }
452 },
453 );
454
455 (forest_status, lotus_status)
456 }
457 };
458
459 TestResult {
460 forest_status,
461 lotus_status,
462 test_dump: Some(TestDump {
463 request: self.request.clone(),
464 path: self.request.api_path,
465 forest_response,
466 lotus_response,
467 }),
468 duration: start.elapsed(),
469 }
470 }
471}
472
473fn common_tests() -> Vec<RpcTest> {
474 vec![
475 RpcTest::validate(Version::request(()).unwrap(), |forest, lotus| {
477 forest.api_version == lotus.api_version && forest.block_delay == lotus.block_delay
478 }),
479 RpcTest::basic(StartTime::request(()).unwrap()),
480 RpcTest::basic(Session::request(()).unwrap()),
481 ]
482}
483
484fn chain_tests(offline: bool) -> Vec<RpcTest> {
485 vec![
486 RpcTest::identity(ChainGetGenesis::request(()).unwrap()),
487 if offline {
488 RpcTest::basic(ChainHead::request(()).unwrap())
489 } else {
490 RpcTest::identity(ChainHead::request(()).unwrap())
491 },
492 if offline {
493 RpcTest::basic(ChainGetTipSetFinalityStatus::request(()).unwrap())
494 } else {
495 RpcTest::identity(ChainGetTipSetFinalityStatus::request(()).unwrap())
496 },
497 RpcTest::basic(ChainGetFinalizedTipset::request(()).unwrap()),
498 ]
499}
500
501fn chain_tests_with_tipset<DB: Blockstore>(
502 store: &Arc<DB>,
503 offline: bool,
504 tipset: &Tipset,
505) -> anyhow::Result<Vec<RpcTest>> {
506 let mut tests = vec![
507 RpcTest::identity(ChainGetTipSetByHeight::request((
508 tipset.epoch(),
509 Default::default(),
510 ))?),
511 RpcTest::identity(ChainGetTipSetAfterHeight::request((
512 tipset.epoch(),
513 Default::default(),
514 ))?),
515 RpcTest::identity(ChainGetTipSet::request((tipset.key().into(),))?),
516 RpcTest::identity(ChainGetTipSet::request((None.into(),))?)
517 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
518 RpcTest::identity(ChainGetTipSetV2::request((TipsetSelector {
519 key: None.into(),
520 height: None,
521 tag: None,
522 },))?)
523 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
524 RpcTest::identity(ChainGetTipSetV2::request((TipsetSelector {
525 key: tipset.key().into(),
526 height: None,
527 tag: Some(TipsetTag::Latest),
528 },))?)
529 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
530 RpcTest::identity(ChainGetTipSetV2::request((TipsetSelector {
531 key: tipset.key().into(),
532 height: None,
533 tag: None,
534 },))?),
535 RpcTest::identity(ChainGetTipSetV2::request((TipsetSelector {
536 key: None.into(),
537 height: Some(TipsetHeight {
538 at: tipset.epoch(),
539 previous: true,
540 anchor: Some(TipsetAnchor {
541 key: None.into(),
542 tag: None,
543 }),
544 }),
545 tag: None,
546 },))?),
547 RpcTest::identity(ChainGetTipSetV2::request((TipsetSelector {
548 key: None.into(),
549 height: Some(TipsetHeight {
550 at: tipset.epoch(),
551 previous: true,
552 anchor: None,
553 }),
554 tag: None,
555 },))?)
556 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError)
557 .ignore("this case should pass when F3 is back on calibnet"),
558 validate_tagged_tipset_v2(
559 ChainGetTipSetV2::request((TipsetSelector {
560 key: None.into(),
561 height: None,
562 tag: Some(TipsetTag::Latest),
563 },))?,
564 offline,
565 ),
566 RpcTest::identity(ChainGetPath::request((
567 tipset.key().clone(),
568 tipset.parents().clone(),
569 ))?),
570 RpcTest::identity(ChainGetMessagesInTipset::request((tipset
571 .key()
572 .clone()
573 .into(),))?),
574 RpcTest::identity(ChainTipSetWeight::request((tipset.key().into(),))?),
575 ];
576
577 if !offline {
578 tests.extend([
579 validate_tagged_tipset_v2(
581 ChainGetTipSetV2::request((TipsetSelector {
582 key: None.into(),
583 height: None,
584 tag: Some(TipsetTag::Safe),
585 },))?,
586 offline,
587 ),
588 validate_tagged_tipset_v2(
590 ChainGetTipSetV2::request((TipsetSelector {
591 key: None.into(),
592 height: None,
593 tag: Some(TipsetTag::Finalized),
594 },))?,
595 offline,
596 ),
597 ]);
598 }
599
600 for block in tipset.block_headers() {
601 let block_cid = *block.cid();
602 tests.extend([
603 RpcTest::identity(ChainReadObj::request((block_cid,))?),
604 RpcTest::identity(ChainHasObj::request((block_cid,))?),
605 RpcTest::identity(ChainGetBlock::request((block_cid,))?),
606 RpcTest::identity(ChainGetBlockMessages::request((block_cid,))?),
607 RpcTest::identity(ChainGetParentMessages::request((block_cid,))?),
608 RpcTest::identity(ChainGetParentReceipts::request((block_cid,))?),
609 RpcTest::identity(ChainStatObj::request((block.messages, None))?),
610 RpcTest::identity(ChainStatObj::request((
611 block.messages,
612 Some(block.messages),
613 ))?),
614 ]);
615
616 let (bls_messages, secp_messages) = crate::chain::store::block_messages(&store, block)?;
617 for msg_cid in sample_message_cids(bls_messages.iter(), secp_messages.iter()) {
618 tests.extend([RpcTest::identity(ChainGetMessage::request((msg_cid,))?)]);
619 }
620
621 for receipt in Receipt::get_receipts(store, block.message_receipts)? {
622 if let Some(events_root) = receipt.events_root() {
623 tests.extend([RpcTest::identity(ChainGetEvents::request((events_root,))?)
624 .sort_policy(SortPolicy::All)]);
625 }
626 }
627 }
628
629 Ok(tests)
630}
631
632fn auth_tests() -> anyhow::Result<Vec<RpcTest>> {
633 Ok(vec![
635 RpcTest::basic(AuthNew::request((
636 AuthNewParams::process_perms(Permission::Admin.to_string())?,
637 None,
638 ))?),
639 RpcTest::basic(AuthNew::request((
640 AuthNewParams::process_perms(Permission::Sign.to_string())?,
641 None,
642 ))?),
643 RpcTest::basic(AuthNew::request((
644 AuthNewParams::process_perms(Permission::Write.to_string())?,
645 None,
646 ))?),
647 RpcTest::basic(AuthNew::request((
648 AuthNewParams::process_perms(Permission::Read.to_string())?,
649 None,
650 ))?),
651 ])
652}
653
654fn mpool_tests() -> Vec<RpcTest> {
655 vec![
656 RpcTest::identity(MpoolGetNonce::request((*KNOWN_CALIBNET_ADDRESS,)).unwrap()),
657 RpcTest::identity(MpoolGetNonce::request((*KNOWN_EMPTY_CALIBNET_ADDRESS,)).unwrap())
666 .policy_on_rejected(PolicyOnRejected::Pass),
667 RpcTest::basic(MpoolPending::request((ApiTipsetKey(None),)).unwrap()),
668 RpcTest::basic(MpoolSelect::request((ApiTipsetKey(None), TICKET_QUALITY_GREEDY)).unwrap()),
669 RpcTest::basic(MpoolSelect::request((ApiTipsetKey(None), TICKET_QUALITY_OPTIMAL)).unwrap())
670 .ignore("https://github.com/ChainSafe/forest/issues/4490"),
671 ]
672}
673
674fn mpool_tests_with_tipset(tipset: &Tipset) -> Vec<RpcTest> {
675 vec![
676 RpcTest::basic(MpoolPending::request((tipset.key().into(),)).unwrap()),
677 RpcTest::basic(MpoolSelect::request((tipset.key().into(), TICKET_QUALITY_GREEDY)).unwrap()),
678 RpcTest::basic(
679 MpoolSelect::request((tipset.key().into(), TICKET_QUALITY_OPTIMAL)).unwrap(),
680 )
681 .ignore("https://github.com/ChainSafe/forest/issues/4490"),
682 ]
683}
684
685fn net_tests() -> Vec<RpcTest> {
686 vec![
689 RpcTest::basic(NetAddrsListen::request(()).unwrap()),
690 RpcTest::basic(NetPeers::request(()).unwrap()),
691 RpcTest::identity(NetListening::request(()).unwrap()),
692 RpcTest::basic(NetAgentVersion::request((PeerId::random().to_string(),)).unwrap())
694 .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError),
695 RpcTest::basic(NetFindPeer::request((PeerId::random().to_string(),)).unwrap())
696 .policy_on_rejected(PolicyOnRejected::Pass)
697 .ignore("It times out in lotus when peer not found"),
698 RpcTest::basic(NetInfo::request(()).unwrap())
699 .ignore("Not implemented in Lotus. Why do we even have this method?"),
700 RpcTest::basic(NetAutoNatStatus::request(()).unwrap()),
701 RpcTest::identity(NetVersion::request(()).unwrap()),
702 RpcTest::identity(NetProtectAdd::request((vec![PeerId::random().to_string()],)).unwrap()),
703 RpcTest::identity(
704 NetProtectRemove::request((vec![PeerId::random().to_string()],)).unwrap(),
705 ),
706 RpcTest::basic(NetProtectList::request(()).unwrap()),
707 ]
708}
709
710fn node_tests() -> Vec<RpcTest> {
711 vec![
712 ]
716}
717
718fn event_tests_with_tipset<DB: Blockstore>(
719 _store: &Arc<DB>,
720 tipset: &Tipset,
721) -> anyhow::Result<Vec<RpcTest>> {
722 let epoch = tipset.epoch();
723 Ok(vec![
724 RpcTest::identity(GetActorEventsRaw::request((None,))?)
725 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
726 RpcTest::identity(GetActorEventsRaw::request((Some(ActorEventFilter {
727 addresses: vec![],
728 fields: Default::default(),
729 from_height: Some(epoch),
730 to_height: Some(epoch),
731 tipset_key: None,
732 }),))?)
733 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError)
734 .sort_policy(SortPolicy::All),
735 RpcTest::identity(GetActorEventsRaw::request((Some(ActorEventFilter {
736 addresses: vec![],
737 fields: Default::default(),
738 from_height: Some(epoch - 100),
739 to_height: Some(epoch),
740 tipset_key: None,
741 }),))?)
742 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError)
743 .sort_policy(SortPolicy::All),
744 RpcTest::identity(GetActorEventsRaw::request((Some(ActorEventFilter {
745 addresses: vec![],
746 fields: Default::default(),
747 from_height: None,
748 to_height: None,
749 tipset_key: Some(tipset.key().clone().into()),
750 }),))?)
751 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError)
752 .sort_policy(SortPolicy::All),
753 RpcTest::identity(GetActorEventsRaw::request((Some(ActorEventFilter {
754 addresses: vec![
755 Address::from_str("t410fvtakbtytk4otbnfymn4zn5ow252nj7lcpbtersq")?.into(),
756 ],
757 fields: Default::default(),
758 from_height: Some(epoch - 100),
759 to_height: Some(epoch),
760 tipset_key: None,
761 }),))?)
762 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError)
763 .sort_policy(SortPolicy::All),
764 {
765 use std::collections::BTreeMap;
766
767 use base64::{Engine, prelude::BASE64_STANDARD};
768
769 use crate::lotus_json::LotusJson;
770 use crate::rpc::misc::ActorEventBlock;
771
772 let topic = BASE64_STANDARD.decode("0Gprf0kYSUs3GSF9GAJ4bB9REqbB2I/iz+wAtFhPauw=")?;
773 let mut fields: BTreeMap<String, Vec<ActorEventBlock>> = Default::default();
774 fields.insert(
775 "t1".into(),
776 vec![ActorEventBlock {
777 codec: 85,
778 value: LotusJson(topic),
779 }],
780 );
781 RpcTest::identity(GetActorEventsRaw::request((Some(ActorEventFilter {
782 addresses: vec![],
783 fields,
784 from_height: Some(epoch - 100),
785 to_height: Some(epoch),
786 tipset_key: None,
787 }),))?)
788 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError)
789 .sort_policy(SortPolicy::All)
790 },
791 ])
792}
793
794fn miner_tests_with_tipset<DB: Blockstore>(
795 store: &Arc<DB>,
796 tipset: &Tipset,
797 miner_address: Option<Address>,
798) -> anyhow::Result<Vec<RpcTest>> {
799 let Some(miner_address) = miner_address else {
801 return Ok(vec![]);
802 };
803
804 let mut tests = Vec::new();
805 for block in tipset.block_headers() {
806 let (bls_messages, secp_messages) = crate::chain::store::block_messages(store, block)?;
807 tests.push(miner_create_block_test(
808 miner_address,
809 tipset,
810 bls_messages,
811 secp_messages,
812 ));
813 }
814 tests.push(miner_create_block_no_messages_test(miner_address, tipset));
815 Ok(tests)
816}
817
818fn miner_create_block_test(
819 miner: Address,
820 tipset: &Tipset,
821 bls_messages: Vec<Message>,
822 secp_messages: Vec<SignedMessage>,
823) -> RpcTest {
824 let priv_key = bls_signatures::PrivateKey::generate(&mut crate::utils::rand::forest_rng());
826 let signed_bls_msgs = bls_messages
827 .into_iter()
828 .map(|message| {
829 let sig = priv_key.sign(message.cid().to_bytes());
830 SignedMessage {
831 message,
832 signature: Signature::new_bls(sig.as_bytes().to_vec()),
833 }
834 })
835 .collect_vec();
836
837 let block_template = BlockTemplate {
838 miner,
839 parents: tipset.parents().to_owned(),
840 ticket: Ticket::default(),
841 eproof: ElectionProof::default(),
842 beacon_values: tipset.block_headers().first().beacon_entries.to_owned(),
843 messages: [signed_bls_msgs, secp_messages].concat(),
844 epoch: tipset.epoch(),
845 timestamp: tipset.min_timestamp(),
846 winning_post_proof: Vec::default(),
847 };
848 RpcTest::identity(MinerCreateBlock::request((block_template,)).unwrap())
849}
850
851fn miner_create_block_no_messages_test(miner: Address, tipset: &Tipset) -> RpcTest {
852 let block_template = BlockTemplate {
853 miner,
854 parents: tipset.parents().to_owned(),
855 ticket: Ticket::default(),
856 eproof: ElectionProof::default(),
857 beacon_values: tipset.block_headers().first().beacon_entries.to_owned(),
858 messages: Vec::default(),
859 epoch: tipset.epoch(),
860 timestamp: tipset.min_timestamp(),
861 winning_post_proof: Vec::default(),
862 };
863 RpcTest::identity(MinerCreateBlock::request((block_template,)).unwrap())
864}
865
866fn state_tests_with_tipset<DB: Blockstore>(
867 store: &Arc<DB>,
868 tipset: &Tipset,
869) -> anyhow::Result<Vec<RpcTest>> {
870 let mut tests = vec![
871 RpcTest::identity(StateNetworkName::request(())?),
872 RpcTest::identity(StateGetNetworkParams::request(())?),
873 RpcTest::identity(StateMinerInitialPledgeForSector::request((
874 1,
875 SectorSize::_32GiB,
876 1024,
877 tipset.key().into(),
878 ))?),
879 RpcTest::identity(StateGetActor::request((
880 Address::SYSTEM_ACTOR,
881 tipset.key().into(),
882 ))?),
883 RpcTest::identity(StateGetActorV2::request((
884 Address::SYSTEM_ACTOR,
885 TipsetSelector {
886 key: tipset.key().into(),
887 ..Default::default()
888 },
889 ))?),
890 RpcTest::identity(StateGetID::request((
891 Address::SYSTEM_ACTOR,
892 TipsetSelector {
893 key: tipset.key().into(),
894 ..Default::default()
895 },
896 ))?),
897 RpcTest::identity(StateGetRandomnessFromTickets::request((
898 DomainSeparationTag::ElectionProofProduction as i64,
899 tipset.epoch(),
900 "dead beef".as_bytes().to_vec(),
901 tipset.key().into(),
902 ))?),
903 RpcTest::identity(StateGetRandomnessDigestFromTickets::request((
904 tipset.epoch(),
905 tipset.key().into(),
906 ))?),
907 RpcTest::identity(StateGetRandomnessFromBeacon::request((
908 DomainSeparationTag::ElectionProofProduction as i64,
909 tipset.epoch(),
910 "dead beef".as_bytes().to_vec(),
911 tipset.key().into(),
912 ))?),
913 RpcTest::identity(StateGetRandomnessDigestFromBeacon::request((
914 tipset.epoch(),
915 tipset.key().into(),
916 ))?),
917 RpcTest::identity(StateLookupID::request((
919 Address::new_id(0xdeadbeef),
920 tipset.key().into(),
921 ))?),
922 RpcTest::identity(StateVerifiedRegistryRootKey::request((tipset
923 .key()
924 .into(),))?),
925 RpcTest::identity(StateVerifierStatus::request((
926 Address::VERIFIED_REGISTRY_ACTOR,
927 tipset.key().into(),
928 ))?),
929 RpcTest::identity(StateNetworkVersion::request((tipset.key().into(),))?),
930 RpcTest::identity(StateListMiners::request((tipset.key().into(),))?),
931 RpcTest::identity(StateListActors::request((tipset.key().into(),))?),
932 RpcTest::identity(MsigGetAvailableBalance::request((
933 Address::new_id(18101), tipset.key().into(),
935 ))?),
936 RpcTest::identity(MsigGetPending::request((
937 Address::new_id(18101), tipset.key().into(),
939 ))?),
940 RpcTest::identity(MsigGetVested::request((
941 Address::new_id(18101), tipset.parents().into(),
943 tipset.key().into(),
944 ))?),
945 RpcTest::identity(MsigGetVestingSchedule::request((
946 Address::new_id(18101), tipset.key().into(),
948 ))?),
949 RpcTest::identity(BeaconGetEntry::request((tipset.epoch(),))?),
950 RpcTest::identity(StateGetBeaconEntry::request((tipset.epoch(),))?),
951 RpcTest::identity(StateVerifiedClientStatus::request((
955 Address::VERIFIED_REGISTRY_ACTOR,
956 tipset.key().into(),
957 ))?),
958 RpcTest::identity(StateVerifiedClientStatus::request((
959 Address::DATACAP_TOKEN_ACTOR,
960 tipset.key().into(),
961 ))?),
962 RpcTest::identity(StateDealProviderCollateralBounds::request((
963 1,
964 true,
965 tipset.key().into(),
966 ))?),
967 RpcTest::identity(StateCirculatingSupply::request((tipset.key().into(),))?),
968 RpcTest::identity(StateVMCirculatingSupplyInternal::request((tipset
969 .key()
970 .into(),))?),
971 RpcTest::identity(StateMarketParticipants::request((tipset.key().into(),))?),
972 RpcTest::identity(StateMarketDeals::request((tipset.key().into(),))?),
973 RpcTest::identity(StateSectorPreCommitInfo::request((
974 Default::default(), u64::from(u16::MAX),
976 tipset.key().into(),
977 ))?)
978 .policy_on_rejected(PolicyOnRejected::Pass),
979 RpcTest::identity(StateSectorGetInfo::request((
980 Default::default(), u64::from(u16::MAX), tipset.key().into(),
983 ))?)
984 .policy_on_rejected(PolicyOnRejected::Pass),
985 RpcTest::identity(StateGetAllocationIdForPendingDeal::request((
986 u64::from(u16::MAX), tipset.key().into(),
988 ))?),
989 RpcTest::identity(StateGetAllocationForPendingDeal::request((
990 u64::from(u16::MAX), tipset.key().into(),
992 ))?),
993 RpcTest::identity(StateCompute::request((
994 tipset.epoch(),
995 vec![],
996 tipset.key().into(),
997 ))?),
998 ];
999
1000 tests.extend(read_state_api_tests(tipset)?);
1001 tests.extend(create_all_state_decode_params_tests(tipset)?);
1002
1003 for &pending_deal_id in
1004 StateGetAllocationIdForPendingDeal::get_allocations_for_pending_deals(store, tipset)?
1005 .keys()
1006 .take(COLLECTION_SAMPLE_SIZE)
1007 {
1008 tests.extend([
1009 RpcTest::identity(StateGetAllocationIdForPendingDeal::request((
1010 pending_deal_id,
1011 tipset.key().into(),
1012 ))?),
1013 RpcTest::identity(StateGetAllocationForPendingDeal::request((
1014 pending_deal_id,
1015 tipset.key().into(),
1016 ))?),
1017 ]);
1018 }
1019
1020 let (deals, deals_map) = {
1022 let state = StateTree::new_from_root(store.clone(), tipset.parent_state())?;
1023 let actor = state.get_required_actor(&Address::MARKET_ACTOR)?;
1024 let market_state = market::State::load(&store, actor.code, actor.state)?;
1025 let proposals = market_state.proposals(&store)?;
1026 let mut deals = vec![];
1027 let mut deals_map = HashMap::default();
1028 proposals.for_each(|deal_id, deal_proposal| {
1029 deals.push(deal_id);
1030 deals_map.insert(deal_id, deal_proposal);
1031 Ok(())
1032 })?;
1033 (deals, deals_map)
1034 };
1035
1036 for deal in deals.into_iter().take(COLLECTION_SAMPLE_SIZE) {
1038 tests.push(RpcTest::identity(StateMarketStorageDeal::request((
1039 deal,
1040 tipset.key().into(),
1041 ))?));
1042 }
1043
1044 for block in tipset.block_headers() {
1045 tests.extend([
1046 RpcTest::identity(StateMinerAllocated::request((
1047 block.miner_address,
1048 tipset.key().into(),
1049 ))?),
1050 RpcTest::identity(StateMinerActiveSectors::request((
1051 block.miner_address,
1052 tipset.key().into(),
1053 ))?),
1054 RpcTest::identity(StateLookupID::request((
1055 block.miner_address,
1056 tipset.key().into(),
1057 ))?),
1058 RpcTest::identity(StateLookupRobustAddress::request((
1059 block.miner_address,
1060 tipset.key().into(),
1061 ))?),
1062 RpcTest::identity(StateMinerSectors::request((
1063 block.miner_address,
1064 None,
1065 tipset.key().into(),
1066 ))?),
1067 RpcTest::identity(StateMinerPartitions::request((
1068 block.miner_address,
1069 0,
1070 tipset.key().into(),
1071 ))?),
1072 RpcTest::identity(StateMarketBalance::request((
1073 block.miner_address,
1074 tipset.key().into(),
1075 ))?),
1076 RpcTest::identity(StateMinerInfo::request((
1077 block.miner_address,
1078 tipset.key().into(),
1079 ))?),
1080 RpcTest::identity(StateMinerPower::request((
1081 block.miner_address,
1082 tipset.key().into(),
1083 ))?),
1084 RpcTest::identity(StateMinerDeadlines::request((
1085 block.miner_address,
1086 tipset.key().into(),
1087 ))?),
1088 RpcTest::identity(StateMinerProvingDeadline::request((
1089 block.miner_address,
1090 tipset.key().into(),
1091 ))?),
1092 RpcTest::identity(StateMinerAvailableBalance::request((
1093 block.miner_address,
1094 tipset.key().into(),
1095 ))?),
1096 RpcTest::identity(StateMinerFaults::request((
1097 block.miner_address,
1098 tipset.key().into(),
1099 ))?),
1100 RpcTest::identity(MinerGetBaseInfo::request((
1101 block.miner_address,
1102 block.epoch,
1103 tipset.key().into(),
1104 ))?),
1105 RpcTest::identity(StateMinerRecoveries::request((
1106 block.miner_address,
1107 tipset.key().into(),
1108 ))?),
1109 RpcTest::identity(StateMinerSectorCount::request((
1110 block.miner_address,
1111 tipset.key().into(),
1112 ))?),
1113 RpcTest::identity(StateGetClaims::request((
1114 block.miner_address,
1115 tipset.key().into(),
1116 ))?),
1117 RpcTest::identity(StateGetAllClaims::request((tipset.key().into(),))?),
1118 RpcTest::identity(StateGetAllAllocations::request((tipset.key().into(),))?),
1119 RpcTest::identity(StateSectorPreCommitInfo::request((
1120 block.miner_address,
1121 u64::from(u16::MAX), tipset.key().into(),
1123 ))?)
1124 .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError),
1125 RpcTest::identity(StateSectorGetInfo::request((
1126 block.miner_address,
1127 u64::from(u16::MAX), tipset.key().into(),
1129 ))?)
1130 .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError),
1131 ]);
1132 for claim_id in StateGetClaims::get_claims(store, &block.miner_address, tipset)?
1133 .keys()
1134 .take(COLLECTION_SAMPLE_SIZE)
1135 {
1136 tests.extend([RpcTest::identity(StateGetClaim::request((
1137 block.miner_address,
1138 *claim_id,
1139 tipset.key().into(),
1140 ))?)]);
1141 }
1142 for address in StateGetAllocations::get_valid_actor_addresses(store, tipset)?
1143 .take(COLLECTION_SAMPLE_SIZE)
1144 {
1145 tests.extend([RpcTest::identity(StateGetAllocations::request((
1146 address,
1147 tipset.key().into(),
1148 ))?)]);
1149 for allocation_id in StateGetAllocations::get_allocations(store, &address, tipset)?
1150 .keys()
1151 .take(COLLECTION_SAMPLE_SIZE)
1152 {
1153 tests.extend([RpcTest::identity(StateGetAllocation::request((
1154 address,
1155 *allocation_id,
1156 tipset.key().into(),
1157 ))?)]);
1158 }
1159 }
1160 for sector in StateSectorGetInfo::get_sectors(store, &block.miner_address, tipset)?
1161 .into_iter()
1162 .take(COLLECTION_SAMPLE_SIZE)
1163 {
1164 tests.extend([
1165 RpcTest::identity(StateSectorGetInfo::request((
1166 block.miner_address,
1167 sector,
1168 tipset.key().into(),
1169 ))?),
1170 RpcTest::identity(StateMinerSectors::request((
1171 block.miner_address,
1172 {
1173 let mut bf = BitField::new();
1174 bf.set(sector);
1175 Some(bf)
1176 },
1177 tipset.key().into(),
1178 ))?),
1179 RpcTest::identity(StateSectorExpiration::request((
1180 block.miner_address,
1181 sector,
1182 tipset.key().into(),
1183 ))?)
1184 .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError),
1185 RpcTest::identity(StateSectorPartition::request((
1186 block.miner_address,
1187 sector,
1188 tipset.key().into(),
1189 ))?),
1190 RpcTest::identity(StateMinerSectorAllocated::request((
1191 block.miner_address,
1192 sector,
1193 tipset.key().into(),
1194 ))?),
1195 ]);
1196 }
1197 for sector in StateSectorPreCommitInfo::get_sectors(store, &block.miner_address, tipset)?
1198 .into_iter()
1199 .take(COLLECTION_SAMPLE_SIZE)
1200 {
1201 tests.extend([RpcTest::identity(StateSectorPreCommitInfo::request((
1202 block.miner_address,
1203 sector,
1204 tipset.key().into(),
1205 ))?)]);
1206 }
1207 for info in StateSectorPreCommitInfo::get_sector_pre_commit_infos(
1208 store,
1209 &block.miner_address,
1210 tipset,
1211 )?
1212 .into_iter()
1213 .take(COLLECTION_SAMPLE_SIZE)
1214 .filter(|info| {
1215 !info.deal_ids.iter().any(|id| {
1216 if let Some(Ok(deal)) = deals_map.get(id) {
1217 tipset.epoch() > deal.start_epoch || info.expiration > deal.end_epoch
1218 } else {
1219 true
1220 }
1221 })
1222 }) {
1223 tests.extend([RpcTest::identity(
1224 StateMinerInitialPledgeCollateral::request((
1225 block.miner_address,
1226 info.clone(),
1227 tipset.key().into(),
1228 ))?,
1229 )]);
1230 tests.extend([RpcTest::identity(
1231 StateMinerPreCommitDepositForPower::request((
1232 block.miner_address,
1233 info,
1234 tipset.key().into(),
1235 ))?,
1236 )]);
1237 }
1238
1239 let (bls_messages, secp_messages) = crate::chain::store::block_messages(store, block)?;
1240 for msg_cid in sample_message_cids(bls_messages.iter(), secp_messages.iter()) {
1241 tests.extend([
1242 RpcTest::identity(StateReplay::request((tipset.key().into(), msg_cid))?),
1243 validate_message_wait(
1244 StateWaitMsg::request((msg_cid, 0, 10101, true))?
1245 .with_timeout(Duration::from_secs(15)),
1246 ),
1247 validate_message_wait(
1248 StateWaitMsg::request((msg_cid, 0, 10101, false))?
1249 .with_timeout(Duration::from_secs(15)),
1250 ),
1251 validate_message_lookup(StateSearchMsg::request((
1252 None.into(),
1253 msg_cid,
1254 MESSAGE_LOOKBACK_LIMIT,
1255 true,
1256 ))?),
1257 validate_message_lookup(StateSearchMsg::request((
1258 None.into(),
1259 msg_cid,
1260 MESSAGE_LOOKBACK_LIMIT,
1261 false,
1262 ))?),
1263 validate_message_lookup(StateSearchMsgLimited::request((
1264 msg_cid,
1265 MESSAGE_LOOKBACK_LIMIT,
1266 ))?),
1267 ]);
1268 }
1269 for msg in sample_messages(bls_messages.iter(), secp_messages.iter()) {
1270 tests.extend([
1271 RpcTest::identity(StateAccountKey::request((msg.from(), tipset.key().into()))?),
1272 RpcTest::identity(StateAccountKey::request((msg.from(), Default::default()))?),
1273 RpcTest::identity(StateLookupID::request((msg.from(), tipset.key().into()))?),
1274 RpcTest::identity(StateListMessages::request((
1275 MessageFilter {
1276 from: Some(msg.from()),
1277 to: Some(msg.to()),
1278 },
1279 tipset.key().into(),
1280 tipset.epoch(),
1281 ))?),
1282 RpcTest::identity(StateListMessages::request((
1283 MessageFilter {
1284 from: Some(msg.from()),
1285 to: None,
1286 },
1287 tipset.key().into(),
1288 tipset.epoch(),
1289 ))?),
1290 RpcTest::identity(StateListMessages::request((
1291 MessageFilter {
1292 from: None,
1293 to: Some(msg.to()),
1294 },
1295 tipset.key().into(),
1296 tipset.epoch(),
1297 ))?),
1298 RpcTest::identity(StateCall::request((msg.clone(), tipset.key().into()))?),
1299 ]);
1300 }
1301 }
1302
1303 Ok(tests)
1304}
1305
1306fn wallet_tests(worker_address: Option<Address>) -> Vec<RpcTest> {
1307 let prefunded_wallets = [
1308 *KNOWN_CALIBNET_F0_ADDRESS,
1310 *KNOWN_CALIBNET_F1_ADDRESS,
1311 *KNOWN_CALIBNET_F2_ADDRESS,
1312 *KNOWN_CALIBNET_F3_ADDRESS,
1313 *KNOWN_CALIBNET_F4_ADDRESS,
1314 *KNOWN_EMPTY_CALIBNET_ADDRESS,
1316 ];
1317
1318 let mut tests = vec![];
1319 for wallet in prefunded_wallets {
1320 tests.push(RpcTest::identity(
1321 WalletBalance::request((wallet,)).unwrap(),
1322 ));
1323 tests.push(RpcTest::identity(
1324 WalletValidateAddress::request((wallet.to_string(),)).unwrap(),
1325 ));
1326 }
1327
1328 let known_wallet = *KNOWN_CALIBNET_ADDRESS;
1329 let signature = "44364ca78d85e53dda5ac6f719a4f2de3261c17f58558ab7730f80c478e6d43775244e7d6855afad82e4a1fd6449490acfa88e3fcfe7c1fe96ed549c100900b400";
1331 let text = "Hello world!".as_bytes().to_vec();
1332 let sig_bytes = hex::decode(signature).unwrap();
1333 let signature = match known_wallet.protocol() {
1334 Protocol::Secp256k1 => Signature::new_secp256k1(sig_bytes),
1335 Protocol::BLS => Signature::new_bls(sig_bytes),
1336 _ => panic!("Invalid signature (must be bls or secp256k1)"),
1337 };
1338
1339 tests.push(RpcTest::identity(
1340 WalletBalance::request((known_wallet,)).unwrap(),
1341 ));
1342 tests.push(RpcTest::identity(
1343 WalletValidateAddress::request((known_wallet.to_string(),)).unwrap(),
1344 ));
1345 tests.push(
1346 RpcTest::identity(
1347 WalletValidateAddress::request((
1349 "Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn".to_string(),
1350 ))
1351 .unwrap(),
1352 )
1353 .policy_on_rejected(PolicyOnRejected::PassWithIdenticalErrorCaseInsensitive),
1355 );
1356 tests.push(RpcTest::identity(
1357 WalletVerify::request((known_wallet, text, signature)).unwrap(),
1358 ));
1359
1360 if let Some(worker_address) = worker_address {
1363 use base64::{Engine, prelude::BASE64_STANDARD};
1364 let msg =
1365 BASE64_STANDARD.encode("Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn".as_bytes());
1366 tests.push(RpcTest::identity(
1367 WalletSign::request((worker_address, msg.into())).unwrap(),
1368 ));
1369 tests.push(RpcTest::identity(
1370 WalletSign::request((worker_address, Vec::new())).unwrap(),
1371 ));
1372 let msg: Message = Message {
1373 from: worker_address,
1374 to: worker_address,
1375 value: TokenAmount::from_whole(1),
1376 method_num: METHOD_SEND,
1377 ..Default::default()
1378 };
1379 tests.push(RpcTest::identity(
1380 WalletSignMessage::request((worker_address, msg)).unwrap(),
1381 ));
1382 }
1383 tests
1384}
1385
1386fn eth_tests() -> anyhow::Result<Vec<RpcTest>> {
1387 let mut tests = vec![];
1388 for use_alias in [false, true] {
1389 tests.push(RpcTest::identity(EthAccounts::request_with_alias(
1390 (),
1391 use_alias,
1392 )?));
1393 tests.push(RpcTest::basic(EthBlockNumber::request_with_alias(
1394 (),
1395 use_alias,
1396 )?));
1397 tests.push(RpcTest::identity(EthChainId::request_with_alias(
1398 (),
1399 use_alias,
1400 )?));
1401 tests.push(RpcTest::validate(
1403 EthGasPrice::request_with_alias((), use_alias)?,
1404 |forest, lotus| forest.0.is_positive() && lotus.0.is_positive(),
1405 ));
1406 tests.push(RpcTest::basic(EthSyncing::request_with_alias(
1407 (),
1408 use_alias,
1409 )?));
1410 tests.push(RpcTest::identity(EthGetBalance::request_with_alias(
1411 (
1412 EthAddress::from_str("0xff38c072f286e3b20b3954ca9f99c05fbecc64aa")?,
1413 Predefined::Latest.into(),
1414 ),
1415 use_alias,
1416 )?));
1417 tests.push(RpcTest::identity(EthGetBalance::request_with_alias(
1418 (
1419 EthAddress::from_str("0xff38c072f286e3b20b3954ca9f99c05fbecc64aa")?,
1420 Predefined::Pending.into(),
1421 ),
1422 use_alias,
1423 )?));
1424 tests.push(RpcTest::basic(Web3ClientVersion::request_with_alias(
1425 (),
1426 use_alias,
1427 )?));
1428 tests.push(RpcTest::basic(EthMaxPriorityFeePerGas::request_with_alias(
1429 (),
1430 use_alias,
1431 )?));
1432 tests.push(RpcTest::identity(EthProtocolVersion::request_with_alias(
1433 (),
1434 use_alias,
1435 )?));
1436
1437 let cases = [
1438 (
1439 Some(EthAddress::from_str(
1440 "0x0c1d86d34e469770339b53613f3a2343accd62cb",
1441 )?),
1442 Some(
1443 "0xf8b2cb4f000000000000000000000000CbfF24DED1CE6B53712078759233Ac8f91ea71B6"
1444 .parse()?,
1445 ),
1446 ),
1447 (Some(EthAddress::from_str(ZERO_ADDRESS)?), None),
1448 (
1451 None,
1452 Some(EthBytes::from_str(
1453 concat!("0x", include_str!("contracts/cthulhu/invoke.hex")).trim(),
1454 )?),
1455 ),
1456 ];
1457
1458 for (to, data) in cases {
1459 let msg = EthCallMessage {
1460 to,
1461 data: data.clone(),
1462 ..EthCallMessage::default()
1463 };
1464
1465 tests.push(RpcTest::identity(EthCall::request_with_alias(
1466 (msg.clone(), Predefined::Latest.into()),
1467 use_alias,
1468 )?));
1469
1470 for tag in [Predefined::Latest, Predefined::Safe, Predefined::Finalized] {
1471 for api_path in [ApiPaths::V1, ApiPaths::V2] {
1472 tests.push(RpcTest::identity(
1473 EthCall::request_with_alias(
1474 (msg.clone(), BlockNumberOrHash::PredefinedBlock(tag)),
1475 use_alias,
1476 )?
1477 .with_api_path(api_path),
1478 ));
1479 }
1480 }
1481 }
1482
1483 let cases = [
1484 Some(EthAddressList::List(vec![])),
1485 Some(EthAddressList::List(vec![
1486 EthAddress::from_str("0x0c1d86d34e469770339b53613f3a2343accd62cb")?,
1487 EthAddress::from_str("0x89beb26addec4bc7e9f475aacfd084300d6de719")?,
1488 ])),
1489 Some(EthAddressList::Single(EthAddress::from_str(
1490 "0x0c1d86d34e469770339b53613f3a2343accd62cb",
1491 )?)),
1492 None,
1493 ];
1494
1495 for address in cases {
1496 tests.push(RpcTest::basic(EthNewFilter::request_with_alias(
1497 (EthFilterSpec {
1498 address,
1499 ..Default::default()
1500 },),
1501 use_alias,
1502 )?));
1503 }
1504 tests.push(RpcTest::basic(
1505 EthNewPendingTransactionFilter::request_with_alias((), use_alias)?,
1506 ));
1507 tests.push(RpcTest::basic(EthNewBlockFilter::request_with_alias(
1508 (),
1509 use_alias,
1510 )?));
1511 tests.push(RpcTest::identity(EthUninstallFilter::request_with_alias(
1512 (FilterID::new()?,),
1513 use_alias,
1514 )?));
1515 tests.push(RpcTest::identity(EthAddressToFilecoinAddress::request((
1516 "0xff38c072f286e3b20b3954ca9f99c05fbecc64aa".parse()?,
1517 ))?));
1518 tests.push(RpcTest::identity(FilecoinAddressToEthAddress::request((
1519 *KNOWN_CALIBNET_F0_ADDRESS,
1520 None,
1521 ))?));
1522 tests.push(RpcTest::identity(FilecoinAddressToEthAddress::request((
1523 *KNOWN_CALIBNET_F1_ADDRESS,
1524 None,
1525 ))?));
1526 tests.push(RpcTest::identity(FilecoinAddressToEthAddress::request((
1527 *KNOWN_CALIBNET_F2_ADDRESS,
1528 None,
1529 ))?));
1530 tests.push(RpcTest::identity(FilecoinAddressToEthAddress::request((
1531 *KNOWN_CALIBNET_F3_ADDRESS,
1532 None,
1533 ))?));
1534 tests.push(RpcTest::identity(FilecoinAddressToEthAddress::request((
1535 *KNOWN_CALIBNET_F4_ADDRESS,
1536 None,
1537 ))?));
1538 }
1539 Ok(tests)
1540}
1541
1542fn eth_call_api_err_tests(epoch: i64) -> Vec<RpcTest> {
1543 let contract_codes = [
1544 include_str!("./contracts/arithmetic_err/arithmetic_overflow_err.hex"),
1545 include_str!("contracts/assert_err/assert_err.hex"),
1546 include_str!("./contracts/divide_by_zero_err/divide_by_zero_err.hex"),
1547 include_str!("./contracts/generic_panic_err/generic_panic_err.hex"),
1548 include_str!("./contracts/index_out_of_bounds_err/index_out_of_bounds_err.hex"),
1549 include_str!("./contracts/invalid_enum_err/invalid_enum_err.hex"),
1550 include_str!("./contracts/invalid_storage_array_err/invalid_storage_array_err.hex"),
1551 include_str!("./contracts/out_of_memory_err/out_of_memory_err.hex"),
1552 include_str!("./contracts/pop_empty_array_err/pop_empty_array_err.hex"),
1553 include_str!("./contracts/uninitialized_fn_err/uninitialized_fn_err.hex"),
1554 ];
1555
1556 let mut tests = Vec::new();
1557
1558 for &contract_hex in &contract_codes {
1559 let contract_code =
1560 EthBytes::from_str(contract_hex).expect("Contract bytecode should be valid hex");
1561
1562 let zero_address = EthAddress::from_str(ZERO_ADDRESS).unwrap();
1563 let msg = EthCallMessage {
1565 from: Some(zero_address),
1566 data: Some(contract_code),
1567 ..EthCallMessage::default()
1568 };
1569
1570 let eth_call_request =
1571 EthCall::request((msg.clone(), BlockNumberOrHash::from_block_number(epoch))).unwrap();
1572 tests.extend([
1573 RpcTest::identity(eth_call_request.clone().with_api_path(ApiPaths::V1))
1574 .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError),
1575 RpcTest::identity(eth_call_request.with_api_path(ApiPaths::V2))
1576 .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError),
1577 ]);
1578 }
1579
1580 tests
1581}
1582
1583fn eth_tests_with_tipset<DB: Blockstore>(
1584 store: &Arc<DB>,
1585 shared_tipset: &Tipset,
1586) -> anyhow::Result<Vec<RpcTest>> {
1587 let block_cid = shared_tipset.key().cid()?;
1588 let block_hash: EthHash = block_cid.into();
1589
1590 let mut tests = vec![
1591 RpcTest::identity(EthGetBlockReceipts::request((
1592 BlockNumberOrHash::from_block_hash_object(block_hash, true),
1593 ))?),
1594 RpcTest::identity(EthGetTransactionByBlockHashAndIndex::request((
1595 block_hash,
1596 0.into(),
1597 ))?)
1598 .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError),
1599 RpcTest::identity(EthGetBlockByHash::request((block_hash, false))?),
1600 RpcTest::identity(EthGetBlockByHash::request((block_hash, true))?),
1601 RpcTest::identity(EthGetLogs::request((EthFilterSpec {
1602 from_block: Some(format!("0x{:x}", shared_tipset.epoch())),
1603 to_block: Some(format!("0x{:x}", shared_tipset.epoch())),
1604 ..Default::default()
1605 },))?)
1606 .sort_policy(SortPolicy::All)
1607 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
1608 RpcTest::identity(EthGetLogs::request((EthFilterSpec {
1609 from_block: Some(format!("0x{:x}", shared_tipset.epoch())),
1610 to_block: Some(format!("0x{:x}", shared_tipset.epoch())),
1611 address: Some(EthAddressList::List(Vec::new())),
1612 ..Default::default()
1613 },))?)
1614 .sort_policy(SortPolicy::All)
1615 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
1616 RpcTest::identity(EthGetLogs::request((EthFilterSpec {
1617 from_block: Some(format!("0x{:x}", shared_tipset.epoch() - 100)),
1618 to_block: Some(format!("0x{:x}", shared_tipset.epoch())),
1619 ..Default::default()
1620 },))?)
1621 .sort_policy(SortPolicy::All)
1622 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
1623 RpcTest::identity(EthGetLogs::request((EthFilterSpec {
1624 address: Some(EthAddressList::Single(EthAddress::from_str(
1625 "0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44",
1626 )?)),
1627 ..Default::default()
1628 },))?)
1629 .sort_policy(SortPolicy::All)
1630 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
1631 RpcTest::identity(EthGetFilterLogs::request((FilterID::new()?,))?)
1632 .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError),
1633 RpcTest::identity(EthGetFilterChanges::request((FilterID::new()?,))?)
1634 .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError),
1635 RpcTest::identity(EthGetTransactionHashByCid::request((block_cid,))?),
1636 RpcTest::identity(
1637 EthGetTransactionReceipt::request((
1638 EthHash::from_str(
1641 "0xf234567890123456789d6a7b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f70809",
1642 )
1643 .unwrap(),
1644 ))
1645 .unwrap(),
1646 ),
1647 ];
1648
1649 for api_path in [ApiPaths::V1, ApiPaths::V2] {
1650 tests.extend([
1651 RpcTest::basic(
1655 EthGetBlockReceipts::request((Predefined::Latest.into(),))?.with_api_path(api_path),
1656 ),
1657 RpcTest::basic(
1658 EthGetBlockReceipts::request((Predefined::Latest.into(),))?.with_api_path(api_path),
1659 ),
1660 RpcTest::basic(
1661 EthGetBlockReceipts::request((Predefined::Latest.into(),))?.with_api_path(api_path),
1662 ),
1663 RpcTest::identity(
1664 EthGetBlockReceipts::request((BlockNumberOrHash::from_block_hash_object(
1665 block_hash, true,
1666 ),))?
1667 .with_api_path(api_path),
1668 ),
1669 RpcTest::identity(
1670 EthGetBlockTransactionCountByHash::request((block_hash,))?.with_api_path(api_path),
1671 ),
1672 RpcTest::identity(
1673 EthGetBlockReceiptsLimited::request((
1674 BlockNumberOrHash::from_block_hash_object(block_hash, true),
1675 4,
1676 ))?
1677 .with_api_path(api_path),
1678 )
1679 .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError),
1680 RpcTest::identity(
1681 EthGetBlockReceiptsLimited::request((
1682 BlockNumberOrHash::from_block_hash_object(block_hash, true),
1683 -1,
1684 ))?
1685 .with_api_path(api_path),
1686 ),
1687 RpcTest::identity(
1688 EthGetBlockTransactionCountByNumber::request((
1689 EthInt64(shared_tipset.epoch()).into(),
1690 ))?
1691 .with_api_path(api_path),
1692 ),
1693 RpcTest::identity(
1694 EthGetBlockTransactionCountByNumber::request((Predefined::Latest.into(),))?
1695 .with_api_path(api_path),
1696 ),
1697 RpcTest::identity(
1698 EthGetBlockTransactionCountByNumber::request((Predefined::Safe.into(),))?
1699 .with_api_path(api_path),
1700 ),
1701 RpcTest::identity(
1702 EthGetBlockTransactionCountByNumber::request((Predefined::Finalized.into(),))?
1703 .with_api_path(api_path),
1704 ),
1705 RpcTest::identity(
1706 EthGetBlockByNumber::request((EthInt64(shared_tipset.epoch()).into(), false))?
1707 .with_api_path(api_path),
1708 ),
1709 RpcTest::identity(
1710 EthGetBlockByNumber::request((EthInt64(shared_tipset.epoch()).into(), true))?
1711 .with_api_path(api_path),
1712 ),
1713 RpcTest::identity(
1714 EthGetBlockByNumber::request((Predefined::Earliest.into(), true))?
1715 .with_api_path(api_path),
1716 )
1717 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
1718 RpcTest::basic(
1719 EthGetBlockByNumber::request((Predefined::Pending.into(), true))?
1720 .with_api_path(api_path),
1721 ),
1722 RpcTest::basic(
1723 EthGetBlockByNumber::request((Predefined::Latest.into(), true))?
1724 .with_api_path(api_path),
1725 ),
1726 RpcTest::basic(
1727 EthGetBlockByNumber::request((Predefined::Safe.into(), true))?
1728 .with_api_path(api_path),
1729 ),
1730 RpcTest::basic(
1731 EthGetBlockByNumber::request((Predefined::Finalized.into(), true))?
1732 .with_api_path(api_path),
1733 ),
1734 RpcTest::identity(
1735 EthGetBalance::request((
1736 generate_eth_random_address()?,
1737 Predefined::Latest.into(),
1738 ))?
1739 .with_api_path(api_path),
1740 ),
1741 RpcTest::identity(
1742 EthGetBalance::request((
1743 EthAddress::from_str("0xff38c072f286e3b20b3954ca9f99c05fbecc64aa")?,
1744 BlockNumberOrHash::from_block_number(shared_tipset.epoch()),
1745 ))?
1746 .with_api_path(api_path),
1747 ),
1748 RpcTest::identity(
1749 EthGetBalance::request((
1750 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
1751 BlockNumberOrHash::from_block_number(shared_tipset.epoch()),
1752 ))?
1753 .with_api_path(api_path),
1754 ),
1755 RpcTest::identity(
1756 EthGetBalance::request((
1757 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
1758 BlockNumberOrHash::from_block_number_object(shared_tipset.epoch()),
1759 ))?
1760 .with_api_path(api_path),
1761 ),
1762 RpcTest::identity(
1763 EthGetBalance::request((
1764 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
1765 BlockNumberOrHash::from_block_hash_object(block_hash, false),
1766 ))?
1767 .with_api_path(api_path),
1768 ),
1769 RpcTest::identity(
1770 EthGetBalance::request((
1771 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
1772 BlockNumberOrHash::from_block_hash_object(block_hash, true),
1773 ))?
1774 .with_api_path(api_path),
1775 ),
1776 RpcTest::identity(
1777 EthGetBalance::request((
1778 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
1779 Predefined::Earliest.into(),
1780 ))?
1781 .with_api_path(api_path),
1782 )
1783 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
1784 RpcTest::basic(
1785 EthGetBalance::request((
1786 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
1787 Predefined::Pending.into(),
1788 ))?
1789 .with_api_path(api_path),
1790 ),
1791 RpcTest::basic(
1792 EthGetBalance::request((
1793 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
1794 Predefined::Latest.into(),
1795 ))?
1796 .with_api_path(api_path),
1797 ),
1798 RpcTest::basic(
1799 EthGetBalance::request((
1800 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
1801 Predefined::Safe.into(),
1802 ))?
1803 .with_api_path(api_path),
1804 ),
1805 RpcTest::basic(
1806 EthGetBalance::request((
1807 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
1808 Predefined::Finalized.into(),
1809 ))?
1810 .with_api_path(api_path),
1811 ),
1812 RpcTest::identity(
1813 EthGetBalance::request((
1814 generate_eth_random_address()?,
1815 Predefined::Latest.into(),
1816 ))?
1817 .with_api_path(api_path),
1818 ),
1819 RpcTest::identity(
1820 EthFeeHistory::request((10.into(), EthInt64(shared_tipset.epoch()).into(), None))?
1821 .with_api_path(api_path),
1822 ),
1823 RpcTest::identity(
1824 EthFeeHistory::request((
1825 10.into(),
1826 EthInt64(shared_tipset.epoch()).into(),
1827 Some(vec![10., 50., 90.]),
1828 ))?
1829 .with_api_path(api_path),
1830 ),
1831 RpcTest::identity(
1832 EthFeeHistory::request((10.into(), Predefined::Earliest.into(), None))?
1833 .with_api_path(api_path),
1834 )
1835 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
1836 RpcTest::basic(
1837 EthFeeHistory::request((
1838 10.into(),
1839 Predefined::Pending.into(),
1840 Some(vec![10., 50., 90.]),
1841 ))?
1842 .with_api_path(api_path),
1843 ),
1844 RpcTest::basic(
1845 EthFeeHistory::request((10.into(), Predefined::Latest.into(), None))?
1846 .with_api_path(api_path),
1847 ),
1848 RpcTest::basic(
1849 EthFeeHistory::request((10.into(), Predefined::Safe.into(), None))?
1850 .with_api_path(api_path),
1851 ),
1852 RpcTest::basic(
1853 EthFeeHistory::request((
1854 10.into(),
1855 Predefined::Finalized.into(),
1856 Some(vec![10., 50., 90.]),
1857 ))?
1858 .with_api_path(api_path),
1859 ),
1860 RpcTest::identity(
1861 EthGetCode::request((
1862 EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44")?,
1864 BlockNumberOrHash::from_block_number(shared_tipset.epoch()),
1865 ))?
1866 .with_api_path(api_path),
1867 ),
1868 RpcTest::identity(
1869 EthGetCode::request((
1870 Address::from_str("f410fpoidg73f7krlfohnla52dotowde5p2sejxnd4mq")?
1872 .try_into()?,
1873 BlockNumberOrHash::from_block_number(shared_tipset.epoch()),
1874 ))?
1875 .with_api_path(api_path),
1876 ),
1877 RpcTest::identity(
1878 EthGetCode::request((
1879 EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44")?,
1880 Predefined::Earliest.into(),
1881 ))?
1882 .with_api_path(api_path),
1883 )
1884 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
1885 RpcTest::basic(
1886 EthGetCode::request((
1887 EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44")?,
1888 Predefined::Pending.into(),
1889 ))?
1890 .with_api_path(api_path),
1891 ),
1892 RpcTest::basic(
1893 EthGetCode::request((
1894 EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44")?,
1895 Predefined::Safe.into(),
1896 ))?
1897 .with_api_path(api_path),
1898 ),
1899 RpcTest::basic(
1900 EthGetCode::request((
1901 EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44")?,
1902 Predefined::Finalized.into(),
1903 ))?
1904 .with_api_path(api_path),
1905 ),
1906 RpcTest::basic(
1907 EthGetCode::request((
1908 EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44")?,
1909 Predefined::Latest.into(),
1910 ))?
1911 .with_api_path(api_path),
1912 ),
1913 RpcTest::identity(
1914 EthGetCode::request((generate_eth_random_address()?, Predefined::Latest.into()))?
1915 .with_api_path(api_path),
1916 ),
1917 RpcTest::identity(
1918 EthGetStorageAt::request((
1919 EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44")?,
1921 EthBytes(vec![0xa]),
1922 BlockNumberOrHash::BlockNumber(EthInt64(shared_tipset.epoch())),
1923 ))?
1924 .with_api_path(api_path),
1925 ),
1926 RpcTest::identity(
1927 EthGetStorageAt::request((
1928 EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44")?,
1929 EthBytes(vec![0xa]),
1930 Predefined::Earliest.into(),
1931 ))?
1932 .with_api_path(api_path),
1933 )
1934 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
1935 RpcTest::basic(
1936 EthGetStorageAt::request((
1937 EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44")?,
1938 EthBytes(vec![0xa]),
1939 Predefined::Pending.into(),
1940 ))?
1941 .with_api_path(api_path),
1942 ),
1943 RpcTest::basic(
1944 EthGetStorageAt::request((
1945 EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44")?,
1946 EthBytes(vec![0xa]),
1947 Predefined::Latest.into(),
1948 ))?
1949 .with_api_path(api_path),
1950 ),
1951 RpcTest::basic(
1952 EthGetStorageAt::request((
1953 EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44")?,
1954 EthBytes(vec![0xa]),
1955 Predefined::Safe.into(),
1956 ))?
1957 .with_api_path(api_path),
1958 ),
1959 RpcTest::basic(
1960 EthGetStorageAt::request((
1961 EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44")?,
1962 EthBytes(vec![0xa]),
1963 Predefined::Finalized.into(),
1964 ))?
1965 .with_api_path(api_path),
1966 ),
1967 RpcTest::identity(
1968 EthGetStorageAt::request((
1969 generate_eth_random_address()?,
1970 EthBytes(vec![0x0]),
1971 Predefined::Latest.into(),
1972 ))?
1973 .with_api_path(api_path),
1974 ),
1975 RpcTest::identity(
1976 EthGetTransactionCount::request((
1977 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
1978 BlockNumberOrHash::from_block_hash_object(block_hash, true),
1979 ))?
1980 .with_api_path(api_path),
1981 ),
1982 RpcTest::identity(
1983 EthGetTransactionCount::request((
1984 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
1985 Predefined::Earliest.into(),
1986 ))?
1987 .with_api_path(api_path),
1988 )
1989 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
1990 RpcTest::basic(
1991 EthGetTransactionCount::request((
1992 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
1993 Predefined::Pending.into(),
1994 ))?
1995 .with_api_path(api_path),
1996 ),
1997 RpcTest::basic(
1998 EthGetTransactionCount::request((
1999 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
2000 Predefined::Latest.into(),
2001 ))?
2002 .with_api_path(api_path),
2003 ),
2004 RpcTest::basic(
2005 EthGetTransactionCount::request((
2006 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
2007 Predefined::Safe.into(),
2008 ))?
2009 .with_api_path(api_path),
2010 ),
2011 RpcTest::basic(
2012 EthGetTransactionCount::request((
2013 EthAddress::from_str("0xff000000000000000000000000000000000003ec")?,
2014 Predefined::Finalized.into(),
2015 ))?
2016 .with_api_path(api_path),
2017 ),
2018 RpcTest::identity(
2019 EthGetTransactionCount::request((
2020 generate_eth_random_address()?,
2021 Predefined::Latest.into(),
2022 ))?
2023 .with_api_path(api_path),
2024 ),
2025 RpcTest::identity(
2026 EthGetTransactionByBlockNumberAndIndex::request((
2027 EthInt64(shared_tipset.epoch()).into(),
2028 0.into(),
2029 ))?
2030 .with_api_path(api_path),
2031 )
2032 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
2033 RpcTest::identity(
2034 EthGetTransactionByBlockNumberAndIndex::request((
2035 Predefined::Earliest.into(),
2036 0.into(),
2037 ))?
2038 .with_api_path(api_path),
2039 )
2040 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
2041 RpcTest::identity(
2042 EthGetTransactionByBlockNumberAndIndex::request((
2043 Predefined::Pending.into(),
2044 0.into(),
2045 ))?
2046 .with_api_path(api_path),
2047 )
2048 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
2049 RpcTest::identity(
2050 EthGetTransactionByBlockNumberAndIndex::request((
2051 Predefined::Latest.into(),
2052 0.into(),
2053 ))?
2054 .with_api_path(api_path),
2055 )
2056 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
2057 RpcTest::identity(
2058 EthGetTransactionByBlockNumberAndIndex::request((
2059 Predefined::Safe.into(),
2060 0.into(),
2061 ))?
2062 .with_api_path(api_path),
2063 )
2064 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
2065 RpcTest::identity(
2066 EthGetTransactionByBlockNumberAndIndex::request((
2067 Predefined::Finalized.into(),
2068 0.into(),
2069 ))?
2070 .with_api_path(api_path),
2071 )
2072 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
2073 RpcTest::identity(
2074 EthTraceBlock::request((BlockNumberOrHash::from_block_number(
2075 shared_tipset.epoch(),
2076 ),))?
2077 .with_api_path(api_path),
2078 ),
2079 RpcTest::identity(
2080 EthTraceBlock::request((Predefined::Earliest.into(),))?.with_api_path(api_path),
2081 )
2082 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
2083 RpcTest::basic(
2084 EthTraceBlock::request((Predefined::Pending.into(),))?.with_api_path(api_path),
2085 ),
2086 RpcTest::basic(
2087 EthTraceBlock::request((Predefined::Latest.into(),))?.with_api_path(api_path),
2088 ),
2089 RpcTest::basic(
2090 EthTraceBlock::request((Predefined::Safe.into(),))?.with_api_path(api_path),
2091 ),
2092 RpcTest::basic(
2093 EthTraceBlock::request((Predefined::Finalized.into(),))?.with_api_path(api_path),
2094 ),
2095 RpcTest::identity(
2096 EthTraceReplayBlockTransactions::request((
2097 BlockNumberOrHash::from_block_number(shared_tipset.epoch()),
2098 vec!["trace".to_string()],
2099 ))?
2100 .with_api_path(api_path),
2101 ),
2102 RpcTest::identity(
2103 EthTraceReplayBlockTransactions::request((
2104 BlockNumberOrHash::PredefinedBlock(Predefined::Latest),
2105 vec!["trace".to_string()],
2106 ))?
2107 .with_api_path(api_path),
2108 ),
2109 RpcTest::identity(
2110 EthTraceReplayBlockTransactions::request((
2111 BlockNumberOrHash::PredefinedBlock(Predefined::Safe),
2112 vec!["trace".to_string()],
2113 ))?
2114 .with_api_path(api_path),
2115 ),
2116 RpcTest::identity(
2117 EthTraceReplayBlockTransactions::request((
2118 BlockNumberOrHash::PredefinedBlock(Predefined::Finalized),
2119 vec!["trace".to_string()],
2120 ))?
2121 .with_api_path(api_path),
2122 ),
2123 RpcTest::identity(
2124 EthTraceFilter::request((EthTraceFilterCriteria {
2125 from_block: Some(format!("0x{:x}", shared_tipset.epoch() - 100)),
2126 to_block: Some(format!(
2127 "0x{:x}",
2128 shared_tipset.epoch() - SAFE_EPOCH_DELAY_FOR_TESTING
2129 )),
2130 ..Default::default()
2131 },))?
2132 .with_api_path(api_path),
2133 )
2134 .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError),
2137 RpcTest::identity(
2138 EthTraceFilter::request((EthTraceFilterCriteria {
2139 from_block: Some(format!(
2140 "0x{:x}",
2141 shared_tipset.epoch() - (SAFE_EPOCH_DELAY_FOR_TESTING + 1)
2142 )),
2143 to_block: Some(format!(
2144 "0x{:x}",
2145 shared_tipset.epoch() - SAFE_EPOCH_DELAY_FOR_TESTING
2146 )),
2147 ..Default::default()
2148 },))?
2149 .with_api_path(api_path),
2150 )
2151 .policy_on_rejected(PolicyOnRejected::PassWithIdenticalError),
2152 RpcTest::basic(
2153 EthTraceFilter::request((EthTraceFilterCriteria {
2154 from_block: Some(Predefined::Safe.to_string()),
2155 count: Some(1.into()),
2156 ..Default::default()
2157 },))?
2158 .with_api_path(api_path),
2159 ),
2160 RpcTest::identity(
2161 EthTraceFilter::request((EthTraceFilterCriteria {
2162 from_block: Some(Predefined::Finalized.to_string()),
2163 count: Some(1.into()),
2164 ..Default::default()
2165 },))?
2166 .with_api_path(api_path),
2167 )
2168 .ignore("`finalized` is not supported by Lotus yet"),
2169 RpcTest::identity(EthTraceFilter::request((EthTraceFilterCriteria {
2170 from_block: Some(Predefined::Latest.to_string()),
2171 count: Some(1.into()),
2172 ..Default::default()
2173 },))?),
2174 RpcTest::identity(
2175 EthTraceFilter::request((EthTraceFilterCriteria {
2176 count: Some(1.into()),
2177 ..Default::default()
2178 },))
2179 .unwrap(),
2180 ),
2181 ]);
2182 }
2183
2184 for block in shared_tipset.block_headers() {
2185 tests.extend([
2186 RpcTest::identity(FilecoinAddressToEthAddress::request((
2187 block.miner_address,
2188 Some(Predefined::Latest.into()),
2189 ))?),
2190 RpcTest::identity(FilecoinAddressToEthAddress::request((
2191 block.miner_address,
2192 Some(Predefined::Safe.into()),
2193 ))?),
2194 RpcTest::identity(FilecoinAddressToEthAddress::request((
2195 block.miner_address,
2196 Some(Predefined::Finalized.into()),
2197 ))?),
2198 ]);
2199 let (bls_messages, secp_messages) = crate::chain::store::block_messages(store, block)?;
2200 for msg in sample_messages(bls_messages.iter(), secp_messages.iter()) {
2201 tests.extend([RpcTest::identity(FilecoinAddressToEthAddress::request((
2202 msg.from(),
2203 Some(Predefined::Latest.into()),
2204 ))?)]);
2205 if let Ok(eth_to_addr) = EthAddress::try_from(msg.to) {
2206 for api_path in [ApiPaths::V1, ApiPaths::V2] {
2207 tests.extend([RpcTest::identity(
2208 EthEstimateGas::request((
2209 EthCallMessage {
2210 to: Some(eth_to_addr),
2211 value: Some(msg.value.clone().into()),
2212 data: Some(msg.params.clone().into()),
2213 ..Default::default()
2214 },
2215 Some(BlockNumberOrHash::BlockNumber(shared_tipset.epoch().into())),
2216 ))?
2217 .with_api_path(api_path),
2218 )
2219 .policy_on_rejected(PolicyOnRejected::Pass)]);
2220 }
2221 }
2222 }
2223 }
2224
2225 Ok(tests)
2226}
2227
2228fn read_state_api_tests(tipset: &Tipset) -> anyhow::Result<Vec<RpcTest>> {
2229 let tests = vec![
2230 RpcTest::identity(StateReadState::request((
2231 Address::SYSTEM_ACTOR,
2232 tipset.key().into(),
2233 ))?),
2234 RpcTest::identity(StateReadState::request((
2235 Address::SYSTEM_ACTOR,
2236 Default::default(),
2237 ))?),
2238 RpcTest::identity(StateReadState::request((
2239 Address::CRON_ACTOR,
2240 tipset.key().into(),
2241 ))?),
2242 RpcTest::identity(StateReadState::request((
2243 Address::MARKET_ACTOR,
2244 tipset.key().into(),
2245 ))?),
2246 RpcTest::identity(StateReadState::request((
2247 Address::INIT_ACTOR,
2248 tipset.key().into(),
2249 ))?),
2250 RpcTest::identity(StateReadState::request((
2251 Address::POWER_ACTOR,
2252 tipset.key().into(),
2253 ))?),
2254 RpcTest::identity(StateReadState::request((
2255 Address::REWARD_ACTOR,
2256 tipset.key().into(),
2257 ))?),
2258 RpcTest::identity(StateReadState::request((
2259 Address::VERIFIED_REGISTRY_ACTOR,
2260 tipset.key().into(),
2261 ))?),
2262 RpcTest::identity(StateReadState::request((
2263 Address::DATACAP_TOKEN_ACTOR,
2264 tipset.key().into(),
2265 ))?),
2266 RpcTest::identity(StateReadState::request((
2267 Address::new_id(66116), tipset.key().into(),
2270 ))?),
2271 RpcTest::identity(StateReadState::request((
2272 Address::new_id(18101), tipset.key().into(),
2275 ))?),
2276 RpcTest::identity(StateReadState::request((
2277 ACCOUNT_ADDRESS,
2278 tipset.key().into(),
2279 ))?),
2280 RpcTest::identity(StateReadState::request((
2281 MINER_ADDRESS,
2282 tipset.key().into(),
2283 ))?),
2284 RpcTest::identity(StateReadState::request((
2285 Address::from_str(EVM_ADDRESS)?, tipset.key().into(),
2287 ))?),
2288 ];
2289
2290 Ok(tests)
2291}
2292
2293fn eth_state_tests_with_tipset<DB: Blockstore>(
2294 store: &Arc<DB>,
2295 shared_tipset: &Tipset,
2296 eth_chain_id: EthChainIdType,
2297) -> anyhow::Result<Vec<RpcTest>> {
2298 let mut tests = vec![];
2299
2300 for block in shared_tipset.block_headers() {
2301 let state = StateTree::new_from_root(store.clone(), shared_tipset.parent_state())?;
2302
2303 let (bls_messages, secp_messages) = crate::chain::store::block_messages(store, block)?;
2304 for smsg in sample_signed_messages(bls_messages.iter(), secp_messages.iter()) {
2305 let tx = new_eth_tx_from_signed_message(&smsg, &state, eth_chain_id)?;
2306 tests.push(RpcTest::identity(
2307 EthGetMessageCidByTransactionHash::request((tx.hash,))?,
2308 ));
2309 tests.push(RpcTest::identity(EthGetTransactionByHash::request((
2310 tx.hash,
2311 ))?));
2312 tests.push(RpcTest::identity(EthGetTransactionByHashLimited::request(
2313 (tx.hash, shared_tipset.epoch()),
2314 )?));
2315 tests.push(RpcTest::identity(EthTraceTransaction::request((tx
2316 .hash
2317 .to_string(),))?));
2318 if smsg.message.from.protocol() == Protocol::Delegated
2319 && smsg.message.to.protocol() == Protocol::Delegated
2320 {
2321 tests.push(
2322 RpcTest::identity(EthGetTransactionReceipt::request((tx.hash,))?)
2323 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
2324 );
2325 tests.push(
2326 RpcTest::identity(EthGetTransactionReceiptLimited::request((
2327 tx.hash,
2328 MESSAGE_LOOKBACK_LIMIT,
2329 ))?)
2330 .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError),
2331 );
2332 }
2333 }
2334 }
2335 tests.push(RpcTest::identity(
2336 EthGetMessageCidByTransactionHash::request((EthHash::from_str(
2337 "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355f",
2338 )?,))?,
2339 ));
2340
2341 tests.extend(eth_call_api_err_tests(shared_tipset.epoch()));
2343
2344 Ok(tests)
2345}
2346
2347fn gas_tests_with_tipset(shared_tipset: &Tipset) -> Vec<RpcTest> {
2348 let addr = Address::from_str("t15ydyu3d65gznpp2qxwpkjsgz4waubeunn6upvla").unwrap();
2351 let message = Message {
2352 from: addr,
2353 to: addr,
2354 value: TokenAmount::from_whole(1),
2355 method_num: METHOD_SEND,
2356 ..Default::default()
2357 };
2358
2359 vec![
2360 RpcTest::identity(
2366 GasEstimateGasLimit::request((message.clone(), shared_tipset.key().into())).unwrap(),
2367 ),
2368 RpcTest::validate(
2372 GasEstimateMessageGas::request((
2373 message,
2374 None, shared_tipset.key().into(),
2376 ))
2377 .unwrap(),
2378 |forest_api_msg, lotus_api_msg| {
2379 let forest_msg = forest_api_msg.message;
2380 let lotus_msg = lotus_api_msg.message;
2381 if forest_msg.gas_limit != lotus_msg.gas_limit {
2383 return false;
2384 }
2385
2386 let forest_fee_cap = &forest_msg.gas_fee_cap;
2388 let lotus_fee_cap = &lotus_msg.gas_fee_cap;
2389 let forest_premium = &forest_msg.gas_premium;
2390 let lotus_premium = &lotus_msg.gas_premium;
2391
2392 if [forest_fee_cap, lotus_fee_cap, forest_premium, lotus_premium]
2394 .iter()
2395 .any(|amt| amt.is_negative())
2396 {
2397 return false;
2398 }
2399
2400 forest_fee_cap.is_within_percent(lotus_fee_cap, 5)
2401 && forest_premium.is_within_percent(lotus_premium, 5)
2402 },
2403 ),
2404 ]
2405}
2406
2407fn f3_tests() -> anyhow::Result<Vec<RpcTest>> {
2408 Ok(vec![
2409 RpcTest::basic(F3GetECPowerTable::request((None.into(),))?),
2411 RpcTest::basic(F3GetLatestCertificate::request(())?),
2412 RpcTest::basic(F3ListParticipants::request(())?),
2413 RpcTest::basic(F3GetProgress::request(())?),
2414 RpcTest::basic(F3GetOrRenewParticipationTicket::request((
2415 Address::new_id(1000),
2416 vec![],
2417 3,
2418 ))?),
2419 RpcTest::identity(F3IsRunning::request(())?),
2420 RpcTest::identity(F3GetCertificate::request((0,))?),
2421 RpcTest::identity(F3GetCertificate::request((50,))?),
2422 RpcTest::identity(F3GetManifest::request(())?),
2423 ])
2424}
2425
2426fn f3_tests_with_tipset(tipset: &Tipset) -> anyhow::Result<Vec<RpcTest>> {
2427 Ok(vec![
2428 RpcTest::identity(F3GetECPowerTable::request((tipset.key().into(),))?),
2429 RpcTest::identity(F3GetF3PowerTable::request((tipset.key().into(),))?),
2430 ])
2431}
2432
2433fn snapshot_tests(
2436 store: Arc<ManyCar>,
2437 offline: bool,
2438 num_tipsets: usize,
2439 miner_address: Option<Address>,
2440 eth_chain_id: u64,
2441) -> anyhow::Result<Vec<RpcTest>> {
2442 let mut tests = vec![];
2443 let shared_tipset = store
2446 .heaviest_tipset()?
2447 .chain(&store)
2448 .take(SAFE_EPOCH_DELAY_FOR_TESTING as usize)
2449 .last()
2450 .expect("Infallible");
2451
2452 for tipset in shared_tipset.chain(&store).take(num_tipsets) {
2453 tests.extend(chain_tests_with_tipset(&store, offline, &tipset)?);
2454 tests.extend(miner_tests_with_tipset(&store, &tipset, miner_address)?);
2455 tests.extend(state_tests_with_tipset(&store, &tipset)?);
2456 tests.extend(eth_tests_with_tipset(&store, &tipset)?);
2457 tests.extend(event_tests_with_tipset(&store, &tipset)?);
2458 tests.extend(gas_tests_with_tipset(&tipset));
2459 tests.extend(mpool_tests_with_tipset(&tipset));
2460 tests.extend(eth_state_tests_with_tipset(&store, &tipset, eth_chain_id)?);
2461 tests.extend(f3_tests_with_tipset(&tipset)?);
2462 }
2463
2464 Ok(tests)
2465}
2466
2467fn sample_message_cids<'a>(
2468 bls_messages: impl Iterator<Item = &'a Message> + 'a,
2469 secp_messages: impl Iterator<Item = &'a SignedMessage> + 'a,
2470) -> impl Iterator<Item = Cid> + 'a {
2471 bls_messages
2472 .map(|m| m.cid())
2473 .unique()
2474 .take(COLLECTION_SAMPLE_SIZE)
2475 .chain(
2476 secp_messages
2477 .map(|m| m.cid())
2478 .unique()
2479 .take(COLLECTION_SAMPLE_SIZE),
2480 )
2481 .unique()
2482}
2483
2484fn sample_messages<'a>(
2485 bls_messages: impl Iterator<Item = &'a Message> + 'a,
2486 secp_messages: impl Iterator<Item = &'a SignedMessage> + 'a,
2487) -> impl Iterator<Item = &'a Message> + 'a {
2488 bls_messages
2489 .unique()
2490 .take(COLLECTION_SAMPLE_SIZE)
2491 .chain(
2492 secp_messages
2493 .map(SignedMessage::message)
2494 .unique()
2495 .take(COLLECTION_SAMPLE_SIZE),
2496 )
2497 .unique()
2498}
2499
2500fn sample_signed_messages<'a>(
2501 bls_messages: impl Iterator<Item = &'a Message> + 'a,
2502 secp_messages: impl Iterator<Item = &'a SignedMessage> + 'a,
2503) -> impl Iterator<Item = SignedMessage> + 'a {
2504 bls_messages
2505 .unique()
2506 .take(COLLECTION_SAMPLE_SIZE)
2507 .map(|msg| {
2508 let sig = Signature::new_bls(vec![]);
2509 SignedMessage::new_unchecked(msg.clone(), sig)
2510 })
2511 .chain(secp_messages.cloned().unique().take(COLLECTION_SAMPLE_SIZE))
2512 .unique()
2513}
2514
2515pub(super) async fn create_tests(
2516 CreateTestsArgs {
2517 offline,
2518 n_tipsets,
2519 miner_address,
2520 worker_address,
2521 eth_chain_id,
2522 snapshot_files,
2523 }: CreateTestsArgs,
2524) -> anyhow::Result<Vec<RpcTest>> {
2525 let mut tests = vec![];
2526 tests.extend(auth_tests()?);
2527 tests.extend(common_tests());
2528 tests.extend(chain_tests(offline));
2529 tests.extend(mpool_tests());
2530 tests.extend(net_tests());
2531 tests.extend(node_tests());
2532 tests.extend(wallet_tests(worker_address));
2533 tests.extend(eth_tests()?);
2534 tests.extend(f3_tests()?);
2535 if !snapshot_files.is_empty() {
2536 let store = Arc::new(ManyCar::try_from(snapshot_files.clone())?);
2537 revalidate_chain(store.clone(), n_tipsets).await?;
2538 tests.extend(snapshot_tests(
2539 store,
2540 offline,
2541 n_tipsets,
2542 miner_address,
2543 eth_chain_id,
2544 )?);
2545 }
2546 tests.sort_by(|a, b| a.request.method_name.cmp(&b.request.method_name));
2547
2548 tests.extend(create_deferred_tests(snapshot_files)?);
2549 Ok(tests)
2550}
2551
2552fn create_deferred_tests(snapshot_files: Vec<PathBuf>) -> anyhow::Result<Vec<RpcTest>> {
2554 let mut tests = vec![];
2555
2556 if !snapshot_files.is_empty() {
2557 let store = Arc::new(ManyCar::try_from(snapshot_files)?);
2558 tests.push(RpcTest::identity(ChainSetHead::request((store
2559 .heaviest_tipset()?
2560 .key()
2561 .clone(),))?));
2562 }
2563
2564 Ok(tests)
2565}
2566
2567async fn revalidate_chain(db: Arc<ManyCar>, n_ts_to_validate: usize) -> anyhow::Result<()> {
2568 if n_ts_to_validate == 0 {
2569 return Ok(());
2570 }
2571 let chain_config = Arc::new(handle_chain_config(&NetworkChain::Calibnet)?);
2572
2573 let genesis_header = crate::genesis::read_genesis_header(
2574 None,
2575 chain_config.genesis_bytes(&db).await?.as_deref(),
2576 &db,
2577 )
2578 .await?;
2579 let chain_store = Arc::new(ChainStore::new(
2580 db.clone(),
2581 db.clone(),
2582 db.clone(),
2583 chain_config,
2584 genesis_header.clone(),
2585 )?);
2586 let state_manager = Arc::new(StateManager::new(chain_store.clone())?);
2587 let head_ts = db.heaviest_tipset()?;
2588
2589 proofs_api::maybe_set_proofs_parameter_cache_dir_env(&Config::default().client.data_dir);
2592 ensure_proof_params_downloaded().await?;
2593 state_manager.validate_tipsets(
2594 head_ts
2595 .chain(&db)
2596 .take(SAFE_EPOCH_DELAY_FOR_TESTING as usize + n_ts_to_validate),
2597 )?;
2598
2599 Ok(())
2600}
2601
2602#[allow(clippy::too_many_arguments)]
2603pub(super) async fn run_tests(
2604 tests: impl IntoIterator<Item = RpcTest>,
2605 forest: impl Into<Arc<rpc::Client>>,
2606 lotus: impl Into<Arc<rpc::Client>>,
2607 max_concurrent_requests: usize,
2608 filter_file: Option<PathBuf>,
2609 filter: String,
2610 filter_version: Option<rpc::ApiPaths>,
2611 run_ignored: RunIgnored,
2612 fail_fast: bool,
2613 dump_dir: Option<PathBuf>,
2614 test_criteria_overrides: &[TestCriteriaOverride],
2615 report_dir: Option<PathBuf>,
2616 report_mode: ReportMode,
2617 n_retries: usize,
2618) -> anyhow::Result<()> {
2619 let forest = Into::<Arc<rpc::Client>>::into(forest);
2620 let lotus = Into::<Arc<rpc::Client>>::into(lotus);
2621 let semaphore = Arc::new(Semaphore::new(max_concurrent_requests));
2622 let mut tasks = JoinSet::new();
2623
2624 let filter_list = if let Some(filter_file) = &filter_file {
2625 FilterList::new_from_file(filter_file)?
2626 } else {
2627 FilterList::default().allow(filter.clone())
2628 };
2629
2630 let mut report_builder = ReportBuilder::new(&filter_list, report_mode);
2632
2633 for test in tests.into_iter().unique_by(
2635 |RpcTest {
2636 request:
2637 rpc::Request {
2638 method_name,
2639 params,
2640 api_path,
2641 ..
2642 },
2643 ignore,
2644 ..
2645 }| {
2646 (
2647 method_name.clone(),
2648 params.clone(),
2649 *api_path,
2650 ignore.is_some(),
2651 )
2652 },
2653 ) {
2654 if matches!(run_ignored, RunIgnored::Default) && test.ignore.is_some() {
2656 continue;
2657 }
2658 if matches!(run_ignored, RunIgnored::IgnoredOnly) && test.ignore.is_none() {
2660 continue;
2661 }
2662
2663 if !filter_list.authorize(&test.request.method_name) {
2664 continue;
2665 }
2666
2667 if let Some(filter_version) = filter_version
2668 && test.request.api_path != filter_version
2669 {
2670 continue;
2671 }
2672
2673 let semaphore = semaphore.clone();
2675 let forest = forest.clone();
2676 let lotus = lotus.clone();
2677 let test_criteria_overrides = test_criteria_overrides.to_vec();
2678 tasks.spawn(async move {
2679 let mut n_retries_left = n_retries;
2680 let mut backoff_secs = 2;
2681 loop {
2682 {
2683 let _permit = semaphore.acquire().await;
2685 let test_result = test.run(&forest, &lotus).await;
2686 let success =
2687 evaluate_test_success(&test_result, &test, &test_criteria_overrides);
2688 if success || n_retries_left == 0 {
2689 return (success, test, test_result);
2690 }
2691 }
2693 tokio::time::sleep(Duration::from_secs(backoff_secs)).await;
2695 n_retries_left = n_retries_left.saturating_sub(1);
2696 backoff_secs = backoff_secs.saturating_mul(2);
2697 }
2698 });
2699 }
2700
2701 if tasks.is_empty() {
2703 return Ok(());
2704 }
2705
2706 while let Some(result) = tasks.join_next().await {
2707 match result {
2708 Ok((success, test, test_result)) => {
2709 let method_name = test.request.method_name.clone();
2710
2711 report_builder.track_test_result(
2712 method_name.as_ref(),
2713 success,
2714 &test_result,
2715 &test.request.params,
2716 );
2717
2718 if let (Some(dump_dir), Some(test_dump)) = (&dump_dir, &test_result.test_dump) {
2720 dump_test_data(dump_dir, success, test_dump)?;
2721 }
2722
2723 if !success && fail_fast {
2724 break;
2725 }
2726 }
2727 Err(e) => tracing::warn!("{e}"),
2728 }
2729 }
2730
2731 let has_failures = report_builder.has_failures();
2732 report_builder.print_summary();
2733
2734 if let Some(path) = report_dir {
2735 report_builder.finalize_and_save(&path)?;
2736 }
2737
2738 anyhow::ensure!(!has_failures, "Some tests failed");
2739
2740 Ok(())
2741}
2742
2743fn evaluate_test_success(
2745 test_result: &TestResult,
2746 test: &RpcTest,
2747 test_criteria_overrides: &[TestCriteriaOverride],
2748) -> bool {
2749 match (&test_result.forest_status, &test_result.lotus_status) {
2750 (TestSummary::Valid, TestSummary::Valid) => true,
2751 (TestSummary::Valid, TestSummary::Timeout) => {
2752 test_criteria_overrides.contains(&TestCriteriaOverride::ValidAndTimeout)
2753 }
2754 (TestSummary::Timeout, TestSummary::Timeout) => {
2755 test_criteria_overrides.contains(&TestCriteriaOverride::TimeoutAndTimeout)
2756 }
2757 (TestSummary::Rejected(reason_forest), TestSummary::Rejected(reason_lotus)) => {
2758 match test.policy_on_rejected {
2759 PolicyOnRejected::Pass => true,
2760 PolicyOnRejected::PassWithIdenticalError => reason_forest == reason_lotus,
2761 PolicyOnRejected::PassWithIdenticalErrorCaseInsensitive => {
2762 reason_forest.eq_ignore_ascii_case(reason_lotus)
2763 }
2764 PolicyOnRejected::PassWithQuasiIdenticalError => {
2765 reason_lotus.contains(reason_forest) || reason_forest.contains(reason_lotus)
2766 }
2767 _ => false,
2768 }
2769 }
2770 _ => false,
2771 }
2772}
2773
2774fn normalized_error_message(s: &str) -> Cow<'_, str> {
2775 let lotus_gateway_error_prefix = lazy_regex::regex!(r#"^RPC\serror\s\(-?\d+\):\s*"#);
2777 lotus_gateway_error_prefix.replace(s, "")
2778}
2779
2780fn dump_test_data(dump_dir: &Path, success: bool, test_dump: &TestDump) -> anyhow::Result<()> {
2782 let dir = dump_dir.join(if success { "valid" } else { "invalid" });
2783 if !dir.is_dir() {
2784 std::fs::create_dir_all(&dir)?;
2785 }
2786 let file_name = format!(
2787 "{}_{}.json",
2788 test_dump
2789 .request
2790 .method_name
2791 .as_ref()
2792 .replace(".", "_")
2793 .to_lowercase(),
2794 Utc::now().timestamp_micros()
2795 );
2796 std::fs::write(
2797 dir.join(file_name),
2798 serde_json::to_string_pretty(test_dump)?,
2799 )?;
2800 Ok(())
2801}
2802
2803fn validate_message_wait(req: rpc::Request<MessageLookup>) -> RpcTest {
2804 RpcTest::validate(req, |mut forest, mut lotus| {
2805 forest.return_dec = Ipld::Null;
2807 lotus.return_dec = Ipld::Null;
2808 forest == lotus
2809 })
2810}
2811
2812fn validate_message_lookup(req: rpc::Request<Option<MessageLookup>>) -> RpcTest {
2813 RpcTest::validate(req, |mut forest, mut lotus| {
2814 if let Some(forest) = &mut forest {
2816 forest.return_dec = Ipld::Null;
2817 }
2818 if let Some(lotus) = &mut lotus {
2819 lotus.return_dec = Ipld::Null;
2820 }
2821 forest == lotus
2822 })
2823}
2824
2825fn validate_tagged_tipset_v2(req: rpc::Request<Tipset>, offline: bool) -> RpcTest {
2826 RpcTest::validate(req, move |forest, lotus| {
2827 if offline {
2828 true
2829 } else {
2830 (forest.epoch() - lotus.epoch()).abs() <= 2
2831 }
2832 })
2833}
2834
2835#[cfg(test)]
2836mod tests {
2837 use super::*;
2838
2839 #[test]
2840 fn test_normalized_error_message_1() {
2841 let s = "RPC error (-32603): exactly one tipset selection criteria must be specified";
2842 let r = normalized_error_message(s);
2843 assert_eq!(
2844 r.as_ref(),
2845 "exactly one tipset selection criteria must be specified"
2846 );
2847 }
2848
2849 #[test]
2850 fn test_normalized_error_message_2() {
2851 let s = "exactly one tipset selection criteria must be specified";
2852 let r = normalized_error_message(s);
2853 assert_eq!(
2854 r.as_ref(),
2855 "exactly one tipset selection criteria must be specified"
2856 );
2857 }
2858}