Skip to main content

freenet_stdlib/client_api/
client_events.rs

1use flatbuffers::WIPOffset;
2use std::borrow::Cow;
3use std::fmt::Display;
4use std::net::SocketAddr;
5
6use serde::{de::DeserializeOwned, Deserialize, Serialize};
7
8use crate::client_api::TryFromFbs;
9use crate::generated::client_request::{
10    root_as_client_request, ClientRequestType, ContractRequest as FbsContractRequest,
11    ContractRequestType, DelegateRequest as FbsDelegateRequest, DelegateRequestType,
12};
13
14use crate::generated::common::{
15    ApplicationMessage as FbsApplicationMessage, ApplicationMessageArgs, ContractCode,
16    ContractCodeArgs, ContractContainer as FbsContractContainer, ContractContainerArgs,
17    ContractInstanceId as FbsContractInstanceId, ContractInstanceIdArgs,
18    ContractKey as FbsContractKey, ContractKeyArgs, ContractType, DeltaUpdate, DeltaUpdateArgs,
19    RelatedDeltaUpdate, RelatedDeltaUpdateArgs, RelatedStateAndDeltaUpdate,
20    RelatedStateAndDeltaUpdateArgs, RelatedStateUpdate, RelatedStateUpdateArgs,
21    StateAndDeltaUpdate, StateAndDeltaUpdateArgs, StateUpdate, StateUpdateArgs,
22    UpdateData as FbsUpdateData, UpdateDataArgs, UpdateDataType, WasmContractV1,
23    WasmContractV1Args,
24};
25use crate::generated::host_response::{
26    finish_host_response_buffer, ClientResponse as FbsClientResponse, ClientResponseArgs,
27    ContextUpdated as FbsContextUpdated, ContextUpdatedArgs,
28    ContractResponse as FbsContractResponse, ContractResponseArgs, ContractResponseType,
29    DelegateKey as FbsDelegateKey, DelegateKeyArgs, DelegateResponse as FbsDelegateResponse,
30    DelegateResponseArgs, GetResponse as FbsGetResponse, GetResponseArgs,
31    HostResponse as FbsHostResponse, HostResponseArgs, HostResponseType, NotFound as FbsNotFound,
32    NotFoundArgs, Ok as FbsOk, OkArgs, OutboundDelegateMsg as FbsOutboundDelegateMsg,
33    OutboundDelegateMsgArgs, OutboundDelegateMsgType, PutResponse as FbsPutResponse,
34    PutResponseArgs, RequestUserInput as FbsRequestUserInput, RequestUserInputArgs,
35    UpdateNotification as FbsUpdateNotification, UpdateNotificationArgs,
36    UpdateResponse as FbsUpdateResponse, UpdateResponseArgs,
37};
38use crate::prelude::ContractContainer::Wasm;
39use crate::prelude::ContractWasmAPIVersion::V1;
40use crate::prelude::UpdateData::{
41    Delta, RelatedDelta, RelatedState, RelatedStateAndDelta, State, StateAndDelta,
42};
43use crate::{
44    delegate_interface::{DelegateKey, InboundDelegateMsg, OutboundDelegateMsg},
45    prelude::{
46        ContractInstanceId, ContractKey, DelegateContainer, Parameters, RelatedContracts,
47        SecretsId, StateSummary, UpdateData, WrappedState,
48    },
49    versioning::ContractContainer,
50};
51
52use super::WsApiError;
53
54#[derive(Debug, Serialize, Deserialize, Clone)]
55pub struct ClientError {
56    kind: Box<ErrorKind>,
57}
58
59impl ClientError {
60    pub fn into_fbs_bytes(self) -> Result<Vec<u8>, Box<ClientError>> {
61        use crate::generated::host_response::{Error, ErrorArgs};
62        let mut builder = flatbuffers::FlatBufferBuilder::new();
63        let msg_offset = builder.create_string(&self.to_string());
64        let err_offset = Error::create(
65            &mut builder,
66            &ErrorArgs {
67                msg: Some(msg_offset),
68            },
69        );
70        let host_response_offset = FbsHostResponse::create(
71            &mut builder,
72            &HostResponseArgs {
73                response_type: HostResponseType::Ok,
74                response: Some(err_offset.as_union_value()),
75            },
76        );
77        finish_host_response_buffer(&mut builder, host_response_offset);
78        Ok(builder.finished_data().to_vec())
79    }
80
81    pub fn kind(&self) -> &ErrorKind {
82        &self.kind
83    }
84}
85
86impl From<ErrorKind> for ClientError {
87    fn from(kind: ErrorKind) -> Self {
88        ClientError {
89            kind: Box::new(kind),
90        }
91    }
92}
93
94impl<T: Into<Cow<'static, str>>> From<T> for ClientError {
95    fn from(cause: T) -> Self {
96        ClientError {
97            kind: Box::new(ErrorKind::Unhandled {
98                cause: cause.into(),
99            }),
100        }
101    }
102}
103
104#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone)]
105#[non_exhaustive]
106pub enum ErrorKind {
107    #[error("comm channel between client/host closed")]
108    ChannelClosed,
109    #[error("error while deserializing: {cause}")]
110    DeserializationError { cause: Cow<'static, str> },
111    #[error("client disconnected")]
112    Disconnect,
113    #[error("failed while trying to unpack state for {0}")]
114    IncorrectState(ContractKey),
115    #[error("node not available")]
116    NodeUnavailable,
117    #[error("lost the connection with the protocol handling connections")]
118    TransportProtocolDisconnect,
119    #[error("unhandled error: {cause}")]
120    Unhandled { cause: Cow<'static, str> },
121    #[error("unknown client id: {0}")]
122    UnknownClient(usize),
123    #[error(transparent)]
124    RequestError(#[from] RequestError),
125    #[error("error while executing operation in the network: {cause}")]
126    OperationError { cause: Cow<'static, str> },
127    // TODO: identify requests by some id so we can inform clients which one failed exactly
128    #[error("operation timed out")]
129    FailedOperation,
130    #[error("peer should shutdown")]
131    Shutdown,
132}
133
134impl Display for ClientError {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        write!(f, "client error: {}", self.kind)
137    }
138}
139
140impl std::error::Error for ClientError {}
141
142#[derive(Debug, thiserror::Error, Serialize, Deserialize, Clone)]
143#[non_exhaustive]
144pub enum RequestError {
145    #[error(transparent)]
146    ContractError(#[from] ContractError),
147    #[error(transparent)]
148    DelegateError(#[from] DelegateError),
149    #[error("client disconnect")]
150    Disconnect,
151    #[error("operation timed out")]
152    Timeout,
153}
154
155/// Errors that may happen while interacting with delegates.
156#[derive(Debug, thiserror::Error, Serialize, Deserialize, Clone)]
157#[non_exhaustive]
158pub enum DelegateError {
159    #[error("error while registering delegate {0}")]
160    RegisterError(DelegateKey),
161    #[error("execution error, cause {0}")]
162    ExecutionError(Cow<'static, str>),
163    #[error("missing delegate {0}")]
164    Missing(DelegateKey),
165    #[error("missing secret `{secret}` for delegate {key}")]
166    MissingSecret { key: DelegateKey, secret: SecretsId },
167    #[error("forbidden access to secret: {0}")]
168    ForbiddenSecretAccess(SecretsId),
169}
170
171/// Errors that may happen while interacting with contracts.
172#[derive(Debug, thiserror::Error, Serialize, Deserialize, Clone)]
173#[non_exhaustive]
174pub enum ContractError {
175    #[error("failed to get contract {key}, reason: {cause}")]
176    Get {
177        key: ContractKey,
178        cause: Cow<'static, str>,
179    },
180    #[error("put error for contract {key}, reason: {cause}")]
181    Put {
182        key: ContractKey,
183        cause: Cow<'static, str>,
184    },
185    #[error("update error for contract {key}, reason: {cause}")]
186    Update {
187        key: ContractKey,
188        cause: Cow<'static, str>,
189    },
190    #[error("failed to subscribe for contract {key}, reason: {cause}")]
191    Subscribe {
192        key: ContractKey,
193        cause: Cow<'static, str>,
194    },
195    // todo: actually build a stack of the involved keys
196    #[error("dependency contract stack overflow : {key}")]
197    ContractStackOverflow {
198        key: crate::contract_interface::ContractInstanceId,
199    },
200    #[error("missing related contract: {key}")]
201    MissingRelated {
202        key: crate::contract_interface::ContractInstanceId,
203    },
204    #[error("missing contract: {key}")]
205    MissingContract {
206        key: crate::contract_interface::ContractInstanceId,
207    },
208}
209
210impl ContractError {
211    const EXECUTION_ERROR: &'static str = "execution error";
212    const INVALID_PUT: &'static str = "invalid put";
213
214    pub fn update_exec_error(key: ContractKey, additional_info: impl std::fmt::Display) -> Self {
215        Self::Update {
216            key,
217            cause: format!(
218                "{exec_err}: {additional_info}",
219                exec_err = Self::EXECUTION_ERROR
220            )
221            .into(),
222        }
223    }
224
225    pub fn invalid_put(key: ContractKey) -> Self {
226        Self::Put {
227            key,
228            cause: Self::INVALID_PUT.into(),
229        }
230    }
231
232    pub fn invalid_update(key: ContractKey) -> Self {
233        Self::Update {
234            key,
235            cause: Self::INVALID_PUT.into(),
236        }
237    }
238}
239
240/// A request from a client application to the host.
241#[derive(Serialize, Deserialize, Debug, Clone)]
242#[non_exhaustive]
243// #[cfg_attr(test, derive(arbitrary::Arbitrary))]
244pub enum ClientRequest<'a> {
245    DelegateOp(#[serde(borrow)] DelegateRequest<'a>),
246    ContractOp(#[serde(borrow)] ContractRequest<'a>),
247    Disconnect {
248        cause: Option<Cow<'static, str>>,
249    },
250    Authenticate {
251        token: String,
252    },
253    NodeQueries(NodeQuery),
254    /// Gracefully disconnect from the host.
255    Close,
256}
257
258#[derive(Serialize, Deserialize, Debug, Clone)]
259pub struct ConnectedPeers {}
260
261#[derive(Serialize, Deserialize, Debug, Clone)]
262pub struct NodeDiagnostics {
263    /// Optional contract key to filter diagnostics for specific contract
264    pub contract_key: Option<ContractKey>,
265}
266
267impl ClientRequest<'_> {
268    pub fn into_owned(self) -> ClientRequest<'static> {
269        match self {
270            ClientRequest::ContractOp(op) => {
271                let owned = match op {
272                    ContractRequest::Put {
273                        contract,
274                        state,
275                        related_contracts,
276                        subscribe,
277                        blocking_subscribe,
278                    } => {
279                        let related_contracts = related_contracts.into_owned();
280                        ContractRequest::Put {
281                            contract,
282                            state,
283                            related_contracts,
284                            subscribe,
285                            blocking_subscribe,
286                        }
287                    }
288                    ContractRequest::Update { key, data } => {
289                        let data = data.into_owned();
290                        ContractRequest::Update { key, data }
291                    }
292                    ContractRequest::Get {
293                        key,
294                        return_contract_code,
295                        subscribe,
296                        blocking_subscribe,
297                    } => ContractRequest::Get {
298                        key,
299                        return_contract_code,
300                        subscribe,
301                        blocking_subscribe,
302                    },
303                    ContractRequest::Subscribe { key, summary } => ContractRequest::Subscribe {
304                        key,
305                        summary: summary.map(StateSummary::into_owned),
306                    },
307                };
308                owned.into()
309            }
310            ClientRequest::DelegateOp(op) => {
311                let op = op.into_owned();
312                ClientRequest::DelegateOp(op)
313            }
314            ClientRequest::Disconnect { cause } => ClientRequest::Disconnect { cause },
315            ClientRequest::Authenticate { token } => ClientRequest::Authenticate { token },
316            ClientRequest::NodeQueries(query) => ClientRequest::NodeQueries(query),
317            ClientRequest::Close => ClientRequest::Close,
318        }
319    }
320
321    pub fn is_disconnect(&self) -> bool {
322        matches!(self, Self::Disconnect { .. })
323    }
324
325    pub fn try_decode_fbs(msg: &[u8]) -> Result<ClientRequest<'_>, WsApiError> {
326        let req = {
327            match root_as_client_request(msg) {
328                Ok(client_request) => match client_request.client_request_type() {
329                    ClientRequestType::ContractRequest => {
330                        let contract_request =
331                            client_request.client_request_as_contract_request().unwrap();
332                        ContractRequest::try_decode_fbs(&contract_request)?.into()
333                    }
334                    ClientRequestType::DelegateRequest => {
335                        let delegate_request =
336                            client_request.client_request_as_delegate_request().unwrap();
337                        DelegateRequest::try_decode_fbs(&delegate_request)?.into()
338                    }
339                    ClientRequestType::Disconnect => {
340                        let delegate_request =
341                            client_request.client_request_as_disconnect().unwrap();
342                        let cause = delegate_request
343                            .cause()
344                            .map(|cause_msg| cause_msg.to_string().into());
345                        ClientRequest::Disconnect { cause }
346                    }
347                    ClientRequestType::Authenticate => {
348                        let auth_req = client_request.client_request_as_authenticate().unwrap();
349                        let token = auth_req.token();
350                        ClientRequest::Authenticate {
351                            token: token.to_owned(),
352                        }
353                    }
354                    _ => unreachable!(),
355                },
356                Err(e) => {
357                    let cause = format!("{e}");
358                    return Err(WsApiError::deserialization(cause));
359                }
360            }
361        };
362
363        Ok(req)
364    }
365}
366
367#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
368#[non_exhaustive]
369pub enum ContractRequest<'a> {
370    /// Insert a new value in a contract corresponding with the provided key.
371    Put {
372        contract: ContractContainer,
373        /// Value to upsert in the contract.
374        state: WrappedState,
375        /// Related contracts.
376        #[serde(borrow)]
377        related_contracts: RelatedContracts<'a>,
378        /// If this flag is set then subscribe to updates for this contract.
379        subscribe: bool,
380        /// If true, the PUT response waits for the subscription to complete.
381        /// Only meaningful when `subscribe` is true.
382        #[serde(default)]
383        blocking_subscribe: bool,
384    },
385    /// Update an existing contract corresponding with the provided key.
386    Update {
387        key: ContractKey,
388        #[serde(borrow)]
389        data: UpdateData<'a>,
390    },
391    /// Fetch the current state from a contract corresponding to the provided key.
392    Get {
393        /// Instance ID of the contract (the hash of code + params).
394        /// Only the instance ID is needed since the client doesn't have the code hash yet.
395        key: ContractInstanceId,
396        /// If this flag is set then fetch also the contract itself.
397        return_contract_code: bool,
398        /// If this flag is set then subscribe to updates for this contract.
399        subscribe: bool,
400        /// If true, the GET response waits for the subscription to complete.
401        /// Only meaningful when `subscribe` is true.
402        #[serde(default)]
403        blocking_subscribe: bool,
404    },
405    /// Subscribe to the changes in a given contract. Implicitly starts a get operation
406    /// if the contract is not present yet.
407    Subscribe {
408        /// Instance ID of the contract.
409        key: ContractInstanceId,
410        summary: Option<StateSummary<'a>>,
411    },
412}
413
414impl ContractRequest<'_> {
415    pub fn into_owned(self) -> ContractRequest<'static> {
416        match self {
417            Self::Put {
418                contract,
419                state,
420                related_contracts,
421                subscribe,
422                blocking_subscribe,
423            } => ContractRequest::Put {
424                contract,
425                state,
426                related_contracts: related_contracts.into_owned(),
427                subscribe,
428                blocking_subscribe,
429            },
430            Self::Update { key, data } => ContractRequest::Update {
431                key,
432                data: data.into_owned(),
433            },
434            Self::Get {
435                key,
436                return_contract_code: fetch_contract,
437                subscribe,
438                blocking_subscribe,
439            } => ContractRequest::Get {
440                key,
441                return_contract_code: fetch_contract,
442                subscribe,
443                blocking_subscribe,
444            },
445            Self::Subscribe { key, summary } => ContractRequest::Subscribe {
446                key,
447                summary: summary.map(StateSummary::into_owned),
448            },
449        }
450    }
451}
452
453impl<'a> From<ContractRequest<'a>> for ClientRequest<'a> {
454    fn from(op: ContractRequest<'a>) -> Self {
455        ClientRequest::ContractOp(op)
456    }
457}
458
459/// Deserializes a `ContractRequest` from a Flatbuffers message.
460impl<'a> TryFromFbs<&FbsContractRequest<'a>> for ContractRequest<'a> {
461    fn try_decode_fbs(request: &FbsContractRequest<'a>) -> Result<Self, WsApiError> {
462        let req = {
463            match request.contract_request_type() {
464                ContractRequestType::Get => {
465                    let get = request.contract_request_as_get().unwrap();
466                    // Extract just the instance ID - GET only needs the instance ID,
467                    // not the full key (which may not be complete on the client side)
468                    let fbs_key = get.key();
469                    let key_bytes: [u8; 32] = fbs_key.instance().data().bytes().try_into().unwrap();
470                    let key = ContractInstanceId::new(key_bytes);
471                    let fetch_contract = get.fetch_contract();
472                    let subscribe = get.subscribe();
473                    let blocking_subscribe = get.blocking_subscribe();
474                    ContractRequest::Get {
475                        key,
476                        return_contract_code: fetch_contract,
477                        subscribe,
478                        blocking_subscribe,
479                    }
480                }
481                ContractRequestType::Put => {
482                    let put = request.contract_request_as_put().unwrap();
483                    let contract = ContractContainer::try_decode_fbs(&put.container())?;
484                    let state = WrappedState::new(put.wrapped_state().bytes().to_vec());
485                    let related_contracts =
486                        RelatedContracts::try_decode_fbs(&put.related_contracts())?.into_owned();
487                    let subscribe = put.subscribe();
488                    let blocking_subscribe = put.blocking_subscribe();
489                    ContractRequest::Put {
490                        contract,
491                        state,
492                        related_contracts,
493                        subscribe,
494                        blocking_subscribe,
495                    }
496                }
497                ContractRequestType::Update => {
498                    let update = request.contract_request_as_update().unwrap();
499                    let key = ContractKey::try_decode_fbs(&update.key())?;
500                    let data = UpdateData::try_decode_fbs(&update.data())?.into_owned();
501                    ContractRequest::Update { key, data }
502                }
503                ContractRequestType::Subscribe => {
504                    let subscribe = request.contract_request_as_subscribe().unwrap();
505                    // Extract just the instance ID for Subscribe
506                    let fbs_key = subscribe.key();
507                    let key_bytes: [u8; 32] = fbs_key.instance().data().bytes().try_into().unwrap();
508                    let key = ContractInstanceId::new(key_bytes);
509                    let summary = subscribe
510                        .summary()
511                        .map(|summary_data| StateSummary::from(summary_data.bytes()));
512                    ContractRequest::Subscribe { key, summary }
513                }
514                _ => unreachable!(),
515            }
516        };
517
518        Ok(req)
519    }
520}
521
522impl<'a> From<DelegateRequest<'a>> for ClientRequest<'a> {
523    fn from(op: DelegateRequest<'a>) -> Self {
524        ClientRequest::DelegateOp(op)
525    }
526}
527
528#[derive(Serialize, Deserialize, Debug, Clone)]
529#[non_exhaustive]
530pub enum DelegateRequest<'a> {
531    ApplicationMessages {
532        key: DelegateKey,
533        #[serde(deserialize_with = "Parameters::deser_params")]
534        params: Parameters<'a>,
535        #[serde(borrow)]
536        inbound: Vec<InboundDelegateMsg<'a>>,
537    },
538    RegisterDelegate {
539        delegate: DelegateContainer,
540        cipher: [u8; 32],
541        nonce: [u8; 24],
542    },
543    UnregisterDelegate(DelegateKey),
544}
545
546impl DelegateRequest<'_> {
547    pub const DEFAULT_CIPHER: [u8; 32] = [
548        0, 24, 22, 150, 112, 207, 24, 65, 182, 161, 169, 227, 66, 182, 237, 215, 206, 164, 58, 161,
549        64, 108, 157, 195, 0, 0, 0, 0, 0, 0, 0, 0,
550    ];
551
552    pub const DEFAULT_NONCE: [u8; 24] = [
553        57, 18, 79, 116, 63, 134, 93, 39, 208, 161, 156, 229, 222, 247, 111, 79, 210, 126, 127, 55,
554        224, 150, 139, 80,
555    ];
556
557    pub fn into_owned(self) -> DelegateRequest<'static> {
558        match self {
559            DelegateRequest::ApplicationMessages {
560                key,
561                inbound,
562                params,
563            } => DelegateRequest::ApplicationMessages {
564                key,
565                params: params.into_owned(),
566                inbound: inbound.into_iter().map(|e| e.into_owned()).collect(),
567            },
568            DelegateRequest::RegisterDelegate {
569                delegate,
570                cipher,
571                nonce,
572            } => DelegateRequest::RegisterDelegate {
573                delegate,
574                cipher,
575                nonce,
576            },
577            DelegateRequest::UnregisterDelegate(key) => DelegateRequest::UnregisterDelegate(key),
578        }
579    }
580
581    pub fn key(&self) -> &DelegateKey {
582        match self {
583            DelegateRequest::ApplicationMessages { key, .. } => key,
584            DelegateRequest::RegisterDelegate { delegate, .. } => delegate.key(),
585            DelegateRequest::UnregisterDelegate(key) => key,
586        }
587    }
588}
589
590impl Display for ClientRequest<'_> {
591    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
592        match self {
593            ClientRequest::ContractOp(op) => match op {
594                ContractRequest::Put {
595                    contract, state, ..
596                } => {
597                    write!(
598                        f,
599                        "ContractRequest::Put for contract `{contract}` with state {state}"
600                    )
601                }
602                ContractRequest::Update { key, .. } => write!(f, "update request for {key}"),
603                ContractRequest::Get {
604                    key,
605                    return_contract_code: contract,
606                    ..
607                } => {
608                    write!(
609                        f,
610                        "ContractRequest::Get for key `{key}` (fetch full contract: {contract})"
611                    )
612                }
613                ContractRequest::Subscribe { key, .. } => {
614                    write!(f, "ContractRequest::Subscribe for `{key}`")
615                }
616            },
617            ClientRequest::DelegateOp(op) => match op {
618                DelegateRequest::ApplicationMessages { key, inbound, .. } => {
619                    write!(
620                        f,
621                        "DelegateRequest::ApplicationMessages for `{key}` with {} messages",
622                        inbound.len()
623                    )
624                }
625                DelegateRequest::RegisterDelegate { delegate, .. } => {
626                    write!(
627                        f,
628                        "DelegateRequest::RegisterDelegate for delegate.key()=`{}`",
629                        delegate.key()
630                    )
631                }
632                DelegateRequest::UnregisterDelegate(key) => {
633                    write!(f, "DelegateRequest::UnregisterDelegate for key `{key}`")
634                }
635            },
636            ClientRequest::Disconnect { .. } => write!(f, "client disconnected"),
637            ClientRequest::Authenticate { .. } => write!(f, "authenticate"),
638            ClientRequest::NodeQueries(query) => write!(f, "node queries: {:?}", query),
639            ClientRequest::Close => write!(f, "close"),
640        }
641    }
642}
643
644/// Deserializes a `DelegateRequest` from a Flatbuffers message.
645impl<'a> TryFromFbs<&FbsDelegateRequest<'a>> for DelegateRequest<'a> {
646    fn try_decode_fbs(request: &FbsDelegateRequest<'a>) -> Result<Self, WsApiError> {
647        let req = {
648            match request.delegate_request_type() {
649                DelegateRequestType::ApplicationMessages => {
650                    let app_msg = request.delegate_request_as_application_messages().unwrap();
651                    let key = DelegateKey::try_decode_fbs(&app_msg.key())?;
652                    let params = Parameters::from(app_msg.params().bytes());
653                    let inbound = app_msg
654                        .inbound()
655                        .iter()
656                        .map(|msg| InboundDelegateMsg::try_decode_fbs(&msg))
657                        .collect::<Result<Vec<_>, _>>()?;
658                    DelegateRequest::ApplicationMessages {
659                        key,
660                        params,
661                        inbound,
662                    }
663                }
664                DelegateRequestType::RegisterDelegate => {
665                    let register = request.delegate_request_as_register_delegate().unwrap();
666                    let delegate = DelegateContainer::try_decode_fbs(&register.delegate())?;
667                    let cipher =
668                        <[u8; 32]>::try_from(register.cipher().bytes().to_vec().as_slice())
669                            .unwrap();
670                    let nonce =
671                        <[u8; 24]>::try_from(register.nonce().bytes().to_vec().as_slice()).unwrap();
672                    DelegateRequest::RegisterDelegate {
673                        delegate,
674                        cipher,
675                        nonce,
676                    }
677                }
678                DelegateRequestType::UnregisterDelegate => {
679                    let unregister = request.delegate_request_as_unregister_delegate().unwrap();
680                    let key = DelegateKey::try_decode_fbs(&unregister.key())?;
681                    DelegateRequest::UnregisterDelegate(key)
682                }
683                _ => unreachable!(),
684            }
685        };
686
687        Ok(req)
688    }
689}
690
691/// A response to a previous [`ClientRequest`]
692#[derive(Serialize, Deserialize, Debug, Clone)]
693#[non_exhaustive]
694pub enum HostResponse<T = WrappedState> {
695    ContractResponse(#[serde(bound(deserialize = "T: DeserializeOwned"))] ContractResponse<T>),
696    DelegateResponse {
697        key: DelegateKey,
698        values: Vec<OutboundDelegateMsg>,
699    },
700    QueryResponse(QueryResponse),
701    /// A requested action which doesn't require an answer was performed successfully.
702    Ok,
703}
704
705type Peer = String;
706
707#[derive(Serialize, Deserialize, Debug, Clone)]
708pub enum QueryResponse {
709    ConnectedPeers { peers: Vec<(Peer, SocketAddr)> },
710    NetworkDebug(NetworkDebugInfo),
711    NodeDiagnostics(NodeDiagnosticsResponse),
712    ProximityCache(ProximityCacheInfo),
713}
714
715#[derive(Serialize, Deserialize, Debug, Clone)]
716pub struct NetworkDebugInfo {
717    pub subscriptions: Vec<SubscriptionInfo>,
718    pub connected_peers: Vec<(String, SocketAddr)>,
719}
720
721#[derive(Serialize, Deserialize, Debug, Clone)]
722pub struct NodeDiagnosticsResponse {
723    /// Node information
724    pub node_info: Option<NodeInfo>,
725
726    /// Network connectivity information
727    pub network_info: Option<NetworkInfo>,
728
729    /// Contract subscription information
730    pub subscriptions: Vec<SubscriptionInfo>,
731
732    /// Contract states for specific contracts
733    pub contract_states: std::collections::HashMap<ContractKey, ContractState>,
734
735    /// System metrics
736    pub system_metrics: Option<SystemMetrics>,
737
738    /// Information about connected peers with detailed data
739    pub connected_peers_detailed: Vec<ConnectedPeerInfo>,
740}
741
742#[derive(Serialize, Deserialize, Debug, Clone)]
743pub struct NodeInfo {
744    pub peer_id: String,
745    pub is_gateway: bool,
746    pub location: Option<String>,
747    pub listening_address: Option<String>,
748    pub uptime_seconds: u64,
749}
750
751#[derive(Serialize, Deserialize, Debug, Clone)]
752pub struct NetworkInfo {
753    pub connected_peers: Vec<(String, String)>, // (peer_id, address)
754    pub active_connections: usize,
755}
756
757#[derive(Serialize, Deserialize, Debug, Clone)]
758pub struct ContractState {
759    /// Number of nodes subscribed to this contract
760    pub subscribers: u32,
761    /// Peer IDs of nodes that are subscribed to this contract
762    pub subscriber_peer_ids: Vec<String>,
763    /// Size of the contract state in bytes
764    #[serde(default)]
765    pub size_bytes: u64,
766}
767
768#[derive(Serialize, Deserialize, Debug, Clone)]
769pub struct SystemMetrics {
770    pub active_connections: u32,
771    pub seeding_contracts: u32,
772}
773
774#[derive(Serialize, Deserialize, Debug, Clone)]
775pub struct SubscriptionInfo {
776    pub contract_key: ContractInstanceId,
777    pub client_id: usize,
778}
779
780/// Basic information about a connected peer
781#[derive(Serialize, Deserialize, Debug, Clone)]
782pub struct ConnectedPeerInfo {
783    pub peer_id: String,
784    pub address: String,
785}
786
787#[derive(Serialize, Deserialize, Debug, Clone)]
788pub enum NodeQuery {
789    ConnectedPeers,
790    SubscriptionInfo,
791    NodeDiagnostics {
792        /// Diagnostic configuration specifying what information to collect
793        config: NodeDiagnosticsConfig,
794    },
795    /// Phase 3: Query proximity cache information for update propagation
796    ProximityCacheInfo,
797}
798
799/// Phase 3: Proximity cache information for update propagation
800#[derive(Serialize, Deserialize, Debug, Clone)]
801pub struct ProximityCacheInfo {
802    /// Contracts this node is currently caching
803    pub my_cache: Vec<ContractCacheEntry>,
804    /// What we know about neighbor caches
805    pub neighbor_caches: Vec<NeighborCacheInfo>,
806    /// Proximity propagation statistics
807    pub stats: ProximityStats,
808}
809
810#[derive(Serialize, Deserialize, Debug, Clone)]
811pub struct ContractCacheEntry {
812    /// Full contract key as string
813    pub contract_key: String,
814    /// 32-bit hash for proximity matching
815    pub cache_hash: u32,
816    /// When this contract was cached (Unix timestamp)
817    pub cached_since: u64,
818}
819
820#[derive(Serialize, Deserialize, Debug, Clone)]
821pub struct NeighborCacheInfo {
822    /// Peer identifier
823    pub peer_id: String,
824    /// Contract hashes this neighbor is known to cache
825    pub known_contracts: Vec<u32>,
826    /// Last update received from this neighbor (Unix timestamp)
827    pub last_update: u64,
828    /// Number of updates received from this neighbor
829    pub update_count: u64,
830}
831
832#[derive(Serialize, Deserialize, Debug, Clone)]
833pub struct ProximityStats {
834    /// Number of cache announcements sent
835    pub cache_announces_sent: u64,
836    /// Number of cache announcements received
837    pub cache_announces_received: u64,
838    /// Updates forwarded via proximity (not subscription)
839    pub updates_via_proximity: u64,
840    /// Updates forwarded via subscription
841    pub updates_via_subscription: u64,
842    /// False positives due to hash collisions
843    pub false_positive_forwards: u64,
844    /// Average number of contracts per neighbor
845    pub avg_neighbor_cache_size: f32,
846}
847
848#[derive(Serialize, Deserialize, Debug, Clone)]
849pub struct NodeDiagnosticsConfig {
850    /// Include basic node information (ID, location, uptime, etc.)
851    pub include_node_info: bool,
852
853    /// Include network connectivity information
854    pub include_network_info: bool,
855
856    /// Include contract subscription information
857    pub include_subscriptions: bool,
858
859    /// Include contract states for specific contracts (empty = all contracts)
860    pub contract_keys: Vec<ContractKey>,
861
862    /// Include memory and performance metrics
863    pub include_system_metrics: bool,
864
865    /// Include detailed information about connected peers (vs basic peer list)
866    pub include_detailed_peer_info: bool,
867
868    /// Include peer IDs of subscribers in contract state information
869    pub include_subscriber_peer_ids: bool,
870}
871
872impl NodeDiagnosticsConfig {
873    /// Create a comprehensive diagnostic config for debugging update propagation issues
874    pub fn for_update_propagation_debugging(contract_key: ContractKey) -> Self {
875        Self {
876            include_node_info: true,
877            include_network_info: true,
878            include_subscriptions: true,
879            contract_keys: vec![contract_key],
880            include_system_metrics: true,
881            include_detailed_peer_info: true,
882            include_subscriber_peer_ids: true,
883        }
884    }
885
886    /// Create a lightweight diagnostic config for basic node status
887    pub fn basic_status() -> Self {
888        Self {
889            include_node_info: true,
890            include_network_info: true,
891            include_subscriptions: false,
892            contract_keys: vec![],
893            include_system_metrics: false,
894            include_detailed_peer_info: false,
895            include_subscriber_peer_ids: false,
896        }
897    }
898
899    /// Create a full diagnostic config (all information)
900    pub fn full() -> Self {
901        Self {
902            include_node_info: true,
903            include_network_info: true,
904            include_subscriptions: true,
905            contract_keys: vec![], // empty = all contracts
906            include_system_metrics: true,
907            include_detailed_peer_info: true,
908            include_subscriber_peer_ids: true,
909        }
910    }
911}
912
913impl HostResponse {
914    pub fn unwrap_put(self) -> ContractKey {
915        if let Self::ContractResponse(ContractResponse::PutResponse { key }) = self {
916            key
917        } else {
918            panic!("called `HostResponse::unwrap_put()` on other than `PutResponse` value")
919        }
920    }
921
922    pub fn unwrap_get(self) -> (WrappedState, Option<ContractContainer>) {
923        if let Self::ContractResponse(ContractResponse::GetResponse {
924            contract, state, ..
925        }) = self
926        {
927            (state, contract)
928        } else {
929            panic!("called `HostResponse::unwrap_put()` on other than `PutResponse` value")
930        }
931    }
932
933    pub fn into_fbs_bytes(self) -> Result<Vec<u8>, Box<ClientError>> {
934        let mut builder = flatbuffers::FlatBufferBuilder::new();
935        match self {
936            HostResponse::ContractResponse(res) => match res {
937                ContractResponse::PutResponse { key } => {
938                    let instance_data = builder.create_vector(key.as_bytes());
939                    let instance_offset = FbsContractInstanceId::create(
940                        &mut builder,
941                        &ContractInstanceIdArgs {
942                            data: Some(instance_data),
943                        },
944                    );
945
946                    let code = Some(builder.create_vector(&key.code_hash().0));
947                    let key_offset = FbsContractKey::create(
948                        &mut builder,
949                        &ContractKeyArgs {
950                            instance: Some(instance_offset),
951                            code,
952                        },
953                    );
954
955                    let put_offset = FbsPutResponse::create(
956                        &mut builder,
957                        &PutResponseArgs {
958                            key: Some(key_offset),
959                        },
960                    );
961
962                    let contract_response_offset = FbsContractResponse::create(
963                        &mut builder,
964                        &ContractResponseArgs {
965                            contract_response: Some(put_offset.as_union_value()),
966                            contract_response_type: ContractResponseType::PutResponse,
967                        },
968                    );
969
970                    let response_offset = FbsHostResponse::create(
971                        &mut builder,
972                        &HostResponseArgs {
973                            response: Some(contract_response_offset.as_union_value()),
974                            response_type: HostResponseType::ContractResponse,
975                        },
976                    );
977
978                    finish_host_response_buffer(&mut builder, response_offset);
979                    Ok(builder.finished_data().to_vec())
980                }
981                ContractResponse::UpdateResponse { key, summary } => {
982                    let instance_data = builder.create_vector(key.as_bytes());
983                    let instance_offset = FbsContractInstanceId::create(
984                        &mut builder,
985                        &ContractInstanceIdArgs {
986                            data: Some(instance_data),
987                        },
988                    );
989
990                    let code = Some(builder.create_vector(&key.code_hash().0));
991
992                    let key_offset = FbsContractKey::create(
993                        &mut builder,
994                        &ContractKeyArgs {
995                            instance: Some(instance_offset),
996                            code,
997                        },
998                    );
999
1000                    let summary_data = builder.create_vector(&summary.into_bytes());
1001
1002                    let update_response_offset = FbsUpdateResponse::create(
1003                        &mut builder,
1004                        &UpdateResponseArgs {
1005                            key: Some(key_offset),
1006                            summary: Some(summary_data),
1007                        },
1008                    );
1009
1010                    let contract_response_offset = FbsContractResponse::create(
1011                        &mut builder,
1012                        &ContractResponseArgs {
1013                            contract_response: Some(update_response_offset.as_union_value()),
1014                            contract_response_type: ContractResponseType::UpdateResponse,
1015                        },
1016                    );
1017
1018                    let response_offset = FbsHostResponse::create(
1019                        &mut builder,
1020                        &HostResponseArgs {
1021                            response: Some(contract_response_offset.as_union_value()),
1022                            response_type: HostResponseType::ContractResponse,
1023                        },
1024                    );
1025
1026                    finish_host_response_buffer(&mut builder, response_offset);
1027                    Ok(builder.finished_data().to_vec())
1028                }
1029                ContractResponse::GetResponse {
1030                    key,
1031                    contract: contract_container,
1032                    state,
1033                } => {
1034                    let instance_data = builder.create_vector(key.as_bytes());
1035                    let instance_offset = FbsContractInstanceId::create(
1036                        &mut builder,
1037                        &ContractInstanceIdArgs {
1038                            data: Some(instance_data),
1039                        },
1040                    );
1041
1042                    let code = Some(builder.create_vector(&key.code_hash().0));
1043                    let key_offset = FbsContractKey::create(
1044                        &mut builder,
1045                        &ContractKeyArgs {
1046                            instance: Some(instance_offset),
1047                            code,
1048                        },
1049                    );
1050
1051                    let container_offset = if let Some(contract) = contract_container {
1052                        let data = builder.create_vector(contract.key().as_bytes());
1053
1054                        let instance_offset = FbsContractInstanceId::create(
1055                            &mut builder,
1056                            &ContractInstanceIdArgs { data: Some(data) },
1057                        );
1058
1059                        let code = Some(builder.create_vector(&contract.key().code_hash().0));
1060                        let contract_key_offset = FbsContractKey::create(
1061                            &mut builder,
1062                            &ContractKeyArgs {
1063                                instance: Some(instance_offset),
1064                                code,
1065                            },
1066                        );
1067
1068                        let contract_data =
1069                            builder.create_vector(contract.clone().unwrap_v1().data.data());
1070                        let contract_code_hash =
1071                            builder.create_vector(&contract.clone().unwrap_v1().data.hash().0);
1072
1073                        let contract_code_offset = ContractCode::create(
1074                            &mut builder,
1075                            &ContractCodeArgs {
1076                                data: Some(contract_data),
1077                                code_hash: Some(contract_code_hash),
1078                            },
1079                        );
1080
1081                        let contract_params =
1082                            builder.create_vector(&contract.clone().params().into_bytes());
1083
1084                        let contract_offset = match contract {
1085                            Wasm(V1(..)) => WasmContractV1::create(
1086                                &mut builder,
1087                                &WasmContractV1Args {
1088                                    key: Some(contract_key_offset),
1089                                    data: Some(contract_code_offset),
1090                                    parameters: Some(contract_params),
1091                                },
1092                            ),
1093                        };
1094
1095                        Some(FbsContractContainer::create(
1096                            &mut builder,
1097                            &ContractContainerArgs {
1098                                contract_type: ContractType::WasmContractV1,
1099                                contract: Some(contract_offset.as_union_value()),
1100                            },
1101                        ))
1102                    } else {
1103                        None
1104                    };
1105
1106                    let state_data = builder.create_vector(&state);
1107
1108                    let get_offset = FbsGetResponse::create(
1109                        &mut builder,
1110                        &GetResponseArgs {
1111                            key: Some(key_offset),
1112                            contract: container_offset,
1113                            state: Some(state_data),
1114                        },
1115                    );
1116
1117                    let contract_response_offset = FbsContractResponse::create(
1118                        &mut builder,
1119                        &ContractResponseArgs {
1120                            contract_response_type: ContractResponseType::GetResponse,
1121                            contract_response: Some(get_offset.as_union_value()),
1122                        },
1123                    );
1124
1125                    let response_offset = FbsHostResponse::create(
1126                        &mut builder,
1127                        &HostResponseArgs {
1128                            response: Some(contract_response_offset.as_union_value()),
1129                            response_type: HostResponseType::ContractResponse,
1130                        },
1131                    );
1132
1133                    finish_host_response_buffer(&mut builder, response_offset);
1134                    Ok(builder.finished_data().to_vec())
1135                }
1136                ContractResponse::UpdateNotification { key, update } => {
1137                    let instance_data = builder.create_vector(key.as_bytes());
1138                    let instance_offset = FbsContractInstanceId::create(
1139                        &mut builder,
1140                        &ContractInstanceIdArgs {
1141                            data: Some(instance_data),
1142                        },
1143                    );
1144
1145                    let code = Some(builder.create_vector(&key.code_hash().0));
1146                    let key_offset = FbsContractKey::create(
1147                        &mut builder,
1148                        &ContractKeyArgs {
1149                            instance: Some(instance_offset),
1150                            code,
1151                        },
1152                    );
1153
1154                    let update_data = match update {
1155                        State(state) => {
1156                            let state_data = builder.create_vector(&state.into_bytes());
1157                            let state_update_offset = StateUpdate::create(
1158                                &mut builder,
1159                                &StateUpdateArgs {
1160                                    state: Some(state_data),
1161                                },
1162                            );
1163                            FbsUpdateData::create(
1164                                &mut builder,
1165                                &UpdateDataArgs {
1166                                    update_data_type: UpdateDataType::StateUpdate,
1167                                    update_data: Some(state_update_offset.as_union_value()),
1168                                },
1169                            )
1170                        }
1171                        Delta(delta) => {
1172                            let delta_data = builder.create_vector(&delta.into_bytes());
1173                            let update_offset = DeltaUpdate::create(
1174                                &mut builder,
1175                                &DeltaUpdateArgs {
1176                                    delta: Some(delta_data),
1177                                },
1178                            );
1179                            FbsUpdateData::create(
1180                                &mut builder,
1181                                &UpdateDataArgs {
1182                                    update_data_type: UpdateDataType::DeltaUpdate,
1183                                    update_data: Some(update_offset.as_union_value()),
1184                                },
1185                            )
1186                        }
1187                        StateAndDelta { state, delta } => {
1188                            let state_data = builder.create_vector(&state.into_bytes());
1189                            let delta_data = builder.create_vector(&delta.into_bytes());
1190
1191                            let update_offset = StateAndDeltaUpdate::create(
1192                                &mut builder,
1193                                &StateAndDeltaUpdateArgs {
1194                                    state: Some(state_data),
1195                                    delta: Some(delta_data),
1196                                },
1197                            );
1198
1199                            FbsUpdateData::create(
1200                                &mut builder,
1201                                &UpdateDataArgs {
1202                                    update_data_type: UpdateDataType::StateAndDeltaUpdate,
1203                                    update_data: Some(update_offset.as_union_value()),
1204                                },
1205                            )
1206                        }
1207                        RelatedState { related_to, state } => {
1208                            let state_data = builder.create_vector(&state.into_bytes());
1209                            let instance_data =
1210                                builder.create_vector(related_to.encode().as_bytes());
1211
1212                            let instance_offset = FbsContractInstanceId::create(
1213                                &mut builder,
1214                                &ContractInstanceIdArgs {
1215                                    data: Some(instance_data),
1216                                },
1217                            );
1218
1219                            let update_offset = RelatedStateUpdate::create(
1220                                &mut builder,
1221                                &RelatedStateUpdateArgs {
1222                                    related_to: Some(instance_offset),
1223                                    state: Some(state_data),
1224                                },
1225                            );
1226
1227                            FbsUpdateData::create(
1228                                &mut builder,
1229                                &UpdateDataArgs {
1230                                    update_data_type: UpdateDataType::RelatedStateUpdate,
1231                                    update_data: Some(update_offset.as_union_value()),
1232                                },
1233                            )
1234                        }
1235                        RelatedDelta { related_to, delta } => {
1236                            let instance_data =
1237                                builder.create_vector(related_to.encode().as_bytes());
1238                            let delta_data = builder.create_vector(&delta.into_bytes());
1239
1240                            let instance_offset = FbsContractInstanceId::create(
1241                                &mut builder,
1242                                &ContractInstanceIdArgs {
1243                                    data: Some(instance_data),
1244                                },
1245                            );
1246
1247                            let update_offset = RelatedDeltaUpdate::create(
1248                                &mut builder,
1249                                &RelatedDeltaUpdateArgs {
1250                                    related_to: Some(instance_offset),
1251                                    delta: Some(delta_data),
1252                                },
1253                            );
1254
1255                            FbsUpdateData::create(
1256                                &mut builder,
1257                                &UpdateDataArgs {
1258                                    update_data_type: UpdateDataType::RelatedDeltaUpdate,
1259                                    update_data: Some(update_offset.as_union_value()),
1260                                },
1261                            )
1262                        }
1263                        RelatedStateAndDelta {
1264                            related_to,
1265                            state,
1266                            delta,
1267                        } => {
1268                            let instance_data =
1269                                builder.create_vector(related_to.encode().as_bytes());
1270                            let state_data = builder.create_vector(&state.into_bytes());
1271                            let delta_data = builder.create_vector(&delta.into_bytes());
1272
1273                            let instance_offset = FbsContractInstanceId::create(
1274                                &mut builder,
1275                                &ContractInstanceIdArgs {
1276                                    data: Some(instance_data),
1277                                },
1278                            );
1279
1280                            let update_offset = RelatedStateAndDeltaUpdate::create(
1281                                &mut builder,
1282                                &RelatedStateAndDeltaUpdateArgs {
1283                                    related_to: Some(instance_offset),
1284                                    state: Some(state_data),
1285                                    delta: Some(delta_data),
1286                                },
1287                            );
1288
1289                            FbsUpdateData::create(
1290                                &mut builder,
1291                                &UpdateDataArgs {
1292                                    update_data_type: UpdateDataType::RelatedStateAndDeltaUpdate,
1293                                    update_data: Some(update_offset.as_union_value()),
1294                                },
1295                            )
1296                        }
1297                    };
1298
1299                    let update_notification_offset = FbsUpdateNotification::create(
1300                        &mut builder,
1301                        &UpdateNotificationArgs {
1302                            key: Some(key_offset),
1303                            update: Some(update_data),
1304                        },
1305                    );
1306
1307                    let put_response_offset = FbsContractResponse::create(
1308                        &mut builder,
1309                        &ContractResponseArgs {
1310                            contract_response_type: ContractResponseType::UpdateNotification,
1311                            contract_response: Some(update_notification_offset.as_union_value()),
1312                        },
1313                    );
1314
1315                    let host_response_offset = FbsHostResponse::create(
1316                        &mut builder,
1317                        &HostResponseArgs {
1318                            response_type: HostResponseType::ContractResponse,
1319                            response: Some(put_response_offset.as_union_value()),
1320                        },
1321                    );
1322
1323                    finish_host_response_buffer(&mut builder, host_response_offset);
1324                    Ok(builder.finished_data().to_vec())
1325                }
1326                ContractResponse::SubscribeResponse { .. } => todo!(),
1327                ContractResponse::NotFound { instance_id } => {
1328                    let instance_data = builder.create_vector(instance_id.as_bytes());
1329                    let instance_offset = FbsContractInstanceId::create(
1330                        &mut builder,
1331                        &ContractInstanceIdArgs {
1332                            data: Some(instance_data),
1333                        },
1334                    );
1335
1336                    let not_found_offset = FbsNotFound::create(
1337                        &mut builder,
1338                        &NotFoundArgs {
1339                            instance_id: Some(instance_offset),
1340                        },
1341                    );
1342
1343                    let contract_response_offset = FbsContractResponse::create(
1344                        &mut builder,
1345                        &ContractResponseArgs {
1346                            contract_response_type: ContractResponseType::NotFound,
1347                            contract_response: Some(not_found_offset.as_union_value()),
1348                        },
1349                    );
1350
1351                    let response_offset = FbsHostResponse::create(
1352                        &mut builder,
1353                        &HostResponseArgs {
1354                            response: Some(contract_response_offset.as_union_value()),
1355                            response_type: HostResponseType::ContractResponse,
1356                        },
1357                    );
1358
1359                    finish_host_response_buffer(&mut builder, response_offset);
1360                    Ok(builder.finished_data().to_vec())
1361                }
1362            },
1363            HostResponse::DelegateResponse { key, values } => {
1364                let key_data = builder.create_vector(key.bytes());
1365                let code_hash_data = builder.create_vector(&key.code_hash().0);
1366                let key_offset = FbsDelegateKey::create(
1367                    &mut builder,
1368                    &DelegateKeyArgs {
1369                        key: Some(key_data),
1370                        code_hash: Some(code_hash_data),
1371                    },
1372                );
1373                let mut messages: Vec<WIPOffset<FbsOutboundDelegateMsg>> = Vec::new();
1374                values.iter().for_each(|msg| match msg {
1375                    OutboundDelegateMsg::ApplicationMessage(app) => {
1376                        let instance_data = builder.create_vector(key.bytes());
1377                        let instance_offset = FbsContractInstanceId::create(
1378                            &mut builder,
1379                            &ContractInstanceIdArgs {
1380                                data: Some(instance_data),
1381                            },
1382                        );
1383                        let payload_data = builder.create_vector(&app.payload);
1384                        let delegate_context_data = builder.create_vector(app.context.as_ref());
1385                        let app_offset = FbsApplicationMessage::create(
1386                            &mut builder,
1387                            &ApplicationMessageArgs {
1388                                app: Some(instance_offset),
1389                                payload: Some(payload_data),
1390                                context: Some(delegate_context_data),
1391                                processed: app.processed,
1392                            },
1393                        );
1394                        let msg = FbsOutboundDelegateMsg::create(
1395                            &mut builder,
1396                            &OutboundDelegateMsgArgs {
1397                                inbound_type: OutboundDelegateMsgType::common_ApplicationMessage,
1398                                inbound: Some(app_offset.as_union_value()),
1399                            },
1400                        );
1401                        messages.push(msg);
1402                    }
1403                    OutboundDelegateMsg::RequestUserInput(input) => {
1404                        let message_data = builder.create_vector(input.message.bytes());
1405                        let mut responses: Vec<WIPOffset<FbsClientResponse>> = Vec::new();
1406                        input.responses.iter().for_each(|resp| {
1407                            let response_data = builder.create_vector(resp.bytes());
1408                            let response = FbsClientResponse::create(
1409                                &mut builder,
1410                                &ClientResponseArgs {
1411                                    data: Some(response_data),
1412                                },
1413                            );
1414                            responses.push(response)
1415                        });
1416                        let responses_offset = builder.create_vector(&responses);
1417                        let input_offset = FbsRequestUserInput::create(
1418                            &mut builder,
1419                            &RequestUserInputArgs {
1420                                request_id: input.request_id,
1421                                message: Some(message_data),
1422                                responses: Some(responses_offset),
1423                            },
1424                        );
1425                        let msg = FbsOutboundDelegateMsg::create(
1426                            &mut builder,
1427                            &OutboundDelegateMsgArgs {
1428                                inbound_type: OutboundDelegateMsgType::RequestUserInput,
1429                                inbound: Some(input_offset.as_union_value()),
1430                            },
1431                        );
1432                        messages.push(msg);
1433                    }
1434                    OutboundDelegateMsg::ContextUpdated(context) => {
1435                        let context_data = builder.create_vector(context.as_ref());
1436                        let context_offset = FbsContextUpdated::create(
1437                            &mut builder,
1438                            &ContextUpdatedArgs {
1439                                context: Some(context_data),
1440                            },
1441                        );
1442                        let msg = FbsOutboundDelegateMsg::create(
1443                            &mut builder,
1444                            &OutboundDelegateMsgArgs {
1445                                inbound_type: OutboundDelegateMsgType::ContextUpdated,
1446                                inbound: Some(context_offset.as_union_value()),
1447                            },
1448                        );
1449                        messages.push(msg);
1450                    }
1451                    OutboundDelegateMsg::GetContractRequest(_) => {
1452                        // GetContractRequest should be handled by the executor and never
1453                        // reach client serialization. If we get here, it's a bug.
1454                        tracing::error!(
1455                            "GetContractRequest reached client serialization - this is a bug"
1456                        );
1457                    }
1458                    OutboundDelegateMsg::PutContractRequest(_) => {
1459                        // PutContractRequest should be handled by the executor and never
1460                        // reach client serialization. If we get here, it's a bug.
1461                        tracing::error!(
1462                            "PutContractRequest reached client serialization - this is a bug"
1463                        );
1464                    }
1465                    OutboundDelegateMsg::UpdateContractRequest(_) => {
1466                        tracing::error!(
1467                            "UpdateContractRequest reached client serialization - this is a bug"
1468                        );
1469                    }
1470                    OutboundDelegateMsg::SubscribeContractRequest(_) => {
1471                        tracing::error!(
1472                            "SubscribeContractRequest reached client serialization - this is a bug"
1473                        );
1474                    }
1475                });
1476                let messages_offset = builder.create_vector(&messages);
1477                let delegate_response_offset = FbsDelegateResponse::create(
1478                    &mut builder,
1479                    &DelegateResponseArgs {
1480                        key: Some(key_offset),
1481                        values: Some(messages_offset),
1482                    },
1483                );
1484                let host_response_offset = FbsHostResponse::create(
1485                    &mut builder,
1486                    &HostResponseArgs {
1487                        response_type: HostResponseType::DelegateResponse,
1488                        response: Some(delegate_response_offset.as_union_value()),
1489                    },
1490                );
1491                finish_host_response_buffer(&mut builder, host_response_offset);
1492                Ok(builder.finished_data().to_vec())
1493            }
1494            HostResponse::Ok => {
1495                let ok_offset = FbsOk::create(&mut builder, &OkArgs { msg: None });
1496                let host_response_offset = FbsHostResponse::create(
1497                    &mut builder,
1498                    &HostResponseArgs {
1499                        response_type: HostResponseType::Ok,
1500                        response: Some(ok_offset.as_union_value()),
1501                    },
1502                );
1503                finish_host_response_buffer(&mut builder, host_response_offset);
1504                Ok(builder.finished_data().to_vec())
1505            }
1506            HostResponse::QueryResponse(_) => unimplemented!(),
1507        }
1508    }
1509}
1510
1511impl Display for HostResponse {
1512    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1513        match self {
1514            HostResponse::ContractResponse(res) => match res {
1515                ContractResponse::PutResponse { key } => {
1516                    f.write_fmt(format_args!("put response for `{key}`"))
1517                }
1518                ContractResponse::UpdateResponse { key, .. } => {
1519                    f.write_fmt(format_args!("update response for `{key}`"))
1520                }
1521                ContractResponse::GetResponse { key, .. } => {
1522                    f.write_fmt(format_args!("get response for `{key}`"))
1523                }
1524                ContractResponse::UpdateNotification { key, .. } => {
1525                    f.write_fmt(format_args!("update notification for `{key}`"))
1526                }
1527                ContractResponse::SubscribeResponse { key, .. } => {
1528                    f.write_fmt(format_args!("subscribe response for `{key}`"))
1529                }
1530                ContractResponse::NotFound { instance_id } => {
1531                    f.write_fmt(format_args!("not found for `{instance_id}`"))
1532                }
1533            },
1534            HostResponse::DelegateResponse { .. } => write!(f, "delegate responses"),
1535            HostResponse::Ok => write!(f, "ok response"),
1536            HostResponse::QueryResponse(_) => write!(f, "query response"),
1537        }
1538    }
1539}
1540
1541#[derive(Clone, Serialize, Deserialize, Debug)]
1542#[non_exhaustive]
1543pub enum ContractResponse<T = WrappedState> {
1544    GetResponse {
1545        key: ContractKey,
1546        contract: Option<ContractContainer>,
1547        #[serde(bound(deserialize = "T: DeserializeOwned"))]
1548        state: T,
1549    },
1550    PutResponse {
1551        key: ContractKey,
1552    },
1553    /// Message sent when there is an update to a subscribed contract.
1554    UpdateNotification {
1555        key: ContractKey,
1556        #[serde(deserialize_with = "UpdateData::deser_update_data")]
1557        update: UpdateData<'static>,
1558    },
1559    /// Successful update
1560    UpdateResponse {
1561        key: ContractKey,
1562        #[serde(deserialize_with = "StateSummary::deser_state_summary")]
1563        summary: StateSummary<'static>,
1564    },
1565    SubscribeResponse {
1566        key: ContractKey,
1567        subscribed: bool,
1568    },
1569    /// Contract was not found after exhaustive search.
1570    /// This is an explicit response that distinguishes "contract doesn't exist"
1571    /// from other failure modes like timeouts or network errors.
1572    NotFound {
1573        /// The instance ID that was searched for.
1574        instance_id: ContractInstanceId,
1575    },
1576}
1577
1578impl<T> From<ContractResponse<T>> for HostResponse<T> {
1579    fn from(value: ContractResponse<T>) -> HostResponse<T> {
1580        HostResponse::ContractResponse(value)
1581    }
1582}
1583
1584#[cfg(test)]
1585mod client_request_test {
1586    use crate::client_api::{ContractRequest, TryFromFbs};
1587    use crate::contract_interface::UpdateData;
1588    use crate::generated::client_request::root_as_client_request;
1589
1590    const EXPECTED_ENCODED_CONTRACT_ID: &str = "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9";
1591
1592    #[test]
1593    fn test_build_contract_put_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1594        let put_req_op = vec![
1595            4, 0, 0, 0, 244, 255, 255, 255, 16, 0, 0, 0, 0, 0, 0, 1, 8, 0, 12, 0, 11, 0, 4, 0, 8,
1596            0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 198, 255, 255, 255, 12, 0, 0, 0, 20, 0, 0, 0, 36, 0,
1597            0, 0, 170, 255, 255, 255, 4, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
1598            8, 0, 10, 0, 9, 0, 4, 0, 8, 0, 0, 0, 16, 0, 0, 0, 0, 1, 10, 0, 16, 0, 12, 0, 8, 0, 4,
1599            0, 10, 0, 0, 0, 12, 0, 0, 0, 76, 0, 0, 0, 92, 0, 0, 0, 176, 255, 255, 255, 8, 0, 0, 0,
1600            16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 8, 0, 4, 0, 6, 0, 0, 0, 4, 0, 0, 0, 32, 0, 0, 0,
1601            85, 111, 11, 171, 40, 85, 240, 177, 207, 81, 106, 157, 173, 90, 234, 2, 250, 253, 75,
1602            210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17, 108, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6,
1603            7, 8, 8, 0, 12, 0, 8, 0, 4, 0, 8, 0, 0, 0, 8, 0, 0, 0, 16, 0, 0, 0, 8, 0, 0, 0, 1, 2,
1604            3, 4, 5, 6, 7, 8, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
1605        ];
1606        let request = if let Ok(client_request) = root_as_client_request(&put_req_op) {
1607            let contract_request = client_request.client_request_as_contract_request().unwrap();
1608            ContractRequest::try_decode_fbs(&contract_request)?
1609        } else {
1610            panic!("failed to decode client request")
1611        };
1612
1613        match request {
1614            ContractRequest::Put {
1615                contract,
1616                state,
1617                related_contracts: _,
1618                subscribe,
1619                blocking_subscribe,
1620            } => {
1621                assert_eq!(
1622                    contract.to_string(),
1623                    "WasmContainer([api=0.0.1](D8fdVLbRyMLw5mZtPRpWMFcrXGN2z8Nq8UGcLGPFBg2W))"
1624                );
1625                assert_eq!(contract.unwrap_v1().data.data(), &[1, 2, 3, 4, 5, 6, 7, 8]);
1626                assert_eq!(state.to_vec(), &[1, 2, 3, 4, 5, 6, 7, 8]);
1627                assert!(!subscribe);
1628                assert!(!blocking_subscribe);
1629            }
1630            _ => panic!("wrong contract request type"),
1631        }
1632
1633        Ok(())
1634    }
1635
1636    #[test]
1637    fn test_build_contract_get_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1638        let get_req_op = vec![
1639            4, 0, 0, 0, 244, 255, 255, 255, 16, 0, 0, 0, 0, 0, 0, 1, 8, 0, 12, 0, 11, 0, 4, 0, 8,
1640            0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 3, 222, 255, 255, 255, 12, 0, 0, 0, 8, 0, 12, 0, 8, 0, 4,
1641            0, 8, 0, 0, 0, 8, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 8, 0, 4, 0, 6, 0, 0, 0,
1642            4, 0, 0, 0, 32, 0, 0, 0, 85, 111, 11, 171, 40, 85, 240, 177, 207, 81, 106, 157, 173,
1643            90, 234, 2, 250, 253, 75, 210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17, 108,
1644        ];
1645        let request = if let Ok(client_request) = root_as_client_request(&get_req_op) {
1646            let contract_request = client_request.client_request_as_contract_request().unwrap();
1647            ContractRequest::try_decode_fbs(&contract_request)?
1648        } else {
1649            panic!("failed to decode client request")
1650        };
1651
1652        match request {
1653            ContractRequest::Get {
1654                key,
1655                return_contract_code: fetch_contract,
1656                subscribe,
1657                blocking_subscribe,
1658            } => {
1659                assert_eq!(key.encode(), EXPECTED_ENCODED_CONTRACT_ID);
1660                assert!(!fetch_contract);
1661                assert!(!subscribe);
1662                assert!(!blocking_subscribe);
1663            }
1664            _ => panic!("wrong contract request type"),
1665        }
1666
1667        Ok(())
1668    }
1669
1670    #[test]
1671    fn test_build_contract_update_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1672        let update_op = vec![
1673            4, 0, 0, 0, 220, 255, 255, 255, 8, 0, 0, 0, 0, 0, 0, 1, 232, 255, 255, 255, 8, 0, 0, 0,
1674            0, 0, 0, 2, 204, 255, 255, 255, 16, 0, 0, 0, 52, 0, 0, 0, 8, 0, 12, 0, 11, 0, 4, 0, 8,
1675            0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 2, 210, 255, 255, 255, 4, 0, 0, 0, 8, 0, 0, 0, 1, 2, 3,
1676            4, 5, 6, 7, 8, 8, 0, 12, 0, 8, 0, 4, 0, 8, 0, 0, 0, 8, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0,
1677            0, 0, 0, 6, 0, 8, 0, 4, 0, 6, 0, 0, 0, 4, 0, 0, 0, 32, 0, 0, 0, 85, 111, 11, 171, 40,
1678            85, 240, 177, 207, 81, 106, 157, 173, 90, 234, 2, 250, 253, 75, 210, 62, 7, 6, 34, 75,
1679            26, 229, 230, 107, 167, 17, 108,
1680        ];
1681        let request = if let Ok(client_request) = root_as_client_request(&update_op) {
1682            let contract_request = client_request.client_request_as_contract_request().unwrap();
1683            ContractRequest::try_decode_fbs(&contract_request)?
1684        } else {
1685            panic!("failed to decode client request")
1686        };
1687
1688        match request {
1689            ContractRequest::Update { key, data } => {
1690                assert_eq!(
1691                    key.encoded_contract_id(),
1692                    "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9"
1693                );
1694                match data {
1695                    UpdateData::Delta(delta) => {
1696                        assert_eq!(delta.to_vec(), &[1, 2, 3, 4, 5, 6, 7, 8])
1697                    }
1698                    _ => panic!("wrong update data type"),
1699                }
1700            }
1701            _ => panic!("wrong contract request type"),
1702        }
1703
1704        Ok(())
1705    }
1706}