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}