jam_std_common/
extrinsic.rs

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
17/// A bunch of preimages.
18pub type PreimagesXt = Vec<Preimage>;
19/// A collection of ticket envelopes.
20pub type TicketsXt = BoundedVec<TicketEnvelope, MaxTicketsPerBlock>;
21/// A bunch of assurances.
22pub type AssurancesXt = BoundedVec<AvailabilityAssurance, jam_types::ValCount>;
23/// A bunch of guarantees.
24pub type GuaranteesXt = BoundedVec<ReportGuarantee, CoreCount>;
25/// The disputes extrinsic.
26#[derive(Clone, Encode, Decode, Debug, Default)]
27pub struct DisputesXt {
28	/// Disputed work reports together with their judgements.
29	pub verdicts: BoundedVec<Verdict, ConstU32<{ MAX_VERDICTS_COUNT as u32 }>>,
30	/// Validators who guaranteed a work report subsequently determined to be invalid.
31	pub culprits: BoundedVec<CulpritProof, ConstU32<{ MAX_OFFENSES_COUNT as u32 }>>,
32	/// Validators who audited a work report in conflict with the final dispute resolution.
33	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
56/// Judgements coming from a supermajority of either the active validators
57/// set (κ) or the previous epoch's validator set (λ).
58pub type VerdictVotes = FixedVec<Judgement, ValSuperMajority>;
59
60/// Collection of judgements for a given target work report.
61#[derive(Clone, Encode, Decode, Debug)]
62pub struct Verdict {
63	/// Target work report.
64	pub target: WorkReportHash,
65	/// Epoch index of the prior state or one less depending on the key set
66	/// used to sign the votes.
67	pub age: EpochIndex,
68	/// Verdict votes collection.
69	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	/// Returns the kind of verdict (good/bad/wonky) based on the vote split, or `Err` if the vote
81	/// split is invalid.
82	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/// A Work Report along with a guarantee of its correctness from staked sources.
164#[derive(Clone, Encode, Decode, Debug, MaxEncodedLen)]
165#[cfg_attr(test, derive(PartialEq, Eq))]
166pub struct ReportGuarantee {
167	/// The Work Report which is being attested.
168	pub report: WorkReport,
169	/// The slot following production of the report. The validator set and hence the meaning of the
170	/// validator indices in `signatures` is inferred from this. The indices must be a subset of
171	/// those assigned to `report.core_index` during the slot.
172	pub slot: Slot,
173	/// The signatures from the guarantors whose message is the hash of the `report`.
174	/// The order of the signatures is the same order as the validators appear in
175	/// the epochal validator set.
176	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/// Secure reference to a Work Package.
190#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen)]
191pub struct WorkPackageSpec {
192	/// The hash of the Work Package.
193	pub hash: WorkPackageHash,
194	/// The length in bytes of Work Bundle.
195	pub len: u32,
196	/// The erasure root of the Work Bundle and export-segment pieces.
197	pub erasure_root: ErasureRoot,
198	/// The segment root of the Work Package.
199	pub exports_root: SegmentTreeRoot,
200	/// The number of segments exported by the Work Package.
201	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/// Execution report of a Work Package, mainly comprising the Results from the Refinement
211/// of its Work Items.
212#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
213pub struct WorkReport {
214	/// The specification of the underlying Work Package.
215	pub package_spec: WorkPackageSpec,
216	/// The context of the underlying Work Package.
217	pub context: RefineContext,
218	/// The Core index under which the Work Package was Refined to generate the Report.
219	#[codec(compact)]
220	pub core_index: CoreIndex,
221	/// The authorizer under which this Work Package got executed. For the Work Package to be
222	/// validly reported, this must appear in the Core's authorizer queue at the time of reporting.
223	pub authorizer_hash: AuthorizerHash,
224	/// The amount of gas actually used by the IsAuthorized call.
225	#[codec(compact)]
226	pub auth_gas_used: UnsignedGas,
227	/// The output of the authorizer under which this Work Package got executed.
228	pub auth_output: AuthTrace,
229	/// The segment-root lookup dictionary.
230	pub sr_lookup: VecMap<WorkPackageHash, SegmentTreeRoot>,
231	/// The results of the evaluation of the Items in the underlying Work Package.
232	pub results: BoundedVec<WorkDigest, MaxWorkItems>,
233}
234
235impl WorkReport {
236	/// Report hash.
237	pub fn hash(&self) -> WorkReportHash {
238		hash_encoded(self).into()
239	}
240
241	/// Report total gas requirement.
242	pub fn gas(&self) -> UnsignedGas {
243		self.results.iter().map(|r| r.accumulate_gas).sum()
244	}
245
246	/// Report dependencies derived from both context prerequisites and the segments root
247	/// lookup dictionary.
248	pub fn deps(&self) -> impl Iterator<Item = &WorkPackageHash> {
249		self.sr_lookup.keys().chain(self.context.prerequisites.iter())
250	}
251
252	/// Count report dependencies derived from both context prerequisites and the segments root
253	/// lookup dictionary.
254	pub fn dep_count(&self) -> usize {
255		self.sr_lookup.len() + self.context.prerequisites.len()
256	}
257
258	/// Determine if the report size is within the limit.
259	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	/// Work package hash for this report.
266	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() + // sr_lookup
279			BoundedVec::<WorkDigest, MaxWorkItems>::max_encoded_len() +
280			codec::Compact::<UnsignedGas>::max_encoded_len(); // auth_gas_used
281
282		// In the max expression above, the max_report_elective_data() bound is effectively applied
283		// separately to auth_output and item results. It actually applies to the combined size of
284		// these -- they cannot _all_ have maximum length. Account for this.
285		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(); // AuthTrace + WorkOutputs
327		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		// The largest possible work-report, or at least very close -- it might be possible to get
336		// a few bytes more by eg rebalancing the output Vec lengths, as Vec lengths use compact
337		// encoding.
338		//
339		// This is actually a bit larger than the largest valid report as both the prerequisites
340		// set and the sr_lookup map have max_dependencies() entries; the max_dependencies() limit
341		// is supposed to apply to the combined count. WorkReport::max_encoded_len() doesn't
342		// account for this though, so we don't bother accounting for it here either.
343		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		// WorkReport::max_encoded_len() assumes each "output" Vec can be the maximum length when
374		// calculating how many bytes are needed for encoding Vec lengths in the worst case. The
375		// Vecs cannot all be the maximum length though due to the shared nature of
376		// max_report_elective_data(). To account for this discrepancy, we allow
377		// WorkReport::max_encoded_len() to overshoot largest_report_size by 1 for each such Vec.
378		let max = WorkReport::max_encoded_len();
379		assert!(largest_report_size <= max);
380		assert!((largest_report_size + max_outputs) >= max);
381	}
382}