1use std::{
9 any::TypeId,
10 future::Future,
11 marker::PhantomData,
12 mem,
13 pin::Pin,
14 task::{Context, Poll},
15};
16
17use saddle_admission::{
18 DbRequestPermit, DbRouteCreditDemand, DbRouteResources, ManagedBytes, ManagedResponse,
19 ManagedResponseBuilder, RequestMemory,
20};
21use saddle_db::internal::{
22 ManagedWriteResult, QueryOptionalExecution, QueryOptionalInvocation,
23 QueryOptionalOperationProof, StaticQueryOptionalOperation, StaticWriteOperation,
24 TransactionExecution, TransactionInvocation, TransactionOperationProof, WriteExecution,
25 WriteInvocation, WriteOperationProof,
26};
27
28use crate::{
29 Service,
30 c4_entry::{
31 EntryCapabilityLimits, EntryFraming, EntryPlanSpec, RegisteredDbDemand, RegisteredEntry,
32 RegisteredRouteExecutionProof, RegistryError, ServiceEntryCapability,
33 ServiceEntryCapabilityBuilder,
34 },
35 registry::ServiceDescriptor,
36};
37
38const SERVICE_CAPACITY_LEAF_SCHEMA: &[u8] = b"saddle.service-capacity-leaf.v1";
39
40#[doc(hidden)]
44pub trait GeneratedServiceCapacityArtifactOwner: Sized {
45 fn build_identity(&self) -> [u8; 32];
46 fn artifact_identity(&self) -> [u8; 32];
47 fn owner_generation(&self) -> u64;
48}
49
50#[derive(Debug)]
51pub(super) struct ServiceCapacityRouteRecord {
52 pub(super) identity: u64,
53 pub(super) commitment: usize,
54 pub(super) managed_objects_peak: usize,
55 pub(super) db_connections: usize,
56 pub(super) db_operations: usize,
57 pub(super) source_identity: [u8; 32],
58}
59
60#[derive(Clone, Copy)]
61enum ManagedOwnerContribution {
62 RequestBody,
63 ResponseReservation,
64}
65
66impl ManagedOwnerContribution {
67 const fn count(self) -> usize {
68 match self {
69 Self::RequestBody | Self::ResponseReservation => 1,
70 }
71 }
72}
73
74fn compiled_route_managed_objects_peak() -> Result<usize, ExecutionError> {
75 [
76 ManagedOwnerContribution::RequestBody,
77 ManagedOwnerContribution::ResponseReservation,
78 ]
79 .into_iter()
80 .try_fold(0_usize, |peak, contribution| {
81 peak.checked_add(contribution.count())
82 .ok_or(ExecutionError::OutputTooLarge)
83 })
84}
85
86#[doc(hidden)]
89#[derive(Debug)]
90pub(super) struct FrozenServiceCapacitySourceLeaf {
91 pub(super) leaf_identity: [u8; 32],
92 pub(super) common_identities: [[u8; 32]; 3],
93 owner_generation: u64,
94 pub(super) route_type_closure_identity: [u8; 32],
95 pub(super) routes: Box<[ServiceCapacityRouteRecord]>,
96}
97
98#[doc(hidden)]
101pub struct FrozenServiceCapacitySourceView<'a> {
102 leaf: &'a FrozenServiceCapacitySourceLeaf,
103}
104
105impl saddle_admission::ServiceCapacitySourceLeaf for FrozenServiceCapacitySourceView<'_> {
106 fn leaf_identity(&self) -> [u8; 32] {
107 self.leaf.leaf_identity
108 }
109
110 fn common_identities(&self) -> [[u8; 32]; 3] {
111 self.leaf.common_identities
112 }
113
114 fn owner_generation(&self) -> u64 {
115 self.leaf.owner_generation
116 }
117
118 fn route_type_closure_identity(&self) -> [u8; 32] {
119 self.leaf.route_type_closure_identity
120 }
121
122 fn route_count(&self) -> usize {
123 self.leaf.routes.len()
124 }
125
126 fn route_identity(&self, index: usize) -> Option<u64> {
127 self.leaf.routes.get(index).map(|route| route.identity)
128 }
129
130 fn managed_commitment_bytes(&self, index: usize) -> Option<usize> {
131 self.leaf.routes.get(index).map(|route| route.commitment)
132 }
133
134 fn managed_objects_peak(&self, index: usize) -> Option<usize> {
135 self.leaf
136 .routes
137 .get(index)
138 .map(|route| route.managed_objects_peak)
139 }
140
141 fn db_connections(&self, index: usize) -> Option<usize> {
142 self.leaf
143 .routes
144 .get(index)
145 .map(|route| route.db_connections)
146 }
147
148 fn db_operations(&self, index: usize) -> Option<usize> {
149 self.leaf.routes.get(index).map(|route| route.db_operations)
150 }
151}
152
153struct ServiceCapacityDigest([u64; 4]);
154
155impl ServiceCapacityDigest {
156 fn new() -> Self {
157 Self([
158 0xcbf2_9ce4_8422_2325,
159 0x9e37_79b9_7f4a_7c15,
160 0x6a09_e667_f3bc_c909,
161 0xbb67_ae85_84ca_a73b,
162 ])
163 }
164
165 fn write(&mut self, bytes: &[u8]) {
166 const PRIMES: [u64; 4] = [
167 0x0000_0100_0000_01b3,
168 0x9e37_79b1_85eb_ca87,
169 0xc2b2_ae3d_27d4_eb4f,
170 0x1656_67b1_9e37_79f9,
171 ];
172 for (index, byte) in bytes.iter().copied().enumerate() {
173 for (lane, prime) in self.0.iter_mut().zip(PRIMES) {
174 *lane ^= u64::from(byte).wrapping_add(index as u64);
175 *lane = lane.wrapping_mul(prime);
176 *lane ^= *lane >> 29;
177 }
178 }
179 }
180
181 fn usize(&mut self, value: usize) {
182 self.write(&value.to_le_bytes());
183 }
184
185 fn finish(self) -> [u8; 32] {
186 let mut output = [0; 32];
187 for (index, lane) in self.0.into_iter().enumerate() {
188 output[index * 8..(index + 1) * 8].copy_from_slice(&lane.to_le_bytes());
189 }
190 output
191 }
192}
193
194struct ServiceRouteSourceFacts<'a> {
195 route_token: &'a [u8],
196 framing: EntryFraming,
197 identity: crate::c4_entry::RegisteredEntryIdentity,
198 layout_source_identity: [u8; 32],
199 commitment: usize,
200 managed_objects_peak: usize,
201 db_connections: usize,
202 db_operations: usize,
203}
204
205fn service_route_source_identity(facts: ServiceRouteSourceFacts<'_>) -> [u8; 32] {
206 let mut digest = ServiceCapacityDigest::new();
207 digest.write(SERVICE_CAPACITY_LEAF_SCHEMA);
208 digest.write(b"route-source");
209 digest.usize(facts.route_token.len());
210 digest.write(facts.route_token);
211 digest.write(&facts.framing.source_code());
212 digest.write(&facts.identity.contract().opaque().to_le_bytes());
213 digest.write(&facts.identity.plan().opaque().to_le_bytes());
214 digest.write(&facts.identity.factory().opaque().to_le_bytes());
215 digest.write(&facts.layout_source_identity);
216 digest.usize(facts.commitment);
217 digest.usize(facts.managed_objects_peak);
218 digest.usize(facts.db_connections);
219 digest.usize(facts.db_operations);
220 digest.finish()
221}
222
223fn service_route_type_closure_identity(
224 artifact: [u8; 32],
225 routes: &[ServiceCapacityRouteRecord],
226) -> [u8; 32] {
227 let mut digest = ServiceCapacityDigest::new();
228 digest.write(SERVICE_CAPACITY_LEAF_SCHEMA);
229 digest.write(b"route-type-owner-closure");
230 digest.write(&artifact);
231 digest.write(b"managed-request-body-owner");
232 digest.write(b"managed-response-reservation-owner");
233 digest.usize(routes.len());
234 for route in routes {
235 digest.write(&route.source_identity);
236 digest.usize(route.managed_objects_peak);
237 }
238 digest.finish()
239}
240
241fn service_route_set_identity(routes: &[ServiceCapacityRouteRecord]) -> [u8; 32] {
242 let mut digest = ServiceCapacityDigest::new();
243 digest.write(SERVICE_CAPACITY_LEAF_SCHEMA);
244 digest.write(b"route-set");
245 digest.usize(routes.len());
246 for route in routes {
247 digest.write(&route.source_identity);
248 }
249 digest.finish()
250}
251
252fn service_leaf_identity(
253 build: [u8; 32],
254 artifact: [u8; 32],
255 route_set: [u8; 32],
256 owner_generation: u64,
257 route_type_closure: [u8; 32],
258 routes: &[ServiceCapacityRouteRecord],
259) -> [u8; 32] {
260 let mut digest = ServiceCapacityDigest::new();
261 digest.write(SERVICE_CAPACITY_LEAF_SCHEMA);
262 digest.write(b"leaf");
263 digest.write(&build);
264 digest.write(&artifact);
265 digest.write(&route_set);
266 digest.write(&owner_generation.to_le_bytes());
267 digest.write(&route_type_closure);
268 digest.usize(routes.len());
269 for route in routes {
270 digest.write(&route.source_identity);
271 }
272 digest.finish()
273}
274
275#[doc(hidden)]
277pub struct CompiledExecutionWithCapacityLeaf<E, C, F, const BODY: usize, const OUTPUT: usize>
278where
279 E: Fn(usize, C, ManagedBytes, CompiledDbPermit, ManagedResponseBuilder) -> F
280 + Send
281 + Sync
282 + 'static,
283 C: Send + Unpin + 'static,
284 F: Future<Output = Result<ManagedResponse, ExecutionError>> + Send + 'static,
285{
286 pub(super) execution: CompiledExecutionCapability<E, C, F, BODY, OUTPUT>,
287 pub(super) service_leaf: FrozenServiceCapacitySourceLeaf,
288}
289
290impl<E, C, F, const BODY: usize, const OUTPUT: usize>
291 CompiledExecutionWithCapacityLeaf<E, C, F, BODY, OUTPUT>
292where
293 E: Fn(usize, C, ManagedBytes, CompiledDbPermit, ManagedResponseBuilder) -> F
294 + Send
295 + Sync
296 + 'static,
297 C: Send + Unpin + 'static,
298 F: Future<Output = Result<ManagedResponse, ExecutionError>> + Send + 'static,
299{
300 pub fn capacity_source(&self) -> FrozenServiceCapacitySourceView<'_> {
302 FrozenServiceCapacitySourceView {
303 leaf: &self.service_leaf,
304 }
305 }
306
307 #[doc(hidden)]
310 pub fn production_fact_input(
311 &self,
312 ) -> Result<
313 crate::production_fact::ServiceProductionFactInput,
314 crate::production_fact::ServiceProductionFactError,
315 > {
316 crate::production_fact::service_production_fact_input(self)
317 }
318
319 pub fn lookup(
321 &self,
322 route_token: &[u8],
323 framing: EntryFraming,
324 declared_length: Option<u64>,
325 ) -> Result<RegisteredRouteExecutionProof, RegistryError> {
326 self.execution.lookup(route_token, framing, declared_length)
327 }
328}
329
330#[derive(Clone, Copy, Debug, Eq, PartialEq)]
331pub enum ExecutionError {
332 Registry(RegistryError),
333 CompiledRegistry(CompiledRegistryError),
334 UnknownExecution,
335 InputTooLarge,
336 InvalidInput,
337 OutputTooLarge,
338 RouteResourcesMismatch,
339 ResponseReservationFailed,
340 Business,
341 DependencyUnavailable,
342 DependencyExecutionFailed,
345 MissingCapacityIdentity,
346}
347
348impl From<RegistryError> for ExecutionError {
349 fn from(error: RegistryError) -> Self {
350 Self::Registry(error)
351 }
352}
353
354impl From<CompiledRegistryError> for ExecutionError {
355 fn from(error: CompiledRegistryError) -> Self {
356 Self::CompiledRegistry(error)
357 }
358}
359
360impl ExecutionError {
361 pub const fn response_outcome_class(
365 self,
366 ) -> saddle_runtime::compiled_route::ResponseOutcomeClass {
367 use saddle_runtime::compiled_route::ResponseOutcomeClass;
368
369 match self {
370 Self::InputTooLarge | Self::InvalidInput => ResponseOutcomeClass::InvalidRequest,
371 Self::Business => ResponseOutcomeClass::BusinessRejected,
372 Self::RouteResourcesMismatch | Self::DependencyUnavailable => {
373 ResponseOutcomeClass::Unavailable
374 }
375 Self::Registry(_)
376 | Self::CompiledRegistry(_)
377 | Self::UnknownExecution
378 | Self::OutputTooLarge
379 | Self::ResponseReservationFailed
380 | Self::DependencyExecutionFailed => ResponseOutcomeClass::Internal,
381 Self::MissingCapacityIdentity => ResponseOutcomeClass::Internal,
382 }
383 }
384}
385
386#[derive(Clone, Copy, Debug, Eq, PartialEq)]
387pub enum CompiledRegistryError {
388 InvalidCapacity,
389 AllocationFailed,
390 CapacityExceeded,
391 EmptyRegistry,
392 DuplicateContract,
393 DuplicateIdentity,
394}
395
396struct CompiledServiceEntry {
397 contract: TypeId,
398 _descriptor: ServiceDescriptor,
399}
400
401pub struct CompiledServiceRegistryBuilder {
406 entries: Vec<CompiledServiceEntry>,
407 max_services: usize,
408}
409
410impl CompiledServiceRegistryBuilder {
411 pub fn new(max_services: usize) -> Result<Self, CompiledRegistryError> {
412 if max_services == 0 || max_services > u32::MAX as usize {
413 return Err(CompiledRegistryError::InvalidCapacity);
414 }
415 let mut entries = Vec::new();
416 entries
417 .try_reserve_exact(max_services)
418 .map_err(|_| CompiledRegistryError::AllocationFailed)?;
419 Ok(Self {
420 entries,
421 max_services,
422 })
423 }
424
425 pub fn register<S>(
426 &mut self,
427 descriptor: ServiceDescriptor,
428 ) -> Result<(), CompiledRegistryError>
429 where
430 S: Service,
431 {
432 if self.entries.len() == self.max_services {
433 return Err(CompiledRegistryError::CapacityExceeded);
434 }
435 let contract = TypeId::of::<S>();
436 if self.entries.iter().any(|entry| entry.contract == contract) {
437 return Err(CompiledRegistryError::DuplicateContract);
438 }
439 if self
440 .entries
441 .iter()
442 .any(|entry| entry._descriptor == descriptor)
443 {
444 return Err(CompiledRegistryError::DuplicateIdentity);
445 }
446 self.entries.push(CompiledServiceEntry {
447 contract,
448 _descriptor: descriptor,
449 });
450 Ok(())
451 }
452
453 pub fn freeze(self) -> Result<CompiledServiceRegistry, CompiledRegistryError> {
454 if self.entries.is_empty() {
455 return Err(CompiledRegistryError::EmptyRegistry);
456 }
457 Ok(CompiledServiceRegistry {
458 entries: self.entries,
459 })
460 }
461}
462
463pub struct CompiledServiceRegistry {
465 entries: Vec<CompiledServiceEntry>,
466}
467
468impl CompiledServiceRegistry {
469 pub(super) fn internal_entry_count(&self) -> usize {
470 self.entries.len()
471 }
472
473 pub(super) fn internal_entry_index<S>(&self) -> Option<usize>
474 where
475 S: Service,
476 {
477 self.entries
478 .iter()
479 .position(|entry| entry.contract == TypeId::of::<S>())
480 }
481}
482
483mod sealed {
484 pub trait ManagedValue {}
485 pub trait ManagedCodec {}
486}
487
488pub trait ManagedValue: sealed::ManagedValue + Send + 'static {}
490
491#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
492pub struct ManagedU64(pub u64);
493
494impl sealed::ManagedValue for ManagedU64 {}
495impl ManagedValue for ManagedU64 {}
496
497#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
498pub struct ManagedBool(pub bool);
499
500impl sealed::ManagedValue for ManagedBool {}
501impl ManagedValue for ManagedBool {}
502
503#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
504pub struct ManagedPair<A: ManagedValue, B: ManagedValue>(pub A, pub B);
505
506impl<A: ManagedValue, B: ManagedValue> sealed::ManagedValue for ManagedPair<A, B> {}
507impl<A: ManagedValue, B: ManagedValue> ManagedValue for ManagedPair<A, B> {}
508
509pub trait ManagedCodec<Request: ManagedValue, Response: ManagedValue>:
512 sealed::ManagedCodec + Send + 'static
513{
514 const WORKSPACE_BYTES: usize;
515}
516
517pub struct FixedManagedCodec<Request: ManagedValue, Response: ManagedValue>(
518 PhantomData<(Request, Response)>,
519);
520
521impl<Request: ManagedValue, Response: ManagedValue> sealed::ManagedCodec
522 for FixedManagedCodec<Request, Response>
523{
524}
525
526impl<Request: ManagedValue, Response: ManagedValue> ManagedCodec<Request, Response>
527 for FixedManagedCodec<Request, Response>
528{
529 const WORKSPACE_BYTES: usize = mem::size_of::<Request>() + mem::size_of::<Response>();
530}
531
532#[derive(Debug)]
534pub struct FixedBytes<const N: usize> {
535 bytes: [u8; N],
536 length: usize,
537}
538
539impl<const N: usize> FixedBytes<N> {
540 pub const fn empty() -> Self {
541 Self {
542 bytes: [0; N],
543 length: 0,
544 }
545 }
546
547 pub fn try_from_slice(value: &[u8]) -> Result<Self, ExecutionError> {
548 if value.len() > N {
549 return Err(ExecutionError::InputTooLarge);
550 }
551 let mut output = Self::empty();
552 output.bytes[..value.len()].copy_from_slice(value);
553 output.length = value.len();
554 Ok(output)
555 }
556
557 pub const fn capacity(&self) -> usize {
558 N
559 }
560
561 pub const fn len(&self) -> usize {
562 self.length
563 }
564
565 pub const fn is_empty(&self) -> bool {
566 self.length == 0
567 }
568
569 pub fn as_slice(&self) -> &[u8] {
570 &self.bytes[..self.length]
571 }
572
573 pub fn try_append(&mut self, value: &[u8]) -> Result<(), ExecutionError> {
574 let end = self
575 .length
576 .checked_add(value.len())
577 .ok_or(ExecutionError::OutputTooLarge)?;
578 if end > N {
579 return Err(ExecutionError::OutputTooLarge);
580 }
581 self.bytes[self.length..end].copy_from_slice(value);
582 self.length = end;
583 Ok(())
584 }
585}
586
587#[derive(Clone, Copy, Debug, Eq, PartialEq)]
590pub struct ExecutionLayoutProof {
591 service: TypeId,
592 binding: TypeId,
593 body_bytes: usize,
594 decode_workspace_bytes: usize,
595 request_bytes: usize,
596 future_bytes: usize,
597 response_bytes: usize,
598 encode_workspace_bytes: usize,
599 output_bytes: usize,
600}
601
602impl ExecutionLayoutProof {
603 pub fn bind<
604 S,
605 const BODY: usize,
606 const OUTPUT: usize,
607 Request,
608 Response,
609 Codec,
610 HandlerFuture,
611 >() -> Result<Self, ExecutionError>
612 where
613 S: Service<Request = Request, Response = Response>,
614 Request: ManagedValue,
615 Response: ManagedValue,
616 Codec: ManagedCodec<Request, Response>,
617 HandlerFuture: Future<Output = Result<Response, ExecutionError>> + Send + 'static,
618 {
619 let proof = Self {
620 service: TypeId::of::<S>(),
621 binding: TypeId::of::<(S, Request, Response, Codec, HandlerFuture)>(),
622 body_bytes: BODY,
623 decode_workspace_bytes: Codec::WORKSPACE_BYTES,
624 request_bytes: mem::size_of::<Request>(),
625 future_bytes: mem::size_of::<HandlerFuture>(),
626 response_bytes: mem::size_of::<Response>(),
627 encode_workspace_bytes: Codec::WORKSPACE_BYTES,
628 output_bytes: OUTPUT,
629 };
630 proof.commitment()?;
631 Ok(proof)
632 }
633
634 pub const fn body_bytes(self) -> usize {
635 self.body_bytes
636 }
637
638 pub const fn future_bytes(self) -> usize {
639 self.future_bytes
640 }
641
642 pub fn same_concrete_binding(self, other: Self) -> bool {
643 self.binding == other.binding
644 }
645
646 pub fn commitment(self) -> Result<usize, ExecutionError> {
647 [
648 self.body_bytes,
649 self.decode_workspace_bytes,
650 self.request_bytes,
651 self.future_bytes,
652 self.response_bytes,
653 self.encode_workspace_bytes,
654 self.output_bytes,
655 ]
656 .into_iter()
657 .try_fold(0_usize, |total, bytes| {
658 total
659 .checked_add(bytes)
660 .ok_or(ExecutionError::OutputTooLarge)
661 })
662 }
663
664 fn plan(self) -> Result<EntryPlanSpec, ExecutionError> {
665 let body = u64::try_from(self.body_bytes).map_err(|_| ExecutionError::OutputTooLarge)?;
666 let decode = self
667 .decode_workspace_bytes
668 .checked_add(self.request_bytes)
669 .ok_or(ExecutionError::OutputTooLarge)?;
670 let business = self.future_bytes;
671 let response = self.response_bytes;
672 let encode = self
673 .encode_workspace_bytes
674 .checked_add(self.output_bytes)
675 .ok_or(ExecutionError::OutputTooLarge)?;
676 Ok(EntryPlanSpec::new()
677 .body_hard_limit(body)
678 .managed_body_peak(body)
679 .decode_peak(u64::try_from(decode).map_err(|_| ExecutionError::OutputTooLarge)?)
680 .business_working_set_peak(
681 u64::try_from(business).map_err(|_| ExecutionError::OutputTooLarge)?,
682 )
683 .internal_call_peak(0)
684 .response_object_peak(
685 u64::try_from(response).map_err(|_| ExecutionError::OutputTooLarge)?,
686 )
687 .encode_destination_peak(
688 u64::try_from(encode).map_err(|_| ExecutionError::OutputTooLarge)?,
689 )
690 .copy_on_success_peak(0))
691 }
692
693 pub const fn without_db(self) -> CompiledRouteExecutionProof {
695 CompiledRouteExecutionProof {
696 layout: self,
697 db_connections: 0,
698 db_operations: 0,
699 db_binding: None,
700 db_binding_conflict: false,
701 }
702 }
703
704 pub fn with_query_optional<O>(
706 self,
707 operation: QueryOptionalOperationProof<O>,
708 ) -> CompiledRouteExecutionProof
709 where
710 O: StaticQueryOptionalOperation,
711 {
712 let credits = operation.layout().credits();
713 let binding = CompiledDbBinding {
714 kind: CompiledDbOperationKind::QueryOptional,
715 operation: TypeId::of::<O>(),
716 parameter_bytes: operation.layout().parameter_bytes(),
717 result_bytes: operation.layout().optional_row_bytes(),
718 };
719 CompiledRouteExecutionProof {
720 layout: self,
721 db_connections: u64::from(credits.connections()),
722 db_operations: u64::from(credits.operations()),
723 db_binding: Some(binding),
724 db_binding_conflict: false,
725 }
726 }
727
728 pub fn with_write<O>(self, operation: WriteOperationProof<O>) -> CompiledRouteExecutionProof
730 where
731 O: StaticWriteOperation,
732 {
733 let layout = operation.layout();
734 let credits = layout.credits();
735 CompiledRouteExecutionProof {
736 layout: self,
737 db_connections: u64::from(credits.connections()),
738 db_operations: u64::from(credits.operations()),
739 db_binding: Some(CompiledDbBinding {
740 kind: CompiledDbOperationKind::Write,
741 operation: TypeId::of::<O>(),
742 parameter_bytes: layout.parameter_bytes(),
743 result_bytes: mem::size_of::<ManagedWriteResult>(),
744 }),
745 db_binding_conflict: false,
746 }
747 }
748
749 pub fn with_transaction<O>(
751 self,
752 operation: TransactionOperationProof<O>,
753 ) -> CompiledRouteExecutionProof
754 where
755 O: StaticWriteOperation,
756 {
757 let layout = operation.layout();
758 let credits = layout.credits();
759 CompiledRouteExecutionProof {
760 layout: self,
761 db_connections: u64::from(credits.connections()),
762 db_operations: u64::from(credits.operations()),
763 db_binding: Some(CompiledDbBinding {
764 kind: CompiledDbOperationKind::Transaction,
765 operation: TypeId::of::<O>(),
766 parameter_bytes: layout.parameter_bytes(),
767 result_bytes: mem::size_of::<ManagedWriteResult>(),
768 }),
769 db_binding_conflict: false,
770 }
771 }
772}
773
774#[derive(Clone, Copy, Debug, Eq, PartialEq)]
779pub struct CompiledRouteExecutionProof {
780 layout: ExecutionLayoutProof,
781 db_connections: u64,
782 db_operations: u64,
783 db_binding: Option<CompiledDbBinding>,
784 db_binding_conflict: bool,
785}
786
787impl CompiledRouteExecutionProof {
788 fn capacity_source_identity(self) -> [u8; 32] {
789 let mut digest = ServiceCapacityDigest::new();
790 digest.write(SERVICE_CAPACITY_LEAF_SCHEMA);
791 digest.write(b"compiled-route-layout");
792 for value in [
793 self.layout.body_bytes,
794 self.layout.decode_workspace_bytes,
795 self.layout.request_bytes,
796 self.layout.future_bytes,
797 self.layout.response_bytes,
798 self.layout.encode_workspace_bytes,
799 self.layout.output_bytes,
800 ] {
801 digest.usize(value);
802 }
803 digest.write(&self.db_connections.to_le_bytes());
804 digest.write(&self.db_operations.to_le_bytes());
805 match self.db_binding {
806 None => digest.write(b"no-db"),
807 Some(binding) => {
808 digest.write(&[binding.kind as u8]);
809 digest.usize(binding.parameter_bytes);
810 digest.usize(binding.result_bytes);
811 }
812 }
813 digest.finish()
814 }
815}
816
817#[derive(Clone, Copy, Debug, Eq, PartialEq)]
818#[repr(u8)]
819enum CompiledDbOperationKind {
820 QueryOptional,
821 Write,
822 Transaction,
823}
824
825#[derive(Clone, Copy, Debug, Eq, PartialEq)]
826struct CompiledDbBinding {
827 kind: CompiledDbOperationKind,
828 operation: TypeId,
829 parameter_bytes: usize,
830 result_bytes: usize,
831}
832
833pub struct CompiledDbPermit {
836 inner: Option<DbRequestPermit>,
837 route_demand: RegisteredDbDemand,
838 binding: Option<CompiledDbBinding>,
839}
840
841impl CompiledDbPermit {
842 pub const fn is_none(&self) -> bool {
843 self.inner.is_none()
844 }
845
846 pub fn demand(&self) -> Option<(usize, usize)> {
847 self.inner.as_ref().map(|permit| {
848 let demand = permit.demand();
849 (demand.connections(), demand.operations())
850 })
851 }
852
853 pub fn handoff_query_optional<O>(
861 self,
862 invocation: QueryOptionalInvocation<O>,
863 ) -> Result<QueryOptionalExecution<O>, ExecutionError>
864 where
865 O: StaticQueryOptionalOperation,
866 {
867 let layout = invocation.layout();
868 let permit = self.into_permit(
869 CompiledDbBinding {
870 kind: CompiledDbOperationKind::QueryOptional,
871 operation: TypeId::of::<O>(),
872 parameter_bytes: layout.parameter_bytes(),
873 result_bytes: layout.optional_row_bytes(),
874 },
875 layout.credits().connections(),
876 layout.credits().operations(),
877 )?;
878 Ok(QueryOptionalExecution::from_compiled_handoff(
879 permit, invocation,
880 ))
881 }
882
883 pub fn handoff_write<O>(
885 self,
886 invocation: WriteInvocation<O>,
887 ) -> Result<WriteExecution<O>, ExecutionError>
888 where
889 O: StaticWriteOperation,
890 {
891 let layout = invocation.layout();
892 let permit = self.into_permit(
893 CompiledDbBinding {
894 kind: CompiledDbOperationKind::Write,
895 operation: TypeId::of::<O>(),
896 parameter_bytes: layout.parameter_bytes(),
897 result_bytes: mem::size_of::<ManagedWriteResult>(),
898 },
899 layout.credits().connections(),
900 layout.credits().operations(),
901 )?;
902 Ok(WriteExecution::from_compiled_handoff(permit, invocation))
903 }
904
905 pub fn handoff_transaction<O>(
907 self,
908 invocation: TransactionInvocation<O>,
909 ) -> Result<TransactionExecution<O>, ExecutionError>
910 where
911 O: StaticWriteOperation,
912 {
913 let layout = invocation.layout();
914 let permit = self.into_permit(
915 CompiledDbBinding {
916 kind: CompiledDbOperationKind::Transaction,
917 operation: TypeId::of::<O>(),
918 parameter_bytes: layout.parameter_bytes(),
919 result_bytes: mem::size_of::<ManagedWriteResult>(),
920 },
921 layout.credits().connections(),
922 layout.credits().operations(),
923 )?;
924 Ok(TransactionExecution::from_compiled_handoff(
925 permit, invocation,
926 ))
927 }
928
929 fn into_permit(
930 self,
931 binding: CompiledDbBinding,
932 connections: u32,
933 operations: u32,
934 ) -> Result<DbRequestPermit, ExecutionError> {
935 let permit = self.inner.ok_or(ExecutionError::RouteResourcesMismatch)?;
936 if self.binding != Some(binding)
937 || self.route_demand.connections() != u64::from(connections)
938 || self.route_demand.operations() != u64::from(operations)
939 {
940 permit
941 .begin_finalizing()
942 .and_then(|claim| claim.complete_after_connection_return())
943 .map_err(|_| ExecutionError::DependencyUnavailable)?;
944 return Err(ExecutionError::RouteResourcesMismatch);
945 }
946 Ok(permit)
947 }
948
949 #[cfg(test)]
950 fn complete_after_test_connection_return(mut self) {
951 self.inner
952 .take()
953 .expect("DB route must carry its admitted permit")
954 .begin_finalizing()
955 .expect("test connection return must enter finalizing")
956 .complete_after_connection_return()
957 .expect("test connection return must complete its role");
958 }
959}
960
961impl CompiledRouteExecutionProof {
962 pub fn with_query_optional<O>(mut self, operation: QueryOptionalOperationProof<O>) -> Self
963 where
964 O: StaticQueryOptionalOperation,
965 {
966 let layout = operation.layout();
967 let credits = layout.credits();
968 self.merge_db_binding(CompiledDbBinding {
969 kind: CompiledDbOperationKind::QueryOptional,
970 operation: TypeId::of::<O>(),
971 parameter_bytes: layout.parameter_bytes(),
972 result_bytes: layout.optional_row_bytes(),
973 });
974 self.db_connections = self.db_connections.max(u64::from(credits.connections()));
975 self.db_operations = self.db_operations.max(u64::from(credits.operations()));
976 self
977 }
978
979 pub fn with_write<O>(mut self, operation: WriteOperationProof<O>) -> Self
980 where
981 O: StaticWriteOperation,
982 {
983 let layout = operation.layout();
984 let credits = layout.credits();
985 self.merge_db_binding(CompiledDbBinding {
986 kind: CompiledDbOperationKind::Write,
987 operation: TypeId::of::<O>(),
988 parameter_bytes: layout.parameter_bytes(),
989 result_bytes: mem::size_of::<ManagedWriteResult>(),
990 });
991 self.db_connections = self.db_connections.max(u64::from(credits.connections()));
992 self.db_operations = self.db_operations.max(u64::from(credits.operations()));
993 self
994 }
995
996 pub fn with_transaction<O>(mut self, operation: TransactionOperationProof<O>) -> Self
997 where
998 O: StaticWriteOperation,
999 {
1000 let layout = operation.layout();
1001 let credits = layout.credits();
1002 self.merge_db_binding(CompiledDbBinding {
1003 kind: CompiledDbOperationKind::Transaction,
1004 operation: TypeId::of::<O>(),
1005 parameter_bytes: layout.parameter_bytes(),
1006 result_bytes: mem::size_of::<ManagedWriteResult>(),
1007 });
1008 self.db_connections = self.db_connections.max(u64::from(credits.connections()));
1009 self.db_operations = self.db_operations.max(u64::from(credits.operations()));
1010 self
1011 }
1012
1013 fn merge_db_binding(&mut self, binding: CompiledDbBinding) {
1014 if self
1015 .db_binding
1016 .is_some_and(|registered| registered != binding)
1017 {
1018 self.db_binding_conflict = true;
1019 } else {
1020 self.db_binding = Some(binding);
1021 }
1022 }
1023}
1024
1025pub struct InternalCallCapability<Caller: Service, Callee: Service> {
1027 _types: PhantomData<fn(Caller) -> Callee>,
1028}
1029
1030pub struct CompiledExecutionBuilder<E, L, C, F, const BODY: usize, const OUTPUT: usize>
1033where
1034 E: Fn(usize, C, ManagedBytes, CompiledDbPermit, ManagedResponseBuilder) -> F
1035 + Send
1036 + Sync
1037 + 'static,
1038 L: Fn(usize) -> Option<CompiledRouteExecutionProof> + Send + Sync + 'static,
1039 C: Send + Unpin + 'static,
1040 F: Future<Output = Result<ManagedResponse, ExecutionError>> + Send + 'static,
1041{
1042 entries: ServiceEntryCapabilityBuilder,
1043 dispatcher: E,
1044 layouts: L,
1045 db_bindings: Vec<Option<CompiledDbBinding>>,
1046 capacity_routes: Vec<ServiceCapacityRouteRecord>,
1047 _types: PhantomData<fn(C) -> F>,
1048}
1049
1050impl<E, L, C, F, const BODY: usize, const OUTPUT: usize>
1051 CompiledExecutionBuilder<E, L, C, F, BODY, OUTPUT>
1052where
1053 E: Fn(usize, C, ManagedBytes, CompiledDbPermit, ManagedResponseBuilder) -> F
1054 + Send
1055 + Sync
1056 + 'static,
1057 L: Fn(usize) -> Option<CompiledRouteExecutionProof> + Send + Sync + 'static,
1058 C: Send + Unpin + 'static,
1059 F: Future<Output = Result<ManagedResponse, ExecutionError>> + Send + 'static,
1060{
1061 pub fn new(
1062 registry: CompiledServiceRegistry,
1063 dispatcher: E,
1064 layouts: L,
1065 limits: EntryCapabilityLimits,
1066 ) -> Result<Self, ExecutionError> {
1067 let entry_count = registry.internal_entry_count();
1068 let mut db_bindings = Vec::new();
1069 db_bindings
1070 .try_reserve_exact(entry_count)
1071 .map_err(|_| CompiledRegistryError::AllocationFailed)?;
1072 db_bindings.resize(entry_count, None);
1073 let mut capacity_routes = Vec::new();
1074 capacity_routes
1075 .try_reserve_exact(limits.max_routes())
1076 .map_err(|_| CompiledRegistryError::AllocationFailed)?;
1077 Ok(Self {
1078 entries: ServiceEntryCapabilityBuilder::new_compiled(registry, limits)?,
1079 dispatcher,
1080 layouts,
1081 db_bindings,
1082 capacity_routes,
1083 _types: PhantomData,
1084 })
1085 }
1086
1087 pub fn expose<S>(
1090 &mut self,
1091 route_token: &[u8],
1092 framing: EntryFraming,
1093 ) -> Result<(), ExecutionError>
1094 where
1095 S: Service,
1096 {
1097 let slot = self.entries.service_index::<S>()?;
1098 let proof = (self.layouts)(slot).ok_or(ExecutionError::UnknownExecution)?;
1099 if proof.layout.service != TypeId::of::<S>()
1100 || proof.layout.body_bytes != BODY
1101 || proof.layout.output_bytes != OUTPUT
1102 || proof.db_binding_conflict
1103 || (proof.db_binding.is_none()
1104 != (proof.db_connections == 0 && proof.db_operations == 0))
1105 {
1106 return Err(ExecutionError::UnknownExecution);
1107 }
1108 if self.db_bindings[slot].is_some() && self.db_bindings[slot] != proof.db_binding {
1109 return Err(ExecutionError::UnknownExecution);
1110 }
1111 self.db_bindings[slot] = proof.db_binding;
1112 let plan = self.entries.register_compiled_plan(
1113 proof.layout.plan()?,
1114 proof.db_connections,
1115 proof.db_operations,
1116 u64::try_from(proof.layout.output_bytes).map_err(|_| ExecutionError::OutputTooLarge)?,
1117 )?;
1118 let identity = self
1119 .entries
1120 .expose_compiled::<S>(route_token, framing, plan)?;
1121 let commitment = proof.layout.commitment()?;
1122 let managed_objects_peak = compiled_route_managed_objects_peak()?;
1123 let db_connections =
1124 usize::try_from(proof.db_connections).map_err(|_| ExecutionError::OutputTooLarge)?;
1125 let db_operations =
1126 usize::try_from(proof.db_operations).map_err(|_| ExecutionError::OutputTooLarge)?;
1127 self.capacity_routes.push(ServiceCapacityRouteRecord {
1128 identity: identity.contract().opaque(),
1129 commitment,
1130 managed_objects_peak,
1131 db_connections,
1132 db_operations,
1133 source_identity: service_route_source_identity(ServiceRouteSourceFacts {
1134 route_token,
1135 framing,
1136 identity,
1137 layout_source_identity: proof.capacity_source_identity(),
1138 commitment,
1139 managed_objects_peak,
1140 db_connections,
1141 db_operations,
1142 }),
1143 });
1144 Ok(())
1145 }
1146
1147 pub fn bind_internal_call<Caller, Callee>(
1148 &self,
1149 ) -> Result<InternalCallCapability<Caller, Callee>, ExecutionError>
1150 where
1151 Caller: Service,
1152 Callee: Service,
1153 {
1154 self.entries.service_index::<Caller>()?;
1155 self.entries.service_index::<Callee>()?;
1156 Ok(InternalCallCapability {
1157 _types: PhantomData,
1158 })
1159 }
1160
1161 pub fn freeze(
1162 self,
1163 ) -> Result<CompiledExecutionCapability<E, C, F, BODY, OUTPUT>, ExecutionError> {
1164 Ok(CompiledExecutionCapability {
1165 entries: self.entries.freeze()?,
1166 dispatcher: self.dispatcher,
1167 db_bindings: self.db_bindings,
1168 _types: PhantomData,
1169 })
1170 }
1171
1172 pub fn freeze_with_capacity_leaf<A>(
1176 self,
1177 artifact: A,
1178 ) -> Result<CompiledExecutionWithCapacityLeaf<E, C, F, BODY, OUTPUT>, ExecutionError>
1179 where
1180 A: GeneratedServiceCapacityArtifactOwner,
1181 {
1182 let build = artifact.build_identity();
1183 let artifact_identity = artifact.artifact_identity();
1184 let owner_generation = artifact.owner_generation();
1185 if build == [0; 32] || artifact_identity == [0; 32] || owner_generation == 0 {
1186 return Err(ExecutionError::MissingCapacityIdentity);
1187 }
1188 if self.capacity_routes.is_empty() {
1189 return Err(ExecutionError::UnknownExecution);
1190 }
1191 let route_set_identity = service_route_set_identity(&self.capacity_routes);
1192 let route_type_closure_identity =
1193 service_route_type_closure_identity(artifact_identity, &self.capacity_routes);
1194 let leaf_identity = service_leaf_identity(
1195 build,
1196 artifact_identity,
1197 route_set_identity,
1198 owner_generation,
1199 route_type_closure_identity,
1200 &self.capacity_routes,
1201 );
1202 let service_leaf = FrozenServiceCapacitySourceLeaf {
1203 leaf_identity,
1204 common_identities: [build, artifact_identity, route_set_identity],
1205 owner_generation,
1206 route_type_closure_identity,
1207 routes: self.capacity_routes.into_boxed_slice(),
1208 };
1209 let execution = CompiledExecutionCapability {
1210 entries: self.entries.freeze()?,
1211 dispatcher: self.dispatcher,
1212 db_bindings: self.db_bindings,
1213 _types: PhantomData,
1214 };
1215 Ok(CompiledExecutionWithCapacityLeaf {
1216 execution,
1217 service_leaf,
1218 })
1219 }
1220}
1221
1222pub struct CompiledExecutionCapability<E, C, F, const BODY: usize, const OUTPUT: usize>
1224where
1225 E: Fn(usize, C, ManagedBytes, CompiledDbPermit, ManagedResponseBuilder) -> F
1226 + Send
1227 + Sync
1228 + 'static,
1229 C: Send + Unpin + 'static,
1230 F: Future<Output = Result<ManagedResponse, ExecutionError>> + Send + 'static,
1231{
1232 pub(super) entries: ServiceEntryCapability,
1233 dispatcher: E,
1234 db_bindings: Vec<Option<CompiledDbBinding>>,
1235 _types: PhantomData<fn(C) -> F>,
1236}
1237
1238impl<E, C, F, const BODY: usize, const OUTPUT: usize>
1239 CompiledExecutionCapability<E, C, F, BODY, OUTPUT>
1240where
1241 E: Fn(usize, C, ManagedBytes, CompiledDbPermit, ManagedResponseBuilder) -> F
1242 + Send
1243 + Sync
1244 + 'static,
1245 C: Send + Unpin + 'static,
1246 F: Future<Output = Result<ManagedResponse, ExecutionError>> + Send + 'static,
1247{
1248 pub fn lookup(
1249 &self,
1250 route_token: &[u8],
1251 framing: EntryFraming,
1252 declared_length: Option<u64>,
1253 ) -> Result<RegisteredRouteExecutionProof, RegistryError> {
1254 self.entries
1255 .lookup(route_token, framing, declared_length)
1256 .map(RegisteredEntry::execution_proof)
1257 }
1258
1259 fn execute(
1260 &self,
1261 proof: RegisteredRouteExecutionProof,
1262 context: C,
1263 body: ManagedBytes,
1264 db_permit: Option<DbRequestPermit>,
1265 memory: &RequestMemory,
1266 ) -> Result<F, ExecutionError> {
1267 let registered = RegisteredEntry::from_execution_proof(proof);
1268 let slot = self.entries.execution_slot(registered)?;
1269 if body.len() > proof.plan().body_hard_limit() as usize {
1270 return Err(ExecutionError::InputTooLarge);
1271 }
1272 let expected = proof.db_demand();
1273 let actual = db_permit.as_ref().map(DbRequestPermit::demand);
1274 let resources_match = match actual {
1275 None => expected.is_none(),
1276 Some(demand) => {
1277 u64::try_from(demand.connections()) == Ok(expected.connections())
1278 && u64::try_from(demand.operations()) == Ok(expected.operations())
1279 && !expected.is_none()
1280 }
1281 };
1282 if !resources_match {
1283 return Err(ExecutionError::RouteResourcesMismatch);
1284 }
1285 let response_capacity = usize::try_from(proof.response_capacity())
1286 .map_err(|_| ExecutionError::OutputTooLarge)?;
1287 if response_capacity != OUTPUT {
1288 return Err(ExecutionError::UnknownExecution);
1289 }
1290 let response = memory
1291 .try_response_builder(response_capacity)
1292 .map_err(|_| ExecutionError::ResponseReservationFailed)?;
1293 Ok((self.dispatcher)(
1294 slot,
1295 context,
1296 body,
1297 CompiledDbPermit {
1298 inner: db_permit,
1299 route_demand: expected,
1300 binding: self.db_bindings[slot],
1301 },
1302 response,
1303 ))
1304 }
1305}
1306
1307pin_project_lite::pin_project! {
1308 pub struct ClassifiedExecutionFuture<F> {
1314 #[pin]
1315 state: ClassifiedExecutionState<F>,
1316 }
1317}
1318
1319pin_project_lite::pin_project! {
1320 #[project = ClassifiedExecutionStateProj]
1321 enum ClassifiedExecutionState<F> {
1322 Running {
1323 #[pin]
1324 future: F,
1325 error_payload: Option<ManagedResponse>,
1326 },
1327 Ready {
1328 outcome: Option<saddle_runtime::compiled_route::CompiledResponseOutcome>,
1329 },
1330 }
1331}
1332
1333impl<F> ClassifiedExecutionFuture<F> {
1334 fn running(future: F, error_payload: ManagedResponse) -> Self {
1335 Self {
1336 state: ClassifiedExecutionState::Running {
1337 future,
1338 error_payload: Some(error_payload),
1339 },
1340 }
1341 }
1342
1343 fn ready(outcome: saddle_runtime::compiled_route::CompiledResponseOutcome) -> Self {
1344 Self {
1345 state: ClassifiedExecutionState::Ready {
1346 outcome: Some(outcome),
1347 },
1348 }
1349 }
1350}
1351
1352fn classified_outcome(
1353 class: saddle_runtime::compiled_route::ResponseOutcomeClass,
1354 payload: ManagedResponse,
1355) -> saddle_runtime::compiled_route::CompiledResponseOutcome {
1356 use saddle_runtime::compiled_route::{CompiledResponseOutcome, ResponseOutcomeClass};
1357
1358 match class {
1359 ResponseOutcomeClass::Success => CompiledResponseOutcome::success(payload),
1360 ResponseOutcomeClass::InvalidRequest => CompiledResponseOutcome::invalid_request(payload),
1361 ResponseOutcomeClass::BusinessRejected => {
1362 CompiledResponseOutcome::business_rejected(payload)
1363 }
1364 ResponseOutcomeClass::Unavailable => CompiledResponseOutcome::unavailable(payload),
1365 ResponseOutcomeClass::Internal => CompiledResponseOutcome::internal(payload),
1366 }
1367}
1368
1369impl<F> Future for ClassifiedExecutionFuture<F>
1370where
1371 F: Future<Output = Result<ManagedResponse, ExecutionError>>,
1372{
1373 type Output = saddle_runtime::compiled_route::CompiledResponseOutcome;
1374
1375 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
1376 match self.project().state.project() {
1377 ClassifiedExecutionStateProj::Running {
1378 future,
1379 error_payload,
1380 } => match future.poll(context) {
1381 Poll::Pending => Poll::Pending,
1382 Poll::Ready(Ok(payload)) => Poll::Ready(
1383 saddle_runtime::compiled_route::CompiledResponseOutcome::success(payload),
1384 ),
1385 Poll::Ready(Err(error)) => {
1386 let payload = error_payload
1387 .take()
1388 .expect("classified execution future completes only once");
1389 Poll::Ready(classified_outcome(error.response_outcome_class(), payload))
1390 }
1391 },
1392 ClassifiedExecutionStateProj::Ready { outcome } => Poll::Ready(
1393 outcome
1394 .take()
1395 .expect("classified execution future completes only once"),
1396 ),
1397 }
1398 }
1399}
1400
1401#[cfg(all(target_arch = "x86_64", target_os = "linux"))]
1402impl<E, C, F, const BODY: usize, const OUTPUT: usize>
1403 saddle_runtime::compiled_route::CompiledRouteAdapter
1404 for CompiledExecutionCapability<E, C, F, BODY, OUTPUT>
1405where
1406 E: Fn(usize, C, ManagedBytes, CompiledDbPermit, ManagedResponseBuilder) -> F
1407 + Send
1408 + Sync
1409 + 'static,
1410 C: Send + Unpin + 'static,
1411 F: Future<Output = Result<ManagedResponse, ExecutionError>> + Send + 'static,
1412{
1413 type Proof = RegisteredRouteExecutionProof;
1414 type Context = C;
1415 type Error = ExecutionError;
1416 type Future = F;
1417
1418 fn managed_commitment(&self, proof: Self::Proof) -> Result<usize, Self::Error> {
1419 self.entries
1420 .execution_slot(RegisteredEntry::from_execution_proof(proof))?;
1421 usize::try_from(proof.plan().commitment()).map_err(|_| ExecutionError::OutputTooLarge)
1422 }
1423
1424 fn response_capacity(&self, proof: Self::Proof) -> Result<usize, Self::Error> {
1425 self.entries
1426 .execution_slot(RegisteredEntry::from_execution_proof(proof))?;
1427 usize::try_from(proof.response_capacity()).map_err(|_| ExecutionError::OutputTooLarge)
1428 }
1429
1430 fn db_resources<'a>(
1431 &self,
1432 proof: Self::Proof,
1433 domain: Option<&'a saddle_admission::DbPermitDomain>,
1434 ) -> Result<DbRouteResources<'a>, Self::Error> {
1435 self.entries
1436 .execution_slot(RegisteredEntry::from_execution_proof(proof))?;
1437 let demand = proof.db_demand();
1438 if demand.is_none() {
1439 return Ok(DbRouteResources::none());
1440 }
1441 let domain = domain.ok_or(ExecutionError::RouteResourcesMismatch)?;
1442 let connections =
1443 usize::try_from(demand.connections()).map_err(|_| ExecutionError::OutputTooLarge)?;
1444 let operations =
1445 usize::try_from(demand.operations()).map_err(|_| ExecutionError::OutputTooLarge)?;
1446 let demand = DbRouteCreditDemand::new(connections, operations)
1447 .map_err(|_| ExecutionError::RouteResourcesMismatch)?;
1448 Ok(DbRouteResources::required(domain, demand))
1449 }
1450
1451 fn execute(
1452 &self,
1453 proof: Self::Proof,
1454 context: Self::Context,
1455 body: ManagedBytes,
1456 permit: Option<DbRequestPermit>,
1457 memory: &RequestMemory,
1458 ) -> Result<Self::Future, Self::Error> {
1459 CompiledExecutionCapability::execute(self, proof, context, body, permit, memory)
1460 }
1461}
1462
1463#[cfg(all(target_arch = "x86_64", target_os = "linux"))]
1464impl<E, C, F, const BODY: usize, const OUTPUT: usize>
1465 saddle_runtime::compiled_route::ClassifiedCompiledRouteAdapter
1466 for CompiledExecutionCapability<E, C, F, BODY, OUTPUT>
1467where
1468 E: Fn(usize, C, ManagedBytes, CompiledDbPermit, ManagedResponseBuilder) -> F
1469 + Send
1470 + Sync
1471 + 'static,
1472 C: Send + Unpin + 'static,
1473 F: Future<Output = Result<ManagedResponse, ExecutionError>> + Send + 'static,
1474{
1475 type Proof = RegisteredRouteExecutionProof;
1476 type Context = C;
1477 type Error = ExecutionError;
1478 type Future = ClassifiedExecutionFuture<F>;
1479
1480 fn managed_commitment(&self, proof: Self::Proof) -> Result<usize, Self::Error> {
1481 saddle_runtime::compiled_route::CompiledRouteAdapter::managed_commitment(self, proof)
1482 }
1483
1484 fn response_capacity(&self, proof: Self::Proof) -> Result<usize, Self::Error> {
1485 saddle_runtime::compiled_route::CompiledRouteAdapter::response_capacity(self, proof)
1486 }
1487
1488 fn db_resources<'a>(
1489 &self,
1490 proof: Self::Proof,
1491 domain: Option<&'a saddle_admission::DbPermitDomain>,
1492 ) -> Result<DbRouteResources<'a>, Self::Error> {
1493 saddle_runtime::compiled_route::CompiledRouteAdapter::db_resources(self, proof, domain)
1494 }
1495
1496 fn execute(
1497 &self,
1498 proof: Self::Proof,
1499 context: Self::Context,
1500 body: ManagedBytes,
1501 permit: Option<DbRequestPermit>,
1502 memory: &RequestMemory,
1503 ) -> Self::Future {
1504 let error_payload = memory
1505 .try_response(&[])
1506 .expect("zero-byte classified response is inside the current account commitment");
1507 match CompiledExecutionCapability::execute(self, proof, context, body, permit, memory) {
1508 Ok(future) => ClassifiedExecutionFuture::running(future, error_payload),
1509 Err(error) => ClassifiedExecutionFuture::ready(classified_outcome(
1510 error.response_outcome_class(),
1511 error_payload,
1512 )),
1513 }
1514 }
1515}
1516
1517#[cfg(all(target_arch = "x86_64", target_os = "linux"))]
1520impl<E, C, F, const BODY: usize, const OUTPUT: usize>
1521 saddle_runtime::compiled_route::ClassifiedCompiledRouteAdapter
1522 for CompiledExecutionWithCapacityLeaf<E, C, F, BODY, OUTPUT>
1523where
1524 E: Fn(usize, C, ManagedBytes, CompiledDbPermit, ManagedResponseBuilder) -> F
1525 + Send
1526 + Sync
1527 + 'static,
1528 C: Send + Unpin + 'static,
1529 F: Future<Output = Result<ManagedResponse, ExecutionError>> + Send + 'static,
1530{
1531 type Proof = RegisteredRouteExecutionProof;
1532 type Context = C;
1533 type Error = ExecutionError;
1534 type Future = ClassifiedExecutionFuture<F>;
1535
1536 fn managed_commitment(&self, proof: Self::Proof) -> Result<usize, Self::Error> {
1537 saddle_runtime::compiled_route::ClassifiedCompiledRouteAdapter::managed_commitment(
1538 &self.execution,
1539 proof,
1540 )
1541 }
1542
1543 fn response_capacity(&self, proof: Self::Proof) -> Result<usize, Self::Error> {
1544 saddle_runtime::compiled_route::ClassifiedCompiledRouteAdapter::response_capacity(
1545 &self.execution,
1546 proof,
1547 )
1548 }
1549
1550 fn db_resources<'a>(
1551 &self,
1552 proof: Self::Proof,
1553 domain: Option<&'a saddle_admission::DbPermitDomain>,
1554 ) -> Result<DbRouteResources<'a>, Self::Error> {
1555 saddle_runtime::compiled_route::ClassifiedCompiledRouteAdapter::db_resources(
1556 &self.execution,
1557 proof,
1558 domain,
1559 )
1560 }
1561
1562 fn execute(
1563 &self,
1564 proof: Self::Proof,
1565 context: Self::Context,
1566 body: ManagedBytes,
1567 permit: Option<DbRequestPermit>,
1568 memory: &RequestMemory,
1569 ) -> Self::Future {
1570 saddle_runtime::compiled_route::ClassifiedCompiledRouteAdapter::execute(
1571 &self.execution,
1572 proof,
1573 context,
1574 body,
1575 permit,
1576 memory,
1577 )
1578 }
1579}
1580
1581#[cfg(test)]
1582mod tests {
1583 use std::{
1584 future::Future,
1585 pin::Pin,
1586 task::{Context, Poll},
1587 };
1588
1589 use saddle_core::Result as SaddleResult;
1590 use saddle_db::internal::{
1591 DbU64, ManagedQueryParameters, ManagedQueryRow, QueryOptionalOperationProof,
1592 StaticQueryOptionalOperation, StaticWriteOperation, TransactionDecision,
1593 TransactionOperationProof, WriteOperationProof,
1594 };
1595
1596 use super::*;
1597 use crate::{
1598 ServiceDescriptor,
1599 c4_entry::{EntryCapabilityLimits, EntryFraming},
1600 };
1601 use saddle_admission::{
1602 AdmissionError, DbCreditProfile, EntryIoAuditPlan, EntryReadPoll,
1603 OfficialTokioEntryIoAttemptOutcome, OfficialTokioRegistrationProfile,
1604 ProcessAllocationProfile, ProcessLedger, RequestMemory, ResourceConfig, ResponseWritePoll,
1605 };
1606 use saddle_runtime::compiled_route::{CompiledRouteAdapter, ResponseOutcomeClass};
1607
1608 const BODY: usize = 32;
1609 const OUTPUT: usize = 64;
1610 static EXECUTION_TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
1611
1612 fn request_ledger() -> (ProcessLedger, ProcessAllocationProfile) {
1613 let state = ProcessLedger::minimum_process_state_reserve(1).unwrap();
1614 let config = ResourceConfig {
1615 managed_capacity: 512,
1616 entry_reserve: 64,
1617 framework_reserve: 4_096,
1618 task_reserve: 4_096,
1619 process_state_reserve: state,
1620 system_estimate: 1_024,
1621 safety_margin: 512,
1622 process_limit: 512 + 64 + 4_096 + 4_096 + state + 1_024 + 512,
1623 max_active_requests: 1,
1624 };
1625 let ledger = ProcessLedger::new(config).unwrap();
1626 let allocation = ledger
1627 .prepare_process_allocation_profile(usize::MAX)
1628 .unwrap();
1629 (ledger, allocation)
1630 }
1631
1632 fn entry_plan() -> EntryIoAuditPlan {
1633 EntryIoAuditPlan::locked_linux_x86_64_tokio_1_53_1(64, 64, &[]).unwrap()
1634 }
1635
1636 fn official_ledger() -> (
1637 ProcessLedger,
1638 saddle_admission::OfficialTokioDomain,
1639 ProcessAllocationProfile,
1640 ) {
1641 let registration = OfficialTokioRegistrationProfile {
1642 listener: 1,
1643 transport_connections: 1,
1644 runtime_fixed: 1,
1645 };
1646 let state = ProcessLedger::minimum_process_state_reserve(1).unwrap()
1647 + ProcessLedger::official_tokio_state_reserve(registration).unwrap();
1648 let config = ResourceConfig {
1649 managed_capacity: 512,
1650 entry_reserve: 64,
1651 framework_reserve: 4_096,
1652 task_reserve: 4_096,
1653 process_state_reserve: state,
1654 system_estimate: 1_024,
1655 safety_margin: 512,
1656 process_limit: 512 + 64 + 4_096 + 4_096 + state + 1_024 + 512,
1657 max_active_requests: 1,
1658 };
1659 let ledger = ProcessLedger::new(config).unwrap();
1660 let domain = ledger.prepare_official_tokio_domain(registration).unwrap();
1661 let allocation = ledger
1662 .prepare_process_allocation_profile(usize::MAX)
1663 .unwrap();
1664 (ledger, domain, allocation)
1665 }
1666
1667 struct TestConnection;
1668
1669 struct UserRead;
1670
1671 impl EntryReadPoll<TestConnection> for UserRead {
1672 fn poll_read(
1673 &mut self,
1674 _: &mut TestConnection,
1675 memory: &RequestMemory,
1676 _: &mut Context<'_>,
1677 ) -> Poll<Result<ManagedBytes, AdmissionError>> {
1678 Poll::Ready(memory.try_bytes(&42_u64.to_le_bytes()))
1679 }
1680 }
1681
1682 struct UserWrite;
1683
1684 impl ResponseWritePoll<TestConnection> for UserWrite {
1685 fn poll_write(
1686 &mut self,
1687 _: &mut TestConnection,
1688 response: &ManagedResponse,
1689 _: &mut Context<'_>,
1690 ) -> Poll<Result<(), AdmissionError>> {
1691 assert_eq!(
1692 u64::from_le_bytes(response.as_slice()[..8].try_into().unwrap()),
1693 45
1694 );
1695 assert_eq!(response.as_slice()[8], 1);
1696 Poll::Ready(Ok(()))
1697 }
1698 }
1699
1700 struct UserLookup;
1701
1702 impl Service for UserLookup {
1703 type Request = UserRequest;
1704 type Response = UserResponse;
1705 }
1706
1707 struct OrderCreate;
1708
1709 impl Service for OrderCreate {
1710 type Request = OrderRequest;
1711 type Response = OrderResponse;
1712 }
1713
1714 #[derive(Clone, Copy)]
1715 struct RequestIdentity(u64);
1716
1717 type UserRequest = ManagedU64;
1718 type UserResponse = ManagedPair<ManagedU64, ManagedBool>;
1719 type UserCodec = FixedManagedCodec<UserRequest, UserResponse>;
1720 type OrderRequest = ManagedPair<ManagedU64, ManagedU64>;
1721 type OrderResponse = ManagedPair<ManagedU64, ManagedU64>;
1722 type OrderCodec = FixedManagedCodec<OrderRequest, OrderResponse>;
1723
1724 struct SelectUser;
1725
1726 impl StaticQueryOptionalOperation for SelectUser {
1727 type Parameters = ManagedQueryParameters<DbU64>;
1728 type Row = ManagedQueryRow<DbU64>;
1729
1730 const OPERATION: &'static str = "users.lookup";
1731 const SQL: &'static str = "SELECT id FROM users WHERE id = ?";
1732 }
1733
1734 struct SelectOrder;
1735
1736 impl StaticQueryOptionalOperation for SelectOrder {
1737 type Parameters = ManagedQueryParameters<DbU64>;
1738 type Row = ManagedQueryRow<DbU64>;
1739
1740 const OPERATION: &'static str = "orders.lookup";
1741 const SQL: &'static str = "SELECT id FROM orders WHERE id = ?";
1742 }
1743
1744 struct UpdateUser;
1745
1746 impl StaticWriteOperation for UpdateUser {
1747 type Parameters = ManagedQueryParameters<DbU64>;
1748
1749 const OPERATION: &'static str = "users.update";
1750 const SQL: &'static str = "UPDATE users SET active = 1 WHERE id = ?";
1751 }
1752
1753 struct ReadyOnce<T> {
1754 value: Option<T>,
1755 yielded: bool,
1756 }
1757
1758 impl<T> ReadyOnce<T> {
1759 fn new(value: T) -> Self {
1760 Self {
1761 value: Some(value),
1762 yielded: false,
1763 }
1764 }
1765 }
1766
1767 impl<T: Unpin> Future for ReadyOnce<T> {
1768 type Output = T;
1769
1770 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<T> {
1771 if !self.yielded {
1772 self.yielded = true;
1773 context.waker().wake_by_ref();
1774 return Poll::Pending;
1775 }
1776 Poll::Ready(self.value.take().expect("future completes only once"))
1777 }
1778 }
1779
1780 fn user_handler(
1781 identity: RequestIdentity,
1782 request: UserRequest,
1783 ) -> ReadyOnce<Result<UserResponse, ExecutionError>> {
1784 ReadyOnce::new(Ok(ManagedPair(
1785 ManagedU64(request.0 ^ identity.0),
1786 ManagedBool(true),
1787 )))
1788 }
1789
1790 fn order_handler(
1791 identity: RequestIdentity,
1792 request: OrderRequest,
1793 ) -> ReadyOnce<Result<OrderResponse, ExecutionError>> {
1794 ReadyOnce::new(Ok(ManagedPair(
1795 ManagedU64(request.0.0 ^ identity.0),
1796 request.1,
1797 )))
1798 }
1799
1800 fn generated_layout(slot: usize) -> Option<CompiledRouteExecutionProof> {
1801 match slot {
1802 0 => ExecutionLayoutProof::bind::<
1803 UserLookup,
1804 BODY,
1805 OUTPUT,
1806 UserRequest,
1807 UserResponse,
1808 UserCodec,
1809 ReadyOnce<Result<UserResponse, ExecutionError>>,
1810 >()
1811 .ok()
1812 .map(|layout| {
1813 layout.with_query_optional(
1814 QueryOptionalOperationProof::<SelectUser>::bind()
1815 .expect("generated DB operation is valid"),
1816 )
1817 }),
1818 1 => ExecutionLayoutProof::bind::<
1819 OrderCreate,
1820 BODY,
1821 OUTPUT,
1822 OrderRequest,
1823 OrderResponse,
1824 OrderCodec,
1825 ReadyOnce<Result<OrderResponse, ExecutionError>>,
1826 >()
1827 .ok()
1828 .map(ExecutionLayoutProof::without_db),
1829 _ => None,
1830 }
1831 }
1832
1833 #[allow(clippy::manual_async_fn)]
1836 fn generated_dispatch(
1837 slot: usize,
1838 identity: RequestIdentity,
1839 body: ManagedBytes,
1840 db: CompiledDbPermit,
1841 mut output: ManagedResponseBuilder,
1842 ) -> impl Future<Output = Result<ManagedResponse, ExecutionError>> + Send + 'static + use<>
1843 {
1844 enum Decoded {
1845 User(UserRequest),
1846 Order(OrderRequest),
1847 }
1848 let decoded = match slot {
1849 0 => body
1850 .as_slice()
1851 .try_into()
1852 .map(u64::from_le_bytes)
1853 .map(ManagedU64)
1854 .map(Decoded::User)
1855 .map_err(|_| ExecutionError::InvalidInput),
1856 1 => body
1857 .as_slice()
1858 .try_into()
1859 .map(|bytes: [u8; 16]| {
1860 Decoded::Order(ManagedPair(
1861 ManagedU64(u64::from_le_bytes(bytes[..8].try_into().unwrap())),
1862 ManagedU64(u64::from_le_bytes(bytes[8..].try_into().unwrap())),
1863 ))
1864 })
1865 .map_err(|_| ExecutionError::InvalidInput),
1866 _ => Err(ExecutionError::UnknownExecution),
1867 };
1868 drop(body);
1869 async move {
1870 match decoded? {
1871 Decoded::User(request) => {
1872 assert_eq!(db.demand(), Some((1, 1)));
1873 let response = user_handler(identity, request).await?;
1874 db.complete_after_test_connection_return();
1878 output
1879 .try_extend_from_slice(&response.0.0.to_le_bytes())
1880 .map_err(|_| ExecutionError::OutputTooLarge)?;
1881 output
1882 .try_extend_from_slice(&[u8::from(response.1.0)])
1883 .map_err(|_| ExecutionError::OutputTooLarge)?;
1884 }
1885 Decoded::Order(request) => {
1886 assert!(db.is_none());
1887 let query = QueryOptionalOperationProof::<SelectUser>::bind()
1888 .expect("generated DB operation is valid");
1889 assert!(matches!(
1890 db.handoff_query_optional(
1891 query.invocation(ManagedQueryParameters(DbU64(1)))
1892 ),
1893 Err(ExecutionError::RouteResourcesMismatch)
1894 ));
1895 let response = order_handler(identity, request).await?;
1896 output
1897 .try_extend_from_slice(&response.0.0.to_le_bytes())
1898 .map_err(|_| ExecutionError::OutputTooLarge)?;
1899 output
1900 .try_extend_from_slice(&response.1.0.to_le_bytes())
1901 .map_err(|_| ExecutionError::OutputTooLarge)?;
1902 }
1903 }
1904 output
1905 .finish()
1906 .map_err(|_| ExecutionError::ResponseReservationFailed)
1907 }
1908 }
1909
1910 fn registry() -> CompiledServiceRegistry {
1911 let mut builder = CompiledServiceRegistryBuilder::new(2).unwrap();
1912 builder
1913 .register::<UserLookup>(ServiceDescriptor::new("users", "user", "lookup"))
1914 .unwrap();
1915 builder
1916 .register::<OrderCreate>(ServiceDescriptor::new("orders", "order", "create"))
1917 .unwrap();
1918 builder.freeze().unwrap()
1919 }
1920
1921 macro_rules! capability {
1922 () => {{
1923 let mut builder = CompiledExecutionBuilder::<_, _, _, _, BODY, OUTPUT>::new(
1924 registry(),
1925 generated_dispatch,
1926 generated_layout,
1927 EntryCapabilityLimits::new(2, 32, 64, 2, 2),
1928 )
1929 .unwrap();
1930 builder
1931 .expose::<UserLookup>(
1932 b"/users/lookup",
1933 EntryFraming::post_managed_content_length(),
1934 )
1935 .unwrap();
1936 builder
1937 .expose::<OrderCreate>(
1938 b"/orders/create",
1939 EntryFraming::post_managed_content_length(),
1940 )
1941 .unwrap();
1942 builder.freeze().unwrap()
1943 }};
1944 }
1945
1946 struct TestCapacityArtifact;
1947
1948 impl GeneratedServiceCapacityArtifactOwner for TestCapacityArtifact {
1949 fn build_identity(&self) -> [u8; 32] {
1950 [0xb1; 32]
1951 }
1952
1953 fn artifact_identity(&self) -> [u8; 32] {
1954 [0xa1; 32]
1955 }
1956
1957 fn owner_generation(&self) -> u64 {
1958 7
1959 }
1960 }
1961
1962 #[test]
1963 fn capacity_leaf_is_frozen_with_the_execution_registry() {
1964 use saddle_admission::ServiceCapacitySourceLeaf;
1965
1966 let mut builder = CompiledExecutionBuilder::<_, _, _, _, BODY, OUTPUT>::new(
1967 registry(),
1968 generated_dispatch,
1969 generated_layout,
1970 EntryCapabilityLimits::new(2, 32, 64, 2, 2),
1971 )
1972 .unwrap();
1973 builder
1974 .expose::<UserLookup>(
1975 b"/users/lookup",
1976 EntryFraming::post_managed_content_length(),
1977 )
1978 .unwrap();
1979 builder
1980 .expose::<OrderCreate>(
1981 b"/orders/create",
1982 EntryFraming::post_managed_content_length(),
1983 )
1984 .unwrap();
1985 let execution = builder
1986 .freeze_with_capacity_leaf(TestCapacityArtifact)
1987 .unwrap();
1988 let leaf = execution.capacity_source();
1989
1990 assert_eq!(leaf.owner_generation(), 7);
1991 assert_eq!(leaf.route_count(), 2);
1992 assert_eq!(leaf.managed_objects_peak(0), Some(2));
1993 assert_eq!(leaf.managed_objects_peak(1), Some(2));
1994 assert_ne!(leaf.leaf_identity(), [0; 32]);
1995 assert_ne!(leaf.route_type_closure_identity(), [0; 32]);
1996 assert_eq!(leaf.common_identities()[..2], [[0xb1; 32], [0xa1; 32]]);
1997 assert_ne!(leaf.common_identities()[2], [0; 32]);
1998
1999 for index in 0..leaf.route_count() {
2000 let route = leaf.route_identity(index).unwrap();
2001 assert_ne!(route, 0);
2002 assert_eq!(route >> 32, leaf.route_identity(0).unwrap() >> 32);
2003 assert!(leaf.managed_commitment_bytes(index).unwrap() > 0);
2004 }
2005 assert_eq!(leaf.db_connections(0), Some(1));
2006 assert_eq!(leaf.db_operations(0), Some(1));
2007 assert_eq!(leaf.db_connections(1), Some(0));
2008 assert_eq!(leaf.db_operations(1), Some(0));
2009
2010 let query = execution
2011 .lookup(
2012 b"/users/lookup",
2013 EntryFraming::post_managed_content_length(),
2014 Some(8),
2015 )
2016 .unwrap();
2017 let no_db = execution
2018 .lookup(
2019 b"/orders/create",
2020 EntryFraming::post_managed_content_length(),
2021 Some(16),
2022 )
2023 .unwrap();
2024 assert_eq!(
2025 query.identity().contract().opaque(),
2026 leaf.route_identity(0).unwrap()
2027 );
2028 assert_eq!(
2029 no_db.identity().contract().opaque(),
2030 leaf.route_identity(1).unwrap()
2031 );
2032 assert_eq!(
2033 query.plan().commitment() as usize,
2034 leaf.managed_commitment_bytes(0).unwrap()
2035 );
2036 assert_eq!(
2037 no_db.plan().commitment() as usize,
2038 leaf.managed_commitment_bytes(1).unwrap()
2039 );
2040
2041 let first = execution
2042 .production_fact_input()
2043 .unwrap()
2044 .into_canonical_json()
2045 .unwrap();
2046 let second = execution
2047 .production_fact_input()
2048 .unwrap()
2049 .into_canonical_json()
2050 .unwrap();
2051 assert_eq!(first, second);
2052 assert!(!first.contains(&b'\n'));
2053 let document: serde_json::Value = serde_json::from_slice(&first).unwrap();
2054 assert_eq!(document["authority"], false);
2055 assert_eq!(document["capacity"]["manifest_leaf"], "service");
2056 assert_eq!(document["capacity"]["commitment"], "service_capacity");
2057 assert_eq!(document["capacity"]["routes"][0]["route"], "/orders/create");
2058 assert_eq!(document["capacity"]["routes"][0]["db_connections"], 0);
2059 assert_eq!(document["capacity"]["routes"][1]["route"], "/users/lookup");
2060 assert_eq!(document["capacity"]["routes"][1]["db_connections"], 1);
2061 assert!(document.get("root_identity").is_none());
2062 assert!(document.get("owner_generation").is_none());
2063 }
2064
2065 #[tokio::test(flavor = "current_thread")]
2066 async fn factory_reserves_response_and_real_async_path_finishes_after_await() {
2067 let _serial = EXECUTION_TEST_LOCK.lock().await;
2068 let capability = capability!();
2069 let order = capability
2070 .lookup(
2071 b"/orders/create",
2072 EntryFraming::post_managed_content_length(),
2073 Some(16),
2074 )
2075 .unwrap();
2076 fn assert_runtime_adapter<A: CompiledRouteAdapter>(_: &A) {}
2077 assert_runtime_adapter(&capability);
2078 assert_eq!(
2079 capability.managed_commitment(order).unwrap(),
2080 usize::try_from(order.plan().commitment()).unwrap()
2081 );
2082 assert_eq!(capability.response_capacity(order).unwrap(), OUTPUT);
2083 assert!(capability.db_resources(order, None).is_ok());
2084 assert!(order.db_demand().is_none());
2085 assert_eq!(order.response_capacity(), OUTPUT as u64);
2086 let (ledger, allocation) = request_ledger();
2087 let envelope = ledger
2088 .try_envelope(256, 2_048, |memory| {
2089 let mut bytes = [0_u8; 16];
2090 bytes[..8].copy_from_slice(&42_u64.to_le_bytes());
2091 bytes[8..].copy_from_slice(&1999_u64.to_le_bytes());
2092 let body = memory.try_bytes(&bytes).unwrap();
2093 let execution = CompiledRouteAdapter::execute(
2094 &capability,
2095 order,
2096 RequestIdentity(9),
2097 body,
2098 None,
2099 memory,
2100 )
2101 .unwrap();
2102 async move {
2103 let response = execution.await.unwrap();
2104 assert_eq!(
2105 u64::from_le_bytes(response.as_slice()[..8].try_into().unwrap()),
2106 35
2107 );
2108 assert_eq!(
2109 u64::from_le_bytes(response.as_slice()[8..].try_into().unwrap()),
2110 1999
2111 );
2112 }
2113 })
2114 .unwrap();
2115 let report = envelope.await.unwrap();
2116 assert_eq!(report.escape_allocations, 0);
2117 assert_eq!(report.managed_allocations, 2);
2118 allocation.finish().unwrap();
2119 ledger.try_shutdown().unwrap();
2120 }
2121
2122 #[tokio::test(flavor = "current_thread")]
2123 async fn sealed_db_permit_and_response_capacity_cross_the_real_async_path() {
2124 let _serial = EXECUTION_TEST_LOCK.lock().await;
2125 let capability = capability!();
2126 let user = capability
2127 .lookup(
2128 b"/users/lookup",
2129 EntryFraming::post_managed_content_length(),
2130 Some(8),
2131 )
2132 .unwrap();
2133 let proof = user;
2134 assert_eq!(proof.db_demand().connections(), 1);
2135 assert_eq!(proof.db_demand().operations(), 1);
2136 assert_eq!(proof.response_capacity(), OUTPUT as u64);
2137
2138 let (ledger, domain, allocation) = official_ledger();
2139 let db = ledger
2140 .prepare_db_domain(DbCreditProfile {
2141 connections: 1,
2142 operations: 1,
2143 })
2144 .unwrap();
2145 let resources = capability.db_resources(proof, Some(&db)).unwrap();
2146 let envelope = match ledger.attempt_official_tokio_entry_io(
2147 &domain,
2148 resources,
2149 256,
2150 4_096,
2151 entry_plan(),
2152 entry_plan(),
2153 |_| (TestConnection, UserRead, UserWrite),
2154 |body, permit, memory| {
2155 let execution = CompiledRouteAdapter::execute(
2156 &capability,
2157 user,
2158 RequestIdentity(7),
2159 body,
2160 permit,
2161 memory,
2162 )
2163 .unwrap();
2164 fn assert_send_static<T: Send + 'static>(_: &T) {}
2165 assert_send_static(&execution);
2166 async move { execution.await.unwrap() }
2167 },
2168 ) {
2169 OfficialTokioEntryIoAttemptOutcome::Ready(envelope) => envelope,
2170 _ => panic!("sealed route resources must admit"),
2171 };
2172 let (envelope, task_slot) = envelope.into_runtime_parts();
2173 let report = envelope.await.unwrap();
2174 drop(task_slot);
2175 assert_eq!(report.escape_allocations, 0);
2176 assert_eq!(db.snapshot().unwrap().connections_in_use, 0);
2177 drop(db);
2178 drop(domain);
2179 allocation.finish().unwrap();
2180 ledger.try_shutdown().unwrap();
2181 }
2182
2183 #[tokio::test(flavor = "current_thread")]
2184 async fn missing_db_permit_fails_before_response_reservation_or_dispatch() {
2185 let _serial = EXECUTION_TEST_LOCK.lock().await;
2186 let capability = capability!();
2187 let user = capability
2188 .lookup(
2189 b"/users/lookup",
2190 EntryFraming::post_managed_content_length(),
2191 Some(8),
2192 )
2193 .unwrap();
2194 let (ledger, allocation) = request_ledger();
2195 let envelope = ledger
2196 .try_envelope(256, 2_048, |memory| {
2197 let body = memory.try_bytes(&42_u64.to_le_bytes()).unwrap();
2198 assert!(matches!(
2199 capability.execute(user, RequestIdentity(7), body, None, memory),
2200 Err(ExecutionError::RouteResourcesMismatch)
2201 ));
2202 std::future::ready(())
2203 })
2204 .unwrap();
2205 let report = envelope.await.unwrap();
2206 assert_eq!(report.managed_allocations, 1);
2207 allocation.finish().unwrap();
2208 ledger.try_shutdown().unwrap();
2209 }
2210
2211 #[tokio::test(flavor = "current_thread")]
2212 async fn registered_entry_is_the_only_execution_credential() {
2213 let _serial = EXECUTION_TEST_LOCK.lock().await;
2214 let first = capability!();
2215 let second = capability!();
2216 let foreign = first
2217 .lookup(
2218 b"/users/lookup",
2219 EntryFraming::post_managed_content_length(),
2220 Some(8),
2221 )
2222 .unwrap();
2223 assert!(matches!(
2224 CompiledRouteAdapter::managed_commitment(&second, foreign),
2225 Err(ExecutionError::Registry(RegistryError::UnknownFactory))
2226 ));
2227 assert!(matches!(
2228 second.db_resources(foreign, None),
2229 Err(ExecutionError::Registry(RegistryError::UnknownFactory))
2230 ));
2231 let (ledger, allocation) = request_ledger();
2232 let envelope = ledger
2233 .try_envelope(256, 2_048, |memory| {
2234 let body = memory.try_bytes(&1_u64.to_le_bytes()).unwrap();
2235 let result = second.execute(foreign, RequestIdentity(1), body, None, memory);
2236 assert!(matches!(
2237 result,
2238 Err(ExecutionError::Registry(RegistryError::UnknownFactory))
2239 ));
2240 std::future::ready(())
2241 })
2242 .unwrap();
2243 envelope.await.unwrap();
2244 allocation.finish().unwrap();
2245 ledger.try_shutdown().unwrap();
2246 }
2247
2248 #[test]
2249 fn proof_is_derived_from_concrete_layouts_and_fixed_payloads_do_not_allocate() {
2250 type Handler = ReadyOnce<Result<OrderResponse, ExecutionError>>;
2251 let proof = ExecutionLayoutProof::bind::<
2252 OrderCreate,
2253 BODY,
2254 OUTPUT,
2255 OrderRequest,
2256 OrderResponse,
2257 OrderCodec,
2258 Handler,
2259 >()
2260 .unwrap();
2261 assert_eq!(proof.body_bytes(), BODY);
2262 assert_eq!(proof.future_bytes(), mem::size_of::<Handler>());
2263 assert!(proof.commitment().unwrap() >= BODY + OUTPUT);
2264 assert_eq!(FixedBytes::<BODY>::empty().capacity(), BODY);
2265 }
2266
2267 #[test]
2268 fn marker_binding_and_typed_internal_edges_are_checked_at_assembly() {
2269 struct Missing;
2270 impl Service for Missing {
2271 type Request = ManagedU64;
2272 type Response = ManagedU64;
2273 }
2274
2275 let mut builder = CompiledExecutionBuilder::<_, _, _, _, BODY, OUTPUT>::new(
2276 registry(),
2277 generated_dispatch,
2278 generated_layout,
2279 EntryCapabilityLimits::new(2, 32, 64, 2, 2),
2280 )
2281 .unwrap();
2282 let edge = builder
2283 .bind_internal_call::<UserLookup, OrderCreate>()
2284 .unwrap();
2285 let _: InternalCallCapability<UserLookup, OrderCreate> = edge;
2286 assert!(matches!(
2287 builder.bind_internal_call::<UserLookup, Missing>(),
2288 Err(ExecutionError::Registry(RegistryError::UnregisteredService))
2289 ));
2290
2291 let order_proof = generated_layout(1).unwrap();
2292 let mut mismatched = CompiledExecutionBuilder::<_, _, _, _, BODY, OUTPUT>::new(
2293 registry(),
2294 generated_dispatch,
2295 move |_| Some(order_proof),
2296 EntryCapabilityLimits::new(2, 32, 64, 2, 2),
2297 )
2298 .unwrap();
2299 assert_eq!(
2300 mismatched
2301 .expose::<UserLookup>(
2302 b"/users/lookup",
2303 EntryFraming::post_managed_content_length(),
2304 )
2305 .unwrap_err(),
2306 ExecutionError::UnknownExecution
2307 );
2308
2309 builder
2310 .expose::<UserLookup>(
2311 b"/users/lookup",
2312 EntryFraming::post_managed_content_length(),
2313 )
2314 .unwrap();
2315 }
2316
2317 #[test]
2318 fn assembly_rejects_foreign_query_proof_for_one_route() {
2319 let conflicting_layout = |slot| {
2320 generated_layout(slot).map(|proof| {
2321 if slot == 0 {
2322 proof.with_query_optional(
2323 QueryOptionalOperationProof::<SelectOrder>::bind().unwrap(),
2324 )
2325 } else {
2326 proof
2327 }
2328 })
2329 };
2330 let mut builder = CompiledExecutionBuilder::<_, _, _, _, BODY, OUTPUT>::new(
2331 registry(),
2332 generated_dispatch,
2333 conflicting_layout,
2334 EntryCapabilityLimits::new(2, 32, 64, 2, 2),
2335 )
2336 .unwrap();
2337 assert_eq!(
2338 builder
2339 .expose::<UserLookup>(
2340 b"/users/lookup",
2341 EntryFraming::post_managed_content_length(),
2342 )
2343 .unwrap_err(),
2344 ExecutionError::UnknownExecution
2345 );
2346 }
2347
2348 #[test]
2349 fn assembly_freezes_standalone_write_and_single_step_transaction_kinds() {
2350 let write_layout = |slot| {
2351 if slot != 0 {
2352 return generated_layout(slot);
2353 }
2354 ExecutionLayoutProof::bind::<
2355 UserLookup,
2356 BODY,
2357 OUTPUT,
2358 UserRequest,
2359 UserResponse,
2360 UserCodec,
2361 ReadyOnce<Result<UserResponse, ExecutionError>>,
2362 >()
2363 .ok()
2364 .map(|layout| layout.with_write(WriteOperationProof::<UpdateUser>::bind().unwrap()))
2365 };
2366 let mut write = CompiledExecutionBuilder::<_, _, _, _, BODY, OUTPUT>::new(
2367 registry(),
2368 generated_dispatch,
2369 write_layout,
2370 EntryCapabilityLimits::new(2, 32, 64, 2, 2),
2371 )
2372 .unwrap();
2373 write
2374 .expose::<UserLookup>(
2375 b"/users/update",
2376 EntryFraming::post_managed_content_length(),
2377 )
2378 .unwrap();
2379 let _write_capability = write.freeze().unwrap();
2380
2381 let transaction_layout = |slot| {
2382 if slot != 0 {
2383 return generated_layout(slot);
2384 }
2385 ExecutionLayoutProof::bind::<
2386 UserLookup,
2387 BODY,
2388 OUTPUT,
2389 UserRequest,
2390 UserResponse,
2391 UserCodec,
2392 ReadyOnce<Result<UserResponse, ExecutionError>>,
2393 >()
2394 .ok()
2395 .map(|layout| {
2396 layout.with_transaction(TransactionOperationProof::<UpdateUser>::bind().unwrap())
2397 })
2398 };
2399 let mut transaction = CompiledExecutionBuilder::<_, _, _, _, BODY, OUTPUT>::new(
2400 registry(),
2401 generated_dispatch,
2402 transaction_layout,
2403 EntryCapabilityLimits::new(2, 32, 64, 2, 2),
2404 )
2405 .unwrap();
2406 transaction
2407 .expose::<UserLookup>(
2408 b"/users/transaction",
2409 EntryFraming::post_managed_content_length(),
2410 )
2411 .unwrap();
2412 let _transaction_capability = transaction.freeze().unwrap();
2413 }
2414
2415 #[test]
2416 fn assembly_rejects_write_transaction_value_recombination() {
2417 let recombined = |slot| {
2418 if slot != 0 {
2419 return generated_layout(slot);
2420 }
2421 ExecutionLayoutProof::bind::<
2422 UserLookup,
2423 BODY,
2424 OUTPUT,
2425 UserRequest,
2426 UserResponse,
2427 UserCodec,
2428 ReadyOnce<Result<UserResponse, ExecutionError>>,
2429 >()
2430 .ok()
2431 .map(|layout| {
2432 layout
2433 .with_write(WriteOperationProof::<UpdateUser>::bind().unwrap())
2434 .with_transaction(TransactionOperationProof::<UpdateUser>::bind().unwrap())
2435 })
2436 };
2437 let mut builder = CompiledExecutionBuilder::<_, _, _, _, BODY, OUTPUT>::new(
2438 registry(),
2439 generated_dispatch,
2440 recombined,
2441 EntryCapabilityLimits::new(2, 32, 64, 2, 2),
2442 )
2443 .unwrap();
2444 assert_eq!(
2445 builder
2446 .expose::<UserLookup>(
2447 b"/users/recombined",
2448 EntryFraming::post_managed_content_length(),
2449 )
2450 .unwrap_err(),
2451 ExecutionError::UnknownExecution
2452 );
2453
2454 let transaction = TransactionOperationProof::<UpdateUser>::bind()
2455 .unwrap()
2456 .invocation(
2457 ManagedQueryParameters(DbU64(1)),
2458 TransactionDecision::Commit,
2459 );
2460 assert_eq!(
2461 transaction.layout().parameter_bytes(),
2462 mem::size_of::<ManagedQueryParameters<DbU64>>()
2463 );
2464 }
2465
2466 #[test]
2467 fn compiled_registry_is_bounded_and_rejects_duplicate_markers_and_identities() {
2468 assert!(matches!(
2469 CompiledServiceRegistryBuilder::new(0),
2470 Err(CompiledRegistryError::InvalidCapacity)
2471 ));
2472 assert!(matches!(
2473 CompiledServiceRegistryBuilder::new(1).unwrap().freeze(),
2474 Err(CompiledRegistryError::EmptyRegistry)
2475 ));
2476
2477 let mut duplicate_contract = CompiledServiceRegistryBuilder::new(2).unwrap();
2478 duplicate_contract
2479 .register::<UserLookup>(ServiceDescriptor::new("users", "user", "lookup"))
2480 .unwrap();
2481 assert_eq!(
2482 duplicate_contract
2483 .register::<UserLookup>(ServiceDescriptor::new("users", "user", "other"))
2484 .unwrap_err(),
2485 CompiledRegistryError::DuplicateContract
2486 );
2487
2488 let mut duplicate_identity = CompiledServiceRegistryBuilder::new(2).unwrap();
2489 duplicate_identity
2490 .register::<UserLookup>(ServiceDescriptor::new("users", "user", "lookup"))
2491 .unwrap();
2492 assert_eq!(
2493 duplicate_identity
2494 .register::<OrderCreate>(ServiceDescriptor::new("users", "user", "lookup"))
2495 .unwrap_err(),
2496 CompiledRegistryError::DuplicateIdentity
2497 );
2498
2499 let mut bounded = CompiledServiceRegistryBuilder::new(1).unwrap();
2500 bounded
2501 .register::<UserLookup>(ServiceDescriptor::new("users", "user", "lookup"))
2502 .unwrap();
2503 assert_eq!(
2504 bounded
2505 .register::<OrderCreate>(ServiceDescriptor::new("orders", "order", "create"))
2506 .unwrap_err(),
2507 CompiledRegistryError::CapacityExceeded
2508 );
2509 }
2510
2511 #[test]
2512 fn execution_errors_exhaustively_map_to_the_closed_response_classes() {
2513 for error in [ExecutionError::InputTooLarge, ExecutionError::InvalidInput] {
2514 assert_eq!(
2515 error.response_outcome_class(),
2516 ResponseOutcomeClass::InvalidRequest
2517 );
2518 }
2519 assert_eq!(
2520 ExecutionError::Business.response_outcome_class(),
2521 ResponseOutcomeClass::BusinessRejected
2522 );
2523 assert_eq!(
2524 ExecutionError::RouteResourcesMismatch.response_outcome_class(),
2525 ResponseOutcomeClass::Unavailable
2526 );
2527 assert_eq!(
2528 ExecutionError::DependencyUnavailable.response_outcome_class(),
2529 ResponseOutcomeClass::Unavailable
2530 );
2531 assert_eq!(
2532 ExecutionError::DependencyExecutionFailed.response_outcome_class(),
2533 ResponseOutcomeClass::Internal
2534 );
2535 for error in [
2536 ExecutionError::Registry(RegistryError::UnknownFactory),
2537 ExecutionError::CompiledRegistry(CompiledRegistryError::EmptyRegistry),
2538 ExecutionError::UnknownExecution,
2539 ExecutionError::OutputTooLarge,
2540 ExecutionError::ResponseReservationFailed,
2541 ] {
2542 assert_eq!(
2543 error.response_outcome_class(),
2544 ResponseOutcomeClass::Internal
2545 );
2546 }
2547 }
2548
2549 #[test]
2550 fn frozen_capability_is_the_classified_production_adapter() {
2551 fn assert_classified<A>(_: &A)
2552 where
2553 A: saddle_runtime::compiled_route::ClassifiedCompiledRouteAdapter<
2554 Proof = RegisteredRouteExecutionProof,
2555 Context = RequestIdentity,
2556 Error = ExecutionError,
2557 >,
2558 {
2559 }
2560
2561 let capability = capability!();
2562 assert_classified(&capability);
2563 }
2564
2565 #[allow(dead_code)]
2566 fn _assert_context_is_owned_and_send(value: RequestIdentity) -> SaddleResult<()> {
2567 fn assert_send_static<T: Send + 'static>(_: T) {}
2568 assert_send_static(value);
2569 Ok(())
2570 }
2571}