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