jam_types/
types.rs

1use core::ops::{Add, AddAssign};
2
3use crate::simple::{MaxImports, MaxWorkItems};
4
5use super::*;
6use simple::OpaqueBlsPublic;
7
8/// Plain-old-data struct of the same length and layout to `ValKeyset` struct. This does not
9/// bring in any cryptography.
10#[derive(Copy, Clone, Encode, Decode, Debug, Eq, PartialEq)]
11pub struct OpaqueValKeyset {
12	/// The opaque Ed25519 public key.
13	pub ed25519: OpaqueEd25519Public,
14	/// The opaque Bandersnatch public key.
15	pub bandersnatch: OpaqueBandersnatchPublic,
16	/// The opaque BLS public key.
17	pub bls: OpaqueBlsPublic,
18	/// The opaque metadata.
19	pub metadata: OpaqueValidatorMetadata,
20}
21impl Default for OpaqueValKeyset {
22	fn default() -> Self {
23		Self {
24			ed25519: OpaqueEd25519Public::zero(),
25			bandersnatch: OpaqueBandersnatchPublic::zero(),
26			bls: OpaqueBlsPublic::zero(),
27			metadata: OpaqueValidatorMetadata::zero(),
28		}
29	}
30}
31
32/// The opaque keys for each validator.
33pub type OpaqueValKeysets = FixedVec<OpaqueValKeyset, ValCount>;
34
35/// Reference to a sequence of import segments, which when combined with an index forms a
36/// commitment to a specific segment of data.
37#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
38pub enum RootIdentifier {
39	/// Direct cryptographic commitment to the export-segments tree root.
40	Direct(SegmentTreeRoot),
41	/// Indirect reference to the export-segments tree root via a hash of the work-package which
42	/// resulted in it.
43	Indirect(WorkPackageHash),
44}
45
46impl From<SegmentTreeRoot> for RootIdentifier {
47	fn from(root: SegmentTreeRoot) -> Self {
48		Self::Direct(root)
49	}
50}
51impl From<WorkPackageHash> for RootIdentifier {
52	fn from(hash: WorkPackageHash) -> Self {
53		Self::Indirect(hash)
54	}
55}
56impl TryFrom<RootIdentifier> for SegmentTreeRoot {
57	type Error = WorkPackageHash;
58	fn try_from(root: RootIdentifier) -> Result<Self, Self::Error> {
59		match root {
60			RootIdentifier::Direct(root) => Ok(root),
61			RootIdentifier::Indirect(hash) => Err(hash),
62		}
63	}
64}
65
66/// Import segments specification, which identifies a single exported segment.
67#[derive(Clone, Debug, Eq, PartialEq, Hash)]
68pub struct ImportSpec {
69	/// The identifier of a series of exported segments.
70	pub root: RootIdentifier,
71	/// The index into the identified series of exported segments.
72	pub index: u16,
73}
74
75impl Encode for ImportSpec {
76	fn encode_to<T: scale::Output + ?Sized>(&self, dest: &mut T) {
77		let off = match &self.root {
78			RootIdentifier::Direct(root) => {
79				root.encode_to(dest);
80				0
81			},
82			RootIdentifier::Indirect(hash) => {
83				hash.encode_to(dest);
84				1 << 15
85			},
86		};
87		(self.index + off).encode_to(dest);
88	}
89}
90
91impl Decode for ImportSpec {
92	fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
93		let h = Hash::decode(input)?;
94		let i = u16::decode(input)?;
95		let root = if i & (1 << 15) == 0 {
96			SegmentTreeRoot::from(h).into()
97		} else {
98			WorkPackageHash::from(h).into()
99		};
100		Ok(Self { root, index: i & !(1 << 15) })
101	}
102}
103
104/// Specification of a single piece of extrinsic data.
105#[derive(Clone, Encode, Decode, Debug)]
106pub struct ExtrinsicSpec {
107	/// The hash of the extrinsic data.
108	pub hash: ExtrinsicHash,
109	/// The length of the extrinsic data.
110	pub len: u32,
111}
112
113/// Sequence of [WorkItem]s within a [WorkPackage] and thus limited in length to [max_work_items()].
114pub type WorkItems = BoundedVec<WorkItem, MaxWorkItems>;
115
116/// A definition of work to be done by the Refinement logic of a service and transformed into a
117/// [WorkOutput] for its Accumulation logic.
118#[derive(Clone, Encode, Decode, Debug)]
119pub struct WorkItem {
120	/// Service identifier to which this work item relates.
121	pub service: ServiceId,
122	/// The service's code hash at the time of reporting. This must be available in-core at the
123	/// time of the lookup-anchor block.
124	pub code_hash: CodeHash,
125	/// Opaque data passed in to the service's Refinement logic to describe its workload.
126	pub payload: WorkPayload,
127	/// Gas limit with which to execute this work item's Refine logic.
128	pub refine_gas_limit: UnsignedGas,
129	/// Gas limit with which to execute this work item's Accumulate logic.
130	pub accumulate_gas_limit: UnsignedGas,
131	/// Sequence of imported data segments.
132	pub import_segments: WorkItemImportsVec,
133	/// Additional data available to the service's Refinement logic while doing its workload.
134	pub extrinsics: Vec<ExtrinsicSpec>,
135	/// Number of segments exported by this work item.
136	pub export_count: u16,
137}
138
139/// A sequence of import specifications.
140pub type WorkItemImportsVec = BoundedVec<ImportSpec, MaxImports>;
141
142/// Various pieces of information helpful to contextualize the Refinement process.
143#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
144pub struct RefineContext {
145	/// The most recent header hash of the chain when building. This must be no more than
146	/// `RECENT_BLOCKS` blocks old when reported.
147	pub anchor: HeaderHash,
148	/// Must be state root of block `anchor`. This is checked on-chain when reported.
149	pub state_root: StateRootHash,
150	/// Must be Beefy root of block `anchor`. This is checked on-chain when reported.
151	pub beefy_root: MmrPeakHash,
152	/// The hash of a header of a block which is final. Availability will not succeed unless a
153	/// super-majority of validators have attested to this.
154	/// Preimage `lookup`s will be judged according to this block.
155	///
156	/// NOTE: Storage pallet may not cycle more frequently than 48 hours (24 hours above
157	///   plus 24 hours dispute period).
158	pub lookup_anchor: HeaderHash,
159	/// The slot of `lookup_anchor` on the chain. This is checked in availability and the
160	/// report's package will not be made available without it being correct.
161	/// This value must be at least `anchor_slot + 14400`.
162	pub lookup_anchor_slot: Slot,
163	/// Hashes of Work Packages, the reports of which must be reported prior to this one.
164	/// This is checked on-chain when reported.
165	pub prerequisites: VecSet<WorkPackageHash>,
166}
167
168/// A work-package, a collection of work-items together with authorization and contextual
169/// information. This is processed _in-core_ with Is-Authorized and Refine logic to produce a
170/// work-report.
171#[derive(Clone, Encode, Decode, Debug)]
172pub struct WorkPackage {
173	/// Authorization token.
174	pub authorization: Authorization,
175	/// Service identifier.
176	pub auth_code_host: ServiceId,
177	/// Authorizer.
178	pub authorizer: Authorizer,
179	/// Refinement context.
180	pub context: RefineContext,
181	/// Sequence of work items.
182	pub items: WorkItems,
183}
184
185/// The authorizer tuple which together identifies a means of determining whether a Work Package is
186/// acceptable to execute on a core.
187#[derive(Clone, Encode, Decode, Debug)]
188pub struct Authorizer {
189	/// Authorization code hash.
190	pub code_hash: CodeHash,
191	/// Parameters blob.
192	pub param: AuthParam,
193}
194
195impl Authorizer {
196	pub fn any() -> Self {
197		Self { code_hash: CodeHash::zero(), param: Default::default() }
198	}
199
200	pub fn with_concat<R>(&self, f: impl Fn(&[u8]) -> R) -> R {
201		f(&[&self.code_hash.0[..], &self.param[..]].concat()[..])
202	}
203}
204
205/// Potential errors encountered during the refinement of a [`WorkItem`].
206///
207/// Although additional errors may be generated internally by the PVM engine,
208/// these are the specific errors designated by the GP for the [`WorkResult`]
209/// and that are eligible to be forwarded to the accumulate process as part
210/// of the [`AccumulateItem`].
211#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
212#[doc(hidden)]
213pub enum WorkError {
214	/// Gas exhausted (∞).
215	OutOfGas = 1,
216	/// Unexpected termination (☇).
217	Panic = 2,
218	/// Invalid amount of segments exported.
219	BadExports = 3,
220	/// Bad code for the service (`BAD`).
221	///
222	/// This may occur due to an unknown service identifier or unavailable code preimage.
223	BadCode = 4,
224	/// Out of bounds code size (`BIG`).
225	CodeOversize = 5,
226}
227
228/// Fields describing the level of activity imposed on the core to construct the `WorkResult`
229/// output.
230#[derive(Copy, Clone, Encode, Decode, Debug, Eq, PartialEq, Default)]
231#[doc(hidden)]
232pub struct RefineLoad {
233	/// The amount of gas actually used for this refinement.
234	#[codec(compact)]
235	pub gas_used: UnsignedGas,
236	/// The number of imports made.
237	#[codec(compact)]
238	pub imports: u16,
239	/// The number of extrinsics referenced.
240	#[codec(compact)]
241	pub extrinsic_count: u16,
242	/// The amount of data used in extrinsics.
243	#[codec(compact)]
244	pub extrinsic_size: u32,
245	/// The number of exports made.
246	#[codec(compact)]
247	pub exports: u16,
248}
249
250impl Add for RefineLoad {
251	type Output = Self;
252	fn add(self, rhs: Self) -> Self {
253		Self {
254			gas_used: self.gas_used + rhs.gas_used,
255			imports: self.imports + rhs.imports,
256			extrinsic_count: self.extrinsic_count + rhs.extrinsic_count,
257			extrinsic_size: self.extrinsic_size + rhs.extrinsic_size,
258			exports: self.exports + rhs.exports,
259		}
260	}
261}
262
263impl AddAssign for RefineLoad {
264	fn add_assign(&mut self, rhs: Self) {
265		self.gas_used += rhs.gas_used;
266		self.imports += rhs.imports;
267		self.extrinsic_count += rhs.extrinsic_count;
268		self.extrinsic_size += rhs.extrinsic_size;
269		self.exports += rhs.exports;
270	}
271}
272
273/// The result and surrounding context of a single Refinement operation passed as part of a Work
274/// Report.
275#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
276#[doc(hidden)]
277pub struct WorkResult {
278	/// The service whose Refinement gave this result.
279	pub service: ServiceId,
280	/// The service's code hash at the time of reporting. This must be available in-core at the
281	/// time of the lookup-anchor block.
282	pub code_hash: CodeHash,
283	/// The hash of the payload data passed into Refinement which gave this result.
284	pub payload_hash: PayloadHash,
285	/// The amount of gas to be used for the accumulation of this result.
286	pub accumulate_gas: UnsignedGas,
287	/// The result of the Refinement operation itself.
288	#[codec(encoded_as = "CompactRefineResult")]
289	pub result: Result<WorkOutput, WorkError>,
290	/// Information the how much resources the refinement consumed.
291	pub refine_load: RefineLoad,
292}
293
294/// The result and surrounding context of a single Refinement operation passed in to the
295/// Accumulation logic.
296#[derive(Debug, Encode, Decode)]
297pub struct AccumulateItem {
298	/// The hash of the work-package in which the work-item which gave this result was placed.
299	pub package: WorkPackageHash,
300	/// The root of the segment tree which was generated by the work-package in which the work-item
301	/// which gave this result was placed.
302	pub exports_root: SegmentTreeRoot,
303	/// The hash of the authorizer which authorized the execution of the work-package
304	/// which generated this result.
305	pub authorizer_hash: AuthorizerHash,
306	/// The output of the Is-Authorized logic which authorized the execution of the work-package
307	/// which generated this result.
308	pub auth_output: AuthOutput,
309	/// The hash of the payload data passed into Refinement which gave this result.
310	pub payload: PayloadHash,
311	/// The result of the Refinement operation itself.
312	#[codec(encoded_as = "CompactRefineResult")]
313	pub result: Result<WorkOutput, WorkError>,
314}
315
316/// Parameters for the invocation of Accumulate.
317#[derive(Debug, Encode, Decode)]
318#[doc(hidden)]
319pub struct AccumulateParams {
320	/// The current time slot.
321	pub slot: Slot,
322	/// The index of the service being accumulated.
323	pub id: ServiceId,
324	/// A sequence of work-results to accumulate.
325	pub results: Vec<AccumulateItem>,
326}
327
328/// A single deferred transfer of balance and/or data, passed in to the invocation of On Transfer.
329#[derive(Debug, Clone, Encode, Decode, Default)]
330pub struct TransferRecord {
331	/// The index of the service from which the transfer was made.
332	pub source: ServiceId,
333	/// The index of the service which is the target of the transfer.
334	pub destination: ServiceId,
335	/// The balance passed from the `source` service to the `destination`.
336	pub amount: Balance,
337	/// The information passed from the `source` service to the `destination`.
338	pub memo: Memo,
339	/// The gas limit with which the `destination` On Transfer logic may execute in order to
340	/// process this transfer.
341	pub gas_limit: UnsignedGas,
342}
343
344/// Parameters for the invocation of On Transfer.
345#[derive(Debug, Encode, Decode)]
346#[doc(hidden)]
347pub struct OnTransferParams {
348	/// The current time slot.
349	pub slot: Slot,
350	/// The index of the service to which the transfers are being made.
351	pub id: ServiceId,
352	/// The sequence of transfers to be processed.
353	pub transfers: Vec<TransferRecord>,
354}
355
356/// Parameters for the invocation of Refine.
357#[derive(Debug, Encode, Decode)]
358#[doc(hidden)]
359pub struct RefineParams {
360	/// The index of the service being refined.
361	pub id: ServiceId,
362	/// The payload data to process.
363	pub payload: WorkPayload,
364	/// The hash of the Work Package.
365	pub package_hash: WorkPackageHash,
366	/// Various pieces of contextual information for the Refinement process.
367	pub context: RefineContext,
368	/// The hash of the code of the authorizer which was used to authorize the Work Package.
369	pub auth_code_hash: CodeHash,
370}
371
372// TODO: @gav Consider moving to jam-node.
373/// Parameters for the invocation of Refine, reference variant.
374#[derive(Debug, Encode)]
375#[doc(hidden)]
376pub struct RefineParamsRef<'a> {
377	/// The index of the service being refined.
378	pub id: ServiceId,
379	/// The payload data to process.
380	pub payload: &'a WorkPayload,
381	/// The hash of the Work Package.
382	pub package_hash: &'a WorkPackageHash,
383	/// Various pieces of contextual information for the Refinement process.
384	pub context: &'a RefineContext,
385	/// The hash of the code of the authorizer which was used to authorize the Work Package.
386	pub auth_code_hash: &'a CodeHash,
387}
388
389// TODO: @gav Consider moving to jam-node.
390/// Parameters for the invocation of On Transfer, reference variant.
391#[derive(Debug, Encode)]
392#[doc(hidden)]
393pub struct OnTransferParamsRef<'a> {
394	/// The current time slot.
395	pub slot: Slot,
396	/// The index of the service to which the transfers are being made.
397	pub id: ServiceId,
398	/// The sequence of transfers to be processed.
399	pub transfers: &'a [TransferRecord],
400}
401
402/// Information concerning a particular service's state.
403///
404/// This is used in the `service_info` host-call.
405#[derive(Debug, Clone, Encode, Decode, MaxEncodedLen)]
406pub struct ServiceInfo {
407	/// The hash of the code of the service.
408	pub code_hash: CodeHash,
409	/// The existing balance of the service.
410	pub balance: Balance,
411	/// The minimum balance which the service must satisfy.
412	pub threshold: Balance,
413	/// The minimum amount of gas which must be provided to this service's `accumulate` for each
414	/// work item it must process.
415	pub min_item_gas: UnsignedGas,
416	/// The minimum amount of gas which must be provided to this service's `on_transfer` for each
417	/// memo (i.e. transfer receipt) it must process.
418	pub min_memo_gas: UnsignedGas,
419	/// The total number of bytes used for data electively held for this service on-chain.
420	pub bytes: u64,
421	/// The total number of items of data electively held for this service on-chain.
422	pub items: u32,
423}
424
425impl scale::ConstEncodedLen for ServiceInfo {}
426/// Refine result used for compact encoding of work result as prescribed by GP.
427struct CompactRefineResult(Result<WorkOutput, WorkError>);
428struct CompactRefineResultRef<'a>(&'a Result<WorkOutput, WorkError>);
429
430impl From<CompactRefineResult> for Result<WorkOutput, WorkError> {
431	fn from(value: CompactRefineResult) -> Self {
432		value.0
433	}
434}
435
436impl<'a> From<&'a Result<WorkOutput, WorkError>> for CompactRefineResultRef<'a> {
437	fn from(value: &'a Result<WorkOutput, WorkError>) -> Self {
438		CompactRefineResultRef(value)
439	}
440}
441
442impl<'a> scale::EncodeAsRef<'a, Result<WorkOutput, WorkError>> for CompactRefineResult {
443	type RefType = CompactRefineResultRef<'a>;
444}
445
446impl Encode for CompactRefineResult {
447	fn encode_to<T: scale::Output + ?Sized>(&self, dest: &mut T) {
448		CompactRefineResultRef(&self.0).encode_to(dest)
449	}
450}
451
452impl Encode for CompactRefineResultRef<'_> {
453	fn encode_to<T: scale::Output + ?Sized>(&self, dest: &mut T) {
454		match &self.0 {
455			Ok(o) => {
456				dest.push_byte(0);
457				o.encode_to(dest)
458			},
459			Err(e) => e.encode_to(dest),
460		}
461	}
462}
463
464impl Decode for CompactRefineResult {
465	fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
466		match input.read_byte()? {
467			0 => Ok(Self(Ok(WorkOutput::decode(input)?))),
468			e => Ok(Self(Err(WorkError::decode(&mut &[e][..])?))),
469		}
470	}
471}
472
473#[cfg(test)]
474mod tests {
475	use super::*;
476
477	#[test]
478	fn compact_refine_result_codec() {
479		let enc_dec = |exp_res, exp_buf: &[u8]| {
480			let buf = CompactRefineResultRef(&exp_res).encode();
481			assert_eq!(buf, exp_buf);
482			let res = CompactRefineResult::decode(&mut &buf[..]).unwrap();
483			assert_eq!(res.0, exp_res);
484		};
485
486		enc_dec(Ok(vec![1, 2, 3].into()), &[0, 3, 1, 2, 3]);
487		enc_dec(Err(WorkError::OutOfGas), &[1]);
488		enc_dec(Err(WorkError::Panic), &[2]);
489		enc_dec(Err(WorkError::BadExports), &[3]);
490		enc_dec(Err(WorkError::BadCode), &[4]);
491		enc_dec(Err(WorkError::CodeOversize), &[5]);
492	}
493}