delta_domain_sdk/client.rs
1//! # Client
2//!
3//! [`DomainClient`] to perform actions on a running domain.
4
5use crate::{
6 base_layer::{
7 BaseLayer,
8 BaseLayerPortError,
9 },
10 domain_agreement,
11};
12use base_sdk::{
13 core::{
14 Planck,
15 SdlId,
16 Shard,
17 },
18 sdl::VerificationKey,
19};
20use domain_runtime::endpoint::Update;
21use snafu::{
22 ResultExt,
23 Snafu,
24};
25use std::num::NonZero;
26use tokio::sync::broadcast;
27use verifiable::types::VerifiableWithDiffs;
28
29/// Errors occurring in the [DomainClient]
30#[derive(Debug, Snafu)]
31pub enum Error {
32 /// Error from the runtime task endpoint
33 #[snafu(display("{source}"))]
34 Endpoint {
35 /// The underlying endpoint error
36 source: domain_runtime::endpoint::Error,
37 },
38
39 /// Base layer client initialization or communication failed
40 #[snafu(display("Base layer error: {source}"))]
41 BaseLayer {
42 /// The underlying error
43 source: BaseLayerPortError,
44 },
45
46 /// Failed to check for the domain agreement
47 #[snafu(display("Domain agreement check failed: {source}"))]
48 DomainAgreement {
49 /// The underlying error
50 source: domain_agreement::Error,
51 },
52}
53
54/// Client to perform actions on a running domain.
55///
56/// The client is cloneable and can be shared across tasks. It provides methods
57/// to apply verifiable messages, submit state diffs to the base layer, and
58/// generate and submit proofs.
59///
60/// Without [running](crate::runner::Runner::run) the domain first, actions
61/// performed by the client cannot be executed.
62#[derive(Debug, Clone)]
63pub struct DomainClient {
64 // data
65 shard: NonZero<Shard>,
66 local_laws_vkey: Option<VerificationKey>,
67
68 // clients, endpoints
69 endpoint: domain_runtime::Endpoint,
70 base_layer: BaseLayer,
71}
72
73impl DomainClient {
74 pub(crate) const fn new(
75 shard: NonZero<Shard>,
76 local_laws_vkey: Option<VerificationKey>,
77 endpoint: domain_runtime::Endpoint,
78 base_layer: BaseLayer,
79 ) -> Self {
80 Self {
81 shard,
82 local_laws_vkey,
83 endpoint,
84 base_layer,
85 }
86 }
87
88 /// Apply state diffs to the vaults
89 ///
90 /// Changes to the vaults are visible immediately locally. The resulting state
91 /// diff is buffered until a call to [Self::submit].
92 ///
93 /// # Parameters
94 ///
95 /// * `messages` - The verifiable messages with diffs to be applied
96 ///
97 /// # Returns
98 ///
99 /// Ok(()) if application succeeded, or an Error if it failed
100 pub async fn apply(&self, messages: Vec<VerifiableWithDiffs>) -> Result<(), Error> {
101 tracing::debug!("Applying {} verifiable(s)", messages.len());
102 self.endpoint
103 .handle_apply(messages)
104 .await
105 .context(EndpointSnafu)
106 }
107
108 /// Submit all diffs that were applied since the last call to the base
109 /// layer and return the hash of the resulting SDL.
110 ///
111 /// # Returns
112 ///
113 /// * `Ok(Some(sdl_id))` - If diffs were successfully submitted, returns the SDL id
114 /// * `Ok(None)` - If there were no pending diffs to submit
115 /// * `Err(error)` - If the submission failed
116 pub async fn submit(&self) -> Result<Option<SdlId>, Error> {
117 let sdl_id = self.endpoint.handle_submit().await.context(EndpointSnafu)?;
118 Ok(sdl_id)
119 }
120
121 /// Prove the SDL specified by the hash.
122 ///
123 /// Generates a proof for the SDL with the given hash and only returns once
124 /// the proof is finished (or fails).
125 ///
126 /// Use [Self::prove_with_local_laws_input] if you need to provide
127 /// additional inputs for the local laws.
128 ///
129 /// # Parameters
130 ///
131 /// * `sdl_id` - The id of the SDL to generate a proof for
132 ///
133 /// # Returns
134 /// Ok(()) if proving succeeded, Error otherwise
135 pub async fn prove(&self, sdl_id: SdlId) -> Result<(), Error> {
136 self.endpoint
137 .handle_prove(sdl_id, None)
138 .await
139 .context(EndpointSnafu)
140 }
141
142 /// Prove the SDL specified by the hash with additional inputs.
143 ///
144 /// Generates a proof for the SDL with the given hash and only returns once
145 /// the proof is finished (or fails).
146 ///
147 /// # Parameters
148 ///
149 /// * `sdl_id` - The id of the SDL to generate a proof for
150 /// * `local_laws_input` - Additional inputs to the configured local laws
151 ///
152 /// # Returns
153 /// Ok(()) if proving succeeded, Error otherwise
154 pub async fn prove_with_local_laws_input(
155 &self,
156 sdl_id: SdlId,
157 local_laws_input: Box<dyn erased_serde::Serialize + Send + Sync>,
158 ) -> Result<(), Error> {
159 self.endpoint
160 .handle_prove(sdl_id, Some(local_laws_input))
161 .await
162 .context(EndpointSnafu)
163 }
164
165 /// Submit the proof of the SDL specified by the hash to the base layer.
166 ///
167 /// # Parameters
168 ///
169 /// * `sdl_id` - The id of the SDL whose proof should be submitted
170 ///
171 /// # Returns
172 ///
173 /// Ok(()) if the proof was successfully submitted, or an Error if it failed
174 pub async fn submit_proof(&self, sdl_id: SdlId) -> Result<(), Error> {
175 self.endpoint
176 .handle_submit_proof(sdl_id)
177 .await
178 .context(EndpointSnafu)
179 }
180
181 /// Subscribes to a stream of events from the running domain.
182 pub fn updates(&self) -> broadcast::Receiver<Update> {
183 self.endpoint.stream_updates()
184 }
185
186 /// Shorthand to apply verifiables, submit an SDL, generate and submit a
187 /// proof
188 ///
189 /// This method combines the following:
190 /// 1. [apply](Self::apply) to apply verifiables to the vaults
191 /// 2. [submit](Self::submit) to batch the changes in an SDL and submit it
192 /// to the base layer
193 /// 3. [proving](Self::prove) to start proving the SDL
194 /// 4. [submit_proof](Self::submit_proof) to submit the proof to the base
195 /// layer
196 ///
197 /// This method does not allow setting additional inputs for the local laws.
198 ///
199 /// # Parameters
200 ///
201 /// * `messages` - The verifiable messages to be applied
202 ///
203 /// # Returns
204 ///
205 /// * `Ok(Some(sdl_id))` - Success, an SDL with this id was created & proven
206 /// * `Ok(None)` - Success but there were no state changes to submit
207 /// * `Err(error)` - Any step in the process failed.
208 pub async fn apply_prove_submit(
209 &self,
210 messages: Vec<VerifiableWithDiffs>,
211 ) -> Result<Option<SdlId>, Error> {
212 tracing::debug!(
213 "Starting apply_prove_submit with {} verifiable(s)",
214 messages.len()
215 );
216 self.apply(messages).await?;
217
218 let Some(new_sdl_id) = self.submit().await? else {
219 tracing::debug!("apply_prove_submit: no diffs to submit");
220 return Ok(None);
221 };
222
223 self.prove(new_sdl_id).await?;
224
225 self.submit_proof(new_sdl_id).await?;
226 tracing::debug!("apply_prove_submit completed for SDL {new_sdl_id}");
227 Ok(Some(new_sdl_id))
228 }
229
230 /// Submit a Domain Agreement for this domain.
231 ///
232 /// Having an active domain agreement is a prerequisite to submit transactions to the
233 /// base layer. You can check whether a domain agreement is already active with
234 /// [Self::check_valid_domain_agreement].
235 ///
236 /// Note that after submitting the domain agreement, it still has to be applied on the
237 /// base layer, before the domain is allowed to send transactions.
238 ///
239 /// # Parameters
240 /// - `new_shard_fee` - amount of native token that will be charged to the
241 /// domain operator keys's base layer vault for creation of the new shard
242 ///
243 /// # Returns
244 /// - `Ok(())` - if the domain agreement was successfully submitted
245 /// - `Err(_)` - see [crate::domain_agreement::Error] for error interpretation
246 pub async fn submit_domain_agreement(&self, new_shard_fee: Planck) -> Result<(), Error> {
247 self.base_layer
248 .submit_domain_agreement(self.shard, new_shard_fee, self.local_laws_vkey)
249 .await
250 .context(BaseLayerSnafu)
251 }
252
253 /// Check that there is an active Domain Agreement for this domain.
254 ///
255 /// This method can typically be used as a safe-guard before calling
256 /// [Self::submit] or other methods that require an active domain agreement
257 /// for transactions to be accepted.
258 ///
259 /// # Returns
260 /// - `Ok(())` - if there is an active domain agreement for this domain
261 /// - `Err(_)` - see [crate::domain_agreement::Error] for interpretation
262 pub async fn check_valid_domain_agreement(&self) -> Result<(), domain_agreement::Error> {
263 self.base_layer
264 .check_valid_domain_agreement(self.shard)
265 .await
266 }
267
268 /// Ensure that the domain has a valid Domain Agreement.
269 ///
270 /// If no valid Domain Agreement is
271 /// [found](Self::check_valid_domain_agreement), a new one will be
272 /// [submitted](Self::submit_domain_agreement) and awaited up to `timeout`.
273 ///
274 /// # Parameters
275 /// - `new_shard_fee` - amount of native token that will be charged to the
276 /// domain operator keys's base layer vault for creation of the new shard
277 /// - `interval` - duration between polling for the the domain agreement
278 /// - `timeout` - maximal duration to await the new domain agreement
279 pub async fn ensure_domain_agreement(&self, new_shard_fee: Planck) -> Result<(), Error> {
280 if self.check_valid_domain_agreement().await.is_ok() {
281 tracing::info!("The domain already has a valid domain agreement.");
282 return Ok(());
283 }
284
285 tracing::info!("Submitting a new domain agreement...");
286 self.submit_domain_agreement(new_shard_fee).await?;
287 let mut updates = self.updates();
288
289 loop {
290 // Since `self.endpoint` holds the `broadcast::Sender` the recv can never error out
291 // so we can safely ignore the else branch here.
292 if let Ok(Update::NewEpoch(_)) = updates.recv().await {
293 tracing::info!("New epoch began, checking for domain agreement");
294 return self
295 .check_valid_domain_agreement()
296 .await
297 .context(DomainAgreementSnafu);
298 }
299 }
300 }
301}
302
303#[cfg(test)]
304mod test {
305 use super::*;
306 use crate::base_layer::mock;
307
308 impl DomainClient {
309 /// Get access to the underlying mock base layer, if configured.
310 ///
311 /// This is a test-only escape hatch for asserting on base-layer interactions
312 /// (e.g. submitted transaction nonces) without going through the public API.
313 pub fn mock_rpc(&self) -> mock::BaseLayer {
314 let BaseLayer::Mock(inner) = &self.base_layer else {
315 panic!("Called `mock_rpc` on a domain with a real RPC")
316 };
317 inner.clone()
318 }
319 }
320}