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