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 #[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#[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#[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 #[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#[derive(Serialize, Deserialize, Debug, Clone)]
248#[non_exhaustive]
249pub 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 Close,
262 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 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 other => {
388 return Err(crate::client_api::unknown_union_discriminant(
389 "ClientRequestType",
390 other.0,
391 ))
392 }
393 },
394 Err(e) => {
395 let cause = format!("{e}");
396 return Err(WsApiError::deserialization(cause));
397 }
398 }
399 };
400
401 Ok(req)
402 }
403}
404
405#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
406#[non_exhaustive]
407pub enum ContractRequest<'a> {
408 Put {
410 contract: ContractContainer,
411 state: WrappedState,
413 #[serde(borrow)]
415 related_contracts: RelatedContracts<'a>,
416 subscribe: bool,
418 #[serde(default)]
421 blocking_subscribe: bool,
422 },
423 Update {
425 key: ContractKey,
426 #[serde(borrow)]
427 data: UpdateData<'a>,
428 },
429 Get {
431 key: ContractInstanceId,
434 return_contract_code: bool,
436 subscribe: bool,
438 #[serde(default)]
441 blocking_subscribe: bool,
442 },
443 Subscribe {
446 key: ContractInstanceId,
448 summary: Option<StateSummary<'a>>,
449 },
450}
451
452impl ContractRequest<'_> {
453 pub fn into_owned(self) -> ContractRequest<'static> {
454 match self {
455 Self::Put {
456 contract,
457 state,
458 related_contracts,
459 subscribe,
460 blocking_subscribe,
461 } => ContractRequest::Put {
462 contract,
463 state,
464 related_contracts: related_contracts.into_owned(),
465 subscribe,
466 blocking_subscribe,
467 },
468 Self::Update { key, data } => ContractRequest::Update {
469 key,
470 data: data.into_owned(),
471 },
472 Self::Get {
473 key,
474 return_contract_code: fetch_contract,
475 subscribe,
476 blocking_subscribe,
477 } => ContractRequest::Get {
478 key,
479 return_contract_code: fetch_contract,
480 subscribe,
481 blocking_subscribe,
482 },
483 Self::Subscribe { key, summary } => ContractRequest::Subscribe {
484 key,
485 summary: summary.map(StateSummary::into_owned),
486 },
487 }
488 }
489}
490
491impl<'a> From<ContractRequest<'a>> for ClientRequest<'a> {
492 fn from(op: ContractRequest<'a>) -> Self {
493 ClientRequest::ContractOp(op)
494 }
495}
496
497impl<'a> TryFromFbs<&FbsContractRequest<'a>> for ContractRequest<'a> {
499 fn try_decode_fbs(request: &FbsContractRequest<'a>) -> Result<Self, WsApiError> {
500 let req = {
501 match request.contract_request_type() {
502 ContractRequestType::Get => {
503 let get = request.contract_request_as_get().unwrap();
504 let fbs_key = get.key();
507 let key = crate::contract_interface::key::instance_id_from_fbs(
508 "ContractKey.instance.data",
509 fbs_key.instance().data().bytes(),
510 )?;
511 let fetch_contract = get.fetch_contract();
512 let subscribe = get.subscribe();
513 let blocking_subscribe = get.blocking_subscribe();
514 ContractRequest::Get {
515 key,
516 return_contract_code: fetch_contract,
517 subscribe,
518 blocking_subscribe,
519 }
520 }
521 ContractRequestType::Put => {
522 let put = request.contract_request_as_put().unwrap();
523 let contract = ContractContainer::try_decode_fbs(&put.container())?;
524 let state = WrappedState::new(put.wrapped_state().bytes().to_vec());
525 let related_contracts =
526 RelatedContracts::try_decode_fbs(&put.related_contracts())?.into_owned();
527 let subscribe = put.subscribe();
528 let blocking_subscribe = put.blocking_subscribe();
529 ContractRequest::Put {
530 contract,
531 state,
532 related_contracts,
533 subscribe,
534 blocking_subscribe,
535 }
536 }
537 ContractRequestType::Update => {
538 let update = request.contract_request_as_update().unwrap();
539 let key = ContractKey::try_decode_fbs(&update.key())?;
540 let data = UpdateData::try_decode_fbs(&update.data())?.into_owned();
541 ContractRequest::Update { key, data }
542 }
543 ContractRequestType::Subscribe => {
544 let subscribe = request.contract_request_as_subscribe().unwrap();
545 let fbs_key = subscribe.key();
547 let key = crate::contract_interface::key::instance_id_from_fbs(
548 "ContractKey.instance.data",
549 fbs_key.instance().data().bytes(),
550 )?;
551 let summary = subscribe
552 .summary()
553 .map(|summary_data| StateSummary::from(summary_data.bytes()));
554 ContractRequest::Subscribe { key, summary }
555 }
556 other => {
563 return Err(crate::client_api::unknown_union_discriminant(
564 "ContractRequestType",
565 other.0,
566 ));
567 }
568 }
569 };
570
571 Ok(req)
572 }
573}
574
575impl<'a> From<DelegateRequest<'a>> for ClientRequest<'a> {
576 fn from(op: DelegateRequest<'a>) -> Self {
577 ClientRequest::DelegateOp(op)
578 }
579}
580
581#[derive(Serialize, Deserialize, Debug, Clone)]
582#[non_exhaustive]
583pub enum DelegateRequest<'a> {
584 ApplicationMessages {
585 key: DelegateKey,
586 #[serde(deserialize_with = "Parameters::deser_params")]
587 params: Parameters<'a>,
588 #[serde(borrow)]
589 inbound: Vec<InboundDelegateMsg<'a>>,
590 },
591 RegisterDelegate {
592 delegate: DelegateContainer,
593 cipher: [u8; 32],
594 nonce: [u8; 24],
595 },
596 UnregisterDelegate(DelegateKey),
597 }
606
607impl DelegateRequest<'_> {
608 pub fn into_owned(self) -> DelegateRequest<'static> {
609 match self {
610 DelegateRequest::ApplicationMessages {
611 key,
612 inbound,
613 params,
614 } => DelegateRequest::ApplicationMessages {
615 key,
616 params: params.into_owned(),
617 inbound: inbound.into_iter().map(|e| e.into_owned()).collect(),
618 },
619 DelegateRequest::RegisterDelegate {
620 delegate,
621 cipher,
622 nonce,
623 } => DelegateRequest::RegisterDelegate {
624 delegate,
625 cipher,
626 nonce,
627 },
628 DelegateRequest::UnregisterDelegate(key) => DelegateRequest::UnregisterDelegate(key),
629 }
630 }
631
632 pub fn key(&self) -> &DelegateKey {
633 match self {
634 DelegateRequest::ApplicationMessages { key, .. } => key,
635 DelegateRequest::RegisterDelegate { delegate, .. } => delegate.key(),
636 DelegateRequest::UnregisterDelegate(key) => key,
637 }
638 }
639}
640
641impl Display for ClientRequest<'_> {
642 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
643 match self {
644 ClientRequest::ContractOp(op) => match op {
645 ContractRequest::Put {
646 contract, state, ..
647 } => {
648 write!(
649 f,
650 "ContractRequest::Put for contract `{contract}` with state {state}"
651 )
652 }
653 ContractRequest::Update { key, .. } => write!(f, "update request for {key}"),
654 ContractRequest::Get {
655 key,
656 return_contract_code: contract,
657 ..
658 } => {
659 write!(
660 f,
661 "ContractRequest::Get for key `{key}` (fetch full contract: {contract})"
662 )
663 }
664 ContractRequest::Subscribe { key, .. } => {
665 write!(f, "ContractRequest::Subscribe for `{key}`")
666 }
667 },
668 ClientRequest::DelegateOp(op) => match op {
669 DelegateRequest::ApplicationMessages { key, inbound, .. } => {
670 write!(
671 f,
672 "DelegateRequest::ApplicationMessages for `{key}` with {} messages",
673 inbound.len()
674 )
675 }
676 DelegateRequest::RegisterDelegate { delegate, .. } => {
677 write!(
678 f,
679 "DelegateRequest::RegisterDelegate for delegate.key()=`{}`",
680 delegate.key()
681 )
682 }
683 DelegateRequest::UnregisterDelegate(key) => {
684 write!(f, "DelegateRequest::UnregisterDelegate for key `{key}`")
685 }
686 },
687 ClientRequest::Disconnect { .. } => write!(f, "client disconnected"),
688 ClientRequest::Authenticate { .. } => write!(f, "authenticate"),
689 ClientRequest::NodeQueries(query) => write!(f, "node queries: {:?}", query),
690 ClientRequest::Close => write!(f, "close"),
691 ClientRequest::StreamChunk {
692 stream_id,
693 index,
694 total,
695 ..
696 } => write!(f, "stream chunk {index}/{total} (stream {stream_id})"),
697 }
698 }
699}
700
701impl<'a> TryFromFbs<&FbsDelegateRequest<'a>> for DelegateRequest<'a> {
703 fn try_decode_fbs(request: &FbsDelegateRequest<'a>) -> Result<Self, WsApiError> {
704 let req = {
705 match request.delegate_request_type() {
706 DelegateRequestType::ApplicationMessages => {
707 let app_msg = request.delegate_request_as_application_messages().unwrap();
708 let key = DelegateKey::try_decode_fbs(&app_msg.key())?;
709 let params = Parameters::from(app_msg.params().bytes());
710 let inbound = app_msg
711 .inbound()
712 .iter()
713 .map(|msg| InboundDelegateMsg::try_decode_fbs(&msg))
714 .collect::<Result<Vec<_>, _>>()?;
715 DelegateRequest::ApplicationMessages {
716 key,
717 params,
718 inbound,
719 }
720 }
721 DelegateRequestType::RegisterDelegate => {
722 let register = request.delegate_request_as_register_delegate().unwrap();
723 let delegate = DelegateContainer::try_decode_fbs(®ister.delegate())?;
724 let cipher = crate::client_api::fixed_size_field::<32>(
731 "RegisterDelegate.cipher",
732 register.cipher().bytes(),
733 )?;
734 let nonce = crate::client_api::fixed_size_field::<24>(
735 "RegisterDelegate.nonce",
736 register.nonce().bytes(),
737 )?;
738 DelegateRequest::RegisterDelegate {
739 delegate,
740 cipher,
741 nonce,
742 }
743 }
744 DelegateRequestType::UnregisterDelegate => {
745 let unregister = request.delegate_request_as_unregister_delegate().unwrap();
746 let key = DelegateKey::try_decode_fbs(&unregister.key())?;
747 DelegateRequest::UnregisterDelegate(key)
748 }
749 other => {
756 return Err(crate::client_api::unknown_union_discriminant(
757 "DelegateRequestType",
758 other.0,
759 ));
760 }
761 }
762 };
763
764 Ok(req)
765 }
766}
767
768#[derive(Serialize, Deserialize, Debug, Clone)]
770#[non_exhaustive]
771pub enum HostResponse<T = WrappedState> {
772 ContractResponse(#[serde(bound(deserialize = "T: DeserializeOwned"))] ContractResponse<T>),
773 DelegateResponse {
774 key: DelegateKey,
775 values: Vec<OutboundDelegateMsg>,
776 },
777 QueryResponse(QueryResponse),
778 Ok,
780 StreamChunk {
782 stream_id: u32,
783 index: u32,
784 total: u32,
785 data: Bytes,
786 },
787 StreamHeader {
791 stream_id: u32,
792 total_bytes: u64,
793 content: StreamContent,
794 },
795}
796
797#[derive(Debug, Serialize, Deserialize, Clone)]
799pub enum StreamContent {
800 GetResponse {
802 key: ContractKey,
803 includes_contract: bool,
804 },
805 Raw,
807}
808
809type Peer = String;
810
811#[derive(Serialize, Deserialize, Debug, Clone)]
812pub enum QueryResponse {
813 ConnectedPeers { peers: Vec<(Peer, SocketAddr)> },
814 NetworkDebug(NetworkDebugInfo),
815 NodeDiagnostics(NodeDiagnosticsResponse),
816 NeighborHosting(NeighborHostingInfo),
817}
818
819#[derive(Serialize, Deserialize, Debug, Clone)]
820pub struct NetworkDebugInfo {
821 pub subscriptions: Vec<SubscriptionInfo>,
822 pub connected_peers: Vec<(String, SocketAddr)>,
823}
824
825#[derive(Serialize, Deserialize, Debug, Clone)]
826pub struct NodeDiagnosticsResponse {
827 pub node_info: Option<NodeInfo>,
829
830 pub network_info: Option<NetworkInfo>,
832
833 pub subscriptions: Vec<SubscriptionInfo>,
835
836 pub contract_states: std::collections::HashMap<String, ContractState>,
847
848 pub system_metrics: Option<SystemMetrics>,
850
851 pub connected_peers_detailed: Vec<ConnectedPeerInfo>,
853}
854
855#[derive(Serialize, Deserialize, Debug, Clone)]
856pub struct NodeInfo {
857 pub peer_id: String,
858 pub is_gateway: bool,
859 pub location: Option<String>,
860 pub listening_address: Option<String>,
861 pub uptime_seconds: u64,
862}
863
864#[derive(Serialize, Deserialize, Debug, Clone)]
865pub struct NetworkInfo {
866 pub connected_peers: Vec<(String, String)>, pub active_connections: usize,
868}
869
870#[derive(Serialize, Deserialize, Debug, Clone)]
871pub struct ContractState {
872 pub subscribers: u32,
874 pub subscriber_peer_ids: Vec<String>,
876 #[serde(default)]
878 pub size_bytes: u64,
879}
880
881#[derive(Serialize, Deserialize, Debug, Clone)]
882pub struct SystemMetrics {
883 pub active_connections: u32,
884 pub hosting_contracts: u32,
885}
886
887#[derive(Serialize, Deserialize, Debug, Clone)]
888pub struct SubscriptionInfo {
889 pub contract_key: ContractInstanceId,
890 pub client_id: usize,
891}
892
893#[derive(Serialize, Deserialize, Debug, Clone)]
895pub struct ConnectedPeerInfo {
896 pub peer_id: String,
897 pub address: String,
898}
899
900#[derive(Serialize, Deserialize, Debug, Clone)]
901pub enum NodeQuery {
902 ConnectedPeers,
903 SubscriptionInfo,
904 NodeDiagnostics {
905 config: NodeDiagnosticsConfig,
907 },
908 NeighborHostingInfo,
910}
911
912#[derive(Serialize, Deserialize, Debug, Clone)]
914pub struct NeighborHostingInfo {
915 pub my_hosted: Vec<ContractHostingEntry>,
917 pub neighbor_hosting: Vec<NeighborHostingDetail>,
919 pub stats: HostingStats,
921}
922
923#[derive(Serialize, Deserialize, Debug, Clone)]
924pub struct ContractHostingEntry {
925 pub contract_key: String,
927 pub hosting_hash: u32,
929 pub hosted_since: u64,
931}
932
933#[derive(Serialize, Deserialize, Debug, Clone)]
934pub struct NeighborHostingDetail {
935 pub peer_id: String,
937 pub known_contracts: Vec<u32>,
939 pub last_update: u64,
941 pub update_count: u64,
943}
944
945#[derive(Serialize, Deserialize, Debug, Clone)]
946pub struct HostingStats {
947 pub hosting_announces_sent: u64,
949 pub hosting_announces_received: u64,
951 pub updates_via_proximity: u64,
953 pub updates_via_subscription: u64,
955 pub false_positive_forwards: u64,
957 pub avg_neighbor_hosting_size: f32,
959}
960
961#[derive(Serialize, Deserialize, Debug, Clone)]
962pub struct NodeDiagnosticsConfig {
963 pub include_node_info: bool,
965
966 pub include_network_info: bool,
968
969 pub include_subscriptions: bool,
971
972 pub contract_keys: Vec<ContractKey>,
974
975 pub include_system_metrics: bool,
977
978 pub include_detailed_peer_info: bool,
980
981 pub include_subscriber_peer_ids: bool,
983}
984
985impl NodeDiagnosticsConfig {
986 pub fn for_update_propagation_debugging(contract_key: ContractKey) -> Self {
988 Self {
989 include_node_info: true,
990 include_network_info: true,
991 include_subscriptions: true,
992 contract_keys: vec![contract_key],
993 include_system_metrics: true,
994 include_detailed_peer_info: true,
995 include_subscriber_peer_ids: true,
996 }
997 }
998
999 pub fn basic_status() -> Self {
1001 Self {
1002 include_node_info: true,
1003 include_network_info: true,
1004 include_subscriptions: false,
1005 contract_keys: vec![],
1006 include_system_metrics: false,
1007 include_detailed_peer_info: false,
1008 include_subscriber_peer_ids: false,
1009 }
1010 }
1011
1012 pub fn full() -> Self {
1014 Self {
1015 include_node_info: true,
1016 include_network_info: true,
1017 include_subscriptions: true,
1018 contract_keys: vec![], include_system_metrics: true,
1020 include_detailed_peer_info: true,
1021 include_subscriber_peer_ids: true,
1022 }
1023 }
1024}
1025
1026impl HostResponse {
1027 pub fn unwrap_put(self) -> ContractKey {
1028 if let Self::ContractResponse(ContractResponse::PutResponse { key }) = self {
1029 key
1030 } else {
1031 panic!("called `HostResponse::unwrap_put()` on other than `PutResponse` value")
1032 }
1033 }
1034
1035 pub fn unwrap_get(self) -> (WrappedState, Option<ContractContainer>) {
1036 if let Self::ContractResponse(ContractResponse::GetResponse {
1037 contract, state, ..
1038 }) = self
1039 {
1040 (state, contract)
1041 } else {
1042 panic!("called `HostResponse::unwrap_put()` on other than `PutResponse` value")
1043 }
1044 }
1045
1046 pub fn into_fbs_bytes(self) -> Result<Vec<u8>, Box<ClientError>> {
1047 let mut builder = flatbuffers::FlatBufferBuilder::new();
1048 match self {
1049 HostResponse::ContractResponse(res) => match res {
1050 ContractResponse::PutResponse { key } => {
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 let key_offset = FbsContractKey::create(
1061 &mut builder,
1062 &ContractKeyArgs {
1063 instance: Some(instance_offset),
1064 code,
1065 },
1066 );
1067
1068 let put_offset = FbsPutResponse::create(
1069 &mut builder,
1070 &PutResponseArgs {
1071 key: Some(key_offset),
1072 },
1073 );
1074
1075 let contract_response_offset = FbsContractResponse::create(
1076 &mut builder,
1077 &ContractResponseArgs {
1078 contract_response: Some(put_offset.as_union_value()),
1079 contract_response_type: ContractResponseType::PutResponse,
1080 },
1081 );
1082
1083 let response_offset = FbsHostResponse::create(
1084 &mut builder,
1085 &HostResponseArgs {
1086 response: Some(contract_response_offset.as_union_value()),
1087 response_type: HostResponseType::ContractResponse,
1088 },
1089 );
1090
1091 finish_host_response_buffer(&mut builder, response_offset);
1092 Ok(builder.finished_data().to_vec())
1093 }
1094 ContractResponse::UpdateResponse { key, summary } => {
1095 let instance_data = builder.create_vector(key.as_bytes());
1096 let instance_offset = FbsContractInstanceId::create(
1097 &mut builder,
1098 &ContractInstanceIdArgs {
1099 data: Some(instance_data),
1100 },
1101 );
1102
1103 let code = Some(builder.create_vector(&key.code_hash().0));
1104
1105 let key_offset = FbsContractKey::create(
1106 &mut builder,
1107 &ContractKeyArgs {
1108 instance: Some(instance_offset),
1109 code,
1110 },
1111 );
1112
1113 let summary_data = builder.create_vector(&summary.into_bytes());
1114
1115 let update_response_offset = FbsUpdateResponse::create(
1116 &mut builder,
1117 &UpdateResponseArgs {
1118 key: Some(key_offset),
1119 summary: Some(summary_data),
1120 },
1121 );
1122
1123 let contract_response_offset = FbsContractResponse::create(
1124 &mut builder,
1125 &ContractResponseArgs {
1126 contract_response: Some(update_response_offset.as_union_value()),
1127 contract_response_type: ContractResponseType::UpdateResponse,
1128 },
1129 );
1130
1131 let response_offset = FbsHostResponse::create(
1132 &mut builder,
1133 &HostResponseArgs {
1134 response: Some(contract_response_offset.as_union_value()),
1135 response_type: HostResponseType::ContractResponse,
1136 },
1137 );
1138
1139 finish_host_response_buffer(&mut builder, response_offset);
1140 Ok(builder.finished_data().to_vec())
1141 }
1142 ContractResponse::GetResponse {
1143 key,
1144 contract: contract_container,
1145 state,
1146 } => {
1147 let instance_data = builder.create_vector(key.as_bytes());
1148 let instance_offset = FbsContractInstanceId::create(
1149 &mut builder,
1150 &ContractInstanceIdArgs {
1151 data: Some(instance_data),
1152 },
1153 );
1154
1155 let code = Some(builder.create_vector(&key.code_hash().0));
1156 let key_offset = FbsContractKey::create(
1157 &mut builder,
1158 &ContractKeyArgs {
1159 instance: Some(instance_offset),
1160 code,
1161 },
1162 );
1163
1164 let container_offset = if let Some(contract) = contract_container {
1165 let data = builder.create_vector(contract.key().as_bytes());
1166
1167 let instance_offset = FbsContractInstanceId::create(
1168 &mut builder,
1169 &ContractInstanceIdArgs { data: Some(data) },
1170 );
1171
1172 let code = Some(builder.create_vector(&contract.key().code_hash().0));
1173 let contract_key_offset = FbsContractKey::create(
1174 &mut builder,
1175 &ContractKeyArgs {
1176 instance: Some(instance_offset),
1177 code,
1178 },
1179 );
1180
1181 let contract_data =
1182 builder.create_vector(contract.clone().unwrap_v1().data.data());
1183 let contract_code_hash =
1184 builder.create_vector(&contract.clone().unwrap_v1().data.hash().0);
1185
1186 let contract_code_offset = ContractCode::create(
1187 &mut builder,
1188 &ContractCodeArgs {
1189 data: Some(contract_data),
1190 code_hash: Some(contract_code_hash),
1191 },
1192 );
1193
1194 let contract_params =
1195 builder.create_vector(&contract.clone().params().into_bytes());
1196
1197 let contract_offset = match contract {
1198 Wasm(V1(..)) => WasmContractV1::create(
1199 &mut builder,
1200 &WasmContractV1Args {
1201 key: Some(contract_key_offset),
1202 data: Some(contract_code_offset),
1203 parameters: Some(contract_params),
1204 },
1205 ),
1206 };
1207
1208 Some(FbsContractContainer::create(
1209 &mut builder,
1210 &ContractContainerArgs {
1211 contract_type: ContractType::WasmContractV1,
1212 contract: Some(contract_offset.as_union_value()),
1213 },
1214 ))
1215 } else {
1216 None
1217 };
1218
1219 let state_data = builder.create_vector(&state);
1220
1221 let get_offset = FbsGetResponse::create(
1222 &mut builder,
1223 &GetResponseArgs {
1224 key: Some(key_offset),
1225 contract: container_offset,
1226 state: Some(state_data),
1227 },
1228 );
1229
1230 let contract_response_offset = FbsContractResponse::create(
1231 &mut builder,
1232 &ContractResponseArgs {
1233 contract_response_type: ContractResponseType::GetResponse,
1234 contract_response: Some(get_offset.as_union_value()),
1235 },
1236 );
1237
1238 let response_offset = FbsHostResponse::create(
1239 &mut builder,
1240 &HostResponseArgs {
1241 response: Some(contract_response_offset.as_union_value()),
1242 response_type: HostResponseType::ContractResponse,
1243 },
1244 );
1245
1246 finish_host_response_buffer(&mut builder, response_offset);
1247 Ok(builder.finished_data().to_vec())
1248 }
1249 ContractResponse::UpdateNotification { key, update } => {
1250 let instance_data = builder.create_vector(key.as_bytes());
1251 let instance_offset = FbsContractInstanceId::create(
1252 &mut builder,
1253 &ContractInstanceIdArgs {
1254 data: Some(instance_data),
1255 },
1256 );
1257
1258 let code = Some(builder.create_vector(&key.code_hash().0));
1259 let key_offset = FbsContractKey::create(
1260 &mut builder,
1261 &ContractKeyArgs {
1262 instance: Some(instance_offset),
1263 code,
1264 },
1265 );
1266
1267 let update_data = match update {
1268 State(state) => {
1269 let state_data = builder.create_vector(&state.into_bytes());
1270 let state_update_offset = StateUpdate::create(
1271 &mut builder,
1272 &StateUpdateArgs {
1273 state: Some(state_data),
1274 },
1275 );
1276 FbsUpdateData::create(
1277 &mut builder,
1278 &UpdateDataArgs {
1279 update_data_type: UpdateDataType::StateUpdate,
1280 update_data: Some(state_update_offset.as_union_value()),
1281 },
1282 )
1283 }
1284 Delta(delta) => {
1285 let delta_data = builder.create_vector(&delta.into_bytes());
1286 let update_offset = DeltaUpdate::create(
1287 &mut builder,
1288 &DeltaUpdateArgs {
1289 delta: Some(delta_data),
1290 },
1291 );
1292 FbsUpdateData::create(
1293 &mut builder,
1294 &UpdateDataArgs {
1295 update_data_type: UpdateDataType::DeltaUpdate,
1296 update_data: Some(update_offset.as_union_value()),
1297 },
1298 )
1299 }
1300 StateAndDelta { state, delta } => {
1301 let state_data = builder.create_vector(&state.into_bytes());
1302 let delta_data = builder.create_vector(&delta.into_bytes());
1303
1304 let update_offset = StateAndDeltaUpdate::create(
1305 &mut builder,
1306 &StateAndDeltaUpdateArgs {
1307 state: Some(state_data),
1308 delta: Some(delta_data),
1309 },
1310 );
1311
1312 FbsUpdateData::create(
1313 &mut builder,
1314 &UpdateDataArgs {
1315 update_data_type: UpdateDataType::StateAndDeltaUpdate,
1316 update_data: Some(update_offset.as_union_value()),
1317 },
1318 )
1319 }
1320 RelatedState { related_to, state } => {
1321 let state_data = builder.create_vector(&state.into_bytes());
1322 let instance_data = builder.create_vector(related_to.as_bytes());
1329
1330 let instance_offset = FbsContractInstanceId::create(
1331 &mut builder,
1332 &ContractInstanceIdArgs {
1333 data: Some(instance_data),
1334 },
1335 );
1336
1337 let update_offset = RelatedStateUpdate::create(
1338 &mut builder,
1339 &RelatedStateUpdateArgs {
1340 related_to: Some(instance_offset),
1341 state: Some(state_data),
1342 },
1343 );
1344
1345 FbsUpdateData::create(
1346 &mut builder,
1347 &UpdateDataArgs {
1348 update_data_type: UpdateDataType::RelatedStateUpdate,
1349 update_data: Some(update_offset.as_union_value()),
1350 },
1351 )
1352 }
1353 RelatedDelta { related_to, delta } => {
1354 let instance_data = builder.create_vector(related_to.as_bytes());
1361 let delta_data = builder.create_vector(&delta.into_bytes());
1362
1363 let instance_offset = FbsContractInstanceId::create(
1364 &mut builder,
1365 &ContractInstanceIdArgs {
1366 data: Some(instance_data),
1367 },
1368 );
1369
1370 let update_offset = RelatedDeltaUpdate::create(
1371 &mut builder,
1372 &RelatedDeltaUpdateArgs {
1373 related_to: Some(instance_offset),
1374 delta: Some(delta_data),
1375 },
1376 );
1377
1378 FbsUpdateData::create(
1379 &mut builder,
1380 &UpdateDataArgs {
1381 update_data_type: UpdateDataType::RelatedDeltaUpdate,
1382 update_data: Some(update_offset.as_union_value()),
1383 },
1384 )
1385 }
1386 RelatedStateAndDelta {
1387 related_to,
1388 state,
1389 delta,
1390 } => {
1391 let instance_data = builder.create_vector(related_to.as_bytes());
1398 let state_data = builder.create_vector(&state.into_bytes());
1399 let delta_data = builder.create_vector(&delta.into_bytes());
1400
1401 let instance_offset = FbsContractInstanceId::create(
1402 &mut builder,
1403 &ContractInstanceIdArgs {
1404 data: Some(instance_data),
1405 },
1406 );
1407
1408 let update_offset = RelatedStateAndDeltaUpdate::create(
1409 &mut builder,
1410 &RelatedStateAndDeltaUpdateArgs {
1411 related_to: Some(instance_offset),
1412 state: Some(state_data),
1413 delta: Some(delta_data),
1414 },
1415 );
1416
1417 FbsUpdateData::create(
1418 &mut builder,
1419 &UpdateDataArgs {
1420 update_data_type: UpdateDataType::RelatedStateAndDeltaUpdate,
1421 update_data: Some(update_offset.as_union_value()),
1422 },
1423 )
1424 }
1425 };
1426
1427 let update_notification_offset = FbsUpdateNotification::create(
1428 &mut builder,
1429 &UpdateNotificationArgs {
1430 key: Some(key_offset),
1431 update: Some(update_data),
1432 },
1433 );
1434
1435 let put_response_offset = FbsContractResponse::create(
1436 &mut builder,
1437 &ContractResponseArgs {
1438 contract_response_type: ContractResponseType::UpdateNotification,
1439 contract_response: Some(update_notification_offset.as_union_value()),
1440 },
1441 );
1442
1443 let host_response_offset = FbsHostResponse::create(
1444 &mut builder,
1445 &HostResponseArgs {
1446 response_type: HostResponseType::ContractResponse,
1447 response: Some(put_response_offset.as_union_value()),
1448 },
1449 );
1450
1451 finish_host_response_buffer(&mut builder, host_response_offset);
1452 Ok(builder.finished_data().to_vec())
1453 }
1454 ContractResponse::SubscribeResponse { key, .. } => {
1455 let instance_data = builder.create_vector(key.as_bytes());
1459 let instance_offset = FbsContractInstanceId::create(
1460 &mut builder,
1461 &ContractInstanceIdArgs {
1462 data: Some(instance_data),
1463 },
1464 );
1465 let code = Some(builder.create_vector(&key.code_hash().0));
1466 let key_offset = FbsContractKey::create(
1467 &mut builder,
1468 &ContractKeyArgs {
1469 instance: Some(instance_offset),
1470 code,
1471 },
1472 );
1473 let put_offset = FbsPutResponse::create(
1474 &mut builder,
1475 &PutResponseArgs {
1476 key: Some(key_offset),
1477 },
1478 );
1479 let contract_response_offset = FbsContractResponse::create(
1480 &mut builder,
1481 &ContractResponseArgs {
1482 contract_response_type: ContractResponseType::PutResponse,
1483 contract_response: Some(put_offset.as_union_value()),
1484 },
1485 );
1486 let host_response_offset = FbsHostResponse::create(
1487 &mut builder,
1488 &HostResponseArgs {
1489 response_type: HostResponseType::ContractResponse,
1490 response: Some(contract_response_offset.as_union_value()),
1491 },
1492 );
1493 finish_host_response_buffer(&mut builder, host_response_offset);
1494 Ok(builder.finished_data().to_vec())
1495 }
1496 ContractResponse::NotFound { instance_id } => {
1497 let instance_data = builder.create_vector(instance_id.as_bytes());
1498 let instance_offset = FbsContractInstanceId::create(
1499 &mut builder,
1500 &ContractInstanceIdArgs {
1501 data: Some(instance_data),
1502 },
1503 );
1504
1505 let not_found_offset = FbsNotFound::create(
1506 &mut builder,
1507 &NotFoundArgs {
1508 instance_id: Some(instance_offset),
1509 },
1510 );
1511
1512 let contract_response_offset = FbsContractResponse::create(
1513 &mut builder,
1514 &ContractResponseArgs {
1515 contract_response_type: ContractResponseType::NotFound,
1516 contract_response: Some(not_found_offset.as_union_value()),
1517 },
1518 );
1519
1520 let response_offset = FbsHostResponse::create(
1521 &mut builder,
1522 &HostResponseArgs {
1523 response: Some(contract_response_offset.as_union_value()),
1524 response_type: HostResponseType::ContractResponse,
1525 },
1526 );
1527
1528 finish_host_response_buffer(&mut builder, response_offset);
1529 Ok(builder.finished_data().to_vec())
1530 }
1531 },
1532 HostResponse::DelegateResponse { key, values } => {
1533 let key_data = builder.create_vector(key.bytes());
1534 let code_hash_data = builder.create_vector(&key.code_hash().0);
1535 let key_offset = FbsDelegateKey::create(
1536 &mut builder,
1537 &DelegateKeyArgs {
1538 key: Some(key_data),
1539 code_hash: Some(code_hash_data),
1540 },
1541 );
1542 let mut messages: Vec<WIPOffset<FbsOutboundDelegateMsg>> = Vec::new();
1543 values.iter().for_each(|msg| match msg {
1544 OutboundDelegateMsg::ApplicationMessage(app) => {
1545 let payload_data = builder.create_vector(&app.payload);
1546 let delegate_context_data = builder.create_vector(app.context.as_ref());
1547 let app_offset = FbsApplicationMessage::create(
1548 &mut builder,
1549 &ApplicationMessageArgs {
1550 payload: Some(payload_data),
1551 context: Some(delegate_context_data),
1552 processed: app.processed,
1553 },
1554 );
1555 let msg = FbsOutboundDelegateMsg::create(
1556 &mut builder,
1557 &OutboundDelegateMsgArgs {
1558 inbound_type: OutboundDelegateMsgType::common_ApplicationMessage,
1559 inbound: Some(app_offset.as_union_value()),
1560 },
1561 );
1562 messages.push(msg);
1563 }
1564 OutboundDelegateMsg::RequestUserInput(input) => {
1565 let message_data = builder.create_vector(input.message.bytes());
1566 let mut responses: Vec<WIPOffset<FbsClientResponse>> = Vec::new();
1567 input.responses.iter().for_each(|resp| {
1568 let response_data = builder.create_vector(resp.bytes());
1569 let response = FbsClientResponse::create(
1570 &mut builder,
1571 &ClientResponseArgs {
1572 data: Some(response_data),
1573 },
1574 );
1575 responses.push(response)
1576 });
1577 let responses_offset = builder.create_vector(&responses);
1578 let input_offset = FbsRequestUserInput::create(
1579 &mut builder,
1580 &RequestUserInputArgs {
1581 request_id: input.request_id,
1582 message: Some(message_data),
1583 responses: Some(responses_offset),
1584 },
1585 );
1586 let msg = FbsOutboundDelegateMsg::create(
1587 &mut builder,
1588 &OutboundDelegateMsgArgs {
1589 inbound_type: OutboundDelegateMsgType::RequestUserInput,
1590 inbound: Some(input_offset.as_union_value()),
1591 },
1592 );
1593 messages.push(msg);
1594 }
1595 OutboundDelegateMsg::ContextUpdated(context) => {
1596 let context_data = builder.create_vector(context.as_ref());
1597 let context_offset = FbsContextUpdated::create(
1598 &mut builder,
1599 &ContextUpdatedArgs {
1600 context: Some(context_data),
1601 },
1602 );
1603 let msg = FbsOutboundDelegateMsg::create(
1604 &mut builder,
1605 &OutboundDelegateMsgArgs {
1606 inbound_type: OutboundDelegateMsgType::ContextUpdated,
1607 inbound: Some(context_offset.as_union_value()),
1608 },
1609 );
1610 messages.push(msg);
1611 }
1612 OutboundDelegateMsg::GetContractRequest(_) => {
1613 tracing::error!(
1616 "GetContractRequest reached client serialization - this is a bug"
1617 );
1618 }
1619 OutboundDelegateMsg::PutContractRequest(_) => {
1620 tracing::error!(
1623 "PutContractRequest reached client serialization - this is a bug"
1624 );
1625 }
1626 OutboundDelegateMsg::UpdateContractRequest(_) => {
1627 tracing::error!(
1628 "UpdateContractRequest reached client serialization - this is a bug"
1629 );
1630 }
1631 OutboundDelegateMsg::SubscribeContractRequest(_) => {
1632 tracing::error!(
1633 "SubscribeContractRequest reached client serialization - this is a bug"
1634 );
1635 }
1636 OutboundDelegateMsg::UnsubscribeContractRequest(_) => {
1637 tracing::error!(
1638 "UnsubscribeContractRequest reached client serialization - this is a bug"
1639 );
1640 }
1641 OutboundDelegateMsg::SendDelegateMessage(_) => {
1642 tracing::error!(
1643 "SendDelegateMessage reached client serialization - this is a bug"
1644 );
1645 }
1646 });
1653 let messages_offset = builder.create_vector(&messages);
1654 let delegate_response_offset = FbsDelegateResponse::create(
1655 &mut builder,
1656 &DelegateResponseArgs {
1657 key: Some(key_offset),
1658 values: Some(messages_offset),
1659 },
1660 );
1661 let host_response_offset = FbsHostResponse::create(
1662 &mut builder,
1663 &HostResponseArgs {
1664 response_type: HostResponseType::DelegateResponse,
1665 response: Some(delegate_response_offset.as_union_value()),
1666 },
1667 );
1668 finish_host_response_buffer(&mut builder, host_response_offset);
1669 Ok(builder.finished_data().to_vec())
1670 }
1671 HostResponse::Ok => {
1672 let ok_offset = FbsOk::create(&mut builder, &OkArgs { msg: None });
1673 let host_response_offset = FbsHostResponse::create(
1674 &mut builder,
1675 &HostResponseArgs {
1676 response_type: HostResponseType::Ok,
1677 response: Some(ok_offset.as_union_value()),
1678 },
1679 );
1680 finish_host_response_buffer(&mut builder, host_response_offset);
1681 Ok(builder.finished_data().to_vec())
1682 }
1683 HostResponse::QueryResponse(_) => unimplemented!(),
1684 HostResponse::StreamChunk {
1685 stream_id,
1686 index,
1687 total,
1688 data,
1689 } => {
1690 let data_offset = builder.create_vector(&data);
1691 let chunk_offset = FbsHostStreamChunk::create(
1692 &mut builder,
1693 &FbsHostStreamChunkArgs {
1694 stream_id,
1695 index,
1696 total,
1697 data: Some(data_offset),
1698 },
1699 );
1700 let host_response_offset = FbsHostResponse::create(
1701 &mut builder,
1702 &HostResponseArgs {
1703 response_type: HostResponseType::StreamChunk,
1704 response: Some(chunk_offset.as_union_value()),
1705 },
1706 );
1707 finish_host_response_buffer(&mut builder, host_response_offset);
1708 Ok(builder.finished_data().to_vec())
1709 }
1710 HostResponse::StreamHeader { .. } => {
1711 Err(Box::new(ClientError::from(ErrorKind::Unhandled {
1715 cause: "StreamHeader is not supported over flatbuffers encoding".into(),
1716 })))
1717 }
1718 }
1719 }
1720}
1721
1722impl Display for HostResponse {
1723 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1724 match self {
1725 HostResponse::ContractResponse(res) => match res {
1726 ContractResponse::PutResponse { key } => {
1727 f.write_fmt(format_args!("put response for `{key}`"))
1728 }
1729 ContractResponse::UpdateResponse { key, .. } => {
1730 f.write_fmt(format_args!("update response for `{key}`"))
1731 }
1732 ContractResponse::GetResponse { key, .. } => {
1733 f.write_fmt(format_args!("get response for `{key}`"))
1734 }
1735 ContractResponse::UpdateNotification { key, .. } => {
1736 f.write_fmt(format_args!("update notification for `{key}`"))
1737 }
1738 ContractResponse::SubscribeResponse { key, .. } => {
1739 f.write_fmt(format_args!("subscribe response for `{key}`"))
1740 }
1741 ContractResponse::NotFound { instance_id } => {
1742 f.write_fmt(format_args!("not found for `{instance_id}`"))
1743 }
1744 },
1745 HostResponse::DelegateResponse { .. } => write!(f, "delegate responses"),
1746 HostResponse::Ok => write!(f, "ok response"),
1747 HostResponse::QueryResponse(_) => write!(f, "query response"),
1748 HostResponse::StreamChunk {
1749 stream_id,
1750 index,
1751 total,
1752 ..
1753 } => write!(f, "stream chunk {index}/{total} (stream {stream_id})"),
1754 HostResponse::StreamHeader {
1755 stream_id,
1756 total_bytes,
1757 ..
1758 } => write!(f, "stream header (stream {stream_id}, {total_bytes} bytes)"),
1759 }
1760 }
1761}
1762
1763#[derive(Clone, Serialize, Deserialize, Debug)]
1764#[non_exhaustive]
1765pub enum ContractResponse<T = WrappedState> {
1766 GetResponse {
1767 key: ContractKey,
1768 contract: Option<ContractContainer>,
1769 #[serde(bound(deserialize = "T: DeserializeOwned"))]
1770 state: T,
1771 },
1772 PutResponse {
1773 key: ContractKey,
1774 },
1775 UpdateNotification {
1777 key: ContractKey,
1778 #[serde(deserialize_with = "UpdateData::deser_update_data")]
1779 update: UpdateData<'static>,
1780 },
1781 UpdateResponse {
1783 key: ContractKey,
1784 #[serde(deserialize_with = "StateSummary::deser_state_summary")]
1785 summary: StateSummary<'static>,
1786 },
1787 SubscribeResponse {
1788 key: ContractKey,
1789 subscribed: bool,
1790 },
1791 NotFound {
1795 instance_id: ContractInstanceId,
1797 },
1798}
1799
1800impl<T> From<ContractResponse<T>> for HostResponse<T> {
1801 fn from(value: ContractResponse<T>) -> HostResponse<T> {
1802 HostResponse::ContractResponse(value)
1803 }
1804}
1805
1806#[cfg(test)]
1807mod node_diagnostics_response_tests {
1808 use super::{
1809 ConnectedPeerInfo, ContractState, NetworkInfo, NodeDiagnosticsResponse, NodeInfo,
1810 SubscriptionInfo, SystemMetrics,
1811 };
1812 use crate::contract_interface::ContractInstanceId;
1813 use std::collections::HashMap;
1814
1815 #[test]
1832 fn node_diagnostics_response_json_round_trips() {
1833 let mut contract_states = HashMap::new();
1834 contract_states.insert(
1835 "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9".to_string(),
1836 ContractState {
1837 subscribers: 3,
1838 subscriber_peer_ids: vec!["peer-a".to_string(), "peer-b".to_string()],
1839 size_bytes: 1024,
1840 },
1841 );
1842
1843 let response = NodeDiagnosticsResponse {
1844 node_info: Some(NodeInfo {
1845 peer_id: "peer-self".to_string(),
1846 is_gateway: true,
1847 location: Some("0.5".to_string()),
1848 listening_address: Some("0.0.0.0:31337".to_string()),
1849 uptime_seconds: 3600,
1850 }),
1851 network_info: Some(NetworkInfo {
1852 connected_peers: vec![("peer-x".to_string(), "10.0.0.1:31337".to_string())],
1853 active_connections: 1,
1854 }),
1855 subscriptions: vec![SubscriptionInfo {
1856 contract_key: ContractInstanceId::new([7u8; 32]),
1857 client_id: 42,
1858 }],
1859 contract_states,
1860 system_metrics: Some(SystemMetrics {
1861 active_connections: 1,
1862 hosting_contracts: 1,
1863 }),
1864 connected_peers_detailed: vec![ConnectedPeerInfo {
1865 peer_id: "peer-x".to_string(),
1866 address: "10.0.0.1:31337".to_string(),
1867 }],
1868 };
1869
1870 let json = serde_json::to_string(&response).expect("must serialize to JSON");
1871 let parsed: serde_json::Value = serde_json::from_str(&json).expect("output is valid JSON");
1872
1873 let obj = parsed.as_object().expect("top-level must be object");
1875 assert_eq!(obj.len(), 6, "expected six top-level fields, got {obj:?}");
1876 assert_eq!(parsed["node_info"]["peer_id"], "peer-self");
1877 assert_eq!(parsed["network_info"]["active_connections"], 1);
1878 assert_eq!(parsed["subscriptions"][0]["client_id"], 42);
1879 assert_eq!(parsed["system_metrics"]["hosting_contracts"], 1);
1880 assert_eq!(parsed["connected_peers_detailed"][0]["peer_id"], "peer-x");
1881
1882 let states = parsed["contract_states"]
1883 .as_object()
1884 .expect("contract_states must be a JSON object");
1885 assert_eq!(states.len(), 1);
1886 assert_eq!(
1887 states["6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9"]["subscribers"],
1888 3
1889 );
1890
1891 let bytes = bincode::serialize(&response).expect("bincode must serialize");
1895 let decoded: NodeDiagnosticsResponse =
1896 bincode::deserialize(&bytes).expect("bincode must round-trip");
1897 assert_eq!(
1898 decoded.contract_states.len(),
1899 1,
1900 "bincode round-trip preserves contract_states entries"
1901 );
1902 }
1903}
1904
1905#[cfg(test)]
1906mod client_request_test {
1907 use crate::client_api::{ContractRequest, TryFromFbs, WsApiError};
1908 use crate::contract_interface::UpdateData;
1909 use crate::generated::client_request::root_as_client_request;
1910
1911 const EXPECTED_ENCODED_CONTRACT_ID: &str = "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9";
1912
1913 #[test]
1914 fn test_build_contract_put_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1915 let put_req_op = vec![
1916 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,
1917 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,
1918 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,
1919 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,
1920 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,
1921 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,
1922 85, 111, 11, 171, 40, 85, 240, 177, 207, 81, 106, 157, 173, 90, 234, 2, 250, 253, 75,
1923 210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17, 108, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6,
1924 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,
1925 3, 4, 5, 6, 7, 8, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
1926 ];
1927 let request = if let Ok(client_request) = root_as_client_request(&put_req_op) {
1928 let contract_request = client_request.client_request_as_contract_request().unwrap();
1929 ContractRequest::try_decode_fbs(&contract_request)?
1930 } else {
1931 panic!("failed to decode client request")
1932 };
1933
1934 match request {
1935 ContractRequest::Put {
1936 contract,
1937 state,
1938 related_contracts: _,
1939 subscribe,
1940 blocking_subscribe,
1941 } => {
1942 assert_eq!(
1943 contract.to_string(),
1944 "WasmContainer([api=0.0.1](D8fdVLbRyMLw5mZtPRpWMFcrXGN2z8Nq8UGcLGPFBg2W))"
1945 );
1946 assert_eq!(contract.unwrap_v1().data.data(), &[1, 2, 3, 4, 5, 6, 7, 8]);
1947 assert_eq!(state.to_vec(), &[1, 2, 3, 4, 5, 6, 7, 8]);
1948 assert!(!subscribe);
1949 assert!(!blocking_subscribe);
1950 }
1951 _ => panic!("wrong contract request type"),
1952 }
1953
1954 Ok(())
1955 }
1956
1957 #[test]
1958 fn test_build_contract_get_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1959 let get_req_op = vec![
1960 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,
1961 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,
1962 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,
1963 4, 0, 0, 0, 32, 0, 0, 0, 85, 111, 11, 171, 40, 85, 240, 177, 207, 81, 106, 157, 173,
1964 90, 234, 2, 250, 253, 75, 210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17, 108,
1965 ];
1966 let request = if let Ok(client_request) = root_as_client_request(&get_req_op) {
1967 let contract_request = client_request.client_request_as_contract_request().unwrap();
1968 ContractRequest::try_decode_fbs(&contract_request)?
1969 } else {
1970 panic!("failed to decode client request")
1971 };
1972
1973 match request {
1974 ContractRequest::Get {
1975 key,
1976 return_contract_code: fetch_contract,
1977 subscribe,
1978 blocking_subscribe,
1979 } => {
1980 assert_eq!(key.encode(), EXPECTED_ENCODED_CONTRACT_ID);
1981 assert!(!fetch_contract);
1982 assert!(!subscribe);
1983 assert!(!blocking_subscribe);
1984 }
1985 _ => panic!("wrong contract request type"),
1986 }
1987
1988 Ok(())
1989 }
1990
1991 #[test]
2006 fn test_build_contract_update_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
2007 use crate::generated::client_request::{
2008 finish_client_request_buffer, ClientRequest as FbsClientRequest, ClientRequestArgs,
2009 ClientRequestType, ContractRequest as FbsContractRequest, ContractRequestArgs,
2010 ContractRequestType, Update as FbsUpdate, UpdateArgs,
2011 };
2012 use crate::generated::common::{
2013 ContractInstanceId as FbsContractInstanceId, ContractInstanceIdArgs,
2014 ContractKey as FbsContractKey, ContractKeyArgs, DeltaUpdate, DeltaUpdateArgs,
2015 UpdateData as FbsUpdateData, UpdateDataArgs, UpdateDataType,
2016 };
2017 use crate::prelude::ContractInstanceId;
2018
2019 let instance_id = ContractInstanceId::try_from(EXPECTED_ENCODED_CONTRACT_ID.to_string())?;
2022 let code_hash = [42u8; 32];
2025 let delta_bytes = [1u8, 2, 3, 4, 5, 6, 7, 8];
2026
2027 let mut b = flatbuffers::FlatBufferBuilder::new();
2028
2029 let instance_data = b.create_vector(instance_id.as_bytes());
2030 let instance_offset = FbsContractInstanceId::create(
2031 &mut b,
2032 &ContractInstanceIdArgs {
2033 data: Some(instance_data),
2034 },
2035 );
2036 let code = Some(b.create_vector(&code_hash));
2037 let key_offset = FbsContractKey::create(
2038 &mut b,
2039 &ContractKeyArgs {
2040 instance: Some(instance_offset),
2041 code,
2042 },
2043 );
2044
2045 let delta = b.create_vector(&delta_bytes);
2046 let delta_offset = DeltaUpdate::create(&mut b, &DeltaUpdateArgs { delta: Some(delta) });
2047 let update_data_offset = FbsUpdateData::create(
2048 &mut b,
2049 &UpdateDataArgs {
2050 update_data_type: UpdateDataType::DeltaUpdate,
2051 update_data: Some(delta_offset.as_union_value()),
2052 },
2053 );
2054
2055 let update_offset = FbsUpdate::create(
2056 &mut b,
2057 &UpdateArgs {
2058 key: Some(key_offset),
2059 data: Some(update_data_offset),
2060 },
2061 );
2062 let contract_offset = FbsContractRequest::create(
2063 &mut b,
2064 &ContractRequestArgs {
2065 contract_request_type: ContractRequestType::Update,
2066 contract_request: Some(update_offset.as_union_value()),
2067 },
2068 );
2069 let client_offset = FbsClientRequest::create(
2070 &mut b,
2071 &ClientRequestArgs {
2072 client_request_type: ClientRequestType::ContractRequest,
2073 client_request: Some(contract_offset.as_union_value()),
2074 },
2075 );
2076 finish_client_request_buffer(&mut b, client_offset);
2077
2078 let update_op = b.finished_data().to_vec();
2079 let request = if let Ok(client_request) = root_as_client_request(&update_op) {
2080 let contract_request = client_request.client_request_as_contract_request().unwrap();
2081 ContractRequest::try_decode_fbs(&contract_request)?
2082 } else {
2083 panic!("failed to decode client request")
2084 };
2085
2086 match request {
2087 ContractRequest::Update { key, data } => {
2088 assert_eq!(key.encoded_contract_id(), EXPECTED_ENCODED_CONTRACT_ID);
2089 assert_eq!(
2090 key.code_hash().as_ref(),
2091 &code_hash,
2092 "the code hash must survive decode unchanged, not be re-hashed"
2093 );
2094 match data {
2095 UpdateData::Delta(delta) => {
2096 assert_eq!(delta.to_vec(), &delta_bytes)
2097 }
2098 _ => panic!("wrong update data type"),
2099 }
2100 }
2101 _ => panic!("wrong contract request type"),
2102 }
2103
2104 Ok(())
2105 }
2106
2107 const TS_SDK_EXPECTED_UPDATE_REQ: &[u8] = &[
2134 4, 0, 0, 0, 220, 255, 255, 255, 8, 0, 0, 0, 0, 0, 0, 1, 232, 255, 255, 255, 8, 0, 0, 0, 0,
2135 0, 0, 2, 204, 255, 255, 255, 16, 0, 0, 0, 52, 0, 0, 0, 8, 0, 12, 0, 11, 0, 4, 0, 8, 0, 0,
2136 0, 8, 0, 0, 0, 0, 0, 0, 2, 210, 255, 255, 255, 4, 0, 0, 0, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7,
2137 8, 8, 0, 12, 0, 8, 0, 4, 0, 8, 0, 0, 0, 8, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 8,
2138 0, 4, 0, 6, 0, 0, 0, 4, 0, 0, 0, 32, 0, 0, 0, 85, 111, 11, 171, 40, 85, 240, 177, 207, 81,
2139 106, 157, 173, 90, 234, 2, 250, 253, 75, 210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17,
2140 108,
2141 ];
2142
2143 #[test]
2152 fn typescript_sdk_instance_only_update_is_rejected_with_guidance() {
2153 let client_request = root_as_client_request(TS_SDK_EXPECTED_UPDATE_REQ)
2154 .expect("the TS SDK blob must still be a well-formed ClientRequest");
2155 let contract_request = client_request
2156 .client_request_as_contract_request()
2157 .expect("the TS SDK blob must still be a ContractRequest");
2158
2159 let err = ContractRequest::try_decode_fbs(&contract_request)
2160 .expect_err("an instance-id-only UPDATE must be rejected");
2161 let msg = err.to_string();
2162
2163 assert!(
2164 msg.contains("ContractKey.code") && msg.contains("got 0 bytes"),
2165 "the TS SDK's zero-length code must be named explicitly, got: {msg}"
2166 );
2167 assert!(
2168 msg.contains("new ContractKey(instance, code)"),
2169 "the error must tell a TypeScript developer how to build the key, got: {msg}"
2170 );
2171 assert!(
2172 msg.contains("4978"),
2173 "the error must point at the tracking issue for the real fix, got: {msg}"
2174 );
2175 }
2176
2177 fn client_request_with_instance_len(instance_len: usize, subscribe: bool) -> Vec<u8> {
2185 use crate::generated::client_request::{
2186 finish_client_request_buffer, ClientRequest as FbsClientRequest, ClientRequestArgs,
2187 ClientRequestType, ContractRequest as FbsContractRequest, ContractRequestArgs,
2188 ContractRequestType, Get as FbsGet, GetArgs, Subscribe as FbsSubscribe, SubscribeArgs,
2189 };
2190 use crate::generated::common::{
2191 ContractInstanceId as FbsContractInstanceId, ContractInstanceIdArgs,
2192 ContractKey as FbsContractKey, ContractKeyArgs,
2193 };
2194
2195 let mut b = flatbuffers::FlatBufferBuilder::new();
2196 let instance_data = b.create_vector(&vec![1u8; instance_len]);
2197 let instance_offset = FbsContractInstanceId::create(
2198 &mut b,
2199 &ContractInstanceIdArgs {
2200 data: Some(instance_data),
2201 },
2202 );
2203 let code = Some(b.create_vector(&[42u8; 32]));
2204 let key_offset = FbsContractKey::create(
2205 &mut b,
2206 &ContractKeyArgs {
2207 instance: Some(instance_offset),
2208 code,
2209 },
2210 );
2211
2212 let (request_type, request_offset) = if subscribe {
2213 let sub = FbsSubscribe::create(
2214 &mut b,
2215 &SubscribeArgs {
2216 key: Some(key_offset),
2217 summary: None,
2218 },
2219 );
2220 (ContractRequestType::Subscribe, sub.as_union_value())
2221 } else {
2222 let get = FbsGet::create(
2223 &mut b,
2224 &GetArgs {
2225 key: Some(key_offset),
2226 fetch_contract: false,
2227 subscribe: false,
2228 blocking_subscribe: false,
2229 },
2230 );
2231 (ContractRequestType::Get, get.as_union_value())
2232 };
2233
2234 let contract_offset = FbsContractRequest::create(
2235 &mut b,
2236 &ContractRequestArgs {
2237 contract_request_type: request_type,
2238 contract_request: Some(request_offset),
2239 },
2240 );
2241 let client_offset = FbsClientRequest::create(
2242 &mut b,
2243 &ClientRequestArgs {
2244 client_request_type: ClientRequestType::ContractRequest,
2245 client_request: Some(contract_offset.as_union_value()),
2246 },
2247 );
2248 finish_client_request_buffer(&mut b, client_offset);
2249 b.finished_data().to_vec()
2250 }
2251
2252 fn decode_client_request(bytes: &[u8]) -> Result<ContractRequest<'_>, WsApiError> {
2253 let client_request =
2254 root_as_client_request(bytes).expect("must be a well-formed ClientRequest");
2255 let contract_request = client_request
2256 .client_request_as_contract_request()
2257 .expect("must be a ContractRequest");
2258 ContractRequest::try_decode_fbs(&contract_request)
2259 }
2260
2261 #[test]
2270 fn get_with_wrong_length_instance_is_rejected_not_panicking() {
2271 let bytes = client_request_with_instance_len(8, false);
2272 let short = decode_client_request(&bytes)
2273 .expect_err("a GET with an 8-byte instance must be rejected");
2274 assert!(
2275 short.to_string().contains("ContractKey.instance")
2276 && short.to_string().contains("got 8 bytes"),
2277 "got: {short}"
2278 );
2279
2280 let bytes = client_request_with_instance_len(64, false);
2281 let long = decode_client_request(&bytes)
2282 .expect_err("a GET with a 64-byte instance must be rejected");
2283 assert!(long.to_string().contains("got 64 bytes"), "got: {long}");
2284 }
2285
2286 #[test]
2288 fn subscribe_with_wrong_length_instance_is_rejected_not_panicking() {
2289 let bytes = client_request_with_instance_len(8, true);
2290 let short = decode_client_request(&bytes)
2291 .expect_err("a SUBSCRIBE with an 8-byte instance must be rejected");
2292 assert!(
2293 short.to_string().contains("ContractKey.instance")
2294 && short.to_string().contains("got 8 bytes"),
2295 "got: {short}"
2296 );
2297
2298 let bytes = client_request_with_instance_len(64, true);
2299 let long = decode_client_request(&bytes)
2300 .expect_err("a SUBSCRIBE with a 64-byte instance must be rejected");
2301 assert!(long.to_string().contains("got 64 bytes"), "got: {long}");
2302 }
2303
2304 #[test]
2307 fn get_with_valid_instance_still_decodes() {
2308 let bytes = client_request_with_instance_len(32, false);
2309 let req = decode_client_request(&bytes).expect("a 32-byte instance must still decode");
2310 assert!(
2311 matches!(req, ContractRequest::Get { .. }),
2312 "expected a Get, got {req:?}"
2313 );
2314 }
2315
2316 #[test]
2323 fn fbs_decode_rejects_unknown_contract_discriminant() {
2324 use crate::generated::client_request::{
2325 finish_client_request_buffer, ClientRequest as FbsClientRequest, ClientRequestArgs,
2326 ClientRequestType, ContractRequest as FbsContractRequest, ContractRequestArgs,
2327 ContractRequestType, DelegateKey as FbsDelegateKey, DelegateKeyArgs,
2328 UnregisterDelegate, UnregisterDelegateArgs,
2329 };
2330
2331 let mut b = flatbuffers::FlatBufferBuilder::new();
2332 let key = b.create_vector(&[0u8; 32]);
2335 let code_hash = b.create_vector(&[0u8; 32]);
2336 let dk = FbsDelegateKey::create(
2337 &mut b,
2338 &DelegateKeyArgs {
2339 key: Some(key),
2340 code_hash: Some(code_hash),
2341 },
2342 );
2343 let dummy = UnregisterDelegate::create(&mut b, &UnregisterDelegateArgs { key: Some(dk) });
2344 let contract = FbsContractRequest::create(
2346 &mut b,
2347 &ContractRequestArgs {
2348 contract_request_type: ContractRequestType(99),
2349 contract_request: Some(dummy.as_union_value()),
2350 },
2351 );
2352 let client = FbsClientRequest::create(
2353 &mut b,
2354 &ClientRequestArgs {
2355 client_request_type: ClientRequestType::ContractRequest,
2356 client_request: Some(contract.as_union_value()),
2357 },
2358 );
2359 finish_client_request_buffer(&mut b, client);
2360 let bytes = b.finished_data().to_vec();
2361
2362 let client =
2363 root_as_client_request(&bytes).expect("verifier accepts an unknown union discriminant");
2364 let fbs_contract = client
2365 .client_request_as_contract_request()
2366 .expect("client_request is a ContractRequest");
2367 assert!(
2368 ContractRequest::try_decode_fbs(&fbs_contract).is_err(),
2369 "an unknown ContractRequestType discriminant must be a clean \
2370 per-request error, never a panic that downs the connection handler"
2371 );
2372 }
2373}
2374
2375#[cfg(test)]
2399mod delegate_request_wire_format {
2400 use super::DelegateRequest;
2401 use crate::code_hash::CodeHash;
2402 use crate::prelude::{
2403 ApplicationMessage, Delegate, DelegateCode, DelegateContainer, DelegateKey,
2404 DelegateWasmAPIVersion, InboundDelegateMsg, Parameters,
2405 };
2406
2407 fn sample_container() -> DelegateContainer {
2408 let code = DelegateCode::from(vec![1u8, 2, 3, 4]);
2409 let params = Parameters::from(vec![9u8, 8, 7]);
2410 DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(Delegate::from((&code, ¶ms))))
2411 }
2412
2413 fn sample_app_messages() -> DelegateRequest<'static> {
2418 DelegateRequest::ApplicationMessages {
2419 key: DelegateKey::new([0x11; 32], CodeHash::new([0x22; 32])),
2420 params: Parameters::from(vec![0xDE, 0xAD, 0xBE, 0xEF]),
2421 inbound: vec![InboundDelegateMsg::ApplicationMessage(
2422 ApplicationMessage::new(vec![0x01, 0x02, 0x03]),
2423 )],
2424 }
2425 }
2426
2427 fn sample_register() -> DelegateRequest<'static> {
2428 DelegateRequest::RegisterDelegate {
2429 delegate: sample_container(),
2430 cipher: [0x55; 32],
2431 nonce: [0x66; 24],
2432 }
2433 }
2434
2435 fn sample_unregister() -> DelegateRequest<'static> {
2436 DelegateRequest::UnregisterDelegate(DelegateKey::new([0x11; 32], CodeHash::new([0x22; 32])))
2437 }
2438
2439 #[test]
2454 fn wire_format_is_frozen() {
2455 const APP_MESSAGES: &[u8] = &[
2457 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
2458 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 34, 34, 34, 34, 34, 34, 34, 34, 34,
2459 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
2460 34, 4, 0, 0, 0, 0, 0, 0, 0, 222, 173, 190, 239, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
2461 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2462 ];
2463 const REGISTER: &[u8] = &[
2464 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 4, 0, 0, 0, 0, 0,
2465 0, 0, 1, 2, 3, 4, 99, 120, 29, 23, 20, 37, 163, 99, 18, 250, 5, 141, 135, 18, 213, 208,
2466 81, 53, 169, 145, 236, 32, 53, 28, 233, 214, 92, 219, 25, 160, 84, 50, 88, 111, 44, 39,
2467 24, 219, 97, 92, 222, 20, 205, 248, 149, 154, 214, 38, 193, 144, 31, 141, 32, 222, 49,
2468 197, 66, 237, 16, 98, 165, 72, 6, 11, 99, 120, 29, 23, 20, 37, 163, 99, 18, 250, 5,
2469 141, 135, 18, 213, 208, 81, 53, 169, 145, 236, 32, 53, 28, 233, 214, 92, 219, 25, 160,
2470 84, 50, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
2471 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 102, 102, 102, 102, 102, 102, 102, 102,
2472 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
2473 ];
2474 const UNREGISTER: &[u8] = &[
2475 2, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
2476 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 34, 34, 34, 34, 34, 34, 34, 34, 34,
2477 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
2478 34,
2479 ];
2480 assert_eq!(
2482 bincode::serialize(&sample_app_messages()).unwrap(),
2483 APP_MESSAGES,
2484 "ApplicationMessages (tag 0) encoding changed"
2485 );
2486 assert_eq!(
2487 bincode::serialize(&sample_register()).unwrap(),
2488 REGISTER,
2489 "RegisterDelegate (tag 1) encoding changed"
2490 );
2491 assert_eq!(
2492 bincode::serialize(&sample_unregister()).unwrap(),
2493 UNREGISTER,
2494 "UnregisterDelegate (tag 2) encoding changed"
2495 );
2496
2497 assert_eq!(APP_MESSAGES[..4], 0u32.to_le_bytes());
2499 assert_eq!(REGISTER[..4], 1u32.to_le_bytes());
2500 assert_eq!(UNREGISTER[..4], 2u32.to_le_bytes());
2501
2502 assert!(matches!(
2505 bincode::deserialize::<DelegateRequest>(APP_MESSAGES).unwrap(),
2506 DelegateRequest::ApplicationMessages { .. }
2507 ));
2508 assert!(matches!(
2509 bincode::deserialize::<DelegateRequest>(REGISTER).unwrap(),
2510 DelegateRequest::RegisterDelegate { .. }
2511 ));
2512 assert!(matches!(
2513 bincode::deserialize::<DelegateRequest>(UNREGISTER).unwrap(),
2514 DelegateRequest::UnregisterDelegate(_)
2515 ));
2516 }
2517
2518 #[test]
2522 fn key_dispatches_for_every_variant() {
2523 let app_key = DelegateKey::new([0x11; 32], CodeHash::new([0x22; 32]));
2524 assert_eq!(sample_app_messages().key(), &app_key);
2525
2526 let register = sample_register();
2527 match ®ister {
2528 DelegateRequest::RegisterDelegate { delegate, .. } => {
2529 assert_eq!(register.key(), delegate.key());
2530 }
2531 other => panic!("sample_register() must build a RegisterDelegate, got {other:?}"),
2532 }
2533
2534 let unregister_key = DelegateKey::new([0x11; 32], CodeHash::new([0x22; 32]));
2535 assert_eq!(sample_unregister().key(), &unregister_key);
2536 }
2537
2538 #[test]
2547 fn fbs_decode_rejects_unknown_discriminant() {
2548 use crate::client_api::TryFromFbs;
2549 use crate::generated::client_request::{
2550 finish_client_request_buffer, root_as_client_request,
2551 ClientRequest as FbsClientRequest, ClientRequestArgs, ClientRequestType,
2552 DelegateKey as FbsDelegateKey, DelegateKeyArgs, DelegateRequest as FbsDelegateRequest,
2553 DelegateRequestArgs, DelegateRequestType, UnregisterDelegate, UnregisterDelegateArgs,
2554 };
2555
2556 let mut b = flatbuffers::FlatBufferBuilder::new();
2557 let key = b.create_vector(&[0u8; 32]);
2559 let code_hash = b.create_vector(&[0u8; 32]);
2560 let dk = FbsDelegateKey::create(
2561 &mut b,
2562 &DelegateKeyArgs {
2563 key: Some(key),
2564 code_hash: Some(code_hash),
2565 },
2566 );
2567 let unreg = UnregisterDelegate::create(&mut b, &UnregisterDelegateArgs { key: Some(dk) });
2568 let dreq = FbsDelegateRequest::create(
2571 &mut b,
2572 &DelegateRequestArgs {
2573 delegate_request_type: DelegateRequestType(99),
2574 delegate_request: Some(unreg.as_union_value()),
2575 },
2576 );
2577 let creq = FbsClientRequest::create(
2578 &mut b,
2579 &ClientRequestArgs {
2580 client_request_type: ClientRequestType::DelegateRequest,
2581 client_request: Some(dreq.as_union_value()),
2582 },
2583 );
2584 finish_client_request_buffer(&mut b, creq);
2585 let bytes = b.finished_data().to_vec();
2586
2587 let client =
2588 root_as_client_request(&bytes).expect("verifier accepts an unknown union discriminant");
2589 let fbs_delegate = client
2590 .client_request_as_delegate_request()
2591 .expect("client_request is a DelegateRequest");
2592 let decoded = DelegateRequest::try_decode_fbs(&fbs_delegate);
2593 assert!(
2594 decoded.is_err(),
2595 "an unknown DelegateRequestType discriminant must be a clean \
2596 per-request error, never a panic that downs the connection handler"
2597 );
2598 }
2599}
2600
2601#[cfg(test)]
2629mod contract_request_wire_format {
2630 use super::ContractRequest;
2631 use crate::code_hash::CodeHash;
2632 use crate::generated::client_request::ContractRequestType;
2633 use crate::prelude::{
2634 ContractCode, ContractContainer, ContractInstanceId, ContractKey, ContractWasmAPIVersion,
2635 Parameters, RelatedContracts, State, StateSummary, UpdateData, WrappedContract,
2636 WrappedState,
2637 };
2638 use std::sync::Arc;
2639
2640 fn sample_key() -> ContractKey {
2641 ContractKey::from_id_and_code(
2642 ContractInstanceId::new([0x11; 32]),
2643 CodeHash::new([0x22; 32]),
2644 )
2645 }
2646
2647 fn sample_container() -> ContractContainer {
2648 let code = Arc::new(ContractCode::from(vec![1u8, 2, 3]));
2649 let params = Parameters::from(vec![9u8, 8, 7]);
2650 ContractContainer::Wasm(ContractWasmAPIVersion::V1(WrappedContract::new(
2651 code, params,
2652 )))
2653 }
2654
2655 fn sample_put() -> ContractRequest<'static> {
2660 ContractRequest::Put {
2661 contract: sample_container(),
2662 state: WrappedState::new(vec![0x44, 0x55, 0x66]),
2663 related_contracts: RelatedContracts::new(),
2664 subscribe: true,
2665 blocking_subscribe: false,
2666 }
2667 }
2668
2669 fn sample_update() -> ContractRequest<'static> {
2670 ContractRequest::Update {
2671 key: sample_key(),
2672 data: UpdateData::State(State::from(vec![0x77, 0x88])),
2673 }
2674 }
2675
2676 fn sample_get() -> ContractRequest<'static> {
2677 ContractRequest::Get {
2678 key: ContractInstanceId::new([0x33; 32]),
2679 return_contract_code: true,
2680 subscribe: false,
2681 blocking_subscribe: false,
2682 }
2683 }
2684
2685 fn sample_get_subscribe_pair() -> ContractRequest<'static> {
2693 ContractRequest::Get {
2694 key: ContractInstanceId::new([0x33; 32]),
2695 return_contract_code: false,
2696 subscribe: true,
2697 blocking_subscribe: false,
2698 }
2699 }
2700
2701 fn sample_subscribe() -> ContractRequest<'static> {
2702 ContractRequest::Subscribe {
2703 key: ContractInstanceId::new([0x33; 32]),
2704 summary: Some(StateSummary::from(vec![0x99, 0xAA])),
2705 }
2706 }
2707
2708 #[test]
2713 fn wire_format_is_frozen() {
2714 const PUT: &[u8] = &[
2715 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 177, 119, 236, 27,
2716 242, 109, 251, 59, 112, 16, 212, 115, 230, 212, 71, 19, 178, 155, 118, 91, 153, 198,
2717 230, 14, 203, 250, 231, 66, 222, 73, 101, 67, 3, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 68, 0,
2718 48, 164, 43, 234, 3, 4, 34, 33, 221, 91, 193, 53, 159, 47, 206, 127, 237, 159, 116, 81,
2719 44, 75, 126, 103, 73, 141, 96, 191, 52, 206, 177, 119, 236, 27, 242, 109, 251, 59, 112,
2720 16, 212, 115, 230, 212, 71, 19, 178, 155, 118, 91, 153, 198, 230, 14, 203, 250, 231,
2721 66, 222, 73, 101, 67, 3, 0, 0, 0, 0, 0, 0, 0, 68, 85, 102, 0, 0, 0, 0, 0, 0, 0, 0, 1,
2722 0,
2723 ];
2724 const UPDATE: &[u8] = &[
2725 1, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
2726 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 34, 34, 34, 34, 34, 34, 34, 34, 34,
2727 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
2728 34, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 119, 136,
2729 ];
2730 const GET: &[u8] = &[
2731 2, 0, 0, 0, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
2732 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 1, 0, 0,
2733 ];
2734 const GET_SUBSCRIBE_PAIR: &[u8] = &[
2740 2, 0, 0, 0, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
2741 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 0, 1, 0,
2742 ];
2743 const SUBSCRIBE: &[u8] = &[
2744 3, 0, 0, 0, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
2745 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 1, 2, 0, 0, 0, 0, 0, 0, 0, 153,
2746 170,
2747 ];
2748
2749 assert_eq!(
2751 bincode::serialize(&sample_put()).unwrap(),
2752 PUT,
2753 "Put (tag 0) encoding changed"
2754 );
2755 assert_eq!(
2756 bincode::serialize(&sample_update()).unwrap(),
2757 UPDATE,
2758 "Update (tag 1) encoding changed"
2759 );
2760 assert_eq!(
2761 bincode::serialize(&sample_get()).unwrap(),
2762 GET,
2763 "Get (tag 2) encoding changed"
2764 );
2765 assert_eq!(
2766 bincode::serialize(&sample_get_subscribe_pair()).unwrap(),
2767 GET_SUBSCRIBE_PAIR,
2768 "Get (tag 2) encoding changed (subscribe/blocking_subscribe pair)"
2769 );
2770 assert_eq!(
2771 bincode::serialize(&sample_subscribe()).unwrap(),
2772 SUBSCRIBE,
2773 "Subscribe (tag 3) encoding changed"
2774 );
2775
2776 assert_eq!(PUT[..4], 0u32.to_le_bytes());
2778 assert_eq!(UPDATE[..4], 1u32.to_le_bytes());
2779 assert_eq!(GET[..4], 2u32.to_le_bytes());
2780 assert_eq!(SUBSCRIBE[..4], 3u32.to_le_bytes());
2781
2782 assert!(matches!(
2785 bincode::deserialize::<ContractRequest>(PUT).unwrap(),
2786 ContractRequest::Put { .. }
2787 ));
2788 assert!(matches!(
2789 bincode::deserialize::<ContractRequest>(UPDATE).unwrap(),
2790 ContractRequest::Update { .. }
2791 ));
2792 assert!(matches!(
2793 bincode::deserialize::<ContractRequest>(GET).unwrap(),
2794 ContractRequest::Get { .. }
2795 ));
2796 assert!(matches!(
2797 bincode::deserialize::<ContractRequest>(SUBSCRIBE).unwrap(),
2798 ContractRequest::Subscribe { .. }
2799 ));
2800 }
2801
2802 #[test]
2809 fn fbs_discriminants_match_declaration_order() {
2810 assert_eq!(ContractRequestType::Put.0, 1);
2811 assert_eq!(ContractRequestType::Update.0, 2);
2812 assert_eq!(ContractRequestType::Get.0, 3);
2813 assert_eq!(ContractRequestType::Subscribe.0, 4);
2814 }
2815}
2816
2817#[cfg(test)]
2836mod client_request_wire_format {
2837 use super::{ClientRequest, ContractRequest, DelegateRequest, NodeQuery};
2838 use crate::code_hash::CodeHash;
2839 use crate::generated::client_request::ClientRequestType;
2840 use crate::prelude::{ContractInstanceId, DelegateKey};
2841 use bytes::Bytes;
2842 use std::borrow::Cow;
2843
2844 fn sample_delegate_op() -> ClientRequest<'static> {
2849 ClientRequest::DelegateOp(DelegateRequest::UnregisterDelegate(DelegateKey::new(
2850 [0x11; 32],
2851 CodeHash::new([0x22; 32]),
2852 )))
2853 }
2854
2855 fn sample_contract_op() -> ClientRequest<'static> {
2856 ClientRequest::ContractOp(ContractRequest::Subscribe {
2857 key: ContractInstanceId::new([0x33; 32]),
2858 summary: None,
2859 })
2860 }
2861
2862 fn sample_disconnect() -> ClientRequest<'static> {
2863 ClientRequest::Disconnect {
2864 cause: Some(Cow::Borrowed("bye")),
2865 }
2866 }
2867
2868 fn sample_authenticate() -> ClientRequest<'static> {
2869 ClientRequest::Authenticate {
2870 token: "tok".to_string(),
2871 }
2872 }
2873
2874 fn sample_node_queries() -> ClientRequest<'static> {
2875 ClientRequest::NodeQueries(NodeQuery::ConnectedPeers)
2876 }
2877
2878 fn sample_close() -> ClientRequest<'static> {
2879 ClientRequest::Close
2880 }
2881
2882 fn sample_stream_chunk() -> ClientRequest<'static> {
2883 ClientRequest::StreamChunk {
2884 stream_id: 1,
2885 index: 2,
2886 total: 3,
2887 data: Bytes::from_static(&[9, 9, 9]),
2888 }
2889 }
2890
2891 #[test]
2896 fn wire_format_is_frozen() {
2897 const DELEGATE_OP: &[u8] = &[
2898 0, 0, 0, 0, 2, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
2899 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 34, 34, 34, 34, 34, 34,
2900 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
2901 34, 34, 34, 34,
2902 ];
2903 const CONTRACT_OP: &[u8] = &[
2904 1, 0, 0, 0, 3, 0, 0, 0, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
2905 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 0,
2906 ];
2907 const DISCONNECT: &[u8] = &[2, 0, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 98, 121, 101];
2908 const AUTHENTICATE: &[u8] = &[3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 116, 111, 107];
2909 const NODE_QUERIES: &[u8] = &[4, 0, 0, 0, 0, 0, 0, 0];
2910 const CLOSE: &[u8] = &[5, 0, 0, 0];
2911 const STREAM_CHUNK: &[u8] = &[
2912 6, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9,
2913 ];
2914
2915 assert_eq!(
2917 bincode::serialize(&sample_delegate_op()).unwrap(),
2918 DELEGATE_OP,
2919 "DelegateOp (tag 0) encoding changed"
2920 );
2921 assert_eq!(
2922 bincode::serialize(&sample_contract_op()).unwrap(),
2923 CONTRACT_OP,
2924 "ContractOp (tag 1) encoding changed"
2925 );
2926 assert_eq!(
2927 bincode::serialize(&sample_disconnect()).unwrap(),
2928 DISCONNECT,
2929 "Disconnect (tag 2) encoding changed"
2930 );
2931 assert_eq!(
2932 bincode::serialize(&sample_authenticate()).unwrap(),
2933 AUTHENTICATE,
2934 "Authenticate (tag 3) encoding changed"
2935 );
2936 assert_eq!(
2937 bincode::serialize(&sample_node_queries()).unwrap(),
2938 NODE_QUERIES,
2939 "NodeQueries (tag 4) encoding changed"
2940 );
2941 assert_eq!(
2942 bincode::serialize(&sample_close()).unwrap(),
2943 CLOSE,
2944 "Close (tag 5) encoding changed"
2945 );
2946 assert_eq!(
2947 bincode::serialize(&sample_stream_chunk()).unwrap(),
2948 STREAM_CHUNK,
2949 "StreamChunk (tag 6) encoding changed"
2950 );
2951
2952 assert_eq!(DELEGATE_OP[..4], 0u32.to_le_bytes());
2954 assert_eq!(CONTRACT_OP[..4], 1u32.to_le_bytes());
2955 assert_eq!(DISCONNECT[..4], 2u32.to_le_bytes());
2956 assert_eq!(AUTHENTICATE[..4], 3u32.to_le_bytes());
2957 assert_eq!(NODE_QUERIES[..4], 4u32.to_le_bytes());
2958 assert_eq!(CLOSE[..4], 5u32.to_le_bytes());
2959 assert_eq!(STREAM_CHUNK[..4], 6u32.to_le_bytes());
2960
2961 assert!(matches!(
2964 bincode::deserialize::<ClientRequest>(DELEGATE_OP).unwrap(),
2965 ClientRequest::DelegateOp(_)
2966 ));
2967 assert!(matches!(
2968 bincode::deserialize::<ClientRequest>(CONTRACT_OP).unwrap(),
2969 ClientRequest::ContractOp(_)
2970 ));
2971 assert!(matches!(
2972 bincode::deserialize::<ClientRequest>(DISCONNECT).unwrap(),
2973 ClientRequest::Disconnect { .. }
2974 ));
2975 assert!(matches!(
2976 bincode::deserialize::<ClientRequest>(AUTHENTICATE).unwrap(),
2977 ClientRequest::Authenticate { .. }
2978 ));
2979 assert!(matches!(
2980 bincode::deserialize::<ClientRequest>(NODE_QUERIES).unwrap(),
2981 ClientRequest::NodeQueries(_)
2982 ));
2983 assert!(matches!(
2984 bincode::deserialize::<ClientRequest>(CLOSE).unwrap(),
2985 ClientRequest::Close
2986 ));
2987 assert!(matches!(
2988 bincode::deserialize::<ClientRequest>(STREAM_CHUNK).unwrap(),
2989 ClientRequest::StreamChunk { .. }
2990 ));
2991 }
2992
2993 #[test]
3002 fn fbs_discriminants_match_declaration_order() {
3003 assert_eq!(ClientRequestType::ContractRequest.0, 1);
3004 assert_eq!(ClientRequestType::DelegateRequest.0, 2);
3005 assert_eq!(ClientRequestType::Disconnect.0, 3);
3006 assert_eq!(ClientRequestType::Authenticate.0, 4);
3007 assert_eq!(ClientRequestType::StreamChunk.0, 5);
3008 }
3009}
3010
3011#[cfg(test)]
3027mod fbs_decode_hardening {
3028 use super::{ClientRequest, ContractRequest};
3029 use crate::client_api::TryFromFbs;
3030 use crate::contract_interface::UpdateData;
3031 use crate::generated::client_request::{
3032 finish_client_request_buffer, ApplicationMessages, ApplicationMessagesArgs,
3033 ClientRequest as FbsClientRequest, ClientRequestArgs, ClientRequestType,
3034 ContractRequest as FbsContractRequest, ContractRequestArgs, ContractRequestType,
3035 DelegateCode as FbsDelegateCode, DelegateCodeArgs,
3036 DelegateContainer as FbsDelegateContainer, DelegateContainerArgs,
3037 DelegateKey as FbsDelegateKey, DelegateKeyArgs, DelegateRequest as FbsDelegateRequest,
3038 DelegateRequestArgs, DelegateRequestType, DelegateType, Get as FbsGet, GetArgs,
3039 InboundDelegateMsg as FbsInboundDelegateMsg, InboundDelegateMsgArgs,
3040 InboundDelegateMsgType, Put as FbsPut, PutArgs, RegisterDelegate, RegisterDelegateArgs,
3041 RelatedContract, RelatedContractArgs, RelatedContracts as FbsRelatedContracts,
3042 RelatedContractsArgs, Update as FbsUpdate, UpdateArgs, WasmDelegateV1, WasmDelegateV1Args,
3043 };
3044 use crate::generated::common::{
3045 ApplicationMessage as FbsApplicationMessage, ApplicationMessageArgs,
3046 ContractCode as FbsContractCode, ContractCodeArgs,
3047 ContractContainer as FbsContractContainer, ContractContainerArgs,
3048 ContractInstanceId as FbsContractInstanceId, ContractInstanceIdArgs,
3049 ContractKey as FbsContractKey, ContractKeyArgs, ContractType, RelatedDeltaUpdate,
3050 RelatedDeltaUpdateArgs, RelatedStateAndDeltaUpdate, RelatedStateAndDeltaUpdateArgs,
3051 RelatedStateUpdate, RelatedStateUpdateArgs, StateUpdate, StateUpdateArgs,
3052 UpdateData as FbsUpdateData, UpdateDataArgs, UpdateDataType, WasmContractV1,
3053 WasmContractV1Args,
3054 };
3055
3056 type Builder<'a> = flatbuffers::FlatBufferBuilder<'a>;
3057
3058 const INSTANCE: [u8; 32] = [
3062 0x00, 0xff, 0x7a, 0x01, 0x30, 0x4f, 0x49, 0x6c, 0x2b, 0x2f, 0x5c, 0x7f, 0x80, 0xfe, 0x10,
3063 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
3064 0x20, 0x21,
3065 ];
3066 const CODE_HASH: [u8; 32] = [42u8; 32];
3067
3068 fn instance_offset<'a>(
3069 b: &mut Builder<'a>,
3070 bytes: &[u8],
3071 ) -> flatbuffers::WIPOffset<FbsContractInstanceId<'a>> {
3072 let data = b.create_vector(bytes);
3073 FbsContractInstanceId::create(b, &ContractInstanceIdArgs { data: Some(data) })
3074 }
3075
3076 fn key_offset<'a>(
3077 b: &mut Builder<'a>,
3078 instance: &[u8],
3079 code: &[u8],
3080 ) -> flatbuffers::WIPOffset<FbsContractKey<'a>> {
3081 let instance = instance_offset(b, instance);
3082 let code = b.create_vector(code);
3083 FbsContractKey::create(
3084 b,
3085 &ContractKeyArgs {
3086 instance: Some(instance),
3087 code: Some(code),
3088 },
3089 )
3090 }
3091
3092 fn delegate_key_offset<'a>(
3093 b: &mut Builder<'a>,
3094 key: &[u8],
3095 code_hash: &[u8],
3096 ) -> flatbuffers::WIPOffset<FbsDelegateKey<'a>> {
3097 let key = b.create_vector(key);
3098 let code_hash = b.create_vector(code_hash);
3099 FbsDelegateKey::create(
3100 b,
3101 &DelegateKeyArgs {
3102 key: Some(key),
3103 code_hash: Some(code_hash),
3104 },
3105 )
3106 }
3107
3108 fn finish_contract(
3110 b: &mut Builder<'_>,
3111 ty: ContractRequestType,
3112 req: flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>,
3113 ) -> Vec<u8> {
3114 let contract = FbsContractRequest::create(
3115 b,
3116 &ContractRequestArgs {
3117 contract_request_type: ty,
3118 contract_request: Some(req),
3119 },
3120 );
3121 let client = FbsClientRequest::create(
3122 b,
3123 &ClientRequestArgs {
3124 client_request_type: ClientRequestType::ContractRequest,
3125 client_request: Some(contract.as_union_value()),
3126 },
3127 );
3128 finish_client_request_buffer(b, client);
3129 b.finished_data().to_vec()
3130 }
3131
3132 fn finish_delegate(
3133 b: &mut Builder<'_>,
3134 ty: DelegateRequestType,
3135 req: flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>,
3136 ) -> Vec<u8> {
3137 let delegate = FbsDelegateRequest::create(
3138 b,
3139 &DelegateRequestArgs {
3140 delegate_request_type: ty,
3141 delegate_request: Some(req),
3142 },
3143 );
3144 let client = FbsClientRequest::create(
3145 b,
3146 &ClientRequestArgs {
3147 client_request_type: ClientRequestType::DelegateRequest,
3148 client_request: Some(delegate.as_union_value()),
3149 },
3150 );
3151 finish_client_request_buffer(b, client);
3152 b.finished_data().to_vec()
3153 }
3154
3155 fn client_request_type(d: u8, force_defaults: bool) -> Vec<u8> {
3162 let mut b = Builder::new();
3163 b.force_defaults(force_defaults);
3164 let key = key_offset(&mut b, &INSTANCE, &CODE_HASH);
3165 let get = FbsGet::create(
3166 &mut b,
3167 &GetArgs {
3168 key: Some(key),
3169 fetch_contract: false,
3170 subscribe: false,
3171 blocking_subscribe: false,
3172 },
3173 );
3174 let contract = FbsContractRequest::create(
3175 &mut b,
3176 &ContractRequestArgs {
3177 contract_request_type: ContractRequestType::Get,
3178 contract_request: Some(get.as_union_value()),
3179 },
3180 );
3181 let client = FbsClientRequest::create(
3182 &mut b,
3183 &ClientRequestArgs {
3184 client_request_type: ClientRequestType(d),
3185 client_request: Some(contract.as_union_value()),
3186 },
3187 );
3188 finish_client_request_buffer(&mut b, client);
3189 b.finished_data().to_vec()
3190 }
3191
3192 fn contract_request_type(d: u8, force_defaults: bool) -> Vec<u8> {
3193 let mut b = Builder::new();
3194 b.force_defaults(force_defaults);
3195 let key = key_offset(&mut b, &INSTANCE, &CODE_HASH);
3196 let get = FbsGet::create(
3197 &mut b,
3198 &GetArgs {
3199 key: Some(key),
3200 fetch_contract: false,
3201 subscribe: false,
3202 blocking_subscribe: false,
3203 },
3204 );
3205 finish_contract(&mut b, ContractRequestType(d), get.as_union_value())
3206 }
3207
3208 fn delegate_request_type(d: u8, force_defaults: bool) -> Vec<u8> {
3209 let mut b = Builder::new();
3210 b.force_defaults(force_defaults);
3211 let dk = delegate_key_offset(&mut b, &[7u8; 32], &CODE_HASH);
3212 let params = b.create_vector(&[1u8, 2, 3]);
3213 let payload = b.create_vector(&[9u8; 4]);
3214 let context = b.create_vector(&[0u8; 2]);
3215 let app = FbsApplicationMessage::create(
3216 &mut b,
3217 &ApplicationMessageArgs {
3218 payload: Some(payload),
3219 context: Some(context),
3220 processed: false,
3221 },
3222 );
3223 let inbound_msg = FbsInboundDelegateMsg::create(
3224 &mut b,
3225 &InboundDelegateMsgArgs {
3226 inbound_type: InboundDelegateMsgType::common_ApplicationMessage,
3227 inbound: Some(app.as_union_value()),
3228 },
3229 );
3230 let inbound = b.create_vector(&[inbound_msg]);
3231 let msgs = ApplicationMessages::create(
3232 &mut b,
3233 &ApplicationMessagesArgs {
3234 key: Some(dk),
3235 params: Some(params),
3236 inbound: Some(inbound),
3237 },
3238 );
3239 finish_delegate(&mut b, DelegateRequestType(d), msgs.as_union_value())
3240 }
3241
3242 fn contract_type(d: u8, force_defaults: bool) -> Vec<u8> {
3244 let mut b = Builder::new();
3245 b.force_defaults(force_defaults);
3246 let code_data = b.create_vector(&[0u8; 8]);
3247 let code_hash = b.create_vector(&CODE_HASH);
3248 let code = FbsContractCode::create(
3249 &mut b,
3250 &ContractCodeArgs {
3251 data: Some(code_data),
3252 code_hash: Some(code_hash),
3253 },
3254 );
3255 let key = key_offset(&mut b, &INSTANCE, &CODE_HASH);
3256 let params = b.create_vector(&[1u8, 2]);
3257 let wasm = WasmContractV1::create(
3258 &mut b,
3259 &WasmContractV1Args {
3260 data: Some(code),
3261 parameters: Some(params),
3262 key: Some(key),
3263 },
3264 );
3265 let container = FbsContractContainer::create(
3266 &mut b,
3267 &ContractContainerArgs {
3268 contract_type: ContractType(d),
3269 contract: Some(wasm.as_union_value()),
3270 },
3271 );
3272 let state = b.create_vector(&[3u8; 4]);
3273 let empty: Vec<flatbuffers::WIPOffset<RelatedContract>> = vec![];
3274 let contracts = b.create_vector(&empty);
3275 let related = FbsRelatedContracts::create(
3276 &mut b,
3277 &RelatedContractsArgs {
3278 contracts: Some(contracts),
3279 },
3280 );
3281 let put = FbsPut::create(
3282 &mut b,
3283 &PutArgs {
3284 container: Some(container),
3285 wrapped_state: Some(state),
3286 related_contracts: Some(related),
3287 subscribe: false,
3288 blocking_subscribe: false,
3289 },
3290 );
3291 finish_contract(&mut b, ContractRequestType::Put, put.as_union_value())
3292 }
3293
3294 fn delegate_type(d: u8, force_defaults: bool) -> Vec<u8> {
3296 let mut b = Builder::new();
3297 b.force_defaults(force_defaults);
3298 let code_data = b.create_vector(&[0u8; 8]);
3299 let code_hash = b.create_vector(&CODE_HASH);
3300 let code = FbsDelegateCode::create(
3301 &mut b,
3302 &DelegateCodeArgs {
3303 data: Some(code_data),
3304 code_hash: Some(code_hash),
3305 },
3306 );
3307 let dk = delegate_key_offset(&mut b, &[7u8; 32], &CODE_HASH);
3308 let params = b.create_vector(&[1u8, 2]);
3309 let wasm = WasmDelegateV1::create(
3310 &mut b,
3311 &WasmDelegateV1Args {
3312 parameters: Some(params),
3313 data: Some(code),
3314 key: Some(dk),
3315 },
3316 );
3317 let container = FbsDelegateContainer::create(
3318 &mut b,
3319 &DelegateContainerArgs {
3320 delegate_type: DelegateType(d),
3321 delegate: Some(wasm.as_union_value()),
3322 },
3323 );
3324 let cipher = b.create_vector(&[1u8; 32]);
3325 let nonce = b.create_vector(&[2u8; 24]);
3326 let register = RegisterDelegate::create(
3327 &mut b,
3328 &RegisterDelegateArgs {
3329 delegate: Some(container),
3330 cipher: Some(cipher),
3331 nonce: Some(nonce),
3332 },
3333 );
3334 finish_delegate(
3335 &mut b,
3336 DelegateRequestType::RegisterDelegate,
3337 register.as_union_value(),
3338 )
3339 }
3340
3341 fn update_data_type(d: u8, force_defaults: bool) -> Vec<u8> {
3343 let mut b = Builder::new();
3344 b.force_defaults(force_defaults);
3345 let state = b.create_vector(&[5u8; 4]);
3346 let state_update = StateUpdate::create(&mut b, &StateUpdateArgs { state: Some(state) });
3347 let data = FbsUpdateData::create(
3348 &mut b,
3349 &UpdateDataArgs {
3350 update_data_type: UpdateDataType(d),
3351 update_data: Some(state_update.as_union_value()),
3352 },
3353 );
3354 let key = key_offset(&mut b, &INSTANCE, &CODE_HASH);
3355 let update = FbsUpdate::create(
3356 &mut b,
3357 &UpdateArgs {
3358 key: Some(key),
3359 data: Some(data),
3360 },
3361 );
3362 finish_contract(&mut b, ContractRequestType::Update, update.as_union_value())
3363 }
3364
3365 fn inbound_delegate_msg_type(d: u8, force_defaults: bool) -> Vec<u8> {
3367 let mut b = Builder::new();
3368 b.force_defaults(force_defaults);
3369 let payload = b.create_vector(&[9u8; 4]);
3370 let context = b.create_vector(&[0u8; 2]);
3371 let app = FbsApplicationMessage::create(
3372 &mut b,
3373 &ApplicationMessageArgs {
3374 payload: Some(payload),
3375 context: Some(context),
3376 processed: false,
3377 },
3378 );
3379 let inbound_msg = FbsInboundDelegateMsg::create(
3380 &mut b,
3381 &InboundDelegateMsgArgs {
3382 inbound_type: InboundDelegateMsgType(d),
3383 inbound: Some(app.as_union_value()),
3384 },
3385 );
3386 let inbound = b.create_vector(&[inbound_msg]);
3387 let dk = delegate_key_offset(&mut b, &[7u8; 32], &CODE_HASH);
3388 let params = b.create_vector(&[1u8, 2, 3]);
3389 let msgs = ApplicationMessages::create(
3390 &mut b,
3391 &ApplicationMessagesArgs {
3392 key: Some(dk),
3393 params: Some(params),
3394 inbound: Some(inbound),
3395 },
3396 );
3397 finish_delegate(
3398 &mut b,
3399 DelegateRequestType::ApplicationMessages,
3400 msgs.as_union_value(),
3401 )
3402 }
3403
3404 type UnionCase = (&'static str, fn(u8, bool) -> Vec<u8>, u8);
3406
3407 #[test]
3431 fn no_union_discriminant_panics_the_decoder() {
3432 let cases: [UnionCase; 7] = [
3433 ("ClientRequestType", client_request_type, 1),
3434 ("ContractRequestType", contract_request_type, 3),
3435 ("DelegateRequestType", delegate_request_type, 1),
3436 ("ContractType", contract_type, 1),
3437 ("DelegateType", delegate_type, 1),
3438 ("UpdateDataType", update_data_type, 1),
3439 (
3440 "InboundDelegateMsgType",
3441 inbound_delegate_msg_type,
3442 InboundDelegateMsgType::common_ApplicationMessage.0,
3443 ),
3444 ];
3445
3446 for (union, build, valid) in cases {
3447 for d in 0..=u8::MAX {
3448 let bytes = build(d, false);
3452 let _ = ClientRequest::try_decode_fbs(&bytes);
3453 }
3454
3455 let out_of_range = build(200, false);
3459 let err = ClientRequest::try_decode_fbs(&out_of_range)
3460 .expect_err("{union}: an out-of-range discriminant must be a clean error");
3461 assert_eq!(
3462 err.to_string(),
3463 format!(
3464 "Failed decoding message from client request: unknown {union} \
3465 discriminant: 200"
3466 ),
3467 "{union}: the error must come from the decoder's union arm, not \
3468 from the verifier — otherwise this sweep pins nothing"
3469 );
3470
3471 let none = build(0, true);
3478 let err = ClientRequest::try_decode_fbs(&none)
3479 .expect_err("{union}: a NONE discriminant must be a clean error");
3480 assert_eq!(
3481 err.to_string(),
3482 format!(
3483 "Failed decoding message from client request: unknown {union} \
3484 discriminant: 0"
3485 ),
3486 "{union}: an explicit NONE must reach the decoder's union arm"
3487 );
3488
3489 let good = build(valid, false);
3490 assert!(
3491 ClientRequest::try_decode_fbs(&good).is_ok(),
3492 "{union}: the real discriminant must still decode; the guard \
3493 must not break the happy path"
3494 );
3495 }
3496 }
3497
3498 fn update_with_related(variant: UpdateDataType, id: &[u8]) -> Vec<u8> {
3508 let mut b = Builder::new();
3509 let related_to = instance_offset(&mut b, id);
3510 let payload = b.create_vector(&[5u8; 4]);
3511 let data_offset = match variant {
3512 UpdateDataType::RelatedStateUpdate => RelatedStateUpdate::create(
3513 &mut b,
3514 &RelatedStateUpdateArgs {
3515 related_to: Some(related_to),
3516 state: Some(payload),
3517 },
3518 )
3519 .as_union_value(),
3520 UpdateDataType::RelatedDeltaUpdate => RelatedDeltaUpdate::create(
3521 &mut b,
3522 &RelatedDeltaUpdateArgs {
3523 related_to: Some(related_to),
3524 delta: Some(payload),
3525 },
3526 )
3527 .as_union_value(),
3528 UpdateDataType::RelatedStateAndDeltaUpdate => {
3529 let delta = b.create_vector(&[6u8; 4]);
3530 RelatedStateAndDeltaUpdate::create(
3531 &mut b,
3532 &RelatedStateAndDeltaUpdateArgs {
3533 related_to: Some(related_to),
3534 state: Some(payload),
3535 delta: Some(delta),
3536 },
3537 )
3538 .as_union_value()
3539 }
3540 other => panic!("not a related update variant: {}", other.0),
3541 };
3542 let data = FbsUpdateData::create(
3543 &mut b,
3544 &UpdateDataArgs {
3545 update_data_type: variant,
3546 update_data: Some(data_offset),
3547 },
3548 );
3549 let key = key_offset(&mut b, &INSTANCE, &CODE_HASH);
3550 let update = FbsUpdate::create(
3551 &mut b,
3552 &UpdateArgs {
3553 key: Some(key),
3554 data: Some(data),
3555 },
3556 );
3557 finish_contract(&mut b, ContractRequestType::Update, update.as_union_value())
3558 }
3559
3560 fn decoded_related_to(bytes: &[u8]) -> [u8; 32] {
3561 let req = ClientRequest::try_decode_fbs(bytes)
3562 .expect("a well-formed related update must decode, not panic");
3563 let ClientRequest::ContractOp(ContractRequest::Update { data, .. }) = req else {
3564 panic!("expected an UPDATE, got {req:?}");
3565 };
3566 match data {
3567 UpdateData::RelatedState { related_to, .. }
3568 | UpdateData::RelatedDelta { related_to, .. }
3569 | UpdateData::RelatedStateAndDelta { related_to, .. } => *related_to,
3570 other => panic!("expected a related update, got {other:?}"),
3571 }
3572 }
3573
3574 #[test]
3584 fn related_state_update_round_trips_the_raw_instance_id() {
3585 let bytes = update_with_related(UpdateDataType::RelatedStateUpdate, &INSTANCE);
3586 assert_eq!(decoded_related_to(&bytes), INSTANCE);
3587 }
3588
3589 #[test]
3590 fn related_delta_update_round_trips_the_raw_instance_id() {
3591 let bytes = update_with_related(UpdateDataType::RelatedDeltaUpdate, &INSTANCE);
3592 assert_eq!(decoded_related_to(&bytes), INSTANCE);
3593 }
3594
3595 #[test]
3596 fn related_state_and_delta_update_round_trips_the_raw_instance_id() {
3597 let bytes = update_with_related(UpdateDataType::RelatedStateAndDeltaUpdate, &INSTANCE);
3598 assert_eq!(decoded_related_to(&bytes), INSTANCE);
3599 }
3600
3601 #[test]
3607 fn related_to_wrong_length_is_rejected() {
3608 for (variant, field) in [
3609 (
3610 UpdateDataType::RelatedStateUpdate,
3611 "RelatedStateUpdate.related_to.data",
3612 ),
3613 (
3614 UpdateDataType::RelatedDeltaUpdate,
3615 "RelatedDeltaUpdate.related_to.data",
3616 ),
3617 (
3618 UpdateDataType::RelatedStateAndDeltaUpdate,
3619 "RelatedStateAndDeltaUpdate.related_to.data",
3620 ),
3621 ] {
3622 let bytes = update_with_related(variant, &[1u8; 8]);
3623 let err = ClientRequest::try_decode_fbs(&bytes)
3624 .expect_err("an 8-byte related_to must be rejected");
3625 let msg = err.to_string();
3626 assert!(
3627 msg.contains(field) && msg.contains("got 8 bytes"),
3628 "the error must name {field} and the observed length, got: {msg}"
3629 );
3630 }
3631 }
3632
3633 fn put_with_related_contract(id: &[u8]) -> Vec<u8> {
3634 let mut b = Builder::new();
3635 let code_data = b.create_vector(&[0u8; 8]);
3636 let code_hash = b.create_vector(&CODE_HASH);
3637 let code = FbsContractCode::create(
3638 &mut b,
3639 &ContractCodeArgs {
3640 data: Some(code_data),
3641 code_hash: Some(code_hash),
3642 },
3643 );
3644 let key = key_offset(&mut b, &INSTANCE, &CODE_HASH);
3645 let params = b.create_vector(&[1u8, 2]);
3646 let wasm = WasmContractV1::create(
3647 &mut b,
3648 &WasmContractV1Args {
3649 data: Some(code),
3650 parameters: Some(params),
3651 key: Some(key),
3652 },
3653 );
3654 let container = FbsContractContainer::create(
3655 &mut b,
3656 &ContractContainerArgs {
3657 contract_type: ContractType::WasmContractV1,
3658 contract: Some(wasm.as_union_value()),
3659 },
3660 );
3661 let related_id = instance_offset(&mut b, id);
3662 let related_state = b.create_vector(&[8u8; 3]);
3663 let related_contract = RelatedContract::create(
3664 &mut b,
3665 &RelatedContractArgs {
3666 instance_id: Some(related_id),
3667 state: Some(related_state),
3668 },
3669 );
3670 let contracts = b.create_vector(&[related_contract]);
3671 let related = FbsRelatedContracts::create(
3672 &mut b,
3673 &RelatedContractsArgs {
3674 contracts: Some(contracts),
3675 },
3676 );
3677 let state = b.create_vector(&[3u8; 4]);
3678 let put = FbsPut::create(
3679 &mut b,
3680 &PutArgs {
3681 container: Some(container),
3682 wrapped_state: Some(state),
3683 related_contracts: Some(related),
3684 subscribe: false,
3685 blocking_subscribe: false,
3686 },
3687 );
3688 finish_contract(&mut b, ContractRequestType::Put, put.as_union_value())
3689 }
3690
3691 #[test]
3695 fn put_related_contract_round_trips_the_raw_instance_id() {
3696 let bytes = put_with_related_contract(&INSTANCE);
3697 let req = ClientRequest::try_decode_fbs(&bytes)
3698 .expect("a PUT carrying a related contract must decode, not panic");
3699 let ClientRequest::ContractOp(ContractRequest::Put {
3700 related_contracts, ..
3701 }) = req
3702 else {
3703 panic!("expected a PUT, got {req:?}");
3704 };
3705 let ids: Vec<[u8; 32]> = related_contracts
3706 .into_owned()
3707 .states()
3708 .map(|(id, _)| **id)
3709 .collect();
3710 assert_eq!(
3711 ids,
3712 vec![INSTANCE],
3713 "the related contract id must round-trip"
3714 );
3715 }
3716
3717 #[test]
3718 fn put_related_contract_wrong_length_id_is_rejected() {
3719 let bytes = put_with_related_contract(&[1u8; 8]);
3720 let err = ClientRequest::try_decode_fbs(&bytes)
3721 .expect_err("an 8-byte related contract id must be rejected");
3722 let msg = err.to_string();
3723 assert!(
3724 msg.contains("RelatedContract.instance_id") && msg.contains("got 8 bytes"),
3725 "got: {msg}"
3726 );
3727 }
3728
3729 fn unregister_delegate(key_len: usize) -> Vec<u8> {
3730 use crate::generated::client_request::{UnregisterDelegate, UnregisterDelegateArgs};
3731 let mut b = Builder::new();
3732 let dk = delegate_key_offset(&mut b, &vec![7u8; key_len], &CODE_HASH);
3733 let unregister =
3734 UnregisterDelegate::create(&mut b, &UnregisterDelegateArgs { key: Some(dk) });
3735 finish_delegate(
3736 &mut b,
3737 DelegateRequestType::UnregisterDelegate,
3738 unregister.as_union_value(),
3739 )
3740 }
3741
3742 #[test]
3748 fn delegate_key_wrong_length_is_rejected_not_panicking() {
3749 let short = unregister_delegate(8);
3750 let err = ClientRequest::try_decode_fbs(&short)
3751 .expect_err("an 8-byte delegate key must be rejected");
3752 let msg = err.to_string();
3753 assert!(
3754 msg.contains("DelegateKey.key") && msg.contains("got 8 bytes"),
3755 "got: {msg}"
3756 );
3757
3758 let long = unregister_delegate(64);
3759 let err = ClientRequest::try_decode_fbs(&long)
3760 .expect_err("a 64-byte delegate key must be rejected");
3761 assert!(err.to_string().contains("got 64 bytes"), "got: {err}");
3762
3763 let good = unregister_delegate(32);
3764 assert!(
3765 ClientRequest::try_decode_fbs(&good).is_ok(),
3766 "a 32-byte delegate key must still decode"
3767 );
3768 }
3769
3770 fn register_delegate(cipher_len: usize, nonce_len: usize) -> Vec<u8> {
3771 let mut b = Builder::new();
3772 let code_data = b.create_vector(&[0u8; 8]);
3773 let code_hash = b.create_vector(&CODE_HASH);
3774 let code = FbsDelegateCode::create(
3775 &mut b,
3776 &DelegateCodeArgs {
3777 data: Some(code_data),
3778 code_hash: Some(code_hash),
3779 },
3780 );
3781 let dk = delegate_key_offset(&mut b, &[7u8; 32], &CODE_HASH);
3782 let params = b.create_vector(&[1u8, 2]);
3783 let wasm = WasmDelegateV1::create(
3784 &mut b,
3785 &WasmDelegateV1Args {
3786 parameters: Some(params),
3787 data: Some(code),
3788 key: Some(dk),
3789 },
3790 );
3791 let container = FbsDelegateContainer::create(
3792 &mut b,
3793 &DelegateContainerArgs {
3794 delegate_type: DelegateType::WasmDelegateV1,
3795 delegate: Some(wasm.as_union_value()),
3796 },
3797 );
3798 let cipher = b.create_vector(&vec![1u8; cipher_len]);
3799 let nonce = b.create_vector(&vec![2u8; nonce_len]);
3800 let register = RegisterDelegate::create(
3801 &mut b,
3802 &RegisterDelegateArgs {
3803 delegate: Some(container),
3804 cipher: Some(cipher),
3805 nonce: Some(nonce),
3806 },
3807 );
3808 finish_delegate(
3809 &mut b,
3810 DelegateRequestType::RegisterDelegate,
3811 register.as_union_value(),
3812 )
3813 }
3814
3815 #[test]
3819 fn register_delegate_wrong_length_cipher_or_nonce_is_rejected() {
3820 let err = ClientRequest::try_decode_fbs(®ister_delegate(16, 24))
3821 .expect_err("a 16-byte cipher must be rejected");
3822 let msg = err.to_string();
3823 assert!(
3824 msg.contains("RegisterDelegate.cipher") && msg.contains("got 16 bytes"),
3825 "got: {msg}"
3826 );
3827
3828 let err = ClientRequest::try_decode_fbs(®ister_delegate(32, 8))
3829 .expect_err("an 8-byte nonce must be rejected");
3830 let msg = err.to_string();
3831 assert!(
3832 msg.contains("RegisterDelegate.nonce") && msg.contains("got 8 bytes"),
3833 "got: {msg}"
3834 );
3835
3836 assert!(
3837 ClientRequest::try_decode_fbs(®ister_delegate(32, 24)).is_ok(),
3838 "correct cipher/nonce lengths must still decode"
3839 );
3840 }
3841
3842 #[test]
3853 fn host_response_encodes_related_to_as_raw_bytes() {
3854 use crate::client_api::{ContractResponse, HostResponse};
3855 use crate::contract_interface::{ContractInstanceId, ContractKey, State};
3856 use crate::generated::host_response::{root_as_host_response, ContractResponseType};
3857
3858 let related = ContractInstanceId::new(INSTANCE);
3859 let key = ContractKey::from_params_and_code(
3860 crate::parameters::Parameters::from(vec![1u8, 2]),
3861 crate::contract_interface::ContractCode::from(vec![0u8; 8]),
3862 );
3863 let response = HostResponse::ContractResponse(ContractResponse::UpdateNotification {
3864 key,
3865 update: UpdateData::RelatedState {
3866 related_to: related,
3867 state: State::from(vec![9u8; 4]),
3868 },
3869 });
3870
3871 let bytes = response.into_fbs_bytes().expect("encoding must succeed");
3872 let host = root_as_host_response(&bytes).expect("the encoder must emit a valid buffer");
3873 let contract = host
3874 .response_as_contract_response()
3875 .expect("a ContractResponse");
3876 assert_eq!(
3877 contract.contract_response_type(),
3878 ContractResponseType::UpdateNotification
3879 );
3880 let notification = contract
3881 .contract_response_as_update_notification()
3882 .expect("an UpdateNotification");
3883 let related_update = notification
3884 .update()
3885 .update_data_as_related_state_update()
3886 .expect("a RelatedStateUpdate");
3887
3888 assert_eq!(
3889 related_update.related_to().data().bytes(),
3890 &INSTANCE,
3891 "related_to must be the 32 RAW id bytes. Encoding it as base58 text \
3892 puts ~44 ASCII bytes in a field the TypeScript SDK reads as a raw \
3893 Uint8Array, and that our own decoder now rejects."
3894 );
3895 }
3896
3897 #[test]
3901 fn secrets_id_wrong_length_hash_is_rejected_not_panicking() {
3902 use crate::delegate_interface::SecretsId;
3903 use crate::generated::common::{SecretsId as FbsSecretsId, SecretsIdArgs};
3904
3905 let build = |hash_len: usize| {
3906 let mut b = Builder::new();
3907 let key = b.create_vector(&[1u8, 2, 3]);
3908 let hash = b.create_vector(&vec![4u8; hash_len]);
3909 let id = FbsSecretsId::create(
3910 &mut b,
3911 &SecretsIdArgs {
3912 key: Some(key),
3913 hash: Some(hash),
3914 },
3915 );
3916 b.finish_minimal(id);
3917 b.finished_data().to_vec()
3918 };
3919
3920 let bytes = build(8);
3921 let fbs = flatbuffers::root::<FbsSecretsId>(&bytes)
3922 .expect("the verifier accepts a short required vector");
3923 let err = SecretsId::try_decode_fbs(&fbs).expect_err("an 8-byte hash must be rejected");
3924 assert!(
3925 err.to_string().contains("SecretsId.hash") && err.to_string().contains("got 8 bytes"),
3926 "got: {err}"
3927 );
3928
3929 let bytes = build(32);
3930 let fbs = flatbuffers::root::<FbsSecretsId>(&bytes).expect("well-formed");
3931 assert!(
3932 SecretsId::try_decode_fbs(&fbs).is_ok(),
3933 "a 32-byte hash must still decode"
3934 );
3935 }
3936}
3937
3938#[cfg(test)]
3963mod struct_field_wire_compat {
3964 use super::{HostResponse, NodeDiagnosticsResponse, QueryResponse, WrappedState};
3965 use serde::{Deserialize, Serialize};
3966
3967 #[derive(Serialize, Deserialize, Debug, PartialEq)]
3969 struct OldShape {
3970 first: u32,
3971 second: String,
3972 }
3973
3974 #[derive(Serialize, Deserialize, Debug, PartialEq)]
3976 struct NewShape {
3977 first: u32,
3978 second: String,
3979 added: Vec<u8>,
3980 }
3981
3982 #[derive(Serialize, Deserialize, Debug, PartialEq)]
3985 struct NewShapeWithDefault {
3986 first: u32,
3987 second: String,
3988 #[serde(default)]
3989 added: Vec<u8>,
3990 }
3991
3992 fn old_value() -> OldShape {
3993 OldShape {
3994 first: 7,
3995 second: "diagnostics".to_string(),
3996 }
3997 }
3998
3999 fn new_value() -> NewShape {
4000 NewShape {
4001 first: 7,
4002 second: "diagnostics".to_string(),
4003 added: vec![0xDE, 0xAD],
4004 }
4005 }
4006
4007 #[test]
4019 fn a_new_field_is_silently_ignored_by_an_old_receiver() {
4020 let bytes = bincode::serialize(&new_value()).expect("new value must serialize");
4021 let decoded: OldShape =
4022 bincode::deserialize(&bytes).expect("trailing bytes are allowed, so this succeeds");
4023 assert_eq!(decoded, old_value(), "the shared prefix decodes unchanged");
4024 }
4025
4026 #[test]
4038 fn an_old_payload_fails_to_decode_once_a_field_is_appended() {
4039 let bytes = bincode::serialize(&old_value()).expect("old value must serialize");
4040 let decoded = bincode::deserialize::<NewShape>(&bytes);
4041 assert!(
4042 decoded.is_err(),
4043 "an old payload must NOT decode into a struct with an appended field; \
4044 if this ever passes, the compatibility table on this module is wrong"
4045 );
4046 }
4047
4048 #[test]
4061 fn serde_default_does_not_rescue_a_missing_bincode_field() {
4062 let bytes = bincode::serialize(&old_value()).expect("old value must serialize");
4063 assert!(
4064 bincode::deserialize::<NewShapeWithDefault>(&bytes).is_err(),
4065 "#[serde(default)] must not be mistaken for bincode wire compatibility"
4066 );
4067
4068 let json = serde_json::to_string(&old_value()).expect("old value must serialize to JSON");
4071 let from_json: NewShapeWithDefault =
4072 serde_json::from_str(&json).expect("serde(default) fills the missing field in JSON");
4073 assert!(from_json.added.is_empty());
4074 }
4075
4076 #[test]
4088 fn an_appended_field_corrupts_whatever_follows_it() {
4089 #[allow(dead_code)] #[derive(Serialize, Deserialize, Debug)]
4091 struct OldEnvelope {
4092 payload: OldShape,
4093 trailer: u32,
4094 }
4095 #[derive(Serialize, Deserialize, Debug)]
4096 struct NewEnvelope {
4097 payload: NewShape,
4098 trailer: u32,
4099 }
4100
4101 let bytes = bincode::serialize(&NewEnvelope {
4102 payload: new_value(),
4103 trailer: 0xABCD_EF01,
4104 })
4105 .expect("new envelope must serialize");
4106
4107 match bincode::deserialize::<OldEnvelope>(&bytes) {
4108 Ok(decoded) => assert_ne!(
4109 decoded.trailer, 0xABCD_EF01,
4110 "if this ever holds, bincode grew field framing and this whole module \
4111 needs revisiting"
4112 ),
4113 Err(_) => {
4114 }
4119 }
4120 }
4121
4122 #[test]
4137 fn node_diagnostics_response_is_terminal_in_its_message() {
4138 let mut contract_states = std::collections::HashMap::new();
4147 contract_states.insert(
4148 "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9".to_string(),
4149 super::ContractState {
4150 subscribers: 0xAB,
4151 subscriber_peer_ids: vec!["peer-a".to_string()],
4152 size_bytes: 0xCDEF,
4153 },
4154 );
4155 let response = NodeDiagnosticsResponse {
4156 node_info: Some(super::NodeInfo {
4157 peer_id: "peer-self".to_string(),
4158 is_gateway: true,
4159 location: Some("0.5".to_string()),
4160 listening_address: Some("0.0.0.0:31337".to_string()),
4161 uptime_seconds: 0x1234,
4162 }),
4163 network_info: Some(super::NetworkInfo {
4164 connected_peers: vec![("peer-x".to_string(), "10.0.0.1:31337".to_string())],
4165 active_connections: 7,
4166 }),
4167 subscriptions: vec![super::SubscriptionInfo {
4168 contract_key: crate::contract_interface::ContractInstanceId::new([7u8; 32]),
4169 client_id: 42,
4170 }],
4171 contract_states,
4172 system_metrics: Some(super::SystemMetrics {
4173 active_connections: 0x5678,
4174 hosting_contracts: 0x9A,
4175 }),
4176 connected_peers_detailed: vec![super::ConnectedPeerInfo {
4177 peer_id: "peer-x".to_string(),
4178 address: "10.0.0.1:31337".to_string(),
4179 }],
4180 };
4181
4182 let inner = bincode::serialize(&response).expect("response must serialize");
4183 assert_ne!(
4184 *inner.last().expect("the fixture encodes to something"),
4185 0,
4186 "ends_with needs a non-zero TAIL, not merely a non-zero byte somewhere. If the last \
4187 field ever becomes empty again, ends_with silently degenerates into 'ends in zeros' \
4188 and stops catching the append it exists to catch"
4189 );
4190 let whole = bincode::serialize(&HostResponse::<WrappedState>::QueryResponse(
4194 QueryResponse::NodeDiagnostics(response),
4195 ))
4196 .expect("host response must serialize");
4197
4198 assert!(
4199 whole.ends_with(&inner),
4200 "NodeDiagnosticsResponse must remain the LAST thing in its encoding. \
4201 Something now follows it, so appending a field to it would no longer be \
4202 trailing-byte-safe for older clients — it would silently corrupt whatever \
4203 was added after it."
4204 );
4205
4206 assert_eq!(
4212 whole.len(),
4213 8 + inner.len(),
4214 "exactly two enum tags may precede the payload and nothing may follow it"
4215 );
4216 }
4217
4218 #[test]
4240 fn the_shipped_size_bytes_append_is_an_instance_of_this() {
4241 use super::{
4242 ConnectedPeerInfo, ContractState, NetworkInfo, NodeInfo, SubscriptionInfo,
4243 SystemMetrics,
4244 };
4245 use crate::contract_interface::ContractInstanceId;
4246 use std::collections::HashMap;
4247
4248 #[allow(dead_code)] #[derive(Serialize, Deserialize, Debug)]
4251 struct OldContractState {
4252 subscribers: u32,
4253 subscriber_peer_ids: Vec<String>,
4254 }
4255
4256 #[allow(dead_code)]
4259 #[derive(Serialize, Deserialize, Debug)]
4260 struct OldNodeDiagnosticsResponse {
4261 node_info: Option<NodeInfo>,
4262 network_info: Option<NetworkInfo>,
4263 subscriptions: Vec<SubscriptionInfo>,
4264 contract_states: HashMap<String, OldContractState>,
4265 system_metrics: Option<SystemMetrics>,
4266 connected_peers_detailed: Vec<ConnectedPeerInfo>,
4267 }
4268
4269 let mut contract_states = HashMap::new();
4270 contract_states.insert(
4271 "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9".to_string(),
4272 ContractState {
4273 subscribers: 3,
4274 subscriber_peer_ids: vec!["peer-a".to_string()],
4275 size_bytes: 1024,
4276 },
4277 );
4278
4279 let new_node_response = NodeDiagnosticsResponse {
4280 node_info: None,
4281 network_info: None,
4282 subscriptions: vec![SubscriptionInfo {
4283 contract_key: ContractInstanceId::new([7u8; 32]),
4284 client_id: 42,
4285 }],
4286 contract_states,
4287 system_metrics: Some(SystemMetrics {
4288 active_connections: 1,
4289 hosting_contracts: 1,
4290 }),
4291 connected_peers_detailed: vec![ConnectedPeerInfo {
4292 peer_id: "peer-x".to_string(),
4293 address: "10.0.0.1:31337".to_string(),
4294 }],
4295 };
4296
4297 let bytes = bincode::serialize(&new_node_response).expect("a new node's response");
4298
4299 let faithfully_decoded = match bincode::deserialize::<OldNodeDiagnosticsResponse>(&bytes) {
4300 Err(_) => false,
4303 Ok(decoded) => {
4306 decoded.system_metrics.is_some() && decoded.connected_peers_detailed.len() == 1
4307 }
4308 };
4309
4310 assert!(
4311 !faithfully_decoded,
4312 "an appended field on a non-terminal struct must not decode cleanly on an older \
4313 reader; if this ever passes, bincode gained field framing and the compatibility \
4314 table on this module needs rewriting"
4315 );
4316 }
4317}