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 async fn peer_then_coinset_opt<T>(
104 &self,
105 peer_fn: impl std::future::Future<Output = Result<Option<T>, ChiaQueryError>>,
106 peer_retry: impl std::future::Future<Output = Result<Option<T>, ChiaQueryError>>,
107 coinset_fn: impl std::future::Future<Output = Result<Option<T>, ChiaQueryError>>,
108 ) -> Result<Option<T>, ChiaQueryError> {
109 match peer_fn.await {
110 Ok(v) => return Ok(v),
111 Err(e) => log::debug!("peer opt attempt 1 failed: {e}"),
112 }
113 match peer_retry.await {
114 Ok(v) => Ok(v),
115 Err(peer_err) => {
116 if !self.coinset_fallback_enabled {
117 return Err(peer_err);
118 }
119 coinset_fn
120 .await
121 .map_err(|ce| ChiaQueryError::AllSourcesFailed {
122 peer_error: Box::new(peer_err),
123 coinset_error: Some(Box::new(ce)),
124 })
125 }
126 }
127 }
128
129 fn require_coinset(&self, endpoint: &str) -> Result<(), ChiaQueryError> {
131 if !self.coinset_fallback_enabled {
132 Err(ChiaQueryError::UnsupportedWithoutCoinset(endpoint.into()))
133 } else {
134 Ok(())
135 }
136 }
137}
138
139impl QueryRouter {
144 pub async fn get_additions_and_removals(
148 &self,
149 header_hash: &str,
150 ) -> Result<AdditionsAndRemovals, ChiaQueryError> {
151 if let Ok(record) = self.get_block_record(header_hash).await {
153 match self
155 .peer
156 .try_get_additions_and_removals_from_block(record.height)
157 .await
158 {
159 Ok(r) => return Ok(r),
160 Err(e) => log::debug!("peer additions_and_removals failed: {e}"),
161 }
162 }
163 if self.coinset_fallback_enabled {
165 self.coinset.get_additions_and_removals(header_hash).await
166 } else {
167 Err(ChiaQueryError::UnsupportedWithoutCoinset(
168 "get_additions_and_removals".into(),
169 ))
170 }
171 }
172
173 pub async fn get_block(&self, header_hash: &str) -> Result<FullBlock, ChiaQueryError> {
174 if let Ok(record) = self.get_block_record(header_hash).await {
176 match self.peer.try_get_block_by_height(record.height).await {
177 Ok(b) => return Ok(b),
178 Err(e) => log::debug!("peer get_block failed: {e}"),
179 }
180 }
181 if self.coinset_fallback_enabled {
182 self.coinset.get_block(header_hash).await
183 } else {
184 Err(ChiaQueryError::UnsupportedWithoutCoinset(
185 "get_block".into(),
186 ))
187 }
188 }
189
190 pub async fn get_block_by_height(&self, height: u32) -> Result<FullBlock, ChiaQueryError> {
192 self.peer_then_coinset(
193 self.peer.try_get_block_by_height(height),
194 self.peer.try_get_block_by_height(height),
195 async {
196 let record = self.coinset.get_block_record_by_height(height).await?;
199 self.coinset.get_block(&record.header_hash).await
200 },
201 )
202 .await
203 }
204
205 pub async fn get_block_count_metrics(&self) -> Result<BlockCountMetrics, ChiaQueryError> {
206 self.require_coinset("get_block_count_metrics")?;
207 self.coinset.get_block_count_metrics().await
208 }
209
210 pub async fn get_block_record(&self, header_hash: &str) -> Result<BlockRecord, ChiaQueryError> {
211 self.require_coinset("get_block_record")?;
212 self.coinset.get_block_record(header_hash).await
213 }
214
215 pub async fn get_block_record_by_height(
218 &self,
219 height: u32,
220 ) -> Result<BlockRecord, ChiaQueryError> {
221 self.peer_then_coinset(
222 self.peer.try_get_block_record_by_height(height),
223 self.peer.try_get_block_record_by_height(height),
224 self.coinset.get_block_record_by_height(height),
225 )
226 .await
227 }
228
229 pub async fn get_block_records(
230 &self,
231 start: u32,
232 end: u32,
233 ) -> Result<Vec<BlockRecord>, ChiaQueryError> {
234 self.peer_then_coinset(
235 self.peer.try_get_block_records(start, end),
236 self.peer.try_get_block_records(start, end),
237 self.coinset.get_block_records(start, end),
238 )
239 .await
240 }
241
242 pub async fn get_block_spends(
245 &self,
246 header_hash: &str,
247 ) -> Result<Vec<CoinSpend>, ChiaQueryError> {
248 if let Ok(record) = self.get_block_record(header_hash).await {
250 match self
251 .peer
252 .try_get_block_spends_by_height(record.height)
253 .await
254 {
255 Ok(r) => return Ok(r),
256 Err(e) => log::debug!("peer block_spends failed: {e}"),
257 }
258 }
259 if self.coinset_fallback_enabled {
260 self.coinset.get_block_spends(header_hash).await
261 } else {
262 Err(ChiaQueryError::UnsupportedWithoutCoinset(
263 "get_block_spends".into(),
264 ))
265 }
266 }
267
268 pub async fn get_block_spends_with_conditions(
271 &self,
272 header_hash: &str,
273 ) -> Result<Vec<CoinSpendWithConditions>, ChiaQueryError> {
274 if let Ok(record) = self.get_block_record(header_hash).await {
275 match self
276 .peer
277 .try_get_block_spends_with_conditions(record.height)
278 .await
279 {
280 Ok(r) => return Ok(r),
281 Err(e) => log::debug!("peer block_spends_with_conditions failed: {e}"),
282 }
283 }
284 if self.coinset_fallback_enabled {
285 self.coinset
286 .get_block_spends_with_conditions(header_hash)
287 .await
288 } else {
289 Err(ChiaQueryError::UnsupportedWithoutCoinset(
290 "get_block_spends_with_conditions".into(),
291 ))
292 }
293 }
294
295 pub async fn get_blocks(
296 &self,
297 start: u32,
298 end: u32,
299 exclude_header_hash: bool,
300 exclude_reorged: bool,
301 ) -> Result<Vec<FullBlock>, ChiaQueryError> {
302 self.peer_then_coinset(
303 self.peer.try_get_blocks_range(start, end),
304 self.peer.try_get_blocks_range(start, end),
305 self.coinset
306 .get_blocks(start, end, exclude_header_hash, exclude_reorged),
307 )
308 .await
309 }
310
311 pub async fn get_unfinished_block_headers(
312 &self,
313 ) -> Result<Vec<UnfinishedBlockHeader>, ChiaQueryError> {
314 self.require_coinset("get_unfinished_block_headers")?;
315 self.coinset.get_unfinished_block_headers().await
316 }
317}
318
319impl QueryRouter {
324 pub async fn get_coin_record_by_name(&self, name: &str) -> Result<CoinRecord, ChiaQueryError> {
325 self.peer_then_coinset(
326 self.peer.try_get_coin_record_by_name(name),
327 self.peer.try_get_coin_record_by_name(name),
328 self.coinset.get_coin_record_by_name(name),
329 )
330 .await
331 }
332
333 pub async fn get_coin_record_by_name_opt(
338 &self,
339 name: &str,
340 ) -> Result<Option<CoinRecord>, ChiaQueryError> {
341 self.peer_then_coinset_opt(
342 self.peer.try_get_coin_record_by_name_opt(name),
343 self.peer.try_get_coin_record_by_name_opt(name),
344 self.coinset.get_coin_record_by_name_opt(name),
345 )
346 .await
347 }
348
349 pub async fn get_coin_spend_opt(
352 &self,
353 coin_id: &str,
354 ) -> Result<Option<CoinSpend>, ChiaQueryError> {
355 self.peer_then_coinset_opt(
356 self.peer.try_get_coin_spend_opt(coin_id),
357 self.peer.try_get_coin_spend_opt(coin_id),
358 self.coinset.get_puzzle_and_solution_opt(coin_id, None),
359 )
360 .await
361 }
362
363 pub async fn peak_height_opt(&self) -> Result<Option<u32>, ChiaQueryError> {
365 let state = self.get_blockchain_state().await?;
366 Ok(state.peak.map(|p| p.height))
367 }
368
369 pub async fn block_timestamp_opt(&self, height: u32) -> Result<Option<u64>, ChiaQueryError> {
372 let record = self.get_block_record_by_height_opt(height).await?;
373 Ok(record.and_then(|r| r.timestamp))
374 }
375
376 async fn get_block_record_by_height_opt(
378 &self,
379 height: u32,
380 ) -> Result<Option<BlockRecord>, ChiaQueryError> {
381 if let Ok(record) = self.peer.try_get_block_record_by_height(height).await {
384 return Ok(Some(record));
385 }
386 match self.peer.try_get_block_record_by_height(height).await {
387 Ok(record) => Ok(Some(record)),
388 Err(peer_err) => {
389 if self.coinset_fallback_enabled {
390 self.coinset
391 .get_block_record_by_height_opt(height)
392 .await
393 .map_err(|ce| ChiaQueryError::AllSourcesFailed {
394 peer_error: Box::new(peer_err),
395 coinset_error: Some(Box::new(ce)),
396 })
397 } else {
398 Err(peer_err)
399 }
400 }
401 }
402 }
403
404 pub async fn get_coin_records_by_hint(
405 &self,
406 hint: &str,
407 start_height: Option<u32>,
408 end_height: Option<u32>,
409 include_spent_coins: bool,
410 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
411 self.peer_then_coinset(
412 self.peer.try_get_coin_records_by_hint(
413 hint,
414 start_height,
415 end_height,
416 include_spent_coins,
417 ),
418 self.peer.try_get_coin_records_by_hint(
419 hint,
420 start_height,
421 end_height,
422 include_spent_coins,
423 ),
424 self.coinset.get_coin_records_by_hint(
425 hint,
426 start_height,
427 end_height,
428 include_spent_coins,
429 ),
430 )
431 .await
432 }
433
434 pub async fn get_coin_records_by_hints(
435 &self,
436 hints: &[String],
437 start_height: Option<u32>,
438 end_height: Option<u32>,
439 include_spent_coins: bool,
440 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
441 self.peer_then_coinset(
442 self.peer.try_get_coin_records_by_hints(
443 hints,
444 start_height,
445 end_height,
446 include_spent_coins,
447 ),
448 self.peer.try_get_coin_records_by_hints(
449 hints,
450 start_height,
451 end_height,
452 include_spent_coins,
453 ),
454 self.coinset.get_coin_records_by_hints(
455 hints,
456 start_height,
457 end_height,
458 include_spent_coins,
459 ),
460 )
461 .await
462 }
463
464 pub async fn get_coin_records_by_names(
465 &self,
466 names: &[String],
467 start_height: Option<u32>,
468 end_height: Option<u32>,
469 include_spent_coins: bool,
470 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
471 self.peer_then_coinset(
472 self.peer.try_get_coin_records_by_names(names),
473 self.peer.try_get_coin_records_by_names(names),
474 self.coinset.get_coin_records_by_names(
475 names,
476 start_height,
477 end_height,
478 include_spent_coins,
479 ),
480 )
481 .await
482 }
483
484 pub async fn get_coin_records_by_parent_ids(
488 &self,
489 parent_ids: &[String],
490 start_height: Option<u32>,
491 end_height: Option<u32>,
492 include_spent_coins: bool,
493 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
494 let peer_attempt = async {
496 let mut all_records = Vec::new();
497 for parent_id in parent_ids {
498 let children = self.peer.try_get_children(parent_id).await?;
499 all_records.extend(children);
500 }
501 all_records.retain(|r| {
503 let height_ok = match (start_height, end_height) {
504 (Some(s), Some(e)) => {
505 r.confirmed_block_index >= s && r.confirmed_block_index <= e
506 }
507 (Some(s), None) => r.confirmed_block_index >= s,
508 (None, Some(e)) => r.confirmed_block_index <= e,
509 (None, None) => true,
510 };
511 let spent_ok = include_spent_coins || !r.spent;
512 height_ok && spent_ok
513 });
514 Ok(all_records)
515 };
516
517 match peer_attempt.await {
518 Ok(r) => Ok(r),
519 Err(peer_err) => {
520 if self.coinset_fallback_enabled {
521 self.coinset
522 .get_coin_records_by_parent_ids(
523 parent_ids,
524 start_height,
525 end_height,
526 include_spent_coins,
527 )
528 .await
529 .map_err(|ce| ChiaQueryError::AllSourcesFailed {
530 peer_error: Box::new(peer_err),
531 coinset_error: Some(Box::new(ce)),
532 })
533 } else {
534 Err(peer_err)
535 }
536 }
537 }
538 }
539
540 pub async fn get_coin_records_by_puzzle_hash(
541 &self,
542 puzzle_hash: &str,
543 start_height: Option<u32>,
544 end_height: Option<u32>,
545 include_spent_coins: bool,
546 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
547 self.peer_then_coinset(
548 self.peer.try_get_coin_records_by_puzzle_hash(
549 puzzle_hash,
550 start_height,
551 end_height,
552 include_spent_coins,
553 ),
554 self.peer.try_get_coin_records_by_puzzle_hash(
555 puzzle_hash,
556 start_height,
557 end_height,
558 include_spent_coins,
559 ),
560 self.coinset.get_coin_records_by_puzzle_hash(
561 puzzle_hash,
562 start_height,
563 end_height,
564 include_spent_coins,
565 ),
566 )
567 .await
568 }
569
570 pub async fn get_coin_records_by_puzzle_hashes(
571 &self,
572 puzzle_hashes: &[String],
573 start_height: Option<u32>,
574 end_height: Option<u32>,
575 include_spent_coins: bool,
576 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
577 self.peer_then_coinset(
578 self.peer.try_get_coin_records_by_puzzle_hashes(
579 puzzle_hashes,
580 start_height,
581 end_height,
582 include_spent_coins,
583 ),
584 self.peer.try_get_coin_records_by_puzzle_hashes(
585 puzzle_hashes,
586 start_height,
587 end_height,
588 include_spent_coins,
589 ),
590 self.coinset.get_coin_records_by_puzzle_hashes(
591 puzzle_hashes,
592 start_height,
593 end_height,
594 include_spent_coins,
595 ),
596 )
597 .await
598 }
599
600 pub async fn get_memos_by_coin_name(&self, name: &str) -> Result<Value, ChiaQueryError> {
602 self.require_coinset("get_memos_by_coin_name")?;
603 self.coinset.get_memos_by_coin_name(name).await
604 }
605
606 pub async fn get_puzzle_and_solution(
607 &self,
608 coin_id: &str,
609 height: Option<u32>,
610 ) -> Result<CoinSpend, ChiaQueryError> {
611 if let Some(h) = height {
612 self.peer_then_coinset(
613 self.peer.try_get_puzzle_and_solution(coin_id, h),
614 self.peer.try_get_puzzle_and_solution(coin_id, h),
615 self.coinset.get_puzzle_and_solution(coin_id, height),
616 )
617 .await
618 } else {
619 self.peer_then_coinset(
621 self.peer.try_get_puzzle_and_solution_auto(coin_id),
622 self.peer.try_get_puzzle_and_solution_auto(coin_id),
623 self.coinset.get_puzzle_and_solution(coin_id, None),
624 )
625 .await
626 }
627 }
628
629 pub async fn get_puzzle_and_solution_with_conditions(
632 &self,
633 coin_id: &str,
634 height: Option<u32>,
635 ) -> Result<CoinSpendWithConditions, ChiaQueryError> {
636 let spend = match self.get_puzzle_and_solution(coin_id, height).await {
638 Ok(s) => s,
639 Err(_) => {
640 if self.coinset_fallback_enabled {
641 return self
642 .coinset
643 .get_puzzle_and_solution_with_conditions(coin_id, height)
644 .await;
645 }
646 return Err(ChiaQueryError::PeerRejection(
647 "could not retrieve puzzle and solution".into(),
648 ));
649 }
650 };
651
652 let conditions = run_puzzle_conditions(&spend, self.peer.constants());
654 Ok(CoinSpendWithConditions {
655 coin_spend: spend,
656 conditions,
657 })
658 }
659
660 pub async fn push_tx(&self, bundle: &SpendBundle) -> Result<TxStatus, ChiaQueryError> {
661 self.peer_then_coinset(
662 self.peer.try_push_tx(bundle),
663 self.peer.try_push_tx(bundle),
664 self.coinset.push_tx(bundle),
665 )
666 .await
667 }
668}
669
670impl QueryRouter {
675 pub async fn get_fee_estimate(
676 &self,
677 spend_bundle: Option<&SpendBundle>,
678 target_times: Option<&[u64]>,
679 spend_count: Option<u64>,
680 ) -> Result<FeeEstimate, ChiaQueryError> {
681 let times = target_times.unwrap_or(&[60, 120, 300]);
682 self.peer_then_coinset(
683 self.peer.try_get_fee_estimate(times),
684 self.peer.try_get_fee_estimate(times),
685 self.coinset
686 .get_fee_estimate(spend_bundle, target_times, spend_count),
687 )
688 .await
689 }
690}
691
692impl QueryRouter {
697 pub async fn get_aggsig_additional_data(&self) -> Result<String, ChiaQueryError> {
700 Ok(self.peer.aggsig_additional_data())
701 }
702
703 pub async fn get_network_info(&self) -> Result<NetworkInfo, ChiaQueryError> {
706 Ok(self.peer.network_info())
707 }
708
709 pub async fn get_blockchain_state(&self) -> Result<BlockchainState, ChiaQueryError> {
712 if self.coinset_fallback_enabled {
714 if let Ok(state) = self.coinset.get_blockchain_state().await {
715 return Ok(state);
716 }
717 }
718 let peak = self.peer.peak_height();
720 if peak == 0 {
721 return Err(ChiaQueryError::PeerConnection(
722 "no peak observed from peers yet".into(),
723 ));
724 }
725 Ok(BlockchainState {
726 peak: Some(BlockRecord {
727 height: peak,
728 ..Default::default()
729 }),
730 sync: Some(SyncState {
731 synced: true,
732 sync_mode: false,
733 sync_progress_height: peak,
734 sync_tip_height: peak,
735 }),
736 ..Default::default()
737 })
738 }
739
740 pub async fn get_network_space(
741 &self,
742 newer_block_header_hash: &str,
743 older_block_header_hash: &str,
744 ) -> Result<u64, ChiaQueryError> {
745 self.require_coinset("get_network_space")?;
746 self.coinset
747 .get_network_space(newer_block_header_hash, older_block_header_hash)
748 .await
749 }
750}
751
752impl QueryRouter {
757 pub async fn get_all_mempool_items(
758 &self,
759 ) -> Result<HashMap<String, MempoolItem>, ChiaQueryError> {
760 self.require_coinset("get_all_mempool_items")?;
761 self.coinset.get_all_mempool_items().await
762 }
763
764 pub async fn get_all_mempool_tx_ids(&self) -> Result<Vec<String>, ChiaQueryError> {
765 self.require_coinset("get_all_mempool_tx_ids")?;
766 self.coinset.get_all_mempool_tx_ids().await
767 }
768
769 pub async fn get_mempool_item_by_tx_id(
770 &self,
771 tx_id: &str,
772 ) -> Result<MempoolItem, ChiaQueryError> {
773 self.require_coinset("get_mempool_item_by_tx_id")?;
774 self.coinset.get_mempool_item_by_tx_id(tx_id).await
775 }
776
777 pub async fn get_mempool_items_by_coin_name(
778 &self,
779 coin_name: &str,
780 include_spent_coins: Option<bool>,
781 ) -> Result<Vec<MempoolItem>, ChiaQueryError> {
782 self.require_coinset("get_mempool_items_by_coin_name")?;
783 self.coinset
784 .get_mempool_items_by_coin_name(coin_name, include_spent_coins)
785 .await
786 }
787}