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    ProximityCache(ProximityCacheInfo),
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 seeding_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    /// Phase 3: Query proximity cache information for update propagation
866    ProximityCacheInfo,
867}
868
869/// Phase 3: Proximity cache information for update propagation
870#[derive(Serialize, Deserialize, Debug, Clone)]
871pub struct ProximityCacheInfo {
872    /// Contracts this node is currently caching
873    pub my_cache: Vec<ContractCacheEntry>,
874    /// What we know about neighbor caches
875    pub neighbor_caches: Vec<NeighborCacheInfo>,
876    /// Proximity propagation statistics
877    pub stats: ProximityStats,
878}
879
880#[derive(Serialize, Deserialize, Debug, Clone)]
881pub struct ContractCacheEntry {
882    /// Full contract key as string
883    pub contract_key: String,
884    /// 32-bit hash for proximity matching
885    pub cache_hash: u32,
886    /// When this contract was cached (Unix timestamp)
887    pub cached_since: u64,
888}
889
890#[derive(Serialize, Deserialize, Debug, Clone)]
891pub struct NeighborCacheInfo {
892    /// Peer identifier
893    pub peer_id: String,
894    /// Contract hashes this neighbor is known to cache
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 ProximityStats {
904    /// Number of cache announcements sent
905    pub cache_announces_sent: u64,
906    /// Number of cache announcements received
907    pub cache_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_cache_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 { .. } => todo!(),
1397                ContractResponse::NotFound { instance_id } => {
1398                    let instance_data = builder.create_vector(instance_id.as_bytes());
1399                    let instance_offset = FbsContractInstanceId::create(
1400                        &mut builder,
1401                        &ContractInstanceIdArgs {
1402                            data: Some(instance_data),
1403                        },
1404                    );
1405
1406                    let not_found_offset = FbsNotFound::create(
1407                        &mut builder,
1408                        &NotFoundArgs {
1409                            instance_id: Some(instance_offset),
1410                        },
1411                    );
1412
1413                    let contract_response_offset = FbsContractResponse::create(
1414                        &mut builder,
1415                        &ContractResponseArgs {
1416                            contract_response_type: ContractResponseType::NotFound,
1417                            contract_response: Some(not_found_offset.as_union_value()),
1418                        },
1419                    );
1420
1421                    let response_offset = FbsHostResponse::create(
1422                        &mut builder,
1423                        &HostResponseArgs {
1424                            response: Some(contract_response_offset.as_union_value()),
1425                            response_type: HostResponseType::ContractResponse,
1426                        },
1427                    );
1428
1429                    finish_host_response_buffer(&mut builder, response_offset);
1430                    Ok(builder.finished_data().to_vec())
1431                }
1432            },
1433            HostResponse::DelegateResponse { key, values } => {
1434                let key_data = builder.create_vector(key.bytes());
1435                let code_hash_data = builder.create_vector(&key.code_hash().0);
1436                let key_offset = FbsDelegateKey::create(
1437                    &mut builder,
1438                    &DelegateKeyArgs {
1439                        key: Some(key_data),
1440                        code_hash: Some(code_hash_data),
1441                    },
1442                );
1443                let mut messages: Vec<WIPOffset<FbsOutboundDelegateMsg>> = Vec::new();
1444                values.iter().for_each(|msg| match msg {
1445                    OutboundDelegateMsg::ApplicationMessage(app) => {
1446                        let instance_data = builder.create_vector(key.bytes());
1447                        let instance_offset = FbsContractInstanceId::create(
1448                            &mut builder,
1449                            &ContractInstanceIdArgs {
1450                                data: Some(instance_data),
1451                            },
1452                        );
1453                        let payload_data = builder.create_vector(&app.payload);
1454                        let delegate_context_data = builder.create_vector(app.context.as_ref());
1455                        let app_offset = FbsApplicationMessage::create(
1456                            &mut builder,
1457                            &ApplicationMessageArgs {
1458                                app: Some(instance_offset),
1459                                payload: Some(payload_data),
1460                                context: Some(delegate_context_data),
1461                                processed: app.processed,
1462                            },
1463                        );
1464                        let msg = FbsOutboundDelegateMsg::create(
1465                            &mut builder,
1466                            &OutboundDelegateMsgArgs {
1467                                inbound_type: OutboundDelegateMsgType::common_ApplicationMessage,
1468                                inbound: Some(app_offset.as_union_value()),
1469                            },
1470                        );
1471                        messages.push(msg);
1472                    }
1473                    OutboundDelegateMsg::RequestUserInput(input) => {
1474                        let message_data = builder.create_vector(input.message.bytes());
1475                        let mut responses: Vec<WIPOffset<FbsClientResponse>> = Vec::new();
1476                        input.responses.iter().for_each(|resp| {
1477                            let response_data = builder.create_vector(resp.bytes());
1478                            let response = FbsClientResponse::create(
1479                                &mut builder,
1480                                &ClientResponseArgs {
1481                                    data: Some(response_data),
1482                                },
1483                            );
1484                            responses.push(response)
1485                        });
1486                        let responses_offset = builder.create_vector(&responses);
1487                        let input_offset = FbsRequestUserInput::create(
1488                            &mut builder,
1489                            &RequestUserInputArgs {
1490                                request_id: input.request_id,
1491                                message: Some(message_data),
1492                                responses: Some(responses_offset),
1493                            },
1494                        );
1495                        let msg = FbsOutboundDelegateMsg::create(
1496                            &mut builder,
1497                            &OutboundDelegateMsgArgs {
1498                                inbound_type: OutboundDelegateMsgType::RequestUserInput,
1499                                inbound: Some(input_offset.as_union_value()),
1500                            },
1501                        );
1502                        messages.push(msg);
1503                    }
1504                    OutboundDelegateMsg::ContextUpdated(context) => {
1505                        let context_data = builder.create_vector(context.as_ref());
1506                        let context_offset = FbsContextUpdated::create(
1507                            &mut builder,
1508                            &ContextUpdatedArgs {
1509                                context: Some(context_data),
1510                            },
1511                        );
1512                        let msg = FbsOutboundDelegateMsg::create(
1513                            &mut builder,
1514                            &OutboundDelegateMsgArgs {
1515                                inbound_type: OutboundDelegateMsgType::ContextUpdated,
1516                                inbound: Some(context_offset.as_union_value()),
1517                            },
1518                        );
1519                        messages.push(msg);
1520                    }
1521                    OutboundDelegateMsg::GetContractRequest(_) => {
1522                        // GetContractRequest should be handled by the executor and never
1523                        // reach client serialization. If we get here, it's a bug.
1524                        tracing::error!(
1525                            "GetContractRequest reached client serialization - this is a bug"
1526                        );
1527                    }
1528                    OutboundDelegateMsg::PutContractRequest(_) => {
1529                        // PutContractRequest should be handled by the executor and never
1530                        // reach client serialization. If we get here, it's a bug.
1531                        tracing::error!(
1532                            "PutContractRequest reached client serialization - this is a bug"
1533                        );
1534                    }
1535                    OutboundDelegateMsg::UpdateContractRequest(_) => {
1536                        tracing::error!(
1537                            "UpdateContractRequest reached client serialization - this is a bug"
1538                        );
1539                    }
1540                    OutboundDelegateMsg::SubscribeContractRequest(_) => {
1541                        tracing::error!(
1542                            "SubscribeContractRequest reached client serialization - this is a bug"
1543                        );
1544                    }
1545                    OutboundDelegateMsg::SendDelegateMessage(_) => {
1546                        tracing::error!(
1547                            "SendDelegateMessage reached client serialization - this is a bug"
1548                        );
1549                    }
1550                });
1551                let messages_offset = builder.create_vector(&messages);
1552                let delegate_response_offset = FbsDelegateResponse::create(
1553                    &mut builder,
1554                    &DelegateResponseArgs {
1555                        key: Some(key_offset),
1556                        values: Some(messages_offset),
1557                    },
1558                );
1559                let host_response_offset = FbsHostResponse::create(
1560                    &mut builder,
1561                    &HostResponseArgs {
1562                        response_type: HostResponseType::DelegateResponse,
1563                        response: Some(delegate_response_offset.as_union_value()),
1564                    },
1565                );
1566                finish_host_response_buffer(&mut builder, host_response_offset);
1567                Ok(builder.finished_data().to_vec())
1568            }
1569            HostResponse::Ok => {
1570                let ok_offset = FbsOk::create(&mut builder, &OkArgs { msg: None });
1571                let host_response_offset = FbsHostResponse::create(
1572                    &mut builder,
1573                    &HostResponseArgs {
1574                        response_type: HostResponseType::Ok,
1575                        response: Some(ok_offset.as_union_value()),
1576                    },
1577                );
1578                finish_host_response_buffer(&mut builder, host_response_offset);
1579                Ok(builder.finished_data().to_vec())
1580            }
1581            HostResponse::QueryResponse(_) => unimplemented!(),
1582            HostResponse::StreamChunk {
1583                stream_id,
1584                index,
1585                total,
1586                data,
1587            } => {
1588                let data_offset = builder.create_vector(&data);
1589                let chunk_offset = FbsHostStreamChunk::create(
1590                    &mut builder,
1591                    &FbsHostStreamChunkArgs {
1592                        stream_id,
1593                        index,
1594                        total,
1595                        data: Some(data_offset),
1596                    },
1597                );
1598                let host_response_offset = FbsHostResponse::create(
1599                    &mut builder,
1600                    &HostResponseArgs {
1601                        response_type: HostResponseType::StreamChunk,
1602                        response: Some(chunk_offset.as_union_value()),
1603                    },
1604                );
1605                finish_host_response_buffer(&mut builder, host_response_offset);
1606                Ok(builder.finished_data().to_vec())
1607            }
1608            HostResponse::StreamHeader { .. } => {
1609                // StreamHeader is only sent over bincode (Native encoding) to
1610                // streaming-capable clients. Flatbuffers clients use transparent
1611                // reassembly via StreamChunk only.
1612                Err(Box::new(ClientError::from(ErrorKind::Unhandled {
1613                    cause: "StreamHeader is not supported over flatbuffers encoding".into(),
1614                })))
1615            }
1616        }
1617    }
1618}
1619
1620impl Display for HostResponse {
1621    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1622        match self {
1623            HostResponse::ContractResponse(res) => match res {
1624                ContractResponse::PutResponse { key } => {
1625                    f.write_fmt(format_args!("put response for `{key}`"))
1626                }
1627                ContractResponse::UpdateResponse { key, .. } => {
1628                    f.write_fmt(format_args!("update response for `{key}`"))
1629                }
1630                ContractResponse::GetResponse { key, .. } => {
1631                    f.write_fmt(format_args!("get response for `{key}`"))
1632                }
1633                ContractResponse::UpdateNotification { key, .. } => {
1634                    f.write_fmt(format_args!("update notification for `{key}`"))
1635                }
1636                ContractResponse::SubscribeResponse { key, .. } => {
1637                    f.write_fmt(format_args!("subscribe response for `{key}`"))
1638                }
1639                ContractResponse::NotFound { instance_id } => {
1640                    f.write_fmt(format_args!("not found for `{instance_id}`"))
1641                }
1642            },
1643            HostResponse::DelegateResponse { .. } => write!(f, "delegate responses"),
1644            HostResponse::Ok => write!(f, "ok response"),
1645            HostResponse::QueryResponse(_) => write!(f, "query response"),
1646            HostResponse::StreamChunk {
1647                stream_id,
1648                index,
1649                total,
1650                ..
1651            } => write!(f, "stream chunk {index}/{total} (stream {stream_id})"),
1652            HostResponse::StreamHeader {
1653                stream_id,
1654                total_bytes,
1655                ..
1656            } => write!(f, "stream header (stream {stream_id}, {total_bytes} bytes)"),
1657        }
1658    }
1659}
1660
1661#[derive(Clone, Serialize, Deserialize, Debug)]
1662#[non_exhaustive]
1663pub enum ContractResponse<T = WrappedState> {
1664    GetResponse {
1665        key: ContractKey,
1666        contract: Option<ContractContainer>,
1667        #[serde(bound(deserialize = "T: DeserializeOwned"))]
1668        state: T,
1669    },
1670    PutResponse {
1671        key: ContractKey,
1672    },
1673    /// Message sent when there is an update to a subscribed contract.
1674    UpdateNotification {
1675        key: ContractKey,
1676        #[serde(deserialize_with = "UpdateData::deser_update_data")]
1677        update: UpdateData<'static>,
1678    },
1679    /// Successful update
1680    UpdateResponse {
1681        key: ContractKey,
1682        #[serde(deserialize_with = "StateSummary::deser_state_summary")]
1683        summary: StateSummary<'static>,
1684    },
1685    SubscribeResponse {
1686        key: ContractKey,
1687        subscribed: bool,
1688    },
1689    /// Contract was not found after exhaustive search.
1690    /// This is an explicit response that distinguishes "contract doesn't exist"
1691    /// from other failure modes like timeouts or network errors.
1692    NotFound {
1693        /// The instance ID that was searched for.
1694        instance_id: ContractInstanceId,
1695    },
1696}
1697
1698impl<T> From<ContractResponse<T>> for HostResponse<T> {
1699    fn from(value: ContractResponse<T>) -> HostResponse<T> {
1700        HostResponse::ContractResponse(value)
1701    }
1702}
1703
1704#[cfg(test)]
1705mod client_request_test {
1706    use crate::client_api::{ContractRequest, TryFromFbs};
1707    use crate::contract_interface::UpdateData;
1708    use crate::generated::client_request::root_as_client_request;
1709
1710    const EXPECTED_ENCODED_CONTRACT_ID: &str = "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9";
1711
1712    #[test]
1713    fn test_build_contract_put_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1714        let put_req_op = vec![
1715            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,
1716            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,
1717            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,
1718            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,
1719            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,
1720            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,
1721            85, 111, 11, 171, 40, 85, 240, 177, 207, 81, 106, 157, 173, 90, 234, 2, 250, 253, 75,
1722            210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17, 108, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6,
1723            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,
1724            3, 4, 5, 6, 7, 8, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
1725        ];
1726        let request = if let Ok(client_request) = root_as_client_request(&put_req_op) {
1727            let contract_request = client_request.client_request_as_contract_request().unwrap();
1728            ContractRequest::try_decode_fbs(&contract_request)?
1729        } else {
1730            panic!("failed to decode client request")
1731        };
1732
1733        match request {
1734            ContractRequest::Put {
1735                contract,
1736                state,
1737                related_contracts: _,
1738                subscribe,
1739                blocking_subscribe,
1740            } => {
1741                assert_eq!(
1742                    contract.to_string(),
1743                    "WasmContainer([api=0.0.1](D8fdVLbRyMLw5mZtPRpWMFcrXGN2z8Nq8UGcLGPFBg2W))"
1744                );
1745                assert_eq!(contract.unwrap_v1().data.data(), &[1, 2, 3, 4, 5, 6, 7, 8]);
1746                assert_eq!(state.to_vec(), &[1, 2, 3, 4, 5, 6, 7, 8]);
1747                assert!(!subscribe);
1748                assert!(!blocking_subscribe);
1749            }
1750            _ => panic!("wrong contract request type"),
1751        }
1752
1753        Ok(())
1754    }
1755
1756    #[test]
1757    fn test_build_contract_get_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1758        let get_req_op = vec![
1759            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,
1760            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,
1761            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,
1762            4, 0, 0, 0, 32, 0, 0, 0, 85, 111, 11, 171, 40, 85, 240, 177, 207, 81, 106, 157, 173,
1763            90, 234, 2, 250, 253, 75, 210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17, 108,
1764        ];
1765        let request = if let Ok(client_request) = root_as_client_request(&get_req_op) {
1766            let contract_request = client_request.client_request_as_contract_request().unwrap();
1767            ContractRequest::try_decode_fbs(&contract_request)?
1768        } else {
1769            panic!("failed to decode client request")
1770        };
1771
1772        match request {
1773            ContractRequest::Get {
1774                key,
1775                return_contract_code: fetch_contract,
1776                subscribe,
1777                blocking_subscribe,
1778            } => {
1779                assert_eq!(key.encode(), EXPECTED_ENCODED_CONTRACT_ID);
1780                assert!(!fetch_contract);
1781                assert!(!subscribe);
1782                assert!(!blocking_subscribe);
1783            }
1784            _ => panic!("wrong contract request type"),
1785        }
1786
1787        Ok(())
1788    }
1789
1790    #[test]
1791    fn test_build_contract_update_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1792        let update_op = vec![
1793            4, 0, 0, 0, 220, 255, 255, 255, 8, 0, 0, 0, 0, 0, 0, 1, 232, 255, 255, 255, 8, 0, 0, 0,
1794            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,
1795            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,
1796            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,
1797            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,
1798            85, 240, 177, 207, 81, 106, 157, 173, 90, 234, 2, 250, 253, 75, 210, 62, 7, 6, 34, 75,
1799            26, 229, 230, 107, 167, 17, 108,
1800        ];
1801        let request = if let Ok(client_request) = root_as_client_request(&update_op) {
1802            let contract_request = client_request.client_request_as_contract_request().unwrap();
1803            ContractRequest::try_decode_fbs(&contract_request)?
1804        } else {
1805            panic!("failed to decode client request")
1806        };
1807
1808        match request {
1809            ContractRequest::Update { key, data } => {
1810                assert_eq!(
1811                    key.encoded_contract_id(),
1812                    "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9"
1813                );
1814                match data {
1815                    UpdateData::Delta(delta) => {
1816                        assert_eq!(delta.to_vec(), &[1, 2, 3, 4, 5, 6, 7, 8])
1817                    }
1818                    _ => panic!("wrong update data type"),
1819                }
1820            }
1821            _ => panic!("wrong contract request type"),
1822        }
1823
1824        Ok(())
1825    }
1826}