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