1use 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 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}