1use std::collections::HashMap;
6
7use chia::consensus::consensus_constants::ConsensusConstants;
8use chia::consensus::flags::DONT_VALIDATE_SIGNATURE;
9use serde_json::Value;
10
11use crate::coinset::CoinsetClient;
12use crate::peer::PeerBackend;
13use crate::types::*;
14
15fn run_puzzle_conditions(spend: &CoinSpend, constants: &ConsensusConstants) -> Vec<Condition> {
22 let flags = DONT_VALIDATE_SIGNATURE;
23 let Ok(puzzle_bytes) = crate::peer::translate::parse_hex(&spend.puzzle_reveal) else {
24 return Vec::new();
25 };
26 let Ok(solution_bytes) = crate::peer::translate::parse_hex(&spend.solution) else {
27 return Vec::new();
28 };
29
30 let mut allocator = chia::consensus::allocator::make_allocator(flags);
31
32 let Ok(puzzle_node) = clvmr::serde::node_from_bytes(&mut allocator, &puzzle_bytes) else {
33 return Vec::new();
34 };
35 let Ok(solution_node) = clvmr::serde::node_from_bytes(&mut allocator, &solution_bytes) else {
36 return Vec::new();
37 };
38
39 let dialect = clvmr::chia_dialect::ChiaDialect::new(flags);
40 match clvmr::run_program::run_program(
41 &mut allocator,
42 &dialect,
43 puzzle_node,
44 solution_node,
45 constants.max_block_cost_clvm,
46 ) {
47 Ok(clvmr::reduction::Reduction(_, output)) => {
48 crate::peer::block::parse_conditions_public(&allocator, output)
49 }
50 Err(_) => Vec::new(),
51 }
52}
53
54pub struct QueryRouter {
55 pub(crate) peer: PeerBackend,
56 pub(crate) coinset: CoinsetClient,
57 pub(crate) coinset_fallback_enabled: bool,
58}
59
60impl QueryRouter {
65 async fn peer_then_coinset<T>(
68 &self,
69 peer_fn: impl std::future::Future<Output = Result<T, ChiaQueryError>>,
70 peer_retry: impl std::future::Future<Output = Result<T, ChiaQueryError>>,
71 coinset_fn: impl std::future::Future<Output = Result<T, ChiaQueryError>>,
72 ) -> Result<T, ChiaQueryError> {
73 match peer_fn.await {
75 Ok(v) => return Ok(v),
76 Err(e) => log::debug!("peer attempt 1 failed: {e}"),
77 }
78
79 match peer_retry.await {
81 Ok(v) => Ok(v),
82 Err(peer_err) => {
83 if !self.coinset_fallback_enabled {
84 return Err(peer_err);
85 }
86 coinset_fn
88 .await
89 .map_err(|ce| ChiaQueryError::AllSourcesFailed {
90 peer_error: Box::new(peer_err),
91 coinset_error: Some(Box::new(ce)),
92 })
93 }
94 }
95 }
96
97 fn require_coinset(&self, endpoint: &str) -> Result<(), ChiaQueryError> {
99 if !self.coinset_fallback_enabled {
100 Err(ChiaQueryError::UnsupportedWithoutCoinset(endpoint.into()))
101 } else {
102 Ok(())
103 }
104 }
105}
106
107impl QueryRouter {
112 pub async fn get_additions_and_removals(
116 &self,
117 header_hash: &str,
118 ) -> Result<AdditionsAndRemovals, ChiaQueryError> {
119 if let Ok(record) = self.get_block_record(header_hash).await {
121 match self
123 .peer
124 .try_get_additions_and_removals_from_block(record.height)
125 .await
126 {
127 Ok(r) => return Ok(r),
128 Err(e) => log::debug!("peer additions_and_removals failed: {e}"),
129 }
130 }
131 if self.coinset_fallback_enabled {
133 self.coinset.get_additions_and_removals(header_hash).await
134 } else {
135 Err(ChiaQueryError::UnsupportedWithoutCoinset(
136 "get_additions_and_removals".into(),
137 ))
138 }
139 }
140
141 pub async fn get_block(&self, header_hash: &str) -> Result<FullBlock, ChiaQueryError> {
142 if let Ok(record) = self.get_block_record(header_hash).await {
144 match self.peer.try_get_block_by_height(record.height).await {
145 Ok(b) => return Ok(b),
146 Err(e) => log::debug!("peer get_block failed: {e}"),
147 }
148 }
149 if self.coinset_fallback_enabled {
150 self.coinset.get_block(header_hash).await
151 } else {
152 Err(ChiaQueryError::UnsupportedWithoutCoinset(
153 "get_block".into(),
154 ))
155 }
156 }
157
158 pub async fn get_block_by_height(&self, height: u32) -> Result<FullBlock, ChiaQueryError> {
160 self.peer_then_coinset(
161 self.peer.try_get_block_by_height(height),
162 self.peer.try_get_block_by_height(height),
163 async {
164 let record = self.coinset.get_block_record_by_height(height).await?;
167 self.coinset.get_block(&record.header_hash).await
168 },
169 )
170 .await
171 }
172
173 pub async fn get_block_count_metrics(&self) -> Result<BlockCountMetrics, ChiaQueryError> {
174 self.require_coinset("get_block_count_metrics")?;
175 self.coinset.get_block_count_metrics().await
176 }
177
178 pub async fn get_block_record(&self, header_hash: &str) -> Result<BlockRecord, ChiaQueryError> {
179 self.require_coinset("get_block_record")?;
180 self.coinset.get_block_record(header_hash).await
181 }
182
183 pub async fn get_block_record_by_height(
186 &self,
187 height: u32,
188 ) -> Result<BlockRecord, ChiaQueryError> {
189 self.peer_then_coinset(
190 self.peer.try_get_block_record_by_height(height),
191 self.peer.try_get_block_record_by_height(height),
192 self.coinset.get_block_record_by_height(height),
193 )
194 .await
195 }
196
197 pub async fn get_block_records(
198 &self,
199 start: u32,
200 end: u32,
201 ) -> Result<Vec<BlockRecord>, ChiaQueryError> {
202 self.peer_then_coinset(
203 self.peer.try_get_block_records(start, end),
204 self.peer.try_get_block_records(start, end),
205 self.coinset.get_block_records(start, end),
206 )
207 .await
208 }
209
210 pub async fn get_block_spends(
213 &self,
214 header_hash: &str,
215 ) -> Result<Vec<CoinSpend>, ChiaQueryError> {
216 if let Ok(record) = self.get_block_record(header_hash).await {
218 match self
219 .peer
220 .try_get_block_spends_by_height(record.height)
221 .await
222 {
223 Ok(r) => return Ok(r),
224 Err(e) => log::debug!("peer block_spends failed: {e}"),
225 }
226 }
227 if self.coinset_fallback_enabled {
228 self.coinset.get_block_spends(header_hash).await
229 } else {
230 Err(ChiaQueryError::UnsupportedWithoutCoinset(
231 "get_block_spends".into(),
232 ))
233 }
234 }
235
236 pub async fn get_block_spends_with_conditions(
239 &self,
240 header_hash: &str,
241 ) -> Result<Vec<CoinSpendWithConditions>, ChiaQueryError> {
242 if let Ok(record) = self.get_block_record(header_hash).await {
243 match self
244 .peer
245 .try_get_block_spends_with_conditions(record.height)
246 .await
247 {
248 Ok(r) => return Ok(r),
249 Err(e) => log::debug!("peer block_spends_with_conditions failed: {e}"),
250 }
251 }
252 if self.coinset_fallback_enabled {
253 self.coinset
254 .get_block_spends_with_conditions(header_hash)
255 .await
256 } else {
257 Err(ChiaQueryError::UnsupportedWithoutCoinset(
258 "get_block_spends_with_conditions".into(),
259 ))
260 }
261 }
262
263 pub async fn get_blocks(
264 &self,
265 start: u32,
266 end: u32,
267 exclude_header_hash: bool,
268 exclude_reorged: bool,
269 ) -> Result<Vec<FullBlock>, ChiaQueryError> {
270 self.peer_then_coinset(
271 self.peer.try_get_blocks_range(start, end),
272 self.peer.try_get_blocks_range(start, end),
273 self.coinset
274 .get_blocks(start, end, exclude_header_hash, exclude_reorged),
275 )
276 .await
277 }
278
279 pub async fn get_unfinished_block_headers(
280 &self,
281 ) -> Result<Vec<UnfinishedBlockHeader>, ChiaQueryError> {
282 self.require_coinset("get_unfinished_block_headers")?;
283 self.coinset.get_unfinished_block_headers().await
284 }
285}
286
287impl QueryRouter {
292 pub async fn get_coin_record_by_name(&self, name: &str) -> Result<CoinRecord, ChiaQueryError> {
293 self.peer_then_coinset(
294 self.peer.try_get_coin_record_by_name(name),
295 self.peer.try_get_coin_record_by_name(name),
296 self.coinset.get_coin_record_by_name(name),
297 )
298 .await
299 }
300
301 pub async fn get_coin_records_by_hint(
302 &self,
303 hint: &str,
304 start_height: Option<u32>,
305 end_height: Option<u32>,
306 include_spent_coins: bool,
307 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
308 self.peer_then_coinset(
309 self.peer.try_get_coin_records_by_hint(
310 hint,
311 start_height,
312 end_height,
313 include_spent_coins,
314 ),
315 self.peer.try_get_coin_records_by_hint(
316 hint,
317 start_height,
318 end_height,
319 include_spent_coins,
320 ),
321 self.coinset.get_coin_records_by_hint(
322 hint,
323 start_height,
324 end_height,
325 include_spent_coins,
326 ),
327 )
328 .await
329 }
330
331 pub async fn get_coin_records_by_hints(
332 &self,
333 hints: &[String],
334 start_height: Option<u32>,
335 end_height: Option<u32>,
336 include_spent_coins: bool,
337 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
338 self.peer_then_coinset(
339 self.peer.try_get_coin_records_by_hints(
340 hints,
341 start_height,
342 end_height,
343 include_spent_coins,
344 ),
345 self.peer.try_get_coin_records_by_hints(
346 hints,
347 start_height,
348 end_height,
349 include_spent_coins,
350 ),
351 self.coinset.get_coin_records_by_hints(
352 hints,
353 start_height,
354 end_height,
355 include_spent_coins,
356 ),
357 )
358 .await
359 }
360
361 pub async fn get_coin_records_by_names(
362 &self,
363 names: &[String],
364 start_height: Option<u32>,
365 end_height: Option<u32>,
366 include_spent_coins: bool,
367 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
368 self.peer_then_coinset(
369 self.peer.try_get_coin_records_by_names(names),
370 self.peer.try_get_coin_records_by_names(names),
371 self.coinset.get_coin_records_by_names(
372 names,
373 start_height,
374 end_height,
375 include_spent_coins,
376 ),
377 )
378 .await
379 }
380
381 pub async fn get_coin_records_by_parent_ids(
385 &self,
386 parent_ids: &[String],
387 start_height: Option<u32>,
388 end_height: Option<u32>,
389 include_spent_coins: bool,
390 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
391 let peer_attempt = async {
393 let mut all_records = Vec::new();
394 for parent_id in parent_ids {
395 let children = self.peer.try_get_children(parent_id).await?;
396 all_records.extend(children);
397 }
398 all_records.retain(|r| {
400 let height_ok = match (start_height, end_height) {
401 (Some(s), Some(e)) => {
402 r.confirmed_block_index >= s && r.confirmed_block_index <= e
403 }
404 (Some(s), None) => r.confirmed_block_index >= s,
405 (None, Some(e)) => r.confirmed_block_index <= e,
406 (None, None) => true,
407 };
408 let spent_ok = include_spent_coins || !r.spent;
409 height_ok && spent_ok
410 });
411 Ok(all_records)
412 };
413
414 match peer_attempt.await {
415 Ok(r) => Ok(r),
416 Err(peer_err) => {
417 if self.coinset_fallback_enabled {
418 self.coinset
419 .get_coin_records_by_parent_ids(
420 parent_ids,
421 start_height,
422 end_height,
423 include_spent_coins,
424 )
425 .await
426 .map_err(|ce| ChiaQueryError::AllSourcesFailed {
427 peer_error: Box::new(peer_err),
428 coinset_error: Some(Box::new(ce)),
429 })
430 } else {
431 Err(peer_err)
432 }
433 }
434 }
435 }
436
437 pub async fn get_coin_records_by_puzzle_hash(
438 &self,
439 puzzle_hash: &str,
440 start_height: Option<u32>,
441 end_height: Option<u32>,
442 include_spent_coins: bool,
443 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
444 self.peer_then_coinset(
445 self.peer.try_get_coin_records_by_puzzle_hash(
446 puzzle_hash,
447 start_height,
448 end_height,
449 include_spent_coins,
450 ),
451 self.peer.try_get_coin_records_by_puzzle_hash(
452 puzzle_hash,
453 start_height,
454 end_height,
455 include_spent_coins,
456 ),
457 self.coinset.get_coin_records_by_puzzle_hash(
458 puzzle_hash,
459 start_height,
460 end_height,
461 include_spent_coins,
462 ),
463 )
464 .await
465 }
466
467 pub async fn get_coin_records_by_puzzle_hashes(
468 &self,
469 puzzle_hashes: &[String],
470 start_height: Option<u32>,
471 end_height: Option<u32>,
472 include_spent_coins: bool,
473 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
474 self.peer_then_coinset(
475 self.peer.try_get_coin_records_by_puzzle_hashes(
476 puzzle_hashes,
477 start_height,
478 end_height,
479 include_spent_coins,
480 ),
481 self.peer.try_get_coin_records_by_puzzle_hashes(
482 puzzle_hashes,
483 start_height,
484 end_height,
485 include_spent_coins,
486 ),
487 self.coinset.get_coin_records_by_puzzle_hashes(
488 puzzle_hashes,
489 start_height,
490 end_height,
491 include_spent_coins,
492 ),
493 )
494 .await
495 }
496
497 pub async fn get_memos_by_coin_name(&self, name: &str) -> Result<Value, ChiaQueryError> {
499 self.require_coinset("get_memos_by_coin_name")?;
500 self.coinset.get_memos_by_coin_name(name).await
501 }
502
503 pub async fn get_puzzle_and_solution(
504 &self,
505 coin_id: &str,
506 height: Option<u32>,
507 ) -> Result<CoinSpend, ChiaQueryError> {
508 if let Some(h) = height {
509 self.peer_then_coinset(
510 self.peer.try_get_puzzle_and_solution(coin_id, h),
511 self.peer.try_get_puzzle_and_solution(coin_id, h),
512 self.coinset.get_puzzle_and_solution(coin_id, height),
513 )
514 .await
515 } else {
516 self.peer_then_coinset(
518 self.peer.try_get_puzzle_and_solution_auto(coin_id),
519 self.peer.try_get_puzzle_and_solution_auto(coin_id),
520 self.coinset.get_puzzle_and_solution(coin_id, None),
521 )
522 .await
523 }
524 }
525
526 pub async fn get_puzzle_and_solution_with_conditions(
529 &self,
530 coin_id: &str,
531 height: Option<u32>,
532 ) -> Result<CoinSpendWithConditions, ChiaQueryError> {
533 let spend = match self.get_puzzle_and_solution(coin_id, height).await {
535 Ok(s) => s,
536 Err(_) => {
537 if self.coinset_fallback_enabled {
538 return self
539 .coinset
540 .get_puzzle_and_solution_with_conditions(coin_id, height)
541 .await;
542 }
543 return Err(ChiaQueryError::PeerRejection(
544 "could not retrieve puzzle and solution".into(),
545 ));
546 }
547 };
548
549 let conditions = run_puzzle_conditions(&spend, self.peer.constants());
551 Ok(CoinSpendWithConditions {
552 coin_spend: spend,
553 conditions,
554 })
555 }
556
557 pub async fn push_tx(&self, bundle: &SpendBundle) -> Result<TxStatus, ChiaQueryError> {
558 self.peer_then_coinset(
559 self.peer.try_push_tx(bundle),
560 self.peer.try_push_tx(bundle),
561 self.coinset.push_tx(bundle),
562 )
563 .await
564 }
565}
566
567impl QueryRouter {
572 pub async fn get_fee_estimate(
573 &self,
574 spend_bundle: Option<&SpendBundle>,
575 target_times: Option<&[u64]>,
576 spend_count: Option<u64>,
577 ) -> Result<FeeEstimate, ChiaQueryError> {
578 let times = target_times.unwrap_or(&[60, 120, 300]);
579 self.peer_then_coinset(
580 self.peer.try_get_fee_estimate(times),
581 self.peer.try_get_fee_estimate(times),
582 self.coinset
583 .get_fee_estimate(spend_bundle, target_times, spend_count),
584 )
585 .await
586 }
587}
588
589impl QueryRouter {
594 pub async fn get_aggsig_additional_data(&self) -> Result<String, ChiaQueryError> {
597 Ok(self.peer.aggsig_additional_data())
598 }
599
600 pub async fn get_network_info(&self) -> Result<NetworkInfo, ChiaQueryError> {
603 Ok(self.peer.network_info())
604 }
605
606 pub async fn get_blockchain_state(&self) -> Result<BlockchainState, ChiaQueryError> {
609 if self.coinset_fallback_enabled {
611 if let Ok(state) = self.coinset.get_blockchain_state().await {
612 return Ok(state);
613 }
614 }
615 let peak = self.peer.peak_height();
617 if peak == 0 {
618 return Err(ChiaQueryError::PeerConnection(
619 "no peak observed from peers yet".into(),
620 ));
621 }
622 Ok(BlockchainState {
623 peak: Some(BlockRecord {
624 height: peak,
625 ..Default::default()
626 }),
627 sync: Some(SyncState {
628 synced: true,
629 sync_mode: false,
630 sync_progress_height: peak,
631 sync_tip_height: peak,
632 }),
633 ..Default::default()
634 })
635 }
636
637 pub async fn get_network_space(
638 &self,
639 newer_block_header_hash: &str,
640 older_block_header_hash: &str,
641 ) -> Result<u64, ChiaQueryError> {
642 self.require_coinset("get_network_space")?;
643 self.coinset
644 .get_network_space(newer_block_header_hash, older_block_header_hash)
645 .await
646 }
647}
648
649impl QueryRouter {
654 pub async fn get_all_mempool_items(
655 &self,
656 ) -> Result<HashMap<String, MempoolItem>, ChiaQueryError> {
657 self.require_coinset("get_all_mempool_items")?;
658 self.coinset.get_all_mempool_items().await
659 }
660
661 pub async fn get_all_mempool_tx_ids(&self) -> Result<Vec<String>, ChiaQueryError> {
662 self.require_coinset("get_all_mempool_tx_ids")?;
663 self.coinset.get_all_mempool_tx_ids().await
664 }
665
666 pub async fn get_mempool_item_by_tx_id(
667 &self,
668 tx_id: &str,
669 ) -> Result<MempoolItem, ChiaQueryError> {
670 self.require_coinset("get_mempool_item_by_tx_id")?;
671 self.coinset.get_mempool_item_by_tx_id(tx_id).await
672 }
673
674 pub async fn get_mempool_items_by_coin_name(
675 &self,
676 coin_name: &str,
677 include_spent_coins: Option<bool>,
678 ) -> Result<Vec<MempoolItem>, ChiaQueryError> {
679 self.require_coinset("get_mempool_items_by_coin_name")?;
680 self.coinset
681 .get_mempool_items_by_coin_name(coin_name, include_spent_coins)
682 .await
683 }
684}