Skip to main content

freenet_stdlib/client_api/
client_events.rs

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