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