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