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