iota_rosetta/
state.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::sync::Arc;
6
7use async_trait::async_trait;
8use iota_json_rpc_types::IotaTransactionBlockResponseOptions;
9use iota_sdk::{IotaClient, rpc_types::Checkpoint};
10use iota_types::messages_checkpoint::CheckpointSequenceNumber;
11
12use crate::{
13    Error,
14    operations::Operations,
15    types::{Block, BlockHash, BlockIdentifier, BlockResponse, Transaction, TransactionIdentifier},
16};
17
18#[cfg(test)]
19#[path = "unit_tests/balance_changing_tx_tests.rs"]
20mod balance_changing_tx_tests;
21
22#[derive(Clone)]
23pub struct OnlineServerContext {
24    pub client: IotaClient,
25    block_provider: Arc<dyn BlockProvider + Send + Sync>,
26}
27
28impl OnlineServerContext {
29    pub fn new(client: IotaClient, block_provider: Arc<dyn BlockProvider + Send + Sync>) -> Self {
30        Self {
31            client,
32            block_provider,
33        }
34    }
35
36    pub fn blocks(&self) -> &(dyn BlockProvider + Sync + Send) {
37        &*self.block_provider
38    }
39}
40
41#[async_trait]
42pub trait BlockProvider {
43    async fn get_block_by_index(&self, index: u64) -> Result<BlockResponse, Error>;
44    async fn get_block_by_hash(&self, hash: BlockHash) -> Result<BlockResponse, Error>;
45    async fn current_block(&self) -> Result<BlockResponse, Error>;
46    async fn genesis_block_identifier(&self) -> Result<BlockIdentifier, Error>;
47    async fn oldest_block_identifier(&self) -> Result<BlockIdentifier, Error>;
48    async fn current_block_identifier(&self) -> Result<BlockIdentifier, Error>;
49    async fn create_block_identifier(
50        &self,
51        checkpoint: CheckpointSequenceNumber,
52    ) -> Result<BlockIdentifier, Error>;
53}
54
55#[derive(Clone)]
56pub struct CheckpointBlockProvider {
57    client: IotaClient,
58}
59
60#[async_trait]
61impl BlockProvider for CheckpointBlockProvider {
62    async fn get_block_by_index(&self, index: u64) -> Result<BlockResponse, Error> {
63        let checkpoint = self.client.read_api().get_checkpoint(index.into()).await?;
64        self.create_block_response(checkpoint).await
65    }
66
67    async fn get_block_by_hash(&self, hash: BlockHash) -> Result<BlockResponse, Error> {
68        let checkpoint = self.client.read_api().get_checkpoint(hash.into()).await?;
69        self.create_block_response(checkpoint).await
70    }
71
72    async fn current_block(&self) -> Result<BlockResponse, Error> {
73        let checkpoint = self
74            .client
75            .read_api()
76            .get_latest_checkpoint_sequence_number()
77            .await?;
78        self.get_block_by_index(checkpoint).await
79    }
80
81    async fn genesis_block_identifier(&self) -> Result<BlockIdentifier, Error> {
82        self.create_block_identifier(0).await
83    }
84
85    async fn oldest_block_identifier(&self) -> Result<BlockIdentifier, Error> {
86        self.create_block_identifier(0).await
87    }
88
89    async fn current_block_identifier(&self) -> Result<BlockIdentifier, Error> {
90        let checkpoint = self
91            .client
92            .read_api()
93            .get_latest_checkpoint_sequence_number()
94            .await?;
95
96        self.create_block_identifier(checkpoint).await
97    }
98
99    async fn create_block_identifier(
100        &self,
101        checkpoint: CheckpointSequenceNumber,
102    ) -> Result<BlockIdentifier, Error> {
103        self.create_block_identifier(checkpoint).await
104    }
105}
106
107impl CheckpointBlockProvider {
108    pub fn new(client: IotaClient) -> Self {
109        Self { client }
110    }
111
112    async fn create_block_response(&self, checkpoint: Checkpoint) -> Result<BlockResponse, Error> {
113        let index = checkpoint.sequence_number;
114        let hash = checkpoint.digest;
115        let mut transactions = vec![];
116        for batch in checkpoint.transactions.chunks(50) {
117            let transaction_responses = self
118                .client
119                .read_api()
120                .multi_get_transactions_with_options(
121                    batch.to_vec(),
122                    IotaTransactionBlockResponseOptions::new()
123                        .with_input()
124                        .with_effects()
125                        .with_balance_changes()
126                        .with_events(),
127                )
128                .await?;
129            for tx in transaction_responses.into_iter() {
130                transactions.push(Transaction {
131                    transaction_identifier: TransactionIdentifier { hash: tx.digest },
132                    operations: Operations::try_from(tx)?,
133                    related_transactions: vec![],
134                    metadata: None,
135                })
136            }
137        }
138
139        // previous digest should only be None for genesis block.
140        if checkpoint.previous_digest.is_none() && index != 0 {
141            return Err(Error::Data(format!(
142                "Previous digest is None for checkpoint [{index}], digest: [{hash:?}]"
143            )));
144        }
145
146        let parent_block_identifier = checkpoint
147            .previous_digest
148            .map(|hash| BlockIdentifier {
149                index: index - 1,
150                hash,
151            })
152            .unwrap_or_else(|| BlockIdentifier { index, hash });
153
154        Ok(BlockResponse {
155            block: Block {
156                block_identifier: BlockIdentifier { index, hash },
157                parent_block_identifier,
158                timestamp: checkpoint.timestamp_ms,
159                transactions,
160                metadata: None,
161            },
162            other_transactions: vec![],
163        })
164    }
165
166    async fn create_block_identifier(
167        &self,
168        seq_number: CheckpointSequenceNumber,
169    ) -> Result<BlockIdentifier, Error> {
170        let checkpoint = self
171            .client
172            .read_api()
173            .get_checkpoint(seq_number.into())
174            .await?;
175        Ok(BlockIdentifier {
176            index: checkpoint.sequence_number,
177            hash: checkpoint.digest,
178        })
179    }
180}