1use crate::{
2 AvailabilityAssurance, EpochIndex, ErasureRoot, TicketEnvelope, hash_encoded, hash_raw,
3 header::OffendersMark,
4};
5use bounded_collections::ConstU32;
6use bytes::Bytes;
7use codec::{Compact, CompactLen, ConstEncodedLen, Decode, Encode, MaxEncodedLen};
8use jam_types::{
9 AuthTrace, AuthorizerHash, BoundedVec, CoreCount, CoreIndex, ExtrinsicHash, MaxDependencies,
10 MaxTicketsPerBlock, MaxValSuperMajority, MaxWorkItems, RefineContext, SegmentTreeRoot,
11 ServiceId, Slot, UnsignedGas, VALS_PER_CORE, ValIndex, VecMap, WorkDigest, WorkPackageHash,
12 WorkReportHash, max_report_elective_data, max_work_items,
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::MaxValCount>;
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 = BoundedVec<Judgement, MaxValSuperMajority>;
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, ()> {
91 let valid_count = self.votes.iter().filter(|v| v.vote).count();
92 if valid_count == self.votes.len() {
93 Ok(VerdictKind::Good)
94 } else if valid_count == 0 {
95 Ok(VerdictKind::Bad)
96 } else if valid_count == (self.votes.len() - 1) / 2 {
97 Ok(VerdictKind::Wonky)
98 } else {
99 Err(())
100 }
101 }
102}
103
104#[derive(Clone, Encode, Decode, Debug, PartialEq, MaxEncodedLen)]
105pub struct Judgement {
106 pub vote: bool,
107 pub index: ValIndex,
108 pub signature: super::ed25519::Signature,
109}
110
111#[derive(Clone, Encode, Decode, Debug, MaxEncodedLen)]
114pub struct CulpritProof {
115 pub report_hash: WorkReportHash,
116 pub key: super::ed25519::Public,
117 pub signature: super::ed25519::Signature,
118}
119
120#[derive(Clone, Encode, Decode, Debug, MaxEncodedLen)]
124pub struct FaultProof {
125 pub report_hash: WorkReportHash,
126 pub vote: bool,
127 pub key: super::ed25519::Public,
128 pub signature: super::ed25519::Signature,
129}
130
131#[derive(Clone, Encode, Decode, Debug, Default)]
132pub struct Extrinsic {
133 pub tickets: TicketsXt,
134 pub preimages: PreimagesXt,
135 pub guarantees: GuaranteesXt,
136 pub assurances: AssurancesXt,
137 pub disputes: DisputesXt,
138}
139
140#[derive(Clone, Encode, Decode, Debug, Default)]
141pub struct LightExtrinsic {
142 pub tickets: TicketsXt,
143 pub preimages: LightPreimagesXt,
144 pub guarantees: LightGuaranteesXt,
145 pub assurances: AssurancesXt,
146 pub disputes: DisputesXt,
147}
148
149impl Extrinsic {
150 pub fn guarantees_prehashed(&self) -> Vec<(WorkReportHash, Slot, &GuaranteeSignatures)> {
151 self.guarantees
152 .iter()
153 .map(|r| (r.report.hash(), r.slot, &r.signatures))
154 .collect()
155 }
156
157 pub fn preimages_prehashed(&self) -> Vec<(ServiceId, jam_types::Hash)> {
158 self.preimages.iter().map(|r| (r.requester, r.hash)).collect()
159 }
160
161 pub fn hash(&self) -> ExtrinsicHash {
162 let tickets_hash = hash_encoded(&self.tickets);
163 let disputes_hash = hash_encoded(&self.disputes);
164 let preimages_hash = hash_encoded(&self.preimages_prehashed());
165 let assurances_hash = hash_encoded(&self.assurances);
166 let guarantees_hash = hash_encoded(&self.guarantees_prehashed());
167 let top = (tickets_hash, preimages_hash, guarantees_hash, assurances_hash, disputes_hash);
168 hash_encoded(&top).into()
169 }
170
171 pub fn encode_as_light<T: codec::Output + ?Sized>(&self, dest: &mut T) {
172 self.tickets.encode_to(dest);
173 let size = codec::Compact::<u32>(self.preimages.len() as u32);
174 size.encode_to(dest);
175 for preimage in &self.preimages {
176 preimage.as_light().encode_to(dest);
177 }
178 let size = codec::Compact::<u32>(self.guarantees.len() as u32);
179 size.encode_to(dest);
180 for guarantee in &self.guarantees {
181 guarantee.as_light().encode_to(dest);
182 }
183 self.assurances.encode_to(dest);
184 self.disputes.encode_to(dest);
185 }
186}
187
188#[derive(Clone, Debug, Eq, PartialOrd, Ord, Encode)]
189pub struct Preimage {
190 pub requester: ServiceId,
191 blob: Bytes,
192 #[codec(skip)]
193 hash: jam_types::Hash,
194}
195
196impl PartialEq<Preimage> for Preimage {
197 fn eq(&self, other: &Preimage) -> bool {
198 self.requester == other.requester && self.hash == other.hash
199 }
200}
201
202impl Preimage {
203 pub fn as_light(&self) -> LightPreimage {
204 LightPreimage { requester: self.requester, hash: self.hash() }
205 }
206}
207
208impl Decode for Preimage {
209 fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
210 let requester: ServiceId = Decode::decode(input)?;
211 let blob: Bytes = Decode::decode(input)?;
212 let hash = hash_raw(&blob);
213 Ok(Preimage { requester, blob, hash })
214 }
215}
216
217impl Preimage {
218 pub fn new_testing(requester: ServiceId, blob: impl Into<Bytes>) -> Self {
222 let blob: Bytes = blob.into();
223 let hash = hash_raw(&blob);
224 Self { requester, blob, hash }
225 }
226
227 pub fn new_with_hash(
230 requester: ServiceId,
231 blob: impl Into<Bytes>,
232 hash: jam_types::Hash,
233 ) -> Self {
234 let blob: Bytes = blob.into();
235 debug_assert_eq!(hash_raw(&blob), hash);
236 Self { requester, blob, hash }
237 }
238
239 pub fn hash(&self) -> jam_types::Hash {
240 self.hash
241 }
242
243 pub fn blob(&self) -> &Bytes {
244 &self.blob
245 }
246
247 pub fn encoded_len(blob_len: usize) -> usize {
248 core::mem::size_of::<ServiceId>() +
249 Compact::<u64>::compact_len(&(blob_len as u64)) +
250 blob_len
251 }
252}
253
254#[derive(Clone, Encode, Decode, Debug, MaxEncodedLen)]
256#[cfg_attr(test, derive(PartialEq, Eq))]
257pub struct LightPreimage {
258 pub requester: ServiceId,
259 pub hash: jam_types::Hash,
260}
261
262#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen)]
264pub struct ReportGuarantee {
265 pub report: WorkReport,
267 pub slot: Slot,
272 pub signatures: GuaranteeSignatures,
276}
277
278impl ReportGuarantee {
279 pub fn as_light(&self) -> LightReportGuaranteeEncode<'_> {
280 LightReportGuaranteeEncode {
281 report_hash: self.report.hash(),
282 slot: self.slot,
283 signatures: &self.signatures,
284 }
285 }
286}
287
288#[derive(Clone, Encode, Decode, Debug, MaxEncodedLen)]
290#[cfg_attr(test, derive(PartialEq, Eq))]
291pub struct LightReportGuarantee {
292 pub report_hash: WorkReportHash,
293 pub slot: Slot,
294 pub signatures: GuaranteeSignatures,
295}
296
297#[derive(Encode, Debug)]
298#[cfg_attr(test, derive(PartialEq, Eq))]
299pub struct LightReportGuaranteeEncode<'a> {
300 pub report_hash: WorkReportHash,
301 pub slot: Slot,
302 pub signatures: &'a GuaranteeSignatures,
303}
304
305pub type GuaranteeSignatures = BoundedVec<ValSignature, ConstU32<{ VALS_PER_CORE as u32 }>>;
306
307#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen)]
308pub struct ValSignature {
309 pub val_index: ValIndex,
310 pub signature: super::ed25519::Signature,
311}
312
313impl ConstEncodedLen for ValSignature {}
314
315#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen)]
317pub struct WorkPackageSpec {
318 pub hash: WorkPackageHash,
320 pub len: u32,
322 pub erasure_root: ErasureRoot,
324 pub erasure_shards: ValIndex,
329 pub exports_root: SegmentTreeRoot,
331 pub exports_count: u16,
333}
334
335impl WorkPackageSpec {
336 pub fn wp_srl(&self) -> (WorkPackageHash, SegmentTreeRoot) {
337 (self.hash, self.exports_root)
338 }
339}
340
341#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
344pub struct WorkReport {
345 pub package_spec: WorkPackageSpec,
347 pub context: RefineContext,
349 #[codec(compact)]
351 pub core_index: CoreIndex,
352 pub authorizer_hash: AuthorizerHash,
355 #[codec(compact)]
357 pub auth_gas_used: UnsignedGas,
358 pub auth_output: AuthTrace,
360 pub sr_lookup: VecMap<WorkPackageHash, SegmentTreeRoot>,
362 pub results: BoundedVec<WorkDigest, MaxWorkItems>,
364}
365
366impl WorkReport {
367 pub fn hash(&self) -> WorkReportHash {
369 hash_encoded(self).into()
370 }
371
372 pub fn gas(&self) -> UnsignedGas {
374 self.results
375 .iter()
376 .fold(0, |gas: UnsignedGas, r| gas.saturating_add(r.accumulate_gas))
377 }
378
379 pub fn deps(&self) -> impl Iterator<Item = &WorkPackageHash> {
382 self.sr_lookup.keys().chain(self.context.prerequisites.iter())
383 }
384
385 pub fn dep_count(&self) -> usize {
388 self.sr_lookup.len() + self.context.prerequisites.len()
389 }
390
391 pub fn check_size(&self) -> bool {
393 let total: usize =
394 self.results.iter().filter_map(|r| Some(r.result.as_ref().ok()?.len())).sum();
395 total + self.auth_output.len() <= max_report_elective_data() as usize
396 }
397
398 pub fn package_hash(&self) -> WorkPackageHash {
400 self.package_spec.hash
401 }
402}
403
404impl MaxEncodedLen for WorkReport {
405 fn max_encoded_len() -> usize {
406 let mut max = WorkPackageSpec::max_encoded_len() +
407 RefineContext::max_encoded_len() +
408 codec::Compact::<CoreIndex>::max_encoded_len() +
409 AuthorizerHash::max_encoded_len() +
410 AuthTrace::max_encoded_len() +
411 BoundedVec::<(WorkPackageHash, SegmentTreeRoot), MaxDependencies>::max_encoded_len() + BoundedVec::<WorkDigest, MaxWorkItems>::max_encoded_len() +
413 codec::Compact::<UnsignedGas>::max_encoded_len(); max -= max_work_items() * (max_report_elective_data() as usize);
419
420 max
421 }
422}
423
424pub mod tests_utils {
425 use super::*;
426 use bounded_collections::TryCollect;
427 use jam_types::{RefineLoad, max_dependencies};
428
429 pub fn largest_report() -> WorkReport {
438 let largest_spec = WorkPackageSpec {
439 hash: Default::default(),
440 len: u32::MAX,
441 erasure_root: Default::default(),
442 erasure_shards: ValIndex::MAX,
443 exports_root: Default::default(),
444 exports_count: u16::MAX,
445 };
446
447 let max_outputs = 1 + max_work_items(); let output = |i| {
449 let mut size = (max_report_elective_data() as usize) / max_outputs;
450 if i == 0 {
451 size += (max_report_elective_data() as usize) % max_outputs;
452 }
453 vec![0; size]
454 };
455
456 WorkReport {
457 package_spec: largest_spec,
458 context: RefineContext::largest(),
459 core_index: CoreIndex::MAX,
460 authorizer_hash: Default::default(),
461 auth_output: output(0).into(),
462 sr_lookup: (0..max_dependencies())
463 .map(|i| ([i as u8; 32].into(), Default::default()))
464 .collect(),
465 results: (0..max_work_items())
466 .map(|i| WorkDigest {
467 service: ServiceId::MAX,
468 code_hash: Default::default(),
469 payload_hash: Default::default(),
470 accumulate_gas: UnsignedGas::MAX,
471 result: Ok(output(1 + i).into()),
472 refine_load: RefineLoad {
473 gas_used: UnsignedGas::MAX,
474 imports: u16::MAX,
475 extrinsic_count: u16::MAX,
476 extrinsic_size: u32::MAX,
477 exports: u16::MAX,
478 },
479 })
480 .try_collect()
481 .expect("max workitem should be this vec bound"),
482 auth_gas_used: UnsignedGas::MAX,
483 }
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490 use jam_types::{MAX_PREIMAGE_BLOB_LEN, MAX_PREIMAGE_LEN, max_work_items};
491
492 #[test]
493 fn report_gas_saturates() {
494 let mut report = tests_utils::largest_report();
495 report.results.truncate(2);
496 report.results[0].accumulate_gas = UnsignedGas::MAX - 2;
497 report.results[1].accumulate_gas = 1;
499 assert_eq!(report.gas(), UnsignedGas::MAX - 1);
500 report.results[1].accumulate_gas = 10000;
502 assert_eq!(report.gas(), UnsignedGas::MAX);
503 }
504
505 #[test]
506 fn max_preimage_blob_len_is_correct() {
507 let preimage = Preimage::new_testing(ServiceId::MAX, vec![123_u8; MAX_PREIMAGE_BLOB_LEN]);
508 let encoded_len = preimage.encode().len();
509 assert_eq!(MAX_PREIMAGE_LEN, encoded_len);
510 }
511
512 #[test]
513 fn preimage_encoded_len_works() {
514 for blob_len in (0..MAX_PREIMAGE_BLOB_LEN).step_by(997 * 64) {
515 let preimage = Preimage::new_testing(ServiceId::MAX, vec![123_u8; blob_len]);
516 let expected_len = Preimage::encoded_len(blob_len);
517 let actual_len = preimage.encode().len();
518 assert_eq!(expected_len, actual_len);
519 }
520 }
521
522 #[test]
523 fn report_max_encoded_len() {
524 let largest_report = super::tests_utils::largest_report();
525 assert_eq!(largest_report.package_spec.encoded_size(), WorkPackageSpec::max_encoded_len());
526
527 let largest_report_size = largest_report.encoded_size();
528
529 let max = WorkReport::max_encoded_len();
535 assert!(largest_report_size <= max);
536 let max_outputs = 1 + max_work_items(); assert!((largest_report_size + max_outputs) >= max);
538 }
539}