1use alloc::{boxed::Box, collections::VecDeque, vec::Vec};
18use core::fmt;
19
20use borsh::{BorshDeserialize, BorshSerialize};
21use risc0_binfmt::{tagged_struct, DecodeError, Digestible, PovwNonce};
22use serde::{Deserialize, Serialize};
23
24use crate::{
25 sha,
26 sha::{Digest, Sha256},
27 MaybePruned, PrunedValueError, ReceiptClaim, Unknown,
28};
29
30#[derive(Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
35pub struct WorkClaim<Claim> {
36 pub claim: MaybePruned<Claim>,
38 pub work: MaybePruned<Work>,
40}
41
42impl<Claim> WorkClaim<Claim> {
43 pub fn into_unknown(self) -> WorkClaim<Unknown>
46 where
47 Claim: Digestible,
48 {
49 WorkClaim {
50 claim: MaybePruned::Pruned(self.claim.digest::<sha::Impl>()),
51 work: self.work,
52 }
53 }
54}
55
56impl<Claim> Digestible for WorkClaim<Claim>
57where
58 Claim: Digestible,
59{
60 fn digest<S: Sha256>(&self) -> Digest {
62 tagged_struct::<S>(
63 "risc0.WorkClaim",
64 &[self.claim.digest::<S>(), self.work.digest::<S>()],
65 &[],
66 )
67 }
68}
69
70impl<Claim> fmt::Debug for WorkClaim<Claim>
71where
72 Claim: Digestible + fmt::Debug,
73{
74 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
75 fmt.debug_struct("WorkClaim")
76 .field("claim", &self.claim)
77 .field("work", &self.work)
78 .finish()
79 }
80}
81
82impl WorkClaim<ReceiptClaim> {
83 pub fn encode_to_seal(&self, buf: &mut Vec<u32>) -> Result<(), PrunedValueError> {
85 self.claim.as_value()?.encode(buf)?;
86 self.work.as_value()?.encode_to_seal(buf);
87 Ok(())
88 }
89
90 pub fn decode_from_seal(
92 buf: &mut VecDeque<u32>,
93 ) -> Result<Self, crate::claim::receipt::DecodeError> {
94 Ok(Self {
95 claim: ReceiptClaim::decode(buf)?.into(),
96 work: Work::decode_from_seal(buf)?.into(),
97 })
98 }
99}
100
101#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
106pub struct Work {
107 pub nonce_min: PovwNonce,
109 pub nonce_max: PovwNonce,
111 pub value: u64,
113}
114
115#[derive(Debug, Clone)]
117#[non_exhaustive]
118pub enum WorkClaimError {
119 NonceRangesNotContiguous(Box<(Work, Work)>),
120 PrunedValue(PrunedValueError),
121}
122
123impl fmt::Display for WorkClaimError {
124 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
125 match self {
126 WorkClaimError::NonceRangesNotContiguous(ab) => {
127 write!(
128 f,
129 "work nonce ranges are not contiguous: ({:?}, {:?}) and ({:?}. {:?})",
130 ab.0.nonce_min, ab.0.nonce_max, ab.1.nonce_min, ab.1.nonce_max
131 )
132 }
133 WorkClaimError::PrunedValue(err) => err.fmt(f),
134 }
135 }
136}
137
138impl From<PrunedValueError> for WorkClaimError {
139 fn from(err: PrunedValueError) -> Self {
140 Self::PrunedValue(err)
141 }
142}
143
144#[cfg(feature = "std")]
145impl std::error::Error for WorkClaimError {}
146
147impl Work {
148 pub fn join(&self, other: &Self) -> Result<Self, WorkClaimError> {
151 let contiguous = self
154 .nonce_max
155 .to_u256()
156 .checked_add(1u64.try_into().unwrap())
157 .map(|max| max == other.nonce_min.to_u256())
158 .unwrap_or(false);
159 if !contiguous {
160 return Err(WorkClaimError::NonceRangesNotContiguous(Box::new((
161 self.clone(),
162 other.clone(),
163 ))));
164 }
165
166 Ok(Self {
167 nonce_min: self.nonce_min,
168 nonce_max: other.nonce_max,
169 value: self.value + other.value,
170 })
171 }
172
173 pub(crate) fn encode_to_seal(&self, buf: &mut Vec<u32>) {
174 buf.extend(self.nonce_min.to_u16s().into_iter().map(u32::from));
175 buf.extend(self.nonce_max.to_u16s().into_iter().map(u32::from));
176 buf.extend(u64_to_u16s(self.value).into_iter().map(u32::from));
177 }
178
179 pub(crate) fn decode_from_seal(
180 buf: &mut VecDeque<u32>,
181 ) -> Result<Self, risc0_binfmt::DecodeError> {
182 Ok(Self {
183 nonce_min: PovwNonce::decode_from_seal(buf)?,
184 nonce_max: PovwNonce::decode_from_seal(buf)?,
185 value: decode_work_value_from_seal(buf)?,
186 })
187 }
188}
189
190impl MaybePruned<Work> {
191 pub fn join(&self, other: &Self) -> Result<Self, WorkClaimError> {
194 Ok(self.as_value()?.join(other.as_value()?)?.into())
195 }
196}
197
198fn u64_to_u16s(x: u64) -> [u16; 4] {
199 let mut u16s = bytemuck::cast::<_, [u16; 4]>(x.to_le_bytes());
200 for x in u16s.iter_mut() {
202 *x = u16::from_le(*x);
203 }
204 u16s
205}
206
207fn u64_from_u16s(mut u16s: [u16; 4]) -> u64 {
208 for x in u16s.iter_mut() {
210 *x = u16::from_le(*x);
211 }
212 u64::from_le_bytes(bytemuck::cast(u16s))
213}
214
215fn decode_work_value_from_seal(buf: &mut VecDeque<u32>) -> Result<u64, risc0_binfmt::DecodeError> {
216 if buf.len() < 4 {
217 return Err(DecodeError::EndOfStream);
218 }
219 fn u16_from_u32(x: u32) -> Result<u16, risc0_binfmt::DecodeError> {
220 x.try_into()
221 .map_err(|_| risc0_binfmt::DecodeError::OutOfRange)
222 }
223 Ok(u64_from_u16s(
224 buf.drain(..4)
225 .map(u16_from_u32)
226 .collect::<Result<Vec<_>, _>>()?
227 .try_into()
228 .unwrap(),
229 ))
230}
231
232impl Digestible for Work {
233 fn digest<S: Sha256>(&self) -> Digest {
235 let mut buf = Vec::new();
236 self.encode_to_seal(&mut buf);
237 tagged_struct::<S>("risc0.Work", &Vec::<Digest>::new(), &buf)
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244 use crate::{sha, ReceiptClaim};
245 use alloc::collections::VecDeque;
246 use risc0_binfmt::{ExitCode, SystemState};
247
248 #[test]
249 fn test_work_seal_encoding_round_trip() {
250 let original = Work {
251 nonce_min: rand::random(),
252 nonce_max: rand::random(),
253 value: rand::random(),
254 };
255
256 let mut buf = Vec::new();
257 original.encode_to_seal(&mut buf);
258
259 let mut decode_buf = VecDeque::from(buf);
260 let decoded = Work::decode_from_seal(&mut decode_buf).unwrap();
261
262 assert_eq!(original.nonce_min, decoded.nonce_min);
263 assert_eq!(original.nonce_max, decoded.nonce_max);
264 assert_eq!(original.value, decoded.value);
265 assert!(decode_buf.is_empty());
266 }
267
268 #[test]
269 fn test_work_claim_seal_encoding_round_trip() {
270 use crate::sha::Digest;
271
272 let claim = ReceiptClaim {
274 pre: SystemState {
275 pc: 0,
276 merkle_root: *sha::Impl::hash_bytes(b"pre"),
277 }
278 .into(),
279 post: SystemState {
280 pc: 0,
281 merkle_root: *sha::Impl::hash_bytes(b"pre"),
282 }
283 .into(),
284 output: MaybePruned::Pruned(Digest::ZERO),
285 input: MaybePruned::Pruned(Digest::ZERO),
286 exit_code: ExitCode::SystemSplit,
287 };
288
289 let original = WorkClaim {
290 claim: claim.into(),
291 work: Work {
292 nonce_min: rand::random(),
293 nonce_max: rand::random(),
294 value: rand::random(),
295 }
296 .into(),
297 };
298
299 let mut buf = Vec::new();
300 original.encode_to_seal(&mut buf).unwrap();
301
302 let mut decode_buf = VecDeque::from(buf);
303 let decoded = WorkClaim::decode_from_seal(&mut decode_buf).unwrap();
304
305 assert_eq!(
306 original.claim.as_value().unwrap(),
307 decoded.claim.as_value().unwrap()
308 );
309 assert_eq!(
310 original.work.as_value().unwrap().nonce_min,
311 decoded.work.as_value().unwrap().nonce_min
312 );
313 assert_eq!(
314 original.work.as_value().unwrap().nonce_max,
315 decoded.work.as_value().unwrap().nonce_max
316 );
317 assert_eq!(
318 original.work.as_value().unwrap().value,
319 decoded.work.as_value().unwrap().value
320 );
321 assert!(decode_buf.is_empty());
322 }
323}