jam-std-common 0.1.29

Common datatypes and utilities for the JAM nodes and tooling
Documentation
use crate::{BytesCursor, Immutable, ImmutableWrap, max_merkle_proof_len};

use super::{
	Extrinsic, Header,
	extrinsic::{AssurancesXt, DisputesXt, GuaranteesXt, TicketsXt},
};
use codec::{Compact, Decode, Encode, MaxEncodedLen};
use jam_types::{
	HeaderHash, MAX_PREIMAGE_LEN, MAX_SEGMENT_SLICE_LEN, SEGMENT_LEN, Slot, VALS_PER_CORE,
	WorkPackage, WorkPackageHash, max_export_segments, max_exports, max_imports, opaque,
	segment_len, slot_period_sec,
};
use std::{sync::Arc, time::Duration};

/// Duration of a time slot, as a Rust-native `Duration`.
pub fn slot_duration() -> Duration {
	Duration::from_secs(slot_period_sec())
}

/// Maximum size of the hashes in the proof of one imported segment, without the length prefix:
/// one hash for each level of the segment tree of the exporting package, which has at most
/// `max_exports()` leaves.
fn max_import_proof_hashes_size() -> u32 {
	32 * max_merkle_proof_len(max_exports())
}

/// (W_F) Number of octets one imported segment counts in the work-bundle size: the segment and its
/// justification.
pub fn max_import_footprint_in_bundle() -> u32 {
	SEGMENT_LEN as u32 + max_import_proof_hashes_size()
}

/// Maximum encoded size of the proof of one imported segment, with its one-octet length prefix.
pub fn max_import_proof_size() -> usize {
	max_import_proof_hashes_size() as usize + 1
}

/// Availability store data expiration in slots (28 days)
pub fn availability_data_expiration() -> Slot {
	28 * 24 * 60 * 60 / slot_period_sec() as Slot
}

/// The conservative maximum size of a Work Package Bundle.
pub fn max_bundle_size() -> usize {
	// Note that WorkPackage::max_encoded_len() includes max_input() so we don't need to explicitly
	// include it here. max_input() also covers the imported segments and their justifications, so
	// only the one-octet length prefix of each justification is added. This assumes a somewhat
	// naive WorkPackage::max_encoded_len implementation, that doesn't eg account for the fact that
	// if there are many import specs, some of the max_input() allowance will be used up by the
	// imported segments. If WorkPackage::max_encoded_len is made less conservative, this function
	// may need to be updated.
	WorkPackage::max_encoded_len() + max_imports() as usize
}

/// Conservative upper bound on the encoded size of a `Block`.
///
/// Sum of `MaxEncodedLen` of each bounded `Extrinsic` field plus a fixed cap on the total
/// preimage data per block (`MAX_PREIMAGE_LEN`).
pub fn max_block_size() -> usize {
	Header::max_encoded_len() +
		TicketsXt::max_encoded_len() +
		AssurancesXt::max_encoded_len() +
		GuaranteesXt::max_encoded_len() +
		DisputesXt::max_encoded_len() +
		MAX_PREIMAGE_LEN +
		Compact::<u32>::max_encoded_len() // Length prefix for Vec<Preimage>
}

/// Maximum basic size of a Work Package that we accept.
/// This should be at least ImportSpec::encoded_fixed_size() * max_imports()
pub const SANE_MAX_PACKAGE_SIZE: usize = 200 * 1024;

/// The min. no. of signatures required for a guarantee to be valid.
///
/// Equals 2/3 of guarantors assigned to a core.
pub const GUARANTEE_MIN_SIGNATURES: usize = (VALS_PER_CORE * 2).div_ceil(3);

// Entropy required by some protocol's operations.
opaque! { pub struct Entropy(pub [u8; 32]); }

pub type ImmutableHeader = Immutable<Header, HeaderHash>;

#[derive(Clone, Debug, Encode, Decode)]
pub struct Block {
	pub header: Arc<ImmutableHeader>,
	pub extrinsic: Arc<Extrinsic>,
}

/// Type to represent the index of a tranche.
pub type TrancheIndex = u8;

/// Maximum size in bytes for a work package exported segments
pub fn max_segment_vec_bytes() -> u32 {
	segment_len() as u32 * max_export_segments()
}

/// One validator's slices of a work package's segments: one slice per segment, all of the
/// same length.
///
/// Erasure-coding `n` segments for `v` validators yields a `v x n` matrix of slices; this
/// type is one row. Segment `j` is reconstructed from the `j`-th slice of any `D(v)` rows:
///
/// ```text
///                  seg 0   seg 1   ...   seg n-1
/// validator 0      slice   slice         slice     <- one SegmentSliceVec
/// ...
/// validator v-1    slice   slice         slice
/// ```
///
/// The row is stored as `data`, the concatenation of the validator's slices in segment
/// order; `slice_len` (`SEGMENT_LEN / D(v)`, a property of the erasure coding rather than a
/// protocol constant) gives the slice boundaries within it. The slice count is bound by the
/// maximum number of segments a work package may export.
///
/// Every constructor takes the slice length, so `slice_len` is always in
/// `[1, MAX_SEGMENT_SLICE_LEN]`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SegmentSliceVec {
	data: Vec<u8>,
	slice_len: usize,
}

impl SegmentSliceVec {
	/// Get the raw byte buffer: all slices concatenated in segment order.
	pub fn raw(&self) -> &[u8] {
		&self.data
	}

	/// Get the underlying `Vec`.
	pub fn into_inner(self) -> Vec<u8> {
		self.data
	}

	/// Returns the length of each contained slice.
	pub fn slice_len(&self) -> usize {
		self.slice_len
	}

	/// Creates a vec of `slice_len`-sized slices from serialized bytes.
	/// Returns `None` if the size is incorrect.
	pub fn from_bytes(data: Vec<u8>, slice_len: usize) -> Option<Self> {
		if slice_len == 0 || slice_len > MAX_SEGMENT_SLICE_LEN {
			return None
		}
		if !data.len().is_multiple_of(slice_len) {
			return None
		}
		if data.len() / slice_len > max_export_segments() as usize {
			return None
		}
		Some(Self { data, slice_len })
	}

	/// Preallocates a vec for the given number of `slice_len`-sized slices.
	pub fn with_capacity(capacity: usize, slice_len: usize) -> Self {
		assert!(slice_len > 0 && slice_len <= MAX_SEGMENT_SLICE_LEN);
		Self { data: Vec::with_capacity(capacity * slice_len), slice_len }
	}

	/// Returns the number of slices.
	pub fn len(&self) -> usize {
		self.data.len() / self.slice_len
	}

	/// Check if the vec is empty.
	pub fn is_empty(&self) -> bool {
		self.data.is_empty()
	}

	/// Appends a slice to this vec. Returns an error if the slice size is incorrect or the
	/// vec overflows.
	pub fn push(&mut self, slice: &[u8]) -> Result<(), &'static str> {
		if slice.len() != self.slice_len {
			return Err("Slice length does not match segment slice length")
		}
		if self.len() == max_export_segments() as usize {
			return Err("Exceeded maximum segment slice vector size")
		}
		self.data.extend_from_slice(slice);
		Ok(())
	}

	/// Adds a new empty slice to the vec and returns a reference to it. Produces an error if the
	/// vec overflows.
	pub fn push_default(&mut self) -> Result<&mut [u8], &'static str> {
		if self.len() == max_export_segments() as usize {
			return Err("Exceeded maximum segment slice vector size")
		}
		let len = self.data.len();
		self.data.resize(len + self.slice_len, 0u8);
		Ok(&mut self.data[len..])
	}
}

impl std::ops::Index<usize> for SegmentSliceVec {
	type Output = [u8];
	fn index(&self, index: usize) -> &Self::Output {
		&self.data[index * self.slice_len..(index + 1) * self.slice_len]
	}
}

pub type ImmutableWorkPackage =
	Immutable<jam_types::WrappedWorkPackage<ImmutableWrap>, WorkPackageHash>;

impl From<&jam_types::WorkPackage> for ImmutableWorkPackage {
	fn from(package: &jam_types::WorkPackage) -> Self {
		// Convert by encoding and then decoding. This is simple and ensures all Bytes instances
		// reference the same buffer. This should only be used by test code so efficiency does not
		// particularly matter.
		BytesCursor::from(package.encode())
			.decode_all()
			.expect("Should be able to decode result of T::encode()")
	}
}

impl From<jam_types::WorkPackage> for ImmutableWorkPackage {
	fn from(package: jam_types::WorkPackage) -> Self {
		(&package).into()
	}
}