sui_indexer_alt/
bootstrap.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::time::Duration;
5
6use crate::Indexer;
7use anyhow::{Context, Result, bail};
8use diesel::{OptionalExtension, QueryDsl, SelectableHelper};
9use diesel_async::RunQueryDsl;
10use sui_indexer_alt_framework::postgres::Db;
11use sui_indexer_alt_framework::types::{
12    full_checkpoint_content::Checkpoint,
13    sui_system_state::{SuiSystemStateTrait, get_sui_system_state},
14    transaction::TransactionKind,
15};
16use sui_indexer_alt_schema::{
17    checkpoints::StoredGenesis,
18    epochs::StoredEpochStart,
19    schema::{kv_epoch_starts, kv_genesis},
20};
21use sui_types::transaction::TransactionDataAPI;
22use tracing::info;
23
24pub struct BootstrapGenesis {
25    pub stored_genesis: StoredGenesis,
26    pub stored_epoch_start: StoredEpochStart,
27}
28
29/// Ensures the genesis table has been populated before the rest of the indexer is run, and returns
30/// the information stored there. If the database has been bootstrapped before, this function will
31/// simply read the previously bootstrapped information. Otherwise, it will wait until the first
32/// checkpoint is available and extract the necessary information from there.
33pub async fn bootstrap(
34    indexer: &Indexer<Db>,
35    retry_interval: Duration,
36    bootstrap_genesis: Option<BootstrapGenesis>,
37) -> Result<StoredGenesis> {
38    info!("Bootstrapping indexer with genesis information");
39
40    let Ok(mut conn) = indexer.store().connect().await else {
41        bail!("Bootstrap failed to get connection for DB");
42    };
43
44    // 1. If the row has already been written, return it.
45    if let Some(genesis) = kv_genesis::table
46        .select(StoredGenesis::as_select())
47        .first(&mut conn)
48        .await
49        .optional()?
50    {
51        info!(
52            chain = genesis.chain()?.as_str(),
53            protocol = ?genesis.initial_protocol_version(),
54            "Indexer already bootstrapped",
55        );
56
57        return Ok(genesis);
58    }
59
60    let BootstrapGenesis {
61        stored_genesis,
62        stored_epoch_start,
63    } = match bootstrap_genesis {
64        // 2. If genesis is provided, use it to bootstrap.
65        Some(bootstrap_genesis) => bootstrap_genesis,
66        // 3. Otherwise, extract the necessary information from the genesis checkpoint:
67        //
68        // - Get the Genesis system transaction from the genesis checkpoint.
69        // - Get the system state object that was written out by the system transaction.
70        None => {
71            let genesis_checkpoint = indexer
72                .ingestion_client()
73                .wait_for(0, retry_interval)
74                .await
75                .context("Failed to fetch genesis checkpoint")?;
76
77            let Checkpoint {
78                summary: checkpoint_summary,
79                transactions,
80                ..
81            } = genesis_checkpoint.as_ref();
82
83            let Some(genesis_transaction) = transactions
84                .iter()
85                .find(|tx| matches!(tx.transaction.kind(), TransactionKind::Genesis(_)))
86            else {
87                bail!("Could not find Genesis transaction");
88            };
89
90            let output_objects: Vec<_> = genesis_transaction
91                .output_objects(&genesis_checkpoint.object_set)
92                .cloned()
93                .collect();
94
95            let sui_system_state = get_sui_system_state(&output_objects.as_slice())
96                .context("Failed to get Genesis SystemState")?;
97
98            let stored_genesis = StoredGenesis {
99                genesis_digest: checkpoint_summary.digest().inner().to_vec(),
100                initial_protocol_version: sui_system_state.protocol_version() as i64,
101            };
102            let stored_epoch_start = StoredEpochStart {
103                epoch: 0,
104                protocol_version: sui_system_state.protocol_version() as i64,
105                cp_lo: 0,
106                start_timestamp_ms: sui_system_state.epoch_start_timestamp_ms() as i64,
107                reference_gas_price: sui_system_state.reference_gas_price() as i64,
108                system_state: bcs::to_bytes(&sui_system_state)
109                    .context("Failed to serialize SystemState")?,
110            };
111
112            BootstrapGenesis {
113                stored_genesis,
114                stored_epoch_start,
115            }
116        }
117    };
118
119    info!(
120        chain = stored_genesis.chain()?.as_str(),
121        protocol = ?stored_genesis.initial_protocol_version(),
122        "Bootstrapped indexer",
123    );
124
125    diesel::insert_into(kv_genesis::table)
126        .values(&stored_genesis)
127        .on_conflict_do_nothing()
128        .execute(&mut conn)
129        .await
130        .context("Failed to write genesis record")?;
131
132    diesel::insert_into(kv_epoch_starts::table)
133        .values(&stored_epoch_start)
134        .on_conflict_do_nothing()
135        .execute(&mut conn)
136        .await
137        .context("Failed to write genesis epoch start record")?;
138
139    Ok(stored_genesis)
140}