use core::{
borrow::Borrow,
ops::{Add, AddAssign},
};
use crate::simple::{Hash, MaxDependencies, MaxExtrinsics, MaxImports, MaxWorkItems};
use super::*;
use simple::OpaqueBlsPublic;
pub trait Wrap {
type Wrap<T>: Borrow<T>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NoWrap;
impl Wrap for NoWrap {
type Wrap<T> = T;
}
#[derive(Copy, Clone, Encode, Decode, Debug, Eq, PartialEq)]
pub struct OpaqueValKeyset {
pub bandersnatch: OpaqueBandersnatchPublic,
pub ed25519: OpaqueEd25519Public,
pub bls: OpaqueBlsPublic,
pub metadata: OpaqueValidatorMetadata,
}
impl Default for OpaqueValKeyset {
fn default() -> Self {
Self {
bandersnatch: OpaqueBandersnatchPublic::zero(),
ed25519: OpaqueEd25519Public::zero(),
bls: OpaqueBlsPublic::zero(),
metadata: OpaqueValidatorMetadata::zero(),
}
}
}
pub type OpaqueValKeysets = FixedVec<OpaqueValKeyset, ValCount>;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum RootIdentifier {
Direct(SegmentTreeRoot),
Indirect(WorkPackageHash),
}
impl From<SegmentTreeRoot> for RootIdentifier {
fn from(root: SegmentTreeRoot) -> Self {
Self::Direct(root)
}
}
impl From<WorkPackageHash> for RootIdentifier {
fn from(hash: WorkPackageHash) -> Self {
Self::Indirect(hash)
}
}
impl TryFrom<RootIdentifier> for SegmentTreeRoot {
type Error = WorkPackageHash;
fn try_from(root: RootIdentifier) -> Result<Self, Self::Error> {
match root {
RootIdentifier::Direct(root) => Ok(root),
RootIdentifier::Indirect(hash) => Err(hash),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct ImportSpec {
pub root: RootIdentifier,
pub index: u16,
}
impl Encode for ImportSpec {
fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
let off = match &self.root {
RootIdentifier::Direct(root) => {
root.encode_to(dest);
0
},
RootIdentifier::Indirect(hash) => {
hash.encode_to(dest);
1 << 15
},
};
(self.index + off).encode_to(dest);
}
}
impl Decode for ImportSpec {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
let h = Hash::decode(input)?;
let i = u16::decode(input)?;
let root = if i & (1 << 15) == 0 {
SegmentTreeRoot::from(h).into()
} else {
WorkPackageHash::from(h).into()
};
Ok(Self { root, index: i & !(1 << 15) })
}
fn encoded_fixed_size() -> Option<usize> {
Some(core::mem::size_of::<Hash>() + core::mem::size_of::<u16>())
}
}
impl MaxEncodedLen for ImportSpec {
fn max_encoded_len() -> usize {
Hash::max_encoded_len() + u16::max_encoded_len()
}
}
#[derive(Clone, Encode, Decode, MaxEncodedLen, Debug)]
pub struct ExtrinsicSpec {
pub hash: ExtrinsicHash,
pub len: u32,
}
pub type WrappedWorkItems<W> = BoundedVec<<W as Wrap>::Wrap<WorkItem>, MaxWorkItems>;
pub type WorkItems = WrappedWorkItems<NoWrap>;
#[derive(Clone, Encode, Decode, MaxEncodedLen, Debug)]
pub struct WorkItem {
pub service: ServiceId,
pub code_hash: CodeHash,
pub refine_gas_limit: UnsignedGas,
pub accumulate_gas_limit: UnsignedGas,
pub export_count: u16,
pub payload: WorkPayload,
pub import_segments: WorkItemImportsVec,
pub extrinsics: BoundedVec<ExtrinsicSpec, MaxExtrinsics>,
}
impl WorkItem {
pub fn extrinsic_size(&self) -> u32 {
self.extrinsics
.iter()
.map(|xt| xt.len)
.fold(0u32, |sum, len| sum.saturating_add(len))
}
}
pub type WorkItemImportsVec = BoundedVec<ImportSpec, MaxImports>;
#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
pub struct RefineContext {
pub anchor: HeaderHash,
pub state_root: StateRootHash,
pub beefy_root: MmrPeakHash,
pub lookup_anchor: HeaderHash,
pub lookup_anchor_slot: Slot,
pub prerequisites: VecSet<WorkPackageHash>,
}
impl MaxEncodedLen for RefineContext {
fn max_encoded_len() -> usize {
HeaderHash::max_encoded_len() + StateRootHash::max_encoded_len() + MmrPeakHash::max_encoded_len() + HeaderHash::max_encoded_len() + Slot::max_encoded_len() + BoundedVec::<WorkPackageHash, MaxDependencies>::max_encoded_len() }
}
impl RefineContext {
#[doc(hidden)]
pub fn largest() -> Self {
Self {
anchor: Default::default(),
state_root: Default::default(),
beefy_root: Default::default(),
lookup_anchor: Default::default(),
lookup_anchor_slot: Slot::MAX,
prerequisites: (0..max_dependencies()).map(|i| [i as u8; 32].into()).collect(),
}
}
}
#[derive(Clone, Debug)]
pub struct WrappedWorkPackage<W: Wrap> {
pub authorization: Authorization,
pub auth_code_host: ServiceId,
pub authorizer: Authorizer,
pub context: W::Wrap<RefineContext>,
pub items: W::Wrap<WrappedWorkItems<W>>,
}
impl<W: Wrap> WrappedWorkPackage<W> {
pub fn extrinsic_count(&self) -> u32 {
self.items
.borrow()
.iter()
.map(|item| item.borrow().extrinsics.len() as u32)
.sum()
}
pub fn saturated_extrinsic_size(&self) -> u32 {
self.items
.borrow()
.iter()
.map(|item| item.borrow().extrinsic_size())
.fold(0u32, |sum, size| sum.saturating_add(size))
}
pub fn import_specs(&self) -> impl Iterator<Item = &ImportSpec> {
self.items.borrow().iter().flat_map(|item| item.borrow().import_segments.iter())
}
pub fn import_count(&self) -> u32 {
self.items
.borrow()
.iter()
.map(|item| item.borrow().import_segments.len() as u32)
.sum()
}
pub fn export_count(&self) -> u32 {
self.items.borrow().iter().map(|item| item.borrow().export_count as u32).sum()
}
pub fn dependency_count(&self) -> u32 {
let mut indirect = VecSet::new();
for spec in self.import_specs() {
if let RootIdentifier::Indirect(wph) = &spec.root {
indirect.insert(wph);
}
}
(self.context.borrow().prerequisites.len() + indirect.len()) as u32
}
}
impl<W: Wrap> Encode for WrappedWorkPackage<W>
where
W::Wrap<RefineContext>: Encode,
W::Wrap<WrappedWorkItems<W>>: Encode,
{
fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
self.auth_code_host.encode_to(dest);
self.authorizer.code_hash.encode_to(dest);
self.context.encode_to(dest);
self.authorization.encode_to(dest);
self.authorizer.config.encode_to(dest);
self.items.encode_to(dest);
}
}
impl<W: Wrap> Decode for WrappedWorkPackage<W>
where
W::Wrap<RefineContext>: Decode,
W::Wrap<WrappedWorkItems<W>>: Decode,
{
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
let auth_code_host = ServiceId::decode(input)?;
let auth_code_hash = CodeHash::decode(input)?;
let context = W::Wrap::<RefineContext>::decode(input)?;
let authorization = Authorization::decode(input)?;
let auth_config = AuthConfig::decode(input)?;
let items = W::Wrap::<WrappedWorkItems<W>>::decode(input)?;
Ok(Self {
authorization,
auth_code_host,
authorizer: Authorizer { code_hash: auth_code_hash, config: auth_config },
context,
items,
})
}
}
impl<W: Wrap> MaxEncodedLen for WrappedWorkPackage<W>
where
Self: Encode,
{
fn max_encoded_len() -> usize {
let mut max = Authorization::max_encoded_len() +
ServiceId::max_encoded_len() + Authorizer::max_encoded_len() +
RefineContext::max_encoded_len() +
WorkItems::max_encoded_len();
let max_inputs = 2 + max_work_items();
max -= (max_inputs - 1) * (max_input() as usize);
max -= (max_work_items() - 1) * (max_imports() as usize) * ImportSpec::max_encoded_len();
max -=
(max_work_items() - 1) * (max_extrinsics() as usize) * ExtrinsicSpec::max_encoded_len();
max
}
}
pub type WorkPackage = WrappedWorkPackage<NoWrap>;
#[derive(Clone, Encode, Decode, MaxEncodedLen, Debug)]
pub struct Authorizer {
pub code_hash: CodeHash,
pub config: AuthConfig,
}
impl Authorizer {
pub fn any() -> Self {
Self { code_hash: CodeHash::zero(), config: Default::default() }
}
pub fn with_concat<R>(&self, f: impl Fn(&[u8]) -> R) -> R {
f(&[&self.code_hash.0[..], &self.config[..]].concat()[..])
}
pub fn hash(&self, hasher: impl Fn(&[u8]) -> Hash) -> AuthorizerHash {
self.with_concat(hasher).into()
}
}
#[derive(Clone, Encode, Decode, MaxEncodedLen, Debug, Eq, PartialEq)]
#[doc(hidden)]
pub enum WorkError {
OutOfGas = 1,
Panic = 2,
BadExports = 3,
OutputOversize = 4,
BadCode = 5,
CodeOversize = 6,
}
#[derive(Copy, Clone, Encode, Decode, MaxEncodedLen, Debug, Eq, PartialEq, Default)]
#[doc(hidden)]
pub struct RefineLoad {
#[codec(compact)]
pub gas_used: UnsignedGas,
#[codec(compact)]
pub imports: u16,
#[codec(compact)]
pub extrinsic_count: u16,
#[codec(compact)]
pub extrinsic_size: u32,
#[codec(compact)]
pub exports: u16,
}
impl Add for RefineLoad {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
gas_used: self.gas_used + rhs.gas_used,
imports: self.imports + rhs.imports,
extrinsic_count: self.extrinsic_count + rhs.extrinsic_count,
extrinsic_size: self.extrinsic_size + rhs.extrinsic_size,
exports: self.exports + rhs.exports,
}
}
}
impl AddAssign for RefineLoad {
fn add_assign(&mut self, rhs: Self) {
self.gas_used += rhs.gas_used;
self.imports += rhs.imports;
self.extrinsic_count += rhs.extrinsic_count;
self.extrinsic_size += rhs.extrinsic_size;
self.exports += rhs.exports;
}
}
#[derive(Clone, Encode, Decode, MaxEncodedLen, Debug, Eq, PartialEq)]
#[doc(hidden)]
pub struct WorkDigest {
pub service: ServiceId,
pub code_hash: CodeHash,
pub payload_hash: PayloadHash,
pub accumulate_gas: UnsignedGas,
#[codec(encoded_as = "CompactRefineResult")]
pub result: Result<WorkOutput, WorkError>,
pub refine_load: RefineLoad,
}
#[derive(Clone, Debug, Encode, Decode)]
pub struct WorkItemRecord {
pub package: WorkPackageHash,
pub exports_root: SegmentTreeRoot,
pub authorizer_hash: AuthorizerHash,
pub payload: PayloadHash,
#[codec(compact)]
pub gas_limit: UnsignedGas,
#[codec(encoded_as = "CompactRefineResult")]
pub result: Result<WorkOutput, WorkError>,
pub auth_output: AuthTrace,
}
#[derive(Debug, Clone, Encode, Decode, Default)]
pub struct TransferRecord {
pub source: ServiceId,
pub destination: ServiceId,
pub amount: Balance,
pub memo: Memo,
pub gas_limit: UnsignedGas,
}
#[derive(Debug, Encode, Decode)]
pub enum AccumulateItem {
WorkItem(WorkItemRecord),
Transfer(TransferRecord),
}
impl From<WorkItemRecord> for AccumulateItem {
fn from(w: WorkItemRecord) -> Self {
AccumulateItem::WorkItem(w)
}
}
impl From<TransferRecord> for AccumulateItem {
fn from(t: TransferRecord) -> Self {
AccumulateItem::Transfer(t)
}
}
#[derive(Debug, Encode, Decode)]
#[doc(hidden)]
pub struct AccumulateParams {
#[codec(compact)]
pub slot: Slot,
#[codec(compact)]
pub service_id: ServiceId,
#[codec(compact)]
pub item_count: u32,
}
#[derive(Debug, Encode, Decode)]
#[doc(hidden)]
pub struct IsAuthorizedParams {
#[codec(compact)]
pub core: u16,
}
#[derive(Debug, Encode, Decode)]
#[doc(hidden)]
pub struct RefineParams {
#[codec(compact)]
pub core_index: CoreIndex,
#[codec(compact)]
pub item_index: u32, #[codec(compact)]
pub service_id: ServiceId,
pub payload: WorkPayload,
pub package_hash: WorkPackageHash,
}
#[derive(Debug, Encode)]
#[doc(hidden)]
pub struct RefineParamsRef<'a> {
#[codec(compact)]
pub core_index: CoreIndex,
#[codec(compact)]
pub item_index: u32,
#[codec(compact)]
pub service_id: ServiceId,
pub payload: &'a WorkPayload,
pub package_hash: &'a WorkPackageHash,
}
#[derive(Debug, Clone, Encode, Decode, MaxEncodedLen)]
pub struct ServiceInfo {
pub code_hash: CodeHash,
pub balance: Balance,
pub threshold: Balance,
pub min_item_gas: UnsignedGas,
pub min_memo_gas: UnsignedGas,
pub bytes: u64,
pub items: u32,
pub deposit_offset: Balance,
pub creation_slot: Slot,
pub last_accumulation_slot: Slot,
pub parent_service: ServiceId,
}
impl ServiceInfo {
pub const CODE_HASH_OFFSET: usize = 0;
pub const BALANCE_OFFSET: usize = 32;
pub const THRESHOLD_OFFSET: usize = 40;
pub const MIN_ITEM_GAS_OFFSET: usize = 48;
pub const MIN_MEMO_GAS_OFFSET: usize = 56;
pub const BYTES_OFFSET: usize = 64;
pub const ITEMS_OFFSET: usize = 72;
pub const DEPOSIT_OFFSET_OFFSET: usize = 76;
pub const CREATION_SLOT_OFFSET: usize = 84;
pub const LAST_ACCUMULATION_SLOT_OFFSET: usize = 88;
pub const PARENT_SERVICE_OFFSET: usize = 92;
pub const ENCODED_LEN: usize = 96;
}
struct CompactRefineResult(Result<WorkOutput, WorkError>);
struct CompactRefineResultRef<'a>(&'a Result<WorkOutput, WorkError>);
impl From<CompactRefineResult> for Result<WorkOutput, WorkError> {
fn from(value: CompactRefineResult) -> Self {
value.0
}
}
impl<'a> From<&'a Result<WorkOutput, WorkError>> for CompactRefineResultRef<'a> {
fn from(value: &'a Result<WorkOutput, WorkError>) -> Self {
CompactRefineResultRef(value)
}
}
impl<'a> codec::EncodeAsRef<'a, Result<WorkOutput, WorkError>> for CompactRefineResult {
type RefType = CompactRefineResultRef<'a>;
}
impl Encode for CompactRefineResult {
fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
CompactRefineResultRef(&self.0).encode_to(dest)
}
}
impl MaxEncodedLen for CompactRefineResult {
fn max_encoded_len() -> usize {
(1 + WorkOutput::max_encoded_len()).max(WorkError::max_encoded_len())
}
}
impl Encode for CompactRefineResultRef<'_> {
fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
match &self.0 {
Ok(o) => {
dest.push_byte(0);
o.encode_to(dest)
},
Err(e) => e.encode_to(dest),
}
}
}
impl Decode for CompactRefineResult {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
match input.read_byte()? {
0 => Ok(Self(Ok(WorkOutput::decode(input)?))),
e => Ok(Self(Err(WorkError::decode(&mut &[e][..])?))),
}
}
}
macro_rules! fetch_kind {
($($(#[$attr:meta])* $variant:ident = $value:expr),* $(,)?) => {
#[derive(Copy, Clone, Debug)]
pub enum FetchKind {
$(
$(#[$attr])*
$variant = $value,
)*
}
impl TryFrom<u64> for FetchKind {
type Error = ();
fn try_from(value: u64) -> Result<Self, Self::Error> {
match value {
$(
$value => Ok(FetchKind::$variant),
)*
_ => Err(()),
}
}
}
};
}
fetch_kind! {
ProtocolParameters = 0,
Entropy = 1,
AuthTrace = 2,
AnyExtrinsic = 3,
OurExtrinsic = 4,
AnyImport = 5,
OurImport = 6,
WorkPackage = 7,
Authorizer = 8,
AuthToken = 9,
RefineContext = 10,
ItemsSummary = 11,
AnyItemSummary = 12,
AnyPayload = 13,
AccumulateItems = 14,
AnyAccumulateItem = 15,
}
#[derive(Clone, Encode, Decode, Debug)]
pub struct WorkItemSummary {
pub service: ServiceId,
pub code_hash: CodeHash,
pub refine_gas_limit: UnsignedGas,
pub accumulate_gas_limit: UnsignedGas,
pub export_count: u16,
pub import_count: u16,
pub extrinsics_count: u16,
pub payload_len: u32,
}
impl From<&WorkItem> for WorkItemSummary {
fn from(w: &WorkItem) -> Self {
WorkItemSummary {
service: w.service,
code_hash: w.code_hash,
refine_gas_limit: w.refine_gas_limit,
accumulate_gas_limit: w.accumulate_gas_limit,
export_count: w.export_count,
import_count: w.import_segments.len() as u16,
extrinsics_count: w.extrinsics.len() as u16,
payload_len: w.payload.len() as u32,
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum PageMode {
ReadOnly = 0,
ReadWrite = 1,
}
#[derive(Copy, Clone, Debug)]
pub enum PageOperation {
Free,
Alloc(PageMode),
SetMode(PageMode),
}
impl From<PageOperation> for u64 {
fn from(value: PageOperation) -> Self {
match value {
PageOperation::Free => 0,
PageOperation::Alloc(mode) => 1 + mode as u64,
PageOperation::SetMode(mode) => 3 + mode as u64,
}
}
}
impl TryFrom<u64> for PageOperation {
type Error = ();
fn try_from(operation: u64) -> Result<Self, Self::Error> {
match operation {
0 => Ok(Self::Free),
1 => Ok(Self::Alloc(PageMode::ReadOnly)),
2 => Ok(Self::Alloc(PageMode::ReadWrite)),
3 => Ok(Self::SetMode(PageMode::ReadOnly)),
4 => Ok(Self::SetMode(PageMode::ReadWrite)),
_ => Err(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use bounded_collections::{bounded_vec, TryCollect};
use codec::DecodeAll;
#[test]
fn compact_refine_result_codec() {
let enc_dec = |exp_res, exp_buf: &[u8]| {
let buf = CompactRefineResultRef(&exp_res).encode();
assert_eq!(buf, exp_buf);
let res = CompactRefineResult::decode(&mut &buf[..]).unwrap();
assert_eq!(res.0, exp_res);
};
enc_dec(Ok(vec![1, 2, 3].into()), &[0, 3, 1, 2, 3]);
enc_dec(Err(WorkError::OutOfGas), &[1]);
enc_dec(Err(WorkError::Panic), &[2]);
enc_dec(Err(WorkError::BadExports), &[3]);
enc_dec(Err(WorkError::OutputOversize), &[4]);
enc_dec(Err(WorkError::BadCode), &[5]);
enc_dec(Err(WorkError::CodeOversize), &[6]);
}
#[test]
fn service_info_encoded_len_is_correct() {
assert_eq!(ServiceInfo::max_encoded_len(), ServiceInfo::ENCODED_LEN);
}
#[test]
fn service_info_item_offset_works() {
let default = ServiceInfo {
code_hash: Default::default(),
balance: Default::default(),
threshold: Default::default(),
min_item_gas: Default::default(),
min_memo_gas: Default::default(),
bytes: Default::default(),
items: Default::default(),
deposit_offset: Default::default(),
creation_slot: Default::default(),
last_accumulation_slot: Default::default(),
parent_service: Default::default(),
};
macro_rules! sanity_check {
($field:ident, $type:ident, $offset:expr, $value:expr) => {{
let type_size = core::mem::size_of::<$type>();
assert_eq!(type_size, $type::max_encoded_len());
let expected = $type::from($value);
let test_struct = ServiceInfo { $field: expected, ..default };
let buffer = test_struct.encode();
let actual: $type =
DecodeAll::decode_all(&mut &buffer[$offset..$offset + type_size]).unwrap();
assert_eq!(expected, actual);
}};
($field:ident, $type:ident, $offset:expr) => {{
sanity_check!($field, $type, $offset, <$type>::MAX);
}};
}
sanity_check!(code_hash, CodeHash, ServiceInfo::CODE_HASH_OFFSET, [0xff_u8; 32]);
sanity_check!(balance, Balance, ServiceInfo::BALANCE_OFFSET);
sanity_check!(threshold, Balance, ServiceInfo::THRESHOLD_OFFSET);
sanity_check!(min_item_gas, UnsignedGas, ServiceInfo::MIN_ITEM_GAS_OFFSET);
sanity_check!(min_memo_gas, UnsignedGas, ServiceInfo::MIN_MEMO_GAS_OFFSET);
sanity_check!(bytes, u64, ServiceInfo::BYTES_OFFSET);
sanity_check!(items, u32, ServiceInfo::ITEMS_OFFSET);
sanity_check!(deposit_offset, Balance, ServiceInfo::DEPOSIT_OFFSET_OFFSET);
sanity_check!(creation_slot, Slot, ServiceInfo::CREATION_SLOT_OFFSET);
sanity_check!(last_accumulation_slot, Slot, ServiceInfo::LAST_ACCUMULATION_SLOT_OFFSET);
sanity_check!(parent_service, ServiceId, ServiceInfo::PARENT_SERVICE_OFFSET);
}
#[test]
fn context_max_encoded_len() {
assert_eq!(RefineContext::largest().encoded_size(), RefineContext::max_encoded_len());
}
#[test]
fn package_max_encoded_len() {
let portion = |total, i, num| (total / num) + if i == 0 { total % num } else { 0 };
let max_inputs = 2 + max_work_items();
let input = |i| vec![0; portion(max_input() as usize, i, max_inputs)];
let imports = |i| {
bounded_vec![
ImportSpec { root: RootIdentifier::Direct(Default::default()), index: u16::MAX };
portion(max_imports() as usize, i, max_work_items())
]
};
let extrinsics = |i| {
bounded_vec![
ExtrinsicSpec { hash: Default::default(), len: u32::MAX };
portion(max_extrinsics() as usize, i, max_work_items())
]
};
let largest_package = WorkPackage {
authorization: input(0).into(),
auth_code_host: ServiceId::MAX,
authorizer: Authorizer { code_hash: Default::default(), config: input(1).into() },
context: RefineContext::largest(),
items: (0..max_work_items())
.map(|i| WorkItem {
service: ServiceId::MAX,
code_hash: Default::default(),
payload: input(2 + i).into(),
refine_gas_limit: UnsignedGas::MAX,
accumulate_gas_limit: UnsignedGas::MAX,
import_segments: imports(i),
extrinsics: extrinsics(i),
export_count: u16::MAX,
})
.try_collect()
.unwrap(),
};
assert_eq!(largest_package.import_count(), max_imports());
assert_eq!(largest_package.extrinsic_count(), max_extrinsics());
let largest_package_size = largest_package.encoded_size();
let max = WorkPackage::max_encoded_len();
assert!(largest_package_size <= max);
assert!((largest_package_size + max_inputs + (2 * max_work_items())) >= max);
}
}