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 RegisterDelegateWithPredecessors {
621 delegate: DelegateContainer,
622 cipher: [u8; 32],
623 nonce: [u8; 24],
624 predecessors: Vec<DelegateKey>,
625 },
626}
627
628impl DelegateRequest<'_> {
629 pub fn into_owned(self) -> DelegateRequest<'static> {
630 match self {
631 DelegateRequest::ApplicationMessages {
632 key,
633 inbound,
634 params,
635 } => DelegateRequest::ApplicationMessages {
636 key,
637 params: params.into_owned(),
638 inbound: inbound.into_iter().map(|e| e.into_owned()).collect(),
639 },
640 DelegateRequest::RegisterDelegate {
641 delegate,
642 cipher,
643 nonce,
644 } => DelegateRequest::RegisterDelegate {
645 delegate,
646 cipher,
647 nonce,
648 },
649 DelegateRequest::UnregisterDelegate(key) => DelegateRequest::UnregisterDelegate(key),
650 DelegateRequest::RegisterDelegateWithPredecessors {
651 delegate,
652 cipher,
653 nonce,
654 predecessors,
655 } => DelegateRequest::RegisterDelegateWithPredecessors {
656 delegate,
657 cipher,
658 nonce,
659 predecessors,
660 },
661 }
662 }
663
664 pub fn key(&self) -> &DelegateKey {
665 match self {
666 DelegateRequest::ApplicationMessages { key, .. } => key,
667 DelegateRequest::RegisterDelegate { delegate, .. } => delegate.key(),
668 DelegateRequest::UnregisterDelegate(key) => key,
669 DelegateRequest::RegisterDelegateWithPredecessors { delegate, .. } => delegate.key(),
670 }
671 }
672}
673
674impl Display for ClientRequest<'_> {
675 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
676 match self {
677 ClientRequest::ContractOp(op) => match op {
678 ContractRequest::Put {
679 contract, state, ..
680 } => {
681 write!(
682 f,
683 "ContractRequest::Put for contract `{contract}` with state {state}"
684 )
685 }
686 ContractRequest::Update { key, .. } => write!(f, "update request for {key}"),
687 ContractRequest::Get {
688 key,
689 return_contract_code: contract,
690 ..
691 } => {
692 write!(
693 f,
694 "ContractRequest::Get for key `{key}` (fetch full contract: {contract})"
695 )
696 }
697 ContractRequest::Subscribe { key, .. } => {
698 write!(f, "ContractRequest::Subscribe for `{key}`")
699 }
700 },
701 ClientRequest::DelegateOp(op) => match op {
702 DelegateRequest::ApplicationMessages { key, inbound, .. } => {
703 write!(
704 f,
705 "DelegateRequest::ApplicationMessages for `{key}` with {} messages",
706 inbound.len()
707 )
708 }
709 DelegateRequest::RegisterDelegate { delegate, .. } => {
710 write!(
711 f,
712 "DelegateRequest::RegisterDelegate for delegate.key()=`{}`",
713 delegate.key()
714 )
715 }
716 DelegateRequest::UnregisterDelegate(key) => {
717 write!(f, "DelegateRequest::UnregisterDelegate for key `{key}`")
718 }
719 DelegateRequest::RegisterDelegateWithPredecessors {
720 delegate,
721 predecessors,
722 ..
723 } => {
724 write!(
725 f,
726 "DelegateRequest::RegisterDelegateWithPredecessors for delegate.key()=`{}` with {} predecessor(s)",
727 delegate.key(),
728 predecessors.len()
729 )
730 }
731 },
732 ClientRequest::Disconnect { .. } => write!(f, "client disconnected"),
733 ClientRequest::Authenticate { .. } => write!(f, "authenticate"),
734 ClientRequest::NodeQueries(query) => write!(f, "node queries: {:?}", query),
735 ClientRequest::Close => write!(f, "close"),
736 ClientRequest::StreamChunk {
737 stream_id,
738 index,
739 total,
740 ..
741 } => write!(f, "stream chunk {index}/{total} (stream {stream_id})"),
742 }
743 }
744}
745
746impl<'a> TryFromFbs<&FbsDelegateRequest<'a>> for DelegateRequest<'a> {
748 fn try_decode_fbs(request: &FbsDelegateRequest<'a>) -> Result<Self, WsApiError> {
749 let req = {
750 match request.delegate_request_type() {
751 DelegateRequestType::ApplicationMessages => {
752 let app_msg = request.delegate_request_as_application_messages().unwrap();
753 let key = DelegateKey::try_decode_fbs(&app_msg.key())?;
754 let params = Parameters::from(app_msg.params().bytes());
755 let inbound = app_msg
756 .inbound()
757 .iter()
758 .map(|msg| InboundDelegateMsg::try_decode_fbs(&msg))
759 .collect::<Result<Vec<_>, _>>()?;
760 DelegateRequest::ApplicationMessages {
761 key,
762 params,
763 inbound,
764 }
765 }
766 DelegateRequestType::RegisterDelegate => {
767 let register = request.delegate_request_as_register_delegate().unwrap();
768 let delegate = DelegateContainer::try_decode_fbs(®ister.delegate())?;
769 let cipher = crate::client_api::fixed_size_field::<32>(
776 "RegisterDelegate.cipher",
777 register.cipher().bytes(),
778 )?;
779 let nonce = crate::client_api::fixed_size_field::<24>(
780 "RegisterDelegate.nonce",
781 register.nonce().bytes(),
782 )?;
783 DelegateRequest::RegisterDelegate {
784 delegate,
785 cipher,
786 nonce,
787 }
788 }
789 DelegateRequestType::UnregisterDelegate => {
790 let unregister = request.delegate_request_as_unregister_delegate().unwrap();
791 let key = DelegateKey::try_decode_fbs(&unregister.key())?;
792 DelegateRequest::UnregisterDelegate(key)
793 }
794 other => {
801 return Err(crate::client_api::unknown_union_discriminant(
802 "DelegateRequestType",
803 other.0,
804 ));
805 }
806 }
807 };
808
809 Ok(req)
810 }
811}
812
813#[derive(Serialize, Deserialize, Debug, Clone)]
815#[non_exhaustive]
816pub enum HostResponse<T = WrappedState> {
817 ContractResponse(#[serde(bound(deserialize = "T: DeserializeOwned"))] ContractResponse<T>),
818 DelegateResponse {
819 key: DelegateKey,
820 values: Vec<OutboundDelegateMsg>,
821 },
822 QueryResponse(QueryResponse),
823 Ok,
825 StreamChunk {
827 stream_id: u32,
828 index: u32,
829 total: u32,
830 data: Bytes,
831 },
832 StreamHeader {
836 stream_id: u32,
837 total_bytes: u64,
838 content: StreamContent,
839 },
840}
841
842#[derive(Debug, Serialize, Deserialize, Clone)]
844pub enum StreamContent {
845 GetResponse {
847 key: ContractKey,
848 includes_contract: bool,
849 },
850 Raw,
852}
853
854type Peer = String;
855
856#[derive(Serialize, Deserialize, Debug, Clone)]
857pub enum QueryResponse {
858 ConnectedPeers { peers: Vec<(Peer, SocketAddr)> },
859 NetworkDebug(NetworkDebugInfo),
860 NodeDiagnostics(NodeDiagnosticsResponse),
861 NeighborHosting(NeighborHostingInfo),
862}
863
864#[derive(Serialize, Deserialize, Debug, Clone)]
865pub struct NetworkDebugInfo {
866 pub subscriptions: Vec<SubscriptionInfo>,
867 pub connected_peers: Vec<(String, SocketAddr)>,
868}
869
870#[derive(Serialize, Deserialize, Debug, Clone)]
871pub struct NodeDiagnosticsResponse {
872 pub node_info: Option<NodeInfo>,
874
875 pub network_info: Option<NetworkInfo>,
877
878 pub subscriptions: Vec<SubscriptionInfo>,
880
881 pub contract_states: std::collections::HashMap<String, ContractState>,
892
893 pub system_metrics: Option<SystemMetrics>,
895
896 pub connected_peers_detailed: Vec<ConnectedPeerInfo>,
898}
899
900#[derive(Serialize, Deserialize, Debug, Clone)]
901pub struct NodeInfo {
902 pub peer_id: String,
903 pub is_gateway: bool,
904 pub location: Option<String>,
905 pub listening_address: Option<String>,
906 pub uptime_seconds: u64,
907}
908
909#[derive(Serialize, Deserialize, Debug, Clone)]
910pub struct NetworkInfo {
911 pub connected_peers: Vec<(String, String)>, pub active_connections: usize,
913}
914
915#[derive(Serialize, Deserialize, Debug, Clone)]
916pub struct ContractState {
917 pub subscribers: u32,
919 pub subscriber_peer_ids: Vec<String>,
921 #[serde(default)]
923 pub size_bytes: u64,
924}
925
926#[derive(Serialize, Deserialize, Debug, Clone)]
927pub struct SystemMetrics {
928 pub active_connections: u32,
929 pub hosting_contracts: u32,
930}
931
932#[derive(Serialize, Deserialize, Debug, Clone)]
933pub struct SubscriptionInfo {
934 pub contract_key: ContractInstanceId,
935 pub client_id: usize,
936}
937
938#[derive(Serialize, Deserialize, Debug, Clone)]
940pub struct ConnectedPeerInfo {
941 pub peer_id: String,
942 pub address: String,
943}
944
945#[derive(Serialize, Deserialize, Debug, Clone)]
946pub enum NodeQuery {
947 ConnectedPeers,
948 SubscriptionInfo,
949 NodeDiagnostics {
950 config: NodeDiagnosticsConfig,
952 },
953 NeighborHostingInfo,
955}
956
957#[derive(Serialize, Deserialize, Debug, Clone)]
959pub struct NeighborHostingInfo {
960 pub my_hosted: Vec<ContractHostingEntry>,
962 pub neighbor_hosting: Vec<NeighborHostingDetail>,
964 pub stats: HostingStats,
966}
967
968#[derive(Serialize, Deserialize, Debug, Clone)]
969pub struct ContractHostingEntry {
970 pub contract_key: String,
972 pub hosting_hash: u32,
974 pub hosted_since: u64,
976}
977
978#[derive(Serialize, Deserialize, Debug, Clone)]
979pub struct NeighborHostingDetail {
980 pub peer_id: String,
982 pub known_contracts: Vec<u32>,
984 pub last_update: u64,
986 pub update_count: u64,
988}
989
990#[derive(Serialize, Deserialize, Debug, Clone)]
991pub struct HostingStats {
992 pub hosting_announces_sent: u64,
994 pub hosting_announces_received: u64,
996 pub updates_via_proximity: u64,
998 pub updates_via_subscription: u64,
1000 pub false_positive_forwards: u64,
1002 pub avg_neighbor_hosting_size: f32,
1004}
1005
1006#[derive(Serialize, Deserialize, Debug, Clone)]
1007pub struct NodeDiagnosticsConfig {
1008 pub include_node_info: bool,
1010
1011 pub include_network_info: bool,
1013
1014 pub include_subscriptions: bool,
1016
1017 pub contract_keys: Vec<ContractKey>,
1019
1020 pub include_system_metrics: bool,
1022
1023 pub include_detailed_peer_info: bool,
1025
1026 pub include_subscriber_peer_ids: bool,
1028}
1029
1030impl NodeDiagnosticsConfig {
1031 pub fn for_update_propagation_debugging(contract_key: ContractKey) -> Self {
1033 Self {
1034 include_node_info: true,
1035 include_network_info: true,
1036 include_subscriptions: true,
1037 contract_keys: vec![contract_key],
1038 include_system_metrics: true,
1039 include_detailed_peer_info: true,
1040 include_subscriber_peer_ids: true,
1041 }
1042 }
1043
1044 pub fn basic_status() -> Self {
1046 Self {
1047 include_node_info: true,
1048 include_network_info: true,
1049 include_subscriptions: false,
1050 contract_keys: vec![],
1051 include_system_metrics: false,
1052 include_detailed_peer_info: false,
1053 include_subscriber_peer_ids: false,
1054 }
1055 }
1056
1057 pub fn full() -> Self {
1059 Self {
1060 include_node_info: true,
1061 include_network_info: true,
1062 include_subscriptions: true,
1063 contract_keys: vec![], include_system_metrics: true,
1065 include_detailed_peer_info: true,
1066 include_subscriber_peer_ids: true,
1067 }
1068 }
1069}
1070
1071impl HostResponse {
1072 pub fn unwrap_put(self) -> ContractKey {
1073 if let Self::ContractResponse(ContractResponse::PutResponse { key }) = self {
1074 key
1075 } else {
1076 panic!("called `HostResponse::unwrap_put()` on other than `PutResponse` value")
1077 }
1078 }
1079
1080 pub fn unwrap_get(self) -> (WrappedState, Option<ContractContainer>) {
1081 if let Self::ContractResponse(ContractResponse::GetResponse {
1082 contract, state, ..
1083 }) = self
1084 {
1085 (state, contract)
1086 } else {
1087 panic!("called `HostResponse::unwrap_put()` on other than `PutResponse` value")
1088 }
1089 }
1090
1091 pub fn into_fbs_bytes(self) -> Result<Vec<u8>, Box<ClientError>> {
1092 let mut builder = flatbuffers::FlatBufferBuilder::new();
1093 match self {
1094 HostResponse::ContractResponse(res) => match res {
1095 ContractResponse::PutResponse { key } => {
1096 let instance_data = builder.create_vector(key.as_bytes());
1097 let instance_offset = FbsContractInstanceId::create(
1098 &mut builder,
1099 &ContractInstanceIdArgs {
1100 data: Some(instance_data),
1101 },
1102 );
1103
1104 let code = Some(builder.create_vector(&key.code_hash().0));
1105 let key_offset = FbsContractKey::create(
1106 &mut builder,
1107 &ContractKeyArgs {
1108 instance: Some(instance_offset),
1109 code,
1110 },
1111 );
1112
1113 let put_offset = FbsPutResponse::create(
1114 &mut builder,
1115 &PutResponseArgs {
1116 key: Some(key_offset),
1117 },
1118 );
1119
1120 let contract_response_offset = FbsContractResponse::create(
1121 &mut builder,
1122 &ContractResponseArgs {
1123 contract_response: Some(put_offset.as_union_value()),
1124 contract_response_type: ContractResponseType::PutResponse,
1125 },
1126 );
1127
1128 let response_offset = FbsHostResponse::create(
1129 &mut builder,
1130 &HostResponseArgs {
1131 response: Some(contract_response_offset.as_union_value()),
1132 response_type: HostResponseType::ContractResponse,
1133 },
1134 );
1135
1136 finish_host_response_buffer(&mut builder, response_offset);
1137 Ok(builder.finished_data().to_vec())
1138 }
1139 ContractResponse::UpdateResponse { key, summary } => {
1140 let instance_data = builder.create_vector(key.as_bytes());
1141 let instance_offset = FbsContractInstanceId::create(
1142 &mut builder,
1143 &ContractInstanceIdArgs {
1144 data: Some(instance_data),
1145 },
1146 );
1147
1148 let code = Some(builder.create_vector(&key.code_hash().0));
1149
1150 let key_offset = FbsContractKey::create(
1151 &mut builder,
1152 &ContractKeyArgs {
1153 instance: Some(instance_offset),
1154 code,
1155 },
1156 );
1157
1158 let summary_data = builder.create_vector(&summary.into_bytes());
1159
1160 let update_response_offset = FbsUpdateResponse::create(
1161 &mut builder,
1162 &UpdateResponseArgs {
1163 key: Some(key_offset),
1164 summary: Some(summary_data),
1165 },
1166 );
1167
1168 let contract_response_offset = FbsContractResponse::create(
1169 &mut builder,
1170 &ContractResponseArgs {
1171 contract_response: Some(update_response_offset.as_union_value()),
1172 contract_response_type: ContractResponseType::UpdateResponse,
1173 },
1174 );
1175
1176 let response_offset = FbsHostResponse::create(
1177 &mut builder,
1178 &HostResponseArgs {
1179 response: Some(contract_response_offset.as_union_value()),
1180 response_type: HostResponseType::ContractResponse,
1181 },
1182 );
1183
1184 finish_host_response_buffer(&mut builder, response_offset);
1185 Ok(builder.finished_data().to_vec())
1186 }
1187 ContractResponse::GetResponse {
1188 key,
1189 contract: contract_container,
1190 state,
1191 } => {
1192 let instance_data = builder.create_vector(key.as_bytes());
1193 let instance_offset = FbsContractInstanceId::create(
1194 &mut builder,
1195 &ContractInstanceIdArgs {
1196 data: Some(instance_data),
1197 },
1198 );
1199
1200 let code = Some(builder.create_vector(&key.code_hash().0));
1201 let key_offset = FbsContractKey::create(
1202 &mut builder,
1203 &ContractKeyArgs {
1204 instance: Some(instance_offset),
1205 code,
1206 },
1207 );
1208
1209 let container_offset = if let Some(contract) = contract_container {
1210 let data = builder.create_vector(contract.key().as_bytes());
1211
1212 let instance_offset = FbsContractInstanceId::create(
1213 &mut builder,
1214 &ContractInstanceIdArgs { data: Some(data) },
1215 );
1216
1217 let code = Some(builder.create_vector(&contract.key().code_hash().0));
1218 let contract_key_offset = FbsContractKey::create(
1219 &mut builder,
1220 &ContractKeyArgs {
1221 instance: Some(instance_offset),
1222 code,
1223 },
1224 );
1225
1226 let contract_data =
1227 builder.create_vector(contract.clone().unwrap_v1().data.data());
1228 let contract_code_hash =
1229 builder.create_vector(&contract.clone().unwrap_v1().data.hash().0);
1230
1231 let contract_code_offset = ContractCode::create(
1232 &mut builder,
1233 &ContractCodeArgs {
1234 data: Some(contract_data),
1235 code_hash: Some(contract_code_hash),
1236 },
1237 );
1238
1239 let contract_params =
1240 builder.create_vector(&contract.clone().params().into_bytes());
1241
1242 let contract_offset = match contract {
1243 Wasm(V1(..)) => WasmContractV1::create(
1244 &mut builder,
1245 &WasmContractV1Args {
1246 key: Some(contract_key_offset),
1247 data: Some(contract_code_offset),
1248 parameters: Some(contract_params),
1249 },
1250 ),
1251 };
1252
1253 Some(FbsContractContainer::create(
1254 &mut builder,
1255 &ContractContainerArgs {
1256 contract_type: ContractType::WasmContractV1,
1257 contract: Some(contract_offset.as_union_value()),
1258 },
1259 ))
1260 } else {
1261 None
1262 };
1263
1264 let state_data = builder.create_vector(&state);
1265
1266 let get_offset = FbsGetResponse::create(
1267 &mut builder,
1268 &GetResponseArgs {
1269 key: Some(key_offset),
1270 contract: container_offset,
1271 state: Some(state_data),
1272 },
1273 );
1274
1275 let contract_response_offset = FbsContractResponse::create(
1276 &mut builder,
1277 &ContractResponseArgs {
1278 contract_response_type: ContractResponseType::GetResponse,
1279 contract_response: Some(get_offset.as_union_value()),
1280 },
1281 );
1282
1283 let response_offset = FbsHostResponse::create(
1284 &mut builder,
1285 &HostResponseArgs {
1286 response: Some(contract_response_offset.as_union_value()),
1287 response_type: HostResponseType::ContractResponse,
1288 },
1289 );
1290
1291 finish_host_response_buffer(&mut builder, response_offset);
1292 Ok(builder.finished_data().to_vec())
1293 }
1294 ContractResponse::UpdateNotification { key, update } => {
1295 let instance_data = builder.create_vector(key.as_bytes());
1296 let instance_offset = FbsContractInstanceId::create(
1297 &mut builder,
1298 &ContractInstanceIdArgs {
1299 data: Some(instance_data),
1300 },
1301 );
1302
1303 let code = Some(builder.create_vector(&key.code_hash().0));
1304 let key_offset = FbsContractKey::create(
1305 &mut builder,
1306 &ContractKeyArgs {
1307 instance: Some(instance_offset),
1308 code,
1309 },
1310 );
1311
1312 let update_data = match update {
1313 State(state) => {
1314 let state_data = builder.create_vector(&state.into_bytes());
1315 let state_update_offset = StateUpdate::create(
1316 &mut builder,
1317 &StateUpdateArgs {
1318 state: Some(state_data),
1319 },
1320 );
1321 FbsUpdateData::create(
1322 &mut builder,
1323 &UpdateDataArgs {
1324 update_data_type: UpdateDataType::StateUpdate,
1325 update_data: Some(state_update_offset.as_union_value()),
1326 },
1327 )
1328 }
1329 Delta(delta) => {
1330 let delta_data = builder.create_vector(&delta.into_bytes());
1331 let update_offset = DeltaUpdate::create(
1332 &mut builder,
1333 &DeltaUpdateArgs {
1334 delta: Some(delta_data),
1335 },
1336 );
1337 FbsUpdateData::create(
1338 &mut builder,
1339 &UpdateDataArgs {
1340 update_data_type: UpdateDataType::DeltaUpdate,
1341 update_data: Some(update_offset.as_union_value()),
1342 },
1343 )
1344 }
1345 StateAndDelta { state, delta } => {
1346 let state_data = builder.create_vector(&state.into_bytes());
1347 let delta_data = builder.create_vector(&delta.into_bytes());
1348
1349 let update_offset = StateAndDeltaUpdate::create(
1350 &mut builder,
1351 &StateAndDeltaUpdateArgs {
1352 state: Some(state_data),
1353 delta: Some(delta_data),
1354 },
1355 );
1356
1357 FbsUpdateData::create(
1358 &mut builder,
1359 &UpdateDataArgs {
1360 update_data_type: UpdateDataType::StateAndDeltaUpdate,
1361 update_data: Some(update_offset.as_union_value()),
1362 },
1363 )
1364 }
1365 RelatedState { related_to, state } => {
1366 let state_data = builder.create_vector(&state.into_bytes());
1367 let instance_data = builder.create_vector(related_to.as_bytes());
1374
1375 let instance_offset = FbsContractInstanceId::create(
1376 &mut builder,
1377 &ContractInstanceIdArgs {
1378 data: Some(instance_data),
1379 },
1380 );
1381
1382 let update_offset = RelatedStateUpdate::create(
1383 &mut builder,
1384 &RelatedStateUpdateArgs {
1385 related_to: Some(instance_offset),
1386 state: Some(state_data),
1387 },
1388 );
1389
1390 FbsUpdateData::create(
1391 &mut builder,
1392 &UpdateDataArgs {
1393 update_data_type: UpdateDataType::RelatedStateUpdate,
1394 update_data: Some(update_offset.as_union_value()),
1395 },
1396 )
1397 }
1398 RelatedDelta { related_to, delta } => {
1399 let instance_data = builder.create_vector(related_to.as_bytes());
1406 let delta_data = builder.create_vector(&delta.into_bytes());
1407
1408 let instance_offset = FbsContractInstanceId::create(
1409 &mut builder,
1410 &ContractInstanceIdArgs {
1411 data: Some(instance_data),
1412 },
1413 );
1414
1415 let update_offset = RelatedDeltaUpdate::create(
1416 &mut builder,
1417 &RelatedDeltaUpdateArgs {
1418 related_to: Some(instance_offset),
1419 delta: Some(delta_data),
1420 },
1421 );
1422
1423 FbsUpdateData::create(
1424 &mut builder,
1425 &UpdateDataArgs {
1426 update_data_type: UpdateDataType::RelatedDeltaUpdate,
1427 update_data: Some(update_offset.as_union_value()),
1428 },
1429 )
1430 }
1431 RelatedStateAndDelta {
1432 related_to,
1433 state,
1434 delta,
1435 } => {
1436 let instance_data = builder.create_vector(related_to.as_bytes());
1443 let state_data = builder.create_vector(&state.into_bytes());
1444 let delta_data = builder.create_vector(&delta.into_bytes());
1445
1446 let instance_offset = FbsContractInstanceId::create(
1447 &mut builder,
1448 &ContractInstanceIdArgs {
1449 data: Some(instance_data),
1450 },
1451 );
1452
1453 let update_offset = RelatedStateAndDeltaUpdate::create(
1454 &mut builder,
1455 &RelatedStateAndDeltaUpdateArgs {
1456 related_to: Some(instance_offset),
1457 state: Some(state_data),
1458 delta: Some(delta_data),
1459 },
1460 );
1461
1462 FbsUpdateData::create(
1463 &mut builder,
1464 &UpdateDataArgs {
1465 update_data_type: UpdateDataType::RelatedStateAndDeltaUpdate,
1466 update_data: Some(update_offset.as_union_value()),
1467 },
1468 )
1469 }
1470 };
1471
1472 let update_notification_offset = FbsUpdateNotification::create(
1473 &mut builder,
1474 &UpdateNotificationArgs {
1475 key: Some(key_offset),
1476 update: Some(update_data),
1477 },
1478 );
1479
1480 let put_response_offset = FbsContractResponse::create(
1481 &mut builder,
1482 &ContractResponseArgs {
1483 contract_response_type: ContractResponseType::UpdateNotification,
1484 contract_response: Some(update_notification_offset.as_union_value()),
1485 },
1486 );
1487
1488 let host_response_offset = FbsHostResponse::create(
1489 &mut builder,
1490 &HostResponseArgs {
1491 response_type: HostResponseType::ContractResponse,
1492 response: Some(put_response_offset.as_union_value()),
1493 },
1494 );
1495
1496 finish_host_response_buffer(&mut builder, host_response_offset);
1497 Ok(builder.finished_data().to_vec())
1498 }
1499 ContractResponse::SubscribeResponse { key, .. } => {
1500 let instance_data = builder.create_vector(key.as_bytes());
1504 let instance_offset = FbsContractInstanceId::create(
1505 &mut builder,
1506 &ContractInstanceIdArgs {
1507 data: Some(instance_data),
1508 },
1509 );
1510 let code = Some(builder.create_vector(&key.code_hash().0));
1511 let key_offset = FbsContractKey::create(
1512 &mut builder,
1513 &ContractKeyArgs {
1514 instance: Some(instance_offset),
1515 code,
1516 },
1517 );
1518 let put_offset = FbsPutResponse::create(
1519 &mut builder,
1520 &PutResponseArgs {
1521 key: Some(key_offset),
1522 },
1523 );
1524 let contract_response_offset = FbsContractResponse::create(
1525 &mut builder,
1526 &ContractResponseArgs {
1527 contract_response_type: ContractResponseType::PutResponse,
1528 contract_response: Some(put_offset.as_union_value()),
1529 },
1530 );
1531 let host_response_offset = FbsHostResponse::create(
1532 &mut builder,
1533 &HostResponseArgs {
1534 response_type: HostResponseType::ContractResponse,
1535 response: Some(contract_response_offset.as_union_value()),
1536 },
1537 );
1538 finish_host_response_buffer(&mut builder, host_response_offset);
1539 Ok(builder.finished_data().to_vec())
1540 }
1541 ContractResponse::NotFound { instance_id } => {
1542 let instance_data = builder.create_vector(instance_id.as_bytes());
1543 let instance_offset = FbsContractInstanceId::create(
1544 &mut builder,
1545 &ContractInstanceIdArgs {
1546 data: Some(instance_data),
1547 },
1548 );
1549
1550 let not_found_offset = FbsNotFound::create(
1551 &mut builder,
1552 &NotFoundArgs {
1553 instance_id: Some(instance_offset),
1554 },
1555 );
1556
1557 let contract_response_offset = FbsContractResponse::create(
1558 &mut builder,
1559 &ContractResponseArgs {
1560 contract_response_type: ContractResponseType::NotFound,
1561 contract_response: Some(not_found_offset.as_union_value()),
1562 },
1563 );
1564
1565 let response_offset = FbsHostResponse::create(
1566 &mut builder,
1567 &HostResponseArgs {
1568 response: Some(contract_response_offset.as_union_value()),
1569 response_type: HostResponseType::ContractResponse,
1570 },
1571 );
1572
1573 finish_host_response_buffer(&mut builder, response_offset);
1574 Ok(builder.finished_data().to_vec())
1575 }
1576 },
1577 HostResponse::DelegateResponse { key, values } => {
1578 let key_data = builder.create_vector(key.bytes());
1579 let code_hash_data = builder.create_vector(&key.code_hash().0);
1580 let key_offset = FbsDelegateKey::create(
1581 &mut builder,
1582 &DelegateKeyArgs {
1583 key: Some(key_data),
1584 code_hash: Some(code_hash_data),
1585 },
1586 );
1587 let mut messages: Vec<WIPOffset<FbsOutboundDelegateMsg>> = Vec::new();
1588 values.iter().for_each(|msg| match msg {
1589 OutboundDelegateMsg::ApplicationMessage(app) => {
1590 let payload_data = builder.create_vector(&app.payload);
1591 let delegate_context_data = builder.create_vector(app.context.as_ref());
1592 let app_offset = FbsApplicationMessage::create(
1593 &mut builder,
1594 &ApplicationMessageArgs {
1595 payload: Some(payload_data),
1596 context: Some(delegate_context_data),
1597 processed: app.processed,
1598 },
1599 );
1600 let msg = FbsOutboundDelegateMsg::create(
1601 &mut builder,
1602 &OutboundDelegateMsgArgs {
1603 inbound_type: OutboundDelegateMsgType::common_ApplicationMessage,
1604 inbound: Some(app_offset.as_union_value()),
1605 },
1606 );
1607 messages.push(msg);
1608 }
1609 OutboundDelegateMsg::RequestUserInput(input) => {
1610 let message_data = builder.create_vector(input.message.bytes());
1611 let mut responses: Vec<WIPOffset<FbsClientResponse>> = Vec::new();
1612 input.responses.iter().for_each(|resp| {
1613 let response_data = builder.create_vector(resp.bytes());
1614 let response = FbsClientResponse::create(
1615 &mut builder,
1616 &ClientResponseArgs {
1617 data: Some(response_data),
1618 },
1619 );
1620 responses.push(response)
1621 });
1622 let responses_offset = builder.create_vector(&responses);
1623 let input_offset = FbsRequestUserInput::create(
1624 &mut builder,
1625 &RequestUserInputArgs {
1626 request_id: input.request_id,
1627 message: Some(message_data),
1628 responses: Some(responses_offset),
1629 },
1630 );
1631 let msg = FbsOutboundDelegateMsg::create(
1632 &mut builder,
1633 &OutboundDelegateMsgArgs {
1634 inbound_type: OutboundDelegateMsgType::RequestUserInput,
1635 inbound: Some(input_offset.as_union_value()),
1636 },
1637 );
1638 messages.push(msg);
1639 }
1640 OutboundDelegateMsg::ContextUpdated(context) => {
1641 let context_data = builder.create_vector(context.as_ref());
1642 let context_offset = FbsContextUpdated::create(
1643 &mut builder,
1644 &ContextUpdatedArgs {
1645 context: Some(context_data),
1646 },
1647 );
1648 let msg = FbsOutboundDelegateMsg::create(
1649 &mut builder,
1650 &OutboundDelegateMsgArgs {
1651 inbound_type: OutboundDelegateMsgType::ContextUpdated,
1652 inbound: Some(context_offset.as_union_value()),
1653 },
1654 );
1655 messages.push(msg);
1656 }
1657 OutboundDelegateMsg::GetContractRequest(_) => {
1658 tracing::error!(
1661 "GetContractRequest reached client serialization - this is a bug"
1662 );
1663 }
1664 OutboundDelegateMsg::PutContractRequest(_) => {
1665 tracing::error!(
1668 "PutContractRequest reached client serialization - this is a bug"
1669 );
1670 }
1671 OutboundDelegateMsg::UpdateContractRequest(_) => {
1672 tracing::error!(
1673 "UpdateContractRequest reached client serialization - this is a bug"
1674 );
1675 }
1676 OutboundDelegateMsg::SubscribeContractRequest(_) => {
1677 tracing::error!(
1678 "SubscribeContractRequest reached client serialization - this is a bug"
1679 );
1680 }
1681 OutboundDelegateMsg::SendDelegateMessage(_) => {
1682 tracing::error!(
1683 "SendDelegateMessage reached client serialization - this is a bug"
1684 );
1685 }
1686 });
1687 let messages_offset = builder.create_vector(&messages);
1688 let delegate_response_offset = FbsDelegateResponse::create(
1689 &mut builder,
1690 &DelegateResponseArgs {
1691 key: Some(key_offset),
1692 values: Some(messages_offset),
1693 },
1694 );
1695 let host_response_offset = FbsHostResponse::create(
1696 &mut builder,
1697 &HostResponseArgs {
1698 response_type: HostResponseType::DelegateResponse,
1699 response: Some(delegate_response_offset.as_union_value()),
1700 },
1701 );
1702 finish_host_response_buffer(&mut builder, host_response_offset);
1703 Ok(builder.finished_data().to_vec())
1704 }
1705 HostResponse::Ok => {
1706 let ok_offset = FbsOk::create(&mut builder, &OkArgs { msg: None });
1707 let host_response_offset = FbsHostResponse::create(
1708 &mut builder,
1709 &HostResponseArgs {
1710 response_type: HostResponseType::Ok,
1711 response: Some(ok_offset.as_union_value()),
1712 },
1713 );
1714 finish_host_response_buffer(&mut builder, host_response_offset);
1715 Ok(builder.finished_data().to_vec())
1716 }
1717 HostResponse::QueryResponse(_) => unimplemented!(),
1718 HostResponse::StreamChunk {
1719 stream_id,
1720 index,
1721 total,
1722 data,
1723 } => {
1724 let data_offset = builder.create_vector(&data);
1725 let chunk_offset = FbsHostStreamChunk::create(
1726 &mut builder,
1727 &FbsHostStreamChunkArgs {
1728 stream_id,
1729 index,
1730 total,
1731 data: Some(data_offset),
1732 },
1733 );
1734 let host_response_offset = FbsHostResponse::create(
1735 &mut builder,
1736 &HostResponseArgs {
1737 response_type: HostResponseType::StreamChunk,
1738 response: Some(chunk_offset.as_union_value()),
1739 },
1740 );
1741 finish_host_response_buffer(&mut builder, host_response_offset);
1742 Ok(builder.finished_data().to_vec())
1743 }
1744 HostResponse::StreamHeader { .. } => {
1745 Err(Box::new(ClientError::from(ErrorKind::Unhandled {
1749 cause: "StreamHeader is not supported over flatbuffers encoding".into(),
1750 })))
1751 }
1752 }
1753 }
1754}
1755
1756impl Display for HostResponse {
1757 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1758 match self {
1759 HostResponse::ContractResponse(res) => match res {
1760 ContractResponse::PutResponse { key } => {
1761 f.write_fmt(format_args!("put response for `{key}`"))
1762 }
1763 ContractResponse::UpdateResponse { key, .. } => {
1764 f.write_fmt(format_args!("update response for `{key}`"))
1765 }
1766 ContractResponse::GetResponse { key, .. } => {
1767 f.write_fmt(format_args!("get response for `{key}`"))
1768 }
1769 ContractResponse::UpdateNotification { key, .. } => {
1770 f.write_fmt(format_args!("update notification for `{key}`"))
1771 }
1772 ContractResponse::SubscribeResponse { key, .. } => {
1773 f.write_fmt(format_args!("subscribe response for `{key}`"))
1774 }
1775 ContractResponse::NotFound { instance_id } => {
1776 f.write_fmt(format_args!("not found for `{instance_id}`"))
1777 }
1778 },
1779 HostResponse::DelegateResponse { .. } => write!(f, "delegate responses"),
1780 HostResponse::Ok => write!(f, "ok response"),
1781 HostResponse::QueryResponse(_) => write!(f, "query response"),
1782 HostResponse::StreamChunk {
1783 stream_id,
1784 index,
1785 total,
1786 ..
1787 } => write!(f, "stream chunk {index}/{total} (stream {stream_id})"),
1788 HostResponse::StreamHeader {
1789 stream_id,
1790 total_bytes,
1791 ..
1792 } => write!(f, "stream header (stream {stream_id}, {total_bytes} bytes)"),
1793 }
1794 }
1795}
1796
1797#[derive(Clone, Serialize, Deserialize, Debug)]
1798#[non_exhaustive]
1799pub enum ContractResponse<T = WrappedState> {
1800 GetResponse {
1801 key: ContractKey,
1802 contract: Option<ContractContainer>,
1803 #[serde(bound(deserialize = "T: DeserializeOwned"))]
1804 state: T,
1805 },
1806 PutResponse {
1807 key: ContractKey,
1808 },
1809 UpdateNotification {
1811 key: ContractKey,
1812 #[serde(deserialize_with = "UpdateData::deser_update_data")]
1813 update: UpdateData<'static>,
1814 },
1815 UpdateResponse {
1817 key: ContractKey,
1818 #[serde(deserialize_with = "StateSummary::deser_state_summary")]
1819 summary: StateSummary<'static>,
1820 },
1821 SubscribeResponse {
1822 key: ContractKey,
1823 subscribed: bool,
1824 },
1825 NotFound {
1829 instance_id: ContractInstanceId,
1831 },
1832}
1833
1834impl<T> From<ContractResponse<T>> for HostResponse<T> {
1835 fn from(value: ContractResponse<T>) -> HostResponse<T> {
1836 HostResponse::ContractResponse(value)
1837 }
1838}
1839
1840#[cfg(test)]
1841mod node_diagnostics_response_tests {
1842 use super::{
1843 ConnectedPeerInfo, ContractState, NetworkInfo, NodeDiagnosticsResponse, NodeInfo,
1844 SubscriptionInfo, SystemMetrics,
1845 };
1846 use crate::contract_interface::ContractInstanceId;
1847 use std::collections::HashMap;
1848
1849 #[test]
1866 fn node_diagnostics_response_json_round_trips() {
1867 let mut contract_states = HashMap::new();
1868 contract_states.insert(
1869 "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9".to_string(),
1870 ContractState {
1871 subscribers: 3,
1872 subscriber_peer_ids: vec!["peer-a".to_string(), "peer-b".to_string()],
1873 size_bytes: 1024,
1874 },
1875 );
1876
1877 let response = NodeDiagnosticsResponse {
1878 node_info: Some(NodeInfo {
1879 peer_id: "peer-self".to_string(),
1880 is_gateway: true,
1881 location: Some("0.5".to_string()),
1882 listening_address: Some("0.0.0.0:31337".to_string()),
1883 uptime_seconds: 3600,
1884 }),
1885 network_info: Some(NetworkInfo {
1886 connected_peers: vec![("peer-x".to_string(), "10.0.0.1:31337".to_string())],
1887 active_connections: 1,
1888 }),
1889 subscriptions: vec![SubscriptionInfo {
1890 contract_key: ContractInstanceId::new([7u8; 32]),
1891 client_id: 42,
1892 }],
1893 contract_states,
1894 system_metrics: Some(SystemMetrics {
1895 active_connections: 1,
1896 hosting_contracts: 1,
1897 }),
1898 connected_peers_detailed: vec![ConnectedPeerInfo {
1899 peer_id: "peer-x".to_string(),
1900 address: "10.0.0.1:31337".to_string(),
1901 }],
1902 };
1903
1904 let json = serde_json::to_string(&response).expect("must serialize to JSON");
1905 let parsed: serde_json::Value = serde_json::from_str(&json).expect("output is valid JSON");
1906
1907 let obj = parsed.as_object().expect("top-level must be object");
1909 assert_eq!(obj.len(), 6, "expected six top-level fields, got {obj:?}");
1910 assert_eq!(parsed["node_info"]["peer_id"], "peer-self");
1911 assert_eq!(parsed["network_info"]["active_connections"], 1);
1912 assert_eq!(parsed["subscriptions"][0]["client_id"], 42);
1913 assert_eq!(parsed["system_metrics"]["hosting_contracts"], 1);
1914 assert_eq!(parsed["connected_peers_detailed"][0]["peer_id"], "peer-x");
1915
1916 let states = parsed["contract_states"]
1917 .as_object()
1918 .expect("contract_states must be a JSON object");
1919 assert_eq!(states.len(), 1);
1920 assert_eq!(
1921 states["6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9"]["subscribers"],
1922 3
1923 );
1924
1925 let bytes = bincode::serialize(&response).expect("bincode must serialize");
1929 let decoded: NodeDiagnosticsResponse =
1930 bincode::deserialize(&bytes).expect("bincode must round-trip");
1931 assert_eq!(
1932 decoded.contract_states.len(),
1933 1,
1934 "bincode round-trip preserves contract_states entries"
1935 );
1936 }
1937}
1938
1939#[cfg(test)]
1940mod client_request_test {
1941 use crate::client_api::{ContractRequest, TryFromFbs, WsApiError};
1942 use crate::contract_interface::UpdateData;
1943 use crate::generated::client_request::root_as_client_request;
1944
1945 const EXPECTED_ENCODED_CONTRACT_ID: &str = "6kVs66bKaQAC6ohr8b43SvJ95r36tc2hnG7HezmaJHF9";
1946
1947 #[test]
1948 fn test_build_contract_put_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1949 let put_req_op = vec![
1950 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,
1951 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,
1952 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,
1953 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,
1954 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,
1955 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,
1956 85, 111, 11, 171, 40, 85, 240, 177, 207, 81, 106, 157, 173, 90, 234, 2, 250, 253, 75,
1957 210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17, 108, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6,
1958 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,
1959 3, 4, 5, 6, 7, 8, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
1960 ];
1961 let request = if let Ok(client_request) = root_as_client_request(&put_req_op) {
1962 let contract_request = client_request.client_request_as_contract_request().unwrap();
1963 ContractRequest::try_decode_fbs(&contract_request)?
1964 } else {
1965 panic!("failed to decode client request")
1966 };
1967
1968 match request {
1969 ContractRequest::Put {
1970 contract,
1971 state,
1972 related_contracts: _,
1973 subscribe,
1974 blocking_subscribe,
1975 } => {
1976 assert_eq!(
1977 contract.to_string(),
1978 "WasmContainer([api=0.0.1](D8fdVLbRyMLw5mZtPRpWMFcrXGN2z8Nq8UGcLGPFBg2W))"
1979 );
1980 assert_eq!(contract.unwrap_v1().data.data(), &[1, 2, 3, 4, 5, 6, 7, 8]);
1981 assert_eq!(state.to_vec(), &[1, 2, 3, 4, 5, 6, 7, 8]);
1982 assert!(!subscribe);
1983 assert!(!blocking_subscribe);
1984 }
1985 _ => panic!("wrong contract request type"),
1986 }
1987
1988 Ok(())
1989 }
1990
1991 #[test]
1992 fn test_build_contract_get_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
1993 let get_req_op = vec![
1994 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,
1995 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,
1996 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,
1997 4, 0, 0, 0, 32, 0, 0, 0, 85, 111, 11, 171, 40, 85, 240, 177, 207, 81, 106, 157, 173,
1998 90, 234, 2, 250, 253, 75, 210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17, 108,
1999 ];
2000 let request = if let Ok(client_request) = root_as_client_request(&get_req_op) {
2001 let contract_request = client_request.client_request_as_contract_request().unwrap();
2002 ContractRequest::try_decode_fbs(&contract_request)?
2003 } else {
2004 panic!("failed to decode client request")
2005 };
2006
2007 match request {
2008 ContractRequest::Get {
2009 key,
2010 return_contract_code: fetch_contract,
2011 subscribe,
2012 blocking_subscribe,
2013 } => {
2014 assert_eq!(key.encode(), EXPECTED_ENCODED_CONTRACT_ID);
2015 assert!(!fetch_contract);
2016 assert!(!subscribe);
2017 assert!(!blocking_subscribe);
2018 }
2019 _ => panic!("wrong contract request type"),
2020 }
2021
2022 Ok(())
2023 }
2024
2025 #[test]
2040 fn test_build_contract_update_op_from_fbs() -> Result<(), Box<dyn std::error::Error>> {
2041 use crate::generated::client_request::{
2042 finish_client_request_buffer, ClientRequest as FbsClientRequest, ClientRequestArgs,
2043 ClientRequestType, ContractRequest as FbsContractRequest, ContractRequestArgs,
2044 ContractRequestType, Update as FbsUpdate, UpdateArgs,
2045 };
2046 use crate::generated::common::{
2047 ContractInstanceId as FbsContractInstanceId, ContractInstanceIdArgs,
2048 ContractKey as FbsContractKey, ContractKeyArgs, DeltaUpdate, DeltaUpdateArgs,
2049 UpdateData as FbsUpdateData, UpdateDataArgs, UpdateDataType,
2050 };
2051 use crate::prelude::ContractInstanceId;
2052
2053 let instance_id = ContractInstanceId::try_from(EXPECTED_ENCODED_CONTRACT_ID.to_string())?;
2056 let code_hash = [42u8; 32];
2059 let delta_bytes = [1u8, 2, 3, 4, 5, 6, 7, 8];
2060
2061 let mut b = flatbuffers::FlatBufferBuilder::new();
2062
2063 let instance_data = b.create_vector(instance_id.as_bytes());
2064 let instance_offset = FbsContractInstanceId::create(
2065 &mut b,
2066 &ContractInstanceIdArgs {
2067 data: Some(instance_data),
2068 },
2069 );
2070 let code = Some(b.create_vector(&code_hash));
2071 let key_offset = FbsContractKey::create(
2072 &mut b,
2073 &ContractKeyArgs {
2074 instance: Some(instance_offset),
2075 code,
2076 },
2077 );
2078
2079 let delta = b.create_vector(&delta_bytes);
2080 let delta_offset = DeltaUpdate::create(&mut b, &DeltaUpdateArgs { delta: Some(delta) });
2081 let update_data_offset = FbsUpdateData::create(
2082 &mut b,
2083 &UpdateDataArgs {
2084 update_data_type: UpdateDataType::DeltaUpdate,
2085 update_data: Some(delta_offset.as_union_value()),
2086 },
2087 );
2088
2089 let update_offset = FbsUpdate::create(
2090 &mut b,
2091 &UpdateArgs {
2092 key: Some(key_offset),
2093 data: Some(update_data_offset),
2094 },
2095 );
2096 let contract_offset = FbsContractRequest::create(
2097 &mut b,
2098 &ContractRequestArgs {
2099 contract_request_type: ContractRequestType::Update,
2100 contract_request: Some(update_offset.as_union_value()),
2101 },
2102 );
2103 let client_offset = FbsClientRequest::create(
2104 &mut b,
2105 &ClientRequestArgs {
2106 client_request_type: ClientRequestType::ContractRequest,
2107 client_request: Some(contract_offset.as_union_value()),
2108 },
2109 );
2110 finish_client_request_buffer(&mut b, client_offset);
2111
2112 let update_op = b.finished_data().to_vec();
2113 let request = if let Ok(client_request) = root_as_client_request(&update_op) {
2114 let contract_request = client_request.client_request_as_contract_request().unwrap();
2115 ContractRequest::try_decode_fbs(&contract_request)?
2116 } else {
2117 panic!("failed to decode client request")
2118 };
2119
2120 match request {
2121 ContractRequest::Update { key, data } => {
2122 assert_eq!(key.encoded_contract_id(), EXPECTED_ENCODED_CONTRACT_ID);
2123 assert_eq!(
2124 key.code_hash().as_ref(),
2125 &code_hash,
2126 "the code hash must survive decode unchanged, not be re-hashed"
2127 );
2128 match data {
2129 UpdateData::Delta(delta) => {
2130 assert_eq!(delta.to_vec(), &delta_bytes)
2131 }
2132 _ => panic!("wrong update data type"),
2133 }
2134 }
2135 _ => panic!("wrong contract request type"),
2136 }
2137
2138 Ok(())
2139 }
2140
2141 const TS_SDK_EXPECTED_UPDATE_REQ: &[u8] = &[
2168 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,
2169 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,
2170 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,
2171 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,
2172 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,
2173 106, 157, 173, 90, 234, 2, 250, 253, 75, 210, 62, 7, 6, 34, 75, 26, 229, 230, 107, 167, 17,
2174 108,
2175 ];
2176
2177 #[test]
2186 fn typescript_sdk_instance_only_update_is_rejected_with_guidance() {
2187 let client_request = root_as_client_request(TS_SDK_EXPECTED_UPDATE_REQ)
2188 .expect("the TS SDK blob must still be a well-formed ClientRequest");
2189 let contract_request = client_request
2190 .client_request_as_contract_request()
2191 .expect("the TS SDK blob must still be a ContractRequest");
2192
2193 let err = ContractRequest::try_decode_fbs(&contract_request)
2194 .expect_err("an instance-id-only UPDATE must be rejected");
2195 let msg = err.to_string();
2196
2197 assert!(
2198 msg.contains("ContractKey.code") && msg.contains("got 0 bytes"),
2199 "the TS SDK's zero-length code must be named explicitly, got: {msg}"
2200 );
2201 assert!(
2202 msg.contains("new ContractKey(instance, code)"),
2203 "the error must tell a TypeScript developer how to build the key, got: {msg}"
2204 );
2205 assert!(
2206 msg.contains("4978"),
2207 "the error must point at the tracking issue for the real fix, got: {msg}"
2208 );
2209 }
2210
2211 fn client_request_with_instance_len(instance_len: usize, subscribe: bool) -> Vec<u8> {
2219 use crate::generated::client_request::{
2220 finish_client_request_buffer, ClientRequest as FbsClientRequest, ClientRequestArgs,
2221 ClientRequestType, ContractRequest as FbsContractRequest, ContractRequestArgs,
2222 ContractRequestType, Get as FbsGet, GetArgs, Subscribe as FbsSubscribe, SubscribeArgs,
2223 };
2224 use crate::generated::common::{
2225 ContractInstanceId as FbsContractInstanceId, ContractInstanceIdArgs,
2226 ContractKey as FbsContractKey, ContractKeyArgs,
2227 };
2228
2229 let mut b = flatbuffers::FlatBufferBuilder::new();
2230 let instance_data = b.create_vector(&vec![1u8; instance_len]);
2231 let instance_offset = FbsContractInstanceId::create(
2232 &mut b,
2233 &ContractInstanceIdArgs {
2234 data: Some(instance_data),
2235 },
2236 );
2237 let code = Some(b.create_vector(&[42u8; 32]));
2238 let key_offset = FbsContractKey::create(
2239 &mut b,
2240 &ContractKeyArgs {
2241 instance: Some(instance_offset),
2242 code,
2243 },
2244 );
2245
2246 let (request_type, request_offset) = if subscribe {
2247 let sub = FbsSubscribe::create(
2248 &mut b,
2249 &SubscribeArgs {
2250 key: Some(key_offset),
2251 summary: None,
2252 },
2253 );
2254 (ContractRequestType::Subscribe, sub.as_union_value())
2255 } else {
2256 let get = FbsGet::create(
2257 &mut b,
2258 &GetArgs {
2259 key: Some(key_offset),
2260 fetch_contract: false,
2261 subscribe: false,
2262 blocking_subscribe: false,
2263 },
2264 );
2265 (ContractRequestType::Get, get.as_union_value())
2266 };
2267
2268 let contract_offset = FbsContractRequest::create(
2269 &mut b,
2270 &ContractRequestArgs {
2271 contract_request_type: request_type,
2272 contract_request: Some(request_offset),
2273 },
2274 );
2275 let client_offset = FbsClientRequest::create(
2276 &mut b,
2277 &ClientRequestArgs {
2278 client_request_type: ClientRequestType::ContractRequest,
2279 client_request: Some(contract_offset.as_union_value()),
2280 },
2281 );
2282 finish_client_request_buffer(&mut b, client_offset);
2283 b.finished_data().to_vec()
2284 }
2285
2286 fn decode_client_request(bytes: &[u8]) -> Result<ContractRequest<'_>, WsApiError> {
2287 let client_request =
2288 root_as_client_request(bytes).expect("must be a well-formed ClientRequest");
2289 let contract_request = client_request
2290 .client_request_as_contract_request()
2291 .expect("must be a ContractRequest");
2292 ContractRequest::try_decode_fbs(&contract_request)
2293 }
2294
2295 #[test]
2304 fn get_with_wrong_length_instance_is_rejected_not_panicking() {
2305 let bytes = client_request_with_instance_len(8, false);
2306 let short = decode_client_request(&bytes)
2307 .expect_err("a GET with an 8-byte instance must be rejected");
2308 assert!(
2309 short.to_string().contains("ContractKey.instance")
2310 && short.to_string().contains("got 8 bytes"),
2311 "got: {short}"
2312 );
2313
2314 let bytes = client_request_with_instance_len(64, false);
2315 let long = decode_client_request(&bytes)
2316 .expect_err("a GET with a 64-byte instance must be rejected");
2317 assert!(long.to_string().contains("got 64 bytes"), "got: {long}");
2318 }
2319
2320 #[test]
2322 fn subscribe_with_wrong_length_instance_is_rejected_not_panicking() {
2323 let bytes = client_request_with_instance_len(8, true);
2324 let short = decode_client_request(&bytes)
2325 .expect_err("a SUBSCRIBE with an 8-byte instance must be rejected");
2326 assert!(
2327 short.to_string().contains("ContractKey.instance")
2328 && short.to_string().contains("got 8 bytes"),
2329 "got: {short}"
2330 );
2331
2332 let bytes = client_request_with_instance_len(64, true);
2333 let long = decode_client_request(&bytes)
2334 .expect_err("a SUBSCRIBE with a 64-byte instance must be rejected");
2335 assert!(long.to_string().contains("got 64 bytes"), "got: {long}");
2336 }
2337
2338 #[test]
2341 fn get_with_valid_instance_still_decodes() {
2342 let bytes = client_request_with_instance_len(32, false);
2343 let req = decode_client_request(&bytes).expect("a 32-byte instance must still decode");
2344 assert!(
2345 matches!(req, ContractRequest::Get { .. }),
2346 "expected a Get, got {req:?}"
2347 );
2348 }
2349
2350 #[test]
2357 fn fbs_decode_rejects_unknown_contract_discriminant() {
2358 use crate::generated::client_request::{
2359 finish_client_request_buffer, ClientRequest as FbsClientRequest, ClientRequestArgs,
2360 ClientRequestType, ContractRequest as FbsContractRequest, ContractRequestArgs,
2361 ContractRequestType, DelegateKey as FbsDelegateKey, DelegateKeyArgs,
2362 UnregisterDelegate, UnregisterDelegateArgs,
2363 };
2364
2365 let mut b = flatbuffers::FlatBufferBuilder::new();
2366 let key = b.create_vector(&[0u8; 32]);
2369 let code_hash = b.create_vector(&[0u8; 32]);
2370 let dk = FbsDelegateKey::create(
2371 &mut b,
2372 &DelegateKeyArgs {
2373 key: Some(key),
2374 code_hash: Some(code_hash),
2375 },
2376 );
2377 let dummy = UnregisterDelegate::create(&mut b, &UnregisterDelegateArgs { key: Some(dk) });
2378 let contract = FbsContractRequest::create(
2380 &mut b,
2381 &ContractRequestArgs {
2382 contract_request_type: ContractRequestType(99),
2383 contract_request: Some(dummy.as_union_value()),
2384 },
2385 );
2386 let client = FbsClientRequest::create(
2387 &mut b,
2388 &ClientRequestArgs {
2389 client_request_type: ClientRequestType::ContractRequest,
2390 client_request: Some(contract.as_union_value()),
2391 },
2392 );
2393 finish_client_request_buffer(&mut b, client);
2394 let bytes = b.finished_data().to_vec();
2395
2396 let client =
2397 root_as_client_request(&bytes).expect("verifier accepts an unknown union discriminant");
2398 let fbs_contract = client
2399 .client_request_as_contract_request()
2400 .expect("client_request is a ContractRequest");
2401 assert!(
2402 ContractRequest::try_decode_fbs(&fbs_contract).is_err(),
2403 "an unknown ContractRequestType discriminant must be a clean \
2404 per-request error, never a panic that downs the connection handler"
2405 );
2406 }
2407}
2408
2409#[cfg(test)]
2425mod delegate_request_wire_format {
2426 use super::DelegateRequest;
2427 use crate::code_hash::CodeHash;
2428 use crate::prelude::{
2429 ApplicationMessage, Delegate, DelegateCode, DelegateContainer, DelegateKey,
2430 DelegateWasmAPIVersion, InboundDelegateMsg, Parameters,
2431 };
2432
2433 fn sample_container() -> DelegateContainer {
2434 let code = DelegateCode::from(vec![1u8, 2, 3, 4]);
2435 let params = Parameters::from(vec![9u8, 8, 7]);
2436 DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(Delegate::from((&code, ¶ms))))
2437 }
2438
2439 fn sample_key(fill: u8) -> DelegateKey {
2440 DelegateKey::new([fill; 32], CodeHash::new([fill.wrapping_add(1); 32]))
2441 }
2442
2443 fn sample_app_messages() -> DelegateRequest<'static> {
2448 DelegateRequest::ApplicationMessages {
2449 key: DelegateKey::new([0x11; 32], CodeHash::new([0x22; 32])),
2450 params: Parameters::from(vec![0xDE, 0xAD, 0xBE, 0xEF]),
2451 inbound: vec![InboundDelegateMsg::ApplicationMessage(
2452 ApplicationMessage::new(vec![0x01, 0x02, 0x03]),
2453 )],
2454 }
2455 }
2456
2457 fn sample_register() -> DelegateRequest<'static> {
2458 DelegateRequest::RegisterDelegate {
2459 delegate: sample_container(),
2460 cipher: [0x55; 32],
2461 nonce: [0x66; 24],
2462 }
2463 }
2464
2465 fn sample_unregister() -> DelegateRequest<'static> {
2466 DelegateRequest::UnregisterDelegate(DelegateKey::new([0x11; 32], CodeHash::new([0x22; 32])))
2467 }
2468
2469 fn sample_register_with_predecessors() -> DelegateRequest<'static> {
2470 DelegateRequest::RegisterDelegateWithPredecessors {
2471 delegate: sample_container(),
2472 cipher: [0x33; 32],
2473 nonce: [0x44; 24],
2474 predecessors: vec![sample_key(0xA0), sample_key(0xB0), sample_key(0xC0)],
2475 }
2476 }
2477
2478 #[test]
2495 fn wire_format_is_frozen() {
2496 const APP_MESSAGES: &[u8] = &[
2498 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
2499 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 34, 34, 34, 34, 34, 34, 34, 34, 34,
2500 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
2501 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,
2502 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2503 ];
2504 const REGISTER: &[u8] = &[
2505 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,
2506 0, 0, 1, 2, 3, 4, 99, 120, 29, 23, 20, 37, 163, 99, 18, 250, 5, 141, 135, 18, 213, 208,
2507 81, 53, 169, 145, 236, 32, 53, 28, 233, 214, 92, 219, 25, 160, 84, 50, 88, 111, 44, 39,
2508 24, 219, 97, 92, 222, 20, 205, 248, 149, 154, 214, 38, 193, 144, 31, 141, 32, 222, 49,
2509 197, 66, 237, 16, 98, 165, 72, 6, 11, 99, 120, 29, 23, 20, 37, 163, 99, 18, 250, 5,
2510 141, 135, 18, 213, 208, 81, 53, 169, 145, 236, 32, 53, 28, 233, 214, 92, 219, 25, 160,
2511 84, 50, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
2512 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 102, 102, 102, 102, 102, 102, 102, 102,
2513 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
2514 ];
2515 const UNREGISTER: &[u8] = &[
2516 2, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
2517 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 34, 34, 34, 34, 34, 34, 34, 34, 34,
2518 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
2519 34,
2520 ];
2521 const REGISTER_WITH_PREDECESSORS: &[u8] = &[
2523 3, 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,
2524 0, 0, 1, 2, 3, 4, 99, 120, 29, 23, 20, 37, 163, 99, 18, 250, 5, 141, 135, 18, 213, 208,
2525 81, 53, 169, 145, 236, 32, 53, 28, 233, 214, 92, 219, 25, 160, 84, 50, 88, 111, 44, 39,
2526 24, 219, 97, 92, 222, 20, 205, 248, 149, 154, 214, 38, 193, 144, 31, 141, 32, 222, 49,
2527 197, 66, 237, 16, 98, 165, 72, 6, 11, 99, 120, 29, 23, 20, 37, 163, 99, 18, 250, 5,
2528 141, 135, 18, 213, 208, 81, 53, 169, 145, 236, 32, 53, 28, 233, 214, 92, 219, 25, 160,
2529 84, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
2530 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68,
2531 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 3, 0, 0, 0, 0, 0, 0, 0, 160,
2532 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160,
2533 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 161, 161, 161,
2534 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161,
2535 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 176, 176, 176, 176, 176,
2536 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176,
2537 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 177, 177, 177, 177, 177, 177, 177,
2538 177, 177, 177, 177, 177, 177, 177, 177, 177, 177, 177, 177, 177, 177, 177, 177, 177,
2539 177, 177, 177, 177, 177, 177, 177, 177, 192, 192, 192, 192, 192, 192, 192, 192, 192,
2540 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,
2541 192, 192, 192, 192, 192, 192, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193,
2542 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193, 193,
2543 193, 193, 193, 193,
2544 ];
2545
2546 assert_eq!(
2548 bincode::serialize(&sample_app_messages()).unwrap(),
2549 APP_MESSAGES,
2550 "ApplicationMessages (tag 0) encoding changed"
2551 );
2552 assert_eq!(
2553 bincode::serialize(&sample_register()).unwrap(),
2554 REGISTER,
2555 "RegisterDelegate (tag 1) encoding changed"
2556 );
2557 assert_eq!(
2558 bincode::serialize(&sample_unregister()).unwrap(),
2559 UNREGISTER,
2560 "UnregisterDelegate (tag 2) encoding changed"
2561 );
2562 assert_eq!(
2563 bincode::serialize(&sample_register_with_predecessors()).unwrap(),
2564 REGISTER_WITH_PREDECESSORS,
2565 "RegisterDelegateWithPredecessors (tag 3) encoding changed"
2566 );
2567
2568 assert_eq!(APP_MESSAGES[..4], 0u32.to_le_bytes());
2571 assert_eq!(REGISTER[..4], 1u32.to_le_bytes());
2572 assert_eq!(UNREGISTER[..4], 2u32.to_le_bytes());
2573 assert_eq!(REGISTER_WITH_PREDECESSORS[..4], 3u32.to_le_bytes());
2574
2575 assert!(matches!(
2578 bincode::deserialize::<DelegateRequest>(APP_MESSAGES).unwrap(),
2579 DelegateRequest::ApplicationMessages { .. }
2580 ));
2581 assert!(matches!(
2582 bincode::deserialize::<DelegateRequest>(REGISTER).unwrap(),
2583 DelegateRequest::RegisterDelegate { .. }
2584 ));
2585 assert!(matches!(
2586 bincode::deserialize::<DelegateRequest>(UNREGISTER).unwrap(),
2587 DelegateRequest::UnregisterDelegate(_)
2588 ));
2589 assert!(matches!(
2590 bincode::deserialize::<DelegateRequest>(REGISTER_WITH_PREDECESSORS).unwrap(),
2591 DelegateRequest::RegisterDelegateWithPredecessors { .. }
2592 ));
2593 }
2594
2595 #[test]
2599 fn register_with_predecessors_tag_is_three() {
2600 let req = DelegateRequest::RegisterDelegateWithPredecessors {
2601 delegate: sample_container(),
2602 cipher: [0; 32],
2603 nonce: [0; 24],
2604 predecessors: vec![],
2605 };
2606 assert_eq!(
2607 bincode::serialize(&req).unwrap()[..4],
2608 3u32.to_le_bytes(),
2609 "RegisterDelegateWithPredecessors must be the 4th variant (tag 3)"
2610 );
2611 }
2612
2613 #[test]
2616 fn register_with_predecessors_round_trips() {
2617 let predecessors = vec![sample_key(0xA0), sample_key(0xB0), sample_key(0xC0)];
2618 let delegate = sample_container();
2619 let expected_key = delegate.key().clone();
2620
2621 let req = DelegateRequest::RegisterDelegateWithPredecessors {
2622 delegate,
2623 cipher: [0x33; 32],
2624 nonce: [0x44; 24],
2625 predecessors: predecessors.clone(),
2626 };
2627
2628 let bytes = bincode::serialize(&req).unwrap();
2629 let decoded: DelegateRequest = bincode::deserialize(&bytes).unwrap();
2630
2631 match decoded {
2632 DelegateRequest::RegisterDelegateWithPredecessors {
2633 delegate,
2634 cipher,
2635 nonce,
2636 predecessors: got,
2637 } => {
2638 assert_eq!(delegate.key(), &expected_key, "delegate preserved");
2639 assert_eq!(cipher, [0x33; 32], "cipher preserved");
2640 assert_eq!(nonce, [0x44; 24], "nonce preserved");
2641 assert_eq!(
2642 got, predecessors,
2643 "full predecessor list preserved in order"
2644 );
2645 }
2646 other => panic!("round-trip produced the wrong variant: {other:?}"),
2647 }
2648 }
2649
2650 #[test]
2653 fn key_returns_the_new_delegate() {
2654 let delegate = sample_container();
2655 let expected_key = delegate.key().clone();
2656 let req = DelegateRequest::RegisterDelegateWithPredecessors {
2657 delegate,
2658 cipher: [0; 32],
2659 nonce: [0; 24],
2660 predecessors: vec![sample_key(0xA0)],
2661 };
2662 assert_eq!(req.key(), &expected_key);
2663 }
2664
2665 #[test]
2674 fn fbs_decode_rejects_unknown_discriminant() {
2675 use crate::client_api::TryFromFbs;
2676 use crate::generated::client_request::{
2677 finish_client_request_buffer, root_as_client_request,
2678 ClientRequest as FbsClientRequest, ClientRequestArgs, ClientRequestType,
2679 DelegateKey as FbsDelegateKey, DelegateKeyArgs, DelegateRequest as FbsDelegateRequest,
2680 DelegateRequestArgs, DelegateRequestType, UnregisterDelegate, UnregisterDelegateArgs,
2681 };
2682
2683 let mut b = flatbuffers::FlatBufferBuilder::new();
2684 let key = b.create_vector(&[0u8; 32]);
2686 let code_hash = b.create_vector(&[0u8; 32]);
2687 let dk = FbsDelegateKey::create(
2688 &mut b,
2689 &DelegateKeyArgs {
2690 key: Some(key),
2691 code_hash: Some(code_hash),
2692 },
2693 );
2694 let unreg = UnregisterDelegate::create(&mut b, &UnregisterDelegateArgs { key: Some(dk) });
2695 let dreq = FbsDelegateRequest::create(
2698 &mut b,
2699 &DelegateRequestArgs {
2700 delegate_request_type: DelegateRequestType(99),
2701 delegate_request: Some(unreg.as_union_value()),
2702 },
2703 );
2704 let creq = FbsClientRequest::create(
2705 &mut b,
2706 &ClientRequestArgs {
2707 client_request_type: ClientRequestType::DelegateRequest,
2708 client_request: Some(dreq.as_union_value()),
2709 },
2710 );
2711 finish_client_request_buffer(&mut b, creq);
2712 let bytes = b.finished_data().to_vec();
2713
2714 let client =
2715 root_as_client_request(&bytes).expect("verifier accepts an unknown union discriminant");
2716 let fbs_delegate = client
2717 .client_request_as_delegate_request()
2718 .expect("client_request is a DelegateRequest");
2719 let decoded = DelegateRequest::try_decode_fbs(&fbs_delegate);
2720 assert!(
2721 decoded.is_err(),
2722 "an unknown DelegateRequestType discriminant must be a clean \
2723 per-request error, never a panic that downs the connection handler"
2724 );
2725 }
2726}
2727
2728#[cfg(test)]
2744mod fbs_decode_hardening {
2745 use super::{ClientRequest, ContractRequest};
2746 use crate::client_api::TryFromFbs;
2747 use crate::contract_interface::UpdateData;
2748 use crate::generated::client_request::{
2749 finish_client_request_buffer, ApplicationMessages, ApplicationMessagesArgs,
2750 ClientRequest as FbsClientRequest, ClientRequestArgs, ClientRequestType,
2751 ContractRequest as FbsContractRequest, ContractRequestArgs, ContractRequestType,
2752 DelegateCode as FbsDelegateCode, DelegateCodeArgs,
2753 DelegateContainer as FbsDelegateContainer, DelegateContainerArgs,
2754 DelegateKey as FbsDelegateKey, DelegateKeyArgs, DelegateRequest as FbsDelegateRequest,
2755 DelegateRequestArgs, DelegateRequestType, DelegateType, Get as FbsGet, GetArgs,
2756 InboundDelegateMsg as FbsInboundDelegateMsg, InboundDelegateMsgArgs,
2757 InboundDelegateMsgType, Put as FbsPut, PutArgs, RegisterDelegate, RegisterDelegateArgs,
2758 RelatedContract, RelatedContractArgs, RelatedContracts as FbsRelatedContracts,
2759 RelatedContractsArgs, Update as FbsUpdate, UpdateArgs, WasmDelegateV1, WasmDelegateV1Args,
2760 };
2761 use crate::generated::common::{
2762 ApplicationMessage as FbsApplicationMessage, ApplicationMessageArgs,
2763 ContractCode as FbsContractCode, ContractCodeArgs,
2764 ContractContainer as FbsContractContainer, ContractContainerArgs,
2765 ContractInstanceId as FbsContractInstanceId, ContractInstanceIdArgs,
2766 ContractKey as FbsContractKey, ContractKeyArgs, ContractType, RelatedDeltaUpdate,
2767 RelatedDeltaUpdateArgs, RelatedStateAndDeltaUpdate, RelatedStateAndDeltaUpdateArgs,
2768 RelatedStateUpdate, RelatedStateUpdateArgs, StateUpdate, StateUpdateArgs,
2769 UpdateData as FbsUpdateData, UpdateDataArgs, UpdateDataType, WasmContractV1,
2770 WasmContractV1Args,
2771 };
2772
2773 type Builder<'a> = flatbuffers::FlatBufferBuilder<'a>;
2774
2775 const INSTANCE: [u8; 32] = [
2779 0x00, 0xff, 0x7a, 0x01, 0x30, 0x4f, 0x49, 0x6c, 0x2b, 0x2f, 0x5c, 0x7f, 0x80, 0xfe, 0x10,
2780 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
2781 0x20, 0x21,
2782 ];
2783 const CODE_HASH: [u8; 32] = [42u8; 32];
2784
2785 fn instance_offset<'a>(
2786 b: &mut Builder<'a>,
2787 bytes: &[u8],
2788 ) -> flatbuffers::WIPOffset<FbsContractInstanceId<'a>> {
2789 let data = b.create_vector(bytes);
2790 FbsContractInstanceId::create(b, &ContractInstanceIdArgs { data: Some(data) })
2791 }
2792
2793 fn key_offset<'a>(
2794 b: &mut Builder<'a>,
2795 instance: &[u8],
2796 code: &[u8],
2797 ) -> flatbuffers::WIPOffset<FbsContractKey<'a>> {
2798 let instance = instance_offset(b, instance);
2799 let code = b.create_vector(code);
2800 FbsContractKey::create(
2801 b,
2802 &ContractKeyArgs {
2803 instance: Some(instance),
2804 code: Some(code),
2805 },
2806 )
2807 }
2808
2809 fn delegate_key_offset<'a>(
2810 b: &mut Builder<'a>,
2811 key: &[u8],
2812 code_hash: &[u8],
2813 ) -> flatbuffers::WIPOffset<FbsDelegateKey<'a>> {
2814 let key = b.create_vector(key);
2815 let code_hash = b.create_vector(code_hash);
2816 FbsDelegateKey::create(
2817 b,
2818 &DelegateKeyArgs {
2819 key: Some(key),
2820 code_hash: Some(code_hash),
2821 },
2822 )
2823 }
2824
2825 fn finish_contract(
2827 b: &mut Builder<'_>,
2828 ty: ContractRequestType,
2829 req: flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>,
2830 ) -> Vec<u8> {
2831 let contract = FbsContractRequest::create(
2832 b,
2833 &ContractRequestArgs {
2834 contract_request_type: ty,
2835 contract_request: Some(req),
2836 },
2837 );
2838 let client = FbsClientRequest::create(
2839 b,
2840 &ClientRequestArgs {
2841 client_request_type: ClientRequestType::ContractRequest,
2842 client_request: Some(contract.as_union_value()),
2843 },
2844 );
2845 finish_client_request_buffer(b, client);
2846 b.finished_data().to_vec()
2847 }
2848
2849 fn finish_delegate(
2850 b: &mut Builder<'_>,
2851 ty: DelegateRequestType,
2852 req: flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>,
2853 ) -> Vec<u8> {
2854 let delegate = FbsDelegateRequest::create(
2855 b,
2856 &DelegateRequestArgs {
2857 delegate_request_type: ty,
2858 delegate_request: Some(req),
2859 },
2860 );
2861 let client = FbsClientRequest::create(
2862 b,
2863 &ClientRequestArgs {
2864 client_request_type: ClientRequestType::DelegateRequest,
2865 client_request: Some(delegate.as_union_value()),
2866 },
2867 );
2868 finish_client_request_buffer(b, client);
2869 b.finished_data().to_vec()
2870 }
2871
2872 fn client_request_type(d: u8, force_defaults: bool) -> Vec<u8> {
2879 let mut b = Builder::new();
2880 b.force_defaults(force_defaults);
2881 let key = key_offset(&mut b, &INSTANCE, &CODE_HASH);
2882 let get = FbsGet::create(
2883 &mut b,
2884 &GetArgs {
2885 key: Some(key),
2886 fetch_contract: false,
2887 subscribe: false,
2888 blocking_subscribe: false,
2889 },
2890 );
2891 let contract = FbsContractRequest::create(
2892 &mut b,
2893 &ContractRequestArgs {
2894 contract_request_type: ContractRequestType::Get,
2895 contract_request: Some(get.as_union_value()),
2896 },
2897 );
2898 let client = FbsClientRequest::create(
2899 &mut b,
2900 &ClientRequestArgs {
2901 client_request_type: ClientRequestType(d),
2902 client_request: Some(contract.as_union_value()),
2903 },
2904 );
2905 finish_client_request_buffer(&mut b, client);
2906 b.finished_data().to_vec()
2907 }
2908
2909 fn contract_request_type(d: u8, force_defaults: bool) -> Vec<u8> {
2910 let mut b = Builder::new();
2911 b.force_defaults(force_defaults);
2912 let key = key_offset(&mut b, &INSTANCE, &CODE_HASH);
2913 let get = FbsGet::create(
2914 &mut b,
2915 &GetArgs {
2916 key: Some(key),
2917 fetch_contract: false,
2918 subscribe: false,
2919 blocking_subscribe: false,
2920 },
2921 );
2922 finish_contract(&mut b, ContractRequestType(d), get.as_union_value())
2923 }
2924
2925 fn delegate_request_type(d: u8, force_defaults: bool) -> Vec<u8> {
2926 let mut b = Builder::new();
2927 b.force_defaults(force_defaults);
2928 let dk = delegate_key_offset(&mut b, &[7u8; 32], &CODE_HASH);
2929 let params = b.create_vector(&[1u8, 2, 3]);
2930 let payload = b.create_vector(&[9u8; 4]);
2931 let context = b.create_vector(&[0u8; 2]);
2932 let app = FbsApplicationMessage::create(
2933 &mut b,
2934 &ApplicationMessageArgs {
2935 payload: Some(payload),
2936 context: Some(context),
2937 processed: false,
2938 },
2939 );
2940 let inbound_msg = FbsInboundDelegateMsg::create(
2941 &mut b,
2942 &InboundDelegateMsgArgs {
2943 inbound_type: InboundDelegateMsgType::common_ApplicationMessage,
2944 inbound: Some(app.as_union_value()),
2945 },
2946 );
2947 let inbound = b.create_vector(&[inbound_msg]);
2948 let msgs = ApplicationMessages::create(
2949 &mut b,
2950 &ApplicationMessagesArgs {
2951 key: Some(dk),
2952 params: Some(params),
2953 inbound: Some(inbound),
2954 },
2955 );
2956 finish_delegate(&mut b, DelegateRequestType(d), msgs.as_union_value())
2957 }
2958
2959 fn contract_type(d: u8, force_defaults: bool) -> Vec<u8> {
2961 let mut b = Builder::new();
2962 b.force_defaults(force_defaults);
2963 let code_data = b.create_vector(&[0u8; 8]);
2964 let code_hash = b.create_vector(&CODE_HASH);
2965 let code = FbsContractCode::create(
2966 &mut b,
2967 &ContractCodeArgs {
2968 data: Some(code_data),
2969 code_hash: Some(code_hash),
2970 },
2971 );
2972 let key = key_offset(&mut b, &INSTANCE, &CODE_HASH);
2973 let params = b.create_vector(&[1u8, 2]);
2974 let wasm = WasmContractV1::create(
2975 &mut b,
2976 &WasmContractV1Args {
2977 data: Some(code),
2978 parameters: Some(params),
2979 key: Some(key),
2980 },
2981 );
2982 let container = FbsContractContainer::create(
2983 &mut b,
2984 &ContractContainerArgs {
2985 contract_type: ContractType(d),
2986 contract: Some(wasm.as_union_value()),
2987 },
2988 );
2989 let state = b.create_vector(&[3u8; 4]);
2990 let empty: Vec<flatbuffers::WIPOffset<RelatedContract>> = vec![];
2991 let contracts = b.create_vector(&empty);
2992 let related = FbsRelatedContracts::create(
2993 &mut b,
2994 &RelatedContractsArgs {
2995 contracts: Some(contracts),
2996 },
2997 );
2998 let put = FbsPut::create(
2999 &mut b,
3000 &PutArgs {
3001 container: Some(container),
3002 wrapped_state: Some(state),
3003 related_contracts: Some(related),
3004 subscribe: false,
3005 blocking_subscribe: false,
3006 },
3007 );
3008 finish_contract(&mut b, ContractRequestType::Put, put.as_union_value())
3009 }
3010
3011 fn delegate_type(d: u8, force_defaults: bool) -> Vec<u8> {
3013 let mut b = Builder::new();
3014 b.force_defaults(force_defaults);
3015 let code_data = b.create_vector(&[0u8; 8]);
3016 let code_hash = b.create_vector(&CODE_HASH);
3017 let code = FbsDelegateCode::create(
3018 &mut b,
3019 &DelegateCodeArgs {
3020 data: Some(code_data),
3021 code_hash: Some(code_hash),
3022 },
3023 );
3024 let dk = delegate_key_offset(&mut b, &[7u8; 32], &CODE_HASH);
3025 let params = b.create_vector(&[1u8, 2]);
3026 let wasm = WasmDelegateV1::create(
3027 &mut b,
3028 &WasmDelegateV1Args {
3029 parameters: Some(params),
3030 data: Some(code),
3031 key: Some(dk),
3032 },
3033 );
3034 let container = FbsDelegateContainer::create(
3035 &mut b,
3036 &DelegateContainerArgs {
3037 delegate_type: DelegateType(d),
3038 delegate: Some(wasm.as_union_value()),
3039 },
3040 );
3041 let cipher = b.create_vector(&[1u8; 32]);
3042 let nonce = b.create_vector(&[2u8; 24]);
3043 let register = RegisterDelegate::create(
3044 &mut b,
3045 &RegisterDelegateArgs {
3046 delegate: Some(container),
3047 cipher: Some(cipher),
3048 nonce: Some(nonce),
3049 },
3050 );
3051 finish_delegate(
3052 &mut b,
3053 DelegateRequestType::RegisterDelegate,
3054 register.as_union_value(),
3055 )
3056 }
3057
3058 fn update_data_type(d: u8, force_defaults: bool) -> Vec<u8> {
3060 let mut b = Builder::new();
3061 b.force_defaults(force_defaults);
3062 let state = b.create_vector(&[5u8; 4]);
3063 let state_update = StateUpdate::create(&mut b, &StateUpdateArgs { state: Some(state) });
3064 let data = FbsUpdateData::create(
3065 &mut b,
3066 &UpdateDataArgs {
3067 update_data_type: UpdateDataType(d),
3068 update_data: Some(state_update.as_union_value()),
3069 },
3070 );
3071 let key = key_offset(&mut b, &INSTANCE, &CODE_HASH);
3072 let update = FbsUpdate::create(
3073 &mut b,
3074 &UpdateArgs {
3075 key: Some(key),
3076 data: Some(data),
3077 },
3078 );
3079 finish_contract(&mut b, ContractRequestType::Update, update.as_union_value())
3080 }
3081
3082 fn inbound_delegate_msg_type(d: u8, force_defaults: bool) -> Vec<u8> {
3084 let mut b = Builder::new();
3085 b.force_defaults(force_defaults);
3086 let payload = b.create_vector(&[9u8; 4]);
3087 let context = b.create_vector(&[0u8; 2]);
3088 let app = FbsApplicationMessage::create(
3089 &mut b,
3090 &ApplicationMessageArgs {
3091 payload: Some(payload),
3092 context: Some(context),
3093 processed: false,
3094 },
3095 );
3096 let inbound_msg = FbsInboundDelegateMsg::create(
3097 &mut b,
3098 &InboundDelegateMsgArgs {
3099 inbound_type: InboundDelegateMsgType(d),
3100 inbound: Some(app.as_union_value()),
3101 },
3102 );
3103 let inbound = b.create_vector(&[inbound_msg]);
3104 let dk = delegate_key_offset(&mut b, &[7u8; 32], &CODE_HASH);
3105 let params = b.create_vector(&[1u8, 2, 3]);
3106 let msgs = ApplicationMessages::create(
3107 &mut b,
3108 &ApplicationMessagesArgs {
3109 key: Some(dk),
3110 params: Some(params),
3111 inbound: Some(inbound),
3112 },
3113 );
3114 finish_delegate(
3115 &mut b,
3116 DelegateRequestType::ApplicationMessages,
3117 msgs.as_union_value(),
3118 )
3119 }
3120
3121 type UnionCase = (&'static str, fn(u8, bool) -> Vec<u8>, u8);
3123
3124 #[test]
3148 fn no_union_discriminant_panics_the_decoder() {
3149 let cases: [UnionCase; 7] = [
3150 ("ClientRequestType", client_request_type, 1),
3151 ("ContractRequestType", contract_request_type, 3),
3152 ("DelegateRequestType", delegate_request_type, 1),
3153 ("ContractType", contract_type, 1),
3154 ("DelegateType", delegate_type, 1),
3155 ("UpdateDataType", update_data_type, 1),
3156 (
3157 "InboundDelegateMsgType",
3158 inbound_delegate_msg_type,
3159 InboundDelegateMsgType::common_ApplicationMessage.0,
3160 ),
3161 ];
3162
3163 for (union, build, valid) in cases {
3164 for d in 0..=u8::MAX {
3165 let bytes = build(d, false);
3169 let _ = ClientRequest::try_decode_fbs(&bytes);
3170 }
3171
3172 let out_of_range = build(200, false);
3176 let err = ClientRequest::try_decode_fbs(&out_of_range)
3177 .expect_err("{union}: an out-of-range discriminant must be a clean error");
3178 assert_eq!(
3179 err.to_string(),
3180 format!(
3181 "Failed decoding message from client request: unknown {union} \
3182 discriminant: 200"
3183 ),
3184 "{union}: the error must come from the decoder's union arm, not \
3185 from the verifier — otherwise this sweep pins nothing"
3186 );
3187
3188 let none = build(0, true);
3195 let err = ClientRequest::try_decode_fbs(&none)
3196 .expect_err("{union}: a NONE discriminant must be a clean error");
3197 assert_eq!(
3198 err.to_string(),
3199 format!(
3200 "Failed decoding message from client request: unknown {union} \
3201 discriminant: 0"
3202 ),
3203 "{union}: an explicit NONE must reach the decoder's union arm"
3204 );
3205
3206 let good = build(valid, false);
3207 assert!(
3208 ClientRequest::try_decode_fbs(&good).is_ok(),
3209 "{union}: the real discriminant must still decode; the guard \
3210 must not break the happy path"
3211 );
3212 }
3213 }
3214
3215 fn update_with_related(variant: UpdateDataType, id: &[u8]) -> Vec<u8> {
3225 let mut b = Builder::new();
3226 let related_to = instance_offset(&mut b, id);
3227 let payload = b.create_vector(&[5u8; 4]);
3228 let data_offset = match variant {
3229 UpdateDataType::RelatedStateUpdate => RelatedStateUpdate::create(
3230 &mut b,
3231 &RelatedStateUpdateArgs {
3232 related_to: Some(related_to),
3233 state: Some(payload),
3234 },
3235 )
3236 .as_union_value(),
3237 UpdateDataType::RelatedDeltaUpdate => RelatedDeltaUpdate::create(
3238 &mut b,
3239 &RelatedDeltaUpdateArgs {
3240 related_to: Some(related_to),
3241 delta: Some(payload),
3242 },
3243 )
3244 .as_union_value(),
3245 UpdateDataType::RelatedStateAndDeltaUpdate => {
3246 let delta = b.create_vector(&[6u8; 4]);
3247 RelatedStateAndDeltaUpdate::create(
3248 &mut b,
3249 &RelatedStateAndDeltaUpdateArgs {
3250 related_to: Some(related_to),
3251 state: Some(payload),
3252 delta: Some(delta),
3253 },
3254 )
3255 .as_union_value()
3256 }
3257 other => panic!("not a related update variant: {}", other.0),
3258 };
3259 let data = FbsUpdateData::create(
3260 &mut b,
3261 &UpdateDataArgs {
3262 update_data_type: variant,
3263 update_data: Some(data_offset),
3264 },
3265 );
3266 let key = key_offset(&mut b, &INSTANCE, &CODE_HASH);
3267 let update = FbsUpdate::create(
3268 &mut b,
3269 &UpdateArgs {
3270 key: Some(key),
3271 data: Some(data),
3272 },
3273 );
3274 finish_contract(&mut b, ContractRequestType::Update, update.as_union_value())
3275 }
3276
3277 fn decoded_related_to(bytes: &[u8]) -> [u8; 32] {
3278 let req = ClientRequest::try_decode_fbs(bytes)
3279 .expect("a well-formed related update must decode, not panic");
3280 let ClientRequest::ContractOp(ContractRequest::Update { data, .. }) = req else {
3281 panic!("expected an UPDATE, got {req:?}");
3282 };
3283 match data {
3284 UpdateData::RelatedState { related_to, .. }
3285 | UpdateData::RelatedDelta { related_to, .. }
3286 | UpdateData::RelatedStateAndDelta { related_to, .. } => *related_to,
3287 other => panic!("expected a related update, got {other:?}"),
3288 }
3289 }
3290
3291 #[test]
3301 fn related_state_update_round_trips_the_raw_instance_id() {
3302 let bytes = update_with_related(UpdateDataType::RelatedStateUpdate, &INSTANCE);
3303 assert_eq!(decoded_related_to(&bytes), INSTANCE);
3304 }
3305
3306 #[test]
3307 fn related_delta_update_round_trips_the_raw_instance_id() {
3308 let bytes = update_with_related(UpdateDataType::RelatedDeltaUpdate, &INSTANCE);
3309 assert_eq!(decoded_related_to(&bytes), INSTANCE);
3310 }
3311
3312 #[test]
3313 fn related_state_and_delta_update_round_trips_the_raw_instance_id() {
3314 let bytes = update_with_related(UpdateDataType::RelatedStateAndDeltaUpdate, &INSTANCE);
3315 assert_eq!(decoded_related_to(&bytes), INSTANCE);
3316 }
3317
3318 #[test]
3324 fn related_to_wrong_length_is_rejected() {
3325 for (variant, field) in [
3326 (
3327 UpdateDataType::RelatedStateUpdate,
3328 "RelatedStateUpdate.related_to.data",
3329 ),
3330 (
3331 UpdateDataType::RelatedDeltaUpdate,
3332 "RelatedDeltaUpdate.related_to.data",
3333 ),
3334 (
3335 UpdateDataType::RelatedStateAndDeltaUpdate,
3336 "RelatedStateAndDeltaUpdate.related_to.data",
3337 ),
3338 ] {
3339 let bytes = update_with_related(variant, &[1u8; 8]);
3340 let err = ClientRequest::try_decode_fbs(&bytes)
3341 .expect_err("an 8-byte related_to must be rejected");
3342 let msg = err.to_string();
3343 assert!(
3344 msg.contains(field) && msg.contains("got 8 bytes"),
3345 "the error must name {field} and the observed length, got: {msg}"
3346 );
3347 }
3348 }
3349
3350 fn put_with_related_contract(id: &[u8]) -> Vec<u8> {
3351 let mut b = Builder::new();
3352 let code_data = b.create_vector(&[0u8; 8]);
3353 let code_hash = b.create_vector(&CODE_HASH);
3354 let code = FbsContractCode::create(
3355 &mut b,
3356 &ContractCodeArgs {
3357 data: Some(code_data),
3358 code_hash: Some(code_hash),
3359 },
3360 );
3361 let key = key_offset(&mut b, &INSTANCE, &CODE_HASH);
3362 let params = b.create_vector(&[1u8, 2]);
3363 let wasm = WasmContractV1::create(
3364 &mut b,
3365 &WasmContractV1Args {
3366 data: Some(code),
3367 parameters: Some(params),
3368 key: Some(key),
3369 },
3370 );
3371 let container = FbsContractContainer::create(
3372 &mut b,
3373 &ContractContainerArgs {
3374 contract_type: ContractType::WasmContractV1,
3375 contract: Some(wasm.as_union_value()),
3376 },
3377 );
3378 let related_id = instance_offset(&mut b, id);
3379 let related_state = b.create_vector(&[8u8; 3]);
3380 let related_contract = RelatedContract::create(
3381 &mut b,
3382 &RelatedContractArgs {
3383 instance_id: Some(related_id),
3384 state: Some(related_state),
3385 },
3386 );
3387 let contracts = b.create_vector(&[related_contract]);
3388 let related = FbsRelatedContracts::create(
3389 &mut b,
3390 &RelatedContractsArgs {
3391 contracts: Some(contracts),
3392 },
3393 );
3394 let state = b.create_vector(&[3u8; 4]);
3395 let put = FbsPut::create(
3396 &mut b,
3397 &PutArgs {
3398 container: Some(container),
3399 wrapped_state: Some(state),
3400 related_contracts: Some(related),
3401 subscribe: false,
3402 blocking_subscribe: false,
3403 },
3404 );
3405 finish_contract(&mut b, ContractRequestType::Put, put.as_union_value())
3406 }
3407
3408 #[test]
3412 fn put_related_contract_round_trips_the_raw_instance_id() {
3413 let bytes = put_with_related_contract(&INSTANCE);
3414 let req = ClientRequest::try_decode_fbs(&bytes)
3415 .expect("a PUT carrying a related contract must decode, not panic");
3416 let ClientRequest::ContractOp(ContractRequest::Put {
3417 related_contracts, ..
3418 }) = req
3419 else {
3420 panic!("expected a PUT, got {req:?}");
3421 };
3422 let ids: Vec<[u8; 32]> = related_contracts
3423 .into_owned()
3424 .states()
3425 .map(|(id, _)| **id)
3426 .collect();
3427 assert_eq!(
3428 ids,
3429 vec![INSTANCE],
3430 "the related contract id must round-trip"
3431 );
3432 }
3433
3434 #[test]
3435 fn put_related_contract_wrong_length_id_is_rejected() {
3436 let bytes = put_with_related_contract(&[1u8; 8]);
3437 let err = ClientRequest::try_decode_fbs(&bytes)
3438 .expect_err("an 8-byte related contract id must be rejected");
3439 let msg = err.to_string();
3440 assert!(
3441 msg.contains("RelatedContract.instance_id") && msg.contains("got 8 bytes"),
3442 "got: {msg}"
3443 );
3444 }
3445
3446 fn unregister_delegate(key_len: usize) -> Vec<u8> {
3447 use crate::generated::client_request::{UnregisterDelegate, UnregisterDelegateArgs};
3448 let mut b = Builder::new();
3449 let dk = delegate_key_offset(&mut b, &vec![7u8; key_len], &CODE_HASH);
3450 let unregister =
3451 UnregisterDelegate::create(&mut b, &UnregisterDelegateArgs { key: Some(dk) });
3452 finish_delegate(
3453 &mut b,
3454 DelegateRequestType::UnregisterDelegate,
3455 unregister.as_union_value(),
3456 )
3457 }
3458
3459 #[test]
3465 fn delegate_key_wrong_length_is_rejected_not_panicking() {
3466 let short = unregister_delegate(8);
3467 let err = ClientRequest::try_decode_fbs(&short)
3468 .expect_err("an 8-byte delegate key must be rejected");
3469 let msg = err.to_string();
3470 assert!(
3471 msg.contains("DelegateKey.key") && msg.contains("got 8 bytes"),
3472 "got: {msg}"
3473 );
3474
3475 let long = unregister_delegate(64);
3476 let err = ClientRequest::try_decode_fbs(&long)
3477 .expect_err("a 64-byte delegate key must be rejected");
3478 assert!(err.to_string().contains("got 64 bytes"), "got: {err}");
3479
3480 let good = unregister_delegate(32);
3481 assert!(
3482 ClientRequest::try_decode_fbs(&good).is_ok(),
3483 "a 32-byte delegate key must still decode"
3484 );
3485 }
3486
3487 fn register_delegate(cipher_len: usize, nonce_len: usize) -> Vec<u8> {
3488 let mut b = Builder::new();
3489 let code_data = b.create_vector(&[0u8; 8]);
3490 let code_hash = b.create_vector(&CODE_HASH);
3491 let code = FbsDelegateCode::create(
3492 &mut b,
3493 &DelegateCodeArgs {
3494 data: Some(code_data),
3495 code_hash: Some(code_hash),
3496 },
3497 );
3498 let dk = delegate_key_offset(&mut b, &[7u8; 32], &CODE_HASH);
3499 let params = b.create_vector(&[1u8, 2]);
3500 let wasm = WasmDelegateV1::create(
3501 &mut b,
3502 &WasmDelegateV1Args {
3503 parameters: Some(params),
3504 data: Some(code),
3505 key: Some(dk),
3506 },
3507 );
3508 let container = FbsDelegateContainer::create(
3509 &mut b,
3510 &DelegateContainerArgs {
3511 delegate_type: DelegateType::WasmDelegateV1,
3512 delegate: Some(wasm.as_union_value()),
3513 },
3514 );
3515 let cipher = b.create_vector(&vec![1u8; cipher_len]);
3516 let nonce = b.create_vector(&vec![2u8; nonce_len]);
3517 let register = RegisterDelegate::create(
3518 &mut b,
3519 &RegisterDelegateArgs {
3520 delegate: Some(container),
3521 cipher: Some(cipher),
3522 nonce: Some(nonce),
3523 },
3524 );
3525 finish_delegate(
3526 &mut b,
3527 DelegateRequestType::RegisterDelegate,
3528 register.as_union_value(),
3529 )
3530 }
3531
3532 #[test]
3536 fn register_delegate_wrong_length_cipher_or_nonce_is_rejected() {
3537 let err = ClientRequest::try_decode_fbs(®ister_delegate(16, 24))
3538 .expect_err("a 16-byte cipher must be rejected");
3539 let msg = err.to_string();
3540 assert!(
3541 msg.contains("RegisterDelegate.cipher") && msg.contains("got 16 bytes"),
3542 "got: {msg}"
3543 );
3544
3545 let err = ClientRequest::try_decode_fbs(®ister_delegate(32, 8))
3546 .expect_err("an 8-byte nonce must be rejected");
3547 let msg = err.to_string();
3548 assert!(
3549 msg.contains("RegisterDelegate.nonce") && msg.contains("got 8 bytes"),
3550 "got: {msg}"
3551 );
3552
3553 assert!(
3554 ClientRequest::try_decode_fbs(®ister_delegate(32, 24)).is_ok(),
3555 "correct cipher/nonce lengths must still decode"
3556 );
3557 }
3558
3559 #[test]
3570 fn host_response_encodes_related_to_as_raw_bytes() {
3571 use crate::client_api::{ContractResponse, HostResponse};
3572 use crate::contract_interface::{ContractInstanceId, ContractKey, State};
3573 use crate::generated::host_response::{root_as_host_response, ContractResponseType};
3574
3575 let related = ContractInstanceId::new(INSTANCE);
3576 let key = ContractKey::from_params_and_code(
3577 crate::parameters::Parameters::from(vec![1u8, 2]),
3578 crate::contract_interface::ContractCode::from(vec![0u8; 8]),
3579 );
3580 let response = HostResponse::ContractResponse(ContractResponse::UpdateNotification {
3581 key,
3582 update: UpdateData::RelatedState {
3583 related_to: related,
3584 state: State::from(vec![9u8; 4]),
3585 },
3586 });
3587
3588 let bytes = response.into_fbs_bytes().expect("encoding must succeed");
3589 let host = root_as_host_response(&bytes).expect("the encoder must emit a valid buffer");
3590 let contract = host
3591 .response_as_contract_response()
3592 .expect("a ContractResponse");
3593 assert_eq!(
3594 contract.contract_response_type(),
3595 ContractResponseType::UpdateNotification
3596 );
3597 let notification = contract
3598 .contract_response_as_update_notification()
3599 .expect("an UpdateNotification");
3600 let related_update = notification
3601 .update()
3602 .update_data_as_related_state_update()
3603 .expect("a RelatedStateUpdate");
3604
3605 assert_eq!(
3606 related_update.related_to().data().bytes(),
3607 &INSTANCE,
3608 "related_to must be the 32 RAW id bytes. Encoding it as base58 text \
3609 puts ~44 ASCII bytes in a field the TypeScript SDK reads as a raw \
3610 Uint8Array, and that our own decoder now rejects."
3611 );
3612 }
3613
3614 #[test]
3618 fn secrets_id_wrong_length_hash_is_rejected_not_panicking() {
3619 use crate::delegate_interface::SecretsId;
3620 use crate::generated::common::{SecretsId as FbsSecretsId, SecretsIdArgs};
3621
3622 let build = |hash_len: usize| {
3623 let mut b = Builder::new();
3624 let key = b.create_vector(&[1u8, 2, 3]);
3625 let hash = b.create_vector(&vec![4u8; hash_len]);
3626 let id = FbsSecretsId::create(
3627 &mut b,
3628 &SecretsIdArgs {
3629 key: Some(key),
3630 hash: Some(hash),
3631 },
3632 );
3633 b.finish_minimal(id);
3634 b.finished_data().to_vec()
3635 };
3636
3637 let bytes = build(8);
3638 let fbs = flatbuffers::root::<FbsSecretsId>(&bytes)
3639 .expect("the verifier accepts a short required vector");
3640 let err = SecretsId::try_decode_fbs(&fbs).expect_err("an 8-byte hash must be rejected");
3641 assert!(
3642 err.to_string().contains("SecretsId.hash") && err.to_string().contains("got 8 bytes"),
3643 "got: {err}"
3644 );
3645
3646 let bytes = build(32);
3647 let fbs = flatbuffers::root::<FbsSecretsId>(&bytes).expect("well-formed");
3648 assert!(
3649 SecretsId::try_decode_fbs(&fbs).is_ok(),
3650 "a 32-byte hash must still decode"
3651 );
3652 }
3653}