From 3e7b76742ef5b0c2d3204c7a2444f387481d436b Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Sun, 21 Dec 2025 22:14:04 -0600 Subject: [PATCH 1/2] chore: regenerate flatbuffers with flatc 24.3.25 --- rust/src/generated/client_request_generated.rs | 8 ++++---- rust/src/generated/host_response_generated.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rust/src/generated/client_request_generated.rs b/rust/src/generated/client_request_generated.rs index bb81ae4..c71edfd 100644 --- a/rust/src/generated/client_request_generated.rs +++ b/rust/src/generated/client_request_generated.rs @@ -4707,7 +4707,7 @@ pub mod client_request { /// `root_as_client_request_unchecked`. pub fn root_as_client_request( buf: &[u8], - ) -> Result, flatbuffers::InvalidFlatbuffer> { + ) -> Result { flatbuffers::root::(buf) } #[inline] @@ -4719,7 +4719,7 @@ pub mod client_request { /// `size_prefixed_root_as_client_request_unchecked`. pub fn size_prefixed_root_as_client_request( buf: &[u8], - ) -> Result, flatbuffers::InvalidFlatbuffer> { + ) -> Result { flatbuffers::size_prefixed_root::(buf) } #[inline] @@ -4752,14 +4752,14 @@ pub mod client_request { /// Assumes, without verification, that a buffer of bytes contains a ClientRequest and returns it. /// # Safety /// Callers must trust the given bytes do indeed contain a valid `ClientRequest`. - pub unsafe fn root_as_client_request_unchecked(buf: &[u8]) -> ClientRequest<'_> { + pub unsafe fn root_as_client_request_unchecked(buf: &[u8]) -> ClientRequest { flatbuffers::root_unchecked::(buf) } #[inline] /// Assumes, without verification, that a buffer of bytes contains a size prefixed ClientRequest and returns it. /// # Safety /// Callers must trust the given bytes do indeed contain a valid size prefixed `ClientRequest`. - pub unsafe fn size_prefixed_root_as_client_request_unchecked(buf: &[u8]) -> ClientRequest<'_> { + pub unsafe fn size_prefixed_root_as_client_request_unchecked(buf: &[u8]) -> ClientRequest { flatbuffers::size_prefixed_root_unchecked::(buf) } #[inline] diff --git a/rust/src/generated/host_response_generated.rs b/rust/src/generated/host_response_generated.rs index af9cc75..d9f75cb 100644 --- a/rust/src/generated/host_response_generated.rs +++ b/rust/src/generated/host_response_generated.rs @@ -3338,7 +3338,7 @@ pub mod host_response { /// `root_as_host_response_unchecked`. pub fn root_as_host_response( buf: &[u8], - ) -> Result, flatbuffers::InvalidFlatbuffer> { + ) -> Result { flatbuffers::root::(buf) } #[inline] @@ -3350,7 +3350,7 @@ pub mod host_response { /// `size_prefixed_root_as_host_response_unchecked`. pub fn size_prefixed_root_as_host_response( buf: &[u8], - ) -> Result, flatbuffers::InvalidFlatbuffer> { + ) -> Result { flatbuffers::size_prefixed_root::(buf) } #[inline] @@ -3383,14 +3383,14 @@ pub mod host_response { /// Assumes, without verification, that a buffer of bytes contains a HostResponse and returns it. /// # Safety /// Callers must trust the given bytes do indeed contain a valid `HostResponse`. - pub unsafe fn root_as_host_response_unchecked(buf: &[u8]) -> HostResponse<'_> { + pub unsafe fn root_as_host_response_unchecked(buf: &[u8]) -> HostResponse { flatbuffers::root_unchecked::(buf) } #[inline] /// Assumes, without verification, that a buffer of bytes contains a size prefixed HostResponse and returns it. /// # Safety /// Callers must trust the given bytes do indeed contain a valid size prefixed `HostResponse`. - pub unsafe fn size_prefixed_root_as_host_response_unchecked(buf: &[u8]) -> HostResponse<'_> { + pub unsafe fn size_prefixed_root_as_host_response_unchecked(buf: &[u8]) -> HostResponse { flatbuffers::size_prefixed_root_unchecked::(buf) } #[inline] From 9ae9d1f74e04e9bfe4c900a4cc391193ded2b5e3 Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Sun, 21 Dec 2025 22:18:00 -0600 Subject: [PATCH 2/2] feat: add NotFound variant to ContractResponse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add explicit NotFound response type to the client API to distinguish "contract not found" from other failure modes like timeouts. Changes: - Add NotFound table to FlatBuffers schema with instance_id field - Add NotFound variant to ContractResponse enum - Update into_fbs_bytes to serialize NotFound - Update Display impl to handle NotFound - Regenerate flatbuffers with flatc 24.3.25 for compatibility - Bump version to 0.1.29 This enables freenet-core to return ContractResponse::NotFound when a GET or SUBSCRIBE operation determines that a contract doesn't exist after exhaustive search, rather than returning a generic error. Closes #2368 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- rust/Cargo.toml | 2 +- rust/src/client_api/client_events.rs | 69 ++++++-- rust/src/contract_interface/key.rs | 1 - rust/src/generated/host_response_generated.rs | 163 +++++++++++++++++- schemas/flatbuffers/host_response.fbs | 7 +- 5 files changed, 224 insertions(+), 18 deletions(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 64b2c72..02f2156 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "freenet-stdlib" -version = "0.1.28" +version = "0.1.29" edition = "2021" rust-version = "1.71.1" publish = true diff --git a/rust/src/client_api/client_events.rs b/rust/src/client_api/client_events.rs index 16e2f9e..8ea9a47 100644 --- a/rust/src/client_api/client_events.rs +++ b/rust/src/client_api/client_events.rs @@ -16,14 +16,14 @@ use crate::generated::common::{ ApplicationMessage as FbsApplicationMessage, ApplicationMessageArgs, ContractCode, ContractCodeArgs, ContractContainer as FbsContractContainer, ContractContainerArgs, ContractInstanceId as FbsContractInstanceId, ContractInstanceIdArgs, - ContractKey as FbsContractKey, ContractKeyArgs, - ContractType, DeltaUpdate, DeltaUpdateArgs, GetSecretRequest as FbsGetSecretRequest, - GetSecretRequestArgs, GetSecretResponse as FbsGetSecretResponse, GetSecretResponseArgs, - RelatedDeltaUpdate, RelatedDeltaUpdateArgs, RelatedStateAndDeltaUpdate, - RelatedStateAndDeltaUpdateArgs, RelatedStateUpdate, RelatedStateUpdateArgs, - SecretsId as FbsSecretsId, SecretsIdArgs, StateAndDeltaUpdate, StateAndDeltaUpdateArgs, - StateUpdate, StateUpdateArgs, UpdateData as FbsUpdateData, UpdateDataArgs, UpdateDataType, - WasmContractV1, WasmContractV1Args, + ContractKey as FbsContractKey, ContractKeyArgs, ContractType, DeltaUpdate, DeltaUpdateArgs, + GetSecretRequest as FbsGetSecretRequest, GetSecretRequestArgs, + GetSecretResponse as FbsGetSecretResponse, GetSecretResponseArgs, RelatedDeltaUpdate, + RelatedDeltaUpdateArgs, RelatedStateAndDeltaUpdate, RelatedStateAndDeltaUpdateArgs, + RelatedStateUpdate, RelatedStateUpdateArgs, SecretsId as FbsSecretsId, SecretsIdArgs, + StateAndDeltaUpdate, StateAndDeltaUpdateArgs, StateUpdate, StateUpdateArgs, + UpdateData as FbsUpdateData, UpdateDataArgs, UpdateDataType, WasmContractV1, + WasmContractV1Args, }; use crate::generated::host_response::{ finish_host_response_buffer, ClientResponse as FbsClientResponse, ClientResponseArgs, @@ -31,10 +31,10 @@ use crate::generated::host_response::{ ContractResponse as FbsContractResponse, ContractResponseArgs, ContractResponseType, DelegateKey as FbsDelegateKey, DelegateKeyArgs, DelegateResponse as FbsDelegateResponse, DelegateResponseArgs, GetResponse as FbsGetResponse, GetResponseArgs, - HostResponse as FbsHostResponse, HostResponseArgs, HostResponseType, Ok as FbsOk, OkArgs, - OutboundDelegateMsg as FbsOutboundDelegateMsg, OutboundDelegateMsgArgs, - OutboundDelegateMsgType, PutResponse as FbsPutResponse, PutResponseArgs, - RequestUserInput as FbsRequestUserInput, RequestUserInputArgs, + HostResponse as FbsHostResponse, HostResponseArgs, HostResponseType, NotFound as FbsNotFound, + NotFoundArgs, Ok as FbsOk, OkArgs, OutboundDelegateMsg as FbsOutboundDelegateMsg, + OutboundDelegateMsgArgs, OutboundDelegateMsgType, PutResponse as FbsPutResponse, + PutResponseArgs, RequestUserInput as FbsRequestUserInput, RequestUserInputArgs, SetSecretRequest as FbsSetSecretRequest, SetSecretRequestArgs, UpdateNotification as FbsUpdateNotification, UpdateNotificationArgs, UpdateResponse as FbsUpdateResponse, UpdateResponseArgs, @@ -1349,6 +1349,41 @@ impl HostResponse { Ok(builder.finished_data().to_vec()) } ContractResponse::SubscribeResponse { .. } => todo!(), + ContractResponse::NotFound { instance_id } => { + let instance_data = builder.create_vector(instance_id.as_bytes()); + let instance_offset = FbsContractInstanceId::create( + &mut builder, + &ContractInstanceIdArgs { + data: Some(instance_data), + }, + ); + + let not_found_offset = FbsNotFound::create( + &mut builder, + &NotFoundArgs { + instance_id: Some(instance_offset), + }, + ); + + let contract_response_offset = FbsContractResponse::create( + &mut builder, + &ContractResponseArgs { + contract_response_type: ContractResponseType::NotFound, + contract_response: Some(not_found_offset.as_union_value()), + }, + ); + + let response_offset = FbsHostResponse::create( + &mut builder, + &HostResponseArgs { + response: Some(contract_response_offset.as_union_value()), + response_type: HostResponseType::ContractResponse, + }, + ); + + finish_host_response_buffer(&mut builder, response_offset); + Ok(builder.finished_data().to_vec()) + } }, HostResponse::DelegateResponse { key, values } => { let key_data = builder.create_vector(key.bytes()); @@ -1588,6 +1623,9 @@ impl Display for HostResponse { ContractResponse::SubscribeResponse { key, .. } => { f.write_fmt(format_args!("subscribe response for `{key}`")) } + ContractResponse::NotFound { instance_id } => { + f.write_fmt(format_args!("not found for `{instance_id}`")) + } }, HostResponse::DelegateResponse { .. } => write!(f, "delegate responses"), HostResponse::Ok => write!(f, "ok response"), @@ -1624,6 +1662,13 @@ pub enum ContractResponse { key: ContractKey, subscribed: bool, }, + /// Contract was not found after exhaustive search. + /// This is an explicit response that distinguishes "contract doesn't exist" + /// from other failure modes like timeouts or network errors. + NotFound { + /// The instance ID that was searched for. + instance_id: ContractInstanceId, + }, } impl From> for HostResponse { diff --git a/rust/src/contract_interface/key.rs b/rust/src/contract_interface/key.rs index 5c56e88..f2ee052 100644 --- a/rust/src/contract_interface/key.rs +++ b/rust/src/contract_interface/key.rs @@ -209,7 +209,6 @@ impl std::hash::Hash for ContractKey { } } - impl From for ContractInstanceId { fn from(key: ContractKey) -> Self { key.instance diff --git a/rust/src/generated/host_response_generated.rs b/rust/src/generated/host_response_generated.rs index d9f75cb..71b7cfb 100644 --- a/rust/src/generated/host_response_generated.rs +++ b/rust/src/generated/host_response_generated.rs @@ -28,18 +28,19 @@ pub mod host_response { since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021." )] - pub const ENUM_MAX_CONTRACT_RESPONSE_TYPE: u8 = 4; + pub const ENUM_MAX_CONTRACT_RESPONSE_TYPE: u8 = 5; #[deprecated( since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021." )] #[allow(non_camel_case_types)] - pub const ENUM_VALUES_CONTRACT_RESPONSE_TYPE: [ContractResponseType; 5] = [ + pub const ENUM_VALUES_CONTRACT_RESPONSE_TYPE: [ContractResponseType; 6] = [ ContractResponseType::NONE, ContractResponseType::GetResponse, ContractResponseType::PutResponse, ContractResponseType::UpdateNotification, ContractResponseType::UpdateResponse, + ContractResponseType::NotFound, ]; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] @@ -52,15 +53,17 @@ pub mod host_response { pub const PutResponse: Self = Self(2); pub const UpdateNotification: Self = Self(3); pub const UpdateResponse: Self = Self(4); + pub const NotFound: Self = Self(5); pub const ENUM_MIN: u8 = 0; - pub const ENUM_MAX: u8 = 4; + pub const ENUM_MAX: u8 = 5; pub const ENUM_VALUES: &'static [Self] = &[ Self::NONE, Self::GetResponse, Self::PutResponse, Self::UpdateNotification, Self::UpdateResponse, + Self::NotFound, ]; /// Returns the variant's name or "" if unknown. pub fn variant_name(self) -> Option<&'static str> { @@ -70,6 +73,7 @@ pub mod host_response { Self::PutResponse => Some("PutResponse"), Self::UpdateNotification => Some("UpdateNotification"), Self::UpdateResponse => Some("UpdateResponse"), + Self::NotFound => Some("NotFound"), _ => None, } } @@ -996,6 +1000,134 @@ pub mod host_response { ds.finish() } } + pub enum NotFoundOffset {} + #[derive(Copy, Clone, PartialEq)] + + pub struct NotFound<'a> { + pub _tab: flatbuffers::Table<'a>, + } + + impl<'a> flatbuffers::Follow<'a> for NotFound<'a> { + type Inner = NotFound<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { + _tab: flatbuffers::Table::new(buf, loc), + } + } + } + + impl<'a> NotFound<'a> { + pub const VT_INSTANCE_ID: flatbuffers::VOffsetT = 4; + + #[inline] + pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + NotFound { _tab: table } + } + #[allow(unused_mut)] + pub fn create< + 'bldr: 'args, + 'args: 'mut_bldr, + 'mut_bldr, + A: flatbuffers::Allocator + 'bldr, + >( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args NotFoundArgs<'args>, + ) -> flatbuffers::WIPOffset> { + let mut builder = NotFoundBuilder::new(_fbb); + if let Some(x) = args.instance_id { + builder.add_instance_id(x); + } + builder.finish() + } + + #[inline] + pub fn instance_id(&self) -> super::common::ContractInstanceId<'a> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { + self._tab + .get::>( + NotFound::VT_INSTANCE_ID, + None, + ) + .unwrap() + } + } + } + + impl flatbuffers::Verifiable for NotFound<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, + pos: usize, + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::>( + "instance_id", + Self::VT_INSTANCE_ID, + true, + )? + .finish(); + Ok(()) + } + } + pub struct NotFoundArgs<'a> { + pub instance_id: Option>>, + } + impl<'a> Default for NotFoundArgs<'a> { + #[inline] + fn default() -> Self { + NotFoundArgs { + instance_id: None, // required field + } + } + } + + pub struct NotFoundBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + start_: flatbuffers::WIPOffset, + } + impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> NotFoundBuilder<'a, 'b, A> { + #[inline] + pub fn add_instance_id( + &mut self, + instance_id: flatbuffers::WIPOffset>, + ) { + self.fbb_ + .push_slot_always::>( + NotFound::VT_INSTANCE_ID, + instance_id, + ); + } + #[inline] + pub fn new( + _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + ) -> NotFoundBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + NotFoundBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + self.fbb_ + .required(o, NotFound::VT_INSTANCE_ID, "instance_id"); + flatbuffers::WIPOffset::new(o.value()) + } + } + + impl core::fmt::Debug for NotFound<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("NotFound"); + ds.field("instance_id", &self.instance_id()); + ds.finish() + } + } pub enum ContractResponseOffset {} #[derive(Copy, Clone, PartialEq)] @@ -1122,6 +1254,20 @@ pub mod host_response { None } } + + #[inline] + #[allow(non_snake_case)] + pub fn contract_response_as_not_found(&self) -> Option> { + if self.contract_response_type() == ContractResponseType::NotFound { + let u = self.contract_response(); + // Safety: + // Created from a valid Table for this object + // Which contains a valid union in this slot + Some(unsafe { NotFound::init_from_table(u) }) + } else { + None + } + } } impl flatbuffers::Verifiable for ContractResponse<'_> { @@ -1138,6 +1284,7 @@ pub mod host_response { ContractResponseType::PutResponse => v.verify_union_variant::>("ContractResponseType::PutResponse", pos), ContractResponseType::UpdateNotification => v.verify_union_variant::>("ContractResponseType::UpdateNotification", pos), ContractResponseType::UpdateResponse => v.verify_union_variant::>("ContractResponseType::UpdateResponse", pos), + ContractResponseType::NotFound => v.verify_union_variant::>("ContractResponseType::NotFound", pos), _ => Ok(()), } })? @@ -1249,6 +1396,16 @@ pub mod host_response { ) } } + ContractResponseType::NotFound => { + if let Some(x) = self.contract_response_as_not_found() { + ds.field("contract_response", &x) + } else { + ds.field( + "contract_response", + &"InvalidFlatbuffer: Union discriminant does not match value.", + ) + } + } _ => { let x: Option<()> = None; ds.field("contract_response", &x) diff --git a/schemas/flatbuffers/host_response.fbs b/schemas/flatbuffers/host_response.fbs index 0a93095..f0d1951 100644 --- a/schemas/flatbuffers/host_response.fbs +++ b/schemas/flatbuffers/host_response.fbs @@ -21,11 +21,16 @@ table UpdateResponse { summary: [ubyte](required); } +table NotFound { + instance_id: common.ContractInstanceId(required); +} + union ContractResponseType { GetResponse, PutResponse, UpdateNotification, - UpdateResponse + UpdateResponse, + NotFound } table ContractResponse {