1use std::{collections::BTreeMap, env, path::PathBuf, sync::Arc};
6
7use anyhow::Result;
8use clap::*;
9use fastcrypto::encoding::Encoding;
10use futures::{StreamExt, future::join_all};
11use iota_archival::{read_manifest_as_json, write_manifest_from_json};
12use iota_config::{
13 Config,
14 genesis::Genesis,
15 object_storage_config::{ObjectStoreConfig, ObjectStoreType},
16};
17use iota_core::{authority_aggregator::AuthorityAggregatorBuilder, authority_client::AuthorityAPI};
18use iota_protocol_config::Chain;
19use iota_replay::{ReplayToolCommand, execute_replay_command};
20use iota_sdk::{IotaClient, IotaClientBuilder, rpc_types::IotaTransactionBlockResponseOptions};
21use iota_types::{
22 base_types::*,
23 crypto::AuthorityPublicKeyBytes,
24 messages_checkpoint::{CheckpointRequest, CheckpointResponse, CheckpointSequenceNumber},
25 messages_grpc::TransactionInfoRequest,
26 transaction::{SenderSignedData, Transaction},
27};
28use telemetry_subscribers::TracingHandle;
29
30use crate::{
31 ConciseObjectOutput, GroupedObjectOutput, SnapshotVerifyMode, VerboseObjectOutput,
32 check_completed_snapshot,
33 db_tool::{DbToolCommand, execute_db_tool_command, print_db_all_tables},
34 download_db_snapshot, download_formal_snapshot, dump_checkpoints_from_archive,
35 get_latest_available_epoch, get_object, get_transaction_block, make_clients,
36 restore_from_db_checkpoint, verify_archive, verify_archive_by_checksum,
37};
38
39#[derive(Parser, Clone, ValueEnum)]
40pub enum Verbosity {
41 Grouped,
42 Concise,
43 Verbose,
44}
45
46#[derive(Parser)]
47pub enum ToolCommand {
48 LockedObject {
51 #[arg(long, help = "The object ID to fetch")]
54 id: Option<ObjectID>,
55 #[arg(long)]
58 address: Option<IotaAddress>,
59 #[arg(long)]
61 fullnode_rpc_url: String,
62 #[arg(long)]
65 rescue: bool,
66 },
67
68 FetchObject {
70 #[arg(long, help = "The object ID to fetch")]
71 id: ObjectID,
72
73 #[arg(long, help = "Fetch object at a specific sequence")]
74 version: Option<u64>,
75
76 #[arg(
77 long,
78 help = "Validator to fetch from - if not specified, all validators are queried"
79 )]
80 validator: Option<AuthorityName>,
81
82 #[arg(long)]
84 fullnode_rpc_url: String,
85
86 #[arg(value_enum, long, default_value = "grouped", ignore_case = true)]
95 verbosity: Verbosity,
96
97 #[arg(long, help = "don't show header in concise output")]
98 concise_no_header: bool,
99 },
100
101 FetchTransaction {
103 #[arg(long)]
105 fullnode_rpc_url: String,
106
107 #[arg(long, help = "The transaction ID to fetch")]
108 digest: TransactionDigest,
109
110 #[arg(long = "show-tx")]
112 show_input_tx: bool,
113 },
114
115 DbTool {
117 #[arg(long)]
119 db_path: String,
120 #[command(subcommand)]
121 cmd: Option<DbToolCommand>,
122 },
123
124 VerifyArchive {
126 #[arg(long)]
127 genesis: PathBuf,
128 #[command(flatten)]
129 object_store_config: ObjectStoreConfig,
130 #[arg(default_value_t = 5)]
131 download_concurrency: usize,
132 },
133
134 PrintArchiveManifest {
136 #[command(flatten)]
137 object_store_config: ObjectStoreConfig,
138 },
139 UpdateArchiveManifest {
141 #[command(flatten)]
142 object_store_config: ObjectStoreConfig,
143 #[arg(long = "archive-path")]
144 archive_json_path: PathBuf,
145 },
146 #[command(name = "verify-archive-from-checksums")]
148 VerifyArchiveByChecksum {
149 #[command(flatten)]
150 object_store_config: ObjectStoreConfig,
151 #[arg(default_value_t = 5)]
152 download_concurrency: usize,
153 },
154
155 #[command(name = "dump-archive")]
157 DumpArchiveByChecksum {
158 #[command(flatten)]
159 object_store_config: ObjectStoreConfig,
160 #[arg(default_value_t = 0)]
161 start: u64,
162 end: u64,
163 #[arg(default_value_t = 80)]
164 max_content_length: usize,
165 },
166
167 DumpPackages {
175 #[arg(long, short)]
177 rpc_url: String,
178
179 #[arg(long, short)]
182 output_dir: PathBuf,
183
184 #[arg(long)]
187 before_checkpoint: Option<u64>,
188
189 #[arg(short, long)]
192 verbose: bool,
193 },
194
195 DumpValidators {
196 #[arg(long)]
197 genesis: PathBuf,
198
199 #[arg(
200 long,
201 help = "show concise output - name, authority key and network address"
202 )]
203 concise: bool,
204 },
205
206 DumpGenesis {
207 #[arg(long)]
208 genesis: PathBuf,
209 },
210
211 FetchCheckpoint {
215 #[arg(long)]
217 fullnode_rpc_url: String,
218
219 #[arg(long, help = "Fetch checkpoint at a specific sequence number")]
220 sequence_number: Option<CheckpointSequenceNumber>,
221 },
222
223 Anemo {
224 #[command(next_help_heading = "foo", flatten)]
225 args: anemo_cli::Args,
226 },
227
228 #[command(name = "restore-db")]
229 RestoreFromDBCheckpoint {
230 #[arg(long)]
231 config_path: PathBuf,
232 #[arg(long)]
233 db_checkpoint_path: PathBuf,
234 },
235
236 #[command(
237 name = "download-db-snapshot",
238 about = "Downloads the legacy database snapshot via cloud object store, outputs to local disk"
239 )]
240 DownloadDBSnapshot {
241 #[arg(long, conflicts_with = "latest")]
242 epoch: Option<u64>,
243 #[arg(long, help = "the path to write the downloaded snapshot files")]
244 path: PathBuf,
245 #[arg(long)]
247 skip_indexes: bool,
248 #[arg(long)]
251 num_parallel_downloads: Option<usize>,
252 #[arg(long, default_value = "mainnet")]
256 network: Chain,
257 #[arg(long, conflicts_with = "no_sign_request")]
260 snapshot_bucket: Option<String>,
261 #[arg(
263 long,
264 conflicts_with = "no_sign_request",
265 help = "Required if --no-sign-request is not set"
266 )]
267 snapshot_bucket_type: Option<ObjectStoreType>,
268 #[arg(long, help = "only used for testing, when --snapshot-bucket-type=FILE")]
271 snapshot_path: Option<PathBuf>,
272 #[arg(
274 long,
275 conflicts_with_all = &["snapshot_bucket", "snapshot_bucket_type"],
276 help = "if set, no authentication is needed for snapshot restore"
277 )]
278 no_sign_request: bool,
279 #[arg(
282 long,
283 conflicts_with = "epoch",
284 help = "defaults to latest available snapshot in chosen bucket"
285 )]
286 latest: bool,
287 #[arg(long)]
290 verbose: bool,
291 },
292
293 #[command(
295 about = "Downloads formal database snapshot via cloud object store, outputs to local disk"
296 )]
297 DownloadFormalSnapshot {
298 #[arg(long, conflicts_with = "latest")]
299 epoch: Option<u64>,
300 #[arg(long)]
301 genesis: PathBuf,
302 #[arg(long)]
303 path: PathBuf,
304 #[arg(long)]
307 num_parallel_downloads: Option<usize>,
308 #[arg(long, default_value = "normal")]
310 verify: Option<SnapshotVerifyMode>,
311 #[arg(long, default_value = "mainnet")]
315 network: Chain,
316 #[arg(long, conflicts_with = "no_sign_request")]
319 snapshot_bucket: Option<String>,
320 #[arg(
322 long,
323 conflicts_with = "no_sign_request",
324 help = "Required if --no-sign-request is not set"
325 )]
326 snapshot_bucket_type: Option<ObjectStoreType>,
327 #[arg(long)]
330 snapshot_path: Option<PathBuf>,
331 #[arg(
333 long,
334 conflicts_with_all = &["snapshot_bucket", "snapshot_bucket_type"],
335 help = "if set, no authentication is needed for snapshot restore"
336 )]
337 no_sign_request: bool,
338 #[arg(
341 long,
342 conflicts_with = "epoch",
343 help = "defaults to latest available snapshot in chosen bucket"
344 )]
345 latest: bool,
346 #[arg(long)]
349 verbose: bool,
350
351 #[arg(long)]
358 all_checkpoints: bool,
359 },
360
361 Replay {
362 #[arg(long = "rpc")]
363 rpc_url: Option<String>,
364 #[arg(long)]
365 safety_checks: bool,
366 #[arg(long = "authority")]
367 use_authority: bool,
368 #[arg(
369 long,
370 short,
371 help = "Path to the network config file. This should be specified when rpc_url is not present. \
372 If not specified we will use the default network config file at ~/.iota-replay/network-config.yaml"
373 )]
374 cfg_path: Option<PathBuf>,
375 #[arg(
376 long,
377 help = "The name of the chain to replay from, could be one of: mainnet, testnet, devnet.\
378 When rpc_url is not specified, this is used to load the corresponding config from the network config file.\
379 If not specified, mainnet will be used by default"
380 )]
381 chain: Option<String>,
382 #[command(subcommand)]
383 cmd: ReplayToolCommand,
384 },
385
386 SignTransaction {
388 #[arg(long)]
389 genesis: PathBuf,
390
391 #[arg(
392 long,
393 help = "The Base64-encoding of the bcs bytes of SenderSignedData"
394 )]
395 sender_signed_data: String,
396 },
397
398 GenesisCeremony(crate::genesis_ceremony::Ceremony),
400 FireDrill {
402 #[command(subcommand)]
403 fire_drill: crate::fire_drill::FireDrill,
404 },
405}
406
407async fn check_locked_object(
408 iota_client: &Arc<IotaClient>,
409 committee: Arc<BTreeMap<AuthorityPublicKeyBytes, u64>>,
410 id: ObjectID,
411 rescue: bool,
412) -> anyhow::Result<()> {
413 let clients = Arc::new(make_clients(iota_client).await?);
414 let output = get_object(id, None, None, clients.clone()).await?;
415 let output = GroupedObjectOutput::new(output, committee);
416 if output.fully_locked {
417 println!("Object {id} is fully locked.");
418 return Ok(());
419 }
420 let top_record = output.voting_power.first().unwrap();
421 let top_record_stake = top_record.1;
422 let top_record = top_record.0.unwrap();
423 if top_record.4.is_none() {
424 println!(
425 "Object {id} does not seem to be locked by majority of validators (unlocked stake: {top_record_stake})"
426 );
427 return Ok(());
428 }
429
430 let tx_digest = top_record.2;
431 if !rescue {
432 println!("Object {id} is rescueable, top tx: {tx_digest:?}");
433 return Ok(());
434 }
435 println!("Object {id} is rescueable, trying tx {tx_digest}");
436 let validator = output
437 .grouped_results
438 .get(&Some(top_record))
439 .unwrap()
440 .first()
441 .unwrap();
442 let client = &clients.get(validator).unwrap().1;
443 let tx = client
444 .handle_transaction_info_request(TransactionInfoRequest {
445 transaction_digest: tx_digest,
446 })
447 .await?
448 .transaction;
449 let res = iota_client
450 .quorum_driver_api()
451 .execute_transaction_block(
452 Transaction::new(tx),
453 IotaTransactionBlockResponseOptions::full_content(),
454 None,
455 )
456 .await;
457 match res {
458 Ok(_) => {
459 println!("Transaction executed successfully ({tx_digest:?})");
460 }
461 Err(e) => {
462 println!("Failed to execute transaction ({tx_digest:?}): {e:?}");
463 }
464 }
465 Ok(())
466}
467
468impl ToolCommand {
469 pub async fn execute(self, tracing_handle: TracingHandle) -> Result<(), anyhow::Error> {
470 match self {
471 ToolCommand::LockedObject {
472 id,
473 fullnode_rpc_url,
474 rescue,
475 address,
476 } => {
477 let iota_client =
478 Arc::new(IotaClientBuilder::default().build(fullnode_rpc_url).await?);
479 let committee = Arc::new(
480 iota_client
481 .governance_api()
482 .get_committee_info(None)
483 .await?
484 .validators
485 .into_iter()
486 .collect::<BTreeMap<_, _>>(),
487 );
488 let object_ids = match id {
489 Some(id) => vec![id],
490 None => {
491 let address = address.expect("Either id or address must be provided");
492 iota_client
493 .coin_read_api()
494 .get_coins_stream(address, None)
495 .map(|c| c.coin_object_id)
496 .collect()
497 .await
498 }
499 };
500 for ids in object_ids.chunks(30) {
501 let mut tasks = vec![];
502 for id in ids {
503 tasks.push(check_locked_object(
504 &iota_client,
505 committee.clone(),
506 *id,
507 rescue,
508 ))
509 }
510 join_all(tasks)
511 .await
512 .into_iter()
513 .collect::<Result<Vec<_>, _>>()?;
514 }
515 }
516 ToolCommand::FetchObject {
517 id,
518 validator,
519 version,
520 fullnode_rpc_url,
521 verbosity,
522 concise_no_header,
523 } => {
524 let iota_client =
525 Arc::new(IotaClientBuilder::default().build(fullnode_rpc_url).await?);
526 let clients = Arc::new(make_clients(&iota_client).await?);
527 let output = get_object(id, version, validator, clients).await?;
528
529 match verbosity {
530 Verbosity::Grouped => {
531 let committee = Arc::new(
532 iota_client
533 .governance_api()
534 .get_committee_info(None)
535 .await?
536 .validators
537 .into_iter()
538 .collect::<BTreeMap<_, _>>(),
539 );
540 println!("{}", GroupedObjectOutput::new(output, committee));
541 }
542 Verbosity::Verbose => {
543 println!("{}", VerboseObjectOutput(output));
544 }
545 Verbosity::Concise => {
546 if !concise_no_header {
547 println!("{}", ConciseObjectOutput::header());
548 }
549 println!("{}", ConciseObjectOutput(output));
550 }
551 }
552 }
553 ToolCommand::FetchTransaction {
554 digest,
555 show_input_tx,
556 fullnode_rpc_url,
557 } => {
558 print!(
559 "{}",
560 get_transaction_block(digest, show_input_tx, fullnode_rpc_url).await?
561 );
562 }
563 ToolCommand::DbTool { db_path, cmd } => {
564 let path = PathBuf::from(db_path);
565 match cmd {
566 Some(c) => execute_db_tool_command(path, c).await?,
567 None => print_db_all_tables(path)?,
568 }
569 }
570 ToolCommand::DumpPackages {
571 rpc_url,
572 output_dir,
573 before_checkpoint,
574 verbose,
575 } => {
576 if !verbose {
577 tracing_handle
578 .update_log("off")
579 .expect("Failed to update log level");
580 }
581
582 iota_package_dump::dump(rpc_url, output_dir, before_checkpoint).await?;
583 }
584 ToolCommand::DumpValidators { genesis, concise } => {
585 let genesis = Genesis::load(genesis).unwrap();
586 if !concise {
587 println!("{:#?}", genesis.validator_set_for_tooling());
588 } else {
589 for (i, val_info) in genesis.validator_set_for_tooling().iter().enumerate() {
590 let metadata = val_info.verified_metadata();
591 println!(
592 "#{:<2} {:<20} {:?} {:?} {}",
593 i,
594 metadata.name,
595 metadata.iota_pubkey_bytes().concise(),
596 metadata.net_address,
597 anemo::PeerId(metadata.network_pubkey.0.to_bytes()),
598 )
599 }
600 }
601 }
602 ToolCommand::DumpGenesis { genesis } => {
603 let genesis = Genesis::load(genesis)?;
604 println!("{genesis:#?}");
605 }
606 ToolCommand::FetchCheckpoint {
607 sequence_number,
608 fullnode_rpc_url,
609 } => {
610 let iota_client =
611 Arc::new(IotaClientBuilder::default().build(fullnode_rpc_url).await?);
612 let clients = make_clients(&iota_client).await?;
613
614 for (name, (_, client)) in clients {
615 let resp = client
616 .handle_checkpoint(CheckpointRequest {
617 sequence_number,
618 request_content: true,
619 certified: true,
620 })
621 .await
622 .unwrap();
623 let CheckpointResponse {
624 checkpoint,
625 contents,
626 } = resp;
627 println!("Validator: {:?}\n", name.concise());
628 println!("Checkpoint: {checkpoint:?}\n");
629 println!("Content: {contents:?}\n");
630 }
631 }
632 ToolCommand::Anemo { args } => {
633 let config = crate::make_anemo_config();
634 anemo_cli::run(config, args).await
635 }
636 ToolCommand::RestoreFromDBCheckpoint {
637 config_path,
638 db_checkpoint_path,
639 } => {
640 let config = iota_config::NodeConfig::load(config_path)?;
641 restore_from_db_checkpoint(&config, &db_checkpoint_path).await?;
642 }
643 ToolCommand::DownloadFormalSnapshot {
644 epoch,
645 genesis,
646 path,
647 num_parallel_downloads,
648 verify,
649 network,
650 snapshot_bucket,
651 snapshot_bucket_type,
652 snapshot_path,
653 no_sign_request,
654 latest,
655 verbose,
656 all_checkpoints,
657 } => {
658 if !verbose {
659 tracing_handle
660 .update_log("off")
661 .expect("Failed to update log level");
662 }
663 let num_parallel_downloads = num_parallel_downloads.unwrap_or_else(|| {
664 num_cpus::get()
665 .checked_sub(1)
666 .expect("Failed to get number of CPUs")
667 });
668 let snapshot_bucket =
669 snapshot_bucket.or_else(|| match (network, no_sign_request) {
670 (Chain::Mainnet, false) => Some(
671 env::var("MAINNET_FORMAL_SIGNED_BUCKET")
672 .unwrap_or("iota-mainnet-formal".to_string()),
673 ),
674 (Chain::Mainnet, true) => env::var("MAINNET_FORMAL_UNSIGNED_BUCKET").ok(),
675 (Chain::Testnet, true) => env::var("TESTNET_FORMAL_UNSIGNED_BUCKET").ok(),
676 (Chain::Testnet, _) => Some(
677 env::var("TESTNET_FORMAL_SIGNED_BUCKET")
678 .unwrap_or("iota-testnet-formal".to_string()),
679 ),
680 (Chain::Unknown, _) => {
681 panic!("Cannot generate default snapshot bucket for unknown network");
682 }
683 });
684
685 let aws_endpoint = env::var("AWS_SNAPSHOT_ENDPOINT").ok().or_else(|| {
686 if no_sign_request {
687 if network == Chain::Mainnet {
688 Some("https://formal-snapshot.mainnet.iota.cafe".to_string())
689 } else if network == Chain::Testnet {
690 Some("https://formal-snapshot.testnet.iota.cafe".to_string())
691 } else {
692 None
693 }
694 } else {
695 None
696 }
697 });
698
699 let snapshot_bucket_type = if no_sign_request {
700 ObjectStoreType::S3
701 } else {
702 snapshot_bucket_type
703 .expect("You must set either --snapshot-bucket-type or --no-sign-request")
704 };
705 let snapshot_store_config = match snapshot_bucket_type {
706 ObjectStoreType::S3 => ObjectStoreConfig {
707 object_store: Some(ObjectStoreType::S3),
708 bucket: snapshot_bucket.filter(|s| !s.is_empty()),
709 aws_access_key_id: env::var("AWS_SNAPSHOT_ACCESS_KEY_ID").ok(),
710 aws_secret_access_key: env::var("AWS_SNAPSHOT_SECRET_ACCESS_KEY").ok(),
711 aws_region: env::var("AWS_SNAPSHOT_REGION").ok(),
712 aws_endpoint: aws_endpoint.filter(|s| !s.is_empty()),
713 aws_virtual_hosted_style_request: env::var(
714 "AWS_SNAPSHOT_VIRTUAL_HOSTED_REQUESTS",
715 )
716 .ok()
717 .and_then(|b| b.parse().ok())
718 .unwrap_or(no_sign_request),
719 object_store_connection_limit: 200,
720 no_sign_request,
721 ..Default::default()
722 },
723 ObjectStoreType::GCS => ObjectStoreConfig {
724 object_store: Some(ObjectStoreType::GCS),
725 bucket: snapshot_bucket,
726 google_service_account: env::var("GCS_SNAPSHOT_SERVICE_ACCOUNT_FILE_PATH")
727 .ok(),
728 object_store_connection_limit: 200,
729 no_sign_request,
730 ..Default::default()
731 },
732 ObjectStoreType::Azure => ObjectStoreConfig {
733 object_store: Some(ObjectStoreType::Azure),
734 bucket: snapshot_bucket,
735 azure_storage_account: env::var("AZURE_SNAPSHOT_STORAGE_ACCOUNT").ok(),
736 azure_storage_access_key: env::var("AZURE_SNAPSHOT_STORAGE_ACCESS_KEY")
737 .ok(),
738 object_store_connection_limit: 200,
739 no_sign_request,
740 ..Default::default()
741 },
742 ObjectStoreType::File => {
743 if snapshot_path.is_some() {
744 ObjectStoreConfig {
745 object_store: Some(ObjectStoreType::File),
746 directory: snapshot_path,
747 ..Default::default()
748 }
749 } else {
750 panic!(
751 "--snapshot-path must be specified for --snapshot-bucket-type=file"
752 );
753 }
754 }
755 };
756
757 let archive_bucket = Some(
758 env::var("FORMAL_SNAPSHOT_ARCHIVE_BUCKET").unwrap_or_else(|_| match network {
759 Chain::Mainnet => "iota-mainnet-archive".to_string(),
760 Chain::Testnet => "iota-testnet-archive".to_string(),
761 Chain::Unknown => {
762 panic!("Cannot generate default archive bucket for unknown network");
763 }
764 }),
765 );
766
767 let mut custom_archive_enabled = false;
768 if let Ok(custom_archive_check) = env::var("CUSTOM_ARCHIVE_BUCKET") {
769 if custom_archive_check == "true" {
770 custom_archive_enabled = true;
771 }
772 }
773 let archive_store_config = if custom_archive_enabled {
774 let aws_region = Some(
775 env::var("FORMAL_SNAPSHOT_ARCHIVE_REGION")
776 .unwrap_or("us-west-2".to_string()),
777 );
778
779 let archive_bucket_type = env::var("FORMAL_SNAPSHOT_ARCHIVE_BUCKET_TYPE").expect("If setting `CUSTOM_ARCHIVE_BUCKET=true` Must set FORMAL_SNAPSHOT_ARCHIVE_BUCKET_TYPE, and credentials");
780 match archive_bucket_type.to_ascii_lowercase().as_str() {
781 "s3" => ObjectStoreConfig {
782 object_store: Some(ObjectStoreType::S3),
783 bucket: archive_bucket.filter(|s| !s.is_empty()),
784 aws_access_key_id: env::var("AWS_ARCHIVE_ACCESS_KEY_ID").ok(),
785 aws_secret_access_key: env::var("AWS_ARCHIVE_SECRET_ACCESS_KEY").ok(),
786 aws_region,
787 aws_endpoint: env::var("AWS_ARCHIVE_ENDPOINT").ok(),
788 aws_virtual_hosted_style_request: env::var(
789 "AWS_ARCHIVE_VIRTUAL_HOSTED_REQUESTS",
790 )
791 .ok()
792 .and_then(|b| b.parse().ok())
793 .unwrap_or(false),
794 object_store_connection_limit: 50,
795 no_sign_request: false,
796 ..Default::default()
797 },
798 "gcs" => ObjectStoreConfig {
799 object_store: Some(ObjectStoreType::GCS),
800 bucket: archive_bucket,
801 google_service_account: env::var(
802 "GCS_ARCHIVE_SERVICE_ACCOUNT_FILE_PATH",
803 )
804 .ok(),
805 object_store_connection_limit: 50,
806 no_sign_request: false,
807 ..Default::default()
808 },
809 "azure" => ObjectStoreConfig {
810 object_store: Some(ObjectStoreType::Azure),
811 bucket: archive_bucket,
812 azure_storage_account: env::var("AZURE_ARCHIVE_STORAGE_ACCOUNT").ok(),
813 azure_storage_access_key: env::var("AZURE_ARCHIVE_STORAGE_ACCESS_KEY")
814 .ok(),
815 object_store_connection_limit: 50,
816 no_sign_request: false,
817 ..Default::default()
818 },
819 _ => panic!(
820 "If setting `CUSTOM_ARCHIVE_BUCKET=true` must set FORMAL_SNAPSHOT_ARCHIVE_BUCKET_TYPE to one of 'gcs', 'azure', or 's3' "
821 ),
822 }
823 } else {
824 let aws_endpoint = env::var("AWS_ARCHIVE_ENDPOINT").ok().or_else(|| {
827 if network == Chain::Mainnet {
828 Some("https://archive.mainnet.iota.cafe".to_string())
829 } else if network == Chain::Testnet {
830 Some("https://archive.testnet.iota.cafe".to_string())
831 } else {
832 None
833 }
834 });
835
836 let aws_virtual_hosted_style_request =
837 env::var("AWS_ARCHIVE_VIRTUAL_HOSTED_REQUESTS")
838 .ok()
839 .and_then(|b| b.parse().ok())
840 .unwrap_or(matches!(network, Chain::Mainnet | Chain::Testnet));
841
842 ObjectStoreConfig {
843 object_store: Some(ObjectStoreType::S3),
844 bucket: archive_bucket.filter(|s| !s.is_empty()),
845 aws_region: Some("us-west-2".to_string()),
846 aws_endpoint,
847 aws_virtual_hosted_style_request,
848 object_store_connection_limit: 200,
849 no_sign_request: true,
850 ..Default::default()
851 }
852 };
853 let latest_available_epoch =
854 latest.then_some(get_latest_available_epoch(&snapshot_store_config).await?);
855 let epoch_to_download = epoch.or(latest_available_epoch).expect(
856 "Either pass epoch with --epoch <epoch_num> or use latest with --latest",
857 );
858
859 if let Err(e) =
860 check_completed_snapshot(&snapshot_store_config, epoch_to_download).await
861 {
862 panic!("Aborting snapshot restore: {e}, snapshot may not be uploaded yet");
863 }
864
865 let verify = verify.unwrap_or_default();
866 download_formal_snapshot(
867 &path,
868 epoch_to_download,
869 &genesis,
870 snapshot_store_config,
871 archive_store_config,
872 num_parallel_downloads,
873 network,
874 verify,
875 all_checkpoints,
876 )
877 .await?;
878 }
879 ToolCommand::DownloadDBSnapshot {
880 epoch,
881 path,
882 skip_indexes,
883 num_parallel_downloads,
884 network,
885 snapshot_bucket,
886 snapshot_bucket_type,
887 snapshot_path,
888 no_sign_request,
889 latest,
890 verbose,
891 } => {
892 if !verbose {
893 tracing_handle
894 .update_log("off")
895 .expect("Failed to update log level");
896 }
897 let num_parallel_downloads = num_parallel_downloads.unwrap_or_else(|| {
898 num_cpus::get()
899 .checked_sub(1)
900 .expect("Failed to get number of CPUs")
901 });
902 let snapshot_bucket =
903 snapshot_bucket.or_else(|| match (network, no_sign_request) {
904 (Chain::Mainnet, false) => Some(
905 env::var("MAINNET_DB_SIGNED_BUCKET")
906 .unwrap_or("iota-mainnet-snapshots".to_string()),
907 ),
908 (Chain::Mainnet, true) => env::var("MAINNET_DB_UNSIGNED_BUCKET").ok(),
909 (Chain::Testnet, true) => env::var("TESTNET_DB_UNSIGNED_BUCKET").ok(),
910 (Chain::Testnet, _) => Some(
911 env::var("TESTNET_DB_SIGNED_BUCKET")
912 .unwrap_or("iota-testnet-snapshots".to_string()),
913 ),
914 (Chain::Unknown, _) => {
915 panic!("Cannot generate default snapshot bucket for unknown network");
916 }
917 });
918
919 let aws_endpoint = env::var("AWS_SNAPSHOT_ENDPOINT").ok();
920 let snapshot_bucket_type = if no_sign_request {
921 ObjectStoreType::S3
922 } else {
923 snapshot_bucket_type
924 .expect("You must set either --snapshot-bucket-type or --no-sign-request")
925 };
926 let snapshot_store_config = if no_sign_request {
927 let aws_endpoint = env::var("AWS_SNAPSHOT_ENDPOINT").ok().or_else(|| {
928 if network == Chain::Mainnet {
929 Some("https://rocksdb-snapshot.mainnet.iota.cafe".to_string())
930 } else if network == Chain::Testnet {
931 Some("https://rocksdb-snapshot.testnet.iota.cafe".to_string())
932 } else {
933 None
934 }
935 });
936 ObjectStoreConfig {
937 object_store: Some(ObjectStoreType::S3),
938 aws_endpoint: aws_endpoint.filter(|s| !s.is_empty()),
939 aws_virtual_hosted_style_request: env::var(
940 "AWS_SNAPSHOT_VIRTUAL_HOSTED_REQUESTS",
941 )
942 .ok()
943 .and_then(|b| b.parse().ok())
944 .unwrap_or(no_sign_request),
945 object_store_connection_limit: 200,
946 no_sign_request,
947 ..Default::default()
948 }
949 } else {
950 match snapshot_bucket_type {
951 ObjectStoreType::S3 => ObjectStoreConfig {
952 object_store: Some(ObjectStoreType::S3),
953 bucket: snapshot_bucket.filter(|s| !s.is_empty()),
954 aws_access_key_id: env::var("AWS_SNAPSHOT_ACCESS_KEY_ID").ok(),
955 aws_secret_access_key: env::var("AWS_SNAPSHOT_SECRET_ACCESS_KEY").ok(),
956 aws_region: env::var("AWS_SNAPSHOT_REGION").ok(),
957 aws_endpoint: aws_endpoint.filter(|s| !s.is_empty()),
958 aws_virtual_hosted_style_request: env::var(
959 "AWS_SNAPSHOT_VIRTUAL_HOSTED_REQUESTS",
960 )
961 .ok()
962 .and_then(|b| b.parse().ok())
963 .unwrap_or(no_sign_request),
964 object_store_connection_limit: 200,
965 no_sign_request,
966 ..Default::default()
967 },
968 ObjectStoreType::GCS => ObjectStoreConfig {
969 object_store: Some(ObjectStoreType::GCS),
970 bucket: snapshot_bucket,
971 google_service_account: env::var(
972 "GCS_SNAPSHOT_SERVICE_ACCOUNT_FILE_PATH",
973 )
974 .ok(),
975 google_project_id: env::var("GCS_SNAPSHOT_SERVICE_ACCOUNT_PROJECT_ID")
976 .ok(),
977 object_store_connection_limit: 200,
978 no_sign_request,
979 ..Default::default()
980 },
981 ObjectStoreType::Azure => ObjectStoreConfig {
982 object_store: Some(ObjectStoreType::Azure),
983 bucket: snapshot_bucket,
984 azure_storage_account: env::var("AZURE_SNAPSHOT_STORAGE_ACCOUNT").ok(),
985 azure_storage_access_key: env::var("AZURE_SNAPSHOT_STORAGE_ACCESS_KEY")
986 .ok(),
987 object_store_connection_limit: 200,
988 no_sign_request,
989 ..Default::default()
990 },
991 ObjectStoreType::File => {
992 if snapshot_path.is_some() {
993 ObjectStoreConfig {
994 object_store: Some(ObjectStoreType::File),
995 directory: snapshot_path,
996 ..Default::default()
997 }
998 } else {
999 panic!(
1000 "--snapshot-path must be specified for --snapshot-bucket-type=file"
1001 );
1002 }
1003 }
1004 }
1005 };
1006
1007 let latest_available_epoch =
1008 latest.then_some(get_latest_available_epoch(&snapshot_store_config).await?);
1009 let epoch_to_download = epoch.or(latest_available_epoch).expect(
1010 "Either pass epoch with --epoch <epoch_num> or use latest with --latest",
1011 );
1012
1013 if let Err(e) =
1014 check_completed_snapshot(&snapshot_store_config, epoch_to_download).await
1015 {
1016 panic!("Aborting snapshot restore: {e}, snapshot may not be uploaded yet");
1017 }
1018 download_db_snapshot(
1019 &path,
1020 epoch_to_download,
1021 snapshot_store_config,
1022 skip_indexes,
1023 num_parallel_downloads,
1024 )
1025 .await?;
1026 }
1027 ToolCommand::Replay {
1028 rpc_url,
1029 safety_checks,
1030 cmd,
1031 use_authority,
1032 cfg_path,
1033 chain,
1034 } => {
1035 execute_replay_command(rpc_url, safety_checks, use_authority, cfg_path, chain, cmd)
1036 .await?;
1037 }
1038 ToolCommand::VerifyArchive {
1039 genesis,
1040 object_store_config,
1041 download_concurrency,
1042 } => {
1043 verify_archive(&genesis, object_store_config, download_concurrency, true).await?;
1044 }
1045 ToolCommand::PrintArchiveManifest {
1046 object_store_config,
1047 } => {
1048 println!("{}", read_manifest_as_json(object_store_config).await?);
1049 }
1050 ToolCommand::UpdateArchiveManifest {
1051 object_store_config,
1052 archive_json_path,
1053 } => {
1054 write_manifest_from_json(object_store_config, archive_json_path).await?;
1055 }
1056 ToolCommand::VerifyArchiveByChecksum {
1057 object_store_config,
1058 download_concurrency,
1059 } => {
1060 verify_archive_by_checksum(object_store_config, download_concurrency).await?;
1061 }
1062 ToolCommand::DumpArchiveByChecksum {
1063 object_store_config,
1064 start,
1065 end,
1066 max_content_length,
1067 } => {
1068 dump_checkpoints_from_archive(object_store_config, start, end, max_content_length)
1069 .await?;
1070 }
1071 ToolCommand::SignTransaction {
1072 genesis,
1073 sender_signed_data,
1074 } => {
1075 let genesis = Genesis::load(genesis)?;
1076 let sender_signed_data = bcs::from_bytes::<SenderSignedData>(
1077 &fastcrypto::encoding::Base64::decode(sender_signed_data.as_str()).unwrap(),
1078 )
1079 .unwrap();
1080 let transaction = Transaction::new(sender_signed_data);
1081 let (agg, _) =
1082 AuthorityAggregatorBuilder::from_genesis(&genesis).build_network_clients();
1083 let result = agg.process_transaction(transaction, None).await;
1084 println!("{result:?}");
1085 }
1086 ToolCommand::GenesisCeremony(cmd) => {
1087 crate::genesis_ceremony::run(cmd).await?;
1088 }
1089 ToolCommand::FireDrill { fire_drill } => {
1090 crate::fire_drill::run_fire_drill(fire_drill).await?;
1091 }
1092 };
1093 Ok(())
1094 }
1095}