Skip to main content

chia_sdk_test/full_node_simulator/
queries.rs

1use std::collections::HashSet;
2
3use chia_bls::SecretKey;
4use chia_protocol::Bytes32;
5use chia_sdk_coinset::{
6    AdditionsAndRemovalsResponse, BlockchainState, BlockchainStateResponse, CoinRecord,
7    GetBlockRecordResponse, GetBlockRecordsResponse, GetBlockSpendsResponse, GetCoinRecordResponse,
8    GetCoinRecordsResponse, GetMempoolItemResponse, GetMempoolItemsResponse,
9    GetNetworkInfoResponse, GetPuzzleAndSolutionResponse, MempoolMinFees, SyncState,
10};
11
12use super::{FullNodeSimulator, SIMULATOR_GENESIS_CHALLENGE, SimCoinRecord, ValidatedBundle};
13
14impl FullNodeSimulator {
15    pub fn height(&self) -> u32 {
16        self.state.height
17    }
18
19    pub fn header_hash(&self) -> Bytes32 {
20        self.state.header_hashes.last().copied().unwrap_or_default()
21    }
22
23    pub fn header_hash_of(&self, height: u32) -> Option<Bytes32> {
24        self.state
25            .header_hashes
26            .get((height as usize).saturating_sub(1))
27            .copied()
28    }
29
30    pub fn get_farming_ph(&self) -> Bytes32 {
31        self.farming_puzzle_hash
32    }
33
34    pub fn get_master_secret_key(&self) -> SecretKey {
35        self.master_secret_key.clone()
36    }
37
38    pub fn get_prefarm_puzzle_hash(&self) -> Bytes32 {
39        self.prefarm_puzzle_hash
40    }
41
42    pub fn get_blockchain_state(&self) -> BlockchainStateResponse {
43        let peak = self.state.blocks.get(&self.header_hash()).map_or_else(
44            || {
45                Self::make_block_record(
46                    Bytes32::default(),
47                    Bytes32::default(),
48                    0,
49                    0,
50                    Bytes32::default(),
51                    0,
52                    0,
53                    self.farming_puzzle_hash,
54                    Vec::new(),
55                )
56            },
57            |block| block.record.clone(),
58        );
59
60        BlockchainStateResponse {
61            blockchain_state: Some(BlockchainState {
62                average_block_time: 1,
63                block_max_cost: 11_000_000_000,
64                difficulty: 1,
65                genesis_challenge_initialized: true,
66                mempool_cost: self.mempool.values().map(|item| item.cost).sum(),
67                mempool_fees: self.mempool.values().map(|item| item.fee).sum(),
68                mempool_max_total_cost: 110_000_000_000,
69                mempool_min_fees: MempoolMinFees { cost_5000000: 0 },
70                mempool_size: self.mempool.len().try_into().unwrap(),
71                node_id: self.node_id,
72                peak,
73                space: 0,
74                sub_slot_iters: 1,
75                sync: SyncState {
76                    sync_mode: false,
77                    sync_progress_height: self.state.height,
78                    sync_tip_height: self.state.height,
79                    synced: true,
80                },
81            }),
82            error: None,
83            success: true,
84        }
85    }
86
87    pub fn get_network_info(&self) -> GetNetworkInfoResponse {
88        GetNetworkInfoResponse {
89            network_name: Some("simulator0".to_string()),
90            network_prefix: Some("txch".to_string()),
91            genesis_challenge: Some(SIMULATOR_GENESIS_CHALLENGE),
92            error: None,
93            success: true,
94        }
95    }
96
97    pub fn get_aggsig_additional_data(&self) -> Bytes32 {
98        SIMULATOR_GENESIS_CHALLENGE
99    }
100
101    pub fn get_block_record(&self, header_hash: Bytes32) -> GetBlockRecordResponse {
102        GetBlockRecordResponse {
103            block_record: self
104                .state
105                .blocks
106                .get(&header_hash)
107                .or_else(|| self.orphaned_blocks.get(&header_hash))
108                .map(|block| block.record.clone()),
109            error: None,
110            success: true,
111        }
112    }
113
114    pub fn get_block_record_by_height(&self, height: u32) -> GetBlockRecordResponse {
115        let block_record = self
116            .header_hash_of(height)
117            .and_then(|header_hash| self.state.blocks.get(&header_hash))
118            .map(|block| block.record.clone());
119
120        GetBlockRecordResponse {
121            block_record,
122            error: None,
123            success: true,
124        }
125    }
126
127    pub fn get_block_records(&self, start: u32, end: u32) -> GetBlockRecordsResponse {
128        let block_records = (start..end)
129            .filter_map(|height| self.get_block_record_by_height(height).block_record)
130            .collect();
131
132        GetBlockRecordsResponse {
133            block_records: Some(block_records),
134            error: None,
135            success: true,
136        }
137    }
138
139    pub fn get_additions_and_removals(&self, header_hash: Bytes32) -> AdditionsAndRemovalsResponse {
140        let Some(block) = self
141            .state
142            .blocks
143            .get(&header_hash)
144            .or_else(|| self.orphaned_blocks.get(&header_hash))
145        else {
146            return AdditionsAndRemovalsResponse {
147                additions: None,
148                removals: None,
149                error: Some("block not found".to_string()),
150                success: false,
151            };
152        };
153
154        AdditionsAndRemovalsResponse {
155            additions: Some(self.records_for_ids(&block.additions)),
156            removals: Some(self.records_for_ids(&block.removals)),
157            error: None,
158            success: true,
159        }
160    }
161
162    pub fn get_block_spends(&self, header_hash: Bytes32) -> GetBlockSpendsResponse {
163        GetBlockSpendsResponse {
164            block_spends: self
165                .state
166                .blocks
167                .get(&header_hash)
168                .or_else(|| self.orphaned_blocks.get(&header_hash))
169                .map(|block| block.spends.clone()),
170            error: None,
171            success: true,
172        }
173    }
174
175    pub fn get_coin_record_by_name(&self, name: Bytes32) -> GetCoinRecordResponse {
176        GetCoinRecordResponse {
177            coin_record: self
178                .state
179                .coins
180                .get(&name)
181                .map(|record| record.to_coin_record()),
182            error: None,
183            success: true,
184        }
185    }
186
187    pub fn get_coin_records_by_names(
188        &self,
189        names: &[Bytes32],
190        start_height: Option<u32>,
191        end_height: Option<u32>,
192        include_spent_coins: Option<bool>,
193    ) -> GetCoinRecordsResponse {
194        Self::records_response(
195            self.state
196                .coins
197                .iter()
198                .filter(|(coin_id, _)| names.contains(coin_id))
199                .map(|(_, record)| *record),
200            start_height,
201            end_height,
202            include_spent_coins,
203        )
204    }
205
206    pub fn get_coin_records_by_hint(
207        &self,
208        hint: Bytes32,
209        start_height: Option<u32>,
210        end_height: Option<u32>,
211        include_spent_coins: Option<bool>,
212    ) -> GetCoinRecordsResponse {
213        self.get_coin_records_by_hints(vec![hint], start_height, end_height, include_spent_coins)
214    }
215
216    pub fn get_coin_records_by_hints(
217        &self,
218        hints: Vec<Bytes32>,
219        start_height: Option<u32>,
220        end_height: Option<u32>,
221        include_spent_coins: Option<bool>,
222    ) -> GetCoinRecordsResponse {
223        let hints: HashSet<Bytes32> = hints.into_iter().collect();
224        Self::records_response(
225            self.state
226                .coins
227                .iter()
228                .filter(|(coin_id, _)| {
229                    self.state
230                        .coin_hints
231                        .get(*coin_id)
232                        .is_some_and(|hint| hints.contains(hint))
233                })
234                .map(|(_, record)| *record),
235            start_height,
236            end_height,
237            include_spent_coins,
238        )
239    }
240
241    pub fn get_coin_records_by_parent_ids(
242        &self,
243        parent_ids: Vec<Bytes32>,
244        start_height: Option<u32>,
245        end_height: Option<u32>,
246        include_spent_coins: Option<bool>,
247    ) -> GetCoinRecordsResponse {
248        let parent_ids: HashSet<Bytes32> = parent_ids.into_iter().collect();
249        Self::records_response(
250            self.state
251                .coins
252                .values()
253                .filter(|record| parent_ids.contains(&record.coin.parent_coin_info))
254                .copied(),
255            start_height,
256            end_height,
257            include_spent_coins,
258        )
259    }
260
261    pub fn get_coin_records_by_puzzle_hash(
262        &self,
263        puzzle_hash: Bytes32,
264        start_height: Option<u32>,
265        end_height: Option<u32>,
266        include_spent_coins: Option<bool>,
267    ) -> GetCoinRecordsResponse {
268        self.get_coin_records_by_puzzle_hashes(
269            vec![puzzle_hash],
270            start_height,
271            end_height,
272            include_spent_coins,
273        )
274    }
275
276    pub fn get_coin_records_by_puzzle_hashes(
277        &self,
278        puzzle_hashes: Vec<Bytes32>,
279        start_height: Option<u32>,
280        end_height: Option<u32>,
281        include_spent_coins: Option<bool>,
282    ) -> GetCoinRecordsResponse {
283        let puzzle_hashes: HashSet<Bytes32> = puzzle_hashes.into_iter().collect();
284        Self::records_response(
285            self.state
286                .coins
287                .values()
288                .filter(|record| puzzle_hashes.contains(&record.coin.puzzle_hash))
289                .copied(),
290            start_height,
291            end_height,
292            include_spent_coins,
293        )
294    }
295
296    pub fn get_puzzle_and_solution(
297        &self,
298        coin_id: Bytes32,
299        height: Option<u32>,
300    ) -> GetPuzzleAndSolutionResponse {
301        let coin_solution = self.state.coin_spends.get(&coin_id).and_then(|spend| {
302            let record = self.state.coins.get(&coin_id)?;
303            if height.is_none() || record.spent_block_index == height {
304                Some(spend.clone())
305            } else {
306                None
307            }
308        });
309
310        GetPuzzleAndSolutionResponse {
311            coin_solution,
312            error: None,
313            success: true,
314        }
315    }
316
317    pub fn get_mempool_item_by_tx_id(&self, tx_id: Bytes32) -> GetMempoolItemResponse {
318        GetMempoolItemResponse {
319            mempool_item: self
320                .mempool
321                .get(&tx_id)
322                .map(ValidatedBundle::to_mempool_item),
323            error: None,
324            success: true,
325        }
326    }
327
328    pub fn get_mempool_items_by_coin_name(&self, coin_name: Bytes32) -> GetMempoolItemsResponse {
329        GetMempoolItemsResponse {
330            mempool_items: Some(
331                self.mempool
332                    .values()
333                    .filter(|item| item.removals.contains(&coin_name))
334                    .map(ValidatedBundle::to_mempool_item)
335                    .collect(),
336            ),
337            error: None,
338            success: true,
339        }
340    }
341
342    pub(super) fn records_for_ids(&self, coin_ids: &[Bytes32]) -> Vec<CoinRecord> {
343        coin_ids
344            .iter()
345            .filter_map(|coin_id| self.state.coins.get(coin_id))
346            .map(|record| record.to_coin_record())
347            .collect()
348    }
349
350    fn records_response(
351        records: impl IntoIterator<Item = SimCoinRecord>,
352        start_height: Option<u32>,
353        end_height: Option<u32>,
354        include_spent_coins: Option<bool>,
355    ) -> GetCoinRecordsResponse {
356        let include_spent = include_spent_coins.unwrap_or(false);
357        let records = records
358            .into_iter()
359            .filter(|record| include_spent || record.spent_block_index.is_none())
360            .filter(|record| {
361                start_height.is_none_or(|start| record.confirmed_block_index >= start)
362                    && end_height.is_none_or(|end| record.confirmed_block_index < end)
363            })
364            .map(SimCoinRecord::to_coin_record)
365            .collect();
366
367        GetCoinRecordsResponse {
368            coin_records: Some(records),
369            error: None,
370            success: true,
371            next_cursor: None,
372            truncated: None,
373        }
374    }
375}