use crate::{
hash_encoded, header::OffendersMark, AvailabilityAssurance, EpochIndex, ErasureRoot,
TicketEnvelope,
};
use bounded_collections::ConstU32;
use codec::{Compact, CompactLen, ConstEncodedLen, Decode, Encode, MaxEncodedLen};
use jam_types::{
max_report_elective_data, max_work_items, val_count, AuthTrace, AuthorizerHash, BoundedVec,
CoreCount, CoreIndex, ExtrinsicHash, FixedVec, MaxDependencies, MaxTicketsPerBlock,
MaxWorkItems, RefineContext, SegmentTreeRoot, ServiceId, Slot, UnsignedGas, ValIndex,
ValSuperMajority, VecMap, WorkDigest, WorkPackageHash, WorkReportHash, VALS_PER_CORE,
};
pub const MAX_VERDICTS_COUNT: usize = 16;
pub const MAX_OFFENSES_COUNT: usize = 16;
pub type PreimagesXt = Vec<Preimage>;
pub type TicketsXt = BoundedVec<TicketEnvelope, MaxTicketsPerBlock>;
pub type AssurancesXt = BoundedVec<AvailabilityAssurance, jam_types::ValCount>;
pub type GuaranteesXt = BoundedVec<ReportGuarantee, CoreCount>;
#[derive(Clone, Encode, Decode, Debug, Default)]
pub struct DisputesXt {
pub verdicts: BoundedVec<Verdict, ConstU32<{ MAX_VERDICTS_COUNT as u32 }>>,
pub culprits: BoundedVec<CulpritProof, ConstU32<{ MAX_OFFENSES_COUNT as u32 }>>,
pub faults: BoundedVec<FaultProof, ConstU32<{ MAX_OFFENSES_COUNT as u32 }>>,
}
impl DisputesXt {
pub fn offenders_mark(&self) -> OffendersMark {
let offenders: Vec<_> = self
.culprits
.iter()
.map(|v| v.key)
.chain(self.faults.iter().map(|v| v.key))
.collect();
offenders.try_into().expect("OffendersMark bounds equal culprits + faults")
}
pub fn implies_offenders_mark(&self, offenders_mark: &OffendersMark) -> bool {
self.culprits
.iter()
.map(|v| v.key)
.chain(self.faults.iter().map(|v| v.key))
.zip(offenders_mark.iter())
.all(|(a, b)| a == *b)
}
}
pub type VerdictVotes = FixedVec<Judgement, ValSuperMajority>;
#[derive(Clone, Encode, Decode, Debug)]
pub struct Verdict {
pub target: WorkReportHash,
pub age: EpochIndex,
pub votes: VerdictVotes,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VerdictKind {
Good,
Bad,
Wonky,
}
impl Verdict {
pub fn kind(&self) -> Result<VerdictKind, ()> {
let valid_count = self.votes.iter().filter(|v| v.vote).count();
if valid_count == self.votes.len() {
Ok(VerdictKind::Good)
} else if valid_count == 0 {
Ok(VerdictKind::Bad)
} else if valid_count as u16 == val_count() / 3 {
Ok(VerdictKind::Wonky)
} else {
Err(())
}
}
}
#[derive(Clone, Encode, Decode, Debug)]
pub struct Judgement {
pub vote: bool,
pub index: ValIndex,
pub signature: super::ed25519::Signature,
}
#[derive(Clone, Encode, Decode, Debug)]
pub struct CulpritProof {
pub report_hash: WorkReportHash,
pub key: super::ed25519::Public,
pub signature: super::ed25519::Signature,
}
#[derive(Clone, Encode, Decode, Debug)]
pub struct FaultProof {
pub report_hash: WorkReportHash,
pub vote: bool,
pub key: super::ed25519::Public,
pub signature: super::ed25519::Signature,
}
#[derive(Clone, Encode, Decode, Debug, Default)]
pub struct Extrinsic {
pub tickets: TicketsXt,
pub preimages: PreimagesXt,
pub guarantees: GuaranteesXt,
pub assurances: AssurancesXt,
pub disputes: DisputesXt,
}
impl Extrinsic {
pub fn guarantees_prehashed(&self) -> Vec<(WorkReportHash, Slot, &GuaranteeSignatures)> {
self.guarantees
.iter()
.map(|r| (r.report.hash(), r.slot, &r.signatures))
.collect()
}
pub fn hash(&self) -> ExtrinsicHash {
let tickets_hash = hash_encoded(&self.tickets);
let disputes_hash = hash_encoded(&self.disputes);
let preimages_hash = hash_encoded(&self.preimages);
let assurances_hash = hash_encoded(&self.assurances);
let guarantees_hash = hash_encoded(&self.guarantees_prehashed());
let top = (tickets_hash, preimages_hash, guarantees_hash, assurances_hash, disputes_hash);
hash_encoded(&top).into()
}
}
#[derive(Clone, Encode, Decode, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Preimage {
pub requester: ServiceId,
pub blob: Vec<u8>,
}
impl Preimage {
pub fn new(requester: ServiceId, blob: impl Into<Vec<u8>>) -> Self {
Self { requester, blob: blob.into() }
}
pub fn encoded_len(blob_len: usize) -> usize {
core::mem::size_of::<ServiceId>() +
Compact::<u64>::compact_len(&(blob_len as u64)) +
blob_len
}
}
#[derive(Clone, Encode, Decode, Debug, MaxEncodedLen)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct ReportGuarantee {
pub report: WorkReport,
pub slot: Slot,
pub signatures: GuaranteeSignatures,
}
pub type GuaranteeSignatures = BoundedVec<ValSignature, ConstU32<{ VALS_PER_CORE as u32 }>>;
#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen)]
pub struct ValSignature {
pub val_index: ValIndex,
pub signature: super::ed25519::Signature,
}
impl ConstEncodedLen for ValSignature {}
#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen)]
pub struct WorkPackageSpec {
pub hash: WorkPackageHash,
pub len: u32,
pub erasure_root: ErasureRoot,
pub exports_root: SegmentTreeRoot,
pub exports_count: u16,
}
impl WorkPackageSpec {
pub fn wp_srl(&self) -> (WorkPackageHash, SegmentTreeRoot) {
(self.hash, self.exports_root)
}
}
#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
pub struct WorkReport {
pub package_spec: WorkPackageSpec,
pub context: RefineContext,
#[codec(compact)]
pub core_index: CoreIndex,
pub authorizer_hash: AuthorizerHash,
#[codec(compact)]
pub auth_gas_used: UnsignedGas,
pub auth_output: AuthTrace,
pub sr_lookup: VecMap<WorkPackageHash, SegmentTreeRoot>,
pub results: BoundedVec<WorkDigest, MaxWorkItems>,
}
impl WorkReport {
pub fn hash(&self) -> WorkReportHash {
hash_encoded(self).into()
}
pub fn gas(&self) -> UnsignedGas {
self.results.iter().map(|r| r.accumulate_gas).sum()
}
pub fn deps(&self) -> impl Iterator<Item = &WorkPackageHash> {
self.sr_lookup.keys().chain(self.context.prerequisites.iter())
}
pub fn dep_count(&self) -> usize {
self.sr_lookup.len() + self.context.prerequisites.len()
}
pub fn check_size(&self) -> bool {
let total: usize =
self.results.iter().filter_map(|r| Some(r.result.as_ref().ok()?.len())).sum();
total + self.auth_output.len() <= max_report_elective_data() as usize
}
pub fn package_hash(&self) -> WorkPackageHash {
self.package_spec.hash
}
}
impl MaxEncodedLen for WorkReport {
fn max_encoded_len() -> usize {
let mut max = WorkPackageSpec::max_encoded_len() +
RefineContext::max_encoded_len() +
codec::Compact::<CoreIndex>::max_encoded_len() +
AuthorizerHash::max_encoded_len() +
AuthTrace::max_encoded_len() +
BoundedVec::<(WorkPackageHash, SegmentTreeRoot), MaxDependencies>::max_encoded_len() + BoundedVec::<WorkDigest, MaxWorkItems>::max_encoded_len() +
codec::Compact::<UnsignedGas>::max_encoded_len();
max -= max_work_items() * (max_report_elective_data() as usize);
max
}
}
#[cfg(test)]
mod tests {
use super::*;
use bounded_collections::TryCollect;
use jam_types::{max_dependencies, RefineLoad, MAX_PREIMAGE_BLOB_LEN, MAX_PREIMAGE_LEN};
#[test]
fn max_preimage_blob_len_is_correct() {
let preimage =
Preimage { requester: ServiceId::MAX, blob: vec![123_u8; MAX_PREIMAGE_BLOB_LEN] };
let encoded_len = preimage.encode().len();
assert_eq!(MAX_PREIMAGE_LEN, encoded_len);
}
#[test]
fn preimage_encoded_len_works() {
for blob_len in (0..MAX_PREIMAGE_BLOB_LEN).step_by(997) {
let preimage = Preimage { requester: ServiceId::MAX, blob: vec![123_u8; blob_len] };
let expected_len = Preimage::encoded_len(blob_len);
let actual_len = preimage.encode().len();
assert_eq!(expected_len, actual_len);
}
}
#[test]
fn report_max_encoded_len() {
let largest_spec = WorkPackageSpec {
hash: Default::default(),
len: u32::MAX,
erasure_root: Default::default(),
exports_root: Default::default(),
exports_count: u16::MAX,
};
assert_eq!(largest_spec.encoded_size(), WorkPackageSpec::max_encoded_len());
let max_outputs = 1 + max_work_items(); let output = |i| {
let mut size = (max_report_elective_data() as usize) / max_outputs;
if i == 0 {
size += (max_report_elective_data() as usize) % max_outputs;
}
vec![0; size]
};
let largest_report = WorkReport {
package_spec: largest_spec,
context: RefineContext::largest(),
core_index: CoreIndex::MAX,
authorizer_hash: Default::default(),
auth_output: output(0).into(),
sr_lookup: (0..max_dependencies())
.map(|i| ([i as u8; 32].into(), Default::default()))
.collect(),
results: (0..max_work_items())
.map(|i| WorkDigest {
service: ServiceId::MAX,
code_hash: Default::default(),
payload_hash: Default::default(),
accumulate_gas: UnsignedGas::MAX,
result: Ok(output(1 + i).into()),
refine_load: RefineLoad {
gas_used: UnsignedGas::MAX,
imports: u16::MAX,
extrinsic_count: u16::MAX,
extrinsic_size: u32::MAX,
exports: u16::MAX,
},
})
.try_collect()
.unwrap(),
auth_gas_used: UnsignedGas::MAX,
};
let largest_report_size = largest_report.encoded_size();
let max = WorkReport::max_encoded_len();
assert!(largest_report_size <= max);
assert!((largest_report_size + max_outputs) >= max);
}
}