nash_protocol/protocol/traits.rs
1//! These traits describe the high level behavior of the Nash protocol. Clients can use them
2//! to provide a generic implementation across requests
3use super::subscriptions::SubscriptionResponse;
4use super::{ProtocolHook, ResponseOrError, State};
5use crate::errors::ProtocolError;
6use crate::errors::Result;
7use async_trait::async_trait;
8use tokio::sync::RwLock;
9use std::fmt::Debug;
10use std::sync::Arc;
11use crate::protocol::ErrorResponse;
12
13//****************************************//
14// Nash protocol trait //
15//****************************************//
16
17/// Trait that all Nash protocol elements implement. Enforces transformation to GraphQL as well
18/// as state changes on response processing.
19#[async_trait]
20pub trait NashProtocol: Debug + Send + Sync {
21 type Response: Debug + Send + Sync;
22 /// If you want to limit the amount of concurrency of a protocol return a Semaphore here
23 async fn acquire_permit(&self, _state: Arc<RwLock<State>>) -> Option<tokio::sync::OwnedSemaphorePermit> {
24 None
25 }
26 /// Convert the protocol request to GraphQL from communication with Nash server
27 // Note: state is declared as mutable
28 async fn graphql(&self, state: Arc<RwLock<State>>) -> Result<serde_json::Value>;
29 /// Convert JSON response to request to the protocol's associated type
30 async fn response_from_json(
31 &self,
32 response: serde_json::Value,
33 state: Arc<RwLock<State>>
34 ) -> Result<ResponseOrError<Self::Response>>;
35 /// Any state changes that result from execution of the protocol request
36 /// The default implementation does nothing to state
37 async fn process_response(
38 &self,
39 _response: &Self::Response,
40 _state: Arc<RwLock<State>>,
41 ) -> Result<()> {
42 Ok(())
43 }
44 /// Any errors that result from execution of the protocol request
45 /// The default implementation does nothing to state
46 async fn process_error(
47 &self,
48 _response: &ErrorResponse,
49 _state: Arc<RwLock<State>>,
50 ) -> Result<()> {
51 Ok(())
52 }
53 // Any dependencies for the pipeline (e.g., if it needs r values or asset nonces)
54 async fn run_before(&self, _state: Arc<RwLock<State>>) -> Result<Option<Vec<ProtocolHook>>> {
55 Ok(None)
56 }
57 // Any requests to run after the pipeline (e.g., update asset nonces after state signing)
58 async fn run_after(&self, _state: Arc<RwLock<State>>) -> Result<Option<Vec<ProtocolHook>>> {
59 Ok(None)
60 }
61}
62
63//****************************************//
64// Nash protocol pipeline trait //
65//****************************************//
66
67/// Trait to abstract over a series of linked protocol requests. For example we use this
68/// to abstract over repeated calls to sign_state until there are no more states to sign.
69#[async_trait]
70pub trait NashProtocolPipeline: Debug + Send + Sync {
71 /// State managed by pipeline and used to hold intermediate results and allow
72 /// the implementer to decide whether the pipeline is over
73 type PipelineState: Send + Sync;
74 /// Wrapper type for all actions this pipeline can take
75 type ActionType: NashProtocol;
76 /// If you want to limit the amount of concurrency of a pipeline return a Semaphore here
77 async fn acquire_permit(&self, _state: Arc<RwLock<State>>) -> Option<tokio::sync::OwnedSemaphorePermit> {
78 None
79 }
80 /// Create initial state for the pipeline
81 async fn init_state(&self, state: Arc<RwLock<State>>) -> Self::PipelineState;
82 /// Give next action to take or return `None` if pipeline is finished. `&State` needs
83 /// to be mutable as client may modify itself when producing the next step (e.g., removing
84 /// and r value generate a signature)
85 async fn next_step(
86 &self,
87 pipeline_state: &Self::PipelineState,
88 client_state: Arc<RwLock<State>>,
89 ) -> Result<Option<Self::ActionType>>;
90 /// Process the results of a pipeline step
91 async fn process_step(
92 &self,
93 result: <<Self as NashProtocolPipeline>::ActionType as NashProtocol>::Response,
94 pipeline_state: &mut Self::PipelineState,
95 );
96 /// Get results from pipeline or `None` if the pipeline is not finished
97 fn output(
98 &self,
99 pipeline_state: Self::PipelineState,
100 ) -> Result<ResponseOrError<<Self::ActionType as NashProtocol>::Response>>;
101 // Any dependencies for the pipeline (e.g., if it needs r values or asset nonces)
102 async fn run_before(&self, _state: Arc<RwLock<State>>) -> Result<Option<Vec<ProtocolHook>>> {
103 Ok(None)
104 }
105 // Any requests to run after the pipeline (e.g., update asset nonces after state signing)
106 async fn run_after(&self, _state: Arc<RwLock<State>>) -> Result<Option<Vec<ProtocolHook>>> {
107 Ok(None)
108 }
109}
110
111/// A pipeline is a superset of `NashProtocol`, so something of type NashProtocol
112/// can by itself be considered a valid pipeline. This is convenient if we just want to
113/// have a single client interface that can take pipelines or protocol requests. We can
114/// do this once generically and it will apply to all implementations
115#[async_trait]
116impl<T> NashProtocolPipeline for T
117where
118 T: NashProtocol + Clone + Sync + Send,
119{
120 type PipelineState = Option<ResponseOrError<T::Response>>;
121 type ActionType = T;
122 // Here we just delegate this to underlying protocol request
123 async fn acquire_permit(&self, state: Arc<RwLock<State>>) -> Option<tokio::sync::OwnedSemaphorePermit> {
124 self.acquire_permit(state).await
125 }
126 // This begins as `None` but will be set to a wrapped T::Response
127 async fn init_state(&self, _state: Arc<RwLock<State>>) -> Self::PipelineState {
128 None
129 }
130 // This will only return a next step once assuming state is set by client in `process_step`
131 async fn next_step(
132 &self,
133 pipeline_state: &Self::PipelineState,
134 _client_state: Arc<RwLock<State>>,
135 ) -> Result<Option<Self::ActionType>> {
136 // If we have a response already, things are done
137 if let Some(_) = pipeline_state {
138 Ok(None)
139 }
140 // Else this request itself is the first (and only) item in the pipeline
141 else {
142 Ok(Some(self.clone()))
143 }
144 }
145 // Just grab the request and set state with some wrapping
146 async fn process_step(
147 &self,
148 result: <<Self as NashProtocolPipeline>::ActionType as NashProtocol>::Response,
149 state: &mut Self::PipelineState,
150 ) {
151 *state = Some(ResponseOrError::from_data(result));
152 }
153 // Just return state
154 fn output(&self, state: Self::PipelineState) -> Result<ResponseOrError<T::Response>> {
155 if let Some(state) = state {
156 Ok(state)
157 } else {
158 Err(ProtocolError(
159 "Protocol request not run, cannot retrieve output",
160 ))
161 }
162 }
163 // Any other requests or pipelines to run before this one.
164 // Here we just delegate this to underlying protocol request
165 async fn run_before(&self, state: Arc<RwLock<State>>) -> Result<Option<Vec<ProtocolHook>>> {
166 self.run_before(state).await
167 }
168 // Any other requests or pipelines to run after this one.
169 // Here we just delegate this to underlying protocol request
170 async fn run_after(&self, state: Arc<RwLock<State>>) -> Result<Option<Vec<ProtocolHook>>> {
171 self.run_after(state).await
172 }
173}
174
175/// Trait that defines subscriptions over the Nash protocol. The main difference is that
176/// the transformation of response data to `SubscriptionResponse` must occur on every
177/// incoming subscription event. Similarly, the subscription is able to modify client state
178/// on every incoming event. This last property is important for a subscription that updates
179/// asset nonces once that is available on the backend.
180#[async_trait]
181pub trait NashProtocolSubscription: Clone {
182 type SubscriptionResponse: Send + Sync;
183 /// Convert the protocol request to GraphQL from communication with Nash server
184 async fn graphql(&self, state: Arc<RwLock<State>>) -> Result<serde_json::Value>;
185 /// Convert JSON response from incoming subscription data into protocol's associated type
186 async fn subscription_response_from_json(
187 &self,
188 response: serde_json::Value,
189 state: Arc<RwLock<State>>
190 ) -> Result<ResponseOrError<Self::SubscriptionResponse>>;
191 /// Update state based on data from incoming subscription response
192 async fn process_subscription_response(
193 &self,
194 _response: &Self::SubscriptionResponse,
195 _state: Arc<RwLock<State>>,
196 ) -> Result<()> {
197 Ok(())
198 }
199 async fn wrap_response_as_any_subscription(
200 &self,
201 response: serde_json::Value,
202 state: Arc<RwLock<State>>
203 ) -> Result<ResponseOrError<SubscriptionResponse>>;
204}
205
206/// Similar to TryFrom, but threads additional State in as context
207/// that is necessary to perform the conversion
208#[async_trait]
209pub trait TryFromState<T>: Sized {
210 async fn from(source: T, state: Arc<RwLock<State>>) -> Result<Self>;
211}