1use crate::{
2 hash_encoded, hash_raw, header::OffendersMark, AvailabilityAssurance, EpochIndex, ErasureRoot,
3 TicketEnvelope,
4};
5use bounded_collections::ConstU32;
6use bytes::Bytes;
7use codec::{Compact, CompactLen, ConstEncodedLen, Decode, Encode, MaxEncodedLen};
8use jam_types::{
9 max_report_elective_data, max_work_items, val_count, AuthTrace, AuthorizerHash, BoundedVec,
10 CoreCount, CoreIndex, ExtrinsicHash, FixedVec, MaxDependencies, MaxTicketsPerBlock,
11 MaxWorkItems, RefineContext, SegmentTreeRoot, ServiceId, Slot, UnsignedGas, ValIndex,
12 ValSuperMajority, VecMap, WorkDigest, WorkPackageHash, WorkReportHash, VALS_PER_CORE,
13};
14
15pub const MAX_VERDICTS_COUNT: usize = 16;
16pub const MAX_OFFENSES_COUNT: usize = 16;
17
18pub type PreimagesXt = Vec<Preimage>;
20pub type LightPreimagesXt = Vec<LightPreimage>;
22pub type TicketsXt = BoundedVec<TicketEnvelope, MaxTicketsPerBlock>;
24pub type AssurancesXt = BoundedVec<AvailabilityAssurance, jam_types::ValCount>;
26pub type GuaranteesXt = BoundedVec<ReportGuarantee, CoreCount>;
28pub type LightGuaranteesXt = BoundedVec<LightReportGuarantee, CoreCount>;
30
31#[derive(Clone, Encode, Decode, Debug, Default, MaxEncodedLen)]
33pub struct DisputesXt {
34 pub verdicts: BoundedVec<Verdict, ConstU32<{ MAX_VERDICTS_COUNT as u32 }>>,
36 pub culprits: BoundedVec<CulpritProof, ConstU32<{ MAX_OFFENSES_COUNT as u32 }>>,
38 pub faults: BoundedVec<FaultProof, ConstU32<{ MAX_OFFENSES_COUNT as u32 }>>,
40}
41
42impl DisputesXt {
43 pub fn offenders_mark(&self) -> OffendersMark {
44 let offenders: Vec<_> = self
45 .culprits
46 .iter()
47 .map(|v| v.key)
48 .chain(self.faults.iter().map(|v| v.key))
49 .collect();
50 offenders.try_into().expect("OffendersMark bounds equal culprits + faults")
51 }
52 pub fn implies_offenders_mark(&self, offenders_mark: &OffendersMark) -> bool {
53 self.culprits
54 .iter()
55 .map(|v| &v.key)
56 .chain(self.faults.iter().map(|v| &v.key))
57 .eq(offenders_mark.iter())
58 }
59}
60
61pub type VerdictVotes = FixedVec<Judgement, ValSuperMajority>;
64
65#[derive(Clone, Encode, Decode, Debug, MaxEncodedLen)]
67pub struct Verdict {
68 pub target: WorkReportHash,
70 pub age: EpochIndex,
73 pub votes: VerdictVotes,
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum VerdictKind {
79 Good,
80 Bad,
81 Wonky,
82}
83
84impl Verdict {
85 pub fn kind(&self) -> Result<VerdictKind, ()> {
88 let valid_count = self.votes.iter().filter(|v| v.vote).count();
89 if valid_count == self.votes.len() {
90 Ok(VerdictKind::Good)
91 } else if valid_count == 0 {
92 Ok(VerdictKind::Bad)
93 } else if valid_count as u16 == val_count() / 3 {
94 Ok(VerdictKind::Wonky)
95 } else {
96 Err(())
97 }
98 }
99}
100
101#[derive(Clone, Encode, Decode, Debug, PartialEq, MaxEncodedLen)]
102pub struct Judgement {
103 pub vote: bool,
104 pub index: ValIndex,
105 pub signature: super::ed25519::Signature,
106}
107
108#[derive(Clone, Encode, Decode, Debug, MaxEncodedLen)]
109pub struct CulpritProof {
110 pub report_hash: WorkReportHash,
111 pub key: super::ed25519::Public,
112 pub signature: super::ed25519::Signature,
113}
114
115#[derive(Clone, Encode, Decode, Debug, MaxEncodedLen)]
116pub struct FaultProof {
117 pub report_hash: WorkReportHash,
118 pub vote: bool,
119 pub key: super::ed25519::Public,
120 pub signature: super::ed25519::Signature,
121}
122
123#[derive(Clone, Encode, Decode, Debug, Default)]
124pub struct Extrinsic {
125 pub tickets: TicketsXt,
126 pub preimages: PreimagesXt,
127 pub guarantees: GuaranteesXt,
128 pub assurances: AssurancesXt,
129 pub disputes: DisputesXt,
130}
131
132#[derive(Clone, Encode, Decode, Debug, Default)]
133pub struct LightExtrinsic {
134 pub tickets: TicketsXt,
135 pub preimages: LightPreimagesXt,
136 pub guarantees: LightGuaranteesXt,
137 pub assurances: AssurancesXt,
138 pub disputes: DisputesXt,
139}
140
141impl Extrinsic {
142 pub fn guarantees_prehashed(&self) -> Vec<(WorkReportHash, Slot, &GuaranteeSignatures)> {
143 self.guarantees
144 .iter()
145 .map(|r| (r.report.hash(), r.slot, &r.signatures))
146 .collect()
147 }
148
149 pub fn hash(&self) -> ExtrinsicHash {
150 let tickets_hash = hash_encoded(&self.tickets);
151 let disputes_hash = hash_encoded(&self.disputes);
152 let preimages_hash = hash_encoded(&self.preimages);
153 let assurances_hash = hash_encoded(&self.assurances);
154 let guarantees_hash = hash_encoded(&self.guarantees_prehashed());
155 let top = (tickets_hash, preimages_hash, guarantees_hash, assurances_hash, disputes_hash);
156 hash_encoded(&top).into()
157 }
158
159 pub fn encode_as_light<T: codec::Output + ?Sized>(&self, dest: &mut T) {
160 self.tickets.encode_to(dest);
161 let size = codec::Compact::<u32>(self.preimages.len() as u32);
162 size.encode_to(dest);
163 for preimage in &self.preimages {
164 preimage.as_light().encode_to(dest);
165 }
166 let size = codec::Compact::<u32>(self.guarantees.len() as u32);
167 size.encode_to(dest);
168 for guarantee in &self.guarantees {
169 guarantee.as_light().encode_to(dest);
170 }
171 self.assurances.encode_to(dest);
172 self.disputes.encode_to(dest);
173 }
174}
175
176#[derive(Clone, Debug, Eq, PartialOrd, Ord, Encode)]
177pub struct Preimage {
178 pub requester: ServiceId,
179 blob: Bytes,
180 #[codec(skip)]
181 hash: jam_types::Hash,
182}
183
184impl PartialEq<Preimage> for Preimage {
185 fn eq(&self, other: &Preimage) -> bool {
186 self.requester == other.requester && self.hash == other.hash
187 }
188}
189
190impl Preimage {
191 pub fn as_light(&self) -> LightPreimage {
192 LightPreimage { requester: self.requester, hash: self.hash() }
193 }
194}
195
196impl Decode for Preimage {
197 fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
198 let requester: ServiceId = Decode::decode(input)?;
199 let blob: Bytes = Decode::decode(input)?;
200 let hash = hash_raw(&blob);
201 Ok(Preimage { requester, blob, hash })
202 }
203}
204
205impl Preimage {
206 pub fn new_testing(requester: ServiceId, blob: impl Into<Bytes>) -> Self {
210 let blob: Bytes = blob.into();
211 let hash = hash_raw(&blob);
212 Self { requester, blob, hash }
213 }
214
215 pub fn new_with_hash(
218 requester: ServiceId,
219 blob: impl Into<Bytes>,
220 hash: jam_types::Hash,
221 ) -> Self {
222 let blob: Bytes = blob.into();
223 debug_assert_eq!(hash_raw(&blob), hash);
224 Self { requester, blob, hash }
225 }
226
227 pub fn hash(&self) -> jam_types::Hash {
228 self.hash
229 }
230
231 pub fn blob(&self) -> &Bytes {
232 &self.blob
233 }
234
235 pub fn encoded_len(blob_len: usize) -> usize {
236 core::mem::size_of::<ServiceId>() +
237 Compact::<u64>::compact_len(&(blob_len as u64)) +
238 blob_len
239 }
240}
241
242#[derive(Clone, Encode, Decode, Debug, MaxEncodedLen)]
244#[cfg_attr(test, derive(PartialEq, Eq))]
245pub struct LightPreimage {
246 pub requester: ServiceId,
247 pub hash: jam_types::Hash,
248}
249
250#[derive(Clone, Encode, Decode, Debug, MaxEncodedLen)]
252#[cfg_attr(test, derive(PartialEq, Eq))]
253pub struct ReportGuarantee {
254 pub report: WorkReport,
256 pub slot: Slot,
261 pub signatures: GuaranteeSignatures,
265}
266
267impl ReportGuarantee {
268 pub fn as_light(&self) -> LightReportGuaranteeEncode<'_> {
269 LightReportGuaranteeEncode {
270 report_hash: self.report.hash(),
271 slot: self.slot,
272 signatures: &self.signatures,
273 }
274 }
275}
276
277#[derive(Clone, Encode, Decode, Debug, MaxEncodedLen)]
279#[cfg_attr(test, derive(PartialEq, Eq))]
280pub struct LightReportGuarantee {
281 pub report_hash: WorkReportHash,
282 pub slot: Slot,
283 pub signatures: GuaranteeSignatures,
284}
285
286#[derive(Encode, Debug)]
287#[cfg_attr(test, derive(PartialEq, Eq))]
288pub struct LightReportGuaranteeEncode<'a> {
289 pub report_hash: WorkReportHash,
290 pub slot: Slot,
291 pub signatures: &'a GuaranteeSignatures,
292}
293
294pub type GuaranteeSignatures = BoundedVec<ValSignature, ConstU32<{ VALS_PER_CORE as u32 }>>;
295
296#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen)]
297pub struct ValSignature {
298 pub val_index: ValIndex,
299 pub signature: super::ed25519::Signature,
300}
301
302impl ConstEncodedLen for ValSignature {}
303
304#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen)]
306pub struct WorkPackageSpec {
307 pub hash: WorkPackageHash,
309 pub len: u32,
311 pub erasure_root: ErasureRoot,
313 pub exports_root: SegmentTreeRoot,
315 pub exports_count: u16,
317}
318
319impl WorkPackageSpec {
320 pub fn wp_srl(&self) -> (WorkPackageHash, SegmentTreeRoot) {
321 (self.hash, self.exports_root)
322 }
323}
324
325#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
328pub struct WorkReport {
329 pub package_spec: WorkPackageSpec,
331 pub context: RefineContext,
333 #[codec(compact)]
335 pub core_index: CoreIndex,
336 pub authorizer_hash: AuthorizerHash,
339 #[codec(compact)]
341 pub auth_gas_used: UnsignedGas,
342 pub auth_output: AuthTrace,
344 pub sr_lookup: VecMap<WorkPackageHash, SegmentTreeRoot>,
346 pub results: BoundedVec<WorkDigest, MaxWorkItems>,
348}
349
350impl WorkReport {
351 pub fn hash(&self) -> WorkReportHash {
353 hash_encoded(self).into()
354 }
355
356 pub fn gas(&self) -> UnsignedGas {
358 self.results.iter().map(|r| r.accumulate_gas).sum()
359 }
360
361 pub fn deps(&self) -> impl Iterator<Item = &WorkPackageHash> {
364 self.sr_lookup.keys().chain(self.context.prerequisites.iter())
365 }
366
367 pub fn dep_count(&self) -> usize {
370 self.sr_lookup.len() + self.context.prerequisites.len()
371 }
372
373 pub fn check_size(&self) -> bool {
375 let total: usize =
376 self.results.iter().filter_map(|r| Some(r.result.as_ref().ok()?.len())).sum();
377 total + self.auth_output.len() <= max_report_elective_data() as usize
378 }
379
380 pub fn package_hash(&self) -> WorkPackageHash {
382 self.package_spec.hash
383 }
384}
385
386impl MaxEncodedLen for WorkReport {
387 fn max_encoded_len() -> usize {
388 let mut max = WorkPackageSpec::max_encoded_len() +
389 RefineContext::max_encoded_len() +
390 codec::Compact::<CoreIndex>::max_encoded_len() +
391 AuthorizerHash::max_encoded_len() +
392 AuthTrace::max_encoded_len() +
393 BoundedVec::<(WorkPackageHash, SegmentTreeRoot), MaxDependencies>::max_encoded_len() + BoundedVec::<WorkDigest, MaxWorkItems>::max_encoded_len() +
395 codec::Compact::<UnsignedGas>::max_encoded_len(); max -= max_work_items() * (max_report_elective_data() as usize);
401
402 max
403 }
404}
405
406pub mod tests_utils {
407 use super::*;
408 use bounded_collections::TryCollect;
409 use jam_types::{max_dependencies, RefineLoad};
410
411 pub fn largest_report() -> WorkReport {
420 let largest_spec = WorkPackageSpec {
421 hash: Default::default(),
422 len: u32::MAX,
423 erasure_root: Default::default(),
424 exports_root: Default::default(),
425 exports_count: u16::MAX,
426 };
427
428 let max_outputs = 1 + max_work_items(); let output = |i| {
430 let mut size = (max_report_elective_data() as usize) / max_outputs;
431 if i == 0 {
432 size += (max_report_elective_data() as usize) % max_outputs;
433 }
434 vec![0; size]
435 };
436
437 WorkReport {
438 package_spec: largest_spec,
439 context: RefineContext::largest(),
440 core_index: CoreIndex::MAX,
441 authorizer_hash: Default::default(),
442 auth_output: output(0).into(),
443 sr_lookup: (0..max_dependencies())
444 .map(|i| ([i as u8; 32].into(), Default::default()))
445 .collect(),
446 results: (0..max_work_items())
447 .map(|i| WorkDigest {
448 service: ServiceId::MAX,
449 code_hash: Default::default(),
450 payload_hash: Default::default(),
451 accumulate_gas: UnsignedGas::MAX,
452 result: Ok(output(1 + i).into()),
453 refine_load: RefineLoad {
454 gas_used: UnsignedGas::MAX,
455 imports: u16::MAX,
456 extrinsic_count: u16::MAX,
457 extrinsic_size: u32::MAX,
458 exports: u16::MAX,
459 },
460 })
461 .try_collect()
462 .expect("max workitem should be this vec bound"),
463 auth_gas_used: UnsignedGas::MAX,
464 }
465 }
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471 use jam_types::{max_work_items, MAX_PREIMAGE_BLOB_LEN, MAX_PREIMAGE_LEN};
472
473 #[test]
474 fn max_preimage_blob_len_is_correct() {
475 let preimage = Preimage::new_testing(ServiceId::MAX, vec![123_u8; MAX_PREIMAGE_BLOB_LEN]);
476 let encoded_len = preimage.encode().len();
477 assert_eq!(MAX_PREIMAGE_LEN, encoded_len);
478 }
479
480 #[test]
481 fn preimage_encoded_len_works() {
482 for blob_len in (0..MAX_PREIMAGE_BLOB_LEN).step_by(997) {
483 let preimage = Preimage::new_testing(ServiceId::MAX, vec![123_u8; blob_len]);
484 let expected_len = Preimage::encoded_len(blob_len);
485 let actual_len = preimage.encode().len();
486 assert_eq!(expected_len, actual_len);
487 }
488 }
489
490 #[test]
491 fn report_max_encoded_len() {
492 let largest_report = super::tests_utils::largest_report();
493 assert_eq!(largest_report.package_spec.encoded_size(), WorkPackageSpec::max_encoded_len());
494
495 let largest_report_size = largest_report.encoded_size();
496
497 let max = WorkReport::max_encoded_len();
503 assert!(largest_report_size <= max);
504 let max_outputs = 1 + max_work_items(); assert!((largest_report_size + max_outputs) >= max);
506 }
507}