Skip to main content

jam_types/
types.rs

1use core::{
2	borrow::Borrow,
3	ops::{Add, AddAssign},
4};
5
6use crate::simple::{Hash, MaxDependencies, MaxExtrinsics, MaxImports, MaxWorkItems};
7
8use super::*;
9use simple::OpaqueBlsPublic;
10
11/// A `Wrap` implementation provides a type constructor which "wraps" a type.
12pub trait Wrap {
13	/// Wrap `T`.
14	type Wrap<T>: Borrow<T>;
15}
16
17/// No-op `Wrap`.
18// These traits are derived to avoid running into issues with Rust's poor handling of derive with
19// generic parameters; see https://github.com/rust-lang/rust/issues/26925
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct NoWrap;
22
23impl Wrap for NoWrap {
24	type Wrap<T> = T;
25}
26
27/// Plain-old-data struct of the same length and layout to `ValKeyset` struct. This does not
28/// bring in any cryptography.
29#[derive(Copy, Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen)]
30pub struct OpaqueValKeyset {
31	/// The opaque Bandersnatch public key.
32	pub bandersnatch: OpaqueBandersnatchPublic,
33	/// The opaque Ed25519 public key.
34	pub ed25519: OpaqueEd25519Public,
35	/// The opaque BLS public key.
36	pub bls: OpaqueBlsPublic,
37	/// The opaque metadata.
38	pub metadata: OpaqueValidatorMetadata,
39}
40
41impl Default for OpaqueValKeyset {
42	fn default() -> Self {
43		Self {
44			bandersnatch: OpaqueBandersnatchPublic::zero(),
45			ed25519: OpaqueEd25519Public::zero(),
46			bls: OpaqueBlsPublic::zero(),
47			metadata: OpaqueValidatorMetadata::zero(),
48		}
49	}
50}
51
52/// The opaque keys for each validator.
53pub type OpaqueValKeysets = BoundedVec<OpaqueValKeyset, MaxValCount>;
54
55/// Reference to a sequence of import segments, which when combined with an index forms a
56/// commitment to a specific segment of data.
57#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
58pub enum RootIdentifier {
59	/// Direct cryptographic commitment to the export-segments tree root.
60	Direct(SegmentTreeRoot),
61	/// Indirect reference to the export-segments tree root via a hash of the work-package which
62	/// resulted in it.
63	Indirect(WorkPackageHash),
64}
65
66impl From<SegmentTreeRoot> for RootIdentifier {
67	fn from(root: SegmentTreeRoot) -> Self {
68		Self::Direct(root)
69	}
70}
71impl From<WorkPackageHash> for RootIdentifier {
72	fn from(hash: WorkPackageHash) -> Self {
73		Self::Indirect(hash)
74	}
75}
76impl TryFrom<RootIdentifier> for SegmentTreeRoot {
77	type Error = WorkPackageHash;
78	fn try_from(root: RootIdentifier) -> Result<Self, Self::Error> {
79		match root {
80			RootIdentifier::Direct(root) => Ok(root),
81			RootIdentifier::Indirect(hash) => Err(hash),
82		}
83	}
84}
85
86/// Import segments specification, which identifies a single exported segment.
87#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
88pub struct ImportSpec {
89	/// The identifier of a series of exported segments.
90	pub root: RootIdentifier,
91	/// The index into the identified series of exported segments.
92	pub index: u16,
93}
94
95impl Encode for ImportSpec {
96	fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
97		let off = match &self.root {
98			RootIdentifier::Direct(root) => {
99				root.encode_to(dest);
100				0
101			},
102			RootIdentifier::Indirect(hash) => {
103				hash.encode_to(dest);
104				1 << 15
105			},
106		};
107		(self.index + off).encode_to(dest);
108	}
109}
110
111impl Decode for ImportSpec {
112	fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
113		let h = Hash::decode(input)?;
114		let i = u16::decode(input)?;
115		let root = if i & (1 << 15) == 0 {
116			SegmentTreeRoot::from(h).into()
117		} else {
118			WorkPackageHash::from(h).into()
119		};
120		Ok(Self { root, index: i & !(1 << 15) })
121	}
122
123	fn encoded_fixed_size() -> Option<usize> {
124		Some(core::mem::size_of::<Hash>() + core::mem::size_of::<u16>())
125	}
126}
127
128impl MaxEncodedLen for ImportSpec {
129	fn max_encoded_len() -> usize {
130		Hash::max_encoded_len() + u16::max_encoded_len()
131	}
132}
133
134/// Specification of a single piece of extrinsic data.
135#[derive(Clone, Encode, Decode, MaxEncodedLen, Debug)]
136pub struct ExtrinsicSpec {
137	/// The hash of the extrinsic data.
138	pub hash: ExtrinsicHash,
139	/// The length of the extrinsic data.
140	pub len: u32,
141}
142
143/// Sequence of [WorkItem]s, each wrapped by `W`, within a [WrappedWorkPackage] and thus limited in
144/// length to [max_work_items()].
145pub type WrappedWorkItems<W> = BoundedVec<<W as Wrap>::Wrap<WorkItem>, MaxWorkItems>;
146
147/// Sequence of [WorkItem]s within a [WorkPackage] and thus limited in length to
148/// [max_work_items()].
149pub type WorkItems = WrappedWorkItems<NoWrap>;
150
151/// A definition of work to be done by the Refinement logic of a service and transformed into a
152/// [WorkOutput] for its Accumulation logic.
153#[derive(Clone, Encode, Decode, MaxEncodedLen, Debug)]
154pub struct WorkItem {
155	/// Service identifier to which this work item relates.
156	pub service: ServiceId,
157	/// The service's code hash at the time of reporting. This must be available in-core at the
158	/// time of the lookup-anchor block.
159	pub code_hash: CodeHash,
160	/// Gas limit with which to execute this work item's Refine logic.
161	pub refine_gas_limit: UnsignedGas,
162	/// Gas limit with which to execute this work item's Accumulate logic.
163	pub accumulate_gas_limit: UnsignedGas,
164	/// Number of segments exported by this work item.
165	pub export_count: u16,
166	/// Opaque data passed in to the service's Refinement logic to describe its workload.
167	pub payload: WorkPayload,
168	/// Sequence of imported data segments.
169	pub import_segments: WorkItemImportsVec,
170	/// Additional data available to the service's Refinement logic while doing its workload.
171	pub extrinsics: BoundedVec<ExtrinsicSpec, MaxExtrinsics>,
172}
173
174impl WorkItem {
175	/// Returns the sum of the lengths of the item's extrinsics.
176	///
177	/// Returns `u32::MAX` on overflow.
178	pub fn extrinsic_size(&self) -> u32 {
179		self.extrinsics
180			.iter()
181			.map(|xt| xt.len)
182			.fold(0u32, |sum, len| sum.saturating_add(len))
183	}
184}
185
186/// A sequence of import specifications.
187pub type WorkItemImportsVec = BoundedVec<ImportSpec, MaxImports>;
188
189/// Various pieces of information helpful to contextualize the Refinement process.
190#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
191pub struct RefineContext {
192	/// The most recent header hash of the chain when building. This must be no more than
193	/// `RECENT_BLOCKS` blocks old when reported.
194	pub anchor: HeaderHash,
195	/// Must be the slot of block `anchor`. This is checked on-chain when reported.
196	pub anchor_slot: Slot,
197	/// Must be state root of block `anchor`. This is checked on-chain when reported.
198	pub state_root: StateRootHash,
199	/// Must be Beefy root of block `anchor`. This is checked on-chain when reported.
200	pub beefy_root: MmrPeakHash,
201	/// The hash of a header of a block which is final. Availability will not succeed unless a
202	/// super-majority of validators have attested to this.
203	/// Preimage `lookup`s will be judged according to this block.
204	pub lookup_anchor: HeaderHash,
205	/// The slot of `lookup_anchor` on the chain. This is checked in availability and the
206	/// report's package will not be made available without it being correct.
207	/// This value must be at least `anchor_slot + 14400`.
208	pub lookup_anchor_slot: Slot,
209	/// Must be the posterior state root of block `lookup_anchor`, i.e. the prior state root of
210	/// its child. This is checked against the ancestor set when reported.
211	pub lookup_anchor_state_root: StateRootHash,
212	/// Hashes of Work Packages, the reports of which must be reported prior to this one.
213	/// This is checked on-chain when reported.
214	pub prerequisites: VecSet<WorkPackageHash>,
215}
216
217impl MaxEncodedLen for RefineContext {
218	fn max_encoded_len() -> usize {
219		HeaderHash::max_encoded_len() + // anchor
220			Slot::max_encoded_len() + // anchor_slot
221			StateRootHash::max_encoded_len() + // state_root
222			MmrPeakHash::max_encoded_len() + // beefy_root
223			HeaderHash::max_encoded_len() + // lookup_anchor
224			Slot::max_encoded_len() + // lookup_anchor_slot
225			StateRootHash::max_encoded_len() + // lookup_anchor_state_root
226			BoundedVec::<WorkPackageHash, MaxDependencies>::max_encoded_len() // prerequisites
227	}
228}
229
230impl RefineContext {
231	#[doc(hidden)]
232	pub fn largest() -> Self {
233		Self {
234			anchor: Default::default(),
235			anchor_slot: Slot::MAX,
236			state_root: Default::default(),
237			beefy_root: Default::default(),
238			lookup_anchor: Default::default(),
239			lookup_anchor_slot: Slot::MAX,
240			lookup_anchor_state_root: Default::default(),
241			prerequisites: (0..max_dependencies()).map(|i| [i as u8; 32].into()).collect(),
242		}
243	}
244}
245
246/// A work-package, a collection of work-items together with authorization and contextual
247/// information. This is processed _in-core_ with Is-Authorized and Refine logic to produce a
248/// work-report.
249///
250/// The `context` and `items` fields are wrapped by `W`. Use [`WorkPackage`] if you want a plain
251/// work-package struct.
252#[derive(Clone, Debug)]
253pub struct WrappedWorkPackage<W: Wrap> {
254	/// Authorization token.
255	pub authorization: Authorization,
256	/// Service identifier.
257	pub auth_code_host: ServiceId,
258	/// Authorizer.
259	pub authorizer: Authorizer,
260	/// Refinement context.
261	pub context: W::Wrap<RefineContext>,
262	/// Sequence of work items.
263	pub items: W::Wrap<WrappedWorkItems<W>>,
264}
265
266impl<W: Wrap> WrappedWorkPackage<W> {
267	/// Returns the total number of extrinsics.
268	pub fn extrinsic_count(&self) -> u32 {
269		self.items
270			.borrow()
271			.iter()
272			.map(|item| item.borrow().extrinsics.len() as u32)
273			.sum()
274	}
275
276	/// Returns the sum of the lengths of the package's extrinsics.
277	///
278	/// Returns `u32::MAX` on overflow.
279	pub fn saturated_extrinsic_size(&self) -> u32 {
280		self.items
281			.borrow()
282			.iter()
283			.map(|item| item.borrow().extrinsic_size())
284			.fold(0u32, |sum, size| sum.saturating_add(size))
285	}
286
287	/// Returns an iterator over all of the import specs.
288	pub fn import_specs(&self) -> impl Iterator<Item = &ImportSpec> {
289		self.items.borrow().iter().flat_map(|item| item.borrow().import_segments.iter())
290	}
291
292	/// Returns the total number of imports.
293	pub fn import_count(&self) -> u32 {
294		self.items
295			.borrow()
296			.iter()
297			.map(|item| item.borrow().import_segments.len() as u32)
298			.sum()
299	}
300
301	/// Returns the total number of exports.
302	pub fn export_count(&self) -> u32 {
303		self.items.borrow().iter().map(|item| item.borrow().export_count as u32).sum()
304	}
305
306	/// Returns the number of dependencies, including dependencies implied by indirect imports.
307	pub fn dependency_count(&self) -> u32 {
308		let mut indirect = VecSet::new();
309		for spec in self.import_specs() {
310			if let RootIdentifier::Indirect(wph) = &spec.root {
311				indirect.insert(wph);
312			}
313		}
314		(self.context.borrow().prerequisites.len() + indirect.len()) as u32
315	}
316}
317
318impl<W: Wrap> Encode for WrappedWorkPackage<W>
319where
320	W::Wrap<RefineContext>: Encode,
321	W::Wrap<WrappedWorkItems<W>>: Encode,
322{
323	fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
324		self.auth_code_host.encode_to(dest);
325		self.authorizer.code_hash.encode_to(dest);
326		self.context.encode_to(dest);
327		self.authorization.encode_to(dest);
328		self.authorizer.config.encode_to(dest);
329		self.items.encode_to(dest);
330	}
331}
332
333impl<W: Wrap> Decode for WrappedWorkPackage<W>
334where
335	W::Wrap<RefineContext>: Decode,
336	W::Wrap<WrappedWorkItems<W>>: Decode,
337{
338	fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
339		let auth_code_host = ServiceId::decode(input)?;
340		let auth_code_hash = CodeHash::decode(input)?;
341		let context = W::Wrap::<RefineContext>::decode(input)?;
342		let authorization = Authorization::decode(input)?;
343		let auth_config = AuthConfig::decode(input)?;
344		let items = W::Wrap::<WrappedWorkItems<W>>::decode(input)?;
345		Ok(Self {
346			authorization,
347			auth_code_host,
348			authorizer: Authorizer { code_hash: auth_code_hash, config: auth_config },
349			context,
350			items,
351		})
352	}
353}
354
355impl<W: Wrap> MaxEncodedLen for WrappedWorkPackage<W>
356where
357	Self: Encode,
358{
359	/// Maximum encoded size of a work-package within the package limits: `max_input()`,
360	/// `max_imports()`, `max_extrinsics()` and `max_dependencies()`. `Decode` does not check these
361	/// limits (for example the authorization, the authorizer config and the work-item payloads are
362	/// plain `Vec<u8>`), so the bound holds only for a package that passed a check of them.
363	///
364	/// `max_input()` limits the authorization, the authorizer config, the work-item payloads, the
365	/// imported segments with their justifications and the extrinsic data together. Only the first
366	/// three are in the encoded package. The bound gives all of `max_input()` to them and also
367	/// counts `max_imports()` import specs, although each import uses part of `max_input()`. So
368	/// the bound is higher than the largest package, by the size of `max_imports()` import specs.
369	/// `max_bundle_size()` in `jam-std-common` relies on this: a bundle holds the imported
370	/// segments too.
371	fn max_encoded_len() -> usize {
372		// Each field at its own bound: a full max_input() for the authorization, the authorizer
373		// config and each work-item payload, and max_imports() import specs and max_extrinsics()
374		// extrinsic specs for each item.
375		let mut max = Authorization::max_encoded_len() +
376			ServiceId::max_encoded_len() + // auth_code_host
377			Authorizer::max_encoded_len() +
378			RefineContext::max_encoded_len() +
379			WorkItems::max_encoded_len();
380
381		// max_input() limits the authorization, the authorizer config and the work-item payloads
382		// together, with the imports and the extrinsic data, which are not in the package. So one
383		// max_input() remains for the three of them. The length prefixes keep their largest size.
384		let max_inputs = 2 + max_work_items();
385		max -= (max_inputs - 1) * (max_input() as usize);
386
387		// The sum above counts max_imports() import specs for each item, but max_imports() limits
388		// the imports of all items together. So the bound keeps max_imports() import specs
389		// in total.
390		max -= (max_work_items() - 1) * (max_imports() as usize) * ImportSpec::max_encoded_len();
391
392		// The sum above counts max_extrinsics() extrinsic specs for each item, but max_extrinsics()
393		// limits the extrinsics of all items together. So the bound keeps max_extrinsics()
394		// extrinsic specs in total.
395		max -=
396			(max_work_items() - 1) * (max_extrinsics() as usize) * ExtrinsicSpec::max_encoded_len();
397
398		max
399	}
400}
401
402/// A work-package, a collection of work-items together with authorization and contextual
403/// information. This is processed _in-core_ with Is-Authorized and Refine logic to produce a
404/// work-report.
405pub type WorkPackage = WrappedWorkPackage<NoWrap>;
406
407/// The authorizer tuple which together identifies a means of determining whether a Work Package is
408/// acceptable to execute on a core.
409#[derive(Clone, Encode, Decode, MaxEncodedLen, Debug)]
410pub struct Authorizer {
411	/// Authorization code hash.
412	pub code_hash: CodeHash,
413	/// Configuration blob for the auth logic.
414	pub config: AuthConfig,
415}
416
417impl Authorizer {
418	pub fn any() -> Self {
419		Self { code_hash: CodeHash::zero(), config: Default::default() }
420	}
421
422	pub fn with_concat<R>(&self, f: impl Fn(&[u8]) -> R) -> R {
423		f(&[&self.code_hash.0[..], &self.config[..]].concat()[..])
424	}
425
426	pub fn hash(&self, hasher: impl Fn(&[u8]) -> Hash) -> AuthorizerHash {
427		self.with_concat(hasher).into()
428	}
429}
430
431/// Potential errors encountered during the refinement of a [`WorkItem`].
432///
433/// Although additional errors may be generated internally by the PVM engine,
434/// these are the specific errors designated by the GP for the [`WorkResult`]
435/// and that are eligible to be forwarded to the accumulate process as part
436/// of the [`AccumulateItem`].
437#[derive(Clone, Encode, Decode, MaxEncodedLen, Debug, Eq, PartialEq)]
438#[doc(hidden)]
439pub enum WorkError {
440	/// Gas exhausted (∞).
441	OutOfGas = 1,
442	/// Unexpected termination (☇).
443	Panic = 2,
444	/// Invalid amount of segments exported (⊚).
445	BadExports = 3,
446	/// Work output is too big (⊖).
447	OutputOversize = 4,
448	/// Bad code for the service (`BAD`).
449	///
450	/// This may occur due to an unknown service identifier or unavailable code preimage.
451	BadCode = 5,
452	/// Out of bounds code size (`BIG`).
453	CodeOversize = 6,
454}
455
456/// Fields describing the level of activity imposed on the core to construct the `WorkResult`
457/// output.
458#[derive(Copy, Clone, Encode, Decode, MaxEncodedLen, Debug, Eq, PartialEq, Default)]
459#[doc(hidden)]
460pub struct RefineLoad {
461	/// The amount of gas actually used for this refinement.
462	#[codec(compact)]
463	pub gas_used: UnsignedGas,
464	/// The number of imports made.
465	#[codec(compact)]
466	pub imports: u16,
467	/// The number of extrinsics referenced.
468	#[codec(compact)]
469	pub extrinsic_count: u16,
470	/// The amount of data used in extrinsics.
471	#[codec(compact)]
472	pub extrinsic_size: u32,
473	/// The number of exports made.
474	#[codec(compact)]
475	pub exports: u16,
476}
477
478impl Add for RefineLoad {
479	type Output = Self;
480	fn add(self, rhs: Self) -> Self {
481		Self {
482			gas_used: self.gas_used + rhs.gas_used,
483			imports: self.imports + rhs.imports,
484			extrinsic_count: self.extrinsic_count + rhs.extrinsic_count,
485			extrinsic_size: self.extrinsic_size + rhs.extrinsic_size,
486			exports: self.exports + rhs.exports,
487		}
488	}
489}
490
491impl AddAssign for RefineLoad {
492	fn add_assign(&mut self, rhs: Self) {
493		self.gas_used += rhs.gas_used;
494		self.imports += rhs.imports;
495		self.extrinsic_count += rhs.extrinsic_count;
496		self.extrinsic_size += rhs.extrinsic_size;
497		self.exports += rhs.exports;
498	}
499}
500
501/// The result and surrounding context of a single Refinement operation passed as part of a Work
502/// Report.
503#[derive(Clone, Encode, Decode, MaxEncodedLen, Debug, Eq, PartialEq)]
504#[doc(hidden)]
505pub struct WorkDigest {
506	/// The service whose Refinement gave this result.
507	pub service: ServiceId,
508	/// The service's code hash at the time of reporting. This must be available in-core at the
509	/// time of the lookup-anchor block.
510	pub code_hash: CodeHash,
511	/// The hash of the payload data passed into Refinement which gave this result.
512	pub payload_hash: PayloadHash,
513	/// The amount of gas to be used for the accumulation of this result.
514	pub accumulate_gas: UnsignedGas,
515	/// The result of the Refinement operation itself.
516	#[codec(encoded_as = "CompactRefineResult")]
517	pub result: Result<WorkOutput, WorkError>,
518	/// Information the how much resources the refinement consumed.
519	pub refine_load: RefineLoad,
520}
521
522/// The result and surrounding context of a single Refinement operation passed in to the
523/// Accumulation logic.
524#[derive(Clone, Debug, Encode, Decode)]
525pub struct WorkItemRecord {
526	/// The hash of the work-package in which the work-item which gave this result was placed.
527	pub package: WorkPackageHash,
528	/// The root of the segment tree which was generated by the work-package in which the work-item
529	/// which gave this result was placed.
530	pub exports_root: SegmentTreeRoot,
531	/// The hash of the authorizer which authorized the execution of the work-package
532	/// which generated this result.
533	pub authorizer_hash: AuthorizerHash,
534	/// The hash of the payload data passed into Refinement which gave this result.
535	pub payload: PayloadHash,
536	/// The amount of gas provided to Accumulate by the work-item behind this result.
537	#[codec(compact)]
538	pub gas_limit: UnsignedGas,
539	/// The result of the Refinement operation itself.
540	#[codec(encoded_as = "CompactRefineResult")]
541	pub result: Result<WorkOutput, WorkError>,
542	/// The output of the Is-Authorized logic which authorized the execution of the work-package
543	/// which generated this result.
544	pub auth_output: AuthTrace,
545}
546
547/// A single deferred transfer of balance and/or data, passed in to the Accumulation logic.
548#[derive(Debug, Clone, Encode, Decode, Default)]
549pub struct TransferRecord {
550	/// The index of the service from which the transfer was made.
551	pub source: ServiceId,
552	/// The index of the service which is the target of the transfer.
553	pub destination: ServiceId,
554	/// The balance passed from the `source` service to the `destination`.
555	pub amount: Balance,
556	/// The information passed from the `source` service to the `destination`.
557	pub memo: Memo,
558	/// The gas limit with which the `destination` On Transfer logic may execute in order to
559	/// process this transfer.
560	pub gas_limit: UnsignedGas,
561}
562
563/// Accumulate item.
564#[derive(Debug, Encode, Decode)]
565pub enum AccumulateItem {
566	/// Work item record
567	WorkItem(WorkItemRecord),
568	/// Incoming transfer record
569	Transfer(TransferRecord),
570}
571
572impl From<WorkItemRecord> for AccumulateItem {
573	fn from(w: WorkItemRecord) -> Self {
574		AccumulateItem::WorkItem(w)
575	}
576}
577
578impl From<TransferRecord> for AccumulateItem {
579	fn from(t: TransferRecord) -> Self {
580		AccumulateItem::Transfer(t)
581	}
582}
583
584/// Parameters for the invocation of Accumulate.
585#[derive(Debug, Encode, Decode)]
586#[doc(hidden)]
587pub struct AccumulateParams {
588	/// The current time slot.
589	#[codec(compact)]
590	pub slot: Slot,
591	/// The index of the service being accumulated.
592	#[codec(compact)]
593	pub service_id: ServiceId,
594	/// Number of work-results or transfers to accumulate.
595	#[codec(compact)]
596	pub item_count: u32,
597}
598
599/// Parameters for the invocation of Refine.
600#[derive(Debug, Encode, Decode)]
601#[doc(hidden)]
602pub struct IsAuthorizedParams {
603	/// Core index.
604	#[codec(compact)]
605	pub core: u16,
606}
607
608/// Parameters for the invocation of Refine.
609#[derive(Debug, Encode, Decode)]
610#[doc(hidden)]
611pub struct RefineParams {
612	/// Core index.
613	#[codec(compact)]
614	pub core_index: CoreIndex,
615	/// Work-item index.
616	#[codec(compact)]
617	pub item_index: u32, // u32?
618	/// The index of the service being refined.
619	#[codec(compact)]
620	pub service_id: ServiceId,
621	/// The payload data to process.
622	pub payload: WorkPayload,
623	/// The hash of the Work Package.
624	pub package_hash: WorkPackageHash,
625}
626
627// TODO: @gav Consider moving to jam-node.
628/// Parameters for the invocation of Refine, reference variant.
629#[derive(Debug, Encode)]
630#[doc(hidden)]
631pub struct RefineParamsRef<'a> {
632	/// Core index.
633	#[codec(compact)]
634	pub core_index: CoreIndex,
635	/// Work-item index.
636	#[codec(compact)]
637	pub item_index: u32,
638	/// The index of the service being refined.
639	#[codec(compact)]
640	pub service_id: ServiceId,
641	/// The payload data to process.
642	pub payload: &'a WorkPayload,
643	/// The hash of the Work Package.
644	pub package_hash: &'a WorkPackageHash,
645}
646
647/// Information concerning a particular service's state.
648///
649/// This is used in the `service_info` host-call.
650#[derive(Debug, Clone, Encode, Decode, MaxEncodedLen)]
651pub struct ServiceInfo {
652	/// The hash of the code of the service.
653	pub code_hash: CodeHash,
654	/// The existing balance of the service.
655	pub balance: Balance,
656	/// The minimum balance which the service must satisfy.
657	pub threshold: Balance,
658	/// The minimum amount of gas which must be provided to this service's `accumulate` for each
659	/// work item it must process.
660	pub min_item_gas: UnsignedGas,
661	/// The minimum amount of gas which must be provided to this service's `accumulate` for each
662	/// incoming transfer it must process.
663	pub min_memo_gas: UnsignedGas,
664	/// The total number of bytes used for data electively held for this service on-chain.
665	pub bytes: u64,
666	/// The total number of items of data electively held for this service on-chain.
667	pub items: u32,
668	/// Offset of storage footprint only above which a minimum deposit is needed.
669	pub deposit_offset: Balance,
670	/// Creation time slot.
671	pub creation_slot: Slot,
672	/// Most recent accumulation time slot.
673	pub last_accumulation_slot: Slot,
674	/// Parent service identifier.
675	pub parent_service: ServiceId,
676}
677
678impl ServiceInfo {
679	/// Field offsets in the encoded representation
680	pub const CODE_HASH_OFFSET: usize = 0;
681	pub const BALANCE_OFFSET: usize = 32;
682	pub const THRESHOLD_OFFSET: usize = 40;
683	pub const MIN_ITEM_GAS_OFFSET: usize = 48;
684	pub const MIN_MEMO_GAS_OFFSET: usize = 56;
685	pub const BYTES_OFFSET: usize = 64;
686	pub const ITEMS_OFFSET: usize = 72;
687	pub const DEPOSIT_OFFSET_OFFSET: usize = 76;
688	pub const CREATION_SLOT_OFFSET: usize = 84;
689	pub const LAST_ACCUMULATION_SLOT_OFFSET: usize = 88;
690	pub const PARENT_SERVICE_OFFSET: usize = 92;
691	/// Encoded length in bytes.
692	pub const ENCODED_LEN: usize = 96;
693}
694
695/// Refine result used for compact encoding of work result as prescribed by GP.
696struct CompactRefineResult(Result<WorkOutput, WorkError>);
697struct CompactRefineResultRef<'a>(&'a Result<WorkOutput, WorkError>);
698
699impl From<CompactRefineResult> for Result<WorkOutput, WorkError> {
700	fn from(value: CompactRefineResult) -> Self {
701		value.0
702	}
703}
704
705impl<'a> From<&'a Result<WorkOutput, WorkError>> for CompactRefineResultRef<'a> {
706	fn from(value: &'a Result<WorkOutput, WorkError>) -> Self {
707		CompactRefineResultRef(value)
708	}
709}
710
711impl<'a> codec::EncodeAsRef<'a, Result<WorkOutput, WorkError>> for CompactRefineResult {
712	type RefType = CompactRefineResultRef<'a>;
713}
714
715impl Encode for CompactRefineResult {
716	fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
717		CompactRefineResultRef(&self.0).encode_to(dest)
718	}
719}
720
721impl MaxEncodedLen for CompactRefineResult {
722	fn max_encoded_len() -> usize {
723		(1 + WorkOutput::max_encoded_len()).max(WorkError::max_encoded_len())
724	}
725}
726
727impl Encode for CompactRefineResultRef<'_> {
728	fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
729		match &self.0 {
730			Ok(o) => {
731				dest.push_byte(0);
732				o.encode_to(dest)
733			},
734			Err(e) => e.encode_to(dest),
735		}
736	}
737}
738
739impl Decode for CompactRefineResult {
740	fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
741		match input.read_byte()? {
742			0 => Ok(Self(Ok(WorkOutput::decode(input)?))),
743			e => Ok(Self(Err(WorkError::decode(&mut &[e][..])?))),
744		}
745	}
746}
747
748macro_rules! fetch_kind {
749	($($(#[$attr:meta])* $variant:ident = $value:expr),* $(,)?) => {
750		/// Fetch host calls variants identifiers.
751		#[derive(Copy, Clone, Debug)]
752		pub enum FetchKind {
753			$(
754				$(#[$attr])*
755				$variant = $value,
756			)*
757		}
758
759		impl TryFrom<u64> for FetchKind {
760			type Error = ();
761			fn try_from(value: u64) -> Result<Self, Self::Error> {
762				match value {
763					$(
764						$value => Ok(FetchKind::$variant),
765					)*
766					_ => Err(()),
767				}
768			}
769		}
770	};
771}
772
773fetch_kind! {
774	/// Protocol stateless parameters.
775	ProtocolParameters = 0,
776	/// Entropy.
777	Entropy = 1,
778	/// Output from the parameterized authorizer code.
779	AuthTrace = 2,
780	/// A particular extrinsic of a given work-item.
781	AnyExtrinsic = 3,
782	/// A particular extrinsic of the executing work-item.
783	OurExtrinsic = 4,
784	/// A particular import-segment of a given work-item.
785	AnyImport = 5,
786	/// A particular import-segment of the executing work-item.
787	OurImport = 6,
788	/// Current work-package.
789	WorkPackage = 7,
790	/// Work package authorization config blob.
791	AuthConfig = 8,
792	/// Input provided to the parameterized authorizer code.
793	AuthToken = 9,
794	/// Refine context.
795	RefineContext = 10,
796	/// All work items information summary.
797	ItemsSummary = 11,
798	/// A particular work item information summary.
799	AnyItemSummary = 12,
800	/// A particular work item payload.
801	AnyPayload = 13,
802	/// All accumulate items.
803	AccumulateItems = 14,
804	/// A particular accumulate item.
805	AnyAccumulateItem = 15,
806}
807
808/// Work item summary information used by fetch host call.
809#[derive(Clone, Encode, Decode, Debug)]
810pub struct WorkItemSummary {
811	pub service: ServiceId,
812	pub code_hash: CodeHash,
813	pub refine_gas_limit: UnsignedGas,
814	pub accumulate_gas_limit: UnsignedGas,
815	pub export_count: u16,
816	pub import_count: u16,
817	pub extrinsics_count: u16,
818	pub payload_len: u32,
819}
820
821impl From<&WorkItem> for WorkItemSummary {
822	fn from(w: &WorkItem) -> Self {
823		WorkItemSummary {
824			service: w.service,
825			code_hash: w.code_hash,
826			refine_gas_limit: w.refine_gas_limit,
827			accumulate_gas_limit: w.accumulate_gas_limit,
828			export_count: w.export_count,
829			import_count: w.import_segments.len() as u16,
830			extrinsics_count: w.extrinsics.len() as u16,
831			payload_len: w.payload.len() as u32,
832		}
833	}
834}
835
836/// Page access mode.
837#[derive(Copy, Clone, Debug)]
838pub enum PageMode {
839	ReadOnly = 0,
840	ReadWrite = 1,
841}
842
843/// Page operation.
844#[derive(Copy, Clone, Debug)]
845pub enum PageOperation {
846	Free,
847	Alloc(PageMode),
848	SetMode(PageMode),
849}
850
851impl From<PageOperation> for u64 {
852	fn from(value: PageOperation) -> Self {
853		match value {
854			PageOperation::Free => 0,
855			PageOperation::Alloc(mode) => 1 + mode as u64,
856			PageOperation::SetMode(mode) => 3 + mode as u64,
857		}
858	}
859}
860
861impl TryFrom<u64> for PageOperation {
862	type Error = ();
863	fn try_from(operation: u64) -> Result<Self, Self::Error> {
864		match operation {
865			0 => Ok(Self::Free),
866			1 => Ok(Self::Alloc(PageMode::ReadOnly)),
867			2 => Ok(Self::Alloc(PageMode::ReadWrite)),
868			3 => Ok(Self::SetMode(PageMode::ReadOnly)),
869			4 => Ok(Self::SetMode(PageMode::ReadWrite)),
870			_ => Err(()),
871		}
872	}
873}
874
875#[cfg(test)]
876mod tests {
877	use super::*;
878	use bounded_collections::{TryCollect, bounded_vec};
879	use codec::DecodeAll;
880
881	#[test]
882	fn compact_refine_result_codec() {
883		let enc_dec = |exp_res, exp_buf: &[u8]| {
884			let buf = CompactRefineResultRef(&exp_res).encode();
885			assert_eq!(buf, exp_buf);
886			let res = CompactRefineResult::decode(&mut &buf[..]).unwrap();
887			assert_eq!(res.0, exp_res);
888		};
889
890		enc_dec(Ok(vec![1, 2, 3].into()), &[0, 3, 1, 2, 3]);
891		enc_dec(Err(WorkError::OutOfGas), &[1]);
892		enc_dec(Err(WorkError::Panic), &[2]);
893		enc_dec(Err(WorkError::BadExports), &[3]);
894		enc_dec(Err(WorkError::OutputOversize), &[4]);
895		enc_dec(Err(WorkError::BadCode), &[5]);
896		enc_dec(Err(WorkError::CodeOversize), &[6]);
897	}
898
899	#[test]
900	fn service_info_encoded_len_is_correct() {
901		assert_eq!(ServiceInfo::max_encoded_len(), ServiceInfo::ENCODED_LEN);
902	}
903
904	#[test]
905	fn service_info_item_offset_works() {
906		let default = ServiceInfo {
907			code_hash: Default::default(),
908			balance: Default::default(),
909			threshold: Default::default(),
910			min_item_gas: Default::default(),
911			min_memo_gas: Default::default(),
912			bytes: Default::default(),
913			items: Default::default(),
914			deposit_offset: Default::default(),
915			creation_slot: Default::default(),
916			last_accumulation_slot: Default::default(),
917			parent_service: Default::default(),
918		};
919
920		macro_rules! sanity_check {
921			($field:ident, $type:ident, $offset:expr, $value:expr) => {{
922				// Verify the type has a fixed encoded size
923				let type_size = core::mem::size_of::<$type>();
924				assert_eq!(type_size, $type::max_encoded_len());
925				let expected = $type::from($value);
926				let test_struct = ServiceInfo { $field: expected, ..default };
927				let buffer = test_struct.encode();
928				// Decode the field from the known offset
929				let actual: $type =
930					DecodeAll::decode_all(&mut &buffer[$offset..$offset + type_size]).unwrap();
931				assert_eq!(expected, actual);
932			}};
933			($field:ident, $type:ident, $offset:expr) => {{
934				sanity_check!($field, $type, $offset, <$type>::MAX);
935			}};
936		}
937
938		sanity_check!(code_hash, CodeHash, ServiceInfo::CODE_HASH_OFFSET, [0xff_u8; 32]);
939		sanity_check!(balance, Balance, ServiceInfo::BALANCE_OFFSET);
940		sanity_check!(threshold, Balance, ServiceInfo::THRESHOLD_OFFSET);
941		sanity_check!(min_item_gas, UnsignedGas, ServiceInfo::MIN_ITEM_GAS_OFFSET);
942		sanity_check!(min_memo_gas, UnsignedGas, ServiceInfo::MIN_MEMO_GAS_OFFSET);
943		sanity_check!(bytes, u64, ServiceInfo::BYTES_OFFSET);
944		sanity_check!(items, u32, ServiceInfo::ITEMS_OFFSET);
945		sanity_check!(deposit_offset, Balance, ServiceInfo::DEPOSIT_OFFSET_OFFSET);
946		sanity_check!(creation_slot, Slot, ServiceInfo::CREATION_SLOT_OFFSET);
947		sanity_check!(last_accumulation_slot, Slot, ServiceInfo::LAST_ACCUMULATION_SLOT_OFFSET);
948		sanity_check!(parent_service, ServiceId, ServiceInfo::PARENT_SERVICE_OFFSET);
949	}
950
951	#[test]
952	fn context_max_encoded_len() {
953		assert_eq!(RefineContext::largest().encoded_size(), RefineContext::max_encoded_len());
954	}
955
956	#[test]
957	fn package_max_encoded_len() {
958		let portion = |total, i, num| (total / num) + if i == 0 { total % num } else { 0 };
959
960		let max_inputs = 2 + max_work_items();
961		let input = |i| vec![0; portion(max_input() as usize, i, max_inputs)];
962
963		let imports = |i| {
964			bounded_vec![
965				ImportSpec { root: RootIdentifier::Direct(Default::default()), index: u16::MAX };
966				portion(max_imports() as usize, i, max_work_items())
967			]
968		};
969
970		let extrinsics = |i| {
971			bounded_vec![
972				ExtrinsicSpec { hash: Default::default(), len: u32::MAX };
973				portion(max_extrinsics() as usize, i, max_work_items())
974			]
975		};
976
977		// The largest possible work-package, or at least very close -- it might be possible to get
978		// a few bytes more by eg rebalancing the input Vec lengths, as Vec lengths use compact
979		// encoding.
980		//
981		// "Largest possible work-package" is actually a bit of a lie -- it isn't really possible
982		// for a work-package to be this large as the total input size would exceed max_input()
983		// after accounting for the import segments. WorkPackage::max_encoded_len() does _not_
984		// account for these though, and we currently assume this accounting is lacking in the
985		// max_bundle_size() implementation in jam-std-common. Hence the construction of an
986		// invalidly large work-package here.
987		let largest_package = WorkPackage {
988			authorization: input(0).into(),
989			auth_code_host: ServiceId::MAX,
990			authorizer: Authorizer { code_hash: Default::default(), config: input(1).into() },
991			context: RefineContext::largest(),
992			items: (0..max_work_items())
993				.map(|i| WorkItem {
994					service: ServiceId::MAX,
995					code_hash: Default::default(),
996					payload: input(2 + i).into(),
997					refine_gas_limit: UnsignedGas::MAX,
998					accumulate_gas_limit: UnsignedGas::MAX,
999					import_segments: imports(i),
1000					extrinsics: extrinsics(i),
1001					export_count: u16::MAX,
1002				})
1003				.try_collect()
1004				.unwrap(),
1005		};
1006		assert_eq!(largest_package.import_count(), max_imports());
1007		assert_eq!(largest_package.extrinsic_count(), max_extrinsics());
1008		let largest_package_size = largest_package.encoded_size();
1009
1010		// WorkPackage::max_encoded_len() assumes each input/import-spec/extrinsic-spec Vec can be
1011		// the maximum length when calculating how many bytes are needed for encoding Vec lengths
1012		// in the worst case. The Vecs cannot all be the maximum length though due to the shared
1013		// nature of max_input()/max_imports()/etc. To account for this discrepancy, we allow
1014		// WorkPackage::max_encoded_len() to overshoot largest_package_size by 1 for each such Vec.
1015		let max = WorkPackage::max_encoded_len();
1016		assert!(largest_package_size <= max);
1017		assert!((largest_package_size + max_inputs + (2 * max_work_items())) >= max);
1018	}
1019}