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                    OutboundDelegateMsg::SendDelegateMessage(_) => {
1480                        tracing::error!(
1481                            "SendDelegateMessage reached client serialization - this is a bug"
1482                        );
1483                    }
1484                });
1485                let messages_offset = builder.create_vector(&messages);
1486                let delegate_response_offset = FbsDelegateResponse::create(
1487                    &mut builder,
1488                    &DelegateResponseArgs {
1489                        key: Some(key_offset),
1490                        values: Some(messages_offset),
1491                    },
1492                );
1493                let host_response_offset = FbsHostResponse::create(
1494                    &mut builder,
1495                    &HostResponseArgs {
1496                        response_type: HostResponseType::DelegateResponse,
1497                        response: Some(delegate_response_offset.as_union_value()),
1498                    },
1499                );
1500                finish_host_response_buffer(&mut builder, host_response_offset);
1501                Ok(builder.finished_data().to_vec())
1502            }
1503            HostResponse::Ok => {
1504                let ok_offset = FbsOk::create(&mut builder, &OkArgs { msg: None });
1505                let host_response_offset = FbsHostResponse::create(
1506                    &mut builder,
1507                    &HostResponseArgs {
1508                        response_type: HostResponseType::Ok,
1509                        response: Some(ok_offset.as_union_value()),
1510                    },
1511                );
1512                finish_host_response_buffer(&mut builder, host_response_offset);
1513                Ok(builder.finished_data().to_vec())
1514            }
1515            HostResponse::QueryResponse(_) => unimplemented!(),
1516        }
1517    }
1518}
1519
1520impl Display for HostResponse {
1521    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1522        match self {
1523            HostResponse::ContractResponse(res) => match res {
1524                ContractResponse::PutResponse { key } => {
1525                    f.write_fmt(format_args!("put response for `{key}`"))
1526                }
1527                ContractResponse::UpdateResponse { key, .. } => {
1528                    f.write_fmt(format_args!("update response for `{key}`"))
1529                }
1530                ContractResponse::GetResponse { key, .. } => {
1531                    f.write_fmt(format_args!("get response for `{key}`"))
1532                }
1533                ContractResponse::UpdateNotification { key, .. } => {
1534                    f.write_fmt(format_args!("update notification for `{key}`"))
1535                }
1536                ContractResponse::SubscribeResponse { key, .. } => {
1537                    f.write_fmt(format_args!("subscribe response for `{key}`"))
1538                }
1539                ContractResponse::NotFound { instance_id } => {
1540                    f.write_fmt(format_args!("not found for `{instance_id}`"))
1541                }
1542            },
1543            HostResponse::DelegateResponse { .. } => write!(f, "delegate responses"),
1544            HostResponse::Ok => write!(f, "ok response"),
1545            HostResponse::QueryResponse(_) => write!(f, "query response"),
1546        }
1547    }
1548}
1549
1550#[derive(Clone, Serialize, Deserialize, Debug)]
1551#[non_exhaustive]
1552pub enum ContractResponse<T = WrappedState> {
1553    GetResponse {
1554        key: ContractKey,
1555        contract: Option<ContractContainer>,
1556        #[serde(bound(deserialize = "T: DeserializeOwned"))]
1557        state: T,
1558    },
1559    PutResponse {
1560        key: ContractKey,
1561    },
1562    /// Message sent when there is an update to a subscribed contract.
1563    UpdateNotification {
1564        key: ContractKey,
1565        #[serde(deserialize_with = "UpdateData::deser_update_data")]
1566        update: UpdateData<'static>,
1567    },
1568    /// Successful update
1569    UpdateResponse {
1570        key: ContractKey,
1571        #[serde(deserialize_with = "StateSummary::deser_state_summary")]
1572        summary: StateSummary<'static>,
1573    },
1574    SubscribeResponse {
1575        key: ContractKey,
1576        subscribed: bool,
1577    },
1578    /// Contract was not found after exhaustive search.
1579    /// This is an explicit response that distinguishes "contract doesn't exist"
1580    /// from other failure modes like timeouts or network errors.
1581    NotFound {
1582        /// The instance ID that was searched for.
1583        instance_id: ContractInstanceId,
1584    },
1585}
1586
1587impl<T> From<ContractResponse<T>> for HostResponse<T> {
1588    fn from(value: ContractResponse<T>) -> HostResponse<T> {
1589        HostResponse::ContractResponse(value)
1590    }
1591}
1592
1593#[cfg(test)]
1594mod client_request_test {
1595    use crate::client_api::{ContractRequest, TryFromFbs};
1596    use crate::contract_interface::UpdateData;
1597    use crate::generated::client_request::root_as_client_request;
1598
1599    const EXPECTED_ENCODED_CONTRACT_ID: &str = "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9";
1600
1601    #[test]
1602    fn test_build_contract_put_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1603        let put_req_op = vec![
1604            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,
1605            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,
1606            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,
1607            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,
1608            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,
1609            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,
1610            85, 111, 11, 171, 40, 85, 240, 177, 207, 81, 106, 157, 173, 90, 234, 2, 250, 253, 75,
1611            210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17, 108, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6,
1612            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,
1613            3, 4, 5, 6, 7, 8, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
1614        ];
1615        let request = if let Ok(client_request) = root_as_client_request(&put_req_op) {
1616            let contract_request = client_request.client_request_as_contract_request().unwrap();
1617            ContractRequest::try_decode_fbs(&contract_request)?
1618        } else {
1619            panic!("failed to decode client request")
1620        };
1621
1622        match request {
1623            ContractRequest::Put {
1624                contract,
1625                state,
1626                related_contracts: _,
1627                subscribe,
1628                blocking_subscribe,
1629            } => {
1630                assert_eq!(
1631                    contract.to_string(),
1632                    "WasmContainer([api=0.0.1](D8fdVLbRyMLw5mZtPRpWMFcrXGN2z8Nq8UGcLGPFBg2W))"
1633                );
1634                assert_eq!(contract.unwrap_v1().data.data(), &[1, 2, 3, 4, 5, 6, 7, 8]);
1635                assert_eq!(state.to_vec(), &[1, 2, 3, 4, 5, 6, 7, 8]);
1636                assert!(!subscribe);
1637                assert!(!blocking_subscribe);
1638            }
1639            _ => panic!("wrong contract request type"),
1640        }
1641
1642        Ok(())
1643    }
1644
1645    #[test]
1646    fn test_build_contract_get_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1647        let get_req_op = vec![
1648            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,
1649            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,
1650            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,
1651            4, 0, 0, 0, 32, 0, 0, 0, 85, 111, 11, 171, 40, 85, 240, 177, 207, 81, 106, 157, 173,
1652            90, 234, 2, 250, 253, 75, 210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17, 108,
1653        ];
1654        let request = if let Ok(client_request) = root_as_client_request(&get_req_op) {
1655            let contract_request = client_request.client_request_as_contract_request().unwrap();
1656            ContractRequest::try_decode_fbs(&contract_request)?
1657        } else {
1658            panic!("failed to decode client request")
1659        };
1660
1661        match request {
1662            ContractRequest::Get {
1663                key,
1664                return_contract_code: fetch_contract,
1665                subscribe,
1666                blocking_subscribe,
1667            } => {
1668                assert_eq!(key.encode(), EXPECTED_ENCODED_CONTRACT_ID);
1669                assert!(!fetch_contract);
1670                assert!(!subscribe);
1671                assert!(!blocking_subscribe);
1672            }
1673            _ => panic!("wrong contract request type"),
1674        }
1675
1676        Ok(())
1677    }
1678
1679    #[test]
1680    fn test_build_contract_update_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1681        let update_op = vec![
1682            4, 0, 0, 0, 220, 255, 255, 255, 8, 0, 0, 0, 0, 0, 0, 1, 232, 255, 255, 255, 8, 0, 0, 0,
1683            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,
1684            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,
1685            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,
1686            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,
1687            85, 240, 177, 207, 81, 106, 157, 173, 90, 234, 2, 250, 253, 75, 210, 62, 7, 6, 34, 75,
1688            26, 229, 230, 107, 167, 17, 108,
1689        ];
1690        let request = if let Ok(client_request) = root_as_client_request(&update_op) {
1691            let contract_request = client_request.client_request_as_contract_request().unwrap();
1692            ContractRequest::try_decode_fbs(&contract_request)?
1693        } else {
1694            panic!("failed to decode client request")
1695        };
1696
1697        match request {
1698            ContractRequest::Update { key, data } => {
1699                assert_eq!(
1700                    key.encoded_contract_id(),
1701                    "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9"
1702                );
1703                match data {
1704                    UpdateData::Delta(delta) => {
1705                        assert_eq!(delta.to_vec(), &[1, 2, 3, 4, 5, 6, 7, 8])
1706                    }
1707                    _ => panic!("wrong update data type"),
1708                }
1709            }
1710            _ => panic!("wrong contract request type"),
1711        }
1712
1713        Ok(())
1714    }
1715}