1use std::collections::HashMap;
5use std::fs;
6use std::net::IpAddr;
7use std::net::Ipv4Addr;
8use std::net::SocketAddr;
9use std::path::Path;
10use std::time::Duration;
11
12use anyhow::Context;
13use anyhow::ensure;
14use diesel::ExpressionMethods;
15use diesel::OptionalExtension;
16use diesel::QueryDsl;
17use diesel_async::RunQueryDsl;
18use prost::Message;
19use reqwest::Client;
20use serde_json::Value;
21use serde_json::json;
22use simulacrum::AdvanceEpochConfig;
23use simulacrum::Simulacrum;
24use sui_futures::service::Service;
25use sui_indexer_alt::BootstrapGenesis;
26use sui_indexer_alt::config::IndexerConfig;
27use sui_indexer_alt::setup_indexer;
28use sui_indexer_alt_consistent_api::proto::rpc::consistent::v1alpha::AvailableRangeRequest;
29use sui_indexer_alt_consistent_api::proto::rpc::consistent::v1alpha::consistent_service_client::ConsistentServiceClient;
30use sui_indexer_alt_consistent_store::args::RpcArgs as ConsistentArgs;
31use sui_indexer_alt_consistent_store::args::TlsArgs as ConsistentTlsArgs;
32use sui_indexer_alt_consistent_store::config::ServiceConfig as ConsistentConfig;
33use sui_indexer_alt_consistent_store::start_service as start_consistent_store;
34use sui_indexer_alt_framework::IndexerArgs;
35use sui_indexer_alt_framework::ingestion::ClientArgs;
36use sui_indexer_alt_framework::ingestion::ingestion_client::IngestionClientArgs;
37use sui_indexer_alt_framework::pipeline::CommitterConfig;
38use sui_indexer_alt_framework::postgres::schema::watermarks;
39use sui_indexer_alt_graphql::RpcArgs as GraphQlArgs;
40use sui_indexer_alt_graphql::args::SubscriptionArgs;
41use sui_indexer_alt_graphql::config::RpcConfig as GraphQlConfig;
42use sui_indexer_alt_graphql::start_rpc as start_graphql;
43use sui_indexer_alt_jsonrpc::NodeArgs as JsonRpcNodeArgs;
44use sui_indexer_alt_jsonrpc::RpcArgs as JsonRpcArgs;
45use sui_indexer_alt_jsonrpc::config::RpcConfig as JsonRpcConfig;
46use sui_indexer_alt_jsonrpc::start_rpc as start_jsonrpc;
47use sui_indexer_alt_reader::consistent_reader::ConsistentReaderArgs;
48use sui_indexer_alt_reader::fullnode_client::FullnodeArgs;
49use sui_indexer_alt_reader::kv_loader::KvArgs;
50use sui_indexer_alt_reader::system_package_task::SystemPackageTaskArgs;
51use sui_kv_rpc::KvRpcConfig;
52use sui_kv_rpc::KvRpcServer;
53use sui_kvstore::ALL_PIPELINE_NAMES;
54use sui_kvstore::BigTableClient;
55use sui_kvstore::BigTableIndexer;
56use sui_kvstore::BigTableStore;
57use sui_kvstore::IndexerConfig as BtIndexerConfig;
58use sui_kvstore::IngestionConfig as BtIngestionConfig;
59use sui_kvstore::KeyValueStoreReader;
60use sui_kvstore::PipelineLayer;
61use sui_kvstore::testing::BigTableEmulator;
62use sui_kvstore::testing::INSTANCE_ID;
63use sui_kvstore::testing::create_tables;
64use sui_pg_db::Db;
65use sui_pg_db::DbArgs;
66use sui_pg_db::temp::TempDb;
67use sui_pg_db::temp::get_available_port;
68use sui_protocol_config::Chain;
69use sui_rpc::field::FieldMask;
70use sui_rpc::field::FieldMaskUtil;
71use sui_rpc::merge::Merge;
72use sui_rpc::proto::sui::rpc;
73use sui_types::base_types::ObjectRef;
74use sui_types::base_types::SuiAddress;
75use sui_types::crypto::AccountKeyPair;
76use sui_types::effects::TransactionEffects;
77use sui_types::error::ExecutionError;
78use sui_types::full_checkpoint_content::Checkpoint;
79use sui_types::messages_checkpoint::VerifiedCheckpoint;
80use sui_types::transaction::Transaction;
81use tempfile::TempDir;
82use tokio::time::error::Elapsed;
83use tokio::time::interval;
84use tokio::try_join;
85use url::Url;
86
87pub mod coin_registry;
88pub mod find;
89pub mod graphql;
90pub mod move_helpers;
91pub mod transaction;
92
93pub struct FullCluster {
96 executor: Simulacrum,
98
99 offchain: OffchainCluster,
102
103 #[allow(unused)]
105 temp_dir: TempDir,
106}
107
108pub struct OffchainCluster {
116 consistent_listen_address: SocketAddr,
118
119 jsonrpc_listen_address: SocketAddr,
121
122 graphql_listen_address: SocketAddr,
124
125 kv_rpc_listen_address: SocketAddr,
127
128 bigtable_client: BigTableClient,
130
131 db: Db,
133
134 pipelines: Vec<&'static str>,
136
137 #[allow(unused)]
140 services: Service,
141
142 #[allow(unused)]
144 bigtable_emulator: BigTableEmulator,
145
146 #[allow(unused)]
148 database: TempDb,
149
150 #[allow(unused)]
153 dir: TempDir,
154}
155
156pub struct OffchainClusterConfig {
157 pub indexer_args: IndexerArgs,
158 pub consistent_indexer_args: IndexerArgs,
159 pub fullnode_args: FullnodeArgs,
160 pub indexer_config: IndexerConfig,
161 pub consistent_config: ConsistentConfig,
162 pub jsonrpc_config: JsonRpcConfig,
163 pub jsonrpc_node_args: JsonRpcNodeArgs,
164 pub graphql_config: GraphQlConfig,
165 pub bootstrap_genesis: Option<BootstrapGenesis>,
166 pub kv_rpc_config: KvRpcConfig,
167}
168
169impl FullCluster {
170 pub async fn new() -> anyhow::Result<Self> {
173 Self::new_with_configs(
174 Simulacrum::new(),
175 OffchainClusterConfig::default(),
176 &prometheus::Registry::new(),
177 )
178 .await
179 }
180
181 pub async fn new_with_configs(
185 mut executor: Simulacrum,
186 offchain_cluster_config: OffchainClusterConfig,
187 registry: &prometheus::Registry,
188 ) -> anyhow::Result<Self> {
189 let (client_args, temp_dir) = local_ingestion_client_args();
190 executor.set_data_ingestion_path(temp_dir.path().to_owned());
191
192 let offchain = OffchainCluster::new(client_args, offchain_cluster_config, registry)
193 .await
194 .context("Failed to create off-chain cluster")?;
195
196 Ok(Self {
197 executor,
198 offchain,
199 temp_dir,
200 })
201 }
202
203 pub fn reference_gas_price(&self) -> u64 {
205 self.executor.reference_gas_price()
206 }
207
208 pub fn funded_account(
211 &mut self,
212 amount: u64,
213 ) -> anyhow::Result<(SuiAddress, AccountKeyPair, ObjectRef)> {
214 self.executor.funded_account(amount)
215 }
216
217 pub fn request_gas(
220 &mut self,
221 address: SuiAddress,
222 amount: u64,
223 ) -> anyhow::Result<TransactionEffects> {
224 self.executor.request_gas(address, amount)
225 }
226
227 pub fn execute_transaction(
229 &mut self,
230 tx: Transaction,
231 ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
232 self.executor.execute_transaction(tx)
233 }
234
235 pub fn advance_clock(&mut self, duration: Duration) -> TransactionEffects {
237 self.executor.advance_clock(duration)
238 }
239
240 pub fn advance_epoch(&mut self) {
244 self.executor.advance_epoch(AdvanceEpochConfig::default());
245 }
246
247 pub async fn create_checkpoint(&mut self) -> VerifiedCheckpoint {
251 let checkpoint = self.executor.create_checkpoint();
252 let timeout = Duration::from_secs(100);
253 let indexer = self
254 .offchain
255 .wait_for_indexer(checkpoint.sequence_number, timeout);
256 let consistent_store = self
257 .offchain
258 .wait_for_consistent_store(checkpoint.sequence_number, timeout);
259 let graphql = self
260 .offchain
261 .wait_for_graphql(checkpoint.sequence_number, timeout);
262 let bigtable = self
263 .offchain
264 .wait_for_bigtable(checkpoint.sequence_number, timeout);
265
266 try_join!(indexer, consistent_store, graphql, bigtable)
267 .expect("Timed out waiting for off-chain services");
268
269 checkpoint
270 }
271
272 pub fn db_url(&self) -> Url {
274 self.offchain.db_url()
275 }
276
277 pub fn consistent_store_url(&self) -> Url {
279 self.offchain.consistent_store_url()
280 }
281
282 pub fn jsonrpc_url(&self) -> Url {
284 self.offchain.jsonrpc_url()
285 }
286
287 pub fn graphql_url(&self) -> Url {
289 self.offchain.graphql_url()
290 }
291
292 pub fn kv_rpc_url(&self) -> Url {
294 self.offchain.kv_rpc_url()
295 }
296
297 pub async fn latest_checkpoint(&self) -> anyhow::Result<Option<u64>> {
300 self.offchain.latest_checkpoint().await
301 }
302
303 pub async fn wait_for_indexer(
306 &self,
307 checkpoint: u64,
308 timeout: Duration,
309 ) -> Result<(), Elapsed> {
310 self.offchain.wait_for_indexer(checkpoint, timeout).await
311 }
312
313 pub async fn wait_for_pruner(
316 &self,
317 pipeline: &str,
318 checkpoint: u64,
319 timeout: Duration,
320 ) -> Result<(), Elapsed> {
321 self.offchain
322 .wait_for_pruner(pipeline, checkpoint, timeout)
323 .await
324 }
325
326 pub async fn wait_for_graphql(
329 &self,
330 checkpoint: u64,
331 timeout: Duration,
332 ) -> Result<(), Elapsed> {
333 self.offchain.wait_for_graphql(checkpoint, timeout).await
334 }
335}
336
337impl OffchainCluster {
338 pub async fn new(
346 client_args: ClientArgs,
347 OffchainClusterConfig {
348 indexer_args,
349 consistent_indexer_args,
350 fullnode_args,
351 indexer_config,
352 consistent_config,
353 jsonrpc_config,
354 jsonrpc_node_args,
355 graphql_config,
356 bootstrap_genesis,
357 kv_rpc_config,
358 }: OffchainClusterConfig,
359 registry: &prometheus::Registry,
360 ) -> anyhow::Result<Self> {
361 let consistent_port = get_available_port();
362 let consistent_listen_address =
363 SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), consistent_port);
364
365 let jsonrpc_port = get_available_port();
366 let jsonrpc_listen_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), jsonrpc_port);
367
368 let graphql_port = get_available_port();
369 let graphql_listen_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), graphql_port);
370
371 let kv_rpc_port = get_available_port();
372 let kv_rpc_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), kv_rpc_port);
373
374 let database = TempDb::new().context("Failed to create database")?;
375 let database_url = database.database().url();
376
377 let dir = tempfile::tempdir().context("Failed to create temporary directory")?;
378 let rocksdb_path = dir.path().join("rocksdb");
379
380 let consistent_args = ConsistentArgs {
381 rpc_listen_address: consistent_listen_address,
382 tls: ConsistentTlsArgs::default(),
383 };
384
385 let jsonrpc_args = JsonRpcArgs {
386 rpc_listen_address: jsonrpc_listen_address,
387 ..Default::default()
388 };
389
390 let graphql_args = GraphQlArgs {
391 rpc_listen_address: graphql_listen_address,
392 no_ide: true,
393 };
394
395 let db = Db::for_read(database_url.clone(), DbArgs::default())
396 .await
397 .context("Failed to connect to database")?;
398
399 let indexer = setup_indexer(
400 database_url.clone(),
401 DbArgs::default(),
402 indexer_args,
403 client_args.clone(),
404 indexer_config,
405 bootstrap_genesis,
406 registry,
407 )
408 .await
409 .context("Failed to setup indexer")?;
410
411 let pipelines: Vec<_> = indexer.pipelines().collect();
412 let indexer = indexer.run().await.context("Failed to start indexer")?;
413
414 let consistent_store = start_consistent_store(
415 rocksdb_path,
416 consistent_indexer_args,
417 client_args.clone(),
418 consistent_args,
419 "0.0.0",
420 consistent_config,
421 registry,
422 )
423 .await
424 .context("Failed to start Consistent Store")?;
425
426 let consistent_reader_args = ConsistentReaderArgs {
427 consistent_store_url: Some(
428 Url::parse(&format!("http://{consistent_listen_address}")).unwrap(),
429 ),
430 ..Default::default()
431 };
432
433 let enable_list_apis = kv_rpc_config.enable_list_apis();
437
438 let (bigtable_client, bigtable_emulator, archival_service) =
439 start_archival(client_args.clone(), kv_rpc_address, kv_rpc_config, registry).await?;
440
441 let kv_args = KvArgs {
442 ledger_grpc_url: Some(
443 format!("http://{kv_rpc_address}")
444 .parse()
445 .expect("Failed to parse kv-rpc URI"),
446 ),
447 enable_list_apis: Some(enable_list_apis),
448 ..Default::default()
449 };
450
451 let jsonrpc = start_jsonrpc(
452 Some(database_url.clone()),
453 DbArgs::default(),
454 kv_args.clone(),
455 consistent_reader_args.clone(),
456 jsonrpc_args,
457 jsonrpc_node_args,
458 SystemPackageTaskArgs::default(),
459 jsonrpc_config,
460 registry,
461 )
462 .await
463 .context("Failed to start JSON-RPC server")?;
464
465 let graphql = start_graphql(
466 Some(database_url.clone()),
467 fullnode_args,
468 DbArgs::default(),
469 kv_args,
470 consistent_reader_args,
471 graphql_args,
472 SystemPackageTaskArgs::default(),
473 SubscriptionArgs::default(),
474 "0.0.0",
475 graphql_config,
476 pipelines.iter().map(|p| p.to_string()).collect(),
477 registry,
478 )
479 .await
480 .context("Failed to start GraphQL server")?;
481
482 let services = indexer
483 .merge(consistent_store)
484 .merge(jsonrpc)
485 .merge(graphql)
486 .merge(archival_service);
487
488 Ok(Self {
489 consistent_listen_address,
490 jsonrpc_listen_address,
491 graphql_listen_address,
492 kv_rpc_listen_address: kv_rpc_address,
493 bigtable_client,
494 db,
495 pipelines,
496 services,
497 bigtable_emulator,
498 database,
499 dir,
500 })
501 }
502
503 pub fn db_url(&self) -> Url {
505 self.database.database().url().clone()
506 }
507
508 pub fn consistent_store_url(&self) -> Url {
510 Url::parse(&format!("http://{}/", self.consistent_listen_address))
511 .expect("Failed to parse RPC URL")
512 }
513
514 pub fn jsonrpc_url(&self) -> Url {
516 Url::parse(&format!("http://{}/", self.jsonrpc_listen_address))
517 .expect("Failed to parse RPC URL")
518 }
519
520 pub fn graphql_url(&self) -> Url {
522 Url::parse(&format!("http://{}/graphql", self.graphql_listen_address))
523 .expect("Failed to parse RPC URL")
524 }
525
526 pub fn kv_rpc_url(&self) -> Url {
528 Url::parse(&format!("http://{}/", self.kv_rpc_listen_address))
529 .expect("Failed to parse RPC URL")
530 }
531
532 pub async fn latest_checkpoint(&self) -> anyhow::Result<Option<u64>> {
535 use watermarks::dsl as w;
536
537 let mut conn = self
538 .db
539 .connect()
540 .await
541 .context("Failed to connect to database")?;
542
543 let latest: HashMap<String, i64> = w::watermarks
544 .select((w::pipeline, w::checkpoint_hi_inclusive))
545 .filter(w::pipeline.eq_any(&self.pipelines))
546 .filter(w::reader_lo.le(w::checkpoint_hi_inclusive))
547 .load(&mut conn)
548 .await?
549 .into_iter()
550 .collect();
551
552 if latest.len() != self.pipelines.len() {
553 return Ok(None);
554 }
555
556 Ok(latest.into_values().min().map(|l| l as u64))
557 }
558
559 pub async fn latest_pruner_checkpoint(&self, pipeline: &str) -> anyhow::Result<Option<u64>> {
562 use watermarks::dsl as w;
563
564 let mut conn = self
565 .db
566 .connect()
567 .await
568 .context("Failed to connect to database")?;
569
570 let latest: Option<i64> = w::watermarks
571 .select(w::reader_lo)
572 .filter(w::pipeline.eq(pipeline))
573 .first(&mut conn)
574 .await
575 .optional()?;
576
577 Ok(latest.map(|l| l as u64))
578 }
579
580 pub async fn latest_consistent_store_checkpoint(&self) -> anyhow::Result<u64> {
582 ConsistentServiceClient::connect(self.consistent_store_url().to_string())
583 .await
584 .context("Failed to connect to Consistent Store")?
585 .available_range(AvailableRangeRequest {})
586 .await
587 .context("Failed to fetch available range from Consistent Store")?
588 .into_inner()
589 .max_checkpoint
590 .context("Consistent Store has not started yet")
591 }
592
593 pub async fn latest_graphql_checkpoint(&self) -> anyhow::Result<u64> {
595 let query = json!({
596 "query": "query { checkpoint { sequenceNumber } }"
597 });
598
599 let client = Client::new();
600 let request = client.post(self.graphql_url()).json(&query);
601 let response = request
602 .send()
603 .await
604 .context("Request to GraphQL server failed")?;
605
606 let body: Value = response
607 .json()
608 .await
609 .context("Failed to parse GraphQL response")?;
610
611 let sequence_number = body
612 .pointer("/data/checkpoint/sequenceNumber")
613 .context("Failed to find checkpoint sequence number in response")?;
614
615 let sequence_number: i64 = serde_json::from_value(sequence_number.clone())
616 .context("Failed to parse sequence number as i64")?;
617
618 ensure!(sequence_number != i64::MAX, "Indexer has not started yet");
619
620 Ok(sequence_number as u64)
621 }
622
623 pub async fn latest_graphql_epoch(&self) -> anyhow::Result<u64> {
625 let query = json!({
626 "query": "query { epoch { epochId } }"
627 });
628
629 let client = Client::new();
630 let request = client.post(self.graphql_url()).json(&query);
631 let response = request
632 .send()
633 .await
634 .context("Request to GraphQL server failed")?;
635
636 let body: Value = response
637 .json()
638 .await
639 .context("Failed to parse GraphQL response")?;
640
641 let epoch_id = body
642 .pointer("/data/epoch/epochId")
643 .context("Failed to find epochId in response")?;
644
645 let epoch_id: i64 =
646 serde_json::from_value(epoch_id.clone()).context("Failed to parse epochId as i64")?;
647
648 ensure!(epoch_id != i64::MAX, "Indexer has not started yet");
649
650 Ok(epoch_id as u64)
651 }
652
653 pub async fn wait_for_indexer(
656 &self,
657 checkpoint: u64,
658 timeout: Duration,
659 ) -> Result<(), Elapsed> {
660 tokio::time::timeout(timeout, async move {
661 let mut interval = interval(Duration::from_millis(200));
662 loop {
663 interval.tick().await;
664 if matches!(self.latest_checkpoint().await, Ok(Some(l)) if l >= checkpoint) {
665 break;
666 }
667 }
668 })
669 .await
670 }
671
672 pub async fn wait_for_pruner(
675 &self,
676 pipeline: &str,
677 checkpoint: u64,
678 timeout: Duration,
679 ) -> Result<(), Elapsed> {
680 tokio::time::timeout(timeout, async move {
681 let mut interval = interval(Duration::from_millis(200));
682 loop {
683 interval.tick().await;
684 if matches!(self.latest_pruner_checkpoint(pipeline).await, Ok(Some(l)) if l >= checkpoint) {
685 break;
686 }
687 }
688 }).await
689 }
690
691 pub async fn wait_for_consistent_store(
694 &self,
695 checkpoint: u64,
696 timeout: Duration,
697 ) -> Result<(), Elapsed> {
698 tokio::time::timeout(timeout, async move {
699 let mut interval = interval(Duration::from_millis(200));
700 loop {
701 interval.tick().await;
702 if matches!(self.latest_consistent_store_checkpoint().await, Ok(l) if l >= checkpoint) {
703 break;
704 }
705 }
706 })
707 .await
708 }
709
710 pub async fn wait_for_graphql(
713 &self,
714 checkpoint: u64,
715 timeout: Duration,
716 ) -> Result<(), Elapsed> {
717 tokio::time::timeout(timeout, async move {
718 let mut interval = interval(Duration::from_millis(200));
719 loop {
720 interval.tick().await;
721 if matches!(self.latest_graphql_checkpoint().await, Ok(l) if l >= checkpoint) {
722 break;
723 }
724 }
725 })
726 .await
727 }
728
729 pub async fn wait_for_bigtable(
732 &self,
733 checkpoint: u64,
734 timeout: Duration,
735 ) -> Result<(), Elapsed> {
736 let mut client = self.bigtable_client.clone();
737 tokio::time::timeout(timeout, async move {
738 let mut interval = interval(Duration::from_millis(200));
739 loop {
740 interval.tick().await;
741 if client
742 .get_watermark_for_pipelines(&ALL_PIPELINE_NAMES)
743 .await
744 .is_ok_and(|wm| {
745 wm.is_some_and(|wm| {
746 wm.checkpoint_hi_inclusive
747 .is_some_and(|cp| cp >= checkpoint)
748 })
749 })
750 {
751 break;
752 }
753 }
754 })
755 .await
756 }
757}
758
759impl Default for OffchainClusterConfig {
760 fn default() -> Self {
761 Self {
762 indexer_args: Default::default(),
763 consistent_indexer_args: Default::default(),
764 fullnode_args: FullnodeArgs::default(),
765 indexer_config: IndexerConfig::for_test(),
766 consistent_config: ConsistentConfig::for_test(),
767 jsonrpc_config: Default::default(),
768 jsonrpc_node_args: Default::default(),
769 graphql_config: Default::default(),
770 bootstrap_genesis: None,
771 kv_rpc_config: KvRpcConfig::default(),
772 }
773 }
774}
775
776pub fn local_ingestion_client_args() -> (ClientArgs, TempDir) {
778 let temp_dir = tempfile::tempdir()
779 .context("Failed to create data ingestion path")
780 .unwrap();
781 let client_args = ClientArgs {
782 ingestion: IngestionClientArgs {
783 local_ingestion_path: Some(temp_dir.path().to_owned()),
784 ..Default::default()
785 },
786 ..Default::default()
787 };
788 (client_args, temp_dir)
789}
790
791pub async fn write_checkpoint(path: &Path, checkpoint: Checkpoint) -> anyhow::Result<()> {
793 let sequence_number = checkpoint.summary.sequence_number;
794
795 let mask = FieldMask::from_paths([
796 rpc::v2::Checkpoint::path_builder().sequence_number(),
797 rpc::v2::Checkpoint::path_builder().summary().bcs().value(),
798 rpc::v2::Checkpoint::path_builder().signature().finish(),
799 rpc::v2::Checkpoint::path_builder().contents().bcs().value(),
800 rpc::v2::Checkpoint::path_builder()
801 .transactions()
802 .transaction()
803 .bcs()
804 .value(),
805 rpc::v2::Checkpoint::path_builder()
806 .transactions()
807 .effects()
808 .bcs()
809 .value(),
810 rpc::v2::Checkpoint::path_builder()
811 .transactions()
812 .effects()
813 .unchanged_loaded_runtime_objects()
814 .finish(),
815 rpc::v2::Checkpoint::path_builder()
816 .transactions()
817 .events()
818 .bcs()
819 .value(),
820 rpc::v2::Checkpoint::path_builder()
821 .objects()
822 .objects()
823 .bcs()
824 .value(),
825 ]);
826
827 let proto_checkpoint = rpc::v2::Checkpoint::merge_from(&checkpoint, &mask.into());
828 let proto_bytes = proto_checkpoint.encode_to_vec();
829 let compressed = zstd::encode_all(&proto_bytes[..], 3)?;
830
831 let file_name = format!("{}.binpb.zst", sequence_number);
832 let file_path = path.join(file_name);
833 fs::write(file_path, compressed)?;
834 Ok(())
835}
836
837async fn start_archival(
839 client_args: ClientArgs,
840 kv_rpc_address: SocketAddr,
841 kv_rpc_config: KvRpcConfig,
842 registry: &prometheus::Registry,
843) -> anyhow::Result<(BigTableClient, BigTableEmulator, Service)> {
844 let emulator = tokio::task::spawn_blocking(BigTableEmulator::start)
845 .await
846 .context("spawn_blocking panicked")?
847 .context("Failed to start BigTable emulator")?;
848
849 create_tables(emulator.host(), INSTANCE_ID)
850 .await
851 .context("Failed to create BigTable tables")?;
852
853 let bigtable_client =
854 BigTableClient::new_local(emulator.host().to_string(), INSTANCE_ID.to_string())
855 .await
856 .context("Failed to create BigTable client")?;
857
858 let store = BigTableStore::new(
859 BigTableClient::new_local(emulator.host().to_string(), INSTANCE_ID.to_string())
860 .await
861 .context("Failed to create BigTable client for indexer")?,
862 );
863 let bt_indexer = BigTableIndexer::new(
864 store,
865 IndexerArgs::default(),
866 client_args,
867 BtIngestionConfig::default(),
868 CommitterConfig::default(),
869 BtIndexerConfig::default(),
870 PipelineLayer::default(),
871 Chain::Unknown,
872 registry,
873 )
874 .await
875 .context("Failed to create BigTable indexer")?;
876
877 let bt_indexer_service = bt_indexer
880 .run()
881 .await
882 .context("Failed to start BigTable indexer")?;
883
884 let kv_rpc_server = KvRpcServer::new_local_with_config(
885 emulator.host().to_string(),
886 INSTANCE_ID.to_string(),
887 None,
888 kv_rpc_config.ledger_history(),
889 kv_rpc_config.request_bigtable_concurrency(),
890 kv_rpc_config.stages(),
891 kv_rpc_config.enable_list_apis(),
892 )
893 .await
894 .context("Failed to create KvRpcServer")?;
895 let kv_rpc_service = kv_rpc_server
896 .start_service(kv_rpc_address, sui_kv_rpc::ServerConfig::default())
897 .await
898 .context("Failed to start kv-rpc server")?;
899
900 let service = bt_indexer_service.merge(kv_rpc_service);
901 Ok((bigtable_client, emulator, service))
902}