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 fn into_owned(self) -> DelegateRequest<'static> {
585        match self {
586            DelegateRequest::ApplicationMessages {
587                key,
588                inbound,
589                params,
590            } => DelegateRequest::ApplicationMessages {
591                key,
592                params: params.into_owned(),
593                inbound: inbound.into_iter().map(|e| e.into_owned()).collect(),
594            },
595            DelegateRequest::RegisterDelegate {
596                delegate,
597                cipher,
598                nonce,
599            } => DelegateRequest::RegisterDelegate {
600                delegate,
601                cipher,
602                nonce,
603            },
604            DelegateRequest::UnregisterDelegate(key) => DelegateRequest::UnregisterDelegate(key),
605        }
606    }
607
608    pub fn key(&self) -> &DelegateKey {
609        match self {
610            DelegateRequest::ApplicationMessages { key, .. } => key,
611            DelegateRequest::RegisterDelegate { delegate, .. } => delegate.key(),
612            DelegateRequest::UnregisterDelegate(key) => key,
613        }
614    }
615}
616
617impl Display for ClientRequest<'_> {
618    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
619        match self {
620            ClientRequest::ContractOp(op) => match op {
621                ContractRequest::Put {
622                    contract, state, ..
623                } => {
624                    write!(
625                        f,
626                        "ContractRequest::Put for contract `{contract}` with state {state}"
627                    )
628                }
629                ContractRequest::Update { key, .. } => write!(f, "update request for {key}"),
630                ContractRequest::Get {
631                    key,
632                    return_contract_code: contract,
633                    ..
634                } => {
635                    write!(
636                        f,
637                        "ContractRequest::Get for key `{key}` (fetch full contract: {contract})"
638                    )
639                }
640                ContractRequest::Subscribe { key, .. } => {
641                    write!(f, "ContractRequest::Subscribe for `{key}`")
642                }
643            },
644            ClientRequest::DelegateOp(op) => match op {
645                DelegateRequest::ApplicationMessages { key, inbound, .. } => {
646                    write!(
647                        f,
648                        "DelegateRequest::ApplicationMessages for `{key}` with {} messages",
649                        inbound.len()
650                    )
651                }
652                DelegateRequest::RegisterDelegate { delegate, .. } => {
653                    write!(
654                        f,
655                        "DelegateRequest::RegisterDelegate for delegate.key()=`{}`",
656                        delegate.key()
657                    )
658                }
659                DelegateRequest::UnregisterDelegate(key) => {
660                    write!(f, "DelegateRequest::UnregisterDelegate for key `{key}`")
661                }
662            },
663            ClientRequest::Disconnect { .. } => write!(f, "client disconnected"),
664            ClientRequest::Authenticate { .. } => write!(f, "authenticate"),
665            ClientRequest::NodeQueries(query) => write!(f, "node queries: {:?}", query),
666            ClientRequest::Close => write!(f, "close"),
667            ClientRequest::StreamChunk {
668                stream_id,
669                index,
670                total,
671                ..
672            } => write!(f, "stream chunk {index}/{total} (stream {stream_id})"),
673        }
674    }
675}
676
677/// Deserializes a `DelegateRequest` from a Flatbuffers message.
678impl<'a> TryFromFbs<&FbsDelegateRequest<'a>> for DelegateRequest<'a> {
679    fn try_decode_fbs(request: &FbsDelegateRequest<'a>) -> Result<Self, WsApiError> {
680        let req = {
681            match request.delegate_request_type() {
682                DelegateRequestType::ApplicationMessages => {
683                    let app_msg = request.delegate_request_as_application_messages().unwrap();
684                    let key = DelegateKey::try_decode_fbs(&app_msg.key())?;
685                    let params = Parameters::from(app_msg.params().bytes());
686                    let inbound = app_msg
687                        .inbound()
688                        .iter()
689                        .map(|msg| InboundDelegateMsg::try_decode_fbs(&msg))
690                        .collect::<Result<Vec<_>, _>>()?;
691                    DelegateRequest::ApplicationMessages {
692                        key,
693                        params,
694                        inbound,
695                    }
696                }
697                DelegateRequestType::RegisterDelegate => {
698                    let register = request.delegate_request_as_register_delegate().unwrap();
699                    let delegate = DelegateContainer::try_decode_fbs(&register.delegate())?;
700                    let cipher =
701                        <[u8; 32]>::try_from(register.cipher().bytes().to_vec().as_slice())
702                            .unwrap();
703                    let nonce =
704                        <[u8; 24]>::try_from(register.nonce().bytes().to_vec().as_slice()).unwrap();
705                    DelegateRequest::RegisterDelegate {
706                        delegate,
707                        cipher,
708                        nonce,
709                    }
710                }
711                DelegateRequestType::UnregisterDelegate => {
712                    let unregister = request.delegate_request_as_unregister_delegate().unwrap();
713                    let key = DelegateKey::try_decode_fbs(&unregister.key())?;
714                    DelegateRequest::UnregisterDelegate(key)
715                }
716                _ => unreachable!(),
717            }
718        };
719
720        Ok(req)
721    }
722}
723
724/// A response to a previous [`ClientRequest`]
725#[derive(Serialize, Deserialize, Debug, Clone)]
726#[non_exhaustive]
727pub enum HostResponse<T = WrappedState> {
728    ContractResponse(#[serde(bound(deserialize = "T: DeserializeOwned"))] ContractResponse<T>),
729    DelegateResponse {
730        key: DelegateKey,
731        values: Vec<OutboundDelegateMsg>,
732    },
733    QueryResponse(QueryResponse),
734    /// A requested action which doesn't require an answer was performed successfully.
735    Ok,
736    /// A chunk of a larger streamed response.
737    StreamChunk {
738        stream_id: u32,
739        index: u32,
740        total: u32,
741        data: Bytes,
742    },
743    /// Header message announcing the start of a streamed response.
744    /// Sent before the corresponding [`StreamChunk`] messages so the client
745    /// can set up incremental consumption via [`WsStreamHandle`].
746    StreamHeader {
747        stream_id: u32,
748        total_bytes: u64,
749        content: StreamContent,
750    },
751}
752
753/// Describes what kind of response is being streamed.
754#[derive(Debug, Serialize, Deserialize, Clone)]
755pub enum StreamContent {
756    /// A streamed GetResponse — the large state is delivered via StreamChunks.
757    GetResponse {
758        key: ContractKey,
759        includes_contract: bool,
760    },
761    /// Raw binary stream (future use).
762    Raw,
763}
764
765type Peer = String;
766
767#[derive(Serialize, Deserialize, Debug, Clone)]
768pub enum QueryResponse {
769    ConnectedPeers { peers: Vec<(Peer, SocketAddr)> },
770    NetworkDebug(NetworkDebugInfo),
771    NodeDiagnostics(NodeDiagnosticsResponse),
772    NeighborHosting(NeighborHostingInfo),
773}
774
775#[derive(Serialize, Deserialize, Debug, Clone)]
776pub struct NetworkDebugInfo {
777    pub subscriptions: Vec<SubscriptionInfo>,
778    pub connected_peers: Vec<(String, SocketAddr)>,
779}
780
781#[derive(Serialize, Deserialize, Debug, Clone)]
782pub struct NodeDiagnosticsResponse {
783    /// Node information
784    pub node_info: Option<NodeInfo>,
785
786    /// Network connectivity information
787    pub network_info: Option<NetworkInfo>,
788
789    /// Contract subscription information
790    pub subscriptions: Vec<SubscriptionInfo>,
791
792    /// Contract states for specific contracts.
793    ///
794    /// Keys are the Base58-encoded contract id (i.e. `ContractKey::Display`),
795    /// matching the convention every other field in this struct uses
796    /// (`peer_id: String`, `connected_peers: Vec<(String, String)>`,
797    /// `ContractHostingEntry::contract_key: String`). Pre-0.7 this was
798    /// `HashMap<ContractKey, ContractState>`, which `serde_json` could not
799    /// serialize because JSON object keys must be strings — every report
800    /// from a node hosting at least one contract had its
801    /// `network_status` silently dropped. See freenet/freenet-core#3987.
802    pub contract_states: std::collections::HashMap<String, ContractState>,
803
804    /// System metrics
805    pub system_metrics: Option<SystemMetrics>,
806
807    /// Information about connected peers with detailed data
808    pub connected_peers_detailed: Vec<ConnectedPeerInfo>,
809}
810
811#[derive(Serialize, Deserialize, Debug, Clone)]
812pub struct NodeInfo {
813    pub peer_id: String,
814    pub is_gateway: bool,
815    pub location: Option<String>,
816    pub listening_address: Option<String>,
817    pub uptime_seconds: u64,
818}
819
820#[derive(Serialize, Deserialize, Debug, Clone)]
821pub struct NetworkInfo {
822    pub connected_peers: Vec<(String, String)>, // (peer_id, address)
823    pub active_connections: usize,
824}
825
826#[derive(Serialize, Deserialize, Debug, Clone)]
827pub struct ContractState {
828    /// Number of nodes subscribed to this contract
829    pub subscribers: u32,
830    /// Peer IDs of nodes that are subscribed to this contract
831    pub subscriber_peer_ids: Vec<String>,
832    /// Size of the contract state in bytes
833    #[serde(default)]
834    pub size_bytes: u64,
835}
836
837#[derive(Serialize, Deserialize, Debug, Clone)]
838pub struct SystemMetrics {
839    pub active_connections: u32,
840    pub hosting_contracts: u32,
841}
842
843#[derive(Serialize, Deserialize, Debug, Clone)]
844pub struct SubscriptionInfo {
845    pub contract_key: ContractInstanceId,
846    pub client_id: usize,
847}
848
849/// Basic information about a connected peer
850#[derive(Serialize, Deserialize, Debug, Clone)]
851pub struct ConnectedPeerInfo {
852    pub peer_id: String,
853    pub address: String,
854}
855
856#[derive(Serialize, Deserialize, Debug, Clone)]
857pub enum NodeQuery {
858    ConnectedPeers,
859    SubscriptionInfo,
860    NodeDiagnostics {
861        /// Diagnostic configuration specifying what information to collect
862        config: NodeDiagnosticsConfig,
863    },
864    /// Query neighbor hosting information for update propagation
865    NeighborHostingInfo,
866}
867
868/// Neighbor hosting information for update propagation
869#[derive(Serialize, Deserialize, Debug, Clone)]
870pub struct NeighborHostingInfo {
871    /// Contracts this node is currently hosting
872    pub my_hosted: Vec<ContractHostingEntry>,
873    /// What we know about neighbor hosting
874    pub neighbor_hosting: Vec<NeighborHostingDetail>,
875    /// Hosting propagation statistics
876    pub stats: HostingStats,
877}
878
879#[derive(Serialize, Deserialize, Debug, Clone)]
880pub struct ContractHostingEntry {
881    /// Full contract key as string
882    pub contract_key: String,
883    /// 32-bit hash for proximity matching
884    pub hosting_hash: u32,
885    /// When this contract was first hosted (Unix timestamp)
886    pub hosted_since: u64,
887}
888
889#[derive(Serialize, Deserialize, Debug, Clone)]
890pub struct NeighborHostingDetail {
891    /// Peer identifier
892    pub peer_id: String,
893    /// Contract hashes this neighbor is known to host
894    pub known_contracts: Vec<u32>,
895    /// Last update received from this neighbor (Unix timestamp)
896    pub last_update: u64,
897    /// Number of updates received from this neighbor
898    pub update_count: u64,
899}
900
901#[derive(Serialize, Deserialize, Debug, Clone)]
902pub struct HostingStats {
903    /// Number of hosting announcements sent
904    pub hosting_announces_sent: u64,
905    /// Number of hosting announcements received
906    pub hosting_announces_received: u64,
907    /// Updates forwarded via proximity (not subscription)
908    pub updates_via_proximity: u64,
909    /// Updates forwarded via subscription
910    pub updates_via_subscription: u64,
911    /// False positives due to hash collisions
912    pub false_positive_forwards: u64,
913    /// Average number of contracts per neighbor
914    pub avg_neighbor_hosting_size: f32,
915}
916
917#[derive(Serialize, Deserialize, Debug, Clone)]
918pub struct NodeDiagnosticsConfig {
919    /// Include basic node information (ID, location, uptime, etc.)
920    pub include_node_info: bool,
921
922    /// Include network connectivity information
923    pub include_network_info: bool,
924
925    /// Include contract subscription information
926    pub include_subscriptions: bool,
927
928    /// Include contract states for specific contracts (empty = all contracts)
929    pub contract_keys: Vec<ContractKey>,
930
931    /// Include memory and performance metrics
932    pub include_system_metrics: bool,
933
934    /// Include detailed information about connected peers (vs basic peer list)
935    pub include_detailed_peer_info: bool,
936
937    /// Include peer IDs of subscribers in contract state information
938    pub include_subscriber_peer_ids: bool,
939}
940
941impl NodeDiagnosticsConfig {
942    /// Create a comprehensive diagnostic config for debugging update propagation issues
943    pub fn for_update_propagation_debugging(contract_key: ContractKey) -> Self {
944        Self {
945            include_node_info: true,
946            include_network_info: true,
947            include_subscriptions: true,
948            contract_keys: vec![contract_key],
949            include_system_metrics: true,
950            include_detailed_peer_info: true,
951            include_subscriber_peer_ids: true,
952        }
953    }
954
955    /// Create a lightweight diagnostic config for basic node status
956    pub fn basic_status() -> Self {
957        Self {
958            include_node_info: true,
959            include_network_info: true,
960            include_subscriptions: false,
961            contract_keys: vec![],
962            include_system_metrics: false,
963            include_detailed_peer_info: false,
964            include_subscriber_peer_ids: false,
965        }
966    }
967
968    /// Create a full diagnostic config (all information)
969    pub fn full() -> Self {
970        Self {
971            include_node_info: true,
972            include_network_info: true,
973            include_subscriptions: true,
974            contract_keys: vec![], // empty = all contracts
975            include_system_metrics: true,
976            include_detailed_peer_info: true,
977            include_subscriber_peer_ids: true,
978        }
979    }
980}
981
982impl HostResponse {
983    pub fn unwrap_put(self) -> ContractKey {
984        if let Self::ContractResponse(ContractResponse::PutResponse { key }) = self {
985            key
986        } else {
987            panic!("called `HostResponse::unwrap_put()` on other than `PutResponse` value")
988        }
989    }
990
991    pub fn unwrap_get(self) -> (WrappedState, Option<ContractContainer>) {
992        if let Self::ContractResponse(ContractResponse::GetResponse {
993            contract, state, ..
994        }) = self
995        {
996            (state, contract)
997        } else {
998            panic!("called `HostResponse::unwrap_put()` on other than `PutResponse` value")
999        }
1000    }
1001
1002    pub fn into_fbs_bytes(self) -> Result<Vec<u8>, Box<ClientError>> {
1003        let mut builder = flatbuffers::FlatBufferBuilder::new();
1004        match self {
1005            HostResponse::ContractResponse(res) => match res {
1006                ContractResponse::PutResponse { key } => {
1007                    let instance_data = builder.create_vector(key.as_bytes());
1008                    let instance_offset = FbsContractInstanceId::create(
1009                        &mut builder,
1010                        &ContractInstanceIdArgs {
1011                            data: Some(instance_data),
1012                        },
1013                    );
1014
1015                    let code = Some(builder.create_vector(&key.code_hash().0));
1016                    let key_offset = FbsContractKey::create(
1017                        &mut builder,
1018                        &ContractKeyArgs {
1019                            instance: Some(instance_offset),
1020                            code,
1021                        },
1022                    );
1023
1024                    let put_offset = FbsPutResponse::create(
1025                        &mut builder,
1026                        &PutResponseArgs {
1027                            key: Some(key_offset),
1028                        },
1029                    );
1030
1031                    let contract_response_offset = FbsContractResponse::create(
1032                        &mut builder,
1033                        &ContractResponseArgs {
1034                            contract_response: Some(put_offset.as_union_value()),
1035                            contract_response_type: ContractResponseType::PutResponse,
1036                        },
1037                    );
1038
1039                    let response_offset = FbsHostResponse::create(
1040                        &mut builder,
1041                        &HostResponseArgs {
1042                            response: Some(contract_response_offset.as_union_value()),
1043                            response_type: HostResponseType::ContractResponse,
1044                        },
1045                    );
1046
1047                    finish_host_response_buffer(&mut builder, response_offset);
1048                    Ok(builder.finished_data().to_vec())
1049                }
1050                ContractResponse::UpdateResponse { key, summary } => {
1051                    let instance_data = builder.create_vector(key.as_bytes());
1052                    let instance_offset = FbsContractInstanceId::create(
1053                        &mut builder,
1054                        &ContractInstanceIdArgs {
1055                            data: Some(instance_data),
1056                        },
1057                    );
1058
1059                    let code = Some(builder.create_vector(&key.code_hash().0));
1060
1061                    let key_offset = FbsContractKey::create(
1062                        &mut builder,
1063                        &ContractKeyArgs {
1064                            instance: Some(instance_offset),
1065                            code,
1066                        },
1067                    );
1068
1069                    let summary_data = builder.create_vector(&summary.into_bytes());
1070
1071                    let update_response_offset = FbsUpdateResponse::create(
1072                        &mut builder,
1073                        &UpdateResponseArgs {
1074                            key: Some(key_offset),
1075                            summary: Some(summary_data),
1076                        },
1077                    );
1078
1079                    let contract_response_offset = FbsContractResponse::create(
1080                        &mut builder,
1081                        &ContractResponseArgs {
1082                            contract_response: Some(update_response_offset.as_union_value()),
1083                            contract_response_type: ContractResponseType::UpdateResponse,
1084                        },
1085                    );
1086
1087                    let response_offset = FbsHostResponse::create(
1088                        &mut builder,
1089                        &HostResponseArgs {
1090                            response: Some(contract_response_offset.as_union_value()),
1091                            response_type: HostResponseType::ContractResponse,
1092                        },
1093                    );
1094
1095                    finish_host_response_buffer(&mut builder, response_offset);
1096                    Ok(builder.finished_data().to_vec())
1097                }
1098                ContractResponse::GetResponse {
1099                    key,
1100                    contract: contract_container,
1101                    state,
1102                } => {
1103                    let instance_data = builder.create_vector(key.as_bytes());
1104                    let instance_offset = FbsContractInstanceId::create(
1105                        &mut builder,
1106                        &ContractInstanceIdArgs {
1107                            data: Some(instance_data),
1108                        },
1109                    );
1110
1111                    let code = Some(builder.create_vector(&key.code_hash().0));
1112                    let key_offset = FbsContractKey::create(
1113                        &mut builder,
1114                        &ContractKeyArgs {
1115                            instance: Some(instance_offset),
1116                            code,
1117                        },
1118                    );
1119
1120                    let container_offset = if let Some(contract) = contract_container {
1121                        let data = builder.create_vector(contract.key().as_bytes());
1122
1123                        let instance_offset = FbsContractInstanceId::create(
1124                            &mut builder,
1125                            &ContractInstanceIdArgs { data: Some(data) },
1126                        );
1127
1128                        let code = Some(builder.create_vector(&contract.key().code_hash().0));
1129                        let contract_key_offset = FbsContractKey::create(
1130                            &mut builder,
1131                            &ContractKeyArgs {
1132                                instance: Some(instance_offset),
1133                                code,
1134                            },
1135                        );
1136
1137                        let contract_data =
1138                            builder.create_vector(contract.clone().unwrap_v1().data.data());
1139                        let contract_code_hash =
1140                            builder.create_vector(&contract.clone().unwrap_v1().data.hash().0);
1141
1142                        let contract_code_offset = ContractCode::create(
1143                            &mut builder,
1144                            &ContractCodeArgs {
1145                                data: Some(contract_data),
1146                                code_hash: Some(contract_code_hash),
1147                            },
1148                        );
1149
1150                        let contract_params =
1151                            builder.create_vector(&contract.clone().params().into_bytes());
1152
1153                        let contract_offset = match contract {
1154                            Wasm(V1(..)) => WasmContractV1::create(
1155                                &mut builder,
1156                                &WasmContractV1Args {
1157                                    key: Some(contract_key_offset),
1158                                    data: Some(contract_code_offset),
1159                                    parameters: Some(contract_params),
1160                                },
1161                            ),
1162                        };
1163
1164                        Some(FbsContractContainer::create(
1165                            &mut builder,
1166                            &ContractContainerArgs {
1167                                contract_type: ContractType::WasmContractV1,
1168                                contract: Some(contract_offset.as_union_value()),
1169                            },
1170                        ))
1171                    } else {
1172                        None
1173                    };
1174
1175                    let state_data = builder.create_vector(&state);
1176
1177                    let get_offset = FbsGetResponse::create(
1178                        &mut builder,
1179                        &GetResponseArgs {
1180                            key: Some(key_offset),
1181                            contract: container_offset,
1182                            state: Some(state_data),
1183                        },
1184                    );
1185
1186                    let contract_response_offset = FbsContractResponse::create(
1187                        &mut builder,
1188                        &ContractResponseArgs {
1189                            contract_response_type: ContractResponseType::GetResponse,
1190                            contract_response: Some(get_offset.as_union_value()),
1191                        },
1192                    );
1193
1194                    let response_offset = FbsHostResponse::create(
1195                        &mut builder,
1196                        &HostResponseArgs {
1197                            response: Some(contract_response_offset.as_union_value()),
1198                            response_type: HostResponseType::ContractResponse,
1199                        },
1200                    );
1201
1202                    finish_host_response_buffer(&mut builder, response_offset);
1203                    Ok(builder.finished_data().to_vec())
1204                }
1205                ContractResponse::UpdateNotification { key, update } => {
1206                    let instance_data = builder.create_vector(key.as_bytes());
1207                    let instance_offset = FbsContractInstanceId::create(
1208                        &mut builder,
1209                        &ContractInstanceIdArgs {
1210                            data: Some(instance_data),
1211                        },
1212                    );
1213
1214                    let code = Some(builder.create_vector(&key.code_hash().0));
1215                    let key_offset = FbsContractKey::create(
1216                        &mut builder,
1217                        &ContractKeyArgs {
1218                            instance: Some(instance_offset),
1219                            code,
1220                        },
1221                    );
1222
1223                    let update_data = match update {
1224                        State(state) => {
1225                            let state_data = builder.create_vector(&state.into_bytes());
1226                            let state_update_offset = StateUpdate::create(
1227                                &mut builder,
1228                                &StateUpdateArgs {
1229                                    state: Some(state_data),
1230                                },
1231                            );
1232                            FbsUpdateData::create(
1233                                &mut builder,
1234                                &UpdateDataArgs {
1235                                    update_data_type: UpdateDataType::StateUpdate,
1236                                    update_data: Some(state_update_offset.as_union_value()),
1237                                },
1238                            )
1239                        }
1240                        Delta(delta) => {
1241                            let delta_data = builder.create_vector(&delta.into_bytes());
1242                            let update_offset = DeltaUpdate::create(
1243                                &mut builder,
1244                                &DeltaUpdateArgs {
1245                                    delta: Some(delta_data),
1246                                },
1247                            );
1248                            FbsUpdateData::create(
1249                                &mut builder,
1250                                &UpdateDataArgs {
1251                                    update_data_type: UpdateDataType::DeltaUpdate,
1252                                    update_data: Some(update_offset.as_union_value()),
1253                                },
1254                            )
1255                        }
1256                        StateAndDelta { state, delta } => {
1257                            let state_data = builder.create_vector(&state.into_bytes());
1258                            let delta_data = builder.create_vector(&delta.into_bytes());
1259
1260                            let update_offset = StateAndDeltaUpdate::create(
1261                                &mut builder,
1262                                &StateAndDeltaUpdateArgs {
1263                                    state: Some(state_data),
1264                                    delta: Some(delta_data),
1265                                },
1266                            );
1267
1268                            FbsUpdateData::create(
1269                                &mut builder,
1270                                &UpdateDataArgs {
1271                                    update_data_type: UpdateDataType::StateAndDeltaUpdate,
1272                                    update_data: Some(update_offset.as_union_value()),
1273                                },
1274                            )
1275                        }
1276                        RelatedState { related_to, state } => {
1277                            let state_data = builder.create_vector(&state.into_bytes());
1278                            let instance_data =
1279                                builder.create_vector(related_to.encode().as_bytes());
1280
1281                            let instance_offset = FbsContractInstanceId::create(
1282                                &mut builder,
1283                                &ContractInstanceIdArgs {
1284                                    data: Some(instance_data),
1285                                },
1286                            );
1287
1288                            let update_offset = RelatedStateUpdate::create(
1289                                &mut builder,
1290                                &RelatedStateUpdateArgs {
1291                                    related_to: Some(instance_offset),
1292                                    state: Some(state_data),
1293                                },
1294                            );
1295
1296                            FbsUpdateData::create(
1297                                &mut builder,
1298                                &UpdateDataArgs {
1299                                    update_data_type: UpdateDataType::RelatedStateUpdate,
1300                                    update_data: Some(update_offset.as_union_value()),
1301                                },
1302                            )
1303                        }
1304                        RelatedDelta { related_to, delta } => {
1305                            let instance_data =
1306                                builder.create_vector(related_to.encode().as_bytes());
1307                            let delta_data = builder.create_vector(&delta.into_bytes());
1308
1309                            let instance_offset = FbsContractInstanceId::create(
1310                                &mut builder,
1311                                &ContractInstanceIdArgs {
1312                                    data: Some(instance_data),
1313                                },
1314                            );
1315
1316                            let update_offset = RelatedDeltaUpdate::create(
1317                                &mut builder,
1318                                &RelatedDeltaUpdateArgs {
1319                                    related_to: Some(instance_offset),
1320                                    delta: Some(delta_data),
1321                                },
1322                            );
1323
1324                            FbsUpdateData::create(
1325                                &mut builder,
1326                                &UpdateDataArgs {
1327                                    update_data_type: UpdateDataType::RelatedDeltaUpdate,
1328                                    update_data: Some(update_offset.as_union_value()),
1329                                },
1330                            )
1331                        }
1332                        RelatedStateAndDelta {
1333                            related_to,
1334                            state,
1335                            delta,
1336                        } => {
1337                            let instance_data =
1338                                builder.create_vector(related_to.encode().as_bytes());
1339                            let state_data = builder.create_vector(&state.into_bytes());
1340                            let delta_data = builder.create_vector(&delta.into_bytes());
1341
1342                            let instance_offset = FbsContractInstanceId::create(
1343                                &mut builder,
1344                                &ContractInstanceIdArgs {
1345                                    data: Some(instance_data),
1346                                },
1347                            );
1348
1349                            let update_offset = RelatedStateAndDeltaUpdate::create(
1350                                &mut builder,
1351                                &RelatedStateAndDeltaUpdateArgs {
1352                                    related_to: Some(instance_offset),
1353                                    state: Some(state_data),
1354                                    delta: Some(delta_data),
1355                                },
1356                            );
1357
1358                            FbsUpdateData::create(
1359                                &mut builder,
1360                                &UpdateDataArgs {
1361                                    update_data_type: UpdateDataType::RelatedStateAndDeltaUpdate,
1362                                    update_data: Some(update_offset.as_union_value()),
1363                                },
1364                            )
1365                        }
1366                    };
1367
1368                    let update_notification_offset = FbsUpdateNotification::create(
1369                        &mut builder,
1370                        &UpdateNotificationArgs {
1371                            key: Some(key_offset),
1372                            update: Some(update_data),
1373                        },
1374                    );
1375
1376                    let put_response_offset = FbsContractResponse::create(
1377                        &mut builder,
1378                        &ContractResponseArgs {
1379                            contract_response_type: ContractResponseType::UpdateNotification,
1380                            contract_response: Some(update_notification_offset.as_union_value()),
1381                        },
1382                    );
1383
1384                    let host_response_offset = FbsHostResponse::create(
1385                        &mut builder,
1386                        &HostResponseArgs {
1387                            response_type: HostResponseType::ContractResponse,
1388                            response: Some(put_response_offset.as_union_value()),
1389                        },
1390                    );
1391
1392                    finish_host_response_buffer(&mut builder, host_response_offset);
1393                    Ok(builder.finished_data().to_vec())
1394                }
1395                ContractResponse::SubscribeResponse { key, .. } => {
1396                    // SubscribeResponse FBS type not yet in generated code,
1397                    // serialize as PutResponse (same shape: just a key) so
1398                    // the client receives a valid response instead of a crash.
1399                    let instance_data = builder.create_vector(key.as_bytes());
1400                    let instance_offset = FbsContractInstanceId::create(
1401                        &mut builder,
1402                        &ContractInstanceIdArgs {
1403                            data: Some(instance_data),
1404                        },
1405                    );
1406                    let code = Some(builder.create_vector(&key.code_hash().0));
1407                    let key_offset = FbsContractKey::create(
1408                        &mut builder,
1409                        &ContractKeyArgs {
1410                            instance: Some(instance_offset),
1411                            code,
1412                        },
1413                    );
1414                    let put_offset = FbsPutResponse::create(
1415                        &mut builder,
1416                        &PutResponseArgs {
1417                            key: Some(key_offset),
1418                        },
1419                    );
1420                    let contract_response_offset = FbsContractResponse::create(
1421                        &mut builder,
1422                        &ContractResponseArgs {
1423                            contract_response_type: ContractResponseType::PutResponse,
1424                            contract_response: Some(put_offset.as_union_value()),
1425                        },
1426                    );
1427                    let host_response_offset = FbsHostResponse::create(
1428                        &mut builder,
1429                        &HostResponseArgs {
1430                            response_type: HostResponseType::ContractResponse,
1431                            response: Some(contract_response_offset.as_union_value()),
1432                        },
1433                    );
1434                    finish_host_response_buffer(&mut builder, host_response_offset);
1435                    Ok(builder.finished_data().to_vec())
1436                }
1437                ContractResponse::NotFound { instance_id } => {
1438                    let instance_data = builder.create_vector(instance_id.as_bytes());
1439                    let instance_offset = FbsContractInstanceId::create(
1440                        &mut builder,
1441                        &ContractInstanceIdArgs {
1442                            data: Some(instance_data),
1443                        },
1444                    );
1445
1446                    let not_found_offset = FbsNotFound::create(
1447                        &mut builder,
1448                        &NotFoundArgs {
1449                            instance_id: Some(instance_offset),
1450                        },
1451                    );
1452
1453                    let contract_response_offset = FbsContractResponse::create(
1454                        &mut builder,
1455                        &ContractResponseArgs {
1456                            contract_response_type: ContractResponseType::NotFound,
1457                            contract_response: Some(not_found_offset.as_union_value()),
1458                        },
1459                    );
1460
1461                    let response_offset = FbsHostResponse::create(
1462                        &mut builder,
1463                        &HostResponseArgs {
1464                            response: Some(contract_response_offset.as_union_value()),
1465                            response_type: HostResponseType::ContractResponse,
1466                        },
1467                    );
1468
1469                    finish_host_response_buffer(&mut builder, response_offset);
1470                    Ok(builder.finished_data().to_vec())
1471                }
1472            },
1473            HostResponse::DelegateResponse { key, values } => {
1474                let key_data = builder.create_vector(key.bytes());
1475                let code_hash_data = builder.create_vector(&key.code_hash().0);
1476                let key_offset = FbsDelegateKey::create(
1477                    &mut builder,
1478                    &DelegateKeyArgs {
1479                        key: Some(key_data),
1480                        code_hash: Some(code_hash_data),
1481                    },
1482                );
1483                let mut messages: Vec<WIPOffset<FbsOutboundDelegateMsg>> = Vec::new();
1484                values.iter().for_each(|msg| match msg {
1485                    OutboundDelegateMsg::ApplicationMessage(app) => {
1486                        let payload_data = builder.create_vector(&app.payload);
1487                        let delegate_context_data = builder.create_vector(app.context.as_ref());
1488                        let app_offset = FbsApplicationMessage::create(
1489                            &mut builder,
1490                            &ApplicationMessageArgs {
1491                                payload: Some(payload_data),
1492                                context: Some(delegate_context_data),
1493                                processed: app.processed,
1494                            },
1495                        );
1496                        let msg = FbsOutboundDelegateMsg::create(
1497                            &mut builder,
1498                            &OutboundDelegateMsgArgs {
1499                                inbound_type: OutboundDelegateMsgType::common_ApplicationMessage,
1500                                inbound: Some(app_offset.as_union_value()),
1501                            },
1502                        );
1503                        messages.push(msg);
1504                    }
1505                    OutboundDelegateMsg::RequestUserInput(input) => {
1506                        let message_data = builder.create_vector(input.message.bytes());
1507                        let mut responses: Vec<WIPOffset<FbsClientResponse>> = Vec::new();
1508                        input.responses.iter().for_each(|resp| {
1509                            let response_data = builder.create_vector(resp.bytes());
1510                            let response = FbsClientResponse::create(
1511                                &mut builder,
1512                                &ClientResponseArgs {
1513                                    data: Some(response_data),
1514                                },
1515                            );
1516                            responses.push(response)
1517                        });
1518                        let responses_offset = builder.create_vector(&responses);
1519                        let input_offset = FbsRequestUserInput::create(
1520                            &mut builder,
1521                            &RequestUserInputArgs {
1522                                request_id: input.request_id,
1523                                message: Some(message_data),
1524                                responses: Some(responses_offset),
1525                            },
1526                        );
1527                        let msg = FbsOutboundDelegateMsg::create(
1528                            &mut builder,
1529                            &OutboundDelegateMsgArgs {
1530                                inbound_type: OutboundDelegateMsgType::RequestUserInput,
1531                                inbound: Some(input_offset.as_union_value()),
1532                            },
1533                        );
1534                        messages.push(msg);
1535                    }
1536                    OutboundDelegateMsg::ContextUpdated(context) => {
1537                        let context_data = builder.create_vector(context.as_ref());
1538                        let context_offset = FbsContextUpdated::create(
1539                            &mut builder,
1540                            &ContextUpdatedArgs {
1541                                context: Some(context_data),
1542                            },
1543                        );
1544                        let msg = FbsOutboundDelegateMsg::create(
1545                            &mut builder,
1546                            &OutboundDelegateMsgArgs {
1547                                inbound_type: OutboundDelegateMsgType::ContextUpdated,
1548                                inbound: Some(context_offset.as_union_value()),
1549                            },
1550                        );
1551                        messages.push(msg);
1552                    }
1553                    OutboundDelegateMsg::GetContractRequest(_) => {
1554                        // GetContractRequest should be handled by the executor and never
1555                        // reach client serialization. If we get here, it's a bug.
1556                        tracing::error!(
1557                            "GetContractRequest reached client serialization - this is a bug"
1558                        );
1559                    }
1560                    OutboundDelegateMsg::PutContractRequest(_) => {
1561                        // PutContractRequest should be handled by the executor and never
1562                        // reach client serialization. If we get here, it's a bug.
1563                        tracing::error!(
1564                            "PutContractRequest reached client serialization - this is a bug"
1565                        );
1566                    }
1567                    OutboundDelegateMsg::UpdateContractRequest(_) => {
1568                        tracing::error!(
1569                            "UpdateContractRequest reached client serialization - this is a bug"
1570                        );
1571                    }
1572                    OutboundDelegateMsg::SubscribeContractRequest(_) => {
1573                        tracing::error!(
1574                            "SubscribeContractRequest reached client serialization - this is a bug"
1575                        );
1576                    }
1577                    OutboundDelegateMsg::SendDelegateMessage(_) => {
1578                        tracing::error!(
1579                            "SendDelegateMessage reached client serialization - this is a bug"
1580                        );
1581                    }
1582                });
1583                let messages_offset = builder.create_vector(&messages);
1584                let delegate_response_offset = FbsDelegateResponse::create(
1585                    &mut builder,
1586                    &DelegateResponseArgs {
1587                        key: Some(key_offset),
1588                        values: Some(messages_offset),
1589                    },
1590                );
1591                let host_response_offset = FbsHostResponse::create(
1592                    &mut builder,
1593                    &HostResponseArgs {
1594                        response_type: HostResponseType::DelegateResponse,
1595                        response: Some(delegate_response_offset.as_union_value()),
1596                    },
1597                );
1598                finish_host_response_buffer(&mut builder, host_response_offset);
1599                Ok(builder.finished_data().to_vec())
1600            }
1601            HostResponse::Ok => {
1602                let ok_offset = FbsOk::create(&mut builder, &OkArgs { msg: None });
1603                let host_response_offset = FbsHostResponse::create(
1604                    &mut builder,
1605                    &HostResponseArgs {
1606                        response_type: HostResponseType::Ok,
1607                        response: Some(ok_offset.as_union_value()),
1608                    },
1609                );
1610                finish_host_response_buffer(&mut builder, host_response_offset);
1611                Ok(builder.finished_data().to_vec())
1612            }
1613            HostResponse::QueryResponse(_) => unimplemented!(),
1614            HostResponse::StreamChunk {
1615                stream_id,
1616                index,
1617                total,
1618                data,
1619            } => {
1620                let data_offset = builder.create_vector(&data);
1621                let chunk_offset = FbsHostStreamChunk::create(
1622                    &mut builder,
1623                    &FbsHostStreamChunkArgs {
1624                        stream_id,
1625                        index,
1626                        total,
1627                        data: Some(data_offset),
1628                    },
1629                );
1630                let host_response_offset = FbsHostResponse::create(
1631                    &mut builder,
1632                    &HostResponseArgs {
1633                        response_type: HostResponseType::StreamChunk,
1634                        response: Some(chunk_offset.as_union_value()),
1635                    },
1636                );
1637                finish_host_response_buffer(&mut builder, host_response_offset);
1638                Ok(builder.finished_data().to_vec())
1639            }
1640            HostResponse::StreamHeader { .. } => {
1641                // StreamHeader is only sent over bincode (Native encoding) to
1642                // streaming-capable clients. Flatbuffers clients use transparent
1643                // reassembly via StreamChunk only.
1644                Err(Box::new(ClientError::from(ErrorKind::Unhandled {
1645                    cause: "StreamHeader is not supported over flatbuffers encoding".into(),
1646                })))
1647            }
1648        }
1649    }
1650}
1651
1652impl Display for HostResponse {
1653    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1654        match self {
1655            HostResponse::ContractResponse(res) => match res {
1656                ContractResponse::PutResponse { key } => {
1657                    f.write_fmt(format_args!("put response for `{key}`"))
1658                }
1659                ContractResponse::UpdateResponse { key, .. } => {
1660                    f.write_fmt(format_args!("update response for `{key}`"))
1661                }
1662                ContractResponse::GetResponse { key, .. } => {
1663                    f.write_fmt(format_args!("get response for `{key}`"))
1664                }
1665                ContractResponse::UpdateNotification { key, .. } => {
1666                    f.write_fmt(format_args!("update notification for `{key}`"))
1667                }
1668                ContractResponse::SubscribeResponse { key, .. } => {
1669                    f.write_fmt(format_args!("subscribe response for `{key}`"))
1670                }
1671                ContractResponse::NotFound { instance_id } => {
1672                    f.write_fmt(format_args!("not found for `{instance_id}`"))
1673                }
1674            },
1675            HostResponse::DelegateResponse { .. } => write!(f, "delegate responses"),
1676            HostResponse::Ok => write!(f, "ok response"),
1677            HostResponse::QueryResponse(_) => write!(f, "query response"),
1678            HostResponse::StreamChunk {
1679                stream_id,
1680                index,
1681                total,
1682                ..
1683            } => write!(f, "stream chunk {index}/{total} (stream {stream_id})"),
1684            HostResponse::StreamHeader {
1685                stream_id,
1686                total_bytes,
1687                ..
1688            } => write!(f, "stream header (stream {stream_id}, {total_bytes} bytes)"),
1689        }
1690    }
1691}
1692
1693#[derive(Clone, Serialize, Deserialize, Debug)]
1694#[non_exhaustive]
1695pub enum ContractResponse<T = WrappedState> {
1696    GetResponse {
1697        key: ContractKey,
1698        contract: Option<ContractContainer>,
1699        #[serde(bound(deserialize = "T: DeserializeOwned"))]
1700        state: T,
1701    },
1702    PutResponse {
1703        key: ContractKey,
1704    },
1705    /// Message sent when there is an update to a subscribed contract.
1706    UpdateNotification {
1707        key: ContractKey,
1708        #[serde(deserialize_with = "UpdateData::deser_update_data")]
1709        update: UpdateData<'static>,
1710    },
1711    /// Successful update
1712    UpdateResponse {
1713        key: ContractKey,
1714        #[serde(deserialize_with = "StateSummary::deser_state_summary")]
1715        summary: StateSummary<'static>,
1716    },
1717    SubscribeResponse {
1718        key: ContractKey,
1719        subscribed: bool,
1720    },
1721    /// Contract was not found after exhaustive search.
1722    /// This is an explicit response that distinguishes "contract doesn't exist"
1723    /// from other failure modes like timeouts or network errors.
1724    NotFound {
1725        /// The instance ID that was searched for.
1726        instance_id: ContractInstanceId,
1727    },
1728}
1729
1730impl<T> From<ContractResponse<T>> for HostResponse<T> {
1731    fn from(value: ContractResponse<T>) -> HostResponse<T> {
1732        HostResponse::ContractResponse(value)
1733    }
1734}
1735
1736#[cfg(test)]
1737mod node_diagnostics_response_tests {
1738    use super::{
1739        ConnectedPeerInfo, ContractState, NetworkInfo, NodeDiagnosticsResponse, NodeInfo,
1740        SubscriptionInfo, SystemMetrics,
1741    };
1742    use crate::contract_interface::ContractInstanceId;
1743    use std::collections::HashMap;
1744
1745    /// Regression for freenet/freenet-core#3987.
1746    ///
1747    /// Pre-0.7 `contract_states` was `HashMap<ContractKey, ContractState>`.
1748    /// `ContractKey` derives `Serialize` as a struct (`{instance, code}`),
1749    /// which `serde_json` rejects with `key must be a string` because JSON
1750    /// object keys must be strings. The wire path between core and clients
1751    /// is bincode (which doesn't care about key types), so the bug stayed
1752    /// invisible until the `freenet service report` binary tried to
1753    /// JSON-serialize the response for upload — every report from a node
1754    /// hosting at least one contract uploaded with empty `network_status`.
1755    ///
1756    /// All six fields are populated so that any future `pub` field added
1757    /// to the struct gets exercised by serde_json the moment a contributor
1758    /// sets a non-default value here. If a future field reintroduces the
1759    /// non-string-key pattern (e.g. `HashMap<PeerId, _>`), this test will
1760    /// fail at the source instead of silently breaking the report path.
1761    #[test]
1762    fn node_diagnostics_response_json_round_trips() {
1763        let mut contract_states = HashMap::new();
1764        contract_states.insert(
1765            "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9".to_string(),
1766            ContractState {
1767                subscribers: 3,
1768                subscriber_peer_ids: vec!["peer-a".to_string(), "peer-b".to_string()],
1769                size_bytes: 1024,
1770            },
1771        );
1772
1773        let response = NodeDiagnosticsResponse {
1774            node_info: Some(NodeInfo {
1775                peer_id: "peer-self".to_string(),
1776                is_gateway: true,
1777                location: Some("0.5".to_string()),
1778                listening_address: Some("0.0.0.0:31337".to_string()),
1779                uptime_seconds: 3600,
1780            }),
1781            network_info: Some(NetworkInfo {
1782                connected_peers: vec![("peer-x".to_string(), "10.0.0.1:31337".to_string())],
1783                active_connections: 1,
1784            }),
1785            subscriptions: vec![SubscriptionInfo {
1786                contract_key: ContractInstanceId::new([7u8; 32]),
1787                client_id: 42,
1788            }],
1789            contract_states,
1790            system_metrics: Some(SystemMetrics {
1791                active_connections: 1,
1792                hosting_contracts: 1,
1793            }),
1794            connected_peers_detailed: vec![ConnectedPeerInfo {
1795                peer_id: "peer-x".to_string(),
1796                address: "10.0.0.1:31337".to_string(),
1797            }],
1798        };
1799
1800        let json = serde_json::to_string(&response).expect("must serialize to JSON");
1801        let parsed: serde_json::Value = serde_json::from_str(&json).expect("output is valid JSON");
1802
1803        // Every top-level field is present and distinguishable.
1804        let obj = parsed.as_object().expect("top-level must be object");
1805        assert_eq!(obj.len(), 6, "expected six top-level fields, got {obj:?}");
1806        assert_eq!(parsed["node_info"]["peer_id"], "peer-self");
1807        assert_eq!(parsed["network_info"]["active_connections"], 1);
1808        assert_eq!(parsed["subscriptions"][0]["client_id"], 42);
1809        assert_eq!(parsed["system_metrics"]["hosting_contracts"], 1);
1810        assert_eq!(parsed["connected_peers_detailed"][0]["peer_id"], "peer-x");
1811
1812        let states = parsed["contract_states"]
1813            .as_object()
1814            .expect("contract_states must be a JSON object");
1815        assert_eq!(states.len(), 1);
1816        assert_eq!(
1817            states["6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9"]["subscribers"],
1818            3
1819        );
1820
1821        // Bincode round-trip must also still work for the same value
1822        // (the new wire format is the contract; older clients are
1823        // documented as incompatible in CHANGELOG).
1824        let bytes = bincode::serialize(&response).expect("bincode must serialize");
1825        let decoded: NodeDiagnosticsResponse =
1826            bincode::deserialize(&bytes).expect("bincode must round-trip");
1827        assert_eq!(
1828            decoded.contract_states.len(),
1829            1,
1830            "bincode round-trip preserves contract_states entries"
1831        );
1832    }
1833}
1834
1835#[cfg(test)]
1836mod client_request_test {
1837    use crate::client_api::{ContractRequest, TryFromFbs};
1838    use crate::contract_interface::UpdateData;
1839    use crate::generated::client_request::root_as_client_request;
1840
1841    const EXPECTED_ENCODED_CONTRACT_ID: &str = "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9";
1842
1843    #[test]
1844    fn test_build_contract_put_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1845        let put_req_op = vec![
1846            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,
1847            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,
1848            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,
1849            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,
1850            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,
1851            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,
1852            85, 111, 11, 171, 40, 85, 240, 177, 207, 81, 106, 157, 173, 90, 234, 2, 250, 253, 75,
1853            210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17, 108, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6,
1854            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,
1855            3, 4, 5, 6, 7, 8, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
1856        ];
1857        let request = if let Ok(client_request) = root_as_client_request(&put_req_op) {
1858            let contract_request = client_request.client_request_as_contract_request().unwrap();
1859            ContractRequest::try_decode_fbs(&contract_request)?
1860        } else {
1861            panic!("failed to decode client request")
1862        };
1863
1864        match request {
1865            ContractRequest::Put {
1866                contract,
1867                state,
1868                related_contracts: _,
1869                subscribe,
1870                blocking_subscribe,
1871            } => {
1872                assert_eq!(
1873                    contract.to_string(),
1874                    "WasmContainer([api=0.0.1](D8fdVLbRyMLw5mZtPRpWMFcrXGN2z8Nq8UGcLGPFBg2W))"
1875                );
1876                assert_eq!(contract.unwrap_v1().data.data(), &[1, 2, 3, 4, 5, 6, 7, 8]);
1877                assert_eq!(state.to_vec(), &[1, 2, 3, 4, 5, 6, 7, 8]);
1878                assert!(!subscribe);
1879                assert!(!blocking_subscribe);
1880            }
1881            _ => panic!("wrong contract request type"),
1882        }
1883
1884        Ok(())
1885    }
1886
1887    #[test]
1888    fn test_build_contract_get_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1889        let get_req_op = vec![
1890            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,
1891            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,
1892            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,
1893            4, 0, 0, 0, 32, 0, 0, 0, 85, 111, 11, 171, 40, 85, 240, 177, 207, 81, 106, 157, 173,
1894            90, 234, 2, 250, 253, 75, 210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17, 108,
1895        ];
1896        let request = if let Ok(client_request) = root_as_client_request(&get_req_op) {
1897            let contract_request = client_request.client_request_as_contract_request().unwrap();
1898            ContractRequest::try_decode_fbs(&contract_request)?
1899        } else {
1900            panic!("failed to decode client request")
1901        };
1902
1903        match request {
1904            ContractRequest::Get {
1905                key,
1906                return_contract_code: fetch_contract,
1907                subscribe,
1908                blocking_subscribe,
1909            } => {
1910                assert_eq!(key.encode(), EXPECTED_ENCODED_CONTRACT_ID);
1911                assert!(!fetch_contract);
1912                assert!(!subscribe);
1913                assert!(!blocking_subscribe);
1914            }
1915            _ => panic!("wrong contract request type"),
1916        }
1917
1918        Ok(())
1919    }
1920
1921    #[test]
1922    fn test_build_contract_update_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1923        let update_op = vec![
1924            4, 0, 0, 0, 220, 255, 255, 255, 8, 0, 0, 0, 0, 0, 0, 1, 232, 255, 255, 255, 8, 0, 0, 0,
1925            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,
1926            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,
1927            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,
1928            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,
1929            85, 240, 177, 207, 81, 106, 157, 173, 90, 234, 2, 250, 253, 75, 210, 62, 7, 6, 34, 75,
1930            26, 229, 230, 107, 167, 17, 108,
1931        ];
1932        let request = if let Ok(client_request) = root_as_client_request(&update_op) {
1933            let contract_request = client_request.client_request_as_contract_request().unwrap();
1934            ContractRequest::try_decode_fbs(&contract_request)?
1935        } else {
1936            panic!("failed to decode client request")
1937        };
1938
1939        match request {
1940            ContractRequest::Update { key, data } => {
1941                assert_eq!(
1942                    key.encoded_contract_id(),
1943                    "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9"
1944                );
1945                match data {
1946                    UpdateData::Delta(delta) => {
1947                        assert_eq!(delta.to_vec(), &[1, 2, 3, 4, 5, 6, 7, 8])
1948                    }
1949                    _ => panic!("wrong update data type"),
1950                }
1951            }
1952            _ => panic!("wrong contract request type"),
1953        }
1954
1955        Ok(())
1956    }
1957}