cuprated/blockchain/
chain_service.rs

1use std::task::{Context, Poll};
2
3use futures::{future::BoxFuture, FutureExt, TryFutureExt};
4use tower::Service;
5
6use cuprate_blockchain::service::BlockchainReadHandle;
7use cuprate_fast_sync::validate_entries;
8use cuprate_p2p::block_downloader::{ChainSvcRequest, ChainSvcResponse};
9use cuprate_p2p_core::NetworkZone;
10use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse};
11
12/// That service that allows retrieving the chain state to give to the P2P crates, so we can figure out
13/// what blocks we need.
14///
15/// This has a more minimal interface than [`BlockchainReadRequest`] to make using the p2p crates easier.
16#[derive(Clone)]
17pub struct ChainService(pub BlockchainReadHandle);
18
19impl<N: NetworkZone> Service<ChainSvcRequest<N>> for ChainService {
20    type Response = ChainSvcResponse<N>;
21    type Error = tower::BoxError;
22    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
23
24    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
25        self.0.poll_ready(cx).map_err(Into::into)
26    }
27
28    fn call(&mut self, req: ChainSvcRequest<N>) -> Self::Future {
29        #[expect(
30            clippy::wildcard_enum_match_arm,
31            reason = "other requests should be unreachable"
32        )]
33        let map_res = |res: BlockchainResponse| match res {
34            BlockchainResponse::CompactChainHistory {
35                block_ids,
36                cumulative_difficulty,
37            } => ChainSvcResponse::CompactHistory {
38                block_ids,
39                cumulative_difficulty,
40            },
41            BlockchainResponse::FindFirstUnknown(res) => ChainSvcResponse::FindFirstUnknown(res),
42            _ => unreachable!(),
43        };
44
45        match req {
46            ChainSvcRequest::CompactHistory => self
47                .0
48                .call(BlockchainReadRequest::CompactChainHistory)
49                .map_ok(map_res)
50                .map_err(Into::into)
51                .boxed(),
52            ChainSvcRequest::FindFirstUnknown(req) => self
53                .0
54                .call(BlockchainReadRequest::FindFirstUnknown(req))
55                .map_ok(map_res)
56                .map_err(Into::into)
57                .boxed(),
58            ChainSvcRequest::CumulativeDifficulty => self
59                .0
60                .call(BlockchainReadRequest::CompactChainHistory)
61                .map_ok(|res| {
62                    // TODO create a custom request instead of hijacking this one.
63                    // TODO: use the context cache.
64                    let BlockchainResponse::CompactChainHistory {
65                        cumulative_difficulty,
66                        ..
67                    } = res
68                    else {
69                        unreachable!()
70                    };
71
72                    ChainSvcResponse::CumulativeDifficulty(cumulative_difficulty)
73                })
74                .map_err(Into::into)
75                .boxed(),
76            ChainSvcRequest::ValidateEntries(entries, start_height) => {
77                let mut blockchain_read_handle = self.0.clone();
78
79                async move {
80                    let (valid, unknown) =
81                        validate_entries(entries, start_height, &mut blockchain_read_handle)
82                            .await?;
83
84                    Ok(ChainSvcResponse::ValidateEntries { valid, unknown })
85                }
86                .boxed()
87            }
88        }
89    }
90}