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