1use std::{collections::HashMap, num::NonZeroUsize, sync::Arc};
2
3use async_trait::async_trait;
4use committable::Commitment;
5use hotshot_types::{
6 data::{EpochNumber, Leaf2, ViewNumber},
7 message::Proposal as SignedProposal,
8 traits::{
9 leaf_fetcher_network::LeafFetcherNetwork, node_implementation::NodeType,
10 signature_key::SignatureKey,
11 },
12 utils::StateAndDelta,
13};
14use tokio::sync::{mpsc, oneshot};
15
16use crate::{
17 block::SubmitError, coordinator::error::CoordinatorError, message::Proposal, state::UpdateLeaf,
18};
19
20#[derive(Clone)]
21pub struct ClientApi<T: NodeType> {
22 tx: mpsc::Sender<ClientRequest<T>>,
23}
24
25impl<T: NodeType> ClientApi<T> {
26 pub async fn current_view(&self) -> Result<ViewNumber, QueryError> {
27 let (tx, rx) = oneshot::channel();
28 self.call(ClientRequest::CurrentView(tx), rx).await
29 }
30
31 pub async fn current_epoch(&self) -> Result<EpochNumber, QueryError> {
32 let (tx, rx) = oneshot::channel();
33 self.call(ClientRequest::CurrentEpoch(tx), rx).await
34 }
35
36 pub async fn decided_leaf(&self) -> Result<Leaf2<T>, QueryError> {
37 let (tx, rx) = oneshot::channel();
38 self.call(ClientRequest::DecidedLeaf(tx), rx).await
39 }
40
41 pub async fn decided_state(&self) -> Result<Option<Arc<T::ValidatedState>>, QueryError> {
42 let (tx, rx) = oneshot::channel();
43 self.call(ClientRequest::DecidedState(tx), rx).await
44 }
45
46 pub async fn undecided_leaves(&self) -> Result<Vec<Leaf2<T>>, QueryError> {
47 let (tx, rx) = oneshot::channel();
48 self.call(ClientRequest::UndecidedLeaves(tx), rx).await
49 }
50
51 pub async fn state(
52 &self,
53 view: ViewNumber,
54 ) -> Result<Option<Arc<T::ValidatedState>>, QueryError> {
55 let (tx, rx) = oneshot::channel();
56 self.call(ClientRequest::GetState { view, respond: tx }, rx)
57 .await
58 }
59
60 pub async fn state_and_delta(&self, view: ViewNumber) -> Result<StateAndDelta<T>, QueryError> {
61 let (tx, rx) = oneshot::channel();
62 self.call(ClientRequest::GetStateAndDelta { view, respond: tx }, rx)
63 .await
64 }
65
66 pub async fn update_leaf(&self, update: UpdateLeaf<T>) -> Result<(), QueryError> {
67 let (tx, rx) = oneshot::channel();
68 self.call(
69 ClientRequest::UpdateLeaf {
70 update,
71 respond: tx,
72 },
73 rx,
74 )
75 .await
76 }
77
78 pub async fn submit_transaction(&self, tx: T::Transaction) -> Result<(), QueryError> {
79 let (respond, rx) = oneshot::channel();
80 self.call(ClientRequest::SubmitTransaction { tx, respond }, rx)
81 .await?
82 .map_err(QueryError::from)
83 }
84
85 pub async fn request_proposal(
86 &self,
87 view: ViewNumber,
88 leaf_commitment: Commitment<Leaf2<T>>,
89 ) -> Result<SignedProposal<T, Proposal<T>>, QueryError> {
90 let (respond, rx) = oneshot::channel();
91 self.call(
92 ClientRequest::RequestProposal {
93 view,
94 leaf_commitment,
95 respond,
96 },
97 rx,
98 )
99 .await?
100 }
101
102 pub async fn send_external_message(
103 &self,
104 payload: Vec<u8>,
105 recipient: T::SignatureKey,
106 ) -> Result<(), QueryError> {
107 let (respond, rx) = oneshot::channel();
108 self.call(
109 ClientRequest::SendExternalMessage {
110 payload,
111 recipient,
112 respond,
113 },
114 rx,
115 )
116 .await?
117 }
118
119 pub async fn proposal_participation(
121 &self,
122 epoch: Option<EpochNumber>,
123 ) -> Result<HashMap<T::SignatureKey, f64>, QueryError> {
124 let (respond, rx) = oneshot::channel();
125 self.call(ClientRequest::ProposalParticipation { epoch, respond }, rx)
126 .await
127 }
128
129 pub async fn vote_participation(
131 &self,
132 epoch: Option<EpochNumber>,
133 ) -> Result<HashMap<<T::SignatureKey as SignatureKey>::VerificationKeyType, f64>, QueryError>
134 {
135 let (respond, rx) = oneshot::channel();
136 self.call(ClientRequest::VoteParticipation { epoch, respond }, rx)
137 .await
138 }
139
140 async fn call<A>(
141 &self,
142 request: ClientRequest<T>,
143 rx: oneshot::Receiver<A>,
144 ) -> Result<A, QueryError> {
145 self.tx
146 .send(request)
147 .await
148 .map_err(|_| QueryError::ChannelClosed)?;
149 rx.await.map_err(|_| QueryError::ResponseDropped)
150 }
151}
152
153pub struct CoordinatorClient<T: NodeType> {
158 rx: mpsc::Receiver<ClientRequest<T>>,
159 api: ClientApi<T>,
160}
161
162impl<T: NodeType> Default for CoordinatorClient<T> {
163 fn default() -> Self {
164 Self::new(NonZeroUsize::new(256).expect("256 > 0"))
165 }
166}
167
168impl<T: NodeType> CoordinatorClient<T> {
169 pub fn new(capacity: NonZeroUsize) -> Self {
170 let (tx, rx) = mpsc::channel(capacity.get());
171 Self {
172 rx,
173 api: ClientApi { tx },
174 }
175 }
176
177 pub fn handle(&self) -> &ClientApi<T> {
178 &self.api
179 }
180
181 pub(crate) async fn next_request(&mut self) -> Option<ClientRequest<T>> {
182 self.rx.recv().await
183 }
184}
185
186#[allow(clippy::large_enum_variant)]
187pub(crate) enum ClientRequest<T: NodeType> {
188 CurrentView(oneshot::Sender<ViewNumber>),
189 CurrentEpoch(oneshot::Sender<EpochNumber>),
190 DecidedLeaf(oneshot::Sender<Leaf2<T>>),
191 DecidedState(oneshot::Sender<Option<Arc<T::ValidatedState>>>),
192 UndecidedLeaves(oneshot::Sender<Vec<Leaf2<T>>>),
193 GetState {
194 view: ViewNumber,
195 respond: oneshot::Sender<Option<Arc<T::ValidatedState>>>,
196 },
197 GetStateAndDelta {
198 view: ViewNumber,
199 respond: oneshot::Sender<StateAndDelta<T>>,
200 },
201 ProposalParticipation {
202 epoch: Option<EpochNumber>,
203 respond: oneshot::Sender<HashMap<T::SignatureKey, f64>>,
204 },
205 VoteParticipation {
206 epoch: Option<EpochNumber>,
207 respond:
208 oneshot::Sender<HashMap<<T::SignatureKey as SignatureKey>::VerificationKeyType, f64>>,
209 },
210 UpdateLeaf {
211 update: UpdateLeaf<T>,
212 respond: oneshot::Sender<()>,
213 },
214 SubmitTransaction {
215 tx: T::Transaction,
216 respond: oneshot::Sender<Result<(), SubmitError>>,
217 },
218 RequestProposal {
219 view: ViewNumber,
220 leaf_commitment: Commitment<Leaf2<T>>,
221 respond: oneshot::Sender<Result<SignedProposal<T, Proposal<T>>, QueryError>>,
222 },
223 SendExternalMessage {
224 payload: Vec<u8>,
225 recipient: T::SignatureKey,
226 respond: oneshot::Sender<Result<(), QueryError>>,
227 },
228}
229
230#[derive(Debug, thiserror::Error)]
231#[non_exhaustive]
232pub enum QueryError {
233 #[error("failed to send request. coordinator channel closed")]
234 ChannelClosed,
235
236 #[error("coordinator dropped the response")]
237 ResponseDropped,
238
239 #[error("coordinator error: {0}")]
240 Coordinator(#[from] CoordinatorError),
241
242 #[error("transaction rejected: {0}")]
243 Rejected(#[from] SubmitError),
244}
245
246pub struct ClientLeafFetcherNetwork<T: NodeType> {
253 client: ClientApi<T>,
254}
255
256impl<T: NodeType> ClientLeafFetcherNetwork<T> {
257 pub fn new(client: ClientApi<T>) -> Self {
258 Self { client }
259 }
260}
261
262#[async_trait]
263impl<T: NodeType> LeafFetcherNetwork<T> for ClientLeafFetcherNetwork<T> {
264 async fn send_leaf_request(
265 &self,
266 _: ViewNumber,
267 payload: Vec<u8>,
268 recipient: T::SignatureKey,
269 ) -> anyhow::Result<()> {
270 self.client
271 .send_external_message(payload, recipient)
272 .await?;
273 Ok(())
274 }
275
276 async fn send_leaf_response(
277 &self,
278 _: ViewNumber,
279 payload: Vec<u8>,
280 recipient: T::SignatureKey,
281 ) -> anyhow::Result<()> {
282 self.client
283 .send_external_message(payload, recipient)
284 .await?;
285 Ok(())
286 }
287}