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::delegate_interface::DelegateContext;
15use crate::generated::common::{
16    ApplicationMessage as FbsApplicationMessage, ApplicationMessageArgs, ContractCode,
17    ContractCodeArgs, ContractContainer as FbsContractContainer, ContractContainerArgs,
18    ContractInstanceId as FbsContractInstanceId, ContractInstanceIdArgs,
19    ContractKey as FbsContractKey, ContractKeyArgs, ContractType, DeltaUpdate, DeltaUpdateArgs,
20    GetSecretRequest as FbsGetSecretRequest, GetSecretRequestArgs,
21    GetSecretResponse as FbsGetSecretResponse, GetSecretResponseArgs, RelatedDeltaUpdate,
22    RelatedDeltaUpdateArgs, RelatedStateAndDeltaUpdate, RelatedStateAndDeltaUpdateArgs,
23    RelatedStateUpdate, RelatedStateUpdateArgs, SecretsId as FbsSecretsId, SecretsIdArgs,
24    StateAndDeltaUpdate, StateAndDeltaUpdateArgs, StateUpdate, StateUpdateArgs,
25    UpdateData as FbsUpdateData, UpdateDataArgs, UpdateDataType, WasmContractV1,
26    WasmContractV1Args,
27};
28use crate::generated::host_response::{
29    finish_host_response_buffer, ClientResponse as FbsClientResponse, ClientResponseArgs,
30    ContextUpdated as FbsContextUpdated, ContextUpdatedArgs,
31    ContractResponse as FbsContractResponse, ContractResponseArgs, ContractResponseType,
32    DelegateKey as FbsDelegateKey, DelegateKeyArgs, DelegateResponse as FbsDelegateResponse,
33    DelegateResponseArgs, GetResponse as FbsGetResponse, GetResponseArgs,
34    HostResponse as FbsHostResponse, HostResponseArgs, HostResponseType, NotFound as FbsNotFound,
35    NotFoundArgs, Ok as FbsOk, OkArgs, OutboundDelegateMsg as FbsOutboundDelegateMsg,
36    OutboundDelegateMsgArgs, OutboundDelegateMsgType, PutResponse as FbsPutResponse,
37    PutResponseArgs, RequestUserInput as FbsRequestUserInput, RequestUserInputArgs,
38    SetSecretRequest as FbsSetSecretRequest, SetSecretRequestArgs,
39    UpdateNotification as FbsUpdateNotification, UpdateNotificationArgs,
40    UpdateResponse as FbsUpdateResponse, UpdateResponseArgs,
41};
42use crate::prelude::ContractContainer::Wasm;
43use crate::prelude::ContractWasmAPIVersion::V1;
44use crate::prelude::UpdateData::{
45    Delta, RelatedDelta, RelatedState, RelatedStateAndDelta, State, StateAndDelta,
46};
47use crate::{
48    delegate_interface::{DelegateKey, InboundDelegateMsg, OutboundDelegateMsg},
49    prelude::{
50        ContractInstanceId, ContractKey, DelegateContainer, GetSecretRequest, Parameters,
51        RelatedContracts, SecretsId, StateSummary, UpdateData, WrappedState,
52    },
53    versioning::ContractContainer,
54};
55
56use super::WsApiError;
57
58#[derive(Debug, Serialize, Deserialize, Clone)]
59pub struct ClientError {
60    kind: Box<ErrorKind>,
61}
62
63impl ClientError {
64    pub fn into_fbs_bytes(self) -> Result<Vec<u8>, Box<ClientError>> {
65        use crate::generated::host_response::{Error, ErrorArgs};
66        let mut builder = flatbuffers::FlatBufferBuilder::new();
67        let msg_offset = builder.create_string(&self.to_string());
68        let err_offset = Error::create(
69            &mut builder,
70            &ErrorArgs {
71                msg: Some(msg_offset),
72            },
73        );
74        let host_response_offset = FbsHostResponse::create(
75            &mut builder,
76            &HostResponseArgs {
77                response_type: HostResponseType::Ok,
78                response: Some(err_offset.as_union_value()),
79            },
80        );
81        finish_host_response_buffer(&mut builder, host_response_offset);
82        Ok(builder.finished_data().to_vec())
83    }
84
85    pub fn kind(&self) -> &ErrorKind {
86        &self.kind
87    }
88}
89
90impl From<ErrorKind> for ClientError {
91    fn from(kind: ErrorKind) -> Self {
92        ClientError {
93            kind: Box::new(kind),
94        }
95    }
96}
97
98impl<T: Into<Cow<'static, str>>> From<T> for ClientError {
99    fn from(cause: T) -> Self {
100        ClientError {
101            kind: Box::new(ErrorKind::Unhandled {
102                cause: cause.into(),
103            }),
104        }
105    }
106}
107
108#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone)]
109#[non_exhaustive]
110pub enum ErrorKind {
111    #[error("comm channel between client/host closed")]
112    ChannelClosed,
113    #[error("error while deserializing: {cause}")]
114    DeserializationError { cause: Cow<'static, str> },
115    #[error("client disconnected")]
116    Disconnect,
117    #[error("failed while trying to unpack state for {0}")]
118    IncorrectState(ContractKey),
119    #[error("node not available")]
120    NodeUnavailable,
121    #[error("lost the connection with the protocol handling connections")]
122    TransportProtocolDisconnect,
123    #[error("unhandled error: {cause}")]
124    Unhandled { cause: Cow<'static, str> },
125    #[error("unknown client id: {0}")]
126    UnknownClient(usize),
127    #[error(transparent)]
128    RequestError(#[from] RequestError),
129    #[error("error while executing operation in the network: {cause}")]
130    OperationError { cause: Cow<'static, str> },
131    // TODO: identify requests by some id so we can inform clients which one failed exactly
132    #[error("operation timed out")]
133    FailedOperation,
134    #[error("peer should shutdown")]
135    Shutdown,
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    GetSecretRequest {
543        key: DelegateKey,
544        #[serde(borrow)]
545        params: Parameters<'a>,
546        get_request: GetSecretRequest,
547    },
548    RegisterDelegate {
549        delegate: DelegateContainer,
550        cipher: [u8; 32],
551        nonce: [u8; 24],
552    },
553    UnregisterDelegate(DelegateKey),
554}
555
556impl DelegateRequest<'_> {
557    pub const DEFAULT_CIPHER: [u8; 32] = [
558        0, 24, 22, 150, 112, 207, 24, 65, 182, 161, 169, 227, 66, 182, 237, 215, 206, 164, 58, 161,
559        64, 108, 157, 195, 0, 0, 0, 0, 0, 0, 0, 0,
560    ];
561
562    pub const DEFAULT_NONCE: [u8; 24] = [
563        57, 18, 79, 116, 63, 134, 93, 39, 208, 161, 156, 229, 222, 247, 111, 79, 210, 126, 127, 55,
564        224, 150, 139, 80,
565    ];
566
567    pub fn into_owned(self) -> DelegateRequest<'static> {
568        match self {
569            DelegateRequest::ApplicationMessages {
570                key,
571                inbound,
572                params,
573            } => DelegateRequest::ApplicationMessages {
574                key,
575                params: params.into_owned(),
576                inbound: inbound.into_iter().map(|e| e.into_owned()).collect(),
577            },
578            DelegateRequest::GetSecretRequest {
579                key,
580                get_request,
581                params,
582            } => DelegateRequest::GetSecretRequest {
583                key,
584                get_request,
585                params: params.into_owned(),
586            },
587            DelegateRequest::RegisterDelegate {
588                delegate,
589                cipher,
590                nonce,
591            } => DelegateRequest::RegisterDelegate {
592                delegate,
593                cipher,
594                nonce,
595            },
596            DelegateRequest::UnregisterDelegate(key) => DelegateRequest::UnregisterDelegate(key),
597        }
598    }
599
600    pub fn key(&self) -> &DelegateKey {
601        match self {
602            DelegateRequest::ApplicationMessages { key, .. } => key,
603            DelegateRequest::GetSecretRequest { key, .. } => key,
604            DelegateRequest::RegisterDelegate { delegate, .. } => delegate.key(),
605            DelegateRequest::UnregisterDelegate(key) => key,
606        }
607    }
608}
609
610impl Display for ClientRequest<'_> {
611    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
612        match self {
613            ClientRequest::ContractOp(op) => match op {
614                ContractRequest::Put {
615                    contract, state, ..
616                } => {
617                    write!(
618                        f,
619                        "ContractRequest::Put for contract `{contract}` with state {state}"
620                    )
621                }
622                ContractRequest::Update { key, .. } => write!(f, "update request for {key}"),
623                ContractRequest::Get {
624                    key,
625                    return_contract_code: contract,
626                    ..
627                } => {
628                    write!(
629                        f,
630                        "ContractRequest::Get for key `{key}` (fetch full contract: {contract})"
631                    )
632                }
633                ContractRequest::Subscribe { key, .. } => {
634                    write!(f, "ContractRequest::Subscribe for `{key}`")
635                }
636            },
637            ClientRequest::DelegateOp(op) => {
638                match op {
639                    DelegateRequest::ApplicationMessages { key, inbound, .. } => {
640                        write!(
641                            f,
642                            "DelegateRequest::ApplicationMessages for `{key}` with {} messages",
643                            inbound.len()
644                        )
645                    }
646                    DelegateRequest::GetSecretRequest {
647                        get_request: GetSecretRequest { key: secret_id, .. },
648                        key,
649                        ..
650                    } => {
651                        write!(f, "DelegateRequest::GetSecretRequest secret_id `{secret_id}` for key `{key}`")
652                    }
653                    DelegateRequest::RegisterDelegate { delegate, .. } => {
654                        write!(
655                            f,
656                            "DelegateRequest::RegisterDelegate for delegate.key()=`{}`",
657                            delegate.key()
658                        )
659                    }
660                    DelegateRequest::UnregisterDelegate(key) => {
661                        write!(f, "DelegateRequest::UnregisterDelegate for key `{key}`")
662                    }
663                }
664            }
665            ClientRequest::Disconnect { .. } => write!(f, "client disconnected"),
666            ClientRequest::Authenticate { .. } => write!(f, "authenticate"),
667            ClientRequest::NodeQueries(query) => write!(f, "node queries: {:?}", query),
668            ClientRequest::Close => write!(f, "close"),
669        }
670    }
671}
672
673/// Deserializes a `DelegateRequest` from a Flatbuffers message.
674impl<'a> TryFromFbs<&FbsDelegateRequest<'a>> for DelegateRequest<'a> {
675    fn try_decode_fbs(request: &FbsDelegateRequest<'a>) -> Result<Self, WsApiError> {
676        let req = {
677            match request.delegate_request_type() {
678                DelegateRequestType::ApplicationMessages => {
679                    let app_msg = request.delegate_request_as_application_messages().unwrap();
680                    let key = DelegateKey::try_decode_fbs(&app_msg.key())?;
681                    let params = Parameters::from(app_msg.params().bytes());
682                    let inbound = app_msg
683                        .inbound()
684                        .iter()
685                        .map(|msg| InboundDelegateMsg::try_decode_fbs(&msg))
686                        .collect::<Result<Vec<_>, _>>()?;
687                    DelegateRequest::ApplicationMessages {
688                        key,
689                        params,
690                        inbound,
691                    }
692                }
693                DelegateRequestType::GetSecretRequestType => {
694                    let get_secret = request
695                        .delegate_request_as_get_secret_request_type()
696                        .unwrap();
697                    let key = DelegateKey::try_decode_fbs(&get_secret.key())?;
698                    let params = Parameters::from(get_secret.params().bytes().to_vec());
699                    let get_request = GetSecretRequest {
700                        key: SecretsId::try_decode_fbs(&get_secret.get_request().key())?,
701                        context: DelegateContext::new(
702                            get_secret.get_request().delegate_context().bytes().to_vec(),
703                        ),
704                        processed: get_secret.get_request().processed(),
705                    };
706                    DelegateRequest::GetSecretRequest {
707                        key,
708                        params,
709                        get_request,
710                    }
711                }
712                DelegateRequestType::RegisterDelegate => {
713                    let register = request.delegate_request_as_register_delegate().unwrap();
714                    let delegate = DelegateContainer::try_decode_fbs(&register.delegate())?;
715                    let cipher =
716                        <[u8; 32]>::try_from(register.cipher().bytes().to_vec().as_slice())
717                            .unwrap();
718                    let nonce =
719                        <[u8; 24]>::try_from(register.nonce().bytes().to_vec().as_slice()).unwrap();
720                    DelegateRequest::RegisterDelegate {
721                        delegate,
722                        cipher,
723                        nonce,
724                    }
725                }
726                DelegateRequestType::UnregisterDelegate => {
727                    let unregister = request.delegate_request_as_unregister_delegate().unwrap();
728                    let key = DelegateKey::try_decode_fbs(&unregister.key())?;
729                    DelegateRequest::UnregisterDelegate(key)
730                }
731                _ => unreachable!(),
732            }
733        };
734
735        Ok(req)
736    }
737}
738
739/// A response to a previous [`ClientRequest`]
740#[derive(Serialize, Deserialize, Debug, Clone)]
741#[non_exhaustive]
742pub enum HostResponse<T = WrappedState> {
743    ContractResponse(#[serde(bound(deserialize = "T: DeserializeOwned"))] ContractResponse<T>),
744    DelegateResponse {
745        key: DelegateKey,
746        values: Vec<OutboundDelegateMsg>,
747    },
748    QueryResponse(QueryResponse),
749    /// A requested action which doesn't require an answer was performed successfully.
750    Ok,
751}
752
753type Peer = String;
754
755#[derive(Serialize, Deserialize, Debug, Clone)]
756pub enum QueryResponse {
757    ConnectedPeers { peers: Vec<(Peer, SocketAddr)> },
758    NetworkDebug(NetworkDebugInfo),
759    NodeDiagnostics(NodeDiagnosticsResponse),
760    ProximityCache(ProximityCacheInfo),
761}
762
763#[derive(Serialize, Deserialize, Debug, Clone)]
764pub struct NetworkDebugInfo {
765    pub subscriptions: Vec<SubscriptionInfo>,
766    pub connected_peers: Vec<(String, SocketAddr)>,
767}
768
769#[derive(Serialize, Deserialize, Debug, Clone)]
770pub struct NodeDiagnosticsResponse {
771    /// Node information
772    pub node_info: Option<NodeInfo>,
773
774    /// Network connectivity information
775    pub network_info: Option<NetworkInfo>,
776
777    /// Contract subscription information
778    pub subscriptions: Vec<SubscriptionInfo>,
779
780    /// Contract states for specific contracts
781    pub contract_states: std::collections::HashMap<ContractKey, ContractState>,
782
783    /// System metrics
784    pub system_metrics: Option<SystemMetrics>,
785
786    /// Information about connected peers with detailed data
787    pub connected_peers_detailed: Vec<ConnectedPeerInfo>,
788}
789
790#[derive(Serialize, Deserialize, Debug, Clone)]
791pub struct NodeInfo {
792    pub peer_id: String,
793    pub is_gateway: bool,
794    pub location: Option<String>,
795    pub listening_address: Option<String>,
796    pub uptime_seconds: u64,
797}
798
799#[derive(Serialize, Deserialize, Debug, Clone)]
800pub struct NetworkInfo {
801    pub connected_peers: Vec<(String, String)>, // (peer_id, address)
802    pub active_connections: usize,
803}
804
805#[derive(Serialize, Deserialize, Debug, Clone)]
806pub struct ContractState {
807    /// Number of nodes subscribed to this contract
808    pub subscribers: u32,
809    /// Peer IDs of nodes that are subscribed to this contract
810    pub subscriber_peer_ids: Vec<String>,
811}
812
813#[derive(Serialize, Deserialize, Debug, Clone)]
814pub struct SystemMetrics {
815    pub active_connections: u32,
816    pub seeding_contracts: u32,
817}
818
819#[derive(Serialize, Deserialize, Debug, Clone)]
820pub struct SubscriptionInfo {
821    pub contract_key: ContractInstanceId,
822    pub client_id: usize,
823}
824
825/// Basic information about a connected peer
826#[derive(Serialize, Deserialize, Debug, Clone)]
827pub struct ConnectedPeerInfo {
828    pub peer_id: String,
829    pub address: String,
830}
831
832#[derive(Serialize, Deserialize, Debug, Clone)]
833pub enum NodeQuery {
834    ConnectedPeers,
835    SubscriptionInfo,
836    NodeDiagnostics {
837        /// Diagnostic configuration specifying what information to collect
838        config: NodeDiagnosticsConfig,
839    },
840    /// Phase 3: Query proximity cache information for update propagation
841    ProximityCacheInfo,
842}
843
844/// Phase 3: Proximity cache information for update propagation
845#[derive(Serialize, Deserialize, Debug, Clone)]
846pub struct ProximityCacheInfo {
847    /// Contracts this node is currently caching
848    pub my_cache: Vec<ContractCacheEntry>,
849    /// What we know about neighbor caches
850    pub neighbor_caches: Vec<NeighborCacheInfo>,
851    /// Proximity propagation statistics
852    pub stats: ProximityStats,
853}
854
855#[derive(Serialize, Deserialize, Debug, Clone)]
856pub struct ContractCacheEntry {
857    /// Full contract key as string
858    pub contract_key: String,
859    /// 32-bit hash for proximity matching
860    pub cache_hash: u32,
861    /// When this contract was cached (Unix timestamp)
862    pub cached_since: u64,
863}
864
865#[derive(Serialize, Deserialize, Debug, Clone)]
866pub struct NeighborCacheInfo {
867    /// Peer identifier
868    pub peer_id: String,
869    /// Contract hashes this neighbor is known to cache
870    pub known_contracts: Vec<u32>,
871    /// Last update received from this neighbor (Unix timestamp)
872    pub last_update: u64,
873    /// Number of updates received from this neighbor
874    pub update_count: u64,
875}
876
877#[derive(Serialize, Deserialize, Debug, Clone)]
878pub struct ProximityStats {
879    /// Number of cache announcements sent
880    pub cache_announces_sent: u64,
881    /// Number of cache announcements received
882    pub cache_announces_received: u64,
883    /// Updates forwarded via proximity (not subscription)
884    pub updates_via_proximity: u64,
885    /// Updates forwarded via subscription
886    pub updates_via_subscription: u64,
887    /// False positives due to hash collisions
888    pub false_positive_forwards: u64,
889    /// Average number of contracts per neighbor
890    pub avg_neighbor_cache_size: f32,
891}
892
893#[derive(Serialize, Deserialize, Debug, Clone)]
894pub struct NodeDiagnosticsConfig {
895    /// Include basic node information (ID, location, uptime, etc.)
896    pub include_node_info: bool,
897
898    /// Include network connectivity information
899    pub include_network_info: bool,
900
901    /// Include contract subscription information
902    pub include_subscriptions: bool,
903
904    /// Include contract states for specific contracts (empty = all contracts)
905    pub contract_keys: Vec<ContractKey>,
906
907    /// Include memory and performance metrics
908    pub include_system_metrics: bool,
909
910    /// Include detailed information about connected peers (vs basic peer list)
911    pub include_detailed_peer_info: bool,
912
913    /// Include peer IDs of subscribers in contract state information
914    pub include_subscriber_peer_ids: bool,
915}
916
917impl NodeDiagnosticsConfig {
918    /// Create a comprehensive diagnostic config for debugging update propagation issues
919    pub fn for_update_propagation_debugging(contract_key: ContractKey) -> Self {
920        Self {
921            include_node_info: true,
922            include_network_info: true,
923            include_subscriptions: true,
924            contract_keys: vec![contract_key],
925            include_system_metrics: true,
926            include_detailed_peer_info: true,
927            include_subscriber_peer_ids: true,
928        }
929    }
930
931    /// Create a lightweight diagnostic config for basic node status
932    pub fn basic_status() -> Self {
933        Self {
934            include_node_info: true,
935            include_network_info: true,
936            include_subscriptions: false,
937            contract_keys: vec![],
938            include_system_metrics: false,
939            include_detailed_peer_info: false,
940            include_subscriber_peer_ids: false,
941        }
942    }
943
944    /// Create a full diagnostic config (all information)
945    pub fn full() -> Self {
946        Self {
947            include_node_info: true,
948            include_network_info: true,
949            include_subscriptions: true,
950            contract_keys: vec![], // empty = all contracts
951            include_system_metrics: true,
952            include_detailed_peer_info: true,
953            include_subscriber_peer_ids: true,
954        }
955    }
956}
957
958impl HostResponse {
959    pub fn unwrap_put(self) -> ContractKey {
960        if let Self::ContractResponse(ContractResponse::PutResponse { key }) = self {
961            key
962        } else {
963            panic!("called `HostResponse::unwrap_put()` on other than `PutResponse` value")
964        }
965    }
966
967    pub fn unwrap_get(self) -> (WrappedState, Option<ContractContainer>) {
968        if let Self::ContractResponse(ContractResponse::GetResponse {
969            contract, state, ..
970        }) = self
971        {
972            (state, contract)
973        } else {
974            panic!("called `HostResponse::unwrap_put()` on other than `PutResponse` value")
975        }
976    }
977
978    pub fn into_fbs_bytes(self) -> Result<Vec<u8>, Box<ClientError>> {
979        let mut builder = flatbuffers::FlatBufferBuilder::new();
980        match self {
981            HostResponse::ContractResponse(res) => match res {
982                ContractResponse::PutResponse { key } => {
983                    let instance_data = builder.create_vector(key.as_bytes());
984                    let instance_offset = FbsContractInstanceId::create(
985                        &mut builder,
986                        &ContractInstanceIdArgs {
987                            data: Some(instance_data),
988                        },
989                    );
990
991                    let code = Some(builder.create_vector(&key.code_hash().0));
992                    let key_offset = FbsContractKey::create(
993                        &mut builder,
994                        &ContractKeyArgs {
995                            instance: Some(instance_offset),
996                            code,
997                        },
998                    );
999
1000                    let put_offset = FbsPutResponse::create(
1001                        &mut builder,
1002                        &PutResponseArgs {
1003                            key: Some(key_offset),
1004                        },
1005                    );
1006
1007                    let contract_response_offset = FbsContractResponse::create(
1008                        &mut builder,
1009                        &ContractResponseArgs {
1010                            contract_response: Some(put_offset.as_union_value()),
1011                            contract_response_type: ContractResponseType::PutResponse,
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::UpdateResponse { key, summary } => {
1027                    let instance_data = builder.create_vector(key.as_bytes());
1028                    let instance_offset = FbsContractInstanceId::create(
1029                        &mut builder,
1030                        &ContractInstanceIdArgs {
1031                            data: Some(instance_data),
1032                        },
1033                    );
1034
1035                    let code = Some(builder.create_vector(&key.code_hash().0));
1036
1037                    let key_offset = FbsContractKey::create(
1038                        &mut builder,
1039                        &ContractKeyArgs {
1040                            instance: Some(instance_offset),
1041                            code,
1042                        },
1043                    );
1044
1045                    let summary_data = builder.create_vector(&summary.into_bytes());
1046
1047                    let update_response_offset = FbsUpdateResponse::create(
1048                        &mut builder,
1049                        &UpdateResponseArgs {
1050                            key: Some(key_offset),
1051                            summary: Some(summary_data),
1052                        },
1053                    );
1054
1055                    let contract_response_offset = FbsContractResponse::create(
1056                        &mut builder,
1057                        &ContractResponseArgs {
1058                            contract_response: Some(update_response_offset.as_union_value()),
1059                            contract_response_type: ContractResponseType::UpdateResponse,
1060                        },
1061                    );
1062
1063                    let response_offset = FbsHostResponse::create(
1064                        &mut builder,
1065                        &HostResponseArgs {
1066                            response: Some(contract_response_offset.as_union_value()),
1067                            response_type: HostResponseType::ContractResponse,
1068                        },
1069                    );
1070
1071                    finish_host_response_buffer(&mut builder, response_offset);
1072                    Ok(builder.finished_data().to_vec())
1073                }
1074                ContractResponse::GetResponse {
1075                    key,
1076                    contract: contract_container,
1077                    state,
1078                } => {
1079                    let instance_data = builder.create_vector(key.as_bytes());
1080                    let instance_offset = FbsContractInstanceId::create(
1081                        &mut builder,
1082                        &ContractInstanceIdArgs {
1083                            data: Some(instance_data),
1084                        },
1085                    );
1086
1087                    let code = Some(builder.create_vector(&key.code_hash().0));
1088                    let key_offset = FbsContractKey::create(
1089                        &mut builder,
1090                        &ContractKeyArgs {
1091                            instance: Some(instance_offset),
1092                            code,
1093                        },
1094                    );
1095
1096                    let container_offset = if let Some(contract) = contract_container {
1097                        let data = builder.create_vector(contract.key().as_bytes());
1098
1099                        let instance_offset = FbsContractInstanceId::create(
1100                            &mut builder,
1101                            &ContractInstanceIdArgs { data: Some(data) },
1102                        );
1103
1104                        let code = Some(builder.create_vector(&contract.key().code_hash().0));
1105                        let contract_key_offset = FbsContractKey::create(
1106                            &mut builder,
1107                            &ContractKeyArgs {
1108                                instance: Some(instance_offset),
1109                                code,
1110                            },
1111                        );
1112
1113                        let contract_data =
1114                            builder.create_vector(contract.clone().unwrap_v1().data.data());
1115                        let contract_code_hash =
1116                            builder.create_vector(&contract.clone().unwrap_v1().data.hash().0);
1117
1118                        let contract_code_offset = ContractCode::create(
1119                            &mut builder,
1120                            &ContractCodeArgs {
1121                                data: Some(contract_data),
1122                                code_hash: Some(contract_code_hash),
1123                            },
1124                        );
1125
1126                        let contract_params =
1127                            builder.create_vector(&contract.clone().params().into_bytes());
1128
1129                        let contract_offset = match contract {
1130                            Wasm(V1(..)) => WasmContractV1::create(
1131                                &mut builder,
1132                                &WasmContractV1Args {
1133                                    key: Some(contract_key_offset),
1134                                    data: Some(contract_code_offset),
1135                                    parameters: Some(contract_params),
1136                                },
1137                            ),
1138                        };
1139
1140                        Some(FbsContractContainer::create(
1141                            &mut builder,
1142                            &ContractContainerArgs {
1143                                contract_type: ContractType::WasmContractV1,
1144                                contract: Some(contract_offset.as_union_value()),
1145                            },
1146                        ))
1147                    } else {
1148                        None
1149                    };
1150
1151                    let state_data = builder.create_vector(&state);
1152
1153                    let get_offset = FbsGetResponse::create(
1154                        &mut builder,
1155                        &GetResponseArgs {
1156                            key: Some(key_offset),
1157                            contract: container_offset,
1158                            state: Some(state_data),
1159                        },
1160                    );
1161
1162                    let contract_response_offset = FbsContractResponse::create(
1163                        &mut builder,
1164                        &ContractResponseArgs {
1165                            contract_response_type: ContractResponseType::GetResponse,
1166                            contract_response: Some(get_offset.as_union_value()),
1167                        },
1168                    );
1169
1170                    let response_offset = FbsHostResponse::create(
1171                        &mut builder,
1172                        &HostResponseArgs {
1173                            response: Some(contract_response_offset.as_union_value()),
1174                            response_type: HostResponseType::ContractResponse,
1175                        },
1176                    );
1177
1178                    finish_host_response_buffer(&mut builder, response_offset);
1179                    Ok(builder.finished_data().to_vec())
1180                }
1181                ContractResponse::UpdateNotification { key, update } => {
1182                    let instance_data = builder.create_vector(key.as_bytes());
1183                    let instance_offset = FbsContractInstanceId::create(
1184                        &mut builder,
1185                        &ContractInstanceIdArgs {
1186                            data: Some(instance_data),
1187                        },
1188                    );
1189
1190                    let code = Some(builder.create_vector(&key.code_hash().0));
1191                    let key_offset = FbsContractKey::create(
1192                        &mut builder,
1193                        &ContractKeyArgs {
1194                            instance: Some(instance_offset),
1195                            code,
1196                        },
1197                    );
1198
1199                    let update_data = match update {
1200                        State(state) => {
1201                            let state_data = builder.create_vector(&state.into_bytes());
1202                            let state_update_offset = StateUpdate::create(
1203                                &mut builder,
1204                                &StateUpdateArgs {
1205                                    state: Some(state_data),
1206                                },
1207                            );
1208                            FbsUpdateData::create(
1209                                &mut builder,
1210                                &UpdateDataArgs {
1211                                    update_data_type: UpdateDataType::StateUpdate,
1212                                    update_data: Some(state_update_offset.as_union_value()),
1213                                },
1214                            )
1215                        }
1216                        Delta(delta) => {
1217                            let delta_data = builder.create_vector(&delta.into_bytes());
1218                            let update_offset = DeltaUpdate::create(
1219                                &mut builder,
1220                                &DeltaUpdateArgs {
1221                                    delta: Some(delta_data),
1222                                },
1223                            );
1224                            FbsUpdateData::create(
1225                                &mut builder,
1226                                &UpdateDataArgs {
1227                                    update_data_type: UpdateDataType::DeltaUpdate,
1228                                    update_data: Some(update_offset.as_union_value()),
1229                                },
1230                            )
1231                        }
1232                        StateAndDelta { state, delta } => {
1233                            let state_data = builder.create_vector(&state.into_bytes());
1234                            let delta_data = builder.create_vector(&delta.into_bytes());
1235
1236                            let update_offset = StateAndDeltaUpdate::create(
1237                                &mut builder,
1238                                &StateAndDeltaUpdateArgs {
1239                                    state: Some(state_data),
1240                                    delta: Some(delta_data),
1241                                },
1242                            );
1243
1244                            FbsUpdateData::create(
1245                                &mut builder,
1246                                &UpdateDataArgs {
1247                                    update_data_type: UpdateDataType::StateAndDeltaUpdate,
1248                                    update_data: Some(update_offset.as_union_value()),
1249                                },
1250                            )
1251                        }
1252                        RelatedState { related_to, state } => {
1253                            let state_data = builder.create_vector(&state.into_bytes());
1254                            let instance_data =
1255                                builder.create_vector(related_to.encode().as_bytes());
1256
1257                            let instance_offset = FbsContractInstanceId::create(
1258                                &mut builder,
1259                                &ContractInstanceIdArgs {
1260                                    data: Some(instance_data),
1261                                },
1262                            );
1263
1264                            let update_offset = RelatedStateUpdate::create(
1265                                &mut builder,
1266                                &RelatedStateUpdateArgs {
1267                                    related_to: Some(instance_offset),
1268                                    state: Some(state_data),
1269                                },
1270                            );
1271
1272                            FbsUpdateData::create(
1273                                &mut builder,
1274                                &UpdateDataArgs {
1275                                    update_data_type: UpdateDataType::RelatedStateUpdate,
1276                                    update_data: Some(update_offset.as_union_value()),
1277                                },
1278                            )
1279                        }
1280                        RelatedDelta { related_to, delta } => {
1281                            let instance_data =
1282                                builder.create_vector(related_to.encode().as_bytes());
1283                            let delta_data = builder.create_vector(&delta.into_bytes());
1284
1285                            let instance_offset = FbsContractInstanceId::create(
1286                                &mut builder,
1287                                &ContractInstanceIdArgs {
1288                                    data: Some(instance_data),
1289                                },
1290                            );
1291
1292                            let update_offset = RelatedDeltaUpdate::create(
1293                                &mut builder,
1294                                &RelatedDeltaUpdateArgs {
1295                                    related_to: Some(instance_offset),
1296                                    delta: Some(delta_data),
1297                                },
1298                            );
1299
1300                            FbsUpdateData::create(
1301                                &mut builder,
1302                                &UpdateDataArgs {
1303                                    update_data_type: UpdateDataType::RelatedDeltaUpdate,
1304                                    update_data: Some(update_offset.as_union_value()),
1305                                },
1306                            )
1307                        }
1308                        RelatedStateAndDelta {
1309                            related_to,
1310                            state,
1311                            delta,
1312                        } => {
1313                            let instance_data =
1314                                builder.create_vector(related_to.encode().as_bytes());
1315                            let state_data = builder.create_vector(&state.into_bytes());
1316                            let delta_data = builder.create_vector(&delta.into_bytes());
1317
1318                            let instance_offset = FbsContractInstanceId::create(
1319                                &mut builder,
1320                                &ContractInstanceIdArgs {
1321                                    data: Some(instance_data),
1322                                },
1323                            );
1324
1325                            let update_offset = RelatedStateAndDeltaUpdate::create(
1326                                &mut builder,
1327                                &RelatedStateAndDeltaUpdateArgs {
1328                                    related_to: Some(instance_offset),
1329                                    state: Some(state_data),
1330                                    delta: Some(delta_data),
1331                                },
1332                            );
1333
1334                            FbsUpdateData::create(
1335                                &mut builder,
1336                                &UpdateDataArgs {
1337                                    update_data_type: UpdateDataType::RelatedStateAndDeltaUpdate,
1338                                    update_data: Some(update_offset.as_union_value()),
1339                                },
1340                            )
1341                        }
1342                    };
1343
1344                    let update_notification_offset = FbsUpdateNotification::create(
1345                        &mut builder,
1346                        &UpdateNotificationArgs {
1347                            key: Some(key_offset),
1348                            update: Some(update_data),
1349                        },
1350                    );
1351
1352                    let put_response_offset = FbsContractResponse::create(
1353                        &mut builder,
1354                        &ContractResponseArgs {
1355                            contract_response_type: ContractResponseType::UpdateNotification,
1356                            contract_response: Some(update_notification_offset.as_union_value()),
1357                        },
1358                    );
1359
1360                    let host_response_offset = FbsHostResponse::create(
1361                        &mut builder,
1362                        &HostResponseArgs {
1363                            response_type: HostResponseType::ContractResponse,
1364                            response: Some(put_response_offset.as_union_value()),
1365                        },
1366                    );
1367
1368                    finish_host_response_buffer(&mut builder, host_response_offset);
1369                    Ok(builder.finished_data().to_vec())
1370                }
1371                ContractResponse::SubscribeResponse { .. } => todo!(),
1372                ContractResponse::NotFound { instance_id } => {
1373                    let instance_data = builder.create_vector(instance_id.as_bytes());
1374                    let instance_offset = FbsContractInstanceId::create(
1375                        &mut builder,
1376                        &ContractInstanceIdArgs {
1377                            data: Some(instance_data),
1378                        },
1379                    );
1380
1381                    let not_found_offset = FbsNotFound::create(
1382                        &mut builder,
1383                        &NotFoundArgs {
1384                            instance_id: Some(instance_offset),
1385                        },
1386                    );
1387
1388                    let contract_response_offset = FbsContractResponse::create(
1389                        &mut builder,
1390                        &ContractResponseArgs {
1391                            contract_response_type: ContractResponseType::NotFound,
1392                            contract_response: Some(not_found_offset.as_union_value()),
1393                        },
1394                    );
1395
1396                    let response_offset = FbsHostResponse::create(
1397                        &mut builder,
1398                        &HostResponseArgs {
1399                            response: Some(contract_response_offset.as_union_value()),
1400                            response_type: HostResponseType::ContractResponse,
1401                        },
1402                    );
1403
1404                    finish_host_response_buffer(&mut builder, response_offset);
1405                    Ok(builder.finished_data().to_vec())
1406                }
1407            },
1408            HostResponse::DelegateResponse { key, values } => {
1409                let key_data = builder.create_vector(key.bytes());
1410                let code_hash_data = builder.create_vector(&key.code_hash().0);
1411                let key_offset = FbsDelegateKey::create(
1412                    &mut builder,
1413                    &DelegateKeyArgs {
1414                        key: Some(key_data),
1415                        code_hash: Some(code_hash_data),
1416                    },
1417                );
1418                let mut messages: Vec<WIPOffset<FbsOutboundDelegateMsg>> = Vec::new();
1419                values.iter().for_each(|msg| match msg {
1420                    OutboundDelegateMsg::ApplicationMessage(app) => {
1421                        let instance_data = builder.create_vector(key.bytes());
1422                        let instance_offset = FbsContractInstanceId::create(
1423                            &mut builder,
1424                            &ContractInstanceIdArgs {
1425                                data: Some(instance_data),
1426                            },
1427                        );
1428                        let payload_data = builder.create_vector(&app.payload);
1429                        let delegate_context_data = builder.create_vector(app.context.as_ref());
1430                        let app_offset = FbsApplicationMessage::create(
1431                            &mut builder,
1432                            &ApplicationMessageArgs {
1433                                app: Some(instance_offset),
1434                                payload: Some(payload_data),
1435                                context: Some(delegate_context_data),
1436                                processed: app.processed,
1437                            },
1438                        );
1439                        let msg = FbsOutboundDelegateMsg::create(
1440                            &mut builder,
1441                            &OutboundDelegateMsgArgs {
1442                                inbound_type: OutboundDelegateMsgType::common_ApplicationMessage,
1443                                inbound: Some(app_offset.as_union_value()),
1444                            },
1445                        );
1446                        messages.push(msg);
1447                    }
1448                    OutboundDelegateMsg::RequestUserInput(input) => {
1449                        let message_data = builder.create_vector(input.message.bytes());
1450                        let mut responses: Vec<WIPOffset<FbsClientResponse>> = Vec::new();
1451                        input.responses.iter().for_each(|resp| {
1452                            let response_data = builder.create_vector(resp.bytes());
1453                            let response = FbsClientResponse::create(
1454                                &mut builder,
1455                                &ClientResponseArgs {
1456                                    data: Some(response_data),
1457                                },
1458                            );
1459                            responses.push(response)
1460                        });
1461                        let responses_offset = builder.create_vector(&responses);
1462                        let input_offset = FbsRequestUserInput::create(
1463                            &mut builder,
1464                            &RequestUserInputArgs {
1465                                request_id: input.request_id,
1466                                message: Some(message_data),
1467                                responses: Some(responses_offset),
1468                            },
1469                        );
1470                        let msg = FbsOutboundDelegateMsg::create(
1471                            &mut builder,
1472                            &OutboundDelegateMsgArgs {
1473                                inbound_type: OutboundDelegateMsgType::RequestUserInput,
1474                                inbound: Some(input_offset.as_union_value()),
1475                            },
1476                        );
1477                        messages.push(msg);
1478                    }
1479                    OutboundDelegateMsg::ContextUpdated(context) => {
1480                        let context_data = builder.create_vector(context.as_ref());
1481                        let context_offset = FbsContextUpdated::create(
1482                            &mut builder,
1483                            &ContextUpdatedArgs {
1484                                context: Some(context_data),
1485                            },
1486                        );
1487                        let msg = FbsOutboundDelegateMsg::create(
1488                            &mut builder,
1489                            &OutboundDelegateMsgArgs {
1490                                inbound_type: OutboundDelegateMsgType::ContextUpdated,
1491                                inbound: Some(context_offset.as_union_value()),
1492                            },
1493                        );
1494                        messages.push(msg);
1495                    }
1496                    OutboundDelegateMsg::GetSecretRequest(request) => {
1497                        let secret_key_data = builder.create_vector(request.key.key());
1498                        let secret_hash_data = builder.create_vector(request.key.hash());
1499                        let secret_id_offset = FbsSecretsId::create(
1500                            &mut builder,
1501                            &SecretsIdArgs {
1502                                key: Some(secret_key_data),
1503                                hash: Some(secret_hash_data),
1504                            },
1505                        );
1506
1507                        let delegate_context_data = builder.create_vector(request.context.as_ref());
1508                        let request_offset = FbsGetSecretRequest::create(
1509                            &mut builder,
1510                            &GetSecretRequestArgs {
1511                                key: Some(secret_id_offset),
1512                                delegate_context: Some(delegate_context_data),
1513                                processed: request.processed,
1514                            },
1515                        );
1516                        let msg = FbsOutboundDelegateMsg::create(
1517                            &mut builder,
1518                            &OutboundDelegateMsgArgs {
1519                                inbound_type: OutboundDelegateMsgType::common_GetSecretRequest,
1520                                inbound: Some(request_offset.as_union_value()),
1521                            },
1522                        );
1523                        messages.push(msg);
1524                    }
1525                    OutboundDelegateMsg::SetSecretRequest(request) => {
1526                        let secret_key_data = builder.create_vector(request.key.key());
1527                        let secret_hash_data = builder.create_vector(request.key.hash());
1528                        let secret_id_offset = FbsSecretsId::create(
1529                            &mut builder,
1530                            &SecretsIdArgs {
1531                                key: Some(secret_key_data),
1532                                hash: Some(secret_hash_data),
1533                            },
1534                        );
1535
1536                        let value_data = request
1537                            .value
1538                            .clone()
1539                            .map(|value| builder.create_vector(value.as_slice()));
1540                        let request_offset = FbsSetSecretRequest::create(
1541                            &mut builder,
1542                            &SetSecretRequestArgs {
1543                                key: Some(secret_id_offset),
1544                                value: value_data,
1545                            },
1546                        );
1547                        let msg = FbsOutboundDelegateMsg::create(
1548                            &mut builder,
1549                            &OutboundDelegateMsgArgs {
1550                                inbound_type: OutboundDelegateMsgType::SetSecretRequest,
1551                                inbound: Some(request_offset.as_union_value()),
1552                            },
1553                        );
1554                        messages.push(msg);
1555                    }
1556                    OutboundDelegateMsg::GetSecretResponse(response) => {
1557                        let secret_key_data = builder.create_vector(response.key.key());
1558                        let secret_hash_data = builder.create_vector(response.key.hash());
1559                        let secret_id_offset = FbsSecretsId::create(
1560                            &mut builder,
1561                            &SecretsIdArgs {
1562                                key: Some(secret_key_data),
1563                                hash: Some(secret_hash_data),
1564                            },
1565                        );
1566
1567                        let value_data = response
1568                            .value
1569                            .clone()
1570                            .map(|value| builder.create_vector(value.as_slice()));
1571
1572                        let delegate_context_data =
1573                            builder.create_vector(response.context.as_ref());
1574                        let response_offset = FbsGetSecretResponse::create(
1575                            &mut builder,
1576                            &GetSecretResponseArgs {
1577                                key: Some(secret_id_offset),
1578                                value: value_data,
1579                                delegate_context: Some(delegate_context_data),
1580                            },
1581                        );
1582                        let msg = FbsOutboundDelegateMsg::create(
1583                            &mut builder,
1584                            &OutboundDelegateMsgArgs {
1585                                inbound_type: OutboundDelegateMsgType::common_GetSecretResponse,
1586                                inbound: Some(response_offset.as_union_value()),
1587                            },
1588                        );
1589                        messages.push(msg);
1590                    }
1591                    OutboundDelegateMsg::GetContractRequest(_) => {
1592                        // GetContractRequest should be handled by the executor and never
1593                        // reach client serialization. If we get here, it's a bug.
1594                        tracing::error!(
1595                            "GetContractRequest reached client serialization - this is a bug"
1596                        );
1597                    }
1598                });
1599                let messages_offset = builder.create_vector(&messages);
1600                let delegate_response_offset = FbsDelegateResponse::create(
1601                    &mut builder,
1602                    &DelegateResponseArgs {
1603                        key: Some(key_offset),
1604                        values: Some(messages_offset),
1605                    },
1606                );
1607                let host_response_offset = FbsHostResponse::create(
1608                    &mut builder,
1609                    &HostResponseArgs {
1610                        response_type: HostResponseType::DelegateResponse,
1611                        response: Some(delegate_response_offset.as_union_value()),
1612                    },
1613                );
1614                finish_host_response_buffer(&mut builder, host_response_offset);
1615                Ok(builder.finished_data().to_vec())
1616            }
1617            HostResponse::Ok => {
1618                let ok_offset = FbsOk::create(&mut builder, &OkArgs { msg: None });
1619                let host_response_offset = FbsHostResponse::create(
1620                    &mut builder,
1621                    &HostResponseArgs {
1622                        response_type: HostResponseType::Ok,
1623                        response: Some(ok_offset.as_union_value()),
1624                    },
1625                );
1626                finish_host_response_buffer(&mut builder, host_response_offset);
1627                Ok(builder.finished_data().to_vec())
1628            }
1629            HostResponse::QueryResponse(_) => unimplemented!(),
1630        }
1631    }
1632}
1633
1634impl Display for HostResponse {
1635    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1636        match self {
1637            HostResponse::ContractResponse(res) => match res {
1638                ContractResponse::PutResponse { key } => {
1639                    f.write_fmt(format_args!("put response for `{key}`"))
1640                }
1641                ContractResponse::UpdateResponse { key, .. } => {
1642                    f.write_fmt(format_args!("update response for `{key}`"))
1643                }
1644                ContractResponse::GetResponse { key, .. } => {
1645                    f.write_fmt(format_args!("get response for `{key}`"))
1646                }
1647                ContractResponse::UpdateNotification { key, .. } => {
1648                    f.write_fmt(format_args!("update notification for `{key}`"))
1649                }
1650                ContractResponse::SubscribeResponse { key, .. } => {
1651                    f.write_fmt(format_args!("subscribe response for `{key}`"))
1652                }
1653                ContractResponse::NotFound { instance_id } => {
1654                    f.write_fmt(format_args!("not found for `{instance_id}`"))
1655                }
1656            },
1657            HostResponse::DelegateResponse { .. } => write!(f, "delegate responses"),
1658            HostResponse::Ok => write!(f, "ok response"),
1659            HostResponse::QueryResponse(_) => write!(f, "query response"),
1660        }
1661    }
1662}
1663
1664#[derive(Clone, Serialize, Deserialize, Debug)]
1665#[non_exhaustive]
1666pub enum ContractResponse<T = WrappedState> {
1667    GetResponse {
1668        key: ContractKey,
1669        contract: Option<ContractContainer>,
1670        #[serde(bound(deserialize = "T: DeserializeOwned"))]
1671        state: T,
1672    },
1673    PutResponse {
1674        key: ContractKey,
1675    },
1676    /// Message sent when there is an update to a subscribed contract.
1677    UpdateNotification {
1678        key: ContractKey,
1679        #[serde(deserialize_with = "UpdateData::deser_update_data")]
1680        update: UpdateData<'static>,
1681    },
1682    /// Successful update
1683    UpdateResponse {
1684        key: ContractKey,
1685        #[serde(deserialize_with = "StateSummary::deser_state_summary")]
1686        summary: StateSummary<'static>,
1687    },
1688    SubscribeResponse {
1689        key: ContractKey,
1690        subscribed: bool,
1691    },
1692    /// Contract was not found after exhaustive search.
1693    /// This is an explicit response that distinguishes "contract doesn't exist"
1694    /// from other failure modes like timeouts or network errors.
1695    NotFound {
1696        /// The instance ID that was searched for.
1697        instance_id: ContractInstanceId,
1698    },
1699}
1700
1701impl<T> From<ContractResponse<T>> for HostResponse<T> {
1702    fn from(value: ContractResponse<T>) -> HostResponse<T> {
1703        HostResponse::ContractResponse(value)
1704    }
1705}
1706
1707#[cfg(test)]
1708mod client_request_test {
1709    use crate::client_api::{ContractRequest, TryFromFbs};
1710    use crate::contract_interface::UpdateData;
1711    use crate::generated::client_request::root_as_client_request;
1712
1713    const EXPECTED_ENCODED_CONTRACT_ID: &str = "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9";
1714
1715    #[test]
1716    fn test_build_contract_put_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1717        let put_req_op = vec![
1718            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,
1719            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,
1720            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,
1721            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,
1722            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,
1723            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,
1724            85, 111, 11, 171, 40, 85, 240, 177, 207, 81, 106, 157, 173, 90, 234, 2, 250, 253, 75,
1725            210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17, 108, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6,
1726            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,
1727            3, 4, 5, 6, 7, 8, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
1728        ];
1729        let request = if let Ok(client_request) = root_as_client_request(&put_req_op) {
1730            let contract_request = client_request.client_request_as_contract_request().unwrap();
1731            ContractRequest::try_decode_fbs(&contract_request)?
1732        } else {
1733            panic!("failed to decode client request")
1734        };
1735
1736        match request {
1737            ContractRequest::Put {
1738                contract,
1739                state,
1740                related_contracts: _,
1741                subscribe,
1742                blocking_subscribe,
1743            } => {
1744                assert_eq!(
1745                    contract.to_string(),
1746                    "WasmContainer([api=0.0.1](D8fdVLbRyMLw5mZtPRpWMFcrXGN2z8Nq8UGcLGPFBg2W))"
1747                );
1748                assert_eq!(contract.unwrap_v1().data.data(), &[1, 2, 3, 4, 5, 6, 7, 8]);
1749                assert_eq!(state.to_vec(), &[1, 2, 3, 4, 5, 6, 7, 8]);
1750                assert!(!subscribe);
1751                assert!(!blocking_subscribe);
1752            }
1753            _ => panic!("wrong contract request type"),
1754        }
1755
1756        Ok(())
1757    }
1758
1759    #[test]
1760    fn test_build_contract_get_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1761        let get_req_op = vec![
1762            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,
1763            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,
1764            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,
1765            4, 0, 0, 0, 32, 0, 0, 0, 85, 111, 11, 171, 40, 85, 240, 177, 207, 81, 106, 157, 173,
1766            90, 234, 2, 250, 253, 75, 210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17, 108,
1767        ];
1768        let request = if let Ok(client_request) = root_as_client_request(&get_req_op) {
1769            let contract_request = client_request.client_request_as_contract_request().unwrap();
1770            ContractRequest::try_decode_fbs(&contract_request)?
1771        } else {
1772            panic!("failed to decode client request")
1773        };
1774
1775        match request {
1776            ContractRequest::Get {
1777                key,
1778                return_contract_code: fetch_contract,
1779                subscribe,
1780                blocking_subscribe,
1781            } => {
1782                assert_eq!(key.encode(), EXPECTED_ENCODED_CONTRACT_ID);
1783                assert!(!fetch_contract);
1784                assert!(!subscribe);
1785                assert!(!blocking_subscribe);
1786            }
1787            _ => panic!("wrong contract request type"),
1788        }
1789
1790        Ok(())
1791    }
1792
1793    #[test]
1794    fn test_build_contract_update_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1795        let update_op = vec![
1796            4, 0, 0, 0, 220, 255, 255, 255, 8, 0, 0, 0, 0, 0, 0, 1, 232, 255, 255, 255, 8, 0, 0, 0,
1797            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,
1798            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,
1799            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,
1800            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,
1801            85, 240, 177, 207, 81, 106, 157, 173, 90, 234, 2, 250, 253, 75, 210, 62, 7, 6, 34, 75,
1802            26, 229, 230, 107, 167, 17, 108,
1803        ];
1804        let request = if let Ok(client_request) = root_as_client_request(&update_op) {
1805            let contract_request = client_request.client_request_as_contract_request().unwrap();
1806            ContractRequest::try_decode_fbs(&contract_request)?
1807        } else {
1808            panic!("failed to decode client request")
1809        };
1810
1811        match request {
1812            ContractRequest::Update { key, data } => {
1813                assert_eq!(
1814                    key.encoded_contract_id(),
1815                    "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9"
1816                );
1817                match data {
1818                    UpdateData::Delta(delta) => {
1819                        assert_eq!(delta.to_vec(), &[1, 2, 3, 4, 5, 6, 7, 8])
1820                    }
1821                    _ => panic!("wrong update data type"),
1822                }
1823            }
1824            _ => panic!("wrong contract request type"),
1825        }
1826
1827        Ok(())
1828    }
1829}