1use crate::{
2 hash_encoded, header::OffendersMark, AvailabilityAssurance, EpochIndex, ErasureRoot,
3 TicketEnvelope,
4};
5use bounded_collections::ConstU32;
6use codec::{Compact, CompactLen, ConstEncodedLen, Decode, Encode, MaxEncodedLen};
7use jam_types::{
8 max_report_elective_data, max_work_items, val_count, AuthTrace, AuthorizerHash, BoundedVec,
9 CoreCount, CoreIndex, ExtrinsicHash, FixedVec, MaxDependencies, MaxTicketsPerBlock,
10 MaxWorkItems, RefineContext, SegmentTreeRoot, ServiceId, Slot, UnsignedGas, ValIndex,
11 ValSuperMajority, VecMap, WorkDigest, WorkPackageHash, WorkReportHash, VALS_PER_CORE,
12};
13
14pub const MAX_VERDICTS_COUNT: usize = 16;
15pub const MAX_OFFENSES_COUNT: usize = 16;
16
17pub type PreimagesXt = Vec<Preimage>;
19pub type TicketsXt = BoundedVec<TicketEnvelope, MaxTicketsPerBlock>;
21pub type AssurancesXt = BoundedVec<AvailabilityAssurance, jam_types::ValCount>;
23pub type GuaranteesXt = BoundedVec<ReportGuarantee, CoreCount>;
25#[derive(Clone, Encode, Decode, Debug, Default)]
27pub struct DisputesXt {
28 pub verdicts: BoundedVec<Verdict, ConstU32<{ MAX_VERDICTS_COUNT as u32 }>>,
30 pub culprits: BoundedVec<CulpritProof, ConstU32<{ MAX_OFFENSES_COUNT as u32 }>>,
32 pub faults: BoundedVec<FaultProof, ConstU32<{ MAX_OFFENSES_COUNT as u32 }>>,
34}
35
36impl DisputesXt {
37 pub fn offenders_mark(&self) -> OffendersMark {
38 let offenders: Vec<_> = self
39 .culprits
40 .iter()
41 .map(|v| v.key)
42 .chain(self.faults.iter().map(|v| v.key))
43 .collect();
44 offenders.try_into().expect("OffendersMark bounds equal culprits + faults")
45 }
46 pub fn implies_offenders_mark(&self, offenders_mark: &OffendersMark) -> bool {
47 self.culprits
48 .iter()
49 .map(|v| v.key)
50 .chain(self.faults.iter().map(|v| v.key))
51 .zip(offenders_mark.iter())
52 .all(|(a, b)| a == *b)
53 }
54}
55
56pub type VerdictVotes = FixedVec<Judgement, ValSuperMajority>;
59
60#[derive(Clone, Encode, Decode, Debug)]
62pub struct Verdict {
63 pub target: WorkReportHash,
65 pub age: EpochIndex,
68 pub votes: VerdictVotes,
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
73pub enum VerdictKind {
74 Good,
75 Bad,
76 Wonky,
77}
78
79impl Verdict {
80 pub fn kind(&self) -> Result<VerdictKind, ()> {
83 let valid_count = self.votes.iter().filter(|v| v.vote).count();
84 if valid_count == self.votes.len() {
85 Ok(VerdictKind::Good)
86 } else if valid_count == 0 {
87 Ok(VerdictKind::Bad)
88 } else if valid_count as u16 == val_count() / 3 {
89 Ok(VerdictKind::Wonky)
90 } else {
91 Err(())
92 }
93 }
94}
95
96#[derive(Clone, Encode, Decode, Debug)]
97pub struct Judgement {
98 pub vote: bool,
99 pub index: ValIndex,
100 pub signature: super::ed25519::Signature,
101}
102
103#[derive(Clone, Encode, Decode, Debug)]
104pub struct CulpritProof {
105 pub report_hash: WorkReportHash,
106 pub key: super::ed25519::Public,
107 pub signature: super::ed25519::Signature,
108}
109
110#[derive(Clone, Encode, Decode, Debug)]
111pub struct FaultProof {
112 pub report_hash: WorkReportHash,
113 pub vote: bool,
114 pub key: super::ed25519::Public,
115 pub signature: super::ed25519::Signature,
116}
117
118#[derive(Clone, Encode, Decode, Debug, Default)]
119pub struct Extrinsic {
120 pub tickets: TicketsXt,
121 pub preimages: PreimagesXt,
122 pub guarantees: GuaranteesXt,
123 pub assurances: AssurancesXt,
124 pub disputes: DisputesXt,
125}
126
127impl Extrinsic {
128 pub fn guarantees_prehashed(&self) -> Vec<(WorkReportHash, Slot, &GuaranteeSignatures)> {
129 self.guarantees
130 .iter()
131 .map(|r| (r.report.hash(), r.slot, &r.signatures))
132 .collect()
133 }
134
135 pub fn hash(&self) -> ExtrinsicHash {
136 let tickets_hash = hash_encoded(&self.tickets);
137 let disputes_hash = hash_encoded(&self.disputes);
138 let preimages_hash = hash_encoded(&self.preimages);
139 let assurances_hash = hash_encoded(&self.assurances);
140 let guarantees_hash = hash_encoded(&self.guarantees_prehashed());
141 let top = (tickets_hash, preimages_hash, guarantees_hash, assurances_hash, disputes_hash);
142 hash_encoded(&top).into()
143 }
144}
145
146#[derive(Clone, Encode, Decode, Debug, PartialEq, Eq, PartialOrd, Ord)]
147pub struct Preimage {
148 pub requester: ServiceId,
149 pub blob: Vec<u8>,
150}
151impl Preimage {
152 pub fn new(requester: ServiceId, blob: impl Into<Vec<u8>>) -> Self {
153 Self { requester, blob: blob.into() }
154 }
155
156 pub fn encoded_len(blob_len: usize) -> usize {
157 core::mem::size_of::<ServiceId>() +
158 Compact::<u64>::compact_len(&(blob_len as u64)) +
159 blob_len
160 }
161}
162
163#[derive(Clone, Encode, Decode, Debug, MaxEncodedLen)]
165#[cfg_attr(test, derive(PartialEq, Eq))]
166pub struct ReportGuarantee {
167 pub report: WorkReport,
169 pub slot: Slot,
173 pub signatures: GuaranteeSignatures,
177}
178
179pub type GuaranteeSignatures = BoundedVec<ValSignature, ConstU32<{ VALS_PER_CORE as u32 }>>;
180
181#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen)]
182pub struct ValSignature {
183 pub val_index: ValIndex,
184 pub signature: super::ed25519::Signature,
185}
186
187impl ConstEncodedLen for ValSignature {}
188
189#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen)]
191pub struct WorkPackageSpec {
192 pub hash: WorkPackageHash,
194 pub len: u32,
196 pub erasure_root: ErasureRoot,
198 pub exports_root: SegmentTreeRoot,
200 pub exports_count: u16,
202}
203
204impl WorkPackageSpec {
205 pub fn wp_srl(&self) -> (WorkPackageHash, SegmentTreeRoot) {
206 (self.hash, self.exports_root)
207 }
208}
209
210#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
213pub struct WorkReport {
214 pub package_spec: WorkPackageSpec,
216 pub context: RefineContext,
218 #[codec(compact)]
220 pub core_index: CoreIndex,
221 pub authorizer_hash: AuthorizerHash,
224 #[codec(compact)]
226 pub auth_gas_used: UnsignedGas,
227 pub auth_output: AuthTrace,
229 pub sr_lookup: VecMap<WorkPackageHash, SegmentTreeRoot>,
231 pub results: BoundedVec<WorkDigest, MaxWorkItems>,
233}
234
235impl WorkReport {
236 pub fn hash(&self) -> WorkReportHash {
238 hash_encoded(self).into()
239 }
240
241 pub fn gas(&self) -> UnsignedGas {
243 self.results.iter().map(|r| r.accumulate_gas).sum()
244 }
245
246 pub fn deps(&self) -> impl Iterator<Item = &WorkPackageHash> {
249 self.sr_lookup.keys().chain(self.context.prerequisites.iter())
250 }
251
252 pub fn dep_count(&self) -> usize {
255 self.sr_lookup.len() + self.context.prerequisites.len()
256 }
257
258 pub fn check_size(&self) -> bool {
260 let total: usize =
261 self.results.iter().filter_map(|r| Some(r.result.as_ref().ok()?.len())).sum();
262 total + self.auth_output.len() <= max_report_elective_data() as usize
263 }
264
265 pub fn package_hash(&self) -> WorkPackageHash {
267 self.package_spec.hash
268 }
269}
270
271impl MaxEncodedLen for WorkReport {
272 fn max_encoded_len() -> usize {
273 let mut max = WorkPackageSpec::max_encoded_len() +
274 RefineContext::max_encoded_len() +
275 codec::Compact::<CoreIndex>::max_encoded_len() +
276 AuthorizerHash::max_encoded_len() +
277 AuthTrace::max_encoded_len() +
278 BoundedVec::<(WorkPackageHash, SegmentTreeRoot), MaxDependencies>::max_encoded_len() + BoundedVec::<WorkDigest, MaxWorkItems>::max_encoded_len() +
280 codec::Compact::<UnsignedGas>::max_encoded_len(); max -= max_work_items() * (max_report_elective_data() as usize);
286
287 max
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294 use bounded_collections::TryCollect;
295 use jam_types::{max_dependencies, RefineLoad, MAX_PREIMAGE_BLOB_LEN, MAX_PREIMAGE_LEN};
296
297 #[test]
298 fn max_preimage_blob_len_is_correct() {
299 let preimage =
300 Preimage { requester: ServiceId::MAX, blob: vec![123_u8; MAX_PREIMAGE_BLOB_LEN] };
301 let encoded_len = preimage.encode().len();
302 assert_eq!(MAX_PREIMAGE_LEN, encoded_len);
303 }
304
305 #[test]
306 fn preimage_encoded_len_works() {
307 for blob_len in (0..MAX_PREIMAGE_BLOB_LEN).step_by(997) {
308 let preimage = Preimage { requester: ServiceId::MAX, blob: vec![123_u8; blob_len] };
309 let expected_len = Preimage::encoded_len(blob_len);
310 let actual_len = preimage.encode().len();
311 assert_eq!(expected_len, actual_len);
312 }
313 }
314
315 #[test]
316 fn report_max_encoded_len() {
317 let largest_spec = WorkPackageSpec {
318 hash: Default::default(),
319 len: u32::MAX,
320 erasure_root: Default::default(),
321 exports_root: Default::default(),
322 exports_count: u16::MAX,
323 };
324 assert_eq!(largest_spec.encoded_size(), WorkPackageSpec::max_encoded_len());
325
326 let max_outputs = 1 + max_work_items(); let output = |i| {
328 let mut size = (max_report_elective_data() as usize) / max_outputs;
329 if i == 0 {
330 size += (max_report_elective_data() as usize) % max_outputs;
331 }
332 vec![0; size]
333 };
334
335 let largest_report = WorkReport {
344 package_spec: largest_spec,
345 context: RefineContext::largest(),
346 core_index: CoreIndex::MAX,
347 authorizer_hash: Default::default(),
348 auth_output: output(0).into(),
349 sr_lookup: (0..max_dependencies())
350 .map(|i| ([i as u8; 32].into(), Default::default()))
351 .collect(),
352 results: (0..max_work_items())
353 .map(|i| WorkDigest {
354 service: ServiceId::MAX,
355 code_hash: Default::default(),
356 payload_hash: Default::default(),
357 accumulate_gas: UnsignedGas::MAX,
358 result: Ok(output(1 + i).into()),
359 refine_load: RefineLoad {
360 gas_used: UnsignedGas::MAX,
361 imports: u16::MAX,
362 extrinsic_count: u16::MAX,
363 extrinsic_size: u32::MAX,
364 exports: u16::MAX,
365 },
366 })
367 .try_collect()
368 .unwrap(),
369 auth_gas_used: UnsignedGas::MAX,
370 };
371 let largest_report_size = largest_report.encoded_size();
372
373 let max = WorkReport::max_encoded_len();
379 assert!(largest_report_size <= max);
380 assert!((largest_report_size + max_outputs) >= max);
381 }
382}