1use std::io::Write;
7
8use futures::future::BoxFuture;
9use serde::{Deserialize, Serialize, de::DeserializeOwned};
10use sha2::{Digest, Sha256};
11use wasmtime::Caller;
12
13use super::StoreData;
14use super::durable::EffectId;
15use super::fuel;
16use super::host::{self, fatal_host_error};
17
18pub const MAX_OUTCOME_BYTES: usize = 64 * 1024 * 1024;
20pub const CODEC_VERSION: u32 = 4;
21
22#[derive(Debug, thiserror::Error)]
23#[error("durable execution: {0}")]
24pub struct HistoryError(pub String);
25
26pub struct EffectCall {
28 pub id: EffectId,
29 pub sequence: u64,
30 pub recorded: Option<RecordedOutcome>,
31}
32
33pub type RecordedOutcome = Result<OutcomeBytes, NativeError>;
34
35#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(transparent)]
38pub struct OutcomeBytes(#[serde(with = "binary")] pub Vec<u8>);
39
40#[async_trait::async_trait]
43pub trait EffectHistory: Send + Sync {
44 async fn begin(
45 &self,
46 operation: &str,
47 fingerprint: [u8; 32],
48 ) -> Result<EffectCall, HistoryError>;
49 async fn complete(
50 &self,
51 call: &EffectCall,
52 outcome: RecordedOutcome,
53 ) -> Result<(), HistoryError>;
54 async fn ensure_history_consumed(&self) -> Result<(), HistoryError>;
56}
57
58pub async fn execute<'store, A, T, F>(
64 caller: &mut Caller<'store, StoreData>,
65 operation: &'static str,
66 arguments: A,
67 action: F,
68) -> wasmtime::Result<T>
69where
70 A: Serialize + Send + 'static,
71 T: Serialize + DeserializeOwned + Send + 'static,
72 F: for<'call> FnOnce(
73 &'call mut Caller<'store, StoreData>,
74 EffectId,
75 A,
76 ) -> BoxFuture<'call, wasmtime::Result<T>>
77 + Send,
78{
79 let Some(history) = caller.data().effects.clone() else {
80 let result = action(caller, EffectId::stub()?, arguments).await;
81 return result.map_err(|error| host::throw_host_error(caller, error));
82 };
83 let fingerprint = fingerprint(caller, &arguments)?;
84 let call = history
85 .begin(operation, fingerprint)
86 .await
87 .map_err(fatal_host_error)?;
88 if let Some(recorded) = &call.recorded {
89 return match recorded {
90 Ok(bytes) => {
91 fuel::charge(&mut *caller, fuel::PARSE, bytes.0.len() as u64)?;
92 decode(&bytes.0)
93 }
94 Err(error) => Err(host::throw_host_error(caller, error.clone().into_error())),
95 };
96 }
97
98 let result = action(caller, call.id.clone(), arguments).await;
99 if let Err(error) = &result
100 && (host::ends_the_run(error) || error.is::<wasmtime::ThrownException>())
101 {
102 return result;
103 }
104 let recorded_result = match &result {
105 Ok(value) => {
106 let encoded = encode(value)?;
107 validate_outcome(&encoded)?;
109 fuel::settle(&mut *caller, fuel::COPY, encoded.len() as u64)?;
110 Ok(OutcomeBytes(encoded))
111 }
112 Err(error) => Err(NativeError::capture(error)),
113 };
114 history
115 .complete(&call, recorded_result)
116 .await
117 .map_err(fatal_host_error)?;
118 result.map_err(|error| host::throw_host_error(caller, error))
119}
120
121pub(crate) mod binary {
122 use serde::{Deserializer, Serializer};
123 pub fn serialize<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
124 serializer.serialize_bytes(bytes)
125 }
126 pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
127 struct Bytes;
128 impl<'de> serde::de::Visitor<'de> for Bytes {
129 type Value = Vec<u8>;
130 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 f.write_str("binary outcome")
132 }
133 fn visit_bytes<E: serde::de::Error>(self, bytes: &[u8]) -> Result<Vec<u8>, E> {
134 Ok(bytes.to_vec())
135 }
136 }
137 deserializer.deserialize_bytes(Bytes)
138 }
139}
140
141fn fingerprint<A: Serialize>(
142 caller: &mut Caller<'_, StoreData>,
143 arguments: &A,
144) -> wasmtime::Result<[u8; 32]> {
145 let mut writer = FingerprintWriter {
146 caller,
147 hash: Sha256::new(),
148 failure: None,
149 };
150 let result =
151 arguments.serialize(&mut rmp_serde::Serializer::new(&mut writer).with_struct_map());
152 if let Some(error) = writer.failure {
153 return Err(error);
154 }
155 result.map_err(fatal_host_error)?;
156 Ok(writer.hash.finalize().into())
157}
158
159struct FingerprintWriter<'a, 'store> {
160 caller: &'a mut Caller<'store, StoreData>,
161 hash: Sha256,
162 failure: Option<wasmtime::Error>,
163}
164impl Write for FingerprintWriter<'_, '_> {
165 fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
166 if let Err(error) = fuel::charge(&mut *self.caller, fuel::HASH, bytes.len() as u64) {
167 self.failure = Some(error);
168 return Err(std::io::Error::other("effect fingerprint exceeded fuel"));
169 }
170 self.hash.update(bytes);
171 Ok(bytes.len())
172 }
173 fn flush(&mut self) -> std::io::Result<()> {
174 Ok(())
175 }
176}
177
178fn encode<T: Serialize>(value: &T) -> wasmtime::Result<Vec<u8>> {
179 let mut writer = OutcomeWriter { bytes: Vec::new() };
180 value
181 .serialize(&mut rmp_serde::Serializer::new(&mut writer).with_struct_map())
182 .map_err(fatal_host_error)?;
183 Ok(writer.bytes)
184}
185struct OutcomeWriter {
186 bytes: Vec<u8>,
187}
188impl Write for OutcomeWriter {
189 fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
190 let length = self
191 .bytes
192 .len()
193 .checked_add(bytes.len())
194 .filter(|n| *n <= MAX_OUTCOME_BYTES)
195 .ok_or_else(|| std::io::Error::other("native outcome exceeds history limit"))?;
196 if length > self.bytes.capacity() {
197 let capacity = length
198 .max(self.bytes.capacity().saturating_mul(2))
199 .min(MAX_OUTCOME_BYTES);
200 self.bytes
201 .try_reserve_exact(capacity - self.bytes.len())
202 .map_err(std::io::Error::other)?;
203 }
204 self.bytes.extend_from_slice(bytes);
205 Ok(bytes.len())
206 }
207 fn flush(&mut self) -> std::io::Result<()> {
208 Ok(())
209 }
210}
211fn validate_outcome(bytes: &[u8]) -> wasmtime::Result<()> {
212 if bytes.len() > MAX_OUTCOME_BYTES {
213 return Err(fatal_host_error("native outcome exceeds history limit"));
214 }
215 super::effects_memory::decoded_heap_bound(bytes, 1, false)?;
217 Ok(())
218}
219
220fn decode<T: DeserializeOwned>(bytes: &[u8]) -> wasmtime::Result<T> {
221 validate_outcome(bytes)?;
222 let mut decoder = rmp_serde::Deserializer::new(std::io::Cursor::new(bytes));
223 decoder.set_max_depth(128);
224 let value = T::deserialize(&mut decoder).map_err(fatal_host_error)?;
225 if decoder.position() != bytes.len() as u64 {
226 return Err(fatal_host_error("trailing native outcome bytes"));
227 }
228 Ok(value)
229}
230
231#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
233pub enum NativeError {
234 Error(String),
235 Range(String),
236 QuotaExceeded(String),
237 Timeout(String),
238 Type(String),
239 Syntax(String),
240 Uri(String),
241 PermissionDenied(host::PermissionDenied),
242}
243impl NativeError {
244 pub(crate) fn capture(error: &wasmtime::Error) -> Self {
245 let message = error.to_string();
246 if let Some(denial) = error.downcast_ref::<host::PermissionDenied>() {
247 return Self::PermissionDenied(denial.clone());
248 }
249 if error.is::<host::RangeError>() {
250 return Self::Range(message);
251 }
252 if error.is::<host::QuotaExceededError>() {
253 return Self::QuotaExceeded(message);
254 }
255 if error.is::<host::TimeoutError>() {
256 return Self::Timeout(message);
257 }
258 if error.is::<host::TypeError>() {
259 return Self::Type(message);
260 }
261 if error.is::<host::SyntaxError>() {
262 return Self::Syntax(message);
263 }
264 if error.is::<host::UriError>() {
265 return Self::Uri(message);
266 }
267 Self::Error(message)
268 }
269 pub(crate) fn into_error(self) -> wasmtime::Error {
270 match self {
271 Self::Error(message) => wasmtime::Error::msg(message),
272 Self::Range(message) => wasmtime::Error::new(host::RangeError(message)),
273 Self::QuotaExceeded(message) => wasmtime::Error::new(host::QuotaExceededError(message)),
274 Self::Timeout(message) => wasmtime::Error::new(host::TimeoutError(message)),
275 Self::Type(message) => wasmtime::Error::new(host::TypeError(message)),
276 Self::Syntax(message) => wasmtime::Error::new(host::SyntaxError(message)),
277 Self::Uri(message) => wasmtime::Error::new(host::UriError(message)),
278 Self::PermissionDenied(denial) => wasmtime::Error::new(denial),
279 }
280 }
281}
282
283pub fn stored_heap_bound(
287 bytes: &[u8],
288 element_bytes: usize,
289 dynamic_maps: bool,
290) -> Result<u64, HistoryError> {
291 super::effects_memory::decoded_heap_bound(bytes, element_bytes, dynamic_maps)
292 .map_err(|error| HistoryError(error.to_string()))
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298 #[test]
299 fn native_floats_keep_nan_bits_and_signed_zero() {
300 for bits in [
301 0x8000_0000_0000_0000,
302 0x7ff8_0000_0000_1234,
303 0x7ff0_0000_0000_0000,
304 ] {
305 let encoded = encode(&f64::from_bits(bits)).unwrap();
306 let restored: f64 = rmp_serde::from_slice(&encoded).unwrap();
307 assert_eq!(restored.to_bits(), bits);
308 }
309 }
310 #[test]
311 fn binary_is_not_expanded_into_integer_elements() {
312 let value = OutcomeBytes(vec![255; 4096]);
313 let encoded = encode(&value).unwrap();
314 assert!(encoded.len() < 4100);
315 let restored: OutcomeBytes = rmp_serde::from_slice(&encoded).unwrap();
316 assert_eq!(restored, value);
317 drop(encoded);
318 }
319 #[test]
320 fn missing_and_empty_native_strings_remain_distinct() {
321 for value in [None, Some(String::new()), Some("text".to_owned())] {
322 let encoded = encode(&value).unwrap();
323 let restored: Option<String> = rmp_serde::from_slice(&encoded).unwrap();
324 assert_eq!(restored, value);
325 }
326 }
327}