Skip to main content

freenet_stdlib/client_api/
client_events.rs

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