1use std::future::Future;
13use std::future::poll_fn;
14use std::sync::Arc;
15use std::task::Poll;
16use std::time::Duration;
17
18use async_trait::async_trait;
19use cratefield_core::{
20 ActorContext, ActorError, ActorHandler, ActorHandlers, Actors, MAX_ACTOR_MESSAGE_BYTES,
21 MAX_ACTOR_REPLY_BYTES, MAX_ACTOR_VALUE_BYTES,
22};
23use futures_util::future::join_all;
24use time::Duration as TimeDuration;
25
26pub const CONTRACT_ACTOR_KIND: &str = "contract";
29
30const OP_INCR: u8 = 1;
33const OP_GET: u8 = 2;
34const OP_INCR_THEN_FAIL: u8 = 3;
35const OP_PUT_N: u8 = 4;
36const OP_DATA_LEN: u8 = 5;
37const OP_REPLY_N: u8 = 6;
38const OP_MARKER: u8 = 7;
39const OP_PAD: u8 = 8;
40const OP_ARM: u8 = 9;
41const OP_ALARMS: u8 = 10;
42
43const KEY_COUNTER: &str = "counter";
46const KEY_DATA: &str = "data";
47const KEY_ALARMS: &str = "alarms";
48const KEY_MARKER: &str = "marker";
49const KEY_REARM: &str = "rearm";
50const KEY_SECS: &str = "secs";
51
52struct ContractActor;
59
60#[async_trait]
61impl ActorHandler for ContractActor {
62 async fn on_message(
63 &self,
64 ctx: &mut dyn ActorContext,
65 message: &[u8],
66 ) -> Result<Vec<u8>, ActorError> {
67 let Some((&op, rest)) = message.split_first() else {
68 return Err(ActorError::Handler("contract: empty message".to_owned()));
69 };
70 match op {
71 OP_INCR => {
72 let next = stored_count(ctx, KEY_COUNTER).await? + 1;
73 yield_once().await;
78 ctx.put(KEY_COUNTER, next.to_string().into_bytes()).await?;
79 Ok(next.to_string().into_bytes())
80 }
81 OP_GET => Ok(stored_count(ctx, KEY_COUNTER)
82 .await?
83 .to_string()
84 .into_bytes()),
85 OP_INCR_THEN_FAIL => {
86 let next = stored_count(ctx, KEY_COUNTER).await? + 1;
87 ctx.put(KEY_COUNTER, next.to_string().into_bytes()).await?;
88 Err(ActorError::Handler("contract: refused".to_owned()))
89 }
90 OP_PUT_N => {
91 let n = read_size(rest)?;
92 ctx.put(KEY_DATA, vec![0_u8; n]).await?;
93 Ok(Vec::new())
94 }
95 OP_DATA_LEN => {
96 let len = ctx.get(KEY_DATA).await?.map(|value| value.len());
97 Ok(match len {
98 Some(len) => len.to_string().into_bytes(),
99 None => b"absent".to_vec(),
100 })
101 }
102 OP_REPLY_N => {
103 let n = read_size(rest)?;
104 ctx.put(KEY_MARKER, b"1".to_vec()).await?;
107 Ok(vec![0_u8; n])
108 }
109 OP_MARKER => Ok(if ctx.get(KEY_MARKER).await?.is_some() {
110 b"yes".to_vec()
111 } else {
112 b"no".to_vec()
113 }),
114 OP_PAD => Ok(b"ok".to_vec()),
115 OP_ARM => {
116 let raw = rest
117 .get(..4)
118 .ok_or_else(|| ActorError::Handler("contract: arm needs seconds".to_owned()))?;
119 let rearm = rest.get(4).copied().unwrap_or(0) != 0;
120 ctx.put(KEY_DATA, b"present".to_vec()).await?;
121 ctx.put(KEY_SECS, raw.to_vec()).await?;
122 if rearm {
123 ctx.put(KEY_REARM, b"1".to_vec()).await?;
124 } else {
125 ctx.delete(KEY_REARM).await?;
126 }
127 let secs = i64::from(u32::from_le_bytes(
128 raw.try_into().expect("exactly four bytes"),
129 ));
130 ctx.set_alarm(ctx.now() + TimeDuration::seconds(secs))
131 .await?;
132 Ok(Vec::new())
133 }
134 OP_ALARMS => Ok(stored_count(ctx, KEY_ALARMS)
135 .await?
136 .to_string()
137 .into_bytes()),
138 other => Err(ActorError::Handler(format!(
139 "contract: unknown opcode {other}"
140 ))),
141 }
142 }
143
144 async fn on_alarm(&self, ctx: &mut dyn ActorContext) -> Result<(), ActorError> {
145 let alarms = stored_count(ctx, KEY_ALARMS).await? + 1;
146 ctx.put(KEY_ALARMS, alarms.to_string().into_bytes()).await?;
147 if ctx.get(KEY_REARM).await?.is_some() {
148 ctx.delete(KEY_REARM).await?;
150 let secs = stored_secs(ctx).await?;
151 let at = ctx.now() + TimeDuration::seconds(secs);
152 ctx.set_alarm(at).await?;
153 } else {
154 ctx.delete(KEY_DATA).await?;
156 }
157 Ok(())
158 }
159}
160
161#[must_use]
163pub fn contract_actor_handler() -> Arc<dyn ActorHandler> {
164 Arc::new(ContractActor)
165}
166
167#[must_use]
170pub fn contract_actor_handlers() -> ActorHandlers {
171 ActorHandlers::new().with(CONTRACT_ACTOR_KIND, contract_actor_handler())
172}
173
174pub async fn assert_actor_contract<F, Fut>(actors: Arc<dyn Actors>, advance: F)
189where
190 F: Fn(Duration) -> Fut,
191 Fut: Future<Output = ()>,
192{
193 let actors = actors.as_ref();
194 check_serialization(actors).await;
195 check_atomic_commit(actors).await;
196 check_size_bounds(actors).await;
197 check_alarm(actors, &advance).await;
198
199 let err = actors.call("no-such-kind", "key", b"x").await.unwrap_err();
201 assert!(
202 matches!(err, ActorError::NotConfigured),
203 "an unknown kind is NotConfigured: {err}"
204 );
205}
206
207async fn check_serialization(actors: &dyn Actors) {
210 let calls = (0..100).map(|_| actors.call(CONTRACT_ACTOR_KIND, "concurrent", &[OP_INCR]));
214 let replies = join_all(calls).await;
215 let mut values: Vec<usize> = replies
216 .into_iter()
217 .map(|reply| parse_decimal(&reply.expect("each concurrent increment succeeds")))
218 .collect();
219 values.sort_unstable();
220 assert_eq!(
221 values,
222 (1..=100).collect::<Vec<usize>>(),
223 "100 serialized increments must each see a distinct value"
224 );
225 assert_eq!(
226 count(actors, "concurrent").await,
227 100,
228 "the counter settles at 100, no update lost"
229 );
230
231 assert_eq!(increment(actors, "independent-a").await, 1);
234 assert_eq!(increment(actors, "independent-b").await, 1);
235 assert_eq!(increment(actors, "independent-a").await, 2);
236 assert_eq!(count(actors, "independent-a").await, 2);
237 assert_eq!(count(actors, "independent-b").await, 1);
238}
239
240async fn check_atomic_commit(actors: &dyn Actors) {
242 assert_eq!(increment(actors, "failing").await, 1);
243 let err = actors
244 .call(CONTRACT_ACTOR_KIND, "failing", &[OP_INCR_THEN_FAIL])
245 .await
246 .unwrap_err();
247 assert!(
248 matches!(err, ActorError::Handler(_)),
249 "a handler error surfaces as Handler: {err}"
250 );
251 assert_eq!(
252 count(actors, "failing").await,
253 1,
254 "the staged write of a failing handler never lands"
255 );
256}
257
258async fn check_size_bounds(actors: &dyn Actors) {
260 let at_bound = vec![OP_PAD; MAX_ACTOR_MESSAGE_BYTES];
261 call_ok(actors, "message-max", &at_bound).await;
262 let over = vec![OP_PAD; MAX_ACTOR_MESSAGE_BYTES + 1];
263 let err = actors
264 .call(CONTRACT_ACTOR_KIND, "message-max", &over)
265 .await
266 .unwrap_err();
267 assert!(
268 matches!(err, ActorError::TooLarge(_)),
269 "a message one byte over the bound is refused: {err}"
270 );
271
272 let reply = call_ok(
273 actors,
274 "reply-max",
275 &sized_message(OP_REPLY_N, MAX_ACTOR_REPLY_BYTES),
276 )
277 .await;
278 assert_eq!(
279 reply.len(),
280 MAX_ACTOR_REPLY_BYTES,
281 "a reply at the bound is accepted"
282 );
283 assert!(
284 marker_present(actors, "reply-max").await,
285 "the accepted reply's marker write committed"
286 );
287 let err = actors
288 .call(
289 CONTRACT_ACTOR_KIND,
290 "reply-over",
291 &sized_message(OP_REPLY_N, MAX_ACTOR_REPLY_BYTES + 1),
292 )
293 .await
294 .unwrap_err();
295 assert!(
296 matches!(err, ActorError::TooLarge(_)),
297 "a reply one byte over the bound is refused: {err}"
298 );
299 assert!(
300 !marker_present(actors, "reply-over").await,
301 "an oversized reply commits nothing"
302 );
303
304 call_ok(
305 actors,
306 "value-max",
307 &sized_message(OP_PUT_N, MAX_ACTOR_VALUE_BYTES),
308 )
309 .await;
310 assert_eq!(
311 data_len(actors, "value-max").await,
312 Some(MAX_ACTOR_VALUE_BYTES),
313 "a value at the bound is stored"
314 );
315 let err = actors
316 .call(
317 CONTRACT_ACTOR_KIND,
318 "value-over",
319 &sized_message(OP_PUT_N, MAX_ACTOR_VALUE_BYTES + 1),
320 )
321 .await
322 .unwrap_err();
323 assert!(
324 matches!(err, ActorError::TooLarge(_)),
325 "a value one byte over the bound is refused: {err}"
326 );
327 assert_eq!(
328 data_len(actors, "value-over").await,
329 None,
330 "an oversized value stores nothing"
331 );
332}
333
334async fn check_alarm<F, Fut>(actors: &dyn Actors, advance: &F)
337where
338 F: Fn(Duration) -> Fut,
339 Fut: Future<Output = ()>,
340{
341 call_ok(actors, "alarm", &arm_message(10, true)).await;
342 assert_eq!(
343 data_len(actors, "alarm").await,
344 Some(b"present".len()),
345 "arming leaves the actor's data in place"
346 );
347 advance(Duration::from_secs(5)).await;
348 assert_eq!(
349 alarm_count(actors, "alarm").await,
350 0,
351 "the alarm does not fire before its time"
352 );
353 advance(Duration::from_secs(5)).await;
354 assert_eq!(
355 alarm_count(actors, "alarm").await,
356 1,
357 "the alarm fires at its time"
358 );
359 advance(Duration::from_secs(10)).await;
360 assert_eq!(
361 alarm_count(actors, "alarm").await,
362 2,
363 "the alarm re-armed once and fired again"
364 );
365 assert_eq!(
366 data_len(actors, "alarm").await,
367 None,
368 "a spent alarm deletes the actor's data"
369 );
370}
371
372async fn call_ok(actors: &dyn Actors, key: &str, message: &[u8]) -> Vec<u8> {
374 actors
375 .call(CONTRACT_ACTOR_KIND, key, message)
376 .await
377 .expect("the contract call succeeds")
378}
379
380async fn count(actors: &dyn Actors, key: &str) -> usize {
382 parse_decimal(&call_ok(actors, key, &[OP_GET]).await)
383}
384
385async fn increment(actors: &dyn Actors, key: &str) -> usize {
387 parse_decimal(&call_ok(actors, key, &[OP_INCR]).await)
388}
389
390async fn alarm_count(actors: &dyn Actors, key: &str) -> usize {
392 parse_decimal(&call_ok(actors, key, &[OP_ALARMS]).await)
393}
394
395async fn data_len(actors: &dyn Actors, key: &str) -> Option<usize> {
397 parse_len(&call_ok(actors, key, &[OP_DATA_LEN]).await)
398}
399
400async fn marker_present(actors: &dyn Actors, key: &str) -> bool {
402 call_ok(actors, key, &[OP_MARKER]).await.as_slice() == b"yes"
403}
404
405fn sized_message(op: u8, n: usize) -> Vec<u8> {
407 let mut message = vec![op];
408 let n = u32::try_from(n).expect("a bounded size fits u32");
409 message.extend_from_slice(&n.to_le_bytes());
410 message
411}
412
413fn arm_message(secs: u32, rearm: bool) -> Vec<u8> {
415 let mut message = vec![OP_ARM];
416 message.extend_from_slice(&secs.to_le_bytes());
417 message.push(u8::from(rearm));
418 message
419}
420
421fn read_size(rest: &[u8]) -> Result<usize, ActorError> {
423 let raw: [u8; 4] = rest
424 .get(..4)
425 .and_then(|bytes| bytes.try_into().ok())
426 .ok_or_else(|| ActorError::Handler("contract: message too short".to_owned()))?;
427 Ok(usize::try_from(u32::from_le_bytes(raw)).expect("a u32 fits usize"))
428}
429
430fn parse_decimal(bytes: &[u8]) -> usize {
432 std::str::from_utf8(bytes)
433 .ok()
434 .and_then(|text| text.parse().ok())
435 .unwrap_or(0)
436}
437
438fn parse_len(bytes: &[u8]) -> Option<usize> {
440 std::str::from_utf8(bytes).ok()?.parse().ok()
441}
442
443async fn stored_count(ctx: &mut dyn ActorContext, key: &str) -> Result<usize, ActorError> {
445 Ok(parse_decimal(&ctx.get(key).await?.unwrap_or_default()))
446}
447
448async fn stored_secs(ctx: &mut dyn ActorContext) -> Result<i64, ActorError> {
450 let Some(raw) = ctx.get(KEY_SECS).await? else {
451 return Ok(0);
452 };
453 Ok(match <[u8; 4]>::try_from(raw) {
454 Ok(bytes) => i64::from(u32::from_le_bytes(bytes)),
455 Err(_) => 0,
456 })
457}
458
459async fn yield_once() {
463 let mut yielded = false;
464 poll_fn(move |cx| {
465 if yielded {
466 Poll::Ready(())
467 } else {
468 yielded = true;
469 cx.waker().wake_by_ref();
470 Poll::Pending
471 }
472 })
473 .await;
474}