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